Pin sceneButtons folds recipe classes to the installed folds #111
2 changed files with 40 additions and 10 deletions
|
|
@ -2,9 +2,14 @@ import { globalStyle } from '@vanilla-extract/css';
|
|||
|
||||
// Meta, 2026-11-10: "buttons are big and ugly, too, they don't need chrome either". Over the scene a folds Button or
|
||||
// Chip is its label in the page's ink, with a faint wash on hover and focus. Menus and dialogs (#portalContainer) keep
|
||||
// folds' look. The selectors are folds' hashed recipe base classes (Button epr39zd, Chip _1bv8u1w6).
|
||||
// folds' look. The selectors are folds' hashed recipe base classes (Button, Chip; pinned below).
|
||||
// sceneButtons.test.ts resolves both from the installed folds and fails when a folds upgrade rehashes them.
|
||||
export const FOLDS_BUTTON_CLASS = 'epr39zd';
|
||||
export const FOLDS_CHIP_CLASS = '_1bv8u1w6';
|
||||
const scene = ':root[data-scene] #root';
|
||||
const controls = [`${scene} .epr39zd`, `${scene} ._1bv8u1w6`].join(', ');
|
||||
const btn = `${scene} .${FOLDS_BUTTON_CLASS}`;
|
||||
const chip = `${scene} .${FOLDS_CHIP_CLASS}`;
|
||||
const controls = [btn, chip].join(', ');
|
||||
globalStyle(controls, {
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: 'none',
|
||||
|
|
@ -12,20 +17,15 @@ globalStyle(controls, {
|
|||
color: 'inherit',
|
||||
});
|
||||
globalStyle(
|
||||
[
|
||||
`${scene} .epr39zd:hover`,
|
||||
`${scene} .epr39zd:focus-visible`,
|
||||
`${scene} ._1bv8u1w6:hover`,
|
||||
`${scene} ._1bv8u1w6:focus-visible`,
|
||||
].join(', '),
|
||||
[`${btn}:hover`, `${btn}:focus-visible`, `${chip}:hover`, `${chip}:focus-visible`].join(', '),
|
||||
{ backgroundColor: 'color-mix(in srgb, currentColor 12%, transparent)' }
|
||||
);
|
||||
// A visible keyboard focus ring in the control's own ink, on top of the hover wash.
|
||||
globalStyle([`${scene} .epr39zd:focus-visible`, `${scene} ._1bv8u1w6:focus-visible`].join(', '), {
|
||||
globalStyle([`${btn}:focus-visible`, `${chip}:focus-visible`].join(', '), {
|
||||
outline: '2px solid currentColor',
|
||||
outlineOffset: '2px',
|
||||
});
|
||||
globalStyle(`${scene} .epr39zd`, {
|
||||
globalStyle(btn, {
|
||||
fontWeight: 700,
|
||||
textDecoration: 'underline',
|
||||
textUnderlineOffset: '0.25em',
|
||||
|
|
|
|||
30
src/app/features/scene/sceneButtons.test.ts
Normal file
30
src/app/features/scene/sceneButtons.test.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { createRequire } from 'module';
|
||||
import { dirname, join } from 'path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
// sceneButtons.css.ts restyles folds' Button and Chip by their hashed recipe base classes. A folds upgrade rehashes
|
||||
// them and the chromeless buttons would silently revert, so resolve the classes from the installed folds.
|
||||
const foldsDir = dirname(createRequire(import.meta.url).resolve('folds/package.json'));
|
||||
const dist = readFileSync(join(foldsDir, 'dist/index.js'), 'utf8');
|
||||
const css = readFileSync(join(foldsDir, 'dist/style.css'), 'utf8');
|
||||
const sceneButtons = readFileSync(
|
||||
`${process.cwd()}/src/app/features/scene/sceneButtons.css.ts`,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const recipeBase = (name: string) =>
|
||||
new RegExp(`\\b${name}\\$1 = createRuntimeFn\\(\\{ defaultClassName: "([^" ]+)`).exec(dist)?.[1];
|
||||
const pinned = (name: string) => new RegExp(`${name} = '([^']+)'`).exec(sceneButtons)?.[1];
|
||||
|
||||
describe('sceneButtons pins folds recipe base classes', () => {
|
||||
it.each([
|
||||
['Button', 'FOLDS_BUTTON_CLASS'],
|
||||
['Chip', 'FOLDS_CHIP_CLASS'],
|
||||
])('%s base class matches the installed folds', (recipe, constant) => {
|
||||
const base = recipeBase(recipe);
|
||||
expect(base, `folds no longer builds ${recipe} with createRuntimeFn`).toBeTruthy();
|
||||
expect(pinned(constant)).toBe(base);
|
||||
expect(css).toContain(`.${base} {`);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue