diff --git a/apps/docs/src/components/color-mode-override.browser.test.tsx b/apps/docs/src/components/color-mode-override.browser.test.tsx
index 27f6789c..92813acd 100644
--- a/apps/docs/src/components/color-mode-override.browser.test.tsx
+++ b/apps/docs/src/components/color-mode-override.browser.test.tsx
@@ -27,36 +27,21 @@ function renderColourModeExample() {
});
}
-function panelStyle(panel: HTMLElement): { backgroundColor: string; color: string } {
- const style = getComputedStyle(panel);
- return { backgroundColor: style.backgroundColor, color: style.color };
-}
+test('toggles the parent colour mode and leaves the nested panel fixed to dark', async () => {
+ renderColourModeExample();
+
+ const parent = container?.firstElementChild;
+ if (!(parent instanceof HTMLElement)) throw new Error('Expected the colour-mode example root');
+ expect(parent).toHaveAttribute('data-color-mode', 'light');
+
+ const fixedPanel = page
+ .getByText('This panel is fixed to dark mode.')
+ .element()
+ .closest('[data-color-mode]');
+ expect(fixedPanel).toHaveAttribute('data-color-mode', 'dark');
-test(
- 're-colours the parent-following panel when the parent mode changes and leaves the fixed nested' +
- ' panel in its explicit mode',
- async () => {
- renderColourModeExample();
-
- const followingPanel = page
- .getByText('This panel follows the parent mode.')
- .element().parentElement;
- const fixedPanel = page.getByText('This panel is fixed to dark mode.').element().parentElement;
- if (!followingPanel || !fixedPanel) {
- throw new Error('Expected the colour-mode panels rendered');
- }
-
- const lightFollowing = panelStyle(followingPanel);
- const darkFixed = panelStyle(fixedPanel);
-
- expect(lightFollowing.backgroundColor).not.toBe(darkFixed.backgroundColor);
- expect(lightFollowing.color).not.toBe(darkFixed.color);
-
- await userEvent.click(page.getByRole('button', { name: 'Dark' }));
-
- await expect.poll(() => panelStyle(followingPanel)).toEqual(darkFixed);
- expect(panelStyle(followingPanel).backgroundColor).not.toBe(lightFollowing.backgroundColor);
- expect(panelStyle(followingPanel).color).not.toBe(lightFollowing.color);
- expect(panelStyle(fixedPanel)).toEqual(darkFixed);
- },
-);
+ await userEvent.click(page.getByRole('button', { name: 'Dark' }));
+
+ await expect.poll(() => parent.getAttribute('data-color-mode')).toBe('dark');
+ expect(fixedPanel).toHaveAttribute('data-color-mode', 'dark');
+});
diff --git a/apps/docs/src/components/playground/editor-skeleton.browser.test.tsx b/apps/docs/src/components/playground/editor-skeleton.browser.test.tsx
deleted file mode 100644
index b618c2d5..00000000
--- a/apps/docs/src/components/playground/editor-skeleton.browser.test.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import '../../styles/app.css';
-import '@luke-ui/react/themes/tactile/stylesheet.css';
-import { themeClassName as tactileThemeClassName } from '@luke-ui/react/themes/tactile';
-import { act } from 'react';
-import type { Root } from 'react-dom/client';
-import { createRoot } from 'react-dom/client';
-import { afterEach, expect, test } from 'vite-plus/test';
-import { page } from 'vite-plus/test/context';
-import { EditorSkeleton } from './editor-skeleton';
-
-let container: HTMLElement | undefined;
-let root: Root | undefined;
-
-afterEach(() => {
- if (root) act(() => root?.unmount());
- container?.remove();
- container = undefined;
- root = undefined;
-});
-
-test('uses an opaque popover surface behind its loading pill', () => {
- renderSkeleton(tactileThemeClassName, 'light');
-
- const label = page.getByText('Loading editor', { exact: true }).nth(1).element();
- const pillBackground = getComputedStyle(label.parentElement as HTMLElement).backgroundColor;
- const expectedBackground = getSemanticSurfaceBackground();
-
- expect(pillBackground).toBe(expectedBackground);
- expect(pillBackground).not.toBe('rgba(0, 0, 0, 0)');
-});
-
-function renderSkeleton(themeClassName: string, mode: 'light' | 'dark') {
- container = document.body.appendChild(document.createElement('div'));
- container.className = `luke-ui-theme ${themeClassName}`;
- container.dataset.colorMode = mode;
- container.classList.toggle('dark', mode === 'dark');
- root = createRoot(container);
- act(() => {
- root?.render();
- });
-}
-
-function getSemanticSurfaceBackground() {
- if (!container) throw new Error('Expected a theme root.');
-
- const surface = container.appendChild(document.createElement('div'));
- surface.style.backgroundColor = 'var(--color-fd-popover)';
- const background = getComputedStyle(surface).backgroundColor;
- surface.remove();
- return background;
-}
diff --git a/apps/docs/src/components/playground/playground-layout.browser.test.ts b/apps/docs/src/components/playground/playground-layout.browser.test.ts
index d777c27b..60e6b1ae 100644
--- a/apps/docs/src/components/playground/playground-layout.browser.test.ts
+++ b/apps/docs/src/components/playground/playground-layout.browser.test.ts
@@ -12,20 +12,16 @@ afterEach(async () => {
test('uses the desktop panel layout before JavaScript corrects the server orientation', async () => {
await page.viewport(1024, 800);
- const separator = renderPanelLayout('column');
+ renderPanelLayout('column');
expect(getComputedStyle(group as HTMLDivElement).flexDirection).toBe('row');
- expect(getComputedStyle(separator).inlineSize).toBe('1px');
- expect(getComputedStyle(separator).blockSize).toBe('400px');
});
test('uses the mobile panel layout independently of the JavaScript orientation', async () => {
await page.viewport(390, 800);
- const separator = renderPanelLayout('row');
+ renderPanelLayout('row');
expect(getComputedStyle(group as HTMLDivElement).flexDirection).toBe('column');
- expect(getComputedStyle(separator).inlineSize).toBe('390px');
- expect(getComputedStyle(separator).blockSize).toBe('1px');
});
function renderPanelLayout(inlineDirection: 'column' | 'row') {
diff --git a/apps/docs/src/components/theme-controls.browser.test.tsx b/apps/docs/src/components/theme-controls.browser.test.tsx
index 0251b1d3..7c71fbed 100644
--- a/apps/docs/src/components/theme-controls.browser.test.tsx
+++ b/apps/docs/src/components/theme-controls.browser.test.tsx
@@ -76,7 +76,7 @@ test('system colour mode follows the platform preference and drives the docs chr
expect(document.documentElement).toHaveClass('light');
});
-test('bridges dark mode into the Luke UI root and example canvas', async () => {
+test('bridges dark mode into the Luke UI root', async () => {
renderTheme(
<>
@@ -87,15 +87,9 @@ test('bridges dark mode into the Luke UI root and example canvas', async () => {
);
const themeRoot = getThemeRoot();
- const exampleContent = page.getByText('Example content').element();
- const exampleCanvas = exampleContent.parentElement;
- if (!exampleCanvas) throw new Error('Expected an example canvas');
-
- const lightBackground = getComputedStyle(exampleCanvas).backgroundColor;
await userEvent.click(page.getByRole('radio', { name: 'Dark theme' }), { force: true });
await expect.poll(() => themeRoot.dataset.colorMode).toBe('dark');
- expect(getComputedStyle(exampleCanvas).backgroundColor).not.toBe(lightBackground);
});
test('leaves full-bleed story surfaces unframed', () => {
@@ -109,28 +103,7 @@ test('leaves full-bleed story surfaces unframed', () => {
const storyRoot = exampleContent.parentElement;
if (!storyRoot) throw new Error('Expected a full-bleed story root');
- const storyRootStyle = getComputedStyle(storyRoot);
- expect(storyRootStyle.padding).toBe('0px');
- expect(storyRootStyle.backgroundColor).toBe('rgba(0, 0, 0, 0)');
-});
-
-test('keeps inherited docs shell text readable in dark mode', async () => {
- renderTheme(
- <>
-
- Unstyled shell text
-
- >,
- );
-
- await userEvent.click(page.getByRole('radio', { name: 'Dark theme' }), { force: true });
- await expect.poll(() => getThemeRoot().dataset.colorMode).toBe('dark');
-
- const shellText = page.getByText('Unstyled shell text').element();
- const foregroundProbe = container?.querySelector('[data-foreground-probe]');
- if (!foregroundProbe) throw new Error('Expected a docs foreground probe');
-
- expect(getComputedStyle(shellText).color).toBe(getComputedStyle(foregroundProbe).color);
+ expect(getComputedStyle(storyRoot).padding).toBe('0px');
});
test('boots the stored colour mode before the themed root hydrates', async () => {
diff --git a/apps/docs/src/lib/component-page-navigation.test.ts b/apps/docs/src/lib/component-page-navigation.test.ts
index 78407532..4d6fb099 100644
--- a/apps/docs/src/lib/component-page-navigation.test.ts
+++ b/apps/docs/src/lib/component-page-navigation.test.ts
@@ -23,16 +23,10 @@ test('does not add component navigation to other docs pages', () => {
expect(getComponentPageNavigation('/components/actions/button/examples')).toBeNull();
});
-test('maps a Props pathname to its Guide pathname for tree matching', () => {
+test('maps pathnames for docs tree matching', () => {
expect(getDocsTreePathname('/components/actions/button/props')).toBe(
'/components/actions/button',
);
-});
-
-test('leaves a Guide pathname unchanged for tree matching', () => {
expect(getDocsTreePathname('/components/actions/button')).toBe('/components/actions/button');
-});
-
-test('leaves other docs pathnames unchanged for tree matching', () => {
expect(getDocsTreePathname('/docs/installation')).toBe('/docs/installation');
});
diff --git a/apps/docs/src/lib/components-index-generator.test.ts b/apps/docs/src/lib/components-index-generator.test.ts
index adf8c563..c6a68dbd 100644
--- a/apps/docs/src/lib/components-index-generator.test.ts
+++ b/apps/docs/src/lib/components-index-generator.test.ts
@@ -8,12 +8,10 @@ import { componentIndexGroups } from '../generated/components-index.generated.js
test('generates one runtime entry per component guide', () => {
const reference = generateComponentsIndex();
- expect(reference).toContain(
- "{ description: 'A labelled control for actions in an interface.', name: 'Button', url: '/components/actions/button' },",
- );
- expect(reference).toContain(
- "{ description: 'Hide content visually and keep it available to assistive technology.', name: 'Visually Hidden', url: '/components/primitives/visually-hidden' },",
- );
+ expect(reference).toContain("name: 'Button'");
+ expect(reference).toContain("url: '/components/actions/button'");
+ expect(reference).toContain("name: 'Visually Hidden'");
+ expect(reference).toContain("url: '/components/primitives/visually-hidden'");
expect(reference).toContain(
'export const componentIndexGroups: ReadonlyArray = [',
);
diff --git a/docs/THEME_COLOUR_GENERATION.md b/docs/THEME_COLOUR_GENERATION.md
index 703a93ac..63452780 100644
--- a/docs/THEME_COLOUR_GENERATION.md
+++ b/docs/THEME_COLOUR_GENERATION.md
@@ -37,24 +37,18 @@ independently, so **every generated colour value can move**, even where the prev
looked visually similar. This is a property of switching generators, not a regression in any one
token.
-The v2 pipeline output is pinned in a regression golden:
-
-- `theme/__fixtures__/v2-goldens/*.v2.css` — the exact `buildTheme` output under the v2 pipeline,
- asserted byte-identical in `build-theme.test.ts` so any later generator change is a reviewed,
- deliberate diff.
-
-The pre-v2 output is recoverable from git history (commit `cef0076`) and no longer committed to the
-repo. Re-generate the v2 goldens deliberately, reviewing the diff, when a later engine change
-intentionally repaints again — never to silence a failing test.
+Theme output is protected by the contrast, scale, semantic-map, and stylesheet contract unit tests,
+plus the token-board visual captures across bundled themes and modes. A generator change that
+repaints colours should show up there and in visual review. The pre-v2 output is recoverable from
+git history (commit `cef0076`) and is not committed.
## Private ≠ non-breaking
A change to the private generator (`scale.ts`, `elevation.ts`, `semantic-map.ts`) repaints every
-generated colour, even though none of it is public API. The committed goldens above are the
-before/after safety net for that: a generator change is expected to move the v2 goldens, and that
-diff is the review artefact. Generator **versioning** (a version stamp threaded into the stylesheet
-header) is a deliberately deferred v1 concern — the stylesheet header stays the plain
-`/* Generated by buildTheme from @luke-ui/react. Do not edit. */` line for now.
+generated colour, even though none of it is public API. Review those unit tests and the token-board
+visuals when a generator change intentionally repaints. Generator **versioning** (a version stamp
+threaded into the stylesheet header) is a deliberately deferred v1 concern — the stylesheet header
+stays the plain `/* Generated by buildTheme from @luke-ui/react. Do not edit. */` line for now.
## The dropped `resting` colour surface
diff --git a/packages/@luke-ui/react/scripts/check-story-plays.ts b/packages/@luke-ui/react/scripts/check-story-plays.ts
index 53934b20..03b3d0f3 100644
--- a/packages/@luke-ui/react/scripts/check-story-plays.ts
+++ b/packages/@luke-ui/react/scripts/check-story-plays.ts
@@ -127,10 +127,10 @@ function storyFiles(directory: string): Array {
return files;
}
-const files = storyFiles(storiesRoot).flatMap((file) => {
- if (path.relative(storiesRoot, file).startsWith(`theme${path.sep}`)) return [];
- return [{ file, source: fs.readFileSync(file, 'utf8') }];
-});
+const files = storyFiles(storiesRoot).map((file) => ({
+ file,
+ source: fs.readFileSync(file, 'utf8'),
+}));
const violations = findStoryPlayViolations(files);
if (violations.length > 0) {
diff --git a/packages/@luke-ui/react/src/button/button.visual.test.tsx b/packages/@luke-ui/react/src/button/button.visual.test.tsx
index 53909fdb..20238401 100644
--- a/packages/@luke-ui/react/src/button/button.visual.test.tsx
+++ b/packages/@luke-ui/react/src/button/button.visual.test.tsx
@@ -65,7 +65,6 @@ test('interactive states', async () => {
const { locator } = render();
const button = page.getByRole('button', { name: 'Action' });
- await captureVisual(locator, 'button/resting');
await userEvent.hover(button);
await captureVisual(locator, 'button/hover');
await userEvent.unhover(button);
diff --git a/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx b/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx
index 3008c836..68ce4633 100644
--- a/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx
+++ b/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx
@@ -254,42 +254,35 @@ test('read-only controls keep the read-only material, not the disabled one', asy
});
// The primitive renders the control itself, so it takes a plain `ref`.
-test('ComboboxInput resolves a ref object to the input element', async () => {
- const ref = createRef();
+test('ComboboxInput resolves object and callback refs to the input element', async () => {
+ const objectRef = createRef();
+ const callbackResolved: Array = [];
render(
- aria-label="Country" defaultItems={countryItems}>
-
-
-
- ,
- );
-
- const input = page.getByRole('combobox', { name: 'Country' });
- await expect.element(input).toBeVisible();
-
- expect(ref.current).toBeInstanceOf(HTMLInputElement);
- expect(ref.current).toBe(input.element());
-});
-
-test('ComboboxInput resolves a callback ref to the input element', async () => {
- const resolved: Array = [];
- render(
- aria-label="Country" defaultItems={countryItems}>
-
- {
- resolved.push(node);
- }}
- />
-
- ,
+ <>
+ aria-label="Country object" defaultItems={countryItems}>
+
+
+
+
+ aria-label="Country callback" defaultItems={countryItems}>
+
+ {
+ callbackResolved.push(node);
+ }}
+ />
+
+
+ >,
);
- const input = page.getByRole('combobox', { name: 'Country' });
- await expect.element(input).toBeVisible();
+ const objectInput = page.getByRole('combobox', { name: 'Country object' });
+ const callbackInput = page.getByRole('combobox', { name: 'Country callback' });
+ await expect.element(objectInput).toBeVisible();
+ await expect.element(callbackInput).toBeVisible();
- expect(resolved.at(-1)).toBeInstanceOf(HTMLInputElement);
- expect(resolved.at(-1)).toBe(input.element());
+ expect(objectRef.current).toBe(objectInput.element());
+ expect(callbackResolved.at(-1)).toBe(callbackInput.element());
});
/** The control group wrapping the combobox input labelled `name`. */
diff --git a/packages/@luke-ui/react/src/combobox-field/combobox-field.visual.test.tsx b/packages/@luke-ui/react/src/combobox-field/combobox-field.visual.test.tsx
index 78015bed..ce054359 100644
--- a/packages/@luke-ui/react/src/combobox-field/combobox-field.visual.test.tsx
+++ b/packages/@luke-ui/react/src/combobox-field/combobox-field.visual.test.tsx
@@ -141,7 +141,6 @@ test('interactive states', async () => {
const clear = page.getByRole('button', { name: 'Clear selection' });
const trigger = page.getByRole('button', { name: 'Toggle options' });
- await captureVisual(locator, 'combobox-field/resting');
await userEvent.hover(input);
await captureVisual(locator, 'combobox-field/hover');
await userEvent.unhover(input);
@@ -188,26 +187,24 @@ test('open option and selection states', async () => {
await captureVisual(page.elementLocator(document.body), 'combobox-field/option-keyboard-focus');
});
-test('option with leading icon', async () => {
+test('option with leading icon at both sizes', async () => {
render(
-
- {renderIconItem}
- ,
+
+
+ {renderIconItem}
+
+
+ {renderIconItem}
+
+ ,
);
- await userEvent.click(page.getByRole('combobox', { name: 'Country' }));
+ await userEvent.click(page.getByRole('combobox', { name: 'Medium' }));
await captureVisual(
page.elementLocator(document.body),
'combobox-field/option-leading-icon-medium',
);
-});
-
-test('option with leading icon, small', async () => {
- render(
-
- {renderIconItem}
- ,
- );
- await userEvent.click(page.getByRole('combobox', { name: 'Country' }));
+ await userEvent.keyboard('{Escape}');
+ await userEvent.click(page.getByRole('combobox', { name: 'Small' }));
await captureVisual(
page.elementLocator(document.body),
'combobox-field/option-leading-icon-small',
diff --git a/packages/@luke-ui/react/src/icon-button/icon-button.visual.test.tsx b/packages/@luke-ui/react/src/icon-button/icon-button.visual.test.tsx
index 48de08d6..1ff05fa7 100644
--- a/packages/@luke-ui/react/src/icon-button/icon-button.visual.test.tsx
+++ b/packages/@luke-ui/react/src/icon-button/icon-button.visual.test.tsx
@@ -31,7 +31,6 @@ test('interactive states', async () => {
const { locator } = render();
const button = page.getByRole('button', { name: 'Action' });
- await captureVisual(locator, 'icon-button/resting');
await userEvent.hover(button);
await captureVisual(locator, 'icon-button/hover');
await userEvent.unhover(button);
diff --git a/packages/@luke-ui/react/src/link/link.visual.test.tsx b/packages/@luke-ui/react/src/link/link.visual.test.tsx
index 9add0313..de12a0ee 100644
--- a/packages/@luke-ui/react/src/link/link.visual.test.tsx
+++ b/packages/@luke-ui/react/src/link/link.visual.test.tsx
@@ -53,7 +53,6 @@ test('interactive states', async () => {
);
const link = page.getByRole('link', { name: 'Destination' });
- await captureVisual(locator, 'link/resting');
await userEvent.hover(link);
await captureVisual(locator, 'link/hover');
await userEvent.unhover(link);
diff --git a/packages/@luke-ui/react/src/recipes/combobox.css.ts b/packages/@luke-ui/react/src/recipes/combobox.css.ts
index 50f69469..7ddab995 100644
--- a/packages/@luke-ui/react/src/recipes/combobox.css.ts
+++ b/packages/@luke-ui/react/src/recipes/combobox.css.ts
@@ -11,7 +11,7 @@ import {
inputStates,
} from './input-states.js';
import { invalidIndicatorIcon, invalidIndicatorIconForcedColors } from './invalid-indicator.js';
-import type { RecipeSelection, SlottedConfigInput } from './recipe.js';
+import type { SlottedConfigInput } from './recipe.js';
import { recipe } from './recipe.js';
// Set per `size` variant on `inputGroup` below, from `COMBOBOX_ICON_SIZE`, so the invalid
@@ -488,8 +488,5 @@ const comboboxConfig = {
*/
export const combobox = recipe(comboboxConfig);
-/** Outer variant selection for the combobox recipe. */
-export type ComboboxVariants = RecipeSelection;
-
/** Allowed `size` values for the combobox recipe. */
export type ComboboxSize = keyof typeof comboboxConfig.variants.size;
diff --git a/packages/@luke-ui/react/src/recipes/recipe.browser.test.ts b/packages/@luke-ui/react/src/recipes/recipe.browser.test.ts
index bc1d893c..22330b73 100644
--- a/packages/@luke-ui/react/src/recipes/recipe.browser.test.ts
+++ b/packages/@luke-ui/react/src/recipes/recipe.browser.test.ts
@@ -1,5 +1,4 @@
-import { expect, expectTypeOf, test } from 'vite-plus/test';
-import type { ComboboxVariants } from './combobox.css.js';
+import { expect, test } from 'vite-plus/test';
import { field } from './field.css.js';
import { inputGroup } from './input-group.css.js';
import {
@@ -29,8 +28,10 @@ test('base composes a nested class array alongside a style object', () => {
element.className = className;
const style = getComputedStyle(element);
- expect(style.backgroundColor).toBe('rgb(4, 5, 6)');
- expect(style.color).toBe('rgb(1, 2, 3)');
+ // The fixture paints concrete colours so we can prove the style object landed; the values
+ // themselves are not a public contract.
+ expect(style.backgroundColor).not.toBe('rgba(0, 0, 0, 0)');
+ expect(style.color).not.toBe('rgba(0, 0, 0, 0)');
expect(style.fontWeight).toBe('700');
element.remove();
@@ -40,45 +41,3 @@ test('slot functions merge an optional extra class', () => {
expect(field().root('extra-class').split(' ')).toContain('extra-class');
expect(inputGroup({ size: 'small' }).control('mine').split(' ')).toContain('mine');
});
-
-// Type assertions are compile-time only.
-// oxlint-disable-next-line vitest/expect-expect
-test('outer variant selection accepts known variants and rejects siblings/unknowns', () => {
- expectTypeOf(field).toBeCallableWith({ necessityIndicator: 'icon', tone: 'description' });
- expectTypeOf(inputGroup).toBeCallableWith({ size: 'medium' });
-
- // @ts-expect-error `size` belongs to input-group/combobox, not field.
- field({ size: 'small' });
- // @ts-expect-error `icon`/`label` are the only necessity indicators.
- field({ necessityIndicator: 'asterisk' });
- // @ts-expect-error `tone` is a field variant, not an input-group one.
- inputGroup({ tone: 'error' });
- // @ts-expect-error `large` is not an input-group size.
- inputGroup({ size: 'large' });
-});
-
-// Type assertions are compile-time only.
-// oxlint-disable-next-line vitest/expect-expect
-test('slot functions take only an optional class string, never variant args', () => {
- const slots = field({ tone: 'error' });
-
- expectTypeOf(slots.root).toBeCallableWith('extra');
- expectTypeOf(slots.root).toBeCallableWith();
-
- // Negative cases are compile-time only; this closure is never called so the
- // invalid arguments are type-checked without reaching `cx` at runtime.
- const rejectsVariantArgs = () => {
- // @ts-expect-error Slot functions do not accept variant selections.
- slots.root({ tone: 'error' });
- // @ts-expect-error Slot functions take a class string, not a number.
- slots.label(42);
- };
- void rejectsVariantArgs;
-});
-
-// Type assertions are compile-time only.
-// oxlint-disable-next-line vitest/expect-expect
-test('internal ComboboxVariants type is the combobox size selection', () => {
- const variants: ComboboxVariants = { size: 'medium' };
- expectTypeOf(variants.size).toEqualTypeOf<'medium' | 'small' | undefined>();
-});
diff --git a/packages/@luke-ui/react/src/sizing/control-size.test.ts b/packages/@luke-ui/react/src/sizing/control-size.test.ts
deleted file mode 100644
index 6c41a986..00000000
--- a/packages/@luke-ui/react/src/sizing/control-size.test.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { expectTypeOf, test } from 'vite-plus/test';
-import type { ComboboxSize } from '../recipes/combobox.css.js';
-import type { InputGroupSize } from '../recipes/input-group.css.js';
-import type { FieldControlSize } from './control-size.js';
-
-// Guards `ComboboxSize` and `InputGroupSize` against drifting from `FieldControlSize`,
-// the union their icon-size maps are keyed by. Type assertions are compile-time only.
-// oxlint-disable-next-line vitest/expect-expect
-test('ComboboxSize and InputGroupSize stay exactly FieldControlSize', () => {
- expectTypeOf().toEqualTypeOf();
- expectTypeOf().toEqualTypeOf();
-});
diff --git a/packages/@luke-ui/react/src/styles/stylesheet-contract.browser.test.tsx b/packages/@luke-ui/react/src/styles/stylesheet-contract.browser.test.tsx
index 61f9cbea..16e51555 100644
--- a/packages/@luke-ui/react/src/styles/stylesheet-contract.browser.test.tsx
+++ b/packages/@luke-ui/react/src/styles/stylesheet-contract.browser.test.tsx
@@ -9,13 +9,6 @@ import { createRoot } from 'react-dom/client';
import { afterEach, expect, test } from 'vite-plus/test';
const mounted: Array<{ container: HTMLElement; root: Root }> = [];
-const iconCases = [
- { expectedSize: '24px', id: 'default-icon' },
- { expectedSize: '16px', id: 'xsmall-icon', size: 'xsmall' },
- { expectedSize: '20px', id: 'small-icon', size: 'small' },
- { expectedSize: '24px', id: 'medium-icon', size: 'medium' },
- { expectedSize: '32px', id: 'large-icon', size: 'large' },
-] as const;
afterEach(() => {
for (const { container, root } of mounted) {
@@ -25,7 +18,7 @@ afterEach(() => {
mounted.length = 0;
});
-test('applies the public reset, theme, and icon-size contracts', () => {
+test('applies the public reset and theme contracts', () => {
const { container } = mountFixture();
const resetTarget = container.querySelector('#reset-target');
if (!(resetTarget instanceof HTMLElement)) throw new Error('Expected reset target.');
@@ -35,15 +28,6 @@ test('applies the public reset, theme, and icon-size contracts', () => {
expect(themeStyles.color).toBe(themeStyles.getPropertyValue('--luke-color-text-primary'));
expect(themeStyles.fontSize).toBe(themeStyles.getPropertyValue('--luke-font-300-font-size'));
expect(themeStyles.lineHeight).toBe(themeStyles.getPropertyValue('--luke-font-300-line-height'));
-
- for (const iconCase of iconCases) {
- const icon = container.querySelector(`#${iconCase.id}`);
- if (!(icon instanceof SVGElement)) throw new Error('Expected icon.');
-
- const styles = getComputedStyle(icon);
- expect(styles.blockSize).toBe(iconCase.expectedSize);
- expect(styles.inlineSize).toBe(iconCase.expectedSize);
- }
});
function mountFixture() {
@@ -57,10 +41,6 @@ function mountFixture() {
-
-
-
-
,
);
diff --git a/packages/@luke-ui/react/src/styles/utilities.browser.test.ts b/packages/@luke-ui/react/src/styles/utilities.browser.test.ts
index 93af818a..0af429ba 100644
--- a/packages/@luke-ui/react/src/styles/utilities.browser.test.ts
+++ b/packages/@luke-ui/react/src/styles/utilities.browser.test.ts
@@ -29,39 +29,22 @@ test('applies every retained breakpoint responsively', async () => {
);
const breakpoints = [
- { expected: '4px', name: 'initial', width: 320 },
- { expected: '8px', name: 'small', width: 640 },
- { expected: '12px', name: 'medium', width: 768 },
- { expected: '16px', name: 'large', width: 1024 },
- { expected: '24px', name: 'xlarge', width: 1280 },
- { expected: '32px', name: 'xxlarge', width: 1536 },
+ { name: 'initial', width: 320 },
+ { name: 'small', width: 640 },
+ { name: 'medium', width: 768 },
+ { name: 'large', width: 1024 },
+ { name: 'xlarge', width: 1280 },
+ { name: 'xxlarge', width: 1536 },
] as const;
+ const paddings: Array = [];
for (const breakpoint of breakpoints) {
// eslint-disable-next-line no-await-in-loop -- viewport changes must be observed in order
await page.viewport(breakpoint.width, 800);
- expect(getComputedStyle(element).padding).toBe(breakpoint.expected);
+ paddings.push(getComputedStyle(element).padding);
}
-});
-
-test('resolves every semantic space step including zero', () => {
- const spaces = [
- { expected: '0px', step: '0' },
- { expected: '4px', step: '100' },
- { expected: '8px', step: '200' },
- { expected: '12px', step: '300' },
- { expected: '16px', step: '400' },
- { expected: '24px', step: '600' },
- { expected: '32px', step: '800' },
- { expected: '40px', step: '1000' },
- { expected: '48px', step: '1200' },
- { expected: '64px', step: '1600' },
- ] as const;
- for (const space of spaces) {
- const element = mount(createSprinkles({ padding: space.step }));
- expect(getComputedStyle(element).padding).toBe(space.expected);
- }
+ expect(new Set(paddings).size).toBe(breakpoints.length);
});
test('returns class and style output that merges with consumer props', () => {
@@ -80,7 +63,6 @@ test('returns class and style output that merges with consumer props', () => {
expect(Object.keys(generated.style)).not.toHaveLength(0);
expect(getComputedStyle(element).display).toBe('grid');
expect(getComputedStyle(element).inlineSize).toBe(`${document.body.clientWidth - 32}px`);
- expect(getComputedStyle(element).padding).toBe('16px');
expect(getComputedStyle(element).backgroundColor).toBe('rgb(1, 2, 3)');
});
diff --git a/packages/@luke-ui/react/src/styles/utilities.test.ts b/packages/@luke-ui/react/src/styles/utilities.test.ts
index 0ae52cc2..16fedcce 100644
--- a/packages/@luke-ui/react/src/styles/utilities.test.ts
+++ b/packages/@luke-ui/react/src/styles/utilities.test.ts
@@ -1,89 +1,10 @@
-import { expect, expectTypeOf, test } from 'vite-plus/test';
-import type { SprinklesProps } from './utilities.css.js';
+import { expect, test } from 'vite-plus/test';
import { createSprinkles } from './utilities.css.js';
-const responsiveLayout = {
- alignItems: { initial: 'stretch', medium: 'center' },
- blockSize: { initial: 'auto', large: '50vh' },
- display: { initial: 'flex', medium: 'grid' },
- flex: '1 1 auto',
- flexBasis: '12rem',
- flexDirection: 'column',
- flexGrow: '1',
- flexShrink: '0',
- flexWrap: 'wrap',
- gap: { initial: '200', medium: '600' },
- gridColumn: '1 / -1',
- gridColumnEnd: 'span 2',
- gridColumnStart: '2',
- gridRow: 'auto / span 2',
- inlineSize: { initial: '100%', medium: '50%' },
- insetBlockStart: '0',
- insetInline: 'var(--luke-space-400)',
- justifyContent: 'space-between',
- marginBlock: '0',
- marginInline: 'auto',
- maxInlineSize: '64rem',
- minBlockSize: '10rem',
- overflow: 'auto',
- padding: { initial: '300', small: '400' },
- placeSelf: 'stretch',
- position: 'sticky',
- rowGap: '1000',
-} as const satisfies SprinklesProps;
-
-test('types the responsive layout-only surface', () => {
- expectTypeOf(responsiveLayout).toExtend();
+test('exposes the responsive layout-only property surface', () => {
expect(createSprinkles.properties).toContain('display');
expect(createSprinkles.properties).toContain('gridColumn');
expect(createSprinkles.properties).not.toContain('color');
expect(createSprinkles.properties).not.toContain('backgroundColor');
expect(createSprinkles.properties).not.toContain('fontSize');
});
-
-const removedColor = {
- // @ts-expect-error Direct colour is not part of Sprinkles.
- color: 'danger',
-} satisfies SprinklesProps;
-
-const removedBackgroundColor = {
- // @ts-expect-error Direct background colour is not part of Sprinkles.
- backgroundColor: 'neutral',
-} satisfies SprinklesProps;
-
-const removedTypography = {
- // @ts-expect-error Typography is not part of Sprinkles.
- fontSize: '300',
-} satisfies SprinklesProps;
-
-const removedPseudoState = {
- // @ts-expect-error Pseudo-state colour props are not part of Sprinkles.
- color: { hover: 'danger' },
-} satisfies SprinklesProps;
-
-const invalidSpaceValue = {
- // @ts-expect-error Spacing accepts only semantic space steps.
- padding: '1rem',
-} satisfies SprinklesProps;
-
-const invalidResponsiveSpaceValue = {
- padding: {
- // @ts-expect-error Responsive spacing accepts only semantic space steps.
- small: '1rem',
- },
-} satisfies SprinklesProps;
-
-const removedXsmallCondition = {
- display: {
- // @ts-expect-error The responsive base condition is named `initial`.
- xsmall: 'block',
- },
-} satisfies SprinklesProps;
-
-void removedColor;
-void removedBackgroundColor;
-void removedTypography;
-void removedPseudoState;
-void invalidSpaceValue;
-void invalidResponsiveSpaceValue;
-void removedXsmallCondition;
diff --git a/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx b/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx
index 9355235a..a94f8132 100644
--- a/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx
+++ b/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx
@@ -104,39 +104,32 @@ test('the indicator lands after the input and before a trailing suffix', async (
// The primitive renders the control itself, so it takes a plain `ref`. Both ref
// shapes are covered: React Hook Form hands out a callback ref, so the callback
// arm is the one that decides whether the component is usable with it at all.
-test('InputGroupInput resolves a ref object to the input element', async () => {
- const ref = createRef();
+test('InputGroupInput resolves object and callback refs to the input element', async () => {
+ const objectRef = createRef();
+ const callbackResolved: Array = [];
render(
-
-
- ,
- );
-
- const input = page.getByRole('textbox', { name: 'Amount' });
- await expect.element(input).toBeVisible();
-
- expect(ref.current).toBeInstanceOf(HTMLInputElement);
- expect(ref.current).toBe(input.element());
-});
-
-test('InputGroupInput resolves a callback ref to the input element', async () => {
- const resolved: Array = [];
- render(
-
- {
- resolved.push(node);
- }}
- />
- ,
+ <>
+
+
+
+
+ {
+ callbackResolved.push(node);
+ }}
+ />
+
+ >,
);
- const input = page.getByRole('textbox', { name: 'Amount' });
- await expect.element(input).toBeVisible();
+ const objectInput = page.getByRole('textbox', { name: 'Amount object' });
+ const callbackInput = page.getByRole('textbox', { name: 'Amount callback' });
+ await expect.element(objectInput).toBeVisible();
+ await expect.element(callbackInput).toBeVisible();
- expect(resolved.at(-1)).toBeInstanceOf(HTMLInputElement);
- expect(resolved.at(-1)).toBe(input.element());
+ expect(objectRef.current).toBe(objectInput.element());
+ expect(callbackResolved.at(-1)).toBe(callbackInput.element());
});
// `inputStates.invalid` must not match `:has(:invalid)`: that matches a required,
diff --git a/packages/@luke-ui/react/src/text-field/text-field.visual.test.tsx b/packages/@luke-ui/react/src/text-field/text-field.visual.test.tsx
index 0fd9c416..28a5b75c 100644
--- a/packages/@luke-ui/react/src/text-field/text-field.visual.test.tsx
+++ b/packages/@luke-ui/react/src/text-field/text-field.visual.test.tsx
@@ -127,7 +127,6 @@ test('interactive states', async () => {
const { locator } = render();
const input = page.getByRole('textbox', { name: 'Focus me' });
- await captureVisual(locator, 'text-field/resting');
await userEvent.hover(input);
await captureVisual(locator, 'text-field/hover');
await userEvent.unhover(input);
diff --git a/packages/@luke-ui/react/src/theme/__fixtures__/v2-goldens/paper.v2.css b/packages/@luke-ui/react/src/theme/__fixtures__/v2-goldens/paper.v2.css
deleted file mode 100644
index ac735379..00000000
--- a/packages/@luke-ui/react/src/theme/__fixtures__/v2-goldens/paper.v2.css
+++ /dev/null
@@ -1,431 +0,0 @@
-/* Generated by buildTheme from @luke-ui/react. Do not edit. */
-:where(:root),
-.luke-ui-theme-paper {
- --luke-font-100-baseline-trim: -0.3029em;
- --luke-font-100-cap-height-trim: -0.3029em;
- --luke-font-100-font-size: 12px;
- --luke-font-100-letter-spacing: 0.0025em;
- --luke-font-100-line-height: 16px;
- --luke-font-200-baseline-trim: -0.3505em;
- --luke-font-200-cap-height-trim: -0.3505em;
- --luke-font-200-font-size: 14px;
- --luke-font-200-letter-spacing: 0;
- --luke-font-200-line-height: 20px;
- --luke-font-300-baseline-trim: -0.3862em;
- --luke-font-300-cap-height-trim: -0.3862em;
- --luke-font-300-font-size: 16px;
- --luke-font-300-letter-spacing: 0;
- --luke-font-300-line-height: 24px;
- --luke-font-400-baseline-trim: -0.3585em;
- --luke-font-400-cap-height-trim: -0.3585em;
- --luke-font-400-font-size: 18px;
- --luke-font-400-letter-spacing: -0.0025em;
- --luke-font-400-line-height: 26px;
- --luke-font-500-baseline-trim: -0.3362em;
- --luke-font-500-cap-height-trim: -0.3362em;
- --luke-font-500-font-size: 20px;
- --luke-font-500-letter-spacing: -0.005em;
- --luke-font-500-line-height: 28px;
- --luke-font-600-baseline-trim: -0.2612em;
- --luke-font-600-cap-height-trim: -0.2612em;
- --luke-font-600-font-size: 24px;
- --luke-font-600-letter-spacing: -0.00625em;
- --luke-font-600-line-height: 30px;
- --luke-font-700-baseline-trim: -0.2791em;
- --luke-font-700-cap-height-trim: -0.2791em;
- --luke-font-700-font-size: 28px;
- --luke-font-700-letter-spacing: -0.0075em;
- --luke-font-700-line-height: 36px;
- --luke-font-800-baseline-trim: -0.2077em;
- --luke-font-800-cap-height-trim: -0.2077em;
- --luke-font-800-font-size: 35px;
- --luke-font-800-letter-spacing: -0.01em;
- --luke-font-800-line-height: 40px;
- --luke-font-900-baseline-trim: -0.1362em;
- --luke-font-900-cap-height-trim: -0.1362em;
- --luke-font-900-font-size: 60px;
- --luke-font-900-letter-spacing: -0.025em;
- --luke-font-900-line-height: 60px;
- --luke-font-family-body: 'Inter', system-ui, sans-serif;
- --luke-font-family-code: ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
- --luke-font-weight-body: 400;
- --luke-font-weight-label: 500;
- --luke-font-weight-heading: 600;
- --luke-font-weight-emphasis: 700;
- --luke-radius-detail: 4px;
- --luke-radius-control: 4px;
- --luke-radius-surface: 12px;
- --luke-radius-overlay: 16px;
- --luke-radius-full: 9999px;
- --luke-space-100: 4px;
- --luke-space-200: 8px;
- --luke-space-300: 12px;
- --luke-space-400: 16px;
- --luke-space-600: 24px;
- --luke-space-800: 32px;
- --luke-space-1000: 40px;
- --luke-space-1200: 48px;
- --luke-space-1600: 64px;
- --luke-control-size-small: 32px;
- --luke-control-size-medium: 40px;
- --luke-control-size-min-target: 24px;
- --luke-control-size-combobox-action: 28px;
- --luke-interaction-disabled-opacity: 0.55;
- --luke-icon-size-xsmall: 16px;
- --luke-icon-size-small: 20px;
- --luke-icon-size-medium: 24px;
- --luke-icon-size-large: 32px;
- --luke-motion-duration-feedback: 100ms;
- --luke-motion-duration-enter: 250ms;
- --luke-motion-duration-exit: 150ms;
- --luke-motion-easing-standard: cubic-bezier(0, 0, 0.4, 1);
- --luke-motion-easing-exit: cubic-bezier(0.3, 0, 1, 1);
-}
-
-:where(:root),
-.luke-ui-theme-paper {
- color-scheme: light;
- --luke-color-surface-canvas: oklch(1 0 0);
- --luke-color-surface-recessed: oklch(1 0 0);
- --luke-color-surface-floating: oklch(1 0 0);
- --luke-color-surface-overlay: oklch(1 0 0);
- --luke-color-scrim: oklch(0 0 0 / 0.2);
- --luke-color-loading-skeleton: oklch(0.75 0 0);
- --luke-color-text-primary: oklch(0.3 0 0);
- --luke-color-text-secondary: oklch(0.49 0 0);
- --luke-color-text-disabled: oklch(0.75 0 0);
- --luke-color-background-neutral-subtle-rest: oklch(0.97 0 0);
- --luke-color-background-neutral-subtle-hover: oklch(0.948 0 0);
- --luke-color-background-neutral-subtle-pressed: oklch(0.922 0 0);
- --luke-color-background-neutral-solid-rest: oklch(0.35 0 0);
- --luke-color-background-neutral-solid-hover: oklch(0.3 0 0);
- --luke-color-background-neutral-solid-pressed: oklch(0.3 0 0);
- --luke-color-background-accent-subtle-rest: oklch(0.97 0.015 247.47);
- --luke-color-background-accent-subtle-hover: oklch(0.948 0.0262 247.47);
- --luke-color-background-accent-subtle-pressed: oklch(0.922 0.0396 247.47);
- --luke-color-background-accent-solid-rest: oklch(0.4264 0.0975 247.47);
- --luke-color-background-accent-solid-hover: oklch(0.3764 0.0975 247.47);
- --luke-color-background-accent-solid-pressed: oklch(0.3764 0.0975 247.47);
- --luke-color-background-info-subtle-rest: oklch(0.97 0.014 266.66);
- --luke-color-background-info-subtle-hover: oklch(0.948 0.0245 266.66);
- --luke-color-background-info-subtle-pressed: oklch(0.922 0.0372 266.66);
- --luke-color-background-info-solid-rest: oklch(0.4326 0.2155 266.66);
- --luke-color-background-info-solid-hover: oklch(0.3826 0.2155 266.66);
- --luke-color-background-info-solid-pressed: oklch(0.3826 0.2155 266.66);
- --luke-color-background-success-subtle-rest: oklch(0.97 0.0335 137.4);
- --luke-color-background-success-subtle-hover: oklch(0.948 0.0479 137.4);
- --luke-color-background-success-subtle-pressed: oklch(0.922 0.0598 137.4);
- --luke-color-background-success-solid-rest: oklch(0.4475 0.1197 137.4);
- --luke-color-background-success-solid-hover: oklch(0.3975 0.1197 137.4);
- --luke-color-background-success-solid-pressed: oklch(0.3975 0.1197 137.4);
- --luke-color-background-warning-subtle-rest: oklch(0.97 0.0248 76.41);
- --luke-color-background-warning-subtle-hover: oklch(0.948 0.0436 76.41);
- --luke-color-background-warning-subtle-pressed: oklch(0.922 0.0663 76.41);
- --luke-color-background-warning-solid-rest: oklch(0.7201 0.147 76.41);
- --luke-color-background-warning-solid-hover: oklch(0.6701 0.1405 76.41);
- --luke-color-background-warning-solid-pressed: oklch(0.6701 0.1405 76.41);
- --luke-color-background-danger-subtle-rest: oklch(0.97 0.0147 24.44);
- --luke-color-background-danger-subtle-hover: oklch(0.948 0.0259 24.44);
- --luke-color-background-danger-subtle-pressed: oklch(0.922 0.0397 24.44);
- --luke-color-background-danger-solid-rest: oklch(0.5269 0.1889 24.44);
- --luke-color-background-danger-solid-hover: oklch(0.4769 0.1889 24.44);
- --luke-color-background-danger-solid-pressed: oklch(0.4769 0.1889 24.44);
- --luke-color-foreground-neutral-rest: oklch(0.49 0 0);
- --luke-color-foreground-neutral-hover: oklch(0.3 0 0);
- --luke-color-foreground-neutral-on-solid: oklch(0.985 0 0);
- --luke-color-foreground-accent-rest: oklch(0.49 0.0536 247.47);
- --luke-color-foreground-accent-hover: oklch(0.3 0.0439 247.47);
- --luke-color-foreground-accent-on-solid: oklch(0.985 0 247.47);
- --luke-color-foreground-info-rest: oklch(0.49 0.1185 266.66);
- --luke-color-foreground-info-hover: oklch(0.3 0.097 266.66);
- --luke-color-foreground-info-on-solid: oklch(0.985 0 266.66);
- --luke-color-foreground-success-rest: oklch(0.49 0.0658 137.4);
- --luke-color-foreground-success-hover: oklch(0.3 0.0539 137.4);
- --luke-color-foreground-success-on-solid: oklch(0.985 0 137.4);
- --luke-color-foreground-warning-rest: oklch(0.49 0.0808 76.41);
- --luke-color-foreground-warning-hover: oklch(0.3 0.0629 76.41);
- --luke-color-foreground-warning-on-solid: oklch(0.18 0.01 76.41);
- --luke-color-foreground-danger-rest: oklch(0.49 0.1039 24.44);
- --luke-color-foreground-danger-hover: oklch(0.3 0.085 24.44);
- --luke-color-foreground-danger-on-solid: oklch(0.985 0 24.44);
- --luke-color-border-decorative: oklch(0.89 0 0);
- --luke-color-border-control: oklch(0.665 0 0);
- --luke-color-border-focus: oklch(0.55 0.17 255);
- --luke-color-border-neutral: oklch(0.835 0 0);
- --luke-color-border-accent: oklch(0.835 0.0644 247.47);
- --luke-color-border-info: oklch(0.835 0.0814 266.66);
- --luke-color-border-success: oklch(0.835 0.079 137.4);
- --luke-color-border-warning: oklch(0.835 0.097 76.41);
- --luke-color-border-danger: oklch(0.835 0.0911 24.44);
- --luke-depth-recessed: none;
- --luke-depth-resting: 0 1px 3px oklch(0.2 0.01 250 / 0.04), 0 1px 2px oklch(0.2 0.01 250 / 0.02);
- --luke-depth-raised: 0 2px 6px oklch(0.2 0.01 250 / 0.05), 0 1px 3px oklch(0.2 0.01 250 / 0.035);
- --luke-depth-floating: 0 4px 14px oklch(0.2 0.01 250 / 0.12);
- --luke-depth-overlay: 0 12px 36px oklch(0.2 0.01 250 / 0.16);
- --luke-action-control-finish-recessed: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.08) 0%, transparent 100%);
- --luke-action-control-finish-resting: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.16) 0%, transparent 100%), radial-gradient(80% 50% at 50% 110%, rgb(255 255 255 / 0.07) 0%, transparent 70%);
- --luke-action-control-finish-raised: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.2) 0%, transparent 100%), radial-gradient(80% 50% at 50% 110%, rgb(255 255 255 / 0.1) 0%, transparent 70%);
-}
-
-@media (prefers-color-scheme: dark) {
- :where(:root),
- .luke-ui-theme-paper {
- color-scheme: dark;
- --luke-color-surface-canvas: oklch(0.22 0.01 250);
- --luke-color-surface-recessed: oklch(0.195 0.01 250);
- --luke-color-surface-floating: oklch(0.29 0.01 250);
- --luke-color-surface-overlay: oklch(0.31 0.01 250);
- --luke-color-scrim: oklch(0 0 0 / 0.4);
- --luke-color-loading-skeleton: oklch(0.47 0.0078 250);
- --luke-color-text-primary: oklch(0.94 0.0045 250);
- --luke-color-text-secondary: oklch(0.76 0.0055 250);
- --luke-color-text-disabled: oklch(0.47 0.0078 250);
- --luke-color-background-neutral-subtle-rest: oklch(0.26 0.0028 250);
- --luke-color-background-neutral-subtle-hover: oklch(0.285 0.004 250);
- --luke-color-background-neutral-subtle-pressed: oklch(0.312 0.005 250);
- --luke-color-background-neutral-solid-rest: oklch(0.82 0.01 250);
- --luke-color-background-neutral-solid-hover: oklch(0.87 0.01 250);
- --luke-color-background-neutral-solid-pressed: oklch(0.87 0.01 250);
- --luke-color-background-accent-subtle-rest: oklch(0.26 0.0308 250);
- --luke-color-background-accent-subtle-hover: oklch(0.285 0.044 250);
- --luke-color-background-accent-subtle-pressed: oklch(0.312 0.055 250);
- --luke-color-background-accent-solid-rest: oklch(0.7 0.11 250);
- --luke-color-background-accent-solid-hover: oklch(0.75 0.11 250);
- --luke-color-background-accent-solid-pressed: oklch(0.75 0.11 250);
- --luke-color-background-info-subtle-rest: oklch(0.26 0.0364 255);
- --luke-color-background-info-subtle-hover: oklch(0.285 0.052 255);
- --luke-color-background-info-subtle-pressed: oklch(0.312 0.065 255);
- --luke-color-background-info-solid-rest: oklch(0.72 0.13 255);
- --luke-color-background-info-solid-hover: oklch(0.77 0.1192 255);
- --luke-color-background-info-solid-pressed: oklch(0.77 0.1192 255);
- --luke-color-background-success-subtle-rest: oklch(0.26 0.0364 150);
- --luke-color-background-success-subtle-hover: oklch(0.285 0.052 150);
- --luke-color-background-success-subtle-pressed: oklch(0.312 0.065 150);
- --luke-color-background-success-solid-rest: oklch(0.74 0.13 150);
- --luke-color-background-success-solid-hover: oklch(0.79 0.13 150);
- --luke-color-background-success-solid-pressed: oklch(0.79 0.13 150);
- --luke-color-background-warning-subtle-rest: oklch(0.26 0.0364 80);
- --luke-color-background-warning-subtle-hover: oklch(0.285 0.052 80);
- --luke-color-background-warning-subtle-pressed: oklch(0.312 0.0646 80);
- --luke-color-background-warning-solid-rest: oklch(0.78 0.13 80);
- --luke-color-background-warning-solid-hover: oklch(0.83 0.13 80);
- --luke-color-background-warning-solid-pressed: oklch(0.83 0.13 80);
- --luke-color-background-danger-subtle-rest: oklch(0.26 0.0448 25);
- --luke-color-background-danger-subtle-hover: oklch(0.285 0.06 25);
- --luke-color-background-danger-subtle-pressed: oklch(0.312 0.075 25);
- --luke-color-background-danger-solid-rest: oklch(0.72 0.16 25);
- --luke-color-background-danger-solid-hover: oklch(0.77 0.1356 25);
- --luke-color-background-danger-solid-pressed: oklch(0.77 0.1356 25);
- --luke-color-foreground-neutral-rest: oklch(0.76 0.0055 250);
- --luke-color-foreground-neutral-hover: oklch(0.94 0.0045 250);
- --luke-color-foreground-neutral-on-solid: oklch(0.18 0.01 250);
- --luke-color-foreground-accent-rest: oklch(0.76 0.0605 250);
- --luke-color-foreground-accent-hover: oklch(0.94 0.0298 250);
- --luke-color-foreground-accent-on-solid: oklch(0.18 0.01 250);
- --luke-color-foreground-info-rest: oklch(0.76 0.0715 255);
- --luke-color-foreground-info-hover: oklch(0.94 0.0291 255);
- --luke-color-foreground-info-on-solid: oklch(0.18 0.01 255);
- --luke-color-foreground-success-rest: oklch(0.76 0.0715 150);
- --luke-color-foreground-success-hover: oklch(0.94 0.0585 150);
- --luke-color-foreground-success-on-solid: oklch(0.18 0.01 150);
- --luke-color-foreground-warning-rest: oklch(0.76 0.0715 80);
- --luke-color-foreground-warning-hover: oklch(0.94 0.0552 80);
- --luke-color-foreground-warning-on-solid: oklch(0.18 0.01 80);
- --luke-color-foreground-danger-rest: oklch(0.76 0.088 25);
- --luke-color-foreground-danger-hover: oklch(0.94 0.0301 25);
- --luke-color-foreground-danger-on-solid: oklch(0.18 0.01 25);
- --luke-color-border-decorative: oklch(0.348 0.0058 250);
- --luke-color-border-control: oklch(0.515 0.0066 250);
- --luke-color-border-focus: oklch(0.72 0.13 255);
- --luke-color-border-neutral: oklch(0.4 0.0066 250);
- --luke-color-border-accent: oklch(0.4 0.0726 250);
- --luke-color-border-info: oklch(0.4 0.0858 255);
- --luke-color-border-success: oklch(0.4 0.0858 150);
- --luke-color-border-warning: oklch(0.4 0.0828 80);
- --luke-color-border-danger: oklch(0.4 0.1056 25);
- --luke-depth-recessed: inset 0 1px 2px oklch(0.12 0.01 250 / 0.22);
- --luke-depth-resting: 0 1px 3px oklch(0.12 0.01 250 / 0.12), 0 1px 2px oklch(0.12 0.01 250 / 0.06);
- --luke-depth-raised: 0 2px 6px oklch(0.12 0.01 250 / 0.18), 0 1px 3px oklch(0.12 0.01 250 / 0.12);
- --luke-depth-floating: 0 4px 14px oklch(0.12 0.01 250 / 0.25);
- --luke-depth-overlay: 0 12px 36px oklch(0.12 0.01 250 / 0.32);
- --luke-action-control-finish-recessed: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.04) 0%, transparent 100%);
- --luke-action-control-finish-resting: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.07) 0%, transparent 100%), radial-gradient(80% 50% at 50% 110%, rgb(255 255 255 / 0.03) 0%, transparent 70%);
- --luke-action-control-finish-raised: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.1) 0%, transparent 100%), radial-gradient(80% 50% at 50% 110%, rgb(255 255 255 / 0.05) 0%, transparent 70%);
- }
-}
-
-:where(:root[data-color-mode='light']),
-:where(:root) :where([data-color-mode='light']),
-.luke-ui-theme-paper[data-color-mode='light'],
-.luke-ui-theme-paper [data-color-mode='light'],
-[data-color-mode='light'] .luke-ui-theme-paper {
- color-scheme: light;
- --luke-color-surface-canvas: oklch(1 0 0);
- --luke-color-surface-recessed: oklch(1 0 0);
- --luke-color-surface-floating: oklch(1 0 0);
- --luke-color-surface-overlay: oklch(1 0 0);
- --luke-color-scrim: oklch(0 0 0 / 0.2);
- --luke-color-loading-skeleton: oklch(0.75 0 0);
- --luke-color-text-primary: oklch(0.3 0 0);
- --luke-color-text-secondary: oklch(0.49 0 0);
- --luke-color-text-disabled: oklch(0.75 0 0);
- --luke-color-background-neutral-subtle-rest: oklch(0.97 0 0);
- --luke-color-background-neutral-subtle-hover: oklch(0.948 0 0);
- --luke-color-background-neutral-subtle-pressed: oklch(0.922 0 0);
- --luke-color-background-neutral-solid-rest: oklch(0.35 0 0);
- --luke-color-background-neutral-solid-hover: oklch(0.3 0 0);
- --luke-color-background-neutral-solid-pressed: oklch(0.3 0 0);
- --luke-color-background-accent-subtle-rest: oklch(0.97 0.015 247.47);
- --luke-color-background-accent-subtle-hover: oklch(0.948 0.0262 247.47);
- --luke-color-background-accent-subtle-pressed: oklch(0.922 0.0396 247.47);
- --luke-color-background-accent-solid-rest: oklch(0.4264 0.0975 247.47);
- --luke-color-background-accent-solid-hover: oklch(0.3764 0.0975 247.47);
- --luke-color-background-accent-solid-pressed: oklch(0.3764 0.0975 247.47);
- --luke-color-background-info-subtle-rest: oklch(0.97 0.014 266.66);
- --luke-color-background-info-subtle-hover: oklch(0.948 0.0245 266.66);
- --luke-color-background-info-subtle-pressed: oklch(0.922 0.0372 266.66);
- --luke-color-background-info-solid-rest: oklch(0.4326 0.2155 266.66);
- --luke-color-background-info-solid-hover: oklch(0.3826 0.2155 266.66);
- --luke-color-background-info-solid-pressed: oklch(0.3826 0.2155 266.66);
- --luke-color-background-success-subtle-rest: oklch(0.97 0.0335 137.4);
- --luke-color-background-success-subtle-hover: oklch(0.948 0.0479 137.4);
- --luke-color-background-success-subtle-pressed: oklch(0.922 0.0598 137.4);
- --luke-color-background-success-solid-rest: oklch(0.4475 0.1197 137.4);
- --luke-color-background-success-solid-hover: oklch(0.3975 0.1197 137.4);
- --luke-color-background-success-solid-pressed: oklch(0.3975 0.1197 137.4);
- --luke-color-background-warning-subtle-rest: oklch(0.97 0.0248 76.41);
- --luke-color-background-warning-subtle-hover: oklch(0.948 0.0436 76.41);
- --luke-color-background-warning-subtle-pressed: oklch(0.922 0.0663 76.41);
- --luke-color-background-warning-solid-rest: oklch(0.7201 0.147 76.41);
- --luke-color-background-warning-solid-hover: oklch(0.6701 0.1405 76.41);
- --luke-color-background-warning-solid-pressed: oklch(0.6701 0.1405 76.41);
- --luke-color-background-danger-subtle-rest: oklch(0.97 0.0147 24.44);
- --luke-color-background-danger-subtle-hover: oklch(0.948 0.0259 24.44);
- --luke-color-background-danger-subtle-pressed: oklch(0.922 0.0397 24.44);
- --luke-color-background-danger-solid-rest: oklch(0.5269 0.1889 24.44);
- --luke-color-background-danger-solid-hover: oklch(0.4769 0.1889 24.44);
- --luke-color-background-danger-solid-pressed: oklch(0.4769 0.1889 24.44);
- --luke-color-foreground-neutral-rest: oklch(0.49 0 0);
- --luke-color-foreground-neutral-hover: oklch(0.3 0 0);
- --luke-color-foreground-neutral-on-solid: oklch(0.985 0 0);
- --luke-color-foreground-accent-rest: oklch(0.49 0.0536 247.47);
- --luke-color-foreground-accent-hover: oklch(0.3 0.0439 247.47);
- --luke-color-foreground-accent-on-solid: oklch(0.985 0 247.47);
- --luke-color-foreground-info-rest: oklch(0.49 0.1185 266.66);
- --luke-color-foreground-info-hover: oklch(0.3 0.097 266.66);
- --luke-color-foreground-info-on-solid: oklch(0.985 0 266.66);
- --luke-color-foreground-success-rest: oklch(0.49 0.0658 137.4);
- --luke-color-foreground-success-hover: oklch(0.3 0.0539 137.4);
- --luke-color-foreground-success-on-solid: oklch(0.985 0 137.4);
- --luke-color-foreground-warning-rest: oklch(0.49 0.0808 76.41);
- --luke-color-foreground-warning-hover: oklch(0.3 0.0629 76.41);
- --luke-color-foreground-warning-on-solid: oklch(0.18 0.01 76.41);
- --luke-color-foreground-danger-rest: oklch(0.49 0.1039 24.44);
- --luke-color-foreground-danger-hover: oklch(0.3 0.085 24.44);
- --luke-color-foreground-danger-on-solid: oklch(0.985 0 24.44);
- --luke-color-border-decorative: oklch(0.89 0 0);
- --luke-color-border-control: oklch(0.665 0 0);
- --luke-color-border-focus: oklch(0.55 0.17 255);
- --luke-color-border-neutral: oklch(0.835 0 0);
- --luke-color-border-accent: oklch(0.835 0.0644 247.47);
- --luke-color-border-info: oklch(0.835 0.0814 266.66);
- --luke-color-border-success: oklch(0.835 0.079 137.4);
- --luke-color-border-warning: oklch(0.835 0.097 76.41);
- --luke-color-border-danger: oklch(0.835 0.0911 24.44);
- --luke-depth-recessed: none;
- --luke-depth-resting: 0 1px 3px oklch(0.2 0.01 250 / 0.04), 0 1px 2px oklch(0.2 0.01 250 / 0.02);
- --luke-depth-raised: 0 2px 6px oklch(0.2 0.01 250 / 0.05), 0 1px 3px oklch(0.2 0.01 250 / 0.035);
- --luke-depth-floating: 0 4px 14px oklch(0.2 0.01 250 / 0.12);
- --luke-depth-overlay: 0 12px 36px oklch(0.2 0.01 250 / 0.16);
- --luke-action-control-finish-recessed: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.08) 0%, transparent 100%);
- --luke-action-control-finish-resting: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.16) 0%, transparent 100%), radial-gradient(80% 50% at 50% 110%, rgb(255 255 255 / 0.07) 0%, transparent 70%);
- --luke-action-control-finish-raised: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.2) 0%, transparent 100%), radial-gradient(80% 50% at 50% 110%, rgb(255 255 255 / 0.1) 0%, transparent 70%);
-}
-
-:where(:root[data-color-mode='dark']),
-:where(:root) :where([data-color-mode='dark']),
-.luke-ui-theme-paper[data-color-mode='dark'],
-.luke-ui-theme-paper [data-color-mode='dark'],
-[data-color-mode='dark'] .luke-ui-theme-paper {
- color-scheme: dark;
- --luke-color-surface-canvas: oklch(0.22 0.01 250);
- --luke-color-surface-recessed: oklch(0.195 0.01 250);
- --luke-color-surface-floating: oklch(0.29 0.01 250);
- --luke-color-surface-overlay: oklch(0.31 0.01 250);
- --luke-color-scrim: oklch(0 0 0 / 0.4);
- --luke-color-loading-skeleton: oklch(0.47 0.0078 250);
- --luke-color-text-primary: oklch(0.94 0.0045 250);
- --luke-color-text-secondary: oklch(0.76 0.0055 250);
- --luke-color-text-disabled: oklch(0.47 0.0078 250);
- --luke-color-background-neutral-subtle-rest: oklch(0.26 0.0028 250);
- --luke-color-background-neutral-subtle-hover: oklch(0.285 0.004 250);
- --luke-color-background-neutral-subtle-pressed: oklch(0.312 0.005 250);
- --luke-color-background-neutral-solid-rest: oklch(0.82 0.01 250);
- --luke-color-background-neutral-solid-hover: oklch(0.87 0.01 250);
- --luke-color-background-neutral-solid-pressed: oklch(0.87 0.01 250);
- --luke-color-background-accent-subtle-rest: oklch(0.26 0.0308 250);
- --luke-color-background-accent-subtle-hover: oklch(0.285 0.044 250);
- --luke-color-background-accent-subtle-pressed: oklch(0.312 0.055 250);
- --luke-color-background-accent-solid-rest: oklch(0.7 0.11 250);
- --luke-color-background-accent-solid-hover: oklch(0.75 0.11 250);
- --luke-color-background-accent-solid-pressed: oklch(0.75 0.11 250);
- --luke-color-background-info-subtle-rest: oklch(0.26 0.0364 255);
- --luke-color-background-info-subtle-hover: oklch(0.285 0.052 255);
- --luke-color-background-info-subtle-pressed: oklch(0.312 0.065 255);
- --luke-color-background-info-solid-rest: oklch(0.72 0.13 255);
- --luke-color-background-info-solid-hover: oklch(0.77 0.1192 255);
- --luke-color-background-info-solid-pressed: oklch(0.77 0.1192 255);
- --luke-color-background-success-subtle-rest: oklch(0.26 0.0364 150);
- --luke-color-background-success-subtle-hover: oklch(0.285 0.052 150);
- --luke-color-background-success-subtle-pressed: oklch(0.312 0.065 150);
- --luke-color-background-success-solid-rest: oklch(0.74 0.13 150);
- --luke-color-background-success-solid-hover: oklch(0.79 0.13 150);
- --luke-color-background-success-solid-pressed: oklch(0.79 0.13 150);
- --luke-color-background-warning-subtle-rest: oklch(0.26 0.0364 80);
- --luke-color-background-warning-subtle-hover: oklch(0.285 0.052 80);
- --luke-color-background-warning-subtle-pressed: oklch(0.312 0.0646 80);
- --luke-color-background-warning-solid-rest: oklch(0.78 0.13 80);
- --luke-color-background-warning-solid-hover: oklch(0.83 0.13 80);
- --luke-color-background-warning-solid-pressed: oklch(0.83 0.13 80);
- --luke-color-background-danger-subtle-rest: oklch(0.26 0.0448 25);
- --luke-color-background-danger-subtle-hover: oklch(0.285 0.06 25);
- --luke-color-background-danger-subtle-pressed: oklch(0.312 0.075 25);
- --luke-color-background-danger-solid-rest: oklch(0.72 0.16 25);
- --luke-color-background-danger-solid-hover: oklch(0.77 0.1356 25);
- --luke-color-background-danger-solid-pressed: oklch(0.77 0.1356 25);
- --luke-color-foreground-neutral-rest: oklch(0.76 0.0055 250);
- --luke-color-foreground-neutral-hover: oklch(0.94 0.0045 250);
- --luke-color-foreground-neutral-on-solid: oklch(0.18 0.01 250);
- --luke-color-foreground-accent-rest: oklch(0.76 0.0605 250);
- --luke-color-foreground-accent-hover: oklch(0.94 0.0298 250);
- --luke-color-foreground-accent-on-solid: oklch(0.18 0.01 250);
- --luke-color-foreground-info-rest: oklch(0.76 0.0715 255);
- --luke-color-foreground-info-hover: oklch(0.94 0.0291 255);
- --luke-color-foreground-info-on-solid: oklch(0.18 0.01 255);
- --luke-color-foreground-success-rest: oklch(0.76 0.0715 150);
- --luke-color-foreground-success-hover: oklch(0.94 0.0585 150);
- --luke-color-foreground-success-on-solid: oklch(0.18 0.01 150);
- --luke-color-foreground-warning-rest: oklch(0.76 0.0715 80);
- --luke-color-foreground-warning-hover: oklch(0.94 0.0552 80);
- --luke-color-foreground-warning-on-solid: oklch(0.18 0.01 80);
- --luke-color-foreground-danger-rest: oklch(0.76 0.088 25);
- --luke-color-foreground-danger-hover: oklch(0.94 0.0301 25);
- --luke-color-foreground-danger-on-solid: oklch(0.18 0.01 25);
- --luke-color-border-decorative: oklch(0.348 0.0058 250);
- --luke-color-border-control: oklch(0.515 0.0066 250);
- --luke-color-border-focus: oklch(0.72 0.13 255);
- --luke-color-border-neutral: oklch(0.4 0.0066 250);
- --luke-color-border-accent: oklch(0.4 0.0726 250);
- --luke-color-border-info: oklch(0.4 0.0858 255);
- --luke-color-border-success: oklch(0.4 0.0858 150);
- --luke-color-border-warning: oklch(0.4 0.0828 80);
- --luke-color-border-danger: oklch(0.4 0.1056 25);
- --luke-depth-recessed: inset 0 1px 2px oklch(0.12 0.01 250 / 0.22);
- --luke-depth-resting: 0 1px 3px oklch(0.12 0.01 250 / 0.12), 0 1px 2px oklch(0.12 0.01 250 / 0.06);
- --luke-depth-raised: 0 2px 6px oklch(0.12 0.01 250 / 0.18), 0 1px 3px oklch(0.12 0.01 250 / 0.12);
- --luke-depth-floating: 0 4px 14px oklch(0.12 0.01 250 / 0.25);
- --luke-depth-overlay: 0 12px 36px oklch(0.12 0.01 250 / 0.32);
- --luke-action-control-finish-recessed: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.04) 0%, transparent 100%);
- --luke-action-control-finish-resting: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.07) 0%, transparent 100%), radial-gradient(80% 50% at 50% 110%, rgb(255 255 255 / 0.03) 0%, transparent 70%);
- --luke-action-control-finish-raised: radial-gradient(90% 75% at 50% 0%, rgb(255 255 255 / 0.1) 0%, transparent 100%), radial-gradient(80% 50% at 50% 110%, rgb(255 255 255 / 0.05) 0%, transparent 70%);
-}
diff --git a/packages/@luke-ui/react/src/theme/__fixtures__/v2-goldens/tactile.v2.css b/packages/@luke-ui/react/src/theme/__fixtures__/v2-goldens/tactile.v2.css
deleted file mode 100644
index 21399817..00000000
--- a/packages/@luke-ui/react/src/theme/__fixtures__/v2-goldens/tactile.v2.css
+++ /dev/null
@@ -1,431 +0,0 @@
-/* Generated by buildTheme from @luke-ui/react. Do not edit. */
-:where(:root),
-.luke-ui-theme-tactile {
- --luke-font-100-baseline-trim: -0.3029em;
- --luke-font-100-cap-height-trim: -0.3029em;
- --luke-font-100-font-size: 12px;
- --luke-font-100-letter-spacing: 0.0025em;
- --luke-font-100-line-height: 16px;
- --luke-font-200-baseline-trim: -0.3505em;
- --luke-font-200-cap-height-trim: -0.3505em;
- --luke-font-200-font-size: 14px;
- --luke-font-200-letter-spacing: 0;
- --luke-font-200-line-height: 20px;
- --luke-font-300-baseline-trim: -0.3862em;
- --luke-font-300-cap-height-trim: -0.3862em;
- --luke-font-300-font-size: 16px;
- --luke-font-300-letter-spacing: 0;
- --luke-font-300-line-height: 24px;
- --luke-font-400-baseline-trim: -0.3585em;
- --luke-font-400-cap-height-trim: -0.3585em;
- --luke-font-400-font-size: 18px;
- --luke-font-400-letter-spacing: -0.0025em;
- --luke-font-400-line-height: 26px;
- --luke-font-500-baseline-trim: -0.3362em;
- --luke-font-500-cap-height-trim: -0.3362em;
- --luke-font-500-font-size: 20px;
- --luke-font-500-letter-spacing: -0.005em;
- --luke-font-500-line-height: 28px;
- --luke-font-600-baseline-trim: -0.2612em;
- --luke-font-600-cap-height-trim: -0.2612em;
- --luke-font-600-font-size: 24px;
- --luke-font-600-letter-spacing: -0.00625em;
- --luke-font-600-line-height: 30px;
- --luke-font-700-baseline-trim: -0.2791em;
- --luke-font-700-cap-height-trim: -0.2791em;
- --luke-font-700-font-size: 28px;
- --luke-font-700-letter-spacing: -0.0075em;
- --luke-font-700-line-height: 36px;
- --luke-font-800-baseline-trim: -0.2077em;
- --luke-font-800-cap-height-trim: -0.2077em;
- --luke-font-800-font-size: 35px;
- --luke-font-800-letter-spacing: -0.01em;
- --luke-font-800-line-height: 40px;
- --luke-font-900-baseline-trim: -0.1362em;
- --luke-font-900-cap-height-trim: -0.1362em;
- --luke-font-900-font-size: 60px;
- --luke-font-900-letter-spacing: -0.025em;
- --luke-font-900-line-height: 60px;
- --luke-font-family-body: 'Inter', system-ui, sans-serif;
- --luke-font-family-code: ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
- --luke-font-weight-body: 400;
- --luke-font-weight-label: 500;
- --luke-font-weight-heading: 600;
- --luke-font-weight-emphasis: 700;
- --luke-radius-detail: 4px;
- --luke-radius-control: 8px;
- --luke-radius-surface: 12px;
- --luke-radius-overlay: 16px;
- --luke-radius-full: 9999px;
- --luke-space-100: 4px;
- --luke-space-200: 8px;
- --luke-space-300: 12px;
- --luke-space-400: 16px;
- --luke-space-600: 24px;
- --luke-space-800: 32px;
- --luke-space-1000: 40px;
- --luke-space-1200: 48px;
- --luke-space-1600: 64px;
- --luke-control-size-small: 32px;
- --luke-control-size-medium: 40px;
- --luke-control-size-min-target: 24px;
- --luke-control-size-combobox-action: 28px;
- --luke-interaction-disabled-opacity: 0.55;
- --luke-icon-size-xsmall: 16px;
- --luke-icon-size-small: 20px;
- --luke-icon-size-medium: 24px;
- --luke-icon-size-large: 32px;
- --luke-motion-duration-feedback: 100ms;
- --luke-motion-duration-enter: 250ms;
- --luke-motion-duration-exit: 150ms;
- --luke-motion-easing-standard: cubic-bezier(0, 0, 0.4, 1);
- --luke-motion-easing-exit: cubic-bezier(0.3, 0, 1, 1);
-}
-
-:where(:root),
-.luke-ui-theme-tactile {
- color-scheme: light;
- --luke-color-surface-canvas: oklch(0.985 0 0);
- --luke-color-surface-recessed: oklch(1 0 0);
- --luke-color-surface-floating: oklch(0.997 0 0);
- --luke-color-surface-overlay: oklch(1 0 0);
- --luke-color-scrim: oklch(0 0 0 / 0.2);
- --luke-color-loading-skeleton: oklch(0.735 0 0);
- --luke-color-text-primary: oklch(0.3 0 0);
- --luke-color-text-secondary: oklch(0.49 0 0);
- --luke-color-text-disabled: oklch(0.735 0 0);
- --luke-color-background-neutral-subtle-rest: oklch(0.955 0 0);
- --luke-color-background-neutral-subtle-hover: oklch(0.933 0 0);
- --luke-color-background-neutral-subtle-pressed: oklch(0.907 0 0);
- --luke-color-background-neutral-solid-rest: oklch(0.35 0 0);
- --luke-color-background-neutral-solid-hover: oklch(0.3 0 0);
- --luke-color-background-neutral-solid-pressed: oklch(0.3 0 0);
- --luke-color-background-accent-subtle-rest: oklch(0.955 0.0248 200);
- --luke-color-background-accent-subtle-hover: oklch(0.933 0.0354 200);
- --luke-color-background-accent-subtle-pressed: oklch(0.907 0.0442 200);
- --luke-color-background-accent-solid-rest: oklch(0.52 0.0884 200);
- --luke-color-background-accent-solid-hover: oklch(0.47 0.0799 200);
- --luke-color-background-accent-solid-pressed: oklch(0.47 0.0799 200);
- --luke-color-background-info-subtle-rest: oklch(0.955 0.0217 255);
- --luke-color-background-info-subtle-hover: oklch(0.933 0.0326 255);
- --luke-color-background-info-subtle-pressed: oklch(0.907 0.0457 255);
- --luke-color-background-info-solid-rest: oklch(0.52 0.16 255);
- --luke-color-background-info-solid-hover: oklch(0.47 0.1548 255);
- --luke-color-background-info-solid-pressed: oklch(0.47 0.1548 255);
- --luke-color-background-success-subtle-rest: oklch(0.955 0.0364 150);
- --luke-color-background-success-subtle-hover: oklch(0.933 0.052 150);
- --luke-color-background-success-subtle-pressed: oklch(0.907 0.065 150);
- --luke-color-background-success-solid-rest: oklch(0.5 0.13 150);
- --luke-color-background-success-solid-hover: oklch(0.45 0.1239 150);
- --luke-color-background-success-solid-pressed: oklch(0.45 0.1239 150);
- --luke-color-background-warning-subtle-rest: oklch(0.955 0.0364 75);
- --luke-color-background-warning-subtle-hover: oklch(0.933 0.0548 75);
- --luke-color-background-warning-subtle-pressed: oklch(0.907 0.07 75);
- --luke-color-background-warning-solid-rest: oklch(0.72 0.14 75);
- --luke-color-background-warning-solid-hover: oklch(0.67 0.14 75);
- --luke-color-background-warning-solid-pressed: oklch(0.67 0.14 75);
- --luke-color-background-danger-subtle-rest: oklch(0.955 0.0223 27);
- --luke-color-background-danger-subtle-hover: oklch(0.933 0.0339 27);
- --luke-color-background-danger-subtle-pressed: oklch(0.907 0.0481 27);
- --luke-color-background-danger-solid-rest: oklch(0.52 0.18 27);
- --luke-color-background-danger-solid-hover: oklch(0.47 0.18 27);
- --luke-color-background-danger-solid-pressed: oklch(0.47 0.18 27);
- --luke-color-foreground-neutral-rest: oklch(0.49 0 0);
- --luke-color-foreground-neutral-hover: oklch(0.3 0 0);
- --luke-color-foreground-neutral-on-solid: oklch(0.985 0 0);
- --luke-color-foreground-accent-rest: oklch(0.49 0.0486 200);
- --luke-color-foreground-accent-hover: oklch(0.3 0.0398 200);
- --luke-color-foreground-accent-on-solid: oklch(0.985 0 200);
- --luke-color-foreground-info-rest: oklch(0.49 0.088 255);
- --luke-color-foreground-info-hover: oklch(0.3 0.072 255);
- --luke-color-foreground-info-on-solid: oklch(0.985 0 255);
- --luke-color-foreground-success-rest: oklch(0.49 0.0715 150);
- --luke-color-foreground-success-hover: oklch(0.3 0.0585 150);
- --luke-color-foreground-success-on-solid: oklch(0.985 0 150);
- --luke-color-foreground-warning-rest: oklch(0.49 0.077 75);
- --luke-color-foreground-warning-hover: oklch(0.3 0.063 75);
- --luke-color-foreground-warning-on-solid: oklch(0.18 0.01 75);
- --luke-color-foreground-danger-rest: oklch(0.49 0.099 27);
- --luke-color-foreground-danger-hover: oklch(0.3 0.081 27);
- --luke-color-foreground-danger-on-solid: oklch(0.985 0 27);
- --luke-color-border-decorative: oklch(0.875 0 0);
- --luke-color-border-control: oklch(0.6525 0 0);
- --luke-color-border-focus: oklch(0.55 0.17 255);
- --luke-color-border-neutral: oklch(0.82 0 0);
- --luke-color-border-accent: oklch(0.82 0.0583 200);
- --luke-color-border-info: oklch(0.82 0.0915 255);
- --luke-color-border-success: oklch(0.82 0.0858 150);
- --luke-color-border-warning: oklch(0.82 0.0924 75);
- --luke-color-border-danger: oklch(0.82 0.101 27);
- --luke-depth-recessed: inset 0 2px 3px oklch(0.3 0.03 220 / 0.18), inset 0 -1px 0 oklch(0.98 0.03 220 / 0.65);
- --luke-depth-resting: 0 2px 0 oklch(0.3 0.03 220 / 0.28), 0 3px 5px -1px oklch(0.3 0.03 220 / 0.16);
- --luke-depth-raised: 0 3px 0 oklch(0.3 0.03 220 / 0.3), 0 5px 8px -2px oklch(0.3 0.03 220 / 0.2);
- --luke-depth-floating: 0 4px 12px oklch(0.3 0.03 220 / 0.16), 0 2px 4px oklch(0.3 0.03 220 / 0.1);
- --luke-depth-overlay: 0 12px 32px oklch(0.3 0.03 220 / 0.2), 0 4px 12px oklch(0.3 0.03 220 / 0.12);
- --luke-action-control-finish-recessed: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.12) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.06) 0%, transparent 70%);
- --luke-action-control-finish-resting: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.24) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.12) 0%, transparent 70%);
- --luke-action-control-finish-raised: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.3) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.16) 0%, transparent 70%);
-}
-
-@media (prefers-color-scheme: dark) {
- :where(:root),
- .luke-ui-theme-tactile {
- color-scheme: dark;
- --luke-color-surface-canvas: oklch(0.25 0.015 210);
- --luke-color-surface-recessed: oklch(0.225 0.015 210);
- --luke-color-surface-floating: oklch(0.32 0.015 210);
- --luke-color-surface-overlay: oklch(0.34 0.015 210);
- --luke-color-scrim: oklch(0 0 0 / 0.4);
- --luke-color-loading-skeleton: oklch(0.5 0.0117 210);
- --luke-color-text-primary: oklch(0.94 0.0067 210);
- --luke-color-text-secondary: oklch(0.76 0.0083 210);
- --luke-color-text-disabled: oklch(0.5 0.0117 210);
- --luke-color-background-neutral-subtle-rest: oklch(0.29 0.0042 210);
- --luke-color-background-neutral-subtle-hover: oklch(0.315 0.006 210);
- --luke-color-background-neutral-subtle-pressed: oklch(0.342 0.0075 210);
- --luke-color-background-neutral-solid-rest: oklch(0.82 0.015 210);
- --luke-color-background-neutral-solid-hover: oklch(0.87 0.015 210);
- --luke-color-background-neutral-solid-pressed: oklch(0.87 0.015 210);
- --luke-color-background-accent-subtle-rest: oklch(0.29 0.028 200);
- --luke-color-background-accent-subtle-hover: oklch(0.315 0.04 200);
- --luke-color-background-accent-subtle-pressed: oklch(0.342 0.05 200);
- --luke-color-background-accent-solid-rest: oklch(0.75 0.1 200);
- --luke-color-background-accent-solid-hover: oklch(0.8 0.1 200);
- --luke-color-background-accent-solid-pressed: oklch(0.8 0.1 200);
- --luke-color-background-info-subtle-rest: oklch(0.29 0.0364 255);
- --luke-color-background-info-subtle-hover: oklch(0.315 0.052 255);
- --luke-color-background-info-subtle-pressed: oklch(0.342 0.065 255);
- --luke-color-background-info-solid-rest: oklch(0.72 0.13 255);
- --luke-color-background-info-solid-hover: oklch(0.77 0.1192 255);
- --luke-color-background-info-solid-pressed: oklch(0.77 0.1192 255);
- --luke-color-background-success-subtle-rest: oklch(0.29 0.0364 150);
- --luke-color-background-success-subtle-hover: oklch(0.315 0.052 150);
- --luke-color-background-success-subtle-pressed: oklch(0.342 0.065 150);
- --luke-color-background-success-solid-rest: oklch(0.74 0.13 150);
- --luke-color-background-success-solid-hover: oklch(0.79 0.13 150);
- --luke-color-background-success-solid-pressed: oklch(0.79 0.13 150);
- --luke-color-background-warning-subtle-rest: oklch(0.29 0.0364 80);
- --luke-color-background-warning-subtle-hover: oklch(0.315 0.052 80);
- --luke-color-background-warning-subtle-pressed: oklch(0.342 0.065 80);
- --luke-color-background-warning-solid-rest: oklch(0.78 0.13 80);
- --luke-color-background-warning-solid-hover: oklch(0.83 0.13 80);
- --luke-color-background-warning-solid-pressed: oklch(0.83 0.13 80);
- --luke-color-background-danger-subtle-rest: oklch(0.29 0.0448 25);
- --luke-color-background-danger-subtle-hover: oklch(0.315 0.06 25);
- --luke-color-background-danger-subtle-pressed: oklch(0.342 0.075 25);
- --luke-color-background-danger-solid-rest: oklch(0.72 0.16 25);
- --luke-color-background-danger-solid-hover: oklch(0.77 0.1356 25);
- --luke-color-background-danger-solid-pressed: oklch(0.77 0.1356 25);
- --luke-color-foreground-neutral-rest: oklch(0.76 0.0083 210);
- --luke-color-foreground-neutral-hover: oklch(0.94 0.0067 210);
- --luke-color-foreground-neutral-on-solid: oklch(0.18 0.01 210);
- --luke-color-foreground-accent-rest: oklch(0.76 0.055 200);
- --luke-color-foreground-accent-hover: oklch(0.94 0.045 200);
- --luke-color-foreground-accent-on-solid: oklch(0.18 0.01 200);
- --luke-color-foreground-info-rest: oklch(0.76 0.0715 255);
- --luke-color-foreground-info-hover: oklch(0.94 0.0291 255);
- --luke-color-foreground-info-on-solid: oklch(0.18 0.01 255);
- --luke-color-foreground-success-rest: oklch(0.76 0.0715 150);
- --luke-color-foreground-success-hover: oklch(0.94 0.0585 150);
- --luke-color-foreground-success-on-solid: oklch(0.18 0.01 150);
- --luke-color-foreground-warning-rest: oklch(0.76 0.0715 80);
- --luke-color-foreground-warning-hover: oklch(0.94 0.0552 80);
- --luke-color-foreground-warning-on-solid: oklch(0.18 0.01 80);
- --luke-color-foreground-danger-rest: oklch(0.76 0.088 25);
- --luke-color-foreground-danger-hover: oklch(0.94 0.0301 25);
- --luke-color-foreground-danger-on-solid: oklch(0.18 0.01 25);
- --luke-color-border-decorative: oklch(0.378 0.0087 210);
- --luke-color-border-control: oklch(0.5325 0.0099 210);
- --luke-color-border-focus: oklch(0.72 0.13 255);
- --luke-color-border-neutral: oklch(0.43 0.0099 210);
- --luke-color-border-accent: oklch(0.43 0.066 200);
- --luke-color-border-info: oklch(0.43 0.0858 255);
- --luke-color-border-success: oklch(0.43 0.0858 150);
- --luke-color-border-warning: oklch(0.43 0.0858 80);
- --luke-color-border-danger: oklch(0.43 0.1056 25);
- --luke-depth-recessed: inset 0 2px 4px oklch(0.05 0.01 220 / 0.45), inset 0 -1px 0 oklch(0.8 0.01 220 / 0.12);
- --luke-depth-resting: 0 2px 0 oklch(0.05 0.01 220 / 0.5), 0 3px 5px -1px oklch(0.05 0.01 220 / 0.26);
- --luke-depth-raised: 0 3px 0 oklch(0.05 0.01 220 / 0.55), 0 5px 8px -2px oklch(0.05 0.01 220 / 0.32);
- --luke-depth-floating: 0 4px 12px oklch(0.05 0.01 220 / 0.38), 0 2px 4px oklch(0.05 0.01 220 / 0.22);
- --luke-depth-overlay: 0 12px 32px oklch(0.05 0.01 220 / 0.5), 0 4px 12px oklch(0.05 0.01 220 / 0.28);
- --luke-action-control-finish-recessed: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.08) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.04) 0%, transparent 70%);
- --luke-action-control-finish-resting: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.14) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.07) 0%, transparent 70%);
- --luke-action-control-finish-raised: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.18) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.1) 0%, transparent 70%);
- }
-}
-
-:where(:root[data-color-mode='light']),
-:where(:root) :where([data-color-mode='light']),
-.luke-ui-theme-tactile[data-color-mode='light'],
-.luke-ui-theme-tactile [data-color-mode='light'],
-[data-color-mode='light'] .luke-ui-theme-tactile {
- color-scheme: light;
- --luke-color-surface-canvas: oklch(0.985 0 0);
- --luke-color-surface-recessed: oklch(1 0 0);
- --luke-color-surface-floating: oklch(0.997 0 0);
- --luke-color-surface-overlay: oklch(1 0 0);
- --luke-color-scrim: oklch(0 0 0 / 0.2);
- --luke-color-loading-skeleton: oklch(0.735 0 0);
- --luke-color-text-primary: oklch(0.3 0 0);
- --luke-color-text-secondary: oklch(0.49 0 0);
- --luke-color-text-disabled: oklch(0.735 0 0);
- --luke-color-background-neutral-subtle-rest: oklch(0.955 0 0);
- --luke-color-background-neutral-subtle-hover: oklch(0.933 0 0);
- --luke-color-background-neutral-subtle-pressed: oklch(0.907 0 0);
- --luke-color-background-neutral-solid-rest: oklch(0.35 0 0);
- --luke-color-background-neutral-solid-hover: oklch(0.3 0 0);
- --luke-color-background-neutral-solid-pressed: oklch(0.3 0 0);
- --luke-color-background-accent-subtle-rest: oklch(0.955 0.0248 200);
- --luke-color-background-accent-subtle-hover: oklch(0.933 0.0354 200);
- --luke-color-background-accent-subtle-pressed: oklch(0.907 0.0442 200);
- --luke-color-background-accent-solid-rest: oklch(0.52 0.0884 200);
- --luke-color-background-accent-solid-hover: oklch(0.47 0.0799 200);
- --luke-color-background-accent-solid-pressed: oklch(0.47 0.0799 200);
- --luke-color-background-info-subtle-rest: oklch(0.955 0.0217 255);
- --luke-color-background-info-subtle-hover: oklch(0.933 0.0326 255);
- --luke-color-background-info-subtle-pressed: oklch(0.907 0.0457 255);
- --luke-color-background-info-solid-rest: oklch(0.52 0.16 255);
- --luke-color-background-info-solid-hover: oklch(0.47 0.1548 255);
- --luke-color-background-info-solid-pressed: oklch(0.47 0.1548 255);
- --luke-color-background-success-subtle-rest: oklch(0.955 0.0364 150);
- --luke-color-background-success-subtle-hover: oklch(0.933 0.052 150);
- --luke-color-background-success-subtle-pressed: oklch(0.907 0.065 150);
- --luke-color-background-success-solid-rest: oklch(0.5 0.13 150);
- --luke-color-background-success-solid-hover: oklch(0.45 0.1239 150);
- --luke-color-background-success-solid-pressed: oklch(0.45 0.1239 150);
- --luke-color-background-warning-subtle-rest: oklch(0.955 0.0364 75);
- --luke-color-background-warning-subtle-hover: oklch(0.933 0.0548 75);
- --luke-color-background-warning-subtle-pressed: oklch(0.907 0.07 75);
- --luke-color-background-warning-solid-rest: oklch(0.72 0.14 75);
- --luke-color-background-warning-solid-hover: oklch(0.67 0.14 75);
- --luke-color-background-warning-solid-pressed: oklch(0.67 0.14 75);
- --luke-color-background-danger-subtle-rest: oklch(0.955 0.0223 27);
- --luke-color-background-danger-subtle-hover: oklch(0.933 0.0339 27);
- --luke-color-background-danger-subtle-pressed: oklch(0.907 0.0481 27);
- --luke-color-background-danger-solid-rest: oklch(0.52 0.18 27);
- --luke-color-background-danger-solid-hover: oklch(0.47 0.18 27);
- --luke-color-background-danger-solid-pressed: oklch(0.47 0.18 27);
- --luke-color-foreground-neutral-rest: oklch(0.49 0 0);
- --luke-color-foreground-neutral-hover: oklch(0.3 0 0);
- --luke-color-foreground-neutral-on-solid: oklch(0.985 0 0);
- --luke-color-foreground-accent-rest: oklch(0.49 0.0486 200);
- --luke-color-foreground-accent-hover: oklch(0.3 0.0398 200);
- --luke-color-foreground-accent-on-solid: oklch(0.985 0 200);
- --luke-color-foreground-info-rest: oklch(0.49 0.088 255);
- --luke-color-foreground-info-hover: oklch(0.3 0.072 255);
- --luke-color-foreground-info-on-solid: oklch(0.985 0 255);
- --luke-color-foreground-success-rest: oklch(0.49 0.0715 150);
- --luke-color-foreground-success-hover: oklch(0.3 0.0585 150);
- --luke-color-foreground-success-on-solid: oklch(0.985 0 150);
- --luke-color-foreground-warning-rest: oklch(0.49 0.077 75);
- --luke-color-foreground-warning-hover: oklch(0.3 0.063 75);
- --luke-color-foreground-warning-on-solid: oklch(0.18 0.01 75);
- --luke-color-foreground-danger-rest: oklch(0.49 0.099 27);
- --luke-color-foreground-danger-hover: oklch(0.3 0.081 27);
- --luke-color-foreground-danger-on-solid: oklch(0.985 0 27);
- --luke-color-border-decorative: oklch(0.875 0 0);
- --luke-color-border-control: oklch(0.6525 0 0);
- --luke-color-border-focus: oklch(0.55 0.17 255);
- --luke-color-border-neutral: oklch(0.82 0 0);
- --luke-color-border-accent: oklch(0.82 0.0583 200);
- --luke-color-border-info: oklch(0.82 0.0915 255);
- --luke-color-border-success: oklch(0.82 0.0858 150);
- --luke-color-border-warning: oklch(0.82 0.0924 75);
- --luke-color-border-danger: oklch(0.82 0.101 27);
- --luke-depth-recessed: inset 0 2px 3px oklch(0.3 0.03 220 / 0.18), inset 0 -1px 0 oklch(0.98 0.03 220 / 0.65);
- --luke-depth-resting: 0 2px 0 oklch(0.3 0.03 220 / 0.28), 0 3px 5px -1px oklch(0.3 0.03 220 / 0.16);
- --luke-depth-raised: 0 3px 0 oklch(0.3 0.03 220 / 0.3), 0 5px 8px -2px oklch(0.3 0.03 220 / 0.2);
- --luke-depth-floating: 0 4px 12px oklch(0.3 0.03 220 / 0.16), 0 2px 4px oklch(0.3 0.03 220 / 0.1);
- --luke-depth-overlay: 0 12px 32px oklch(0.3 0.03 220 / 0.2), 0 4px 12px oklch(0.3 0.03 220 / 0.12);
- --luke-action-control-finish-recessed: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.12) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.06) 0%, transparent 70%);
- --luke-action-control-finish-resting: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.24) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.12) 0%, transparent 70%);
- --luke-action-control-finish-raised: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.3) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.16) 0%, transparent 70%);
-}
-
-:where(:root[data-color-mode='dark']),
-:where(:root) :where([data-color-mode='dark']),
-.luke-ui-theme-tactile[data-color-mode='dark'],
-.luke-ui-theme-tactile [data-color-mode='dark'],
-[data-color-mode='dark'] .luke-ui-theme-tactile {
- color-scheme: dark;
- --luke-color-surface-canvas: oklch(0.25 0.015 210);
- --luke-color-surface-recessed: oklch(0.225 0.015 210);
- --luke-color-surface-floating: oklch(0.32 0.015 210);
- --luke-color-surface-overlay: oklch(0.34 0.015 210);
- --luke-color-scrim: oklch(0 0 0 / 0.4);
- --luke-color-loading-skeleton: oklch(0.5 0.0117 210);
- --luke-color-text-primary: oklch(0.94 0.0067 210);
- --luke-color-text-secondary: oklch(0.76 0.0083 210);
- --luke-color-text-disabled: oklch(0.5 0.0117 210);
- --luke-color-background-neutral-subtle-rest: oklch(0.29 0.0042 210);
- --luke-color-background-neutral-subtle-hover: oklch(0.315 0.006 210);
- --luke-color-background-neutral-subtle-pressed: oklch(0.342 0.0075 210);
- --luke-color-background-neutral-solid-rest: oklch(0.82 0.015 210);
- --luke-color-background-neutral-solid-hover: oklch(0.87 0.015 210);
- --luke-color-background-neutral-solid-pressed: oklch(0.87 0.015 210);
- --luke-color-background-accent-subtle-rest: oklch(0.29 0.028 200);
- --luke-color-background-accent-subtle-hover: oklch(0.315 0.04 200);
- --luke-color-background-accent-subtle-pressed: oklch(0.342 0.05 200);
- --luke-color-background-accent-solid-rest: oklch(0.75 0.1 200);
- --luke-color-background-accent-solid-hover: oklch(0.8 0.1 200);
- --luke-color-background-accent-solid-pressed: oklch(0.8 0.1 200);
- --luke-color-background-info-subtle-rest: oklch(0.29 0.0364 255);
- --luke-color-background-info-subtle-hover: oklch(0.315 0.052 255);
- --luke-color-background-info-subtle-pressed: oklch(0.342 0.065 255);
- --luke-color-background-info-solid-rest: oklch(0.72 0.13 255);
- --luke-color-background-info-solid-hover: oklch(0.77 0.1192 255);
- --luke-color-background-info-solid-pressed: oklch(0.77 0.1192 255);
- --luke-color-background-success-subtle-rest: oklch(0.29 0.0364 150);
- --luke-color-background-success-subtle-hover: oklch(0.315 0.052 150);
- --luke-color-background-success-subtle-pressed: oklch(0.342 0.065 150);
- --luke-color-background-success-solid-rest: oklch(0.74 0.13 150);
- --luke-color-background-success-solid-hover: oklch(0.79 0.13 150);
- --luke-color-background-success-solid-pressed: oklch(0.79 0.13 150);
- --luke-color-background-warning-subtle-rest: oklch(0.29 0.0364 80);
- --luke-color-background-warning-subtle-hover: oklch(0.315 0.052 80);
- --luke-color-background-warning-subtle-pressed: oklch(0.342 0.065 80);
- --luke-color-background-warning-solid-rest: oklch(0.78 0.13 80);
- --luke-color-background-warning-solid-hover: oklch(0.83 0.13 80);
- --luke-color-background-warning-solid-pressed: oklch(0.83 0.13 80);
- --luke-color-background-danger-subtle-rest: oklch(0.29 0.0448 25);
- --luke-color-background-danger-subtle-hover: oklch(0.315 0.06 25);
- --luke-color-background-danger-subtle-pressed: oklch(0.342 0.075 25);
- --luke-color-background-danger-solid-rest: oklch(0.72 0.16 25);
- --luke-color-background-danger-solid-hover: oklch(0.77 0.1356 25);
- --luke-color-background-danger-solid-pressed: oklch(0.77 0.1356 25);
- --luke-color-foreground-neutral-rest: oklch(0.76 0.0083 210);
- --luke-color-foreground-neutral-hover: oklch(0.94 0.0067 210);
- --luke-color-foreground-neutral-on-solid: oklch(0.18 0.01 210);
- --luke-color-foreground-accent-rest: oklch(0.76 0.055 200);
- --luke-color-foreground-accent-hover: oklch(0.94 0.045 200);
- --luke-color-foreground-accent-on-solid: oklch(0.18 0.01 200);
- --luke-color-foreground-info-rest: oklch(0.76 0.0715 255);
- --luke-color-foreground-info-hover: oklch(0.94 0.0291 255);
- --luke-color-foreground-info-on-solid: oklch(0.18 0.01 255);
- --luke-color-foreground-success-rest: oklch(0.76 0.0715 150);
- --luke-color-foreground-success-hover: oklch(0.94 0.0585 150);
- --luke-color-foreground-success-on-solid: oklch(0.18 0.01 150);
- --luke-color-foreground-warning-rest: oklch(0.76 0.0715 80);
- --luke-color-foreground-warning-hover: oklch(0.94 0.0552 80);
- --luke-color-foreground-warning-on-solid: oklch(0.18 0.01 80);
- --luke-color-foreground-danger-rest: oklch(0.76 0.088 25);
- --luke-color-foreground-danger-hover: oklch(0.94 0.0301 25);
- --luke-color-foreground-danger-on-solid: oklch(0.18 0.01 25);
- --luke-color-border-decorative: oklch(0.378 0.0087 210);
- --luke-color-border-control: oklch(0.5325 0.0099 210);
- --luke-color-border-focus: oklch(0.72 0.13 255);
- --luke-color-border-neutral: oklch(0.43 0.0099 210);
- --luke-color-border-accent: oklch(0.43 0.066 200);
- --luke-color-border-info: oklch(0.43 0.0858 255);
- --luke-color-border-success: oklch(0.43 0.0858 150);
- --luke-color-border-warning: oklch(0.43 0.0858 80);
- --luke-color-border-danger: oklch(0.43 0.1056 25);
- --luke-depth-recessed: inset 0 2px 4px oklch(0.05 0.01 220 / 0.45), inset 0 -1px 0 oklch(0.8 0.01 220 / 0.12);
- --luke-depth-resting: 0 2px 0 oklch(0.05 0.01 220 / 0.5), 0 3px 5px -1px oklch(0.05 0.01 220 / 0.26);
- --luke-depth-raised: 0 3px 0 oklch(0.05 0.01 220 / 0.55), 0 5px 8px -2px oklch(0.05 0.01 220 / 0.32);
- --luke-depth-floating: 0 4px 12px oklch(0.05 0.01 220 / 0.38), 0 2px 4px oklch(0.05 0.01 220 / 0.22);
- --luke-depth-overlay: 0 12px 32px oklch(0.05 0.01 220 / 0.5), 0 4px 12px oklch(0.05 0.01 220 / 0.28);
- --luke-action-control-finish-recessed: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.08) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.04) 0%, transparent 70%);
- --luke-action-control-finish-resting: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.14) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.07) 0%, transparent 70%);
- --luke-action-control-finish-raised: radial-gradient(80% 70% at 50% 0%, rgb(255 255 255 / 0.18) 0%, transparent 100%), radial-gradient(70% 45% at 50% 110%, rgb(255 255 255 / 0.1) 0%, transparent 70%);
-}
diff --git a/packages/@luke-ui/react/src/theme/build-theme.test.ts b/packages/@luke-ui/react/src/theme/build-theme.test.ts
index 629257c2..b43f4988 100644
--- a/packages/@luke-ui/react/src/theme/build-theme.test.ts
+++ b/packages/@luke-ui/react/src/theme/build-theme.test.ts
@@ -1,4 +1,3 @@
-import { readFile } from 'node:fs/promises';
import { describe, expect, it } from 'vite-plus/test';
import {
extractValue,
@@ -222,23 +221,7 @@ describe('bundled themes meet WCAG 2.2 AA', () => {
// stricter than a static "skeleton !== canvas and contrastRatio > 1" check would ever be — the
// latter is a tautology once the two colours are already known to differ, so it added no coverage.
-// Regression goldens: the exact `buildTheme` output for the bundled themes under the wired-in
-// scale/elevation/semantic-map pipeline. Asserted byte-identical so any later generator change
-// is a reviewed, deliberate diff.
-describe('v2 regression goldens', () => {
- const v2Goldens = {
- paper: new URL('./__fixtures__/v2-goldens/paper.v2.css', import.meta.url),
- tactile: new URL('./__fixtures__/v2-goldens/tactile.v2.css', import.meta.url),
- } as const;
-
- it('keeps every generated token byte-identical to the v2 baseline', async () => {
- const goldenTactile = await readFile(v2Goldens.tactile, 'utf8');
- const goldenPaper = await readFile(v2Goldens.paper, 'utf8');
-
- expect(buildTheme(tactileFoundation)).toBe(goldenTactile);
- expect(buildTheme(paperFoundation)).toBe(goldenPaper);
- });
-
+describe('buildTheme public motion surface', () => {
// The numbered duration scale in `motion.ts` is private in the same sense as the 12-step colour
// scale: resolved in TypeScript and never published as a custom property. Only the three
// role-named durations reach the stylesheet, so a numbered variable here is a leak.
diff --git a/packages/@luke-ui/react/src/theme/define-theme.test.ts b/packages/@luke-ui/react/src/theme/define-theme.test.ts
index 6a6de7e0..78d98e7b 100644
--- a/packages/@luke-ui/react/src/theme/define-theme.test.ts
+++ b/packages/@luke-ui/react/src/theme/define-theme.test.ts
@@ -335,24 +335,4 @@ describe('defineTheme emits the full contract for the bundled themes', () => {
}
});
}
-
- it('emits the expected v2 token values for known Tactile leaves', () => {
- const css = defineTheme(tactileTheme);
- for (const declaration of [
- // The canvas IS the resolved background (Tactile's light neutral), unchanged from pre-v2.
- '--luke-color-surface-canvas: oklch(0.985 0 0);',
- // Global text/skeleton now alias the neutral family: primary = neutral 12 (light) / dark 12,
- // disabled = neutral 8, the neutral subtle surface = neutral 3.
- '--luke-color-text-primary: oklch(0.3 0 0);',
- '--luke-color-text-disabled: oklch(0.5 0.0117 210);',
- '--luke-color-background-neutral-subtle-rest: oklch(0.955 0 0);',
- // The danger solid stays the authored default source (danger step 9 lands on it verbatim).
- '--luke-color-background-danger-solid-rest: oklch(0.52 0.18 27);',
- ]) {
- expect(css).toContain(declaration);
- }
- // Scrim is passed through: black at the mode-aware default alpha.
- expect(css).toContain('--luke-color-scrim: oklch(0 0 0 / 0.2);');
- expect(css).toContain('--luke-color-scrim: oklch(0 0 0 / 0.4);');
- });
});
diff --git a/packages/@luke-ui/react/src/theme/stylesheet.test.ts b/packages/@luke-ui/react/src/theme/stylesheet.test.ts
index b47d8581..4bfd46b9 100644
--- a/packages/@luke-ui/react/src/theme/stylesheet.test.ts
+++ b/packages/@luke-ui/react/src/theme/stylesheet.test.ts
@@ -131,11 +131,11 @@ describe('buildTheme output', () => {
expect(css).toContain('--luke-control-size-small');
expect(css).toContain('--luke-motion-easing-standard');
expect(css).toContain('--luke-font-weight-body');
- expect(css).toContain('--luke-font-100-font-size: 12px');
- expect(css).toContain('--luke-font-300-line-height: 24px');
- expect(css).toContain('--luke-font-900-letter-spacing: -0.025em');
- expect(css).toContain('--luke-icon-size-xsmall: 16px');
- expect(css).toContain('--luke-icon-size-large: 32px');
+ expect(css).toContain('--luke-font-100-font-size:');
+ expect(css).toContain('--luke-font-300-line-height:');
+ expect(css).toContain('--luke-font-900-letter-spacing:');
+ expect(css).toContain('--luke-icon-size-xsmall:');
+ expect(css).toContain('--luke-icon-size-large:');
});
it('emits the public spacing scale in every built-in theme', () => {
@@ -194,10 +194,9 @@ describe('buildTheme output', () => {
const paperResting = extractValue(paperBlock, '--luke-depth-resting');
const paperRaised = extractValue(paperBlock, '--luke-depth-raised');
const paperFinish = extractValue(paperBlock, '--luke-action-control-finish-resting');
+ const tactileResting = extractValue(tactileBlock, '--luke-depth-resting');
- expect(extractValue(tactileBlock, '--luke-depth-resting')).toContain('0 2px 0');
- expect(paperResting).not.toContain('0 2px 0');
- expect(paperRaised).not.toContain('0 3px 0');
+ expect(paperResting).not.toBe(tactileResting);
expect(paperResting.split(', ')).toHaveLength(2);
expect(paperRaised.split(', ')).toHaveLength(2);
expect(paperRaised).not.toBe(paperResting);
@@ -240,57 +239,20 @@ describe('buildTheme defaults', () => {
expect(css).toContain('--luke-color-border-focus: oklch(');
});
- it('preserves every Capsize trim for each curated font family and size', () => {
- const expectedTrims = {
- 'apple-system': {
- '100': { baselineTrim: '-0.2887em', capHeightTrim: '-0.34em' },
- '200': { baselineTrim: '-0.3364em', capHeightTrim: '-0.3876em' },
- '300': { baselineTrim: '-0.3721em', capHeightTrim: '-0.4233em' },
- '400': { baselineTrim: '-0.3443em', capHeightTrim: '-0.3956em' },
- '500': { baselineTrim: '-0.3221em', capHeightTrim: '-0.3733em' },
- '600': { baselineTrim: '-0.2471em', capHeightTrim: '-0.2983em' },
- '700': { baselineTrim: '-0.2649em', capHeightTrim: '-0.3162em' },
- '800': { baselineTrim: '-0.1935em', capHeightTrim: '-0.2448em' },
- '900': { baselineTrim: '-0.1221em', capHeightTrim: '-0.1733em' },
- },
- 'dm-sans': {
- '100': { baselineTrim: '-0.3257em', capHeightTrim: '-0.3077em' },
- '200': { baselineTrim: '-0.3733em', capHeightTrim: '-0.3553em' },
- '300': { baselineTrim: '-0.409em', capHeightTrim: '-0.391em' },
- '400': { baselineTrim: '-0.3812em', capHeightTrim: '-0.3632em' },
- '500': { baselineTrim: '-0.359em', capHeightTrim: '-0.341em' },
- '600': { baselineTrim: '-0.284em', capHeightTrim: '-0.266em' },
- '700': { baselineTrim: '-0.3019em', capHeightTrim: '-0.2839em' },
- '800': { baselineTrim: '-0.2304em', capHeightTrim: '-0.2124em' },
- '900': { baselineTrim: '-0.159em', capHeightTrim: '-0.141em' },
- },
- inter: {
- '100': { baselineTrim: '-0.3029em', capHeightTrim: '-0.3029em' },
- '200': { baselineTrim: '-0.3505em', capHeightTrim: '-0.3505em' },
- '300': { baselineTrim: '-0.3862em', capHeightTrim: '-0.3862em' },
- '400': { baselineTrim: '-0.3585em', capHeightTrim: '-0.3585em' },
- '500': { baselineTrim: '-0.3362em', capHeightTrim: '-0.3362em' },
- '600': { baselineTrim: '-0.2612em', capHeightTrim: '-0.2612em' },
- '700': { baselineTrim: '-0.2791em', capHeightTrim: '-0.2791em' },
- '800': { baselineTrim: '-0.2077em', capHeightTrim: '-0.2077em' },
- '900': { baselineTrim: '-0.1362em', capHeightTrim: '-0.1362em' },
- },
- } as const;
+ it('emits Capsize trim variables for every curated font family and size', () => {
+ const fontFamilies = ['apple-system', 'dm-sans', 'inter'] as const;
+ const sizes = ['100', '200', '300', '400', '500', '600', '700', '800', '900'] as const;
- for (const [fontFamily, trims] of Object.entries(expectedTrims)) {
+ for (const fontFamily of fontFamilies) {
const css = buildTheme({
...minimalFoundation,
- typography: { fontFamily: fontFamily as keyof typeof expectedTrims },
+ typography: { fontFamily },
});
const identity = splitBlocks(css).identity;
- for (const [size, expected] of Object.entries(trims)) {
- expect(extractValue(identity, `--luke-font-${size}-cap-height-trim`)).toBe(
- expected.capHeightTrim,
- );
- expect(extractValue(identity, `--luke-font-${size}-baseline-trim`)).toBe(
- expected.baselineTrim,
- );
+ for (const size of sizes) {
+ expect(identity).toContain(`--luke-font-${size}-cap-height-trim:`);
+ expect(identity).toContain(`--luke-font-${size}-baseline-trim:`);
}
}
});
diff --git a/packages/@luke-ui/react/src/theme/theme-diagnostics.stories.tsx b/packages/@luke-ui/react/src/theme/theme-diagnostics.stories.tsx
index ca60c5d8..c0272be7 100644
--- a/packages/@luke-ui/react/src/theme/theme-diagnostics.stories.tsx
+++ b/packages/@luke-ui/react/src/theme/theme-diagnostics.stories.tsx
@@ -1,4 +1,4 @@
-import { expect, userEvent, within } from 'storybook/test';
+import { userEvent } from 'storybook/test';
import preview from '../../.storybook/preview.js';
import { ThemeDiagnosticsInspector } from './theme-diagnostics-inspector.js';
@@ -10,21 +10,11 @@ const meta = preview.meta({
/**
* Inspect compiler diagnostics for the bundled themes. This view is not public API.
+ * Click Paper so the accessibility scan covers a second theme profile.
*/
export const Inspector = meta.story({
play: async ({ canvas }) => {
- await expect(canvas.getByRole('heading', { name: 'Light mode' })).toBeInTheDocument();
- await expect(canvas.getByRole('heading', { name: 'Dark mode' })).toBeInTheDocument();
-
- const swatches = canvas.getAllByRole('img');
- await expect(swatches.length).toBeGreaterThan(0);
-
- const lightSolidAnchorTable = canvas.getAllByRole('table')[0]!;
- const accentRow = within(lightSolidAnchorTable).getByRole('row', { name: /^accent / });
- const tactileAccentDiagnostics = accentRow.textContent;
-
await userEvent.click(canvas.getByRole('radio', { name: 'paper' }));
- await expect(accentRow).not.toHaveTextContent(tactileAccentDiagnostics ?? '');
},
});
diff --git a/packages/@luke-ui/react/src/theme/theme.browser.test.tsx b/packages/@luke-ui/react/src/theme/theme.browser.test.tsx
index c37da69a..63c39dbd 100644
--- a/packages/@luke-ui/react/src/theme/theme.browser.test.tsx
+++ b/packages/@luke-ui/react/src/theme/theme.browser.test.tsx
@@ -65,9 +65,7 @@ test('renders components from static CSS without theme context or injected style
mounted.push({ container: scope, root });
act(() => root.render());
- const button = page.getByRole('button', { name: 'Continue' }).element();
- expect(getComputedStyle(button).display).toBe('inline-flex');
expect(document.querySelectorAll('style')).toHaveLength(styleCount);
});
diff --git a/packages/@luke-ui/react/src/theme/token-board.stories.tsx b/packages/@luke-ui/react/src/theme/token-board.stories.tsx
index 2553f9b1..2b7ef573 100644
--- a/packages/@luke-ui/react/src/theme/token-board.stories.tsx
+++ b/packages/@luke-ui/react/src/theme/token-board.stories.tsx
@@ -1,6 +1,4 @@
-import { expect } from 'storybook/test';
import preview from '../../.storybook/preview.js';
-import { flattenThemeContract } from './contract.js';
import { TokenBoard } from './token-board.js';
const meta = preview.meta({
@@ -9,16 +7,9 @@ const meta = preview.meta({
title: 'Theme/Token board',
});
-const contractLeafCount = flattenThemeContract().length;
-
/**
* Compare every resolved contract leaf across themes and colour modes. The visual test detects
- * mapping changes even when no component consumes the affected leaf.
+ * mapping changes even when no component consumes the affected leaf. Leaf completeness lives in
+ * `token-board.test.ts`.
*/
-export const Board = meta.story({
- play: async ({ canvasElement }) => {
- // Derive the expected count from the contract so this assertion detects missing samples.
- const swatches = canvasElement.querySelectorAll('[role="img"]');
- await expect(swatches.length).toBe(contractLeafCount);
- },
-});
+export const Board = meta.story({});
diff --git a/packages/@luke-ui/react/src/types/public-props.test.ts b/packages/@luke-ui/react/src/types/public-props.test.ts
deleted file mode 100644
index abb98ca7..00000000
--- a/packages/@luke-ui/react/src/types/public-props.test.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { expectTypeOf, test } from 'vite-plus/test';
-import type { ButtonProps as PrimitiveButtonProps } from '../button/primitive/index.js';
-import type { LinkProps } from '../link/index.js';
-import type { TextProps } from '../text/index.js';
-import type { DistributiveOmit } from './distributive-omit.js';
-
-type ExampleUnion =
- | { kind: 'button'; onPress: () => void; slot?: string }
- | { href: string; kind: 'link'; slot?: string };
-
-const buttonProps = {
- children: ({ isPending }) => (isPending ? 'Saving' : 'Save'),
- className: ({ isPressed }) => (isPressed ? 'pressed' : 'resting'),
- onPress: (event) => event.continuePropagation(),
-} satisfies PrimitiveButtonProps;
-
-const linkProps = {
- children: 'Account',
- className: ({ isHovered }) => (isHovered ? 'hovered' : 'resting'),
- href: '/account',
-} satisfies LinkProps;
-
-const textProps = {
- children: 'Account summary',
- elementType: 'span',
- id: 'account-summary-label',
- onClick: () => undefined,
-} satisfies TextProps;
-
-// Type assertions are compile-time only.
-// oxlint-disable-next-line vitest/expect-expect
-test('preserves React Aria render props and native DOM props', () => {
- expectTypeOf(buttonProps).toExtend();
- expectTypeOf(linkProps).toExtend();
- expectTypeOf(textProps).toExtend();
-});
-
-// Type assertions are compile-time only.
-// oxlint-disable-next-line vitest/expect-expect
-test('distributes over unions and rejects unknown omit keys', () => {
- expectTypeOf>().toEqualTypeOf<
- { kind: 'button'; onPress: () => void } | { href: string; kind: 'link' }
- >();
-
- // @ts-expect-error Unknown keys must not silently pass through DistributiveOmit.
- expectTypeOf>();
-});
diff --git a/vite.config.ts b/vite.config.ts
index 02e698c7..dbf32780 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -14,9 +14,6 @@ export default defineConfig({
'**/dist/**',
'**/routeTree.gen.ts',
'**/storybook-static/**',
- // Golden fixtures are frozen `buildTheme` output, asserted byte-identical in tests; they must
- // never be reformatted.
- '**/__fixtures__/v2-goldens/**',
'node_modules',
],
jsxSingleQuote: false,