diff --git a/apps/docs/content/docs/components/forms/combobox-field.mdx b/apps/docs/content/docs/components/forms/combobox-field.mdx index 657f8338..a4dc4fa6 100644 --- a/apps/docs/content/docs/components/forms/combobox-field.mdx +++ b/apps/docs/content/docs/components/forms/combobox-field.mdx @@ -82,10 +82,9 @@ Pass `onLoadMore` for incremental results. It adds the built-in load-more row an `ComboboxField` checks the device screen width, not the browser viewport width. Narrowing a desktop browser window does not switch to the tray. -Below the `small` breakpoint (640px), the field shows a button that displays the selected option. -Someone taps the button to open a modal tray with a focused search input. The tray has a fixed -height and locks page scroll. The tray motion and desktop popover fade respect reduced-motion -preferences. +Below the `bp640` breakpoint, the field shows a button that displays the selected option. Someone +taps the button to open a modal tray with a focused search input. The tray has a fixed height and +locks page scroll. The tray motion and desktop popover fade respect reduced-motion preferences. ## Accessibility diff --git a/apps/docs/content/docs/docs/layout.mdx b/apps/docs/content/docs/docs/layout.mdx index 4e6accaa..d03df561 100644 --- a/apps/docs/content/docs/docs/layout.mdx +++ b/apps/docs/content/docs/docs/layout.mdx @@ -1,6 +1,6 @@ --- title: Layout -description: Build responsive structure with Box, layout utilities, and named breakpoints. +description: Build responsive structure with Box, layout utilities, and responsive breakpoints. --- ## Box @@ -10,7 +10,7 @@ Box is the layout component. Use it to: - Provide spacing to child elements. - Impose sizing constraints on content. - Control layout behaviour within flex and grid containers. -- Hide content at a screen size with its responsive display prop. +- Hide content responsively. ```tsx import { Box } from '@luke-ui/react/box'; @@ -26,8 +26,8 @@ element needs the same responsive layout properties without wrapping it in a `Bo ## Responsive values -Properties passed to `Box` and Sprinkles accept a direct value when a layout property stays -constant, or an object keyed by breakpoint when it changes at a named container inline size. +Properties passed to `Box` and Sprinkles accept either a direct value or an object keyed by +breakpoint. Responsive values resolve against the nearest ancestor size container. Luke UI uses the document root as the fallback. Add `container-type: inline-size` to a nearer ancestor when descendants should @@ -43,24 +43,24 @@ pixels when a scrollbar is present. | Breakpoint | Minimum container inline size | | ---------- | ----------------------------- | | `initial` | 0px (base) | -| `small` | 640px | -| `medium` | 768px | -| `large` | 1024px | -| `xlarge` | 1280px | -| `xxlarge` | 1536px | +| `bp640` | 640px | +| `bp768` | 768px | +| `bp1024` | 1024px | +| `bp1280` | 1280px | +| `bp1536` | 1536px | Each breakpoint is a fixed constant, not a theme token. A custom theme cannot change these sizes. Every breakpoint is a minimum inline size, so there is no maximum size or range condition. A value -set at `small` also applies at `medium` and above, unless a later breakpoint overrides it. Values +set at `bp640` also applies at `bp768` and above, unless a later breakpoint overrides it. Values cascade up from `initial`, so specify only the points where the layout changes. ```tsx - + {children} ``` -This example stacks the children by default, then places them in a row from `medium`. +This example stacks the children by default, then places them in a row from `bp768`. diff --git a/apps/docs/src/components/example-block.browser.test.tsx b/apps/docs/src/components/example-block.browser.test.tsx index e575336b..cc95f1aa 100644 --- a/apps/docs/src/components/example-block.browser.test.tsx +++ b/apps/docs/src/components/example-block.browser.test.tsx @@ -59,8 +59,6 @@ test('narrowing the preview panel flips a responsive example below its container await page.viewport(1000, 800); await renderExampleBlock(); - // The example's `medium` breakpoint is 768px; starting above it puts the - // items in a row. await expect.poll(flexDirection).toBe('row'); await commands.dragFromSeparator(0, -500); diff --git a/apps/docs/src/examples/box/responsive-layout.tsx b/apps/docs/src/examples/box/responsive-layout.tsx index 13bb2596..cd847e27 100644 --- a/apps/docs/src/examples/box/responsive-layout.tsx +++ b/apps/docs/src/examples/box/responsive-layout.tsx @@ -4,7 +4,7 @@ import { vars } from '@luke-ui/react/theme'; export default () => { return ( - + diff --git a/apps/docs/src/lib/playground-default-code.tsx b/apps/docs/src/lib/playground-default-code.tsx index 85a5ff73..d965836d 100644 --- a/apps/docs/src/lib/playground-default-code.tsx +++ b/apps/docs/src/lib/playground-default-code.tsx @@ -48,9 +48,9 @@ export default () => { maxInlineSize="26rem" // Utility props take an object keyed by breakpoint. Breakpoints are // container queries resolved against the theme root, so this follows the - // preview width. - paddingBlock={{ initial: '800', medium: '1600' }} - paddingInline={{ initial: '600', medium: '1200' }} + // preview's inline size. + paddingBlock={{ initial: '800', bp768: '1600' }} + paddingInline={{ initial: '600', bp768: '1200' }} render={(props) =>
} > Sign in diff --git a/docs/STYLING.md b/docs/STYLING.md index 5b82dad8..6637440f 100644 --- a/docs/STYLING.md +++ b/docs/STYLING.md @@ -76,10 +76,11 @@ with no class and no JS required. Neither step injects styles at runtime. - `theme/motion.ts`: the private ordinal duration scale (`MOTION_DURATION_SCALE`) behind the public `motion.duration` roles in `token-values.ts`. It is resolved in TypeScript and never emitted, so no `--luke-motion-duration-*` custom property exists. -- `theme/breakpoints.ts`: the private responsive breakpoint widths, in pixels. Like `motion.ts`, it - is a plain module with no Vanilla Extract import, resolved in TypeScript and never emitted as a - custom property, because a media query cannot read one. The styling utilities turn the widths into - media queries, and `useIsMobileDevice` reads the same values for its mobile threshold. +- `theme/breakpoints.ts`: the private responsive breakpoint inline sizes, in pixels. Like + `motion.ts`, it is a plain module with no Vanilla Extract import, resolved in TypeScript and never + emitted as a custom property, because a container query cannot read one. The styling utilities + turn the inline sizes into container queries, and `useIsMobileDevice` reads the same values for + its mobile threshold. - `theme/elevation.ts`: the mode-aware elevation surface generator (`generateSurfaces`), where `surfaces.canvas` is always exactly the resolved `background`. - `theme/semantic-map.ts`: the one default mapping (`mapSemanticColors`) from generated families and @@ -363,13 +364,13 @@ only overrides need to be specified. ```tsx const responsive = createSprinkles({ display: 'flex', - flexDirection: { initial: 'column', medium: 'row' }, - gap: { initial: '300', medium: '600' }, + flexDirection: { initial: 'column', bp768: 'row' }, + gap: { initial: '300', bp768: '600' }, }); ``` -The retained breakpoints are `initial` (base), `small` (640px), `medium` (768px), `large` (1024px), -`xlarge` (1280px), and `xxlarge` (1536px). +The retained breakpoints are `initial` (base), `bp640` (640px), `bp768` (768px), `bp1024` (1024px), +`bp1280` (1280px), and `bp1536` (1536px). ## React Aria `render` prop diff --git a/packages/@luke-ui/react/src/box/box.browser.test.tsx b/packages/@luke-ui/react/src/box/box.browser.test.tsx index 119c37c1..7d4012b5 100644 --- a/packages/@luke-ui/react/src/box/box.browser.test.tsx +++ b/packages/@luke-ui/react/src/box/box.browser.test.tsx @@ -22,8 +22,8 @@ test('renders a responsive layout at the retained breakpoints', async () => { const { locator } = render( First item Second item diff --git a/packages/@luke-ui/react/src/box/box.stories.tsx b/packages/@luke-ui/react/src/box/box.stories.tsx index bf4bde0d..54df0f66 100644 --- a/packages/@luke-ui/react/src/box/box.stories.tsx +++ b/packages/@luke-ui/react/src/box/box.stories.tsx @@ -20,9 +20,9 @@ export const Default = meta.story({ ), display: 'flex', - flexDirection: { initial: 'column', medium: 'row' }, - gap: { initial: '200', medium: '600' }, - padding: { initial: '300', medium: '600' }, + flexDirection: { initial: 'column', bp768: 'row' }, + gap: { initial: '200', bp768: '600' }, + padding: { initial: '300', bp768: '600' }, style: { backgroundColor: vars.color.surface.recessed }, } satisfies Partial, }); diff --git a/packages/@luke-ui/react/src/overlays/use-is-mobile-device.ts b/packages/@luke-ui/react/src/overlays/use-is-mobile-device.ts index b909b316..e6aabf04 100644 --- a/packages/@luke-ui/react/src/overlays/use-is-mobile-device.ts +++ b/packages/@luke-ui/react/src/overlays/use-is-mobile-device.ts @@ -7,7 +7,7 @@ import { breakpoints } from '../theme/breakpoints.js'; * * This reads the device screen width on purpose, not the viewport width. Narrowing * a desktop browser window must not swap the overlay architecture underneath the - * user. Mobile means a screen below the shared `small` breakpoint. + * user. Mobile means a screen narrower than the shared 640px breakpoint. * * Based on Apache-2.0 `packages/@adobe/react-spectrum/src/utils/useIsMobileDevice.ts`. */ @@ -26,7 +26,7 @@ function subscribe(onStoreChange: StoreListener): StoreListener { } function getSnapshot(): boolean { - return window.screen.width < breakpoints.small; + return window.screen.width < breakpoints.bp640; } function getServerSnapshot(): boolean { diff --git a/packages/@luke-ui/react/src/styles/utilities-emitted.test-d.ts b/packages/@luke-ui/react/src/styles/utilities-emitted.test-d.ts index 1de8bc67..a6733fc8 100644 --- a/packages/@luke-ui/react/src/styles/utilities-emitted.test-d.ts +++ b/packages/@luke-ui/react/src/styles/utilities-emitted.test-d.ts @@ -105,7 +105,7 @@ test('constrained appearance props do not accept arbitrary CSS values', async () expect(propertyType(declaration, 'borderStyle')).toContain('solid'); }); -test('responsive conditions expose the theme breakpoints in size order', async () => { +test('responsive conditions preserve breakpoint declaration order', async () => { const declaration = await readUtilitiesDeclaration(); // Conditions are matched by name and checked in declaration order. const expected = ['initial', ...Object.keys(breakpoints)]; 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 fbac5556..5a97fcd4 100644 --- a/packages/@luke-ui/react/src/styles/utilities.browser.test.ts +++ b/packages/@luke-ui/react/src/styles/utilities.browser.test.ts @@ -20,22 +20,22 @@ test('applies every retained breakpoint responsively', async () => { createSprinkles({ padding: { initial: '100', - large: '400', - medium: '300', - small: '200', - xlarge: '600', - xxlarge: '800', + bp640: '200', + bp768: '300', + bp1024: '400', + bp1280: '600', + bp1536: '800', }, }), ); const viewports = [ { width: 320, property: '--luke-space-100' }, - { width: breakpoints.small, property: '--luke-space-200' }, - { width: breakpoints.medium, property: '--luke-space-300' }, - { width: breakpoints.large, property: '--luke-space-400' }, - { width: breakpoints.xlarge, property: '--luke-space-600' }, - { width: breakpoints.xxlarge, property: '--luke-space-800' }, + { width: breakpoints.bp640, property: '--luke-space-200' }, + { width: breakpoints.bp768, property: '--luke-space-300' }, + { width: breakpoints.bp1024, property: '--luke-space-400' }, + { width: breakpoints.bp1280, property: '--luke-space-600' }, + { width: breakpoints.bp1536, property: '--luke-space-800' }, ] as const; for (const viewport of viewports) { @@ -52,9 +52,9 @@ test('resolves against a nearer explicit container instead of the root', async ( const wrapper = document.body.appendChild(document.createElement('div')); mounted.push(wrapper); wrapper.style.containerType = 'inline-size'; - wrapper.style.inlineSize = `${breakpoints.small - 1}px`; + wrapper.style.inlineSize = `${breakpoints.bp640 - 1}px`; - const generated = createSprinkles({ padding: { initial: '100', small: '200' } }); + const generated = createSprinkles({ padding: { initial: '100', bp640: '200' } }); const element = mount(generated, wrapper); const computedStyle = getComputedStyle(element); @@ -65,11 +65,11 @@ test('resolves against the root content box, not the viewport width', async () = // A scrollbar takes its width out of the root's content box, so the root container's inline // size measures narrower than the viewport. Root padding reproduces that narrowing without // depending on the headless browser rendering a scrollbar. - await page.viewport(breakpoints.small, 800); + await page.viewport(breakpoints.bp640, 800); const rootStyle = document.head.appendChild(document.createElement('style')); rootStyle.textContent = ':root { padding-inline-end: 17px; }'; - const generated = createSprinkles({ padding: { initial: '100', small: '200' } }); + const generated = createSprinkles({ padding: { initial: '100', bp640: '200' } }); const element = mount(generated); const computedStyle = getComputedStyle(element); diff --git a/packages/@luke-ui/react/src/styles/utilities.css.ts b/packages/@luke-ui/react/src/styles/utilities.css.ts index 9389c7fc..1b3878d9 100644 --- a/packages/@luke-ui/react/src/styles/utilities.css.ts +++ b/packages/@luke-ui/react/src/styles/utilities.css.ts @@ -7,17 +7,17 @@ import { SEMANTIC_ROLES } from '../theme/contrast-policy.js'; import { typedEntries } from '../utils/index.js'; import { layers } from './layers.css.js'; -function fromBreakpoint(minimumWidth: number) { - return { '@container': `(inline-size >= ${minimumWidth}px)` }; +function fromBreakpoint(minimumInlineSize: number) { + return { '@container': `(inline-size >= ${minimumInlineSize}px)` }; } const responsiveConditions = { initial: {}, - small: fromBreakpoint(breakpoints.small), - medium: fromBreakpoint(breakpoints.medium), - large: fromBreakpoint(breakpoints.large), - xlarge: fromBreakpoint(breakpoints.xlarge), - xxlarge: fromBreakpoint(breakpoints.xxlarge), + bp640: fromBreakpoint(breakpoints.bp640), + bp768: fromBreakpoint(breakpoints.bp768), + bp1024: fromBreakpoint(breakpoints.bp1024), + bp1280: fromBreakpoint(breakpoints.bp1280), + bp1536: fromBreakpoint(breakpoints.bp1536), } as const; /** Space steps plus `0`, written explicitly so declaration emit preserves string keys. */ diff --git a/packages/@luke-ui/react/src/styles/utilities.stories.tsx b/packages/@luke-ui/react/src/styles/utilities.stories.tsx index 33fcef61..36c430e7 100644 --- a/packages/@luke-ui/react/src/styles/utilities.stories.tsx +++ b/packages/@luke-ui/react/src/styles/utilities.stories.tsx @@ -59,7 +59,7 @@ export const Layout = meta.story({ }); /** - * Responsive values use object notation keyed by breakpoint names. Values + * Responsive values use object notation keyed by breakpoint. Values * cascade from smaller to larger breakpoints, so only changes need to be * specified. */ @@ -67,9 +67,9 @@ export const Responsive = meta.story({ render: () => { const responsive = createSprinkles({ display: 'flex', - flexDirection: { initial: 'column', medium: 'row' }, - gap: { initial: '300', medium: '600' }, - padding: { initial: '300', large: '800' }, + flexDirection: { initial: 'column', bp768: 'row' }, + gap: { initial: '300', bp768: '600' }, + padding: { initial: '300', bp1024: '800' }, }); return (
diff --git a/packages/@luke-ui/react/src/styles/utilities.test-d.ts b/packages/@luke-ui/react/src/styles/utilities.test-d.ts index b06fae6c..00b5c9ae 100644 --- a/packages/@luke-ui/react/src/styles/utilities.test-d.ts +++ b/packages/@luke-ui/react/src/styles/utilities.test-d.ts @@ -70,8 +70,8 @@ test('border props stay constrained to the design system vocabulary', () => { }); test('responsive objects are keyed by the theme breakpoints', () => { - assertType({ initial: '100', medium: '400' }); - assertType({ initial: 'block', xxlarge: 'flex' }); + assertType({ initial: '100', bp768: '400' }); + assertType({ initial: 'block', bp1536: 'flex' }); // @ts-expect-error — not a breakpoint assertType({ initial: '100', tablet: '400' }); diff --git a/packages/@luke-ui/react/src/theme/breakpoints.ts b/packages/@luke-ui/react/src/theme/breakpoints.ts index f25bcbc1..748018ea 100644 --- a/packages/@luke-ui/react/src/theme/breakpoints.ts +++ b/packages/@luke-ui/react/src/theme/breakpoints.ts @@ -1,16 +1,11 @@ /** - * The private responsive breakpoint widths, in pixels. A container query cannot read a custom - * property, so these are never emitted as one and are resolved in TypeScript instead. - * - * The styling utilities turn them into minimum container inline sizes, and `useIsMobileDevice` - * treats a device screen below `small` as mobile. + * Responsive breakpoint inline sizes in CSS pixels. + * Kept in TypeScript because container query conditions cannot use custom properties. */ - -/** The retained breakpoint widths, from the narrowest to the widest. */ export const breakpoints = { - small: 640, - medium: 768, - large: 1024, - xlarge: 1280, - xxlarge: 1536, + bp640: 640, + bp768: 768, + bp1024: 1024, + bp1280: 1280, + bp1536: 1536, } as const;