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,