diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml
index 6f46aef1..918f9306 100644
--- a/.github/workflows/visual-regression.yml
+++ b/.github/workflows/visual-regression.yml
@@ -42,9 +42,10 @@ jobs:
visual-main-${{ runner.os }}-${{ runner.arch }}-${{ github.event.pull_request.base.sha
|| github.event.before }}-${{ hashFiles('pnpm-lock.yaml',
'packages/@luke-ui/react/vitest.config.ts',
- 'packages/@luke-ui/react/src/test-utils/render-setup.ts',
- 'packages/@luke-ui/react/src/test-utils/render.tsx',
- 'packages/@luke-ui/react/src/test-utils/visual-setup.ts') }}
+ 'packages/@luke-ui/react/src/core/stylesheet.css.ts',
+ 'packages/@luke-ui/react/src/core/test-utils/render-setup.ts',
+ 'packages/@luke-ui/react/src/core/test-utils/render.tsx',
+ 'packages/@luke-ui/react/src/core/test-utils/visual-setup.ts') }}
- name: Compare visual captures
run: corepack pnpm test:visual
env:
diff --git a/README.md b/README.md
index 88713bd0..eb5c6986 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,8 @@ Useful repo commands:
`@luke-ui/react` contains the public React package.
- Theme contract and compiler: `packages/@luke-ui/react/src/theme/`.
-- Styles: `packages/@luke-ui/react/src/styles/`.
+- Styles: `packages/@luke-ui/react/src/core/styles/`.
+- Shared low-level primitives: `packages/@luke-ui/react/src/shared/`.
- Build output: `packages/@luke-ui/react/dist/stylesheet.css`.
## Docs
diff --git a/apps/docs/content/docs/components/actions/button.mdx b/apps/docs/content/docs/components/actions/button.mdx
index 5588dcbf..694e40d2 100644
--- a/apps/docs/content/docs/components/actions/button.mdx
+++ b/apps/docs/content/docs/components/actions/button.mdx
@@ -1,11 +1,11 @@
---
title: Button
description: A labelled control for actions in an interface.
-source: packages/@luke-ui/react/src/button
+source: packages/@luke-ui/react/src/exports/button.ts
reactAria: https://react-spectrum.adobe.com/react-aria/Button.html
props:
- name: ButtonProps
- path: packages/@luke-ui/react/src/button/button.tsx
+ path: packages/@luke-ui/react/src/core/button/button.tsx
---
`Button` triggers an action, such as saving a form, opening a dialog, or deleting an item. Use
diff --git a/apps/docs/content/docs/components/actions/icon-button.mdx b/apps/docs/content/docs/components/actions/icon-button.mdx
index 09fd65e4..573317b7 100644
--- a/apps/docs/content/docs/components/actions/icon-button.mdx
+++ b/apps/docs/content/docs/components/actions/icon-button.mdx
@@ -1,11 +1,11 @@
---
title: Icon Button
description: A compact icon-only control for an action.
-source: packages/@luke-ui/react/src/icon-button
+source: packages/@luke-ui/react/src/exports/icon-button.ts
reactAria: https://react-spectrum.adobe.com/react-aria/Button.html
props:
- name: IconButtonProps
- path: packages/@luke-ui/react/src/icon-button/icon-button.tsx
+ path: packages/@luke-ui/react/src/core/icon-button/icon-button.tsx
---
`IconButton` triggers a familiar compact action, such as closing a panel, deleting a row, or adding
diff --git a/apps/docs/content/docs/components/actions/link.mdx b/apps/docs/content/docs/components/actions/link.mdx
index 45605c8d..57104e5e 100644
--- a/apps/docs/content/docs/components/actions/link.mdx
+++ b/apps/docs/content/docs/components/actions/link.mdx
@@ -1,11 +1,11 @@
---
title: Link
description: A styled link for inline and standalone navigation.
-source: packages/@luke-ui/react/src/link
+source: packages/@luke-ui/react/src/exports/link.ts
reactAria: https://react-spectrum.adobe.com/react-aria/Link.html
props:
- name: LinkProps
- path: packages/@luke-ui/react/src/link/link.tsx
+ path: packages/@luke-ui/react/src/core/link/link.tsx
---
`Link` navigates to another URL or route. Use [`Button`](/components/actions/button) for an action
diff --git a/apps/docs/content/docs/components/feedback/loading-skeleton.mdx b/apps/docs/content/docs/components/feedback/loading-skeleton.mdx
index b61aefb3..31188957 100644
--- a/apps/docs/content/docs/components/feedback/loading-skeleton.mdx
+++ b/apps/docs/content/docs/components/feedback/loading-skeleton.mdx
@@ -1,12 +1,12 @@
---
title: Loading Skeleton
description: Replaces content with a placeholder that keeps its shape while it loads.
-source: packages/@luke-ui/react/src/loading-skeleton
+source: packages/@luke-ui/react/src/exports/loading-skeleton.ts
props:
- name: LoadingSkeletonProps
- path: packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.tsx
+ path: packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.tsx
- name: LoadingSkeletonProviderProps
- path: packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.tsx
+ path: packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.tsx
---
Use `LoadingSkeleton` when content is loading but its layout is known. Wrap the content that will
diff --git a/apps/docs/content/docs/components/feedback/loading-spinner.mdx b/apps/docs/content/docs/components/feedback/loading-spinner.mdx
index 36b5941a..a4ae7d95 100644
--- a/apps/docs/content/docs/components/feedback/loading-spinner.mdx
+++ b/apps/docs/content/docs/components/feedback/loading-spinner.mdx
@@ -1,10 +1,10 @@
---
title: Loading Spinner
description: Animated indicator for work that is still in progress.
-source: packages/@luke-ui/react/src/loading-spinner
+source: packages/@luke-ui/react/src/exports/loading-spinner.ts
props:
- name: LoadingSpinnerProps
- path: packages/@luke-ui/react/src/loading-spinner/loading-spinner.tsx
+ path: packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.tsx
---
`LoadingSpinner` shows that work is in progress. Use it on its own for a small loading region. Wrap
diff --git a/apps/docs/content/docs/components/forms/checkbox.mdx b/apps/docs/content/docs/components/forms/checkbox.mdx
index 1afc5497..96afad03 100644
--- a/apps/docs/content/docs/components/forms/checkbox.mdx
+++ b/apps/docs/content/docs/components/forms/checkbox.mdx
@@ -2,11 +2,11 @@
title: Checkbox
description:
Lets someone select an independent option, with optional supporting and validation text.
-source: packages/@luke-ui/react/src/checkbox
+source: packages/@luke-ui/react/src/exports/checkbox.ts
reactAria: https://react-spectrum.adobe.com/react-aria/Checkbox.html
props:
- name: CheckboxProps
- path: packages/@luke-ui/react/src/checkbox/checkbox.tsx
+ path: packages/@luke-ui/react/src/core/checkbox/checkbox.tsx
---
Use `Checkbox` when someone can choose an option independently of nearby controls. Pass
diff --git a/apps/docs/content/docs/components/forms/combobox-field.mdx b/apps/docs/content/docs/components/forms/combobox-field.mdx
index eabe4de7..871e5846 100644
--- a/apps/docs/content/docs/components/forms/combobox-field.mdx
+++ b/apps/docs/content/docs/components/forms/combobox-field.mdx
@@ -1,11 +1,11 @@
---
title: Combobox Field
description: Searchable single-select field with label, validation, and option list.
-source: packages/@luke-ui/react/src/combobox-field
+source: packages/@luke-ui/react/src/exports/combobox-field.ts
reactAria: https://react-spectrum.adobe.com/react-aria/ComboBox.html
props:
- name: ComboboxFieldProps
- path: packages/@luke-ui/react/src/combobox-field/combobox-field.tsx
+ path: packages/@luke-ui/react/src/core/combobox-field/combobox-field.tsx
---
Use `ComboboxField` when someone needs to search for or choose one option from a list. It combines
diff --git a/apps/docs/content/docs/components/forms/text-field.mdx b/apps/docs/content/docs/components/forms/text-field.mdx
index 81a1a3da..607032d0 100644
--- a/apps/docs/content/docs/components/forms/text-field.mdx
+++ b/apps/docs/content/docs/components/forms/text-field.mdx
@@ -2,11 +2,11 @@
title: Text Field
description:
Captures a single line of text with a label, validation, and an optional prefix or suffix.
-source: packages/@luke-ui/react/src/text-field
+source: packages/@luke-ui/react/src/exports/text-field.ts
reactAria: https://react-spectrum.adobe.com/react-aria/TextField.html
props:
- name: TextFieldProps
- path: packages/@luke-ui/react/src/text-field/text-field.tsx
+ path: packages/@luke-ui/react/src/core/text-field/text-field.tsx
---
Use `TextField` to collect a single line of text. It combines an input with a label, description,
diff --git a/apps/docs/content/docs/components/layout/box.mdx b/apps/docs/content/docs/components/layout/box.mdx
index 7fdfe806..f2c4efa2 100644
--- a/apps/docs/content/docs/components/layout/box.mdx
+++ b/apps/docs/content/docs/components/layout/box.mdx
@@ -1,10 +1,10 @@
---
title: Box
description: A layout element with responsive Luke UI layout properties.
-source: packages/@luke-ui/react/src/box
+source: packages/@luke-ui/react/src/exports/box.ts
props:
- name: BoxProps
- path: packages/@luke-ui/react/src/box/box.tsx
+ path: packages/@luke-ui/react/src/core/box/box.tsx
---
`Box` accepts Luke UI layout properties and renders a `div` by default. It places content in a
diff --git a/apps/docs/content/docs/components/layout/visually-hidden.mdx b/apps/docs/content/docs/components/layout/visually-hidden.mdx
index 8d77dcc3..e25ad020 100644
--- a/apps/docs/content/docs/components/layout/visually-hidden.mdx
+++ b/apps/docs/content/docs/components/layout/visually-hidden.mdx
@@ -1,10 +1,10 @@
---
title: Visually Hidden
description: Hide content visually and keep it available to assistive technology.
-source: packages/@luke-ui/react/src/visually-hidden
+source: packages/@luke-ui/react/src/exports/visually-hidden.ts
props:
- name: VisuallyHiddenProps
- path: packages/@luke-ui/react/src/visually-hidden/visually-hidden.tsx
+ path: packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.tsx
---
Use `VisuallyHidden` to hide content from sight. The content stays in the accessibility tree and the
diff --git a/apps/docs/content/docs/components/primitives/button.mdx b/apps/docs/content/docs/components/primitives/button.mdx
index aee51405..2c2186df 100644
--- a/apps/docs/content/docs/components/primitives/button.mdx
+++ b/apps/docs/content/docs/components/primitives/button.mdx
@@ -1,11 +1,11 @@
---
title: Button primitive
description: Lower-level button styles and behaviour for custom control layouts.
-source: packages/@luke-ui/react/src/primitives/button
+source: packages/@luke-ui/react/src/exports/primitives/button.ts
reactAria: https://react-spectrum.adobe.com/react-aria/Button.html
props:
- name: ButtonProps
- path: packages/@luke-ui/react/src/primitives/button/button.tsx
+ path: packages/@luke-ui/react/src/core/primitives/button/button.tsx
---
Use the button primitive to build a custom button pattern. It provides Luke UI button styles and
diff --git a/apps/docs/content/docs/components/primitives/checkbox.mdx b/apps/docs/content/docs/components/primitives/checkbox.mdx
index e75719bb..6f3cc159 100644
--- a/apps/docs/content/docs/components/primitives/checkbox.mdx
+++ b/apps/docs/content/docs/components/primitives/checkbox.mdx
@@ -1,17 +1,17 @@
---
title: Checkbox primitive
description: Lower-level checkbox anatomy for custom form control layouts.
-source: packages/@luke-ui/react/src/primitives/checkbox
+source: packages/@luke-ui/react/src/exports/primitives/checkbox.ts
reactAria: https://react-spectrum.adobe.com/react-aria/Checkbox.html
props:
- name: CheckboxProps
- path: packages/@luke-ui/react/src/primitives/checkbox/checkbox.tsx
+ path: packages/@luke-ui/react/src/core/primitives/checkbox/checkbox.tsx
- name: CheckboxContentProps
- path: packages/@luke-ui/react/src/primitives/checkbox/checkbox.tsx
+ path: packages/@luke-ui/react/src/core/primitives/checkbox/checkbox.tsx
- name: CheckboxControlProps
- path: packages/@luke-ui/react/src/primitives/checkbox/checkbox.tsx
+ path: packages/@luke-ui/react/src/core/primitives/checkbox/checkbox.tsx
- name: CheckboxIndicatorProps
- path: packages/@luke-ui/react/src/primitives/checkbox/checkbox.tsx
+ path: packages/@luke-ui/react/src/core/primitives/checkbox/checkbox.tsx
---
Use the Checkbox primitive when you need a custom label layout or extra content. For normal
diff --git a/apps/docs/content/docs/components/primitives/combobox.mdx b/apps/docs/content/docs/components/primitives/combobox.mdx
index 50de0edc..36e9ba59 100644
--- a/apps/docs/content/docs/components/primitives/combobox.mdx
+++ b/apps/docs/content/docs/components/primitives/combobox.mdx
@@ -1,31 +1,31 @@
---
title: Combobox primitives
description: Lower-level parts for composing a searchable single-select combobox.
-source: packages/@luke-ui/react/src/primitives/combobox
+source: packages/@luke-ui/react/src/exports/primitives/combobox.ts
reactAria: https://react-spectrum.adobe.com/react-aria/ComboBox.html
props:
- name: ComboboxRootProps
- path: packages/@luke-ui/react/src/primitives/combobox/root.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/root.tsx
- name: ComboboxInputGroupProps
- path: packages/@luke-ui/react/src/primitives/combobox/input-group.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/input-group.tsx
- name: ComboboxInputProps
- path: packages/@luke-ui/react/src/primitives/combobox/input.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/input.tsx
- name: ComboboxClearButtonProps
- path: packages/@luke-ui/react/src/primitives/combobox/clear-button.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/clear-button.tsx
- name: ComboboxTriggerProps
- path: packages/@luke-ui/react/src/primitives/combobox/trigger.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/trigger.tsx
- name: ComboboxPopoverProps
- path: packages/@luke-ui/react/src/primitives/combobox/popover.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/popover.tsx
- name: ComboboxListBoxProps
- path: packages/@luke-ui/react/src/primitives/combobox/listbox.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/listbox.tsx
- name: ComboboxItemProps
- path: packages/@luke-ui/react/src/primitives/combobox/item.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/item.tsx
- name: ComboboxLoadMoreItemProps
- path: packages/@luke-ui/react/src/primitives/combobox/item.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/item.tsx
- name: ComboboxSectionProps
- path: packages/@luke-ui/react/src/primitives/combobox/section.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/section.tsx
- name: ComboboxEmptyStateProps
- path: packages/@luke-ui/react/src/primitives/combobox/empty-state.tsx
+ path: packages/@luke-ui/react/src/core/primitives/combobox/empty-state.tsx
---
Use these primitives when [`ComboboxField`](/components/forms/combobox-field) does not fit your
diff --git a/apps/docs/content/docs/components/primitives/field.mdx b/apps/docs/content/docs/components/primitives/field.mdx
index de491a78..37360ad0 100644
--- a/apps/docs/content/docs/components/primitives/field.mdx
+++ b/apps/docs/content/docs/components/primitives/field.mdx
@@ -1,16 +1,16 @@
---
title: Field primitive
description: Shared label, description, and validation parts for custom fields.
-source: packages/@luke-ui/react/src/primitives/field
+source: packages/@luke-ui/react/src/exports/primitives/field.ts
props:
- name: FieldProps
- path: packages/@luke-ui/react/src/primitives/field/field.tsx
+ path: packages/@luke-ui/react/src/core/primitives/field/field.tsx
- name: FieldLabelProps
- path: packages/@luke-ui/react/src/primitives/field/label.tsx
+ path: packages/@luke-ui/react/src/core/primitives/field/label.tsx
- name: FieldDescriptionProps
- path: packages/@luke-ui/react/src/primitives/field/description.tsx
+ path: packages/@luke-ui/react/src/core/primitives/field/description.tsx
- name: FieldErrorProps
- path: packages/@luke-ui/react/src/primitives/field/error.tsx
+ path: packages/@luke-ui/react/src/core/primitives/field/error.tsx
---
Use the field primitives to build a custom field with Luke UI labels, descriptions, and validation
diff --git a/apps/docs/content/docs/components/primitives/input-group.mdx b/apps/docs/content/docs/components/primitives/input-group.mdx
index 75bd6076..bddf788d 100644
--- a/apps/docs/content/docs/components/primitives/input-group.mdx
+++ b/apps/docs/content/docs/components/primitives/input-group.mdx
@@ -1,16 +1,16 @@
---
title: InputGroup primitives
description: Lower-level parts for composing a text input with content at either end.
-source: packages/@luke-ui/react/src/primitives/input-group
+source: packages/@luke-ui/react/src/exports/primitives/input-group.ts
props:
- name: InputGroupProps
- path: packages/@luke-ui/react/src/primitives/input-group/input-group.tsx
+ path: packages/@luke-ui/react/src/core/primitives/input-group/input-group.tsx
- name: InputGroupInputProps
- path: packages/@luke-ui/react/src/primitives/input-group/input-group.tsx
+ path: packages/@luke-ui/react/src/core/primitives/input-group/input-group.tsx
- name: InputGroupPrefixProps
- path: packages/@luke-ui/react/src/primitives/input-group/input-group.tsx
+ path: packages/@luke-ui/react/src/core/primitives/input-group/input-group.tsx
- name: InputGroupSuffixProps
- path: packages/@luke-ui/react/src/primitives/input-group/input-group.tsx
+ path: packages/@luke-ui/react/src/core/primitives/input-group/input-group.tsx
---
Use these primitives to build a custom text control. Reach for them when you need the styled input
diff --git a/apps/docs/content/docs/components/typography/blockquote.mdx b/apps/docs/content/docs/components/typography/blockquote.mdx
index effa8b39..e8f856e1 100644
--- a/apps/docs/content/docs/components/typography/blockquote.mdx
+++ b/apps/docs/content/docs/components/typography/blockquote.mdx
@@ -1,10 +1,10 @@
---
title: Blockquote
description: Block-level quotation from another source.
-source: packages/@luke-ui/react/src/blockquote
+source: packages/@luke-ui/react/src/exports/blockquote.ts
props:
- name: BlockquoteProps
- path: packages/@luke-ui/react/src/blockquote/blockquote.tsx
+ path: packages/@luke-ui/react/src/core/blockquote/blockquote.tsx
---
`Blockquote` quotes another source as a block. It renders a native `blockquote` element, which
diff --git a/apps/docs/content/docs/components/typography/code.mdx b/apps/docs/content/docs/components/typography/code.mdx
index 1427f4e9..b8658744 100644
--- a/apps/docs/content/docs/components/typography/code.mdx
+++ b/apps/docs/content/docs/components/typography/code.mdx
@@ -1,10 +1,10 @@
---
title: Code
description: Marks text to signify a short fragment of computer code.
-source: packages/@luke-ui/react/src/code
+source: packages/@luke-ui/react/src/exports/code.ts
props:
- name: CodeProps
- path: packages/@luke-ui/react/src/code/code.tsx
+ path: packages/@luke-ui/react/src/core/code/code.tsx
---
`Code` marks a short fragment of computer code. It renders a native `code` element, which conveys
diff --git a/apps/docs/content/docs/components/typography/em.mdx b/apps/docs/content/docs/components/typography/em.mdx
index 2c29a588..7a6f1438 100644
--- a/apps/docs/content/docs/components/typography/em.mdx
+++ b/apps/docs/content/docs/components/typography/em.mdx
@@ -1,10 +1,10 @@
---
title: Em
description: Marks text to stress emphasis.
-source: packages/@luke-ui/react/src/em
+source: packages/@luke-ui/react/src/exports/em.ts
props:
- name: EmProps
- path: packages/@luke-ui/react/src/em/em.tsx
+ path: packages/@luke-ui/react/src/core/em/em.tsx
---
`Em` stresses a word or phrase, changing how the sentence reads. It renders a native `em` element,
diff --git a/apps/docs/content/docs/components/typography/emoji.mdx b/apps/docs/content/docs/components/typography/emoji.mdx
index 48df66bc..054df531 100644
--- a/apps/docs/content/docs/components/typography/emoji.mdx
+++ b/apps/docs/content/docs/components/typography/emoji.mdx
@@ -1,10 +1,10 @@
---
title: Emoji
description: Emoji rendering with a reliable screen reader label.
-source: packages/@luke-ui/react/src/emoji
+source: packages/@luke-ui/react/src/exports/emoji.ts
props:
- name: EmojiProps
- path: packages/@luke-ui/react/src/emoji/emoji.tsx
+ path: packages/@luke-ui/react/src/core/emoji/emoji.tsx
---
`Emoji` renders an emoji with a reliable accessible name. It inherits its size from the surrounding
diff --git a/apps/docs/content/docs/components/typography/heading.mdx b/apps/docs/content/docs/components/typography/heading.mdx
index 77736f01..f172cbd0 100644
--- a/apps/docs/content/docs/components/typography/heading.mdx
+++ b/apps/docs/content/docs/components/typography/heading.mdx
@@ -1,14 +1,14 @@
---
title: Heading
description: Semantic heading with automatic level management.
-source: packages/@luke-ui/react/src/heading
+source: packages/@luke-ui/react/src/exports/heading.ts
props:
- name: HeadingProps
- path: packages/@luke-ui/react/src/heading/heading.tsx
+ path: packages/@luke-ui/react/src/core/heading/heading.tsx
- name: HeadingLevelsProps
- path: packages/@luke-ui/react/src/heading/heading-context.tsx
+ path: packages/@luke-ui/react/src/core/heading/heading-context.tsx
- name: HeadingLevelsRenderProps
- path: packages/@luke-ui/react/src/heading/heading-context.tsx
+ path: packages/@luke-ui/react/src/core/heading/heading-context.tsx
---
`Heading` renders a semantic section heading. It reads its level from `HeadingLevels` context. Set
diff --git a/apps/docs/content/docs/components/typography/kbd.mdx b/apps/docs/content/docs/components/typography/kbd.mdx
index f59cd580..2f592233 100644
--- a/apps/docs/content/docs/components/typography/kbd.mdx
+++ b/apps/docs/content/docs/components/typography/kbd.mdx
@@ -1,10 +1,10 @@
---
title: Kbd
description: Represents keyboard input or a hotkey.
-source: packages/@luke-ui/react/src/kbd
+source: packages/@luke-ui/react/src/exports/kbd.ts
props:
- name: KbdProps
- path: packages/@luke-ui/react/src/kbd/kbd.tsx
+ path: packages/@luke-ui/react/src/core/kbd/kbd.tsx
---
`Kbd` marks keyboard input, such as a shortcut or a key name. It renders a native `kbd` element,
diff --git a/apps/docs/content/docs/components/typography/numeral.mdx b/apps/docs/content/docs/components/typography/numeral.mdx
index dbb6ad1a..3610e913 100644
--- a/apps/docs/content/docs/components/typography/numeral.mdx
+++ b/apps/docs/content/docs/components/typography/numeral.mdx
@@ -1,10 +1,10 @@
---
title: Numeral
description: Locale-aware number formatting powered by Intl.NumberFormat.
-source: packages/@luke-ui/react/src/numeral
+source: packages/@luke-ui/react/src/exports/numeral.ts
props:
- name: NumeralProps
- path: packages/@luke-ui/react/src/numeral/numeral.tsx
+ path: packages/@luke-ui/react/src/core/numeral/numeral.tsx
---
`Numeral` formats a number with `Intl.NumberFormat`. It reads the active locale from React Aria's
diff --git a/apps/docs/content/docs/components/typography/quote.mdx b/apps/docs/content/docs/components/typography/quote.mdx
index e66be4ab..dddc5641 100644
--- a/apps/docs/content/docs/components/typography/quote.mdx
+++ b/apps/docs/content/docs/components/typography/quote.mdx
@@ -1,10 +1,10 @@
---
title: Quote
description: Short inline quotation.
-source: packages/@luke-ui/react/src/quote
+source: packages/@luke-ui/react/src/exports/quote.ts
props:
- name: QuoteProps
- path: packages/@luke-ui/react/src/quote/quote.tsx
+ path: packages/@luke-ui/react/src/core/quote/quote.tsx
---
`Quote` marks a short quotation inside a sentence. It renders a native `q` element, which conveys
diff --git a/apps/docs/content/docs/components/typography/strong.mdx b/apps/docs/content/docs/components/typography/strong.mdx
index 5ba01efd..835aaee7 100644
--- a/apps/docs/content/docs/components/typography/strong.mdx
+++ b/apps/docs/content/docs/components/typography/strong.mdx
@@ -1,10 +1,10 @@
---
title: Strong
description: Marks text to signify strong importance.
-source: packages/@luke-ui/react/src/strong
+source: packages/@luke-ui/react/src/exports/strong.ts
props:
- name: StrongProps
- path: packages/@luke-ui/react/src/strong/strong.tsx
+ path: packages/@luke-ui/react/src/core/strong/strong.tsx
---
`Strong` marks text as important. It renders a native `strong` element, which conveys importance to
diff --git a/apps/docs/content/docs/components/typography/text.mdx b/apps/docs/content/docs/components/typography/text.mdx
index 273d0f6c..60f9520b 100644
--- a/apps/docs/content/docs/components/typography/text.mdx
+++ b/apps/docs/content/docs/components/typography/text.mdx
@@ -1,10 +1,10 @@
---
title: Text
description: Styled text with semantic typography styles and colour roles.
-source: packages/@luke-ui/react/src/text
+source: packages/@luke-ui/react/src/exports/text.ts
props:
- name: TextProps
- path: packages/@luke-ui/react/src/text/text.tsx
+ path: packages/@luke-ui/react/src/core/text/text.tsx
---
`Text` applies Luke UI typography without adding heading semantics. It needs a Luke UI theme root.
diff --git a/apps/docs/content/docs/components/visuals/icon.mdx b/apps/docs/content/docs/components/visuals/icon.mdx
index fba79a26..f11e8af7 100644
--- a/apps/docs/content/docs/components/visuals/icon.mdx
+++ b/apps/docs/content/docs/components/visuals/icon.mdx
@@ -1,16 +1,16 @@
---
title: Icon
description: SVG icon component backed by the generated spritesheet.
-source: packages/@luke-ui/react/src/icon
+source: packages/@luke-ui/react/src/exports/icon.ts
props:
- name: IconProps
- path: packages/@luke-ui/react/src/icon/icon.tsx
+ path: packages/@luke-ui/react/src/core/icon/icon.tsx
- name: CreateIconOptions
- path: packages/@luke-ui/react/src/icon/icon.tsx
+ path: packages/@luke-ui/react/src/core/icon/icon.tsx
- name: CustomIconProps
- path: packages/@luke-ui/react/src/icon/icon.tsx
+ path: packages/@luke-ui/react/src/core/icon/icon.tsx
- name: IconSpritesheetProviderProps
- path: packages/@luke-ui/react/src/icon/icon.tsx
+ path: packages/@luke-ui/react/src/core/icon/icon.tsx
---
`Icon` renders a symbol from the generated Luke UI spritesheet. It needs an
diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts
index fb52c996..63706419 100644
--- a/apps/docs/source.config.ts
+++ b/apps/docs/source.config.ts
@@ -26,14 +26,14 @@ export const docs = defineDocs({
z.object({
/** Exported type name to render, e.g. `ButtonProps`. */
name: z.string(),
- /** Repo-relative path to the file exporting `name`, e.g. `packages/@luke-ui/react/src/button/button.tsx`. */
+ /** Repo-relative path to the file exporting `name`, e.g. `packages/@luke-ui/react/src/core/button/button.tsx`. */
path: z.string(),
}),
)
.optional(),
/** Full URL to this component's React Aria Components docs page, when it genuinely wraps one. */
reactAria: z.string().optional(),
- /** Repo-relative path to this component's source, e.g. `packages/@luke-ui/react/src/button`. */
+ /** Repo-relative path to this component's public module, e.g. `packages/@luke-ui/react/src/exports/button.ts`. */
source: z.string().optional(),
}),
},
diff --git a/apps/docs/src/lib/check-docs.test.ts b/apps/docs/src/lib/check-docs.test.ts
index d04b982b..3202bfbe 100644
--- a/apps/docs/src/lib/check-docs.test.ts
+++ b/apps/docs/src/lib/check-docs.test.ts
@@ -44,7 +44,7 @@ Run the install command.
components: {
'actions/button.mdx': `---
title: Button
-source: packages/@luke-ui/react/src/button
+source: packages/@luke-ui/react/src/exports/button.ts
---
@@ -76,7 +76,7 @@ test('reports heading vocabulary, order, and required Accessibility issues', ()
components: {
'actions/button.mdx': `---
title: Button
-source: packages/@luke-ui/react/src/button
+source: packages/@luke-ui/react/src/exports/button.ts
---
@@ -95,7 +95,7 @@ Use Button for actions.
`,
'visuals/icon.mdx': `---
title: Icon
-source: packages/@luke-ui/react/src/icon
+source: packages/@luke-ui/react/src/exports/icon.ts
---
@@ -159,7 +159,7 @@ title: Layout
components: {
'layout/box.mdx': `---
title: Box
-source: packages/@luke-ui/react/src/box
+source: packages/@luke-ui/react/src/exports/box.ts
---
@@ -179,7 +179,7 @@ test('does not report a shared-example issue for a page referencing the same exa
components: {
'layout/box.mdx': `---
title: Box
-source: packages/@luke-ui/react/src/box
+source: packages/@luke-ui/react/src/exports/box.ts
---
@@ -516,7 +516,7 @@ test('finds no extra docs issues in the docs app beyond the baseline', () => {
const INVENTORY_GUIDE = `---
title: Button
-source: packages/@luke-ui/react/src/button
+source: packages/@luke-ui/react/src/exports/button.ts
---
@@ -653,7 +653,7 @@ test('reports category metadata that does not match the root metadata', () => {
'actions/button.mdx': INVENTORY_GUIDE,
'actions/link.mdx': `---
title: Link
-source: packages/@luke-ui/react/src/link
+source: packages/@luke-ui/react/src/exports/link.ts
---
@@ -676,15 +676,15 @@ test('reports a guide source that is not a public package entry point', () => {
const paths = inventoryFixture({ packageExports: ['./link'] });
expect(findDocsIssues(paths)).toEqual([
- 'component-guide-inventory: actions/button.mdx: source "packages/@luke-ui/react/src/button" is not a public package entry point (expected export "./button" in @luke-ui/react)',
+ 'component-guide-inventory: actions/button.mdx: source "packages/@luke-ui/react/src/exports/button.ts" is not a public package entry point (expected export "./button" in @luke-ui/react)',
]);
});
-test('reports a source directory with no index.ts', () => {
+test('reports a missing exports module', () => {
const paths = inventoryFixture({ sourceDirs: [] });
expect(findDocsIssues(paths)).toEqual([
- 'component-guide-inventory: actions/button.mdx: source "packages/@luke-ui/react/src/button" has no packages/@luke-ui/react/src/button/index.ts',
+ 'component-guide-inventory: actions/button.mdx: source "packages/@luke-ui/react/src/exports/button.ts" does not exist',
]);
});
@@ -696,7 +696,7 @@ function createDocsFixture(input: {
metadata?: Record;
/** Export keys for the fake `@luke-ui/react` manifest. */
packageExports?: ReadonlyArray;
- /** Subpaths under the fake package `src` that get an `index.ts`. */
+ /** Subpaths under the fake package `src/exports` that get a `.ts` module. */
sourceDirs?: ReadonlyArray;
}): DocsCheckPaths {
const directory = mkdtempSync(join(tmpdir(), 'luke-ui-check-docs-'));
@@ -738,9 +738,9 @@ function createDocsFixture(input: {
writeFileSync(reactPackageJsonPath, JSON.stringify({ exports, name: '@luke-ui/react' }));
for (const subpath of input.sourceDirs ?? guideSources) {
- const sourceDir = join(reactPackageDir, 'src', subpath);
- mkdirSync(sourceDir, { recursive: true });
- writeFileSync(join(sourceDir, 'index.ts'), 'export {};\n');
+ const sourcePath = join(reactPackageDir, 'src', 'exports', `${subpath}.ts`);
+ mkdirSync(join(sourcePath, '..'), { recursive: true });
+ writeFileSync(sourcePath, 'export {};\n');
}
return {
@@ -790,5 +790,5 @@ function defaultMetadata(guidePaths: ReadonlyArray): Record
const docsDir = createDocsFixture({
guide: `---
title: Status Badge
-source: packages/@luke-ui/react/src/status-badge
+source: packages/@luke-ui/react/src/exports/status-badge.ts
---
@@ -33,7 +33,7 @@ test('reports placeholders and the wrong primary example', () => {
guide: `---
title: Status Badge
description: Status Badge component.
-source: packages/@luke-ui/react/src/status-badge
+source: packages/@luke-ui/react/src/exports/status-badge.ts
---
\`StatusBadge\` from \`@luke-ui/react/status-badge\`.
diff --git a/apps/docs/src/lib/component-guide-inventory.ts b/apps/docs/src/lib/component-guide-inventory.ts
index b2801974..bd601871 100644
--- a/apps/docs/src/lib/component-guide-inventory.ts
+++ b/apps/docs/src/lib/component-guide-inventory.ts
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { parseComponentFrontmatter, readFrontmatter } from './docs-frontmatter.js';
-const SOURCE_PREFIX = 'packages/@luke-ui/react/src/';
+const EXPORTS_PREFIX = 'packages/@luke-ui/react/src/exports/';
/** One authored component guide, keyed by the slug the navigation uses. */
interface ComponentGuide {
@@ -158,8 +158,8 @@ function categoryDirectories(componentsDir: string): Array {
}
/**
- * A guide `source` must name a public package entry point and a real source directory with an
- * `index.ts`, so a documented component is one a developer can import.
+ * A guide `source` must name a public package entry point and its exports module, so a documented
+ * component is one a developer can import.
*/
export function findGuideSourceIssues(
inventory: ComponentGuideInventory,
@@ -172,14 +172,20 @@ export function findGuideSourceIssues(
const { relativePath, source } = guide;
if (source === undefined) continue;
- if (!source.startsWith(SOURCE_PREFIX)) {
+ if (!source.startsWith(EXPORTS_PREFIX)) {
issues.push(
- `${relativePath}: source "${source}" is not a public package entry point (expected a path under ${SOURCE_PREFIX})`,
+ `${relativePath}: source "${source}" is not a public package entry point (expected a path under ${EXPORTS_PREFIX})`,
);
continue;
}
- const subpath = source.slice(SOURCE_PREFIX.length);
+ const exportPath = source.slice(EXPORTS_PREFIX.length);
+ if (!exportPath.endsWith('.ts')) {
+ issues.push(`${relativePath}: source "${source}" must name an exports .ts module`);
+ continue;
+ }
+
+ const subpath = exportPath.slice(0, -'.ts'.length);
if (!exportSet.has(`./${subpath}`)) {
issues.push(
`${relativePath}: source "${source}" is not a public package entry point (expected export "./${subpath}" in @luke-ui/react)`,
@@ -187,9 +193,9 @@ export function findGuideSourceIssues(
continue;
}
- const indexPath = resolve(reactPackageDir, 'src', subpath, 'index.ts');
- if (!existsSync(indexPath)) {
- issues.push(`${relativePath}: source "${source}" has no ${source}/index.ts`);
+ const sourcePath = resolve(reactPackageDir, 'src', 'exports', exportPath);
+ if (!existsSync(sourcePath)) {
+ issues.push(`${relativePath}: source "${source}" does not exist`);
}
}
diff --git a/apps/docs/src/lib/component-props-contract.test.ts b/apps/docs/src/lib/component-props-contract.test.ts
index 88367fe5..e3f9973e 100644
--- a/apps/docs/src/lib/component-props-contract.test.ts
+++ b/apps/docs/src/lib/component-props-contract.test.ts
@@ -24,7 +24,21 @@ test('requires component props from an exported component parameter', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
+ ]);
+});
+
+test('follows an export-all module into core', () => {
+ const fixture = createFixture({
+ entry: "export * from './button.js';\n",
+ files: {
+ 'button.ts':
+ 'export interface ButtonProps {}\nexport function Button(props: ButtonProps) {}\n',
+ },
+ });
+
+ expect(issues(fixture)).toEqual([
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -38,7 +52,7 @@ test('requires provider props from an exported provider parameter', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ProviderProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ProviderProps" in props frontmatter',
]);
});
@@ -53,8 +67,8 @@ test('requires factory options and the factory return type', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "CreateThingOptions" in props frontmatter',
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "CreatedThing" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "CreateThingOptions" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "CreatedThing" in props frontmatter',
]);
});
@@ -68,7 +82,7 @@ test('requires a render callback value', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "RenderProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "RenderProps" in props frontmatter',
]);
});
@@ -82,7 +96,7 @@ test('requires a named object generic constraint', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ThingProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ThingProps" in props frontmatter',
]);
});
@@ -96,8 +110,8 @@ test('requires contracts from a multipart entry point', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "RootProps" in props frontmatter',
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ItemProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "RootProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ItemProps" in props frontmatter',
]);
});
@@ -113,7 +127,7 @@ test('requires contracts re-exported through an intermediate local module', () =
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "FieldLabelProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "FieldLabelProps" in props frontmatter',
]);
});
@@ -129,7 +143,7 @@ test('follows aliased values and types through an intermediate re-export', () =>
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -143,7 +157,7 @@ test('follows an aliased value through an intermediate re-export', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" has an unsupported exported signature "Button"',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" has an unsupported exported signature "Button"',
]);
});
@@ -158,7 +172,7 @@ test('follows an aliased type through an intermediate re-export', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -175,7 +189,7 @@ test('follows aliased values and types through multiple local re-exports', () =>
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -191,7 +205,7 @@ test('requires a public imported type from another local module', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -209,7 +223,7 @@ test('uses the public alias for an imported type from another local module', ()
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -294,7 +308,7 @@ test('requires an indirect alias that resolves to an object type', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -320,7 +334,7 @@ test('requires a parameterised alias that resolves to an object type', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -346,7 +360,7 @@ test('requires a chained parameterised alias that resolves to an object type', (
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
]);
});
@@ -363,7 +377,7 @@ test('classifies private aliases using the module that declared them', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "RootProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "RootProps" in props frontmatter',
]);
});
@@ -395,7 +409,7 @@ test('requires a public object type when a local leaf type has the same name', (
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "SharedProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "SharedProps" in props frontmatter',
]);
});
@@ -410,7 +424,7 @@ test('inspects only the re-exported callable when local modules share a value na
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "RootProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "RootProps" in props frontmatter',
]);
});
@@ -424,7 +438,7 @@ test('reports an unsupported relevant exported signature', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" has an unsupported exported signature "Button"',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" has an unsupported exported signature "Button"',
]);
});
@@ -440,13 +454,13 @@ test('does not treat a typed data constant as an unsupported signature', () => {
expect(issues(fixture)).toEqual([]);
});
-test('does not follow public re-exports outside the entry directory', () => {
+test('does not follow public re-exports outside the implementation directory', () => {
const fixture = createFixture({
entry: "export { Icon, iconNames, type IconProps } from './icon.js';\n",
files: {
'icon.ts':
- "import { iconNames } from '../generated/icon-data.js';\nexport { iconNames };\nexport interface IconProps {}\nexport function Icon(props: IconProps) {}\n",
- '../generated/icon-data.ts':
+ "import { iconNames } from '../../generated/icon-data.js';\nexport { iconNames };\nexport interface IconProps {}\nexport function Icon(props: IconProps) {}\n",
+ '../../generated/icon-data.ts':
'export declare const iconNames: (props: IconProps) => unknown;\n',
},
props: ['IconProps'],
@@ -466,8 +480,8 @@ test('reports frontmatter props that the entry point does not export', () => {
});
expect(issues(fixture)).toEqual([
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" requires public object contract "ButtonProps" in props frontmatter',
- 'actions/button.mdx: entry point "packages/@luke-ui/react/src/button/index.ts" does not export props frontmatter type "MissingProps"',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" requires public object contract "ButtonProps" in props frontmatter',
+ 'actions/button.mdx: entry point "packages/@luke-ui/react/src/exports/button.ts" does not export props frontmatter type "MissingProps"',
]);
});
@@ -481,15 +495,17 @@ function createFixture(input: {
const componentsDir = join(directory, 'content', 'components');
const reactPackageDir = join(directory, 'react-package');
- const sourceDir = join(reactPackageDir, 'src', 'button');
+ const sourceDir = join(reactPackageDir, 'src', 'core', 'button');
+ const exportsDir = join(reactPackageDir, 'src', 'exports');
mkdirSync(componentsDir, { recursive: true });
mkdirSync(sourceDir, { recursive: true });
+ mkdirSync(exportsDir, { recursive: true });
writeFileSync(join(componentsDir, 'meta.json'), '{"pages":["---Actions---","actions/button"]}');
writeFileSync(
join(reactPackageDir, 'package.json'),
'{"exports":{"./button":"./dist/button/index.js"}}',
);
- writeFileSync(join(sourceDir, 'index.ts'), input.entry);
+ writeFileSync(join(exportsDir, 'button.ts'), input.entry.replaceAll("'./", "'../core/button/"));
for (const [path, contents] of Object.entries(input.files)) {
const filePath = join(sourceDir, path);
@@ -498,7 +514,7 @@ function createFixture(input: {
}
const props = input.props ?? [];
- const guide = `---\ntitle: Button\nsource: packages/@luke-ui/react/src/button\n${renderProps(props)}---\n`;
+ const guide = `---\ntitle: Button\nsource: packages/@luke-ui/react/src/exports/button.ts\n${renderProps(props)}---\n`;
return {
inventory: buildComponentGuideInventory({
componentsDir,
@@ -511,7 +527,7 @@ function createFixture(input: {
function renderProps(props: ReadonlyArray): string {
if (props.length === 0) return '';
- return `props:\n${props.map((name) => ` - name: ${name}\n path: packages/@luke-ui/react/src/button/button.ts`).join('\n')}\n`;
+ return `props:\n${props.map((name) => ` - name: ${name}\n path: packages/@luke-ui/react/src/core/button/button.ts`).join('\n')}\n`;
}
function issues(fixture: {
diff --git a/apps/docs/src/lib/component-props-contract.ts b/apps/docs/src/lib/component-props-contract.ts
index 2a2f4bdc..26a3ac65 100644
--- a/apps/docs/src/lib/component-props-contract.ts
+++ b/apps/docs/src/lib/component-props-contract.ts
@@ -3,7 +3,7 @@ import { dirname, resolve, sep } from 'node:path';
import { parseSync } from 'oxc-parser';
import type { ComponentGuideInventory } from './component-guide-inventory.js';
-const SOURCE_PREFIX = 'packages/@luke-ui/react/src/';
+const EXPORTS_PREFIX = 'packages/@luke-ui/react/src/exports/';
interface AstNode {
readonly type: string;
@@ -28,13 +28,13 @@ export function findComponentPropsContractIssues(
const issues: Array = [];
for (const guide of inventory.guides) {
- if (guide.source === undefined || !guide.source.startsWith(SOURCE_PREFIX)) continue;
+ if (guide.source === undefined || !guide.source.startsWith(EXPORTS_PREFIX)) continue;
const entryPath = resolve(
reactPackageDir,
'src',
- guide.source.slice(SOURCE_PREFIX.length),
- 'index.ts',
+ 'exports',
+ guide.source.slice(EXPORTS_PREFIX.length),
);
if (!existsSync(entryPath)) continue;
@@ -71,7 +71,7 @@ function findGuideIssues(
}
}
- const entryPoint = `${source}/index.ts`;
+ const entryPoint = source;
const documented = new Set(frontmatterProps.map((entry) => entry.name));
const issues: Array = [];
@@ -98,9 +98,21 @@ function findGuideIssues(
return issues;
}
-// Public names may be imported and re-exported by an intermediate file. Follow those local
-// modules so the callable signature that is inspected is the one consumers actually import.
-// Stay inside the entry directory so generated data modules are not scanned.
+// Entries live under `src/exports/` (some nested, such as `src/exports/primitives/button.ts`), so
+// the traversal boundary is the `src/core` directory that sits alongside `exports` in the source
+// tree, found by walking up from the entry module to the `exports` segment.
+function srcDir(entryPath: string): string {
+ const segments = entryPath.split(sep);
+ const exportsIndex = segments.lastIndexOf('exports');
+ if (exportsIndex === -1) {
+ throw new Error(`Expected an entry path under "src/exports/", received "${entryPath}".`);
+ }
+ return segments.slice(0, exportsIndex).join(sep);
+}
+
+// Public names may be imported and re-exported by an intermediate file. Follow the public module
+// and implementation modules so the callable signature that is inspected is the one consumers
+// actually import. Do not follow other local modules such as generated data.
function localContractModules(
entryModule: ParsedModule,
): Array<{ module: ParsedModule } & PublicExports> {
@@ -108,7 +120,7 @@ function localContractModules(
const entryExports = exportedNames(entryModule.program);
modules.set(entryModule.path, { module: entryModule, ...entryExports });
const pending = [entryModule.path];
- const entryDir = dirname(entryModule.path);
+ const coreDir = resolve(srcDir(entryModule.path), 'core');
while (pending.length > 0) {
const path = pending.pop();
@@ -125,7 +137,7 @@ function localContractModules(
}
const targetPath = target.path;
- if (targetPath.endsWith('.css.ts') || !isInsideDirectory(targetPath, entryDir)) continue;
+ if (targetPath.endsWith('.css.ts') || !isInsideDirectory(targetPath, coreDir)) continue;
const module = parseModule(targetPath);
if (module === undefined) continue;
@@ -160,6 +172,12 @@ function localReexports(
const reexports = new Map();
for (const statement of body(module.program)) {
+ if (statement.type === 'ExportAllDeclaration') {
+ const source = literalString(statement.source);
+ if (source === undefined || !source.startsWith('.')) continue;
+ addWildcardReexport(reexports, resolveLocalModule(module.path, source));
+ continue;
+ }
if (statement.type !== 'ExportNamedDeclaration') continue;
const source = literalString(statement.source);
@@ -193,6 +211,19 @@ function localReexports(
return [...reexports.values()];
}
+function addWildcardReexport(
+ reexports: Map,
+ path: string,
+): void {
+ const module = parseModule(path);
+ if (module === undefined) return;
+
+ const exported = exportedNames(module.program);
+ const reexport = reexports.get(path) ?? { path, types: new Map(), values: new Map() };
+ mergePublicExports(reexport, exported);
+ reexports.set(path, reexport);
+}
+
function addReexportedNames(
reexports: Map,
path: string,
diff --git a/apps/docs/src/lib/docs-frontmatter.test.ts b/apps/docs/src/lib/docs-frontmatter.test.ts
index 9436ba37..c249e0a4 100644
--- a/apps/docs/src/lib/docs-frontmatter.test.ts
+++ b/apps/docs/src/lib/docs-frontmatter.test.ts
@@ -8,14 +8,14 @@ title: Button
description:
A labelled control
for actions.
-source: packages/@luke-ui/react/src/button
+source: packages/@luke-ui/react/src/exports/button.ts
---
Body.
`),
).toEqual({
description: 'A labelled control for actions.',
- source: 'packages/@luke-ui/react/src/button',
+ source: 'packages/@luke-ui/react/src/exports/button.ts',
title: 'Button',
});
});
diff --git a/apps/docs/src/lib/generate-props-pages.test.ts b/apps/docs/src/lib/generate-props-pages.test.ts
index 2911e677..b02b320b 100644
--- a/apps/docs/src/lib/generate-props-pages.test.ts
+++ b/apps/docs/src/lib/generate-props-pages.test.ts
@@ -17,11 +17,11 @@ test('renders a single-entry Props page without a type heading', () => {
const frontmatter = parseComponentFrontmatter(`---
title: Button
description: A labelled control for actions in an interface.
-source: packages/@luke-ui/react/src/button
+source: packages/@luke-ui/react/src/exports/button.ts
reactAria: https://react-spectrum.adobe.com/react-aria/Button.html
props:
- name: ButtonProps
- path: packages/@luke-ui/react/src/button/button.tsx
+ path: packages/@luke-ui/react/src/core/button/button.tsx
---
`);
@@ -29,9 +29,9 @@ props:
expect(page).toContain('## Props');
expect(page).not.toContain('### ');
expect(page).toContain(
- '',
+ '',
);
- expect(page).toContain('source: packages/@luke-ui/react/src/button');
+ expect(page).toContain('source: packages/@luke-ui/react/src/exports/button.ts');
expect(page).not.toContain('props:');
});
@@ -39,12 +39,12 @@ test('renders a multi-entry Props page with a heading per entry', () => {
const frontmatter = parseComponentFrontmatter(`---
title: Field primitive
description: Shared label, description, and validation parts for custom fields.
-source: packages/@luke-ui/react/src/primitives/field
+source: packages/@luke-ui/react/src/exports/primitives/field.ts
props:
- name: FieldProps
- path: packages/@luke-ui/react/src/primitives/field/field.tsx
+ path: packages/@luke-ui/react/src/core/primitives/field/field.tsx
- name: FieldLabelProps
- path: packages/@luke-ui/react/src/primitives/field/label.tsx
+ path: packages/@luke-ui/react/src/core/primitives/field/label.tsx
---
`);
@@ -52,9 +52,7 @@ props:
expect(page).toContain('## Props');
expect(page).toContain('### FieldProps');
expect(page).toContain('### FieldLabelProps');
- expect(page).toContain(
- '',
- );
+ expect(page).toContain('\tpath="packages/@luke-ui/react/src/core/primitives/field/field.tsx"');
expect(page).toContain('name="FieldLabelProps"');
expect(page.indexOf('### FieldProps')).toBeLessThan(page.indexOf('### FieldLabelProps'));
});
@@ -95,7 +93,7 @@ const SCRATCH_GUIDE = `---
title: Button
props:
- name: ButtonProps
- path: packages/@luke-ui/react/src/button/button.tsx
+ path: packages/@luke-ui/react/src/core/button/button.tsx
---
Guide body.
diff --git a/apps/docs/src/lib/storybook.test.ts b/apps/docs/src/lib/storybook.test.ts
index dd6c680d..9910a44d 100644
--- a/apps/docs/src/lib/storybook.test.ts
+++ b/apps/docs/src/lib/storybook.test.ts
@@ -6,7 +6,7 @@ test('links component guides to their Storybook docs', () => {
getStorybookStoryUrl(
'components/actions/icon-button.mdx',
'/',
- 'packages/@luke-ui/react/src/icon-button',
+ 'packages/@luke-ui/react/src/exports/icon-button.ts',
),
).toBe('http://localhost:6006/?path=/docs/actions-iconbutton--docs');
});
@@ -20,7 +20,7 @@ test('does not link component props pages to Storybook', () => {
getStorybookStoryUrl(
'components/actions/icon-button/props.mdx',
'/',
- 'packages/@luke-ui/react/src/icon-button',
+ 'packages/@luke-ui/react/src/exports/icon-button.ts',
),
).toBeNull();
});
diff --git a/apps/docs/vite.config.ts b/apps/docs/vite.config.ts
index c1ef09ab..a4808bd0 100644
--- a/apps/docs/vite.config.ts
+++ b/apps/docs/vite.config.ts
@@ -145,7 +145,10 @@ export default defineConfig(async () => {
resolve: {
alias: {
'#recipe-engine': fileURLToPath(
- new URL('../../packages/@luke-ui/react/src/styles/recipe-engine.ts', import.meta.url),
+ new URL(
+ '../../packages/@luke-ui/react/src/core/styles/recipe-engine.ts',
+ import.meta.url,
+ ),
),
},
tsconfigPaths: true,
diff --git a/docs/COMPONENTS.md b/docs/COMPONENTS.md
index 8efa3a6b..9daa9fcc 100644
--- a/docs/COMPONENTS.md
+++ b/docs/COMPONENTS.md
@@ -31,8 +31,10 @@ pnpm run generate:component --args
Primitive scaffolding is not handled by the component generator.
-Public subpath `index.ts` files are export-only. A component's implementation lives in a named
-sibling file. Support modules and multi-part primitives each keep their own file.
+Public subpath modules live under `src/exports/`. Single-segment subpaths use a flat module such as
+`src/exports/button.ts`. Grouped subpaths use nested modules such as
+`src/exports/primitives/button.ts`. A component's implementation lives under `src/core/`. Support
+modules and multi-part primitives each keep their own file.
The component creation rules live in `packages/turbo-generators/src/component-creation-plan.ts`.
That module owns component-name validation, documentation groups, conformance contracts, and
diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md
index 71b93508..201371e8 100644
--- a/docs/DOCUMENTATION.md
+++ b/docs/DOCUMENTATION.md
@@ -332,10 +332,10 @@ fit. Keep primitive pages at the end of the Components area.
`apps/docs/content/docs/components/meta.json` is the source for component category and order. List
every component guide there once. Each category `meta.json` must list the same components as its
slice of that file, in the same order. A leftover category file with no remaining guides or root
-entries is stale. A guide's `source` must name a public entry point in
-`packages/@luke-ui/react/package.json`, and that directory needs an `index.ts`. `check:docs`
-enforces all of this, so a guide cannot leave the navigation or point at a path a developer cannot
-import.
+entries is stale. A guide's `source` must name the explicit module under
+`packages/@luke-ui/react/src/exports/` for a public entry point in
+`packages/@luke-ui/react/package.json`. `check:docs` enforces all of this, so a guide cannot leave
+the navigation or point at a path a developer cannot import.
## MDX page structure
diff --git a/docs/STYLING.md b/docs/STYLING.md
index 574662bd..416f1942 100644
--- a/docs/STYLING.md
+++ b/docs/STYLING.md
@@ -10,48 +10,51 @@ with no class and no JS required. Neither step injects styles at runtime.
## Structure
-- `styles/index.css.ts`: stylesheet graph in cascade order — layers, reset, theme root, style
+Paths below are rooted in `packages/@luke-ui/react/src/`. Core style, primitive, overlay, and
+utility modules live under `core/`. Theme modules live under `theme/`.
+
+- `core/styles/index.css.ts`: stylesheet graph in cascade order — layers, reset, theme root, style
modules, utilities.
-- `styles/reset.css.ts`: reset scoped to `.luke-ui-reset`.
-- `styles/theme-root.css.ts`: base typography and text colour scoped to `.luke-ui-theme`.
-- `styles/modules.css.ts`: the committed stylesheet registry. It explicitly imports every colocated
- `recipe.css.ts` and `styles.css.ts` that participates in the shipped stylesheet, plus primitive
- and overlay style modules. Keep the list in code-point order by path for deterministic output.
- Named layers make cross-layer priority explicit. Specificity and source order still matter within
- a layer.
-- `styles/recipe.ts`: the internal `recipe()` engine shared by every component recipe, plus the
+- `core/styles/reset.css.ts`: reset scoped to `.luke-ui-reset`.
+- `core/styles/theme-root.css.ts`: base typography and text colour scoped to `.luke-ui-theme`.
+- `core/styles/modules.css.ts`: the committed stylesheet registry. It explicitly imports every
+ colocated `recipe.css.ts` and `styles.css.ts` that participates in the shipped stylesheet, plus
+ primitive and overlay style modules. Keep the list in code-point order by path for deterministic
+ output. Named layers make cross-layer priority explicit. Specificity and source order still matter
+ within a layer.
+- `core/styles/recipe.ts`: the internal `recipe()` engine shared by every component recipe, plus the
`RecipeSelection` helper that derives a recipe's variant type.
-- `styles/input-states.ts`: the shared field control-state selectors (`composeInputStateSelectors`,
- `descendantDisabledSelector`) that field recipes compose. It is named `.ts`, not `.css.ts`,
- because it emits no CSS. Each field recipe's `.css.ts` module composes its plain data and
- functions.
-- `styles/invalid-indicator.ts`: the shared invalid-state `exclamationTriangle` icon, rendered as a
- CSS mask in two sizes. `invalidIndicatorIcon` (plus `invalidIndicatorIconForcedColors`) is the
- in-control icon `primitives/combobox/styles.css.ts` applies under its own invalid selector's
- `::after` — the border stays at its resting 1px there, since the icon is already the non-colour
- cue. It renders as the pseudo-element's own last DOM child, so the style gives its trailing
- affordances (the combobox clear button and trigger) a flex `order` ahead of the icon's default
- `order: 0`, so it lands right after the field's text content and before them, matching the
+- `core/styles/input-states.ts`: the shared field control-state selectors
+ (`composeInputStateSelectors`, `descendantDisabledSelector`) that field recipes compose. It is
+ named `.ts`, not `.css.ts`, because it emits no CSS. Each field recipe's `.css.ts` module composes
+ its plain data and functions.
+- `core/styles/invalid-indicator.ts`: the shared invalid-state `exclamationTriangle` icon, rendered
+ as a CSS mask in two sizes. `invalidIndicatorIcon` (plus `invalidIndicatorIconForcedColors`) is
+ the in-control icon `core/primitives/combobox/styles.css.ts` applies under its own invalid
+ selector's `::after` — the border stays at its resting 1px there, since the icon is already the
+ non-colour cue. It renders as the pseudo-element's own last DOM child, so the style gives its
+ trailing affordances (the combobox clear button and trigger) a flex `order` ahead of the icon's
+ default `order: 0`, so it lands right after the field's text content and before them, matching the
Spectrum reference this ordering is drawn from. `invalidMessageIcon` is the smaller,
- message-leading variant `primitives/field/recipe.css.ts` draws on its `message` slot, switched on
- by `primitives/checkbox/recipe.css.ts` alone: `Checkbox`'s own box has no room for an in-control
- icon without floating past the label, so its icon moves to the message and its box keeps a `2px`
- border as its own non-colour cue instead. Named `.ts` for the same reason as `input-states.ts`: it
- emits no CSS of its own, only plain style-rule data each recipe composes.
-- `primitives/input-group/recipe.css.ts` draws the same glyph, but as a real `Icon` element on its
- own `invalidIndicator` slot rather than a mask: `InputGroup` (`primitives/input-group/`) reads
- React Aria's `Group` `isInvalid` render prop and renders the icon itself, so an invalid control
- cannot be composed without a non-colour cue. The recipe owns only the icon's colour and margins —
- `Icon` owns its box, and `IconSizeProvider` (`FIELD_CONTROL_ICON_SIZE`) owns its per-size step —
- and gives the `suffix` slot the same `order: 1` for the same Spectrum ordering. Combobox's control
- is not a plain `Group` with that state to hand, so it stays CSS-driven.
-- `overlays/mobile-overlay.css.ts`: the backdrop, tray, and dialog styles `MobileOverlay` renders
- for the mobile combobox tray, based on Apache-2.0 React Spectrum's `Tray.tsx` and
+ message-leading variant `core/primitives/field/recipe.css.ts` draws on its `message` slot,
+ switched on by `core/primitives/checkbox/recipe.css.ts` alone: `Checkbox`'s own box has no room
+ for an in-control icon without floating past the label, so its icon moves to the message and its
+ box keeps a `2px` border as its own non-colour cue instead. Named `.ts` for the same reason as
+ `input-states.ts`: it emits no CSS of its own, only plain style-rule data each recipe composes.
+- `core/primitives/input-group/recipe.css.ts` draws the same glyph, but as a real `Icon` element on
+ its own `invalidIndicator` slot rather than a mask: `InputGroup` (`core/primitives/input-group/`)
+ reads React Aria's `Group` `isInvalid` render prop and renders the icon itself, so an invalid
+ control cannot be composed without a non-colour cue. The recipe owns only the icon's colour and
+ margins — `Icon` owns its box, and `IconSizeProvider` (`FIELD_CONTROL_ICON_SIZE`) owns its
+ per-size step — and gives the `suffix` slot the same `order: 1` for the same Spectrum ordering.
+ Combobox's control is not a plain `Group` with that state to hand, so it stays CSS-driven.
+- `core/overlays/mobile-overlay.css.ts`: the backdrop, tray, and dialog styles `MobileOverlay`
+ renders for the mobile combobox tray, based on Apache-2.0 React Spectrum's `Tray.tsx` and
`tray/index.css`.
-- `overlays/`: the private mobile tray plumbing. `mobile-overlay.tsx` wraps React Aria's
+- `core/overlays/`: the private mobile tray plumbing. `mobile-overlay.tsx` wraps React Aria's
`ModalOverlay`, `Modal`, and `Dialog` for the combobox tray. `use-is-mobile-device.ts` reads the
device screen width, not the viewport width, to decide when a combobox switches to it.
-- `styles/`: layout utilities, most exported from `@luke-ui/react/styles`.
+- `core/styles/`: layout utilities, most exported from `@luke-ui/react/styles`.
- `theme/contract.ts`: the theme token tree, the mode-family declaration, `--luke-*` variable
naming, and the source-owned `typeStyles` typography keys.
- `theme/path-record.ts`: the typed `[path, value]` record constructor value producers use so
@@ -96,12 +99,12 @@ with no class and no JS required. Neither step injects styles at runtime.
class never drags in the compiler or a foundation.
- `theme/foundations/tactile.ts` and `theme/foundations/paper.ts`: each bundled theme's
`defineTheme(...)` input, kept as separate leaf modules so importing one never pulls in the other.
-- `themes/tactile/` and `themes/paper/`: each theme's public entrypoint, exported from
+- `theme/bundles/tactile/` and `theme/bundles/paper/`: each theme's public entrypoint, exported from
`@luke-ui/react/themes/tactile` and `@luke-ui/react/themes/paper`. Each exports its own
`themeClassName` identity class and its `theme` (the public `ThemeInput` a consumer can read,
copy, or spread). The class comes from a per-theme `theme-class-name.ts` leaf holding the name as
a literal, so importing the class alone leaves the foundation out of a consumer's bundle.
- `themes/theme-bundle.test.ts` proves that with a real bundler run.
+ `theme/bundles/theme-bundle.test.ts` proves that with a real bundler run.
- `scripts/build-themes.ts`: writes the bundled theme stylesheets to
`dist/themes//stylesheet.css`, alongside the entrypoint `vp pack` emits there.
@@ -180,13 +183,13 @@ Specificity and source order still decide conflicts within a layer.
| `recipes` | Component styles, variants, and compound variants. |
| `utilities` | One-off layout and override escape hatches. |
-Use `styleInLayer` and `globalStyleInLayer` from `styles/layered-style.css.ts` to place a plain
+Use `styleInLayer` and `globalStyleInLayer` from `core/styles/layered-style.css.ts` to place a plain
Vanilla Extract style for a recipe with no variants in a named layer (see
-`loading-skeleton/styles.css.ts`). A variant-driven recipe instead calls `recipe()` from
-`styles/recipe.ts`, which wraps every base, variant, and compound-variant style it is given in the
-`recipes` layer. A recipe can still pre-build a static `base` with `styleInLayer('recipes', …)` and
-hand the resulting class string to `recipe()`, which passes a string value through unchanged rather
-than wrapping it again.
+`core/loading-skeleton/styles.css.ts`). A variant-driven recipe instead calls `recipe()` from
+`core/styles/recipe.ts`, which wraps every base, variant, and compound-variant style it is given in
+the `recipes` layer. A recipe can still pre-build a static `base` with `styleInLayer('recipes', …)`
+and hand the resulting class string to `recipe()`, which passes a string value through unchanged
+rather than wrapping it again.
Text's Capsize trim declarations use logical properties for the pseudo-element margins and are
authored as one of the Text recipe's `recipe()` compound-variant styles, so they remain owned by
@@ -220,10 +223,10 @@ Colocate recipe files beside their owner:
- `recipe.css.ts` — public recipe contract
- `styles.css.ts` — private implementation styling
-Every recipe is built with the internal `recipe()` engine from `styles/recipe.ts`. It is not part of
-the public package entry. Component authors inside `@luke-ui/react` use it to define a new recipe.
-Consumers call the built recipe functions it returns (`buttonRecipe`, `textRecipe`, and so on).
-`recipe()` wraps every base, variant, and compound-variant style it is given in the `recipes`
+Every recipe is built with the internal `recipe()` engine from `core/styles/recipe.ts`. It is not
+part of the public package entry. Component authors inside `@luke-ui/react` use it to define a new
+recipe. Consumers call the built recipe functions it returns (`buttonRecipe`, `textRecipe`, and so
+on). `recipe()` wraps every base, variant, and compound-variant style it is given in the `recipes`
cascade layer itself, so a recipe author does not add layering by hand.
### Single-part recipes
@@ -244,7 +247,7 @@ export const buttonRecipe = recipe({
});
```
-See `primitives/button/recipe.css.ts` for the full recipe this abbreviates.
+See `core/primitives/button/recipe.css.ts` for the full recipe this abbreviates.
### Slotted recipes
@@ -265,7 +268,7 @@ const { label, message, root } = fieldRecipe({ tone: 'description' });
;
```
-See `primitives/field/recipe.css.ts` for a complete public slotted recipe. Apply
+See `core/primitives/field/recipe.css.ts` for a complete public slotted recipe. Apply
`as const satisfies SlottedConfigInput` at the definition site: `as const` preserves the literal
slot names and variant values `recipe()` infers, and `satisfies` type-checks every slot and variant
style against `StyleRule` where it is written.
@@ -288,9 +291,9 @@ with the recipe.
### Shared input-state selectors
-`InputGroup` and Combobox styling (`primitives/input-group/recipe.css.ts`,
-`primitives/combobox/styles.css.ts`) share one definition of what "hovered", "focused", "disabled",
-"invalid", and "read-only" mean for a control, from `styles/input-states.ts`:
+`InputGroup` and Combobox styling (`core/primitives/input-group/recipe.css.ts`,
+`core/primitives/combobox/styles.css.ts`) share one definition of what "hovered", "focused",
+"disabled", "invalid", and "read-only" mean for a control, from `core/styles/input-states.ts`:
```ts
import { composeInputStateSelectors, descendantDisabledSelector } from './input-states.js';
diff --git a/docs/TESTING.md b/docs/TESTING.md
index 111864f3..7f2ce9e4 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -18,7 +18,7 @@ Use the smallest surface that can falsify the intention.
Do not add a new test flavour for a component. Recipe tests are infrastructure tests, not a
component-testing category. If a recipe implementation needs a browser to test its own machinery,
-keep that test under `src/styles/` and exclude it from these component rules.
+keep that test under `src/core/styles/` and exclude it from these component rules.
## Component tests
@@ -109,8 +109,8 @@ menu, focus a control, or reveal a loading state for documentation or an accessi
## Visual tests
-Visual tests use `render()` from `src/test-utils/render.tsx` and capture helpers from
-`src/test-utils/visual.tsx`. The visual test is not a story and does not reuse a story as its
+Visual tests use `render()` from `src/core/test-utils/render.tsx` and capture helpers from
+`src/core/test-utils/visual.tsx`. The visual test is not a story and does not reuse a story as its
fixture.
Each component with a meaningful visual surface gets one kitchen-sink fixture:
diff --git a/docs/VISUAL_TESTING.md b/docs/VISUAL_TESTING.md
index 69aeb2c3..9acf6a2d 100644
--- a/docs/VISUAL_TESTING.md
+++ b/docs/VISUAL_TESTING.md
@@ -15,7 +15,7 @@ current working tree, including uncommitted changes. Fetch before running when y
remote commit. Set `VISUAL_BASE_REF` to use another local ref, such as `upstream/main` in a fork.
The command compares matching capture IDs and writes a self-contained report to
`.artifacts/visual-regression/report/index.html`. Capture identity, harness files, and that artifact
-directory are defined in `packages/@luke-ui/react/src/test-utils/visual-capture-id.ts` and
+directory are defined in `packages/@luke-ui/react/src/core/test-utils/visual-capture-id.ts` and
`packages/@luke-ui/react/scripts/visual-regression-contract.ts`. GitHub Actions cannot import those
modules. A unit test fails if the workflow lists a different harness file or artifact path.
`vitest.config.ts` is copied into the base worktree, so it also cannot import those modules. A unit
diff --git a/knip.config.ts b/knip.config.ts
index 60a4ff45..d35b2b05 100644
--- a/knip.config.ts
+++ b/knip.config.ts
@@ -24,9 +24,9 @@ export default {
'packages/@luke-ui/react': {
entry: [
'src/**/*.stories.tsx',
- 'src/**/index.ts',
- 'src/stylesheet.css.ts',
- 'src/styles/index.css.ts',
+ 'src/exports/**/*.ts',
+ 'src/core/stylesheet.css.ts',
+ 'src/core/styles/index.css.ts',
'scripts/**/*.ts',
],
project: ['src/**/*.{ts,tsx}'],
diff --git a/packages/@luke-ui/react/.storybook/main.ts b/packages/@luke-ui/react/.storybook/main.ts
index 2030372d..4a44f9f7 100644
--- a/packages/@luke-ui/react/.storybook/main.ts
+++ b/packages/@luke-ui/react/.storybook/main.ts
@@ -36,7 +36,7 @@ export default defineMain({
config.resolve.alias = [
{
find: '#recipe-engine',
- replacement: resolve(srcDir, 'styles/recipe-engine.ts'),
+ replacement: resolve(srcDir, 'core/styles/recipe-engine.ts'),
},
{
find: /^@luke-ui\/react\/spritesheet\.svg(\?.*)?$/,
@@ -44,11 +44,11 @@ export default defineMain({
},
{
find: /^@luke-ui\/react\/stylesheet\.css(\?.*)?$/,
- replacement: `${resolve(srcDir, 'stylesheet.css.ts')}$1`,
+ replacement: `${resolve(srcDir, 'core/stylesheet.css.ts')}$1`,
},
{
find: /^@luke-ui\/react\/(.+?)(\?.*)?$/,
- replacement: `${srcDir}/$1$2`,
+ replacement: `${resolve(srcDir, 'exports')}/$1$2`,
},
...existingAliases,
];
diff --git a/packages/@luke-ui/react/AGENTS.md b/packages/@luke-ui/react/AGENTS.md
index 2563b2da..14b23686 100644
--- a/packages/@luke-ui/react/AGENTS.md
+++ b/packages/@luke-ui/react/AGENTS.md
@@ -3,8 +3,9 @@
- Do not hand-edit `.generated/entries.ts` or `package.json#exports`. `vp pack` generates entries
and updates exports during build. The `stylesheet` build entry is excluded from the public export
map via `exports.exclude` in `vite.config.ts`. Vanilla Extract serializes recipes to
- `#recipe-engine`; pack, Vitest, Storybook, and the docs app alias that specifier to source. Pack
- then bundles a relative runtime chunk. The specifier is not a public package subpath.
+ `#recipe-engine`; pack, Vitest, Storybook, and the docs app alias that specifier to
+ `src/core/styles/recipe-engine.ts`. Pack then bundles a relative runtime chunk. The specifier is
+ not a public package subpath.
- When adding a component, use `pnpm generate:component` from the repo root. Do not create component
files by hand. The generator updates the style-module registry, conformance manifest, and docs
wiring.
@@ -14,19 +15,34 @@
- React Compiler is enabled. Do not use `useCallback` or `useMemo` unless there is a specific reason
the compiler cannot handle.
+## Source structure
+
+- `src/core/` holds implementation, styles, test helpers, and their co-located tests, stories, and
+ fixtures.
+- `src/exports/` holds the thin public modules that pack publishes for each package subpath.
+- `src/theme/` holds the theme compiler, foundations, and bundled themes in `bundles/`.
+- `src/shared/` holds low-level primitives with no domain of their own, such as the class-name
+ constants and the typed object helpers. It exists so a zone never has to duplicate a value or
+ reach across a forbidden edge to use one. It stays deliberately small. Do not put component code,
+ theme compiler logic, recipes, or other domain-specific implementation there.
+
+`exports` may import from `core`, `theme`, and `shared`. `core` may import from `theme` and
+`shared`. `theme` may import from `shared`. `shared` imports from no other zone.
+
## Component structure
-A component directory contains:
+`src/exports/` is the only barrel layer in the package. A component directory holds implementation,
+styles, tests, and stories, and its matching `src/exports/` module re-exports directly from those
+files. A component directory contains:
- `[component].stories.tsx`: Storybook documentation and render/a11y fixtures
- `[component].browser.test.tsx`: component behaviour, conformance, and the integration tripwire
- `[component].visual.test.tsx`: visual regression captures when the component has a visual surface
- `.tsx`: component implementation
-- `index.ts`: export-only public subpath barrel
- `recipe.css.ts`: public recipe contract (scaffolded by the generator)
- `styles.css.ts`: private implementation styling when needed
-Lower-level composition APIs live under `src/primitives/*` and export from
+Lower-level composition APIs live under `src/core/primitives/*` and export from
`@luke-ui/react/primitives/*`. See [`docs/COMPONENTS.md`](../../docs/COMPONENTS.md).
## Exported prop types
diff --git a/packages/@luke-ui/react/package.json b/packages/@luke-ui/react/package.json
index d86fe052..f468ba1a 100644
--- a/packages/@luke-ui/react/package.json
+++ b/packages/@luke-ui/react/package.json
@@ -19,37 +19,37 @@
"./dist/themes/*/stylesheet.css"
],
"exports": {
- "./blockquote": "./dist/blockquote/index.js",
- "./box": "./dist/box/index.js",
- "./button": "./dist/button/index.js",
- "./checkbox": "./dist/checkbox/index.js",
- "./code": "./dist/code/index.js",
- "./combobox-field": "./dist/combobox-field/index.js",
- "./em": "./dist/em/index.js",
- "./emoji": "./dist/emoji/index.js",
- "./heading": "./dist/heading/index.js",
- "./icon": "./dist/icon/index.js",
- "./icon-button": "./dist/icon-button/index.js",
- "./kbd": "./dist/kbd/index.js",
- "./link": "./dist/link/index.js",
- "./loading-skeleton": "./dist/loading-skeleton/index.js",
- "./loading-spinner": "./dist/loading-spinner/index.js",
- "./numeral": "./dist/numeral/index.js",
- "./primitives/button": "./dist/primitives/button/index.js",
- "./primitives/checkbox": "./dist/primitives/checkbox/index.js",
- "./primitives/combobox": "./dist/primitives/combobox/index.js",
- "./primitives/field": "./dist/primitives/field/index.js",
- "./primitives/input-group": "./dist/primitives/input-group/index.js",
- "./quote": "./dist/quote/index.js",
- "./strong": "./dist/strong/index.js",
- "./styles": "./dist/styles/index.js",
- "./text": "./dist/text/index.js",
- "./text-field": "./dist/text-field/index.js",
- "./theme": "./dist/theme/index.js",
- "./themes/paper": "./dist/themes/paper/index.js",
- "./themes/tactile": "./dist/themes/tactile/index.js",
- "./utils": "./dist/utils/index.js",
- "./visually-hidden": "./dist/visually-hidden/index.js",
+ "./blockquote": "./dist/blockquote.js",
+ "./box": "./dist/box.js",
+ "./button": "./dist/button.js",
+ "./checkbox": "./dist/checkbox.js",
+ "./code": "./dist/code.js",
+ "./combobox-field": "./dist/combobox-field.js",
+ "./em": "./dist/em.js",
+ "./emoji": "./dist/emoji.js",
+ "./heading": "./dist/heading.js",
+ "./icon": "./dist/icon.js",
+ "./icon-button": "./dist/icon-button.js",
+ "./kbd": "./dist/kbd.js",
+ "./link": "./dist/link.js",
+ "./loading-skeleton": "./dist/loading-skeleton.js",
+ "./loading-spinner": "./dist/loading-spinner.js",
+ "./numeral": "./dist/numeral.js",
+ "./primitives/button": "./dist/primitives/button.js",
+ "./primitives/checkbox": "./dist/primitives/checkbox.js",
+ "./primitives/combobox": "./dist/primitives/combobox.js",
+ "./primitives/field": "./dist/primitives/field.js",
+ "./primitives/input-group": "./dist/primitives/input-group.js",
+ "./quote": "./dist/quote.js",
+ "./strong": "./dist/strong.js",
+ "./styles": "./dist/styles.js",
+ "./text": "./dist/text.js",
+ "./text-field": "./dist/text-field.js",
+ "./theme": "./dist/theme.js",
+ "./themes/paper": "./dist/themes/paper.js",
+ "./themes/tactile": "./dist/themes/tactile.js",
+ "./utils": "./dist/utils.js",
+ "./visually-hidden": "./dist/visually-hidden.js",
"./package.json": "./package.json",
"./stylesheet.css": "./dist/stylesheet.css",
"./spritesheet.svg": "./dist/spritesheet.svg",
@@ -63,7 +63,7 @@
"build": "pnpm run build:tsdown",
"build:storybook": "storybook build",
"build:tsdown": "vp pack",
- "check:barrels": "unbarrelify --check --skip 'src/*/index.ts' --skip 'src/primitives/*/index.ts' --skip 'src/themes/*/index.ts'",
+ "check:barrels": "unbarrelify --check --skip 'src/exports/**/*.ts' --skip 'src/theme/index.ts' --skip 'src/theme/bundles/*/index.ts'",
"check:format": "vp fmt . --check",
"check:lint": "vp lint . --type-aware",
"check:story-plays": "tsx scripts/check-story-plays.ts",
diff --git a/packages/@luke-ui/react/scripts/visual-regression-contract.ts b/packages/@luke-ui/react/scripts/visual-regression-contract.ts
index 731b084f..ac9c9053 100644
--- a/packages/@luke-ui/react/scripts/visual-regression-contract.ts
+++ b/packages/@luke-ui/react/scripts/visual-regression-contract.ts
@@ -3,12 +3,16 @@ import { fileURLToPath } from 'node:url';
export const VISUAL_LOCKFILE = 'pnpm-lock.yaml';
-/** Files that shape how a capture renders. Hashed into the cache key and copied into the base worktree. */
+/** Marks a baseline worktree that can accept the current visual harness. */
+export const VISUAL_HARNESS_LAYOUT_FILE = 'packages/@luke-ui/react/src/core/stylesheet.css.ts';
+
+/** Files that shape how a capture renders. Hashed into the cache key and copied into compatible base worktrees. */
export const VISUAL_HARNESS_FILES = [
'packages/@luke-ui/react/vitest.config.ts',
- 'packages/@luke-ui/react/src/test-utils/render-setup.ts',
- 'packages/@luke-ui/react/src/test-utils/render.tsx',
- 'packages/@luke-ui/react/src/test-utils/visual-setup.ts',
+ VISUAL_HARNESS_LAYOUT_FILE,
+ 'packages/@luke-ui/react/src/core/test-utils/render-setup.ts',
+ 'packages/@luke-ui/react/src/core/test-utils/render.tsx',
+ 'packages/@luke-ui/react/src/core/test-utils/visual-setup.ts',
] as const;
export const VISUAL_CACHE_HASH_FILES = [VISUAL_LOCKFILE, ...VISUAL_HARNESS_FILES];
diff --git a/packages/@luke-ui/react/scripts/visual-regression-lib.test.ts b/packages/@luke-ui/react/scripts/visual-regression-lib.test.ts
index 49977e3b..f78eb075 100644
--- a/packages/@luke-ui/react/scripts/visual-regression-lib.test.ts
+++ b/packages/@luke-ui/react/scripts/visual-regression-lib.test.ts
@@ -6,7 +6,7 @@ import { expect, test } from 'vite-plus/test';
import {
formatVisualCaptureName,
formatVisualViewport,
-} from '../src/test-utils/visual-capture-id.js';
+} from '../src/core/test-utils/visual-capture-id.js';
import { assertCapturesPainted, compareCaptures, renderReport } from './visual-regression-lib.js';
const png = (red: number) => {
diff --git a/packages/@luke-ui/react/scripts/visual-regression-lib.ts b/packages/@luke-ui/react/scripts/visual-regression-lib.ts
index 06ed56dd..dcd88955 100644
--- a/packages/@luke-ui/react/scripts/visual-regression-lib.ts
+++ b/packages/@luke-ui/react/scripts/visual-regression-lib.ts
@@ -2,7 +2,7 @@ import path from 'node:path';
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';
-import { parseVisualCaptureIdentity } from '../src/test-utils/visual-capture-id.js';
+import { parseVisualCaptureIdentity } from '../src/core/test-utils/visual-capture-id.js';
export type VisualResult = {
id: string;
diff --git a/packages/@luke-ui/react/scripts/visual-regression-runner.ts b/packages/@luke-ui/react/scripts/visual-regression-runner.ts
index 0f8ce8d6..fa4ec7c5 100644
--- a/packages/@luke-ui/react/scripts/visual-regression-runner.ts
+++ b/packages/@luke-ui/react/scripts/visual-regression-runner.ts
@@ -7,6 +7,7 @@ import {
VISUAL_CACHE_HASH_FILES,
VISUAL_CAPTURE_DIR_ENV,
VISUAL_HARNESS_FILES,
+ VISUAL_HARNESS_LAYOUT_FILE,
visualArtifactsRoot,
visualPackageRoot,
visualRepoRootFromPackage,
@@ -130,7 +131,7 @@ export async function runVisualRegression(io: VisualRegressionIo = createVisualR
}
async function capture(io: VisualRegressionIo, worktree: string, target: string) {
- if (worktree !== io.repoRoot) {
+ if (worktree !== io.repoRoot && (await hasCurrentVisualHarnessLayout(io, worktree))) {
await Promise.all(
VISUAL_HARNESS_FILES.map(async (file) => {
const destination = path.join(worktree, file);
@@ -167,3 +168,15 @@ async function capture(io: VisualRegressionIo, worktree: string, target: string)
{ ...io.env, [VISUAL_CAPTURE_DIR_ENV]: target },
);
}
+
+async function hasCurrentVisualHarnessLayout(
+ io: VisualRegressionIo,
+ worktree: string,
+): Promise {
+ try {
+ await io.readFile(path.join(worktree, VISUAL_HARNESS_LAYOUT_FILE));
+ return true;
+ } catch {
+ return false;
+ }
+}
diff --git a/packages/@luke-ui/react/src/blockquote/index.ts b/packages/@luke-ui/react/src/blockquote/index.ts
deleted file mode 100644
index 0ecc08ac..00000000
--- a/packages/@luke-ui/react/src/blockquote/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { Blockquote, type BlockquoteProps } from './blockquote.js';
-export { type BlockquoteRecipeVariants, blockquoteRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/box/index.ts b/packages/@luke-ui/react/src/box/index.ts
deleted file mode 100644
index 4cc734ce..00000000
--- a/packages/@luke-ui/react/src/box/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Box, type BoxProps } from './box.js';
diff --git a/packages/@luke-ui/react/src/button/index.ts b/packages/@luke-ui/react/src/button/index.ts
deleted file mode 100644
index 58bbed93..00000000
--- a/packages/@luke-ui/react/src/button/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { type ButtonRecipeVariants, buttonRecipe } from '../primitives/button/recipe.css.js';
-export { Button, type ButtonProps } from './button.js';
diff --git a/packages/@luke-ui/react/src/checkbox/index.ts b/packages/@luke-ui/react/src/checkbox/index.ts
deleted file mode 100644
index 8550f049..00000000
--- a/packages/@luke-ui/react/src/checkbox/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { type CheckboxRecipeVariants, checkboxRecipe } from '../primitives/checkbox/recipe.css.js';
-export { Checkbox, type CheckboxProps } from './checkbox.js';
diff --git a/packages/@luke-ui/react/src/code/index.ts b/packages/@luke-ui/react/src/code/index.ts
deleted file mode 100644
index 52c9543c..00000000
--- a/packages/@luke-ui/react/src/code/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { Code, type CodeProps } from './code.js';
-export { type CodeRecipeVariants, codeRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/combobox-field/index.ts b/packages/@luke-ui/react/src/combobox-field/index.ts
deleted file mode 100644
index c66a32c6..00000000
--- a/packages/@luke-ui/react/src/combobox-field/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { ComboboxField, type ComboboxFieldProps } from './combobox-field.js';
diff --git a/packages/@luke-ui/react/src/blockquote/blockquote.browser.test.tsx b/packages/@luke-ui/react/src/core/blockquote/blockquote.browser.test.tsx
similarity index 90%
rename from packages/@luke-ui/react/src/blockquote/blockquote.browser.test.tsx
rename to packages/@luke-ui/react/src/core/blockquote/blockquote.browser.test.tsx
index 4661fbce..0c669f86 100644
--- a/packages/@luke-ui/react/src/blockquote/blockquote.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/blockquote/blockquote.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Blockquote } from './index.js';
+import { Blockquote } from './blockquote.js';
testConformance({
path: 'blockquote',
diff --git a/packages/@luke-ui/react/src/blockquote/blockquote.stories.tsx b/packages/@luke-ui/react/src/core/blockquote/blockquote.stories.tsx
similarity index 90%
rename from packages/@luke-ui/react/src/blockquote/blockquote.stories.tsx
rename to packages/@luke-ui/react/src/core/blockquote/blockquote.stories.tsx
index fdd2fd58..2c6103c5 100644
--- a/packages/@luke-ui/react/src/blockquote/blockquote.stories.tsx
+++ b/packages/@luke-ui/react/src/core/blockquote/blockquote.stories.tsx
@@ -1,6 +1,6 @@
import { Blockquote } from '@luke-ui/react/blockquote';
import { Text } from '@luke-ui/react/text';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Blockquote,
diff --git a/packages/@luke-ui/react/src/blockquote/blockquote.tsx b/packages/@luke-ui/react/src/core/blockquote/blockquote.tsx
similarity index 94%
rename from packages/@luke-ui/react/src/blockquote/blockquote.tsx
rename to packages/@luke-ui/react/src/core/blockquote/blockquote.tsx
index 7a33c805..053f24df 100644
--- a/packages/@luke-ui/react/src/blockquote/blockquote.tsx
+++ b/packages/@luke-ui/react/src/core/blockquote/blockquote.tsx
@@ -1,8 +1,8 @@
+import { cx } from '../../shared/utils/utils.js';
import type { TextProps } from '../text/text.js';
import { Text } from '../text/text.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import { blockquoteRecipe } from './recipe.css.js';
type _BlockquoteOmit = DistributiveOmit;
diff --git a/packages/@luke-ui/react/src/blockquote/recipe.css.ts b/packages/@luke-ui/react/src/core/blockquote/recipe.css.ts
similarity index 91%
rename from packages/@luke-ui/react/src/blockquote/recipe.css.ts
rename to packages/@luke-ui/react/src/core/blockquote/recipe.css.ts
index f3899f54..1f7a12c4 100644
--- a/packages/@luke-ui/react/src/blockquote/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/blockquote/recipe.css.ts
@@ -1,7 +1,7 @@
+import { vars } from '../../theme/contract.css.js';
import { styleInLayer } from '../styles/layered-style.css.js';
import type { RecipeSelection } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
-import { vars } from '../theme/contract.css.js';
const base = styleInLayer('recipes', {
borderInlineStart: `3px solid ${vars.color.border.decorative}`,
diff --git a/packages/@luke-ui/react/src/box/box.browser.test.tsx b/packages/@luke-ui/react/src/core/box/box.browser.test.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/box/box.browser.test.tsx
rename to packages/@luke-ui/react/src/core/box/box.browser.test.tsx
index 733fcc59..f3853f86 100644
--- a/packages/@luke-ui/react/src/box/box.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/box/box.browser.test.tsx
@@ -1,7 +1,7 @@
import { expect, test } from 'vite-plus/test';
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Box } from './index.js';
+import { Box } from './box.js';
testConformance({
path: 'box',
diff --git a/packages/@luke-ui/react/src/box/box.stories.tsx b/packages/@luke-ui/react/src/core/box/box.stories.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/box/box.stories.tsx
rename to packages/@luke-ui/react/src/core/box/box.stories.tsx
index 45276490..4840abe6 100644
--- a/packages/@luke-ui/react/src/box/box.stories.tsx
+++ b/packages/@luke-ui/react/src/core/box/box.stories.tsx
@@ -2,7 +2,7 @@ import type { BoxProps } from '@luke-ui/react/box';
import { Box } from '@luke-ui/react/box';
import { vars } from '@luke-ui/react/theme';
import type { ComponentPropsWithRef } from 'react';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Box,
diff --git a/packages/@luke-ui/react/src/box/box.tsx b/packages/@luke-ui/react/src/core/box/box.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/box/box.tsx
rename to packages/@luke-ui/react/src/core/box/box.tsx
index 363955bd..8af7e1ff 100644
--- a/packages/@luke-ui/react/src/box/box.tsx
+++ b/packages/@luke-ui/react/src/core/box/box.tsx
@@ -1,9 +1,9 @@
import type { HTMLAttributes, JSX, ReactElement, Ref, RefCallback } from 'react';
+import { mergeProps } from '../../shared/utils/utils.js';
import type { SprinklesProps } from '../styles/utilities.css.js';
import { createSprinkles } from '../styles/utilities.css.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { mergeProps } from '../utils/utils.js';
/** Props for `Box`. */
export type BoxProps = Prettify<_BoxElementProps | _BoxRenderProps>;
diff --git a/packages/@luke-ui/react/src/box/box.visual.test.tsx b/packages/@luke-ui/react/src/core/box/box.visual.test.tsx
similarity index 91%
rename from packages/@luke-ui/react/src/box/box.visual.test.tsx
rename to packages/@luke-ui/react/src/core/box/box.visual.test.tsx
index f230c163..dc60f09a 100644
--- a/packages/@luke-ui/react/src/box/box.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/box/box.visual.test.tsx
@@ -1,8 +1,8 @@
import { test } from 'vite-plus/test';
+import { vars } from '../../theme/index.js';
import { render, visualAppearances } from '../test-utils/render.js';
import { captureVisualAppearance } from '../test-utils/visual.js';
-import { vars } from '../theme/index.js';
-import { Box } from './index.js';
+import { Box } from './box.js';
for (const appearance of visualAppearances) {
test(`layout: ${appearance.theme} ${appearance.mode}`, async () => {
diff --git a/packages/@luke-ui/react/src/button/button.browser.test.tsx b/packages/@luke-ui/react/src/core/button/button.browser.test.tsx
similarity index 95%
rename from packages/@luke-ui/react/src/button/button.browser.test.tsx
rename to packages/@luke-ui/react/src/core/button/button.browser.test.tsx
index c971f097..b302de78 100644
--- a/packages/@luke-ui/react/src/button/button.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/button/button.browser.test.tsx
@@ -1,7 +1,7 @@
import { expect } from 'vite-plus/test';
import { testConformance, testIntegration } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Button } from './index.js';
+import { Button } from './button.js';
testConformance({
path: 'button',
diff --git a/packages/@luke-ui/react/src/button/button.stories.tsx b/packages/@luke-ui/react/src/core/button/button.stories.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/button/button.stories.tsx
rename to packages/@luke-ui/react/src/core/button/button.stories.tsx
index 757ec094..9dee82f1 100644
--- a/packages/@luke-ui/react/src/button/button.stories.tsx
+++ b/packages/@luke-ui/react/src/core/button/button.stories.tsx
@@ -3,8 +3,8 @@ import { Button } from '@luke-ui/react/button';
import { Icon } from '@luke-ui/react/icon';
import type { CSSProperties } from 'react';
import { fn } from 'storybook/test';
-import preview from '../../.storybook/preview.js';
-import { vars } from '../theme/index.js';
+import preview from '../../../.storybook/preview.js';
+import { vars } from '../../theme/index.js';
const meta = preview.meta({
component: Button,
diff --git a/packages/@luke-ui/react/src/button/button.tsx b/packages/@luke-ui/react/src/core/button/button.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/button/button.tsx
rename to packages/@luke-ui/react/src/core/button/button.tsx
diff --git a/packages/@luke-ui/react/src/button/button.visual.test.tsx b/packages/@luke-ui/react/src/core/button/button.visual.test.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/button/button.visual.test.tsx
rename to packages/@luke-ui/react/src/core/button/button.visual.test.tsx
index 20238401..460b31b1 100644
--- a/packages/@luke-ui/react/src/button/button.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/button/button.visual.test.tsx
@@ -1,6 +1,6 @@
import { test } from 'vite-plus/test';
import { page, userEvent } from 'vite-plus/test/context';
-import { Icon } from '../icon/index.js';
+import { Icon } from '../icon/icon.js';
import { render, visualAppearances } from '../test-utils/render.js';
import {
captureVisual,
@@ -10,7 +10,7 @@ import {
Grid,
variantValuesFor,
} from '../test-utils/visual.js';
-import { Button } from './index.js';
+import { Button } from './button.js';
const tones = variantValuesFor()(['neutral', 'accent', 'danger']);
const appearances = variantValuesFor()(['solid', 'subtle', 'ghost']);
diff --git a/packages/@luke-ui/react/src/button/styles.css.ts b/packages/@luke-ui/react/src/core/button/styles.css.ts
similarity index 94%
rename from packages/@luke-ui/react/src/button/styles.css.ts
rename to packages/@luke-ui/react/src/core/button/styles.css.ts
index 0ec4d16f..68af3c5e 100644
--- a/packages/@luke-ui/react/src/button/styles.css.ts
+++ b/packages/@luke-ui/react/src/core/button/styles.css.ts
@@ -1,7 +1,7 @@
+import { vars } from '../../theme/contract.css.js';
import type { RecipeSelection } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
import { spinnerOverlayBase } from '../styles/spinner-overlay.js';
-import { vars } from '../theme/contract.css.js';
export const buttonContent = recipe({
base: {
diff --git a/packages/@luke-ui/react/src/checkbox/checkbox.browser.test.tsx b/packages/@luke-ui/react/src/core/checkbox/checkbox.browser.test.tsx
similarity index 99%
rename from packages/@luke-ui/react/src/checkbox/checkbox.browser.test.tsx
rename to packages/@luke-ui/react/src/core/checkbox/checkbox.browser.test.tsx
index 92899455..46c4784f 100644
--- a/packages/@luke-ui/react/src/checkbox/checkbox.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/checkbox/checkbox.browser.test.tsx
@@ -2,7 +2,7 @@ import { expect, test } from 'vite-plus/test';
import { cdp, page } from 'vite-plus/test/context';
import { testConformance, testIntegration } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Checkbox } from './index.js';
+import { Checkbox } from './checkbox.js';
testConformance({
path: 'checkbox',
diff --git a/packages/@luke-ui/react/src/checkbox/checkbox.stories.tsx b/packages/@luke-ui/react/src/core/checkbox/checkbox.stories.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/checkbox/checkbox.stories.tsx
rename to packages/@luke-ui/react/src/core/checkbox/checkbox.stories.tsx
index 9d3c69b8..23be6caa 100644
--- a/packages/@luke-ui/react/src/checkbox/checkbox.stories.tsx
+++ b/packages/@luke-ui/react/src/core/checkbox/checkbox.stories.tsx
@@ -2,7 +2,7 @@ import { Checkbox } from '@luke-ui/react/checkbox';
import type { CSSProperties } from 'react';
import { Form } from 'react-aria-components/Form';
import { userEvent, within } from 'storybook/test';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const stackStyle = {
display: 'flex',
diff --git a/packages/@luke-ui/react/src/checkbox/checkbox.tsx b/packages/@luke-ui/react/src/core/checkbox/checkbox.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/checkbox/checkbox.tsx
rename to packages/@luke-ui/react/src/core/checkbox/checkbox.tsx
diff --git a/packages/@luke-ui/react/src/checkbox/checkbox.visual.test.tsx b/packages/@luke-ui/react/src/core/checkbox/checkbox.visual.test.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/checkbox/checkbox.visual.test.tsx
rename to packages/@luke-ui/react/src/core/checkbox/checkbox.visual.test.tsx
index 41fec9bf..9309406b 100644
--- a/packages/@luke-ui/react/src/checkbox/checkbox.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/checkbox/checkbox.visual.test.tsx
@@ -1,6 +1,7 @@
import { test } from 'vite-plus/test';
import type { Locator } from 'vite-plus/test/context';
import { page, userEvent } from 'vite-plus/test/context';
+import { typeStyles } from '../../theme/contract.js';
import { render, visualAppearances } from '../test-utils/render.js';
import {
captureVisual,
@@ -9,9 +10,8 @@ import {
focusViaKeyboard,
Stack,
} from '../test-utils/visual.js';
-import { Text } from '../text/index.js';
-import { typeStyles } from '../theme/contract.js';
-import { Checkbox } from './index.js';
+import { Text } from '../text/text.js';
+import { Checkbox } from './checkbox.js';
test('kitchen sink', async () => {
for (const appearance of visualAppearances) {
diff --git a/packages/@luke-ui/react/src/code/code.browser.test.tsx b/packages/@luke-ui/react/src/core/code/code.browser.test.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/code/code.browser.test.tsx
rename to packages/@luke-ui/react/src/core/code/code.browser.test.tsx
index 29004c2d..f74ff2c1 100644
--- a/packages/@luke-ui/react/src/code/code.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/code/code.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Code } from './index.js';
+import { Code } from './code.js';
testConformance({
path: 'code',
diff --git a/packages/@luke-ui/react/src/code/code.stories.tsx b/packages/@luke-ui/react/src/core/code/code.stories.tsx
similarity index 88%
rename from packages/@luke-ui/react/src/code/code.stories.tsx
rename to packages/@luke-ui/react/src/core/code/code.stories.tsx
index 2944238a..0a92cbd5 100644
--- a/packages/@luke-ui/react/src/code/code.stories.tsx
+++ b/packages/@luke-ui/react/src/core/code/code.stories.tsx
@@ -1,6 +1,6 @@
import { Code } from '@luke-ui/react/code';
import { Text } from '@luke-ui/react/text';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Code,
diff --git a/packages/@luke-ui/react/src/code/code.tsx b/packages/@luke-ui/react/src/core/code/code.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/code/code.tsx
rename to packages/@luke-ui/react/src/core/code/code.tsx
index 0ae31a3a..4ccbf537 100644
--- a/packages/@luke-ui/react/src/code/code.tsx
+++ b/packages/@luke-ui/react/src/core/code/code.tsx
@@ -1,6 +1,6 @@
+import { cx } from '../../shared/utils/utils.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import { codeRecipe } from './recipe.css.js';
type _CodeOmit = DistributiveOmit, never>;
diff --git a/packages/@luke-ui/react/src/code/recipe.css.ts b/packages/@luke-ui/react/src/core/code/recipe.css.ts
similarity index 92%
rename from packages/@luke-ui/react/src/code/recipe.css.ts
rename to packages/@luke-ui/react/src/core/code/recipe.css.ts
index 93868be4..9cb3f08d 100644
--- a/packages/@luke-ui/react/src/code/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/code/recipe.css.ts
@@ -1,7 +1,7 @@
+import { vars } from '../../theme/contract.css.js';
import { styleInLayer } from '../styles/layered-style.css.js';
import type { RecipeSelection } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
-import { vars } from '../theme/contract.css.js';
const base = styleInLayer('recipes', {
backgroundColor: vars.color.surface.recessed,
diff --git a/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx b/packages/@luke-ui/react/src/core/combobox-field/combobox-field.browser.test.tsx
similarity index 99%
rename from packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx
rename to packages/@luke-ui/react/src/core/combobox-field/combobox-field.browser.test.tsx
index 0501dd2e..00dbc9ee 100644
--- a/packages/@luke-ui/react/src/combobox-field/combobox-field.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/combobox-field/combobox-field.browser.test.tsx
@@ -9,7 +9,7 @@ import { ComboboxRoot } from '../primitives/combobox/root.js';
import { mockScreenWidth } from '../test-utils/mock-screen-width.js';
import { render } from '../test-utils/render.js';
import { waitForOverlayEnter } from '../test-utils/wait-for-overlay-enter.js';
-import { ComboboxField } from './index.js';
+import { ComboboxField } from './combobox-field.js';
type CountryItem = {
id: string;
diff --git a/packages/@luke-ui/react/src/combobox-field/combobox-field.stories.tsx b/packages/@luke-ui/react/src/core/combobox-field/combobox-field.stories.tsx
similarity index 99%
rename from packages/@luke-ui/react/src/combobox-field/combobox-field.stories.tsx
rename to packages/@luke-ui/react/src/core/combobox-field/combobox-field.stories.tsx
index 3dd4119c..6f5d3f64 100644
--- a/packages/@luke-ui/react/src/combobox-field/combobox-field.stories.tsx
+++ b/packages/@luke-ui/react/src/core/combobox-field/combobox-field.stories.tsx
@@ -7,7 +7,7 @@ import type { Key } from 'react-aria-components/Breadcrumbs';
import { Form } from 'react-aria-components/Form';
import { useAsyncList } from 'react-aria-components/useAsyncList';
import { userEvent, within } from 'storybook/test';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: ComboboxField,
diff --git a/packages/@luke-ui/react/src/combobox-field/combobox-field.tsx b/packages/@luke-ui/react/src/core/combobox-field/combobox-field.tsx
similarity index 99%
rename from packages/@luke-ui/react/src/combobox-field/combobox-field.tsx
rename to packages/@luke-ui/react/src/core/combobox-field/combobox-field.tsx
index 07836c0a..37129e9e 100644
--- a/packages/@luke-ui/react/src/combobox-field/combobox-field.tsx
+++ b/packages/@luke-ui/react/src/core/combobox-field/combobox-field.tsx
@@ -9,6 +9,7 @@ import { LabelContext } from 'react-aria-components/Label';
import { PopoverContext } from 'react-aria-components/Popover';
import { composeRenderProps } from 'react-aria-components/composeRenderProps';
import { useSlottedContext } from 'react-aria-components/slots';
+import { cx } from '../../shared/utils/utils.js';
import { Icon } from '../icon/icon.js';
import { LoadingSpinner } from '../loading-spinner/loading-spinner.js';
import { MobileOverlay } from '../overlays/mobile-overlay.js';
@@ -35,7 +36,6 @@ import {
} from '../primitives/field/field.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
type ComboboxLoadingState = 'error' | 'filtering' | 'idle' | 'loading' | 'loadingMore' | 'sorting';
diff --git a/packages/@luke-ui/react/src/combobox-field/combobox-field.visual.test.tsx b/packages/@luke-ui/react/src/core/combobox-field/combobox-field.visual.test.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/combobox-field/combobox-field.visual.test.tsx
rename to packages/@luke-ui/react/src/core/combobox-field/combobox-field.visual.test.tsx
index 104f0ab0..781932c3 100644
--- a/packages/@luke-ui/react/src/combobox-field/combobox-field.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/combobox-field/combobox-field.visual.test.tsx
@@ -1,7 +1,7 @@
import { expect, test } from 'vite-plus/test';
import { page, userEvent } from 'vite-plus/test/context';
-import { Icon } from '../icon/index.js';
-import { LoadingSpinner } from '../loading-spinner/index.js';
+import { Icon } from '../icon/icon.js';
+import { LoadingSpinner } from '../loading-spinner/loading-spinner.js';
import { ComboboxInputGroup } from '../primitives/combobox/input-group.js';
import { ComboboxInput } from '../primitives/combobox/input.js';
import { ComboboxItem, ComboboxLoadMoreItem } from '../primitives/combobox/item.js';
@@ -21,7 +21,7 @@ import {
Stack,
} from '../test-utils/visual.js';
import { waitForOverlayEnter } from '../test-utils/wait-for-overlay-enter.js';
-import { ComboboxField } from './index.js';
+import { ComboboxField } from './combobox-field.js';
type CountryItem = {
id: string;
diff --git a/packages/@luke-ui/react/src/conformance/helpers.ts b/packages/@luke-ui/react/src/core/conformance/helpers.ts
similarity index 100%
rename from packages/@luke-ui/react/src/conformance/helpers.ts
rename to packages/@luke-ui/react/src/core/conformance/helpers.ts
diff --git a/packages/@luke-ui/react/src/conformance/manifest.test.ts b/packages/@luke-ui/react/src/core/conformance/manifest.test.ts
similarity index 94%
rename from packages/@luke-ui/react/src/conformance/manifest.test.ts
rename to packages/@luke-ui/react/src/core/conformance/manifest.test.ts
index d8d10b39..f1e26618 100644
--- a/packages/@luke-ui/react/src/conformance/manifest.test.ts
+++ b/packages/@luke-ui/react/src/core/conformance/manifest.test.ts
@@ -6,8 +6,9 @@ import { expect, test } from 'vite-plus/test';
import type { ComponentTestManifestEntry } from './manifest.js';
import { componentTestManifest } from './manifest.js';
-const sourceRoot = resolve(import.meta.dirname, '..');
-const packageRoot = resolve(sourceRoot, '..');
+const coreRoot = resolve(import.meta.dirname, '..');
+const exportsRoot = resolve(coreRoot, '../exports');
+const packageRoot = resolve(coreRoot, '../..');
// Public subpaths that are not normal component entrypoints, so they are exempt from the
// conformance manifest. `theme` is deliberately absent from this set: it has its own manifest
@@ -34,7 +35,7 @@ function getExportPaths() {
if (!exportPath.startsWith('./') || exportPath.includes('*')) continue;
const sourcePath = exportPath.slice(2);
if (NON_COMPONENT_EXPORTS.has(sourcePath)) continue;
- if (existsSync(resolve(sourceRoot, sourcePath, 'index.ts'))) {
+ if (existsSync(resolve(exportsRoot, `${sourcePath}.ts`))) {
paths.push(sourcePath);
}
}
@@ -45,7 +46,7 @@ function getExportPaths() {
function componentFile(path: string, suffix: 'browser.test.tsx' | 'visual.test.tsx') {
const basename = path.split('/').at(-1);
if (basename == null) throw new Error(`Invalid component test path: ${path}`);
- return resolve(sourceRoot, path, `${basename}.${suffix}`);
+ return resolve(coreRoot, path, `${basename}.${suffix}`);
}
function hasHelperCall(source: string, helperName: string, path: string): boolean {
@@ -137,10 +138,14 @@ test('covers every public component entrypoint exactly once', () => {
expect(new Set(paths).size).toBe(paths.length);
expect([...paths].sort()).toEqual(getExportPaths());
for (const path of paths) {
- expect(existsSync(resolve(sourceRoot, path, 'index.ts'))).toBe(true);
+ expect(existsSync(packageExportModule(path))).toBe(true);
}
});
+function packageExportModule(path: string): string {
+ return resolve(exportsRoot, `${path}.ts`);
+}
+
test('browser tests invoke the helpers required by their manifest entry', () => {
expect(
getBrowserCoverageErrors(componentTestManifest, (path) => {
diff --git a/packages/@luke-ui/react/src/conformance/manifest.ts b/packages/@luke-ui/react/src/core/conformance/manifest.ts
similarity index 100%
rename from packages/@luke-ui/react/src/conformance/manifest.ts
rename to packages/@luke-ui/react/src/core/conformance/manifest.ts
diff --git a/packages/@luke-ui/react/src/em/em.browser.test.tsx b/packages/@luke-ui/react/src/core/em/em.browser.test.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/em/em.browser.test.tsx
rename to packages/@luke-ui/react/src/core/em/em.browser.test.tsx
index f15e9e06..b4b23369 100644
--- a/packages/@luke-ui/react/src/em/em.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/em/em.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Em } from './index.js';
+import { Em } from './em.js';
testConformance({
path: 'em',
diff --git a/packages/@luke-ui/react/src/em/em.stories.tsx b/packages/@luke-ui/react/src/core/em/em.stories.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/em/em.stories.tsx
rename to packages/@luke-ui/react/src/core/em/em.stories.tsx
index 02a50c1f..aa1e071c 100644
--- a/packages/@luke-ui/react/src/em/em.stories.tsx
+++ b/packages/@luke-ui/react/src/core/em/em.stories.tsx
@@ -1,6 +1,6 @@
import { Em } from '@luke-ui/react/em';
import { Text } from '@luke-ui/react/text';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Em,
diff --git a/packages/@luke-ui/react/src/em/em.tsx b/packages/@luke-ui/react/src/core/em/em.tsx
similarity index 95%
rename from packages/@luke-ui/react/src/em/em.tsx
rename to packages/@luke-ui/react/src/core/em/em.tsx
index 081c8e5f..67990121 100644
--- a/packages/@luke-ui/react/src/em/em.tsx
+++ b/packages/@luke-ui/react/src/core/em/em.tsx
@@ -1,8 +1,8 @@
+import { cx } from '../../shared/utils/utils.js';
import type { TextProps } from '../text/text.js';
import { Text } from '../text/text.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import { em } from './styles.css.js';
interface EmStyleProps {
diff --git a/packages/@luke-ui/react/src/em/styles.css.ts b/packages/@luke-ui/react/src/core/em/styles.css.ts
similarity index 100%
rename from packages/@luke-ui/react/src/em/styles.css.ts
rename to packages/@luke-ui/react/src/core/em/styles.css.ts
diff --git a/packages/@luke-ui/react/src/emoji/emoji.browser.test.tsx b/packages/@luke-ui/react/src/core/emoji/emoji.browser.test.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/emoji/emoji.browser.test.tsx
rename to packages/@luke-ui/react/src/core/emoji/emoji.browser.test.tsx
index aa3a08f0..935cc29e 100644
--- a/packages/@luke-ui/react/src/emoji/emoji.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/emoji/emoji.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Emoji } from './index.js';
+import { Emoji } from './emoji.js';
testConformance({
path: 'emoji',
diff --git a/packages/@luke-ui/react/src/emoji/emoji.stories.tsx b/packages/@luke-ui/react/src/core/emoji/emoji.stories.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/emoji/emoji.stories.tsx
rename to packages/@luke-ui/react/src/core/emoji/emoji.stories.tsx
index add990f8..c6481c00 100644
--- a/packages/@luke-ui/react/src/emoji/emoji.stories.tsx
+++ b/packages/@luke-ui/react/src/core/emoji/emoji.stories.tsx
@@ -2,7 +2,7 @@ import type { EmojiProps } from '@luke-ui/react/emoji';
import { Emoji } from '@luke-ui/react/emoji';
import { Text } from '@luke-ui/react/text';
import type { CSSProperties } from 'react';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Emoji,
diff --git a/packages/@luke-ui/react/src/emoji/emoji.tsx b/packages/@luke-ui/react/src/core/emoji/emoji.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/emoji/emoji.tsx
rename to packages/@luke-ui/react/src/core/emoji/emoji.tsx
diff --git a/packages/@luke-ui/react/src/emoji/emoji.visual.test.tsx b/packages/@luke-ui/react/src/core/emoji/emoji.visual.test.tsx
similarity index 93%
rename from packages/@luke-ui/react/src/emoji/emoji.visual.test.tsx
rename to packages/@luke-ui/react/src/core/emoji/emoji.visual.test.tsx
index c2dc487a..56b7f787 100644
--- a/packages/@luke-ui/react/src/emoji/emoji.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/emoji/emoji.visual.test.tsx
@@ -2,8 +2,8 @@ import type { CSSProperties } from 'react';
import { test } from 'vite-plus/test';
import { render, visualAppearances } from '../test-utils/render.js';
import { captureVisualAppearance, Stack } from '../test-utils/visual.js';
-import { Text } from '../text/index.js';
-import { Emoji } from './index.js';
+import { Text } from '../text/text.js';
+import { Emoji } from './emoji.js';
const stackStyle = {
display: 'flex',
diff --git a/packages/@luke-ui/react/src/heading/heading-context.tsx b/packages/@luke-ui/react/src/core/heading/heading-context.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/heading/heading-context.tsx
rename to packages/@luke-ui/react/src/core/heading/heading-context.tsx
diff --git a/packages/@luke-ui/react/src/heading/heading.browser.test.tsx b/packages/@luke-ui/react/src/core/heading/heading.browser.test.tsx
similarity index 95%
rename from packages/@luke-ui/react/src/heading/heading.browser.test.tsx
rename to packages/@luke-ui/react/src/core/heading/heading.browser.test.tsx
index 43d27226..b61c7d27 100644
--- a/packages/@luke-ui/react/src/heading/heading.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/heading/heading.browser.test.tsx
@@ -1,7 +1,7 @@
import { expect, test } from 'vite-plus/test';
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Heading, HeadingLevels, useHeadingLevel } from './index.js';
+import { Heading, HeadingLevels, useHeadingLevel } from './heading.js';
testConformance({
path: 'heading',
diff --git a/packages/@luke-ui/react/src/heading/heading.stories.tsx b/packages/@luke-ui/react/src/core/heading/heading.stories.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/heading/heading.stories.tsx
rename to packages/@luke-ui/react/src/core/heading/heading.stories.tsx
index a64ee971..dd7016d0 100644
--- a/packages/@luke-ui/react/src/heading/heading.stories.tsx
+++ b/packages/@luke-ui/react/src/core/heading/heading.stories.tsx
@@ -1,7 +1,7 @@
import type { HeadingProps } from '@luke-ui/react/heading';
import { Heading } from '@luke-ui/react/heading';
import type { CSSProperties } from 'react';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Heading,
diff --git a/packages/@luke-ui/react/src/heading/heading.tsx b/packages/@luke-ui/react/src/core/heading/heading.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/heading/heading.tsx
rename to packages/@luke-ui/react/src/core/heading/heading.tsx
diff --git a/packages/@luke-ui/react/src/heading/heading.visual.test.tsx b/packages/@luke-ui/react/src/core/heading/heading.visual.test.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/heading/heading.visual.test.tsx
rename to packages/@luke-ui/react/src/core/heading/heading.visual.test.tsx
index f4520e1b..22506c7d 100644
--- a/packages/@luke-ui/react/src/heading/heading.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/heading/heading.visual.test.tsx
@@ -6,7 +6,7 @@ import {
Stack,
variantValuesFor,
} from '../test-utils/visual.js';
-import { Heading } from './index.js';
+import { Heading } from './heading.js';
const levels = variantValuesFor()([1, 2, 3, 4, 5, 6]);
diff --git a/packages/@luke-ui/react/src/icon-button/icon-button.browser.test.tsx b/packages/@luke-ui/react/src/core/icon-button/icon-button.browser.test.tsx
similarity index 94%
rename from packages/@luke-ui/react/src/icon-button/icon-button.browser.test.tsx
rename to packages/@luke-ui/react/src/core/icon-button/icon-button.browser.test.tsx
index 941de384..8e7586d4 100644
--- a/packages/@luke-ui/react/src/icon-button/icon-button.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/icon-button/icon-button.browser.test.tsx
@@ -1,7 +1,7 @@
import { expect } from 'vite-plus/test';
import { testConformance, testIntegration } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { IconButton } from './index.js';
+import { IconButton } from './icon-button.js';
testConformance({
path: 'icon-button',
diff --git a/packages/@luke-ui/react/src/icon-button/icon-button.stories.tsx b/packages/@luke-ui/react/src/core/icon-button/icon-button.stories.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/icon-button/icon-button.stories.tsx
rename to packages/@luke-ui/react/src/core/icon-button/icon-button.stories.tsx
index a113402a..45e6101a 100644
--- a/packages/@luke-ui/react/src/icon-button/icon-button.stories.tsx
+++ b/packages/@luke-ui/react/src/core/icon-button/icon-button.stories.tsx
@@ -2,7 +2,7 @@ import type { IconButtonProps } from '@luke-ui/react/icon-button';
import { IconButton } from '@luke-ui/react/icon-button';
import type { CSSProperties } from 'react';
import { fn } from 'storybook/test';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: IconButton,
diff --git a/packages/@luke-ui/react/src/icon-button/icon-button.tsx b/packages/@luke-ui/react/src/core/icon-button/icon-button.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/icon-button/icon-button.tsx
rename to packages/@luke-ui/react/src/core/icon-button/icon-button.tsx
index af51a654..1808467d 100644
--- a/packages/@luke-ui/react/src/icon-button/icon-button.tsx
+++ b/packages/@luke-ui/react/src/core/icon-button/icon-button.tsx
@@ -1,5 +1,6 @@
import type { JSX } from 'react';
import { composeRenderProps } from 'react-aria-components/composeRenderProps';
+import { cx } from '../../shared/utils/utils.js';
import type { IconName } from '../icon/icon.js';
import { Icon } from '../icon/icon.js';
import type { ButtonProps as PrimitiveButtonProps } from '../primitives/button/button.js';
@@ -7,7 +8,6 @@ import { Button } from '../primitives/button/button.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { DocumentedPressProps } from '../types/documented-rac-props.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import type { IconButtonRecipeVariants } from './recipe.css.js';
import { iconButtonIcon, iconButtonRecipe, iconButtonReset } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/icon-button/icon-button.visual.test.tsx b/packages/@luke-ui/react/src/core/icon-button/icon-button.visual.test.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/icon-button/icon-button.visual.test.tsx
rename to packages/@luke-ui/react/src/core/icon-button/icon-button.visual.test.tsx
index 1ff05fa7..21f88128 100644
--- a/packages/@luke-ui/react/src/icon-button/icon-button.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/icon-button/icon-button.visual.test.tsx
@@ -8,7 +8,7 @@ import {
focusViaKeyboard,
Grid,
} from '../test-utils/visual.js';
-import { IconButton } from './index.js';
+import { IconButton } from './icon-button.js';
test('kitchen sink', async () => {
for (const appearance of visualAppearances) {
diff --git a/packages/@luke-ui/react/src/icon-button/recipe.css.ts b/packages/@luke-ui/react/src/core/icon-button/recipe.css.ts
similarity index 96%
rename from packages/@luke-ui/react/src/icon-button/recipe.css.ts
rename to packages/@luke-ui/react/src/core/icon-button/recipe.css.ts
index 0b4a4ca2..994ae2c6 100644
--- a/packages/@luke-ui/react/src/icon-button/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/icon-button/recipe.css.ts
@@ -1,7 +1,7 @@
+import { vars } from '../../theme/contract.css.js';
import { styleInLayer } from '../styles/layered-style.css.js';
import type { RecipeSelection } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
-import { vars } from '../theme/contract.css.js';
export const iconButtonReset = styleInLayer('recipes', {
'@media': {
diff --git a/packages/@luke-ui/react/src/icon/icon-size-context.tsx b/packages/@luke-ui/react/src/core/icon/icon-size-context.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/icon/icon-size-context.tsx
rename to packages/@luke-ui/react/src/core/icon/icon-size-context.tsx
diff --git a/packages/@luke-ui/react/src/icon/icon.stories.tsx b/packages/@luke-ui/react/src/core/icon/icon.stories.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/icon/icon.stories.tsx
rename to packages/@luke-ui/react/src/core/icon/icon.stories.tsx
index b3ee837e..3a32240a 100644
--- a/packages/@luke-ui/react/src/icon/icon.stories.tsx
+++ b/packages/@luke-ui/react/src/core/icon/icon.stories.tsx
@@ -4,7 +4,7 @@ import { createIcon, Icon, iconNames } from '@luke-ui/react/icon';
import type { TextProps } from '@luke-ui/react/text';
import { vars } from '@luke-ui/react/theme';
import type { CSSProperties } from 'react';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Icon,
diff --git a/packages/@luke-ui/react/src/icon/icon.tsx b/packages/@luke-ui/react/src/core/icon/icon.tsx
similarity index 95%
rename from packages/@luke-ui/react/src/icon/icon.tsx
rename to packages/@luke-ui/react/src/core/icon/icon.tsx
index 6312515d..b0891a56 100644
--- a/packages/@luke-ui/react/src/icon/icon.tsx
+++ b/packages/@luke-ui/react/src/core/icon/icon.tsx
@@ -1,15 +1,15 @@
import type { JSX, ReactNode, SVGAttributes } from 'react';
import { createContext, useContext } from 'react';
-import { iconNames, iconViewBoxes } from '../../.generated/icon-data.js';
+import { iconNames, iconViewBoxes } from '../../../.generated/icon-data.js';
+import { cx } from '../../shared/utils/utils.js';
import { ICON_VIEWBOX } from '../sizing/icon-sizing.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import { useIconSizeContext } from './icon-size-context.js';
import type { IconRecipeVariants } from './recipe.css.js';
import { iconRecipe } from './recipe.css.js';
-export type { IconName } from '../../.generated/icon-data.js';
+export type { IconName } from '../../../.generated/icon-data.js';
export { IconSizeProvider } from './icon-size-context.js';
export { iconNames, iconViewBoxes };
diff --git a/packages/@luke-ui/react/src/icon/icon.visual.test.tsx b/packages/@luke-ui/react/src/core/icon/icon.visual.test.tsx
similarity index 94%
rename from packages/@luke-ui/react/src/icon/icon.visual.test.tsx
rename to packages/@luke-ui/react/src/core/icon/icon.visual.test.tsx
index 2b1f034f..abf6b6c5 100644
--- a/packages/@luke-ui/react/src/icon/icon.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/icon/icon.visual.test.tsx
@@ -1,5 +1,6 @@
import type { CSSProperties } from 'react';
import { test } from 'vite-plus/test';
+import { vars } from '../../theme/index.js';
import { render, visualAppearances } from '../test-utils/render.js';
import {
captureVisual,
@@ -7,8 +8,7 @@ import {
Stack,
variantValuesFor,
} from '../test-utils/visual.js';
-import { vars } from '../theme/index.js';
-import { Icon } from './index.js';
+import { Icon } from './icon.js';
const rowStyle = {
alignItems: 'center',
diff --git a/packages/@luke-ui/react/src/icon/recipe.css.ts b/packages/@luke-ui/react/src/core/icon/recipe.css.ts
similarity index 94%
rename from packages/@luke-ui/react/src/icon/recipe.css.ts
rename to packages/@luke-ui/react/src/core/icon/recipe.css.ts
index 23a38f7f..fdab23c2 100644
--- a/packages/@luke-ui/react/src/icon/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/icon/recipe.css.ts
@@ -1,6 +1,6 @@
+import { vars } from '../../theme/contract.css.js';
import type { RecipeSelection } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
-import { vars } from '../theme/contract.css.js';
/** Shared size dimensions for Icon and LoadingSpinner (icon-aligned sizing). */
export const iconSizeVariants = {
diff --git a/packages/@luke-ui/react/src/kbd/kbd.browser.test.tsx b/packages/@luke-ui/react/src/core/kbd/kbd.browser.test.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/kbd/kbd.browser.test.tsx
rename to packages/@luke-ui/react/src/core/kbd/kbd.browser.test.tsx
index 53701230..2c1304ff 100644
--- a/packages/@luke-ui/react/src/kbd/kbd.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/kbd/kbd.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Kbd } from './index.js';
+import { Kbd } from './kbd.js';
testConformance({
path: 'kbd',
diff --git a/packages/@luke-ui/react/src/kbd/kbd.stories.tsx b/packages/@luke-ui/react/src/core/kbd/kbd.stories.tsx
similarity index 90%
rename from packages/@luke-ui/react/src/kbd/kbd.stories.tsx
rename to packages/@luke-ui/react/src/core/kbd/kbd.stories.tsx
index 3ecce2be..ee69a293 100644
--- a/packages/@luke-ui/react/src/kbd/kbd.stories.tsx
+++ b/packages/@luke-ui/react/src/core/kbd/kbd.stories.tsx
@@ -1,6 +1,6 @@
import { Kbd } from '@luke-ui/react/kbd';
import { Text } from '@luke-ui/react/text';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Kbd,
diff --git a/packages/@luke-ui/react/src/kbd/kbd.tsx b/packages/@luke-ui/react/src/core/kbd/kbd.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/kbd/kbd.tsx
rename to packages/@luke-ui/react/src/core/kbd/kbd.tsx
index 13367f27..7bf2c556 100644
--- a/packages/@luke-ui/react/src/kbd/kbd.tsx
+++ b/packages/@luke-ui/react/src/core/kbd/kbd.tsx
@@ -1,6 +1,6 @@
+import { cx } from '../../shared/utils/utils.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import { kbdRecipe } from './recipe.css.js';
type _KbdOmit = DistributiveOmit, never>;
diff --git a/packages/@luke-ui/react/src/kbd/recipe.css.ts b/packages/@luke-ui/react/src/core/kbd/recipe.css.ts
similarity index 88%
rename from packages/@luke-ui/react/src/kbd/recipe.css.ts
rename to packages/@luke-ui/react/src/core/kbd/recipe.css.ts
index 77c278e9..b760854b 100644
--- a/packages/@luke-ui/react/src/kbd/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/kbd/recipe.css.ts
@@ -1,8 +1,8 @@
+import { vars } from '../../theme/contract.css.js';
+import { FONT_METRIC_SCALE } from '../../theme/font-metric-scale.js';
import { styleInLayer } from '../styles/layered-style.css.js';
import type { RecipeSelection } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
-import { vars } from '../theme/contract.css.js';
-import { FONT_METRIC_SCALE } from '../theme/font-metric-scale.js';
const base = styleInLayer('recipes', {
alignItems: 'center',
diff --git a/packages/@luke-ui/react/src/link/link.browser.test.tsx b/packages/@luke-ui/react/src/core/link/link.browser.test.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/link/link.browser.test.tsx
rename to packages/@luke-ui/react/src/core/link/link.browser.test.tsx
index c07c4524..ddedfd70 100644
--- a/packages/@luke-ui/react/src/link/link.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/link/link.browser.test.tsx
@@ -1,7 +1,7 @@
import { expect } from 'vite-plus/test';
import { testConformance, testIntegration } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Link } from './index.js';
+import { Link } from './link.js';
testConformance({
path: 'link',
diff --git a/packages/@luke-ui/react/src/link/link.stories.tsx b/packages/@luke-ui/react/src/core/link/link.stories.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/link/link.stories.tsx
rename to packages/@luke-ui/react/src/core/link/link.stories.tsx
index fd6fc895..88d71508 100644
--- a/packages/@luke-ui/react/src/link/link.stories.tsx
+++ b/packages/@luke-ui/react/src/core/link/link.stories.tsx
@@ -2,7 +2,7 @@ import type { LinkProps } from '@luke-ui/react/link';
import { Link } from '@luke-ui/react/link';
import type { CSSProperties } from 'react';
import { fn } from 'storybook/test';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Link,
diff --git a/packages/@luke-ui/react/src/link/link.tsx b/packages/@luke-ui/react/src/core/link/link.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/link/link.tsx
rename to packages/@luke-ui/react/src/core/link/link.tsx
index b7da3642..d6969095 100644
--- a/packages/@luke-ui/react/src/link/link.tsx
+++ b/packages/@luke-ui/react/src/core/link/link.tsx
@@ -2,10 +2,10 @@ import type { JSX } from 'react';
import type { LinkProps as RacLinkProps } from 'react-aria-components/Link';
import { Link as RacLink } from 'react-aria-components/Link';
import { composeRenderProps } from 'react-aria-components/composeRenderProps';
+import { cx } from '../../shared/utils/utils.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { DocumentedLinkProps } from '../types/documented-rac-props.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import type { LinkRecipeVariants } from './recipe.css.js';
import { linkRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/link/link.visual.test.tsx b/packages/@luke-ui/react/src/core/link/link.visual.test.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/link/link.visual.test.tsx
rename to packages/@luke-ui/react/src/core/link/link.visual.test.tsx
index de12a0ee..eef47da9 100644
--- a/packages/@luke-ui/react/src/link/link.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/link/link.visual.test.tsx
@@ -9,7 +9,7 @@ import {
Grid,
Stack,
} from '../test-utils/visual.js';
-import { Link } from './index.js';
+import { Link } from './link.js';
test('kitchen sink', async () => {
for (const appearance of visualAppearances) {
diff --git a/packages/@luke-ui/react/src/link/recipe.css.ts b/packages/@luke-ui/react/src/core/link/recipe.css.ts
similarity index 97%
rename from packages/@luke-ui/react/src/link/recipe.css.ts
rename to packages/@luke-ui/react/src/core/link/recipe.css.ts
index b30e0ae1..ae2cb6e1 100644
--- a/packages/@luke-ui/react/src/link/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/link/recipe.css.ts
@@ -1,7 +1,7 @@
+import { vars } from '../../theme/contract.css.js';
import { styleInLayer } from '../styles/layered-style.css.js';
import type { RecipeSelection } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
-import { vars } from '../theme/contract.css.js';
const base = styleInLayer('recipes', {
'@media': {
diff --git a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.browser.test.tsx b/packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.browser.test.tsx
similarity index 88%
rename from packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.browser.test.tsx
rename to packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.browser.test.tsx
index 22bf53d6..c6b3266f 100644
--- a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { LoadingSkeleton } from './index.js';
+import { LoadingSkeleton } from './loading-skeleton.js';
testConformance({
path: 'loading-skeleton',
diff --git a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.stories.tsx b/packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.stories.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.stories.tsx
rename to packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.stories.tsx
index 6e9a7622..778980f9 100644
--- a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.stories.tsx
+++ b/packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.stories.tsx
@@ -3,7 +3,7 @@ import { LoadingSkeleton, LoadingSkeletonProvider } from '@luke-ui/react/loading
import { Text } from '@luke-ui/react/text';
import { TextField } from '@luke-ui/react/text-field';
import type { CSSProperties } from 'react';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: LoadingSkeleton,
diff --git a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.tsx b/packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.tsx
rename to packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.tsx
index f677c2f0..82ab40b2 100644
--- a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.tsx
+++ b/packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.tsx
@@ -1,9 +1,9 @@
import type { ComponentProps, ElementType, JSX, ReactNode } from 'react';
import { createContext, isValidElement, useContext } from 'react';
-import { vars } from '../theme/contract.css.js';
+import { cx } from '../../shared/utils/utils.js';
+import { vars } from '../../theme/contract.css.js';
import type { Prettify } from '../types/prettify.js';
import { useSynchronizeAnimations } from '../use-synchronize-animations/use-synchronize-animations.js';
-import { cx } from '../utils/utils.js';
import {
loadingSkeletonClassName,
skeletonAnimationName,
diff --git a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.visual.test.tsx b/packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.visual.test.tsx
similarity index 91%
rename from packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.visual.test.tsx
rename to packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.visual.test.tsx
index 25845e42..118bda48 100644
--- a/packages/@luke-ui/react/src/loading-skeleton/loading-skeleton.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/loading-skeleton/loading-skeleton.visual.test.tsx
@@ -1,10 +1,10 @@
import type { CSSProperties } from 'react';
import { test } from 'vite-plus/test';
-import { Button } from '../button/index.js';
+import { Button } from '../button/button.js';
import { render, visualAppearances } from '../test-utils/render.js';
import { captureVisual, captureVisualAppearance, Stack } from '../test-utils/visual.js';
-import { TextField } from '../text-field/index.js';
-import { LoadingSkeleton } from './index.js';
+import { TextField } from '../text-field/text-field.js';
+import { LoadingSkeleton } from './loading-skeleton.js';
test('text and component placeholders', async () => {
const { locator } = render(
diff --git a/packages/@luke-ui/react/src/loading-skeleton/styles.css.ts b/packages/@luke-ui/react/src/core/loading-skeleton/styles.css.ts
similarity index 98%
rename from packages/@luke-ui/react/src/loading-skeleton/styles.css.ts
rename to packages/@luke-ui/react/src/core/loading-skeleton/styles.css.ts
index 6aab278b..01428ecb 100644
--- a/packages/@luke-ui/react/src/loading-skeleton/styles.css.ts
+++ b/packages/@luke-ui/react/src/core/loading-skeleton/styles.css.ts
@@ -1,7 +1,7 @@
import type { StyleRule } from '@vanilla-extract/css';
import { fallbackVar, keyframes } from '@vanilla-extract/css';
+import { vars } from '../../theme/contract.css.js';
import { globalStyleInLayer, styleInLayer } from '../styles/layered-style.css.js';
-import { vars } from '../theme/contract.css.js';
/**
* @internal
diff --git a/packages/@luke-ui/react/src/loading-spinner/loading-spinner.browser.test.tsx b/packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.browser.test.tsx
similarity index 88%
rename from packages/@luke-ui/react/src/loading-spinner/loading-spinner.browser.test.tsx
rename to packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.browser.test.tsx
index dceceb30..f9554b19 100644
--- a/packages/@luke-ui/react/src/loading-spinner/loading-spinner.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { LoadingSpinner } from './index.js';
+import { LoadingSpinner } from './loading-spinner.js';
testConformance({
path: 'loading-spinner',
diff --git a/packages/@luke-ui/react/src/loading-spinner/loading-spinner.stories.tsx b/packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.stories.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/loading-spinner/loading-spinner.stories.tsx
rename to packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.stories.tsx
index 5751f1d2..b3733755 100644
--- a/packages/@luke-ui/react/src/loading-spinner/loading-spinner.stories.tsx
+++ b/packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.stories.tsx
@@ -4,7 +4,7 @@ import { vars } from '@luke-ui/react/theme';
import type { CSSProperties } from 'react';
import { useState } from 'react';
import { userEvent } from 'storybook/test';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: LoadingSpinner,
diff --git a/packages/@luke-ui/react/src/loading-spinner/loading-spinner.tsx b/packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/loading-spinner/loading-spinner.tsx
rename to packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.tsx
diff --git a/packages/@luke-ui/react/src/loading-spinner/loading-spinner.visual.test.tsx b/packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.visual.test.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/loading-spinner/loading-spinner.visual.test.tsx
rename to packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.visual.test.tsx
index c2c5e0a3..0e1eac93 100644
--- a/packages/@luke-ui/react/src/loading-spinner/loading-spinner.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/loading-spinner/loading-spinner.visual.test.tsx
@@ -1,5 +1,6 @@
import type { CSSProperties, ReactNode } from 'react';
import { test } from 'vite-plus/test';
+import { vars } from '../../theme/index.js';
import { render, visualAppearances } from '../test-utils/render.js';
import {
captureVisual,
@@ -7,8 +8,7 @@ import {
Stack,
variantValuesFor,
} from '../test-utils/visual.js';
-import { vars } from '../theme/index.js';
-import { LoadingSpinner } from './index.js';
+import { LoadingSpinner } from './loading-spinner.js';
const rowStyle = {
alignItems: 'center',
diff --git a/packages/@luke-ui/react/src/loading-spinner/recipe.css.ts b/packages/@luke-ui/react/src/core/loading-spinner/recipe.css.ts
similarity index 98%
rename from packages/@luke-ui/react/src/loading-spinner/recipe.css.ts
rename to packages/@luke-ui/react/src/core/loading-spinner/recipe.css.ts
index 6a82ab24..c06374bd 100644
--- a/packages/@luke-ui/react/src/loading-spinner/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/loading-spinner/recipe.css.ts
@@ -1,9 +1,9 @@
import { keyframes } from '@vanilla-extract/css';
+import { vars } from '../../theme/contract.css.js';
import { iconSizeVariants } from '../icon/recipe.css.js';
import type { RecipeSelection, SlottedConfigInput } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
import { spinnerOverlayBase } from '../styles/spinner-overlay.js';
-import { vars } from '../theme/contract.css.js';
const rotationDuration = '1.2s';
const rubberBandDuration = '2s';
diff --git a/packages/@luke-ui/react/src/numeral/numeral.browser.test.tsx b/packages/@luke-ui/react/src/core/numeral/numeral.browser.test.tsx
similarity index 91%
rename from packages/@luke-ui/react/src/numeral/numeral.browser.test.tsx
rename to packages/@luke-ui/react/src/core/numeral/numeral.browser.test.tsx
index 285e75c7..fa29eb6f 100644
--- a/packages/@luke-ui/react/src/numeral/numeral.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/numeral/numeral.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Numeral } from './index.js';
+import { Numeral } from './numeral.js';
testConformance({
path: 'numeral',
diff --git a/packages/@luke-ui/react/src/numeral/numeral.stories.tsx b/packages/@luke-ui/react/src/core/numeral/numeral.stories.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/numeral/numeral.stories.tsx
rename to packages/@luke-ui/react/src/core/numeral/numeral.stories.tsx
index 4ad48c5b..ecd002b3 100644
--- a/packages/@luke-ui/react/src/numeral/numeral.stories.tsx
+++ b/packages/@luke-ui/react/src/core/numeral/numeral.stories.tsx
@@ -3,7 +3,7 @@ import type { NumeralProps } from '@luke-ui/react/numeral';
import { Numeral } from '@luke-ui/react/numeral';
import { Text } from '@luke-ui/react/text';
import type { CSSProperties } from 'react';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Numeral,
diff --git a/packages/@luke-ui/react/src/numeral/numeral.tsx b/packages/@luke-ui/react/src/core/numeral/numeral.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/numeral/numeral.tsx
rename to packages/@luke-ui/react/src/core/numeral/numeral.tsx
diff --git a/packages/@luke-ui/react/src/numeral/numeral.visual.test.tsx b/packages/@luke-ui/react/src/core/numeral/numeral.visual.test.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/numeral/numeral.visual.test.tsx
rename to packages/@luke-ui/react/src/core/numeral/numeral.visual.test.tsx
index 3eda5620..e75bc9e6 100644
--- a/packages/@luke-ui/react/src/numeral/numeral.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/numeral/numeral.visual.test.tsx
@@ -2,7 +2,7 @@ import type { CSSProperties } from 'react';
import { test } from 'vite-plus/test';
import { render, visualAppearances } from '../test-utils/render.js';
import { captureVisualAppearance, Stack } from '../test-utils/visual.js';
-import { Numeral } from './index.js';
+import { Numeral } from './numeral.js';
const rowStyle = {
display: 'flex',
diff --git a/packages/@luke-ui/react/src/overlays/mobile-overlay.css.ts b/packages/@luke-ui/react/src/core/overlays/mobile-overlay.css.ts
similarity index 98%
rename from packages/@luke-ui/react/src/overlays/mobile-overlay.css.ts
rename to packages/@luke-ui/react/src/core/overlays/mobile-overlay.css.ts
index 02c7a356..a413331d 100644
--- a/packages/@luke-ui/react/src/overlays/mobile-overlay.css.ts
+++ b/packages/@luke-ui/react/src/core/overlays/mobile-overlay.css.ts
@@ -1,7 +1,7 @@
import { createVar } from '@vanilla-extract/css';
+import { vars } from '../../theme/contract.css.js';
import { styleInLayer } from '../styles/layered-style.css.js';
import { overlayEnterTransition, overlayExitTransition } from '../styles/overlay-motion.js';
-import { vars } from '../theme/contract.css.js';
const trayPaddingBlockEnd = createVar();
diff --git a/packages/@luke-ui/react/src/overlays/mobile-overlay.tsx b/packages/@luke-ui/react/src/core/overlays/mobile-overlay.tsx
similarity index 95%
rename from packages/@luke-ui/react/src/overlays/mobile-overlay.tsx
rename to packages/@luke-ui/react/src/core/overlays/mobile-overlay.tsx
index 8de1967f..3f688b42 100644
--- a/packages/@luke-ui/react/src/overlays/mobile-overlay.tsx
+++ b/packages/@luke-ui/react/src/core/overlays/mobile-overlay.tsx
@@ -2,8 +2,8 @@ import type { JSX, ReactNode, Ref } from 'react';
import type { DialogProps } from 'react-aria-components/Dialog';
import { Dialog, OverlayTriggerStateContext } from 'react-aria-components/Dialog';
import { Modal, ModalOverlay } from 'react-aria-components/Modal';
-import { rootClassName } from '../theme/theme.js';
-import { cx } from '../utils/utils.js';
+import { cx } from '../../shared/utils/utils.js';
+import { rootClassName } from '../../theme/theme.js';
import { mobileDialog, mobileModal, mobileOverlay } from './mobile-overlay.css.js';
interface MobileOverlayProps {
diff --git a/packages/@luke-ui/react/src/overlays/use-is-mobile-device.ts b/packages/@luke-ui/react/src/core/overlays/use-is-mobile-device.ts
similarity index 94%
rename from packages/@luke-ui/react/src/overlays/use-is-mobile-device.ts
rename to packages/@luke-ui/react/src/core/overlays/use-is-mobile-device.ts
index e6aabf04..edfedd58 100644
--- a/packages/@luke-ui/react/src/overlays/use-is-mobile-device.ts
+++ b/packages/@luke-ui/react/src/core/overlays/use-is-mobile-device.ts
@@ -1,5 +1,5 @@
import { useSyncExternalStore } from 'react';
-import { breakpoints } from '../theme/breakpoints.js';
+import { breakpoints } from '../../theme/breakpoints.js';
/**
* Reports whether the current device is a mobile one, so an overlay can switch to
diff --git a/packages/@luke-ui/react/src/primitives/button/button.browser.test.tsx b/packages/@luke-ui/react/src/core/primitives/button/button.browser.test.tsx
similarity index 91%
rename from packages/@luke-ui/react/src/primitives/button/button.browser.test.tsx
rename to packages/@luke-ui/react/src/core/primitives/button/button.browser.test.tsx
index 13a96cf6..71126102 100644
--- a/packages/@luke-ui/react/src/primitives/button/button.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/primitives/button/button.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../../conformance/helpers.js';
import { render } from '../../test-utils/render.js';
-import { Button } from './index.js';
+import { Button } from './button.js';
testConformance({
path: 'primitives/button',
diff --git a/packages/@luke-ui/react/src/primitives/button/button.tsx b/packages/@luke-ui/react/src/core/primitives/button/button.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/primitives/button/button.tsx
rename to packages/@luke-ui/react/src/core/primitives/button/button.tsx
index 8a26ce62..bfc27afe 100644
--- a/packages/@luke-ui/react/src/primitives/button/button.tsx
+++ b/packages/@luke-ui/react/src/core/primitives/button/button.tsx
@@ -2,10 +2,10 @@ import type { JSX } from 'react';
import type { ButtonProps as RacButtonProps } from 'react-aria-components/Button';
import { Button as RacButton } from 'react-aria-components/Button';
import { composeRenderProps } from 'react-aria-components/composeRenderProps';
+import { cx } from '../../../shared/utils/utils.js';
import { IconSizeProvider } from '../../icon/icon-size-context.js';
import { BUTTON_ICON_SIZE } from '../../sizing/button-sizing.js';
import type { Prettify } from '../../types/prettify.js';
-import { cx } from '../../utils/utils.js';
import type { ButtonRecipeVariants } from './recipe.css.js';
import { buttonRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/primitives/button/recipe.css.ts b/packages/@luke-ui/react/src/core/primitives/button/recipe.css.ts
similarity index 97%
rename from packages/@luke-ui/react/src/primitives/button/recipe.css.ts
rename to packages/@luke-ui/react/src/core/primitives/button/recipe.css.ts
index 11b0da9f..20ab4df5 100644
--- a/packages/@luke-ui/react/src/primitives/button/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/primitives/button/recipe.css.ts
@@ -1,8 +1,8 @@
+import { vars } from '../../../theme/contract.css.js';
+import { FONT_METRIC_SCALE } from '../../../theme/font-metric-scale.js';
import { styleInLayer } from '../../styles/layered-style.css.js';
import type { RecipeSelection } from '../../styles/recipe.js';
import { recipe } from '../../styles/recipe.js';
-import { vars } from '../../theme/contract.css.js';
-import { FONT_METRIC_SCALE } from '../../theme/font-metric-scale.js';
const base = styleInLayer('recipes', {
'@media': {
diff --git a/packages/@luke-ui/react/src/primitives/checkbox/checkbox.tsx b/packages/@luke-ui/react/src/core/primitives/checkbox/checkbox.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/checkbox/checkbox.tsx
rename to packages/@luke-ui/react/src/core/primitives/checkbox/checkbox.tsx
diff --git a/packages/@luke-ui/react/src/primitives/checkbox/recipe.css.ts b/packages/@luke-ui/react/src/core/primitives/checkbox/recipe.css.ts
similarity index 98%
rename from packages/@luke-ui/react/src/primitives/checkbox/recipe.css.ts
rename to packages/@luke-ui/react/src/core/primitives/checkbox/recipe.css.ts
index 7943953a..4c844d56 100644
--- a/packages/@luke-ui/react/src/primitives/checkbox/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/primitives/checkbox/recipe.css.ts
@@ -1,10 +1,10 @@
import { createVar, fallbackVar } from '@vanilla-extract/css';
+import { vars } from '../../../theme/contract.css.js';
+import { FONT_METRIC_SCALE } from '../../../theme/font-metric-scale.js';
import { focusRing } from '../../styles/focus-ring.js';
import type { RecipeSelection, SlottedConfigInput } from '../../styles/recipe.js';
import { recipe } from '../../styles/recipe.js';
import { textLineHeight } from '../../text/recipe.css.js';
-import { vars } from '../../theme/contract.css.js';
-import { FONT_METRIC_SCALE } from '../../theme/font-metric-scale.js';
import { fieldMessageIcon, fieldMessageIndent } from '../field/recipe.css.js';
const checkboxControlSize = createVar();
diff --git a/packages/@luke-ui/react/src/primitives/combobox/clear-button.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/clear-button.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/clear-button.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/clear-button.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/empty-state.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/empty-state.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/empty-state.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/empty-state.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/input-group.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/input-group.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/input-group.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/input-group.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/input.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/input.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/input.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/input.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/item.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/item.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/item.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/item.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/listbox.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/listbox.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/listbox.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/listbox.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/popover.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/popover.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/primitives/combobox/popover.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/popover.tsx
index 5c9712f3..b040492a 100644
--- a/packages/@luke-ui/react/src/primitives/combobox/popover.tsx
+++ b/packages/@luke-ui/react/src/core/primitives/combobox/popover.tsx
@@ -2,10 +2,10 @@ import type { JSX, Ref } from 'react';
import type { PopoverProps as RacPopoverProps } from 'react-aria-components/ComboBox';
import { Popover as RacPopover } from 'react-aria-components/ComboBox';
import { composeRenderProps } from 'react-aria-components/composeRenderProps';
-import { rootClassName } from '../../theme/theme.js';
+import { cx } from '../../../shared/utils/utils.js';
+import { rootClassName } from '../../../theme/theme.js';
import type { DistributiveOmit } from '../../types/distributive-omit.js';
import type { Prettify } from '../../types/prettify.js';
-import { cx } from '../../utils/utils.js';
import { comboboxRecipe } from './styles.css.js';
type _ComboboxPopoverOmit = DistributiveOmit;
diff --git a/packages/@luke-ui/react/src/primitives/combobox/root.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/root.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/root.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/root.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/section.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/section.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/section.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/section.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/size-context.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/size-context.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/size-context.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/size-context.tsx
diff --git a/packages/@luke-ui/react/src/primitives/combobox/styles.css.ts b/packages/@luke-ui/react/src/core/primitives/combobox/styles.css.ts
similarity index 99%
rename from packages/@luke-ui/react/src/primitives/combobox/styles.css.ts
rename to packages/@luke-ui/react/src/core/primitives/combobox/styles.css.ts
index 3e085a47..7c4d7a96 100644
--- a/packages/@luke-ui/react/src/primitives/combobox/styles.css.ts
+++ b/packages/@luke-ui/react/src/core/primitives/combobox/styles.css.ts
@@ -1,5 +1,7 @@
import type { StyleRule } from '@vanilla-extract/css';
import { createVar } from '@vanilla-extract/css';
+import { vars } from '../../../theme/contract.css.js';
+import { FONT_METRIC_SCALE } from '../../../theme/font-metric-scale.js';
import { FIELD_CONTROL_ICON_SIZE } from '../../sizing/control-size.js';
import { focusRing } from '../../styles/focus-ring.js';
import {
@@ -14,8 +16,6 @@ import { styleInLayer } from '../../styles/layered-style.css.js';
import { overlayEnterTransition, overlayExitTransition } from '../../styles/overlay-motion.js';
import type { SlottedConfigInput } from '../../styles/recipe.js';
import { recipe } from '../../styles/recipe.js';
-import { vars } from '../../theme/contract.css.js';
-import { FONT_METRIC_SCALE } from '../../theme/font-metric-scale.js';
// Set per `size` variant on `inputGroup` below, from `FIELD_CONTROL_ICON_SIZE`, so the invalid
// `::after` icon matches the trigger/clear chevrons at each size instead of a constant.
diff --git a/packages/@luke-ui/react/src/primitives/combobox/trigger.tsx b/packages/@luke-ui/react/src/core/primitives/combobox/trigger.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/combobox/trigger.tsx
rename to packages/@luke-ui/react/src/core/primitives/combobox/trigger.tsx
diff --git a/packages/@luke-ui/react/src/primitives/field/description.tsx b/packages/@luke-ui/react/src/core/primitives/field/description.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/field/description.tsx
rename to packages/@luke-ui/react/src/core/primitives/field/description.tsx
diff --git a/packages/@luke-ui/react/src/primitives/field/error.tsx b/packages/@luke-ui/react/src/core/primitives/field/error.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/field/error.tsx
rename to packages/@luke-ui/react/src/core/primitives/field/error.tsx
diff --git a/packages/@luke-ui/react/src/primitives/field/field.tsx b/packages/@luke-ui/react/src/core/primitives/field/field.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/field/field.tsx
rename to packages/@luke-ui/react/src/core/primitives/field/field.tsx
diff --git a/packages/@luke-ui/react/src/primitives/field/is-invalid-from-error-message.ts b/packages/@luke-ui/react/src/core/primitives/field/is-invalid-from-error-message.ts
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/field/is-invalid-from-error-message.ts
rename to packages/@luke-ui/react/src/core/primitives/field/is-invalid-from-error-message.ts
diff --git a/packages/@luke-ui/react/src/primitives/field/label.tsx b/packages/@luke-ui/react/src/core/primitives/field/label.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/field/label.tsx
rename to packages/@luke-ui/react/src/core/primitives/field/label.tsx
diff --git a/packages/@luke-ui/react/src/primitives/field/recipe.css.ts b/packages/@luke-ui/react/src/core/primitives/field/recipe.css.ts
similarity index 98%
rename from packages/@luke-ui/react/src/primitives/field/recipe.css.ts
rename to packages/@luke-ui/react/src/core/primitives/field/recipe.css.ts
index 43b5f839..6cc896d0 100644
--- a/packages/@luke-ui/react/src/primitives/field/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/primitives/field/recipe.css.ts
@@ -1,11 +1,11 @@
import { createVar, fallbackVar } from '@vanilla-extract/css';
+import { vars } from '../../../theme/contract.css.js';
import {
invalidIndicatorIconForcedColors,
invalidMessageIcon,
} from '../../styles/invalid-indicator.js';
import type { RecipeSelection, SlottedConfigInput } from '../../styles/recipe.js';
import { recipe } from '../../styles/recipe.js';
-import { vars } from '../../theme/contract.css.js';
const dataDisabledSelector = '[data-disabled="true"]';
const dataRequiredSelector = '[data-required="true"]';
diff --git a/packages/@luke-ui/react/src/primitives/field/root.tsx b/packages/@luke-ui/react/src/core/primitives/field/root.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/field/root.tsx
rename to packages/@luke-ui/react/src/core/primitives/field/root.tsx
diff --git a/packages/@luke-ui/react/src/primitives/input-group/input-group.tsx b/packages/@luke-ui/react/src/core/primitives/input-group/input-group.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/primitives/input-group/input-group.tsx
rename to packages/@luke-ui/react/src/core/primitives/input-group/input-group.tsx
diff --git a/packages/@luke-ui/react/src/primitives/input-group/recipe.css.ts b/packages/@luke-ui/react/src/core/primitives/input-group/recipe.css.ts
similarity index 98%
rename from packages/@luke-ui/react/src/primitives/input-group/recipe.css.ts
rename to packages/@luke-ui/react/src/core/primitives/input-group/recipe.css.ts
index c059333c..3a988e84 100644
--- a/packages/@luke-ui/react/src/primitives/input-group/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/primitives/input-group/recipe.css.ts
@@ -1,3 +1,5 @@
+import { vars } from '../../../theme/contract.css.js';
+import { FONT_METRIC_SCALE } from '../../../theme/font-metric-scale.js';
import { focusRing } from '../../styles/focus-ring.js';
import {
composeInputStateSelectors,
@@ -5,8 +7,6 @@ import {
} from '../../styles/input-states.js';
import type { RecipeSelection, SlottedConfigInput } from '../../styles/recipe.js';
import { recipe } from '../../styles/recipe.js';
-import { vars } from '../../theme/contract.css.js';
-import { FONT_METRIC_SCALE } from '../../theme/font-metric-scale.js';
const { disabled, focusWithin, hover, invalid, invalidFocusWithin, readOnly, readOnlyFocusWithin } =
composeInputStateSelectors();
diff --git a/packages/@luke-ui/react/src/quote/quote.browser.test.tsx b/packages/@luke-ui/react/src/core/quote/quote.browser.test.tsx
similarity index 91%
rename from packages/@luke-ui/react/src/quote/quote.browser.test.tsx
rename to packages/@luke-ui/react/src/core/quote/quote.browser.test.tsx
index 98c6c865..e42749f6 100644
--- a/packages/@luke-ui/react/src/quote/quote.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/quote/quote.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Quote } from './index.js';
+import { Quote } from './quote.js';
testConformance({
path: 'quote',
diff --git a/packages/@luke-ui/react/src/quote/quote.stories.tsx b/packages/@luke-ui/react/src/core/quote/quote.stories.tsx
similarity index 93%
rename from packages/@luke-ui/react/src/quote/quote.stories.tsx
rename to packages/@luke-ui/react/src/core/quote/quote.stories.tsx
index 5457f4cd..e72ee154 100644
--- a/packages/@luke-ui/react/src/quote/quote.stories.tsx
+++ b/packages/@luke-ui/react/src/core/quote/quote.stories.tsx
@@ -1,6 +1,6 @@
import { Quote } from '@luke-ui/react/quote';
import { Text } from '@luke-ui/react/text';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Quote,
diff --git a/packages/@luke-ui/react/src/quote/quote.tsx b/packages/@luke-ui/react/src/core/quote/quote.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/quote/quote.tsx
rename to packages/@luke-ui/react/src/core/quote/quote.tsx
diff --git a/packages/@luke-ui/react/src/sizing/button-sizing.ts b/packages/@luke-ui/react/src/core/sizing/button-sizing.ts
similarity index 100%
rename from packages/@luke-ui/react/src/sizing/button-sizing.ts
rename to packages/@luke-ui/react/src/core/sizing/button-sizing.ts
diff --git a/packages/@luke-ui/react/src/sizing/combobox-sizing.ts b/packages/@luke-ui/react/src/core/sizing/combobox-sizing.ts
similarity index 100%
rename from packages/@luke-ui/react/src/sizing/combobox-sizing.ts
rename to packages/@luke-ui/react/src/core/sizing/combobox-sizing.ts
diff --git a/packages/@luke-ui/react/src/sizing/control-size.ts b/packages/@luke-ui/react/src/core/sizing/control-size.ts
similarity index 100%
rename from packages/@luke-ui/react/src/sizing/control-size.ts
rename to packages/@luke-ui/react/src/core/sizing/control-size.ts
diff --git a/packages/@luke-ui/react/src/sizing/icon-sizing.ts b/packages/@luke-ui/react/src/core/sizing/icon-sizing.ts
similarity index 100%
rename from packages/@luke-ui/react/src/sizing/icon-sizing.ts
rename to packages/@luke-ui/react/src/core/sizing/icon-sizing.ts
diff --git a/packages/@luke-ui/react/src/strong/strong.browser.test.tsx b/packages/@luke-ui/react/src/core/strong/strong.browser.test.tsx
similarity index 91%
rename from packages/@luke-ui/react/src/strong/strong.browser.test.tsx
rename to packages/@luke-ui/react/src/core/strong/strong.browser.test.tsx
index a852439e..688c236b 100644
--- a/packages/@luke-ui/react/src/strong/strong.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/strong/strong.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Strong } from './index.js';
+import { Strong } from './strong.js';
testConformance({
path: 'strong',
diff --git a/packages/@luke-ui/react/src/strong/strong.stories.tsx b/packages/@luke-ui/react/src/core/strong/strong.stories.tsx
similarity index 92%
rename from packages/@luke-ui/react/src/strong/strong.stories.tsx
rename to packages/@luke-ui/react/src/core/strong/strong.stories.tsx
index 06e47f36..6d952d3e 100644
--- a/packages/@luke-ui/react/src/strong/strong.stories.tsx
+++ b/packages/@luke-ui/react/src/core/strong/strong.stories.tsx
@@ -1,6 +1,6 @@
import { Strong } from '@luke-ui/react/strong';
import { Text } from '@luke-ui/react/text';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: Strong,
diff --git a/packages/@luke-ui/react/src/strong/strong.tsx b/packages/@luke-ui/react/src/core/strong/strong.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/strong/strong.tsx
rename to packages/@luke-ui/react/src/core/strong/strong.tsx
index 0381f6c7..ec90dbf1 100644
--- a/packages/@luke-ui/react/src/strong/strong.tsx
+++ b/packages/@luke-ui/react/src/core/strong/strong.tsx
@@ -1,8 +1,8 @@
+import { cx } from '../../shared/utils/utils.js';
import type { TextProps } from '../text/text.js';
import { Text } from '../text/text.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import { strong } from './styles.css.js';
interface StrongStyleProps {
diff --git a/packages/@luke-ui/react/src/strong/styles.css.ts b/packages/@luke-ui/react/src/core/strong/styles.css.ts
similarity index 75%
rename from packages/@luke-ui/react/src/strong/styles.css.ts
rename to packages/@luke-ui/react/src/core/strong/styles.css.ts
index 26d626f2..c823f39f 100644
--- a/packages/@luke-ui/react/src/strong/styles.css.ts
+++ b/packages/@luke-ui/react/src/core/strong/styles.css.ts
@@ -1,5 +1,5 @@
+import { vars } from '../../theme/contract.css.js';
import { styleInLayer } from '../styles/layered-style.css.js';
-import { vars } from '../theme/contract.css.js';
export const strong = styleInLayer('recipes', {
fontWeight: vars.font.weight.emphasis,
diff --git a/packages/@luke-ui/react/src/styles/class-names.ts b/packages/@luke-ui/react/src/core/styles/class-selector.ts
similarity index 54%
rename from packages/@luke-ui/react/src/styles/class-names.ts
rename to packages/@luke-ui/react/src/core/styles/class-selector.ts
index 5093c2d0..bd6f204c 100644
--- a/packages/@luke-ui/react/src/styles/class-names.ts
+++ b/packages/@luke-ui/react/src/core/styles/class-selector.ts
@@ -1,8 +1,4 @@
-export const lukeUiClassNames = {
- resetRoot: 'luke-ui-reset',
- themeRoot: 'luke-ui-theme',
-} as const;
-
+/** Builds a CSS class selector (`.foo`) from a class name, typed to preserve the literal. */
export function classSelector(className: TClassName): `.${TClassName}` {
return `.${className}`;
}
diff --git a/packages/@luke-ui/react/src/styles/focus-ring.ts b/packages/@luke-ui/react/src/core/styles/focus-ring.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/focus-ring.ts
rename to packages/@luke-ui/react/src/core/styles/focus-ring.ts
diff --git a/packages/@luke-ui/react/src/styles/index.css.ts b/packages/@luke-ui/react/src/core/styles/index.css.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/index.css.ts
rename to packages/@luke-ui/react/src/core/styles/index.css.ts
diff --git a/packages/@luke-ui/react/src/styles/input-states.ts b/packages/@luke-ui/react/src/core/styles/input-states.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/input-states.ts
rename to packages/@luke-ui/react/src/core/styles/input-states.ts
diff --git a/packages/@luke-ui/react/src/styles/invalid-indicator.ts b/packages/@luke-ui/react/src/core/styles/invalid-indicator.ts
similarity index 95%
rename from packages/@luke-ui/react/src/styles/invalid-indicator.ts
rename to packages/@luke-ui/react/src/core/styles/invalid-indicator.ts
index 8c83d788..2a2219c9 100644
--- a/packages/@luke-ui/react/src/styles/invalid-indicator.ts
+++ b/packages/@luke-ui/react/src/core/styles/invalid-indicator.ts
@@ -1,6 +1,6 @@
import type { StyleRule } from '@vanilla-extract/css';
-import { iconMaskUrls } from '../../.generated/icon-mask-data.js';
-import { vars } from '../theme/contract.css.js';
+import { iconMaskUrls } from '../../../.generated/icon-mask-data.js';
+import { vars } from '../../theme/contract.css.js';
/** Renders the `exclamationTriangle` icon as a CSS mask `::before`/`::after`, at `size`. */
function invalidIconMask(size: string) {
diff --git a/packages/@luke-ui/react/src/styles/layered-style.css.ts b/packages/@luke-ui/react/src/core/styles/layered-style.css.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/layered-style.css.ts
rename to packages/@luke-ui/react/src/core/styles/layered-style.css.ts
diff --git a/packages/@luke-ui/react/src/styles/layers.css.ts b/packages/@luke-ui/react/src/core/styles/layers.css.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/layers.css.ts
rename to packages/@luke-ui/react/src/core/styles/layers.css.ts
diff --git a/packages/@luke-ui/react/src/styles/layers.test.ts b/packages/@luke-ui/react/src/core/styles/layers.test.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/layers.test.ts
rename to packages/@luke-ui/react/src/core/styles/layers.test.ts
diff --git a/packages/@luke-ui/react/src/styles/modules.css.ts b/packages/@luke-ui/react/src/core/styles/modules.css.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/modules.css.ts
rename to packages/@luke-ui/react/src/core/styles/modules.css.ts
diff --git a/packages/@luke-ui/react/src/styles/overlay-motion.ts b/packages/@luke-ui/react/src/core/styles/overlay-motion.ts
similarity index 95%
rename from packages/@luke-ui/react/src/styles/overlay-motion.ts
rename to packages/@luke-ui/react/src/core/styles/overlay-motion.ts
index 32b9d277..3bd41ac2 100644
--- a/packages/@luke-ui/react/src/styles/overlay-motion.ts
+++ b/packages/@luke-ui/react/src/core/styles/overlay-motion.ts
@@ -1,4 +1,4 @@
-import { vars } from '../theme/contract.css.js';
+import { vars } from '../../theme/contract.css.js';
/*
* Overlays take the enter and exit duration roles rather than the in-place feedback one. Entry
diff --git a/packages/@luke-ui/react/src/styles/package-exports.test.ts b/packages/@luke-ui/react/src/core/styles/package-exports.test.ts
similarity index 66%
rename from packages/@luke-ui/react/src/styles/package-exports.test.ts
rename to packages/@luke-ui/react/src/core/styles/package-exports.test.ts
index 970cb6c8..c40c3592 100644
--- a/packages/@luke-ui/react/src/styles/package-exports.test.ts
+++ b/packages/@luke-ui/react/src/core/styles/package-exports.test.ts
@@ -1,6 +1,6 @@
import { readFile } from 'node:fs/promises';
import { expect, test } from 'vite-plus/test';
-import packageJson from '../../package.json' with { type: 'json' };
+import packageJson from '../../../package.json' with { type: 'json' };
const absentExportPaths = [
'./button/primitive',
@@ -18,17 +18,17 @@ const absentExportPaths = [
] as const;
const presentExportPaths = {
- './box': './dist/box/index.js',
- './theme': './dist/theme/index.js',
- './themes/tactile': './dist/themes/tactile/index.js',
- './themes/paper': './dist/themes/paper/index.js',
- './styles': './dist/styles/index.js',
+ './box': './dist/box.js',
+ './theme': './dist/theme.js',
+ './themes/tactile': './dist/themes/tactile.js',
+ './themes/paper': './dist/themes/paper.js',
+ './styles': './dist/styles.js',
'./stylesheet.css': './dist/stylesheet.css',
- './primitives/button': './dist/primitives/button/index.js',
- './primitives/checkbox': './dist/primitives/checkbox/index.js',
- './primitives/combobox': './dist/primitives/combobox/index.js',
- './primitives/field': './dist/primitives/field/index.js',
- './primitives/input-group': './dist/primitives/input-group/index.js',
+ './primitives/button': './dist/primitives/button.js',
+ './primitives/checkbox': './dist/primitives/checkbox.js',
+ './primitives/combobox': './dist/primitives/combobox.js',
+ './primitives/field': './dist/primitives/field.js',
+ './primitives/input-group': './dist/primitives/input-group.js',
} as const;
test('publishes only the final styling entrypoints', () => {
@@ -46,9 +46,9 @@ test('publishes only the final styling entrypoints', () => {
});
test('bundles recipe runtime through a relative chunk, not a package import', async () => {
- const source = await readFile(new URL('../../dist/blockquote/index.js', import.meta.url), 'utf8');
+ const source = await readFile(new URL('../../../dist/blockquote.js', import.meta.url), 'utf8');
expect(source).not.toContain('#recipe-engine');
- expect(source).toMatch(/createSingleRecipe[\s\S]*from ["']\.\.\//);
+ expect(source).toMatch(/createSingleRecipe[\s\S]*from ["']\.\//);
});
test('requires react-aria-components as a peer dependency', () => {
diff --git a/packages/@luke-ui/react/src/styles/recipe-engine.ts b/packages/@luke-ui/react/src/core/styles/recipe-engine.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/recipe-engine.ts
rename to packages/@luke-ui/react/src/core/styles/recipe-engine.ts
diff --git a/packages/@luke-ui/react/src/styles/recipe.browser.test.ts b/packages/@luke-ui/react/src/core/styles/recipe.browser.test.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/recipe.browser.test.ts
rename to packages/@luke-ui/react/src/core/styles/recipe.browser.test.ts
diff --git a/packages/@luke-ui/react/src/styles/recipe.fixtures.css.ts b/packages/@luke-ui/react/src/core/styles/recipe.fixtures.css.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/recipe.fixtures.css.ts
rename to packages/@luke-ui/react/src/core/styles/recipe.fixtures.css.ts
diff --git a/packages/@luke-ui/react/src/styles/recipe.ts b/packages/@luke-ui/react/src/core/styles/recipe.ts
similarity index 99%
rename from packages/@luke-ui/react/src/styles/recipe.ts
rename to packages/@luke-ui/react/src/core/styles/recipe.ts
index 1745d566..48aa2596 100644
--- a/packages/@luke-ui/react/src/styles/recipe.ts
+++ b/packages/@luke-ui/react/src/core/styles/recipe.ts
@@ -1,8 +1,8 @@
import type { StyleRule } from '@vanilla-extract/css';
import { addFunctionSerializer } from '@vanilla-extract/css/functionSerializer';
import { recipe as vanillaRecipe } from '@vanilla-extract/recipes';
+import { cx } from '../../shared/utils/utils.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
-import { cx } from '../utils/utils.js';
/**
* `recipe()` styling helper for Vanilla Extract.
diff --git a/packages/@luke-ui/react/src/styles/reset.css.ts b/packages/@luke-ui/react/src/core/styles/reset.css.ts
similarity index 93%
rename from packages/@luke-ui/react/src/styles/reset.css.ts
rename to packages/@luke-ui/react/src/core/styles/reset.css.ts
index 8c6b6da6..17e5c9dd 100644
--- a/packages/@luke-ui/react/src/styles/reset.css.ts
+++ b/packages/@luke-ui/react/src/core/styles/reset.css.ts
@@ -1,5 +1,6 @@
-import { vars } from '../theme/contract.css.js';
-import { classSelector, lukeUiClassNames } from './class-names.js';
+import { lukeUiClassNames } from '../../shared/class-names.js';
+import { vars } from '../../theme/contract.css.js';
+import { classSelector } from './class-selector.js';
import { focusRing } from './focus-ring.js';
import { globalStyleInLayer } from './layered-style.css.js';
diff --git a/packages/@luke-ui/react/src/styles/spinner-overlay.ts b/packages/@luke-ui/react/src/core/styles/spinner-overlay.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/spinner-overlay.ts
rename to packages/@luke-ui/react/src/core/styles/spinner-overlay.ts
diff --git a/packages/@luke-ui/react/src/styles/stylesheet-contract.test.ts b/packages/@luke-ui/react/src/core/styles/stylesheet-contract.test.ts
similarity index 97%
rename from packages/@luke-ui/react/src/styles/stylesheet-contract.test.ts
rename to packages/@luke-ui/react/src/core/styles/stylesheet-contract.test.ts
index b23e8987..05525dae 100644
--- a/packages/@luke-ui/react/src/styles/stylesheet-contract.test.ts
+++ b/packages/@luke-ui/react/src/core/styles/stylesheet-contract.test.ts
@@ -3,8 +3,8 @@ import type { AtRule, Root, Rule } from 'postcss';
import { parse } from 'postcss';
import selectorParser from 'postcss-selector-parser';
import { expect, test } from 'vite-plus/test';
-import type { TypeStyle } from '../theme/contract.js';
-import { typeStyles } from '../theme/contract.js';
+import type { TypeStyle } from '../../theme/contract.js';
+import { typeStyles } from '../../theme/contract.js';
const retainedLayerNames = ['reset', 'theme', 'recipes', 'utilities'] as const;
const retainedLayerNameSet = new Set(retainedLayerNames);
@@ -17,7 +17,10 @@ type LineClampClasses = {
};
test('builds the public stylesheet with the retained layer contract', async () => {
- const stylesheet = await readFile(new URL('../../dist/stylesheet.css', import.meta.url), 'utf8');
+ const stylesheet = await readFile(
+ new URL('../../../dist/stylesheet.css', import.meta.url),
+ 'utf8',
+ );
const icon = await import('@luke-ui/react/icon');
const text = await import('@luke-ui/react/text');
const styles = await import('@luke-ui/react/styles');
@@ -106,7 +109,10 @@ for (const [name, mutate] of stylesheetMutations) {
}
test('queries responsive conditions on the logical inline axis', async () => {
- const stylesheet = await readFile(new URL('../../dist/stylesheet.css', import.meta.url), 'utf8');
+ const stylesheet = await readFile(
+ new URL('../../../dist/stylesheet.css', import.meta.url),
+ 'utf8',
+ );
// The containers are `container-type: inline-size`, which answers inline-axis queries. A
// physical `width` query only agrees with that in a horizontal writing mode.
diff --git a/packages/@luke-ui/react/src/styles/stylesheet-size.test.ts b/packages/@luke-ui/react/src/core/styles/stylesheet-size.test.ts
similarity index 88%
rename from packages/@luke-ui/react/src/styles/stylesheet-size.test.ts
rename to packages/@luke-ui/react/src/core/styles/stylesheet-size.test.ts
index 381cfbe2..d719ef30 100644
--- a/packages/@luke-ui/react/src/styles/stylesheet-size.test.ts
+++ b/packages/@luke-ui/react/src/core/styles/stylesheet-size.test.ts
@@ -9,7 +9,7 @@ import { expect, test } from 'vite-plus/test';
const maximumGzipBytes = 12_000;
test('keeps the public stylesheet within its size budget', async () => {
- const stylesheet = await readFile(new URL('../../dist/stylesheet.css', import.meta.url));
+ const stylesheet = await readFile(new URL('../../../dist/stylesheet.css', import.meta.url));
expect(gzipSync(stylesheet, { level: 9 }).byteLength, 'gzip stylesheet size').toBeLessThanOrEqual(
maximumGzipBytes,
diff --git a/packages/@luke-ui/react/src/styles/theme-root.css.ts b/packages/@luke-ui/react/src/core/styles/theme-root.css.ts
similarity index 59%
rename from packages/@luke-ui/react/src/styles/theme-root.css.ts
rename to packages/@luke-ui/react/src/core/styles/theme-root.css.ts
index 0dff29c0..303f6175 100644
--- a/packages/@luke-ui/react/src/styles/theme-root.css.ts
+++ b/packages/@luke-ui/react/src/core/styles/theme-root.css.ts
@@ -1,5 +1,6 @@
-import { vars } from '../theme/contract.css.js';
-import { classSelector, lukeUiClassNames } from './class-names.js';
+import { lukeUiClassNames } from '../../shared/class-names.js';
+import { vars } from '../../theme/contract.css.js';
+import { classSelector } from './class-selector.js';
import { globalStyleInLayer } from './layered-style.css.js';
globalStyleInLayer('theme', classSelector(lukeUiClassNames.themeRoot), {
diff --git a/packages/@luke-ui/react/src/styles/utilities-emitted.test-d.ts b/packages/@luke-ui/react/src/core/styles/utilities-emitted.test-d.ts
similarity index 92%
rename from packages/@luke-ui/react/src/styles/utilities-emitted.test-d.ts
rename to packages/@luke-ui/react/src/core/styles/utilities-emitted.test-d.ts
index e71d1f88..02e58eb9 100644
--- a/packages/@luke-ui/react/src/styles/utilities-emitted.test-d.ts
+++ b/packages/@luke-ui/react/src/core/styles/utilities-emitted.test-d.ts
@@ -2,20 +2,17 @@
import { readFile } from 'node:fs/promises';
import { expect, test } from 'vite-plus/test';
-import { breakpoints } from '../theme/breakpoints.js';
-import { SEMANTIC_ROLES } from '../theme/contrast-policy.js';
+import { breakpoints } from '../../theme/breakpoints.js';
+import { SEMANTIC_ROLES } from '../../theme/contrast-policy.js';
-/** Reads the content-hashed declaration chunk referenced by `box/index.d.ts`. */
+/** Reads the content-hashed declaration chunk referenced by `box.d.ts`. */
async function readUtilitiesDeclaration(): Promise {
- const boxDeclaration = await readFile(
- new URL('../../dist/box/index.d.ts', import.meta.url),
- 'utf8',
- );
+ const boxDeclaration = await readFile(new URL('../../../dist/box.d.ts', import.meta.url), 'utf8');
const chunk = /utilities\.css-[A-Za-z0-9_-]+/.exec(boxDeclaration)?.[0];
if (chunk === undefined) {
- throw new Error('Could not find the utilities declaration chunk from dist/box/index.d.ts');
+ throw new Error('Could not find the utilities declaration chunk from dist/box.d.ts');
}
- return readFile(new URL(`../../dist/${chunk}.d.ts`, import.meta.url), 'utf8');
+ return readFile(new URL(`../../../dist/${chunk}.d.ts`, import.meta.url), 'utf8');
}
/** Extracts one property's declared type while balancing braces. */
diff --git a/packages/@luke-ui/react/src/styles/utilities.browser.test.ts b/packages/@luke-ui/react/src/core/styles/utilities.browser.test.ts
similarity index 93%
rename from packages/@luke-ui/react/src/styles/utilities.browser.test.ts
rename to packages/@luke-ui/react/src/core/styles/utilities.browser.test.ts
index ed3364d2..b40c1832 100644
--- a/packages/@luke-ui/react/src/styles/utilities.browser.test.ts
+++ b/packages/@luke-ui/react/src/core/styles/utilities.browser.test.ts
@@ -1,10 +1,10 @@
-import '../../dist/themes/tactile/stylesheet.css';
+import '../../../dist/themes/tactile/stylesheet.css';
import '../stylesheet.css.js';
import { afterEach, expect, test } from 'vite-plus/test';
import { page } from 'vite-plus/test/context';
-import { breakpoints } from '../theme/breakpoints.js';
-import { themeClassName as tactileThemeClassName } from '../themes/tactile/index.js';
-import { mergeProps } from '../utils/index.js';
+import { mergeProps } from '../../shared/utils/utils.js';
+import { breakpoints } from '../../theme/breakpoints.js';
+import { themeClassName as tactileThemeClassName } from '../../theme/bundles/tactile/index.js';
import { createSprinkles } from './utilities.css.js';
const mounted: Array = [];
diff --git a/packages/@luke-ui/react/src/styles/utilities.css.ts b/packages/@luke-ui/react/src/core/styles/utilities.css.ts
similarity index 95%
rename from packages/@luke-ui/react/src/styles/utilities.css.ts
rename to packages/@luke-ui/react/src/core/styles/utilities.css.ts
index 976225a1..c7101fcd 100644
--- a/packages/@luke-ui/react/src/styles/utilities.css.ts
+++ b/packages/@luke-ui/react/src/core/styles/utilities.css.ts
@@ -1,10 +1,10 @@
import type { SprinklesFn } from '@luke-ui/rainbow-sprinkles';
import { defineProperties, defineSprinkles } from '@luke-ui/rainbow-sprinkles';
-import { breakpoints } from '../theme/breakpoints.js';
-import { vars } from '../theme/contract.css.js';
-import type { SpaceStep } from '../theme/contract.js';
-import { SEMANTIC_ROLES } from '../theme/contrast-policy.js';
-import { typedEntries } from '../utils/index.js';
+import { typedEntries } from '../../shared/utils/utils.js';
+import { breakpoints } from '../../theme/breakpoints.js';
+import { vars } from '../../theme/contract.css.js';
+import type { SpaceStep } from '../../theme/contract.js';
+import { SEMANTIC_ROLES } from '../../theme/contrast-policy.js';
import { layers } from './layers.css.js';
function fromBreakpoint(minimumInlineSize: number) {
diff --git a/packages/@luke-ui/react/src/styles/utilities.stories.tsx b/packages/@luke-ui/react/src/core/styles/utilities.stories.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/styles/utilities.stories.tsx
rename to packages/@luke-ui/react/src/core/styles/utilities.stories.tsx
index bf4189e6..b574903d 100644
--- a/packages/@luke-ui/react/src/styles/utilities.stories.tsx
+++ b/packages/@luke-ui/react/src/core/styles/utilities.stories.tsx
@@ -2,8 +2,8 @@ import { Button } from '@luke-ui/react/button';
import { createSprinkles } from '@luke-ui/react/styles';
import { mergeProps } from '@luke-ui/react/utils';
import type { CSSProperties } from 'react';
-import preview from '../../.storybook/preview.js';
-import { vars } from '../theme/index.js';
+import preview from '../../../.storybook/preview.js';
+import { vars } from '../../theme/index.js';
const meta = preview.meta({
title: 'Foundation/Utilities',
diff --git a/packages/@luke-ui/react/src/styles/utilities.test-d.ts b/packages/@luke-ui/react/src/core/styles/utilities.test-d.ts
similarity index 98%
rename from packages/@luke-ui/react/src/styles/utilities.test-d.ts
rename to packages/@luke-ui/react/src/core/styles/utilities.test-d.ts
index bb1bb621..41dab941 100644
--- a/packages/@luke-ui/react/src/styles/utilities.test-d.ts
+++ b/packages/@luke-ui/react/src/core/styles/utilities.test-d.ts
@@ -1,7 +1,7 @@
/** Type-level assertions against the utility props emitted for package consumers. */
import { assertType, expectTypeOf, test } from 'vite-plus/test';
-import type { BoxProps } from '../../dist/box/index.js';
+import type { BoxProps } from '../../../dist/box.js';
type UtilityProps = NonNullable;
diff --git a/packages/@luke-ui/react/src/styles/utilities.test.ts b/packages/@luke-ui/react/src/core/styles/utilities.test.ts
similarity index 100%
rename from packages/@luke-ui/react/src/styles/utilities.test.ts
rename to packages/@luke-ui/react/src/core/styles/utilities.test.ts
diff --git a/packages/@luke-ui/react/src/stylesheet.css.ts b/packages/@luke-ui/react/src/core/stylesheet.css.ts
similarity index 100%
rename from packages/@luke-ui/react/src/stylesheet.css.ts
rename to packages/@luke-ui/react/src/core/stylesheet.css.ts
diff --git a/packages/@luke-ui/react/src/test-utils/get-described-text.ts b/packages/@luke-ui/react/src/core/test-utils/get-described-text.ts
similarity index 100%
rename from packages/@luke-ui/react/src/test-utils/get-described-text.ts
rename to packages/@luke-ui/react/src/core/test-utils/get-described-text.ts
diff --git a/packages/@luke-ui/react/src/test-utils/mock-screen-width.ts b/packages/@luke-ui/react/src/core/test-utils/mock-screen-width.ts
similarity index 100%
rename from packages/@luke-ui/react/src/test-utils/mock-screen-width.ts
rename to packages/@luke-ui/react/src/core/test-utils/mock-screen-width.ts
diff --git a/packages/@luke-ui/react/src/test-utils/render-setup.ts b/packages/@luke-ui/react/src/core/test-utils/render-setup.ts
similarity index 100%
rename from packages/@luke-ui/react/src/test-utils/render-setup.ts
rename to packages/@luke-ui/react/src/core/test-utils/render-setup.ts
diff --git a/packages/@luke-ui/react/src/test-utils/render.browser.test.tsx b/packages/@luke-ui/react/src/core/test-utils/render.browser.test.tsx
similarity index 88%
rename from packages/@luke-ui/react/src/test-utils/render.browser.test.tsx
rename to packages/@luke-ui/react/src/core/test-utils/render.browser.test.tsx
index 00d537b3..0784f100 100644
--- a/packages/@luke-ui/react/src/test-utils/render.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/test-utils/render.browser.test.tsx
@@ -1,6 +1,6 @@
import { expect, test } from 'vite-plus/test';
-import { themeClassName as paperThemeClassName } from '../themes/paper/index.js';
-import { themeClassName as tactileThemeClassName } from '../themes/tactile/index.js';
+import { themeClassName as paperThemeClassName } from '../../theme/bundles/paper/index.js';
+import { themeClassName as tactileThemeClassName } from '../../theme/bundles/tactile/index.js';
import { render, visualAppearances } from './render.js';
test('renders every bundled identity and explicit colour mode independently', () => {
diff --git a/packages/@luke-ui/react/src/test-utils/render.tsx b/packages/@luke-ui/react/src/core/test-utils/render.tsx
similarity index 91%
rename from packages/@luke-ui/react/src/test-utils/render.tsx
rename to packages/@luke-ui/react/src/core/test-utils/render.tsx
index 773eb095..fa7f82a5 100644
--- a/packages/@luke-ui/react/src/test-utils/render.tsx
+++ b/packages/@luke-ui/react/src/core/test-utils/render.tsx
@@ -12,11 +12,11 @@ import type { Locator } from 'vite-plus/test/context';
import { page, userEvent } from 'vite-plus/test/context';
// The generated spritesheet is emitted to `dist/` by the `generate` task, which
// both `build` and `test` depend on, so it is always present when tests run.
-import spritesheetHref from '../../dist/spritesheet.svg?url';
-import { IconSpritesheetProvider } from '../icon/index.js';
-import { rootClassName, vars } from '../theme/index.js';
-import { themeClassName as paperThemeClassName } from '../themes/paper/index.js';
-import { themeClassName as tactileThemeClassName } from '../themes/tactile/index.js';
+import spritesheetHref from '../../../dist/spritesheet.svg?url';
+import { themeClassName as paperThemeClassName } from '../../theme/bundles/paper/index.js';
+import { themeClassName as tactileThemeClassName } from '../../theme/bundles/tactile/index.js';
+import { rootClassName, vars } from '../../theme/index.js';
+import { IconSpritesheetProvider } from '../icon/icon.js';
const mounted: Array<{ container: HTMLElement; root: Root }> = [];
diff --git a/packages/@luke-ui/react/src/test-utils/visual-capture-id.test.ts b/packages/@luke-ui/react/src/core/test-utils/visual-capture-id.test.ts
similarity index 100%
rename from packages/@luke-ui/react/src/test-utils/visual-capture-id.test.ts
rename to packages/@luke-ui/react/src/core/test-utils/visual-capture-id.test.ts
diff --git a/packages/@luke-ui/react/src/test-utils/visual-capture-id.ts b/packages/@luke-ui/react/src/core/test-utils/visual-capture-id.ts
similarity index 100%
rename from packages/@luke-ui/react/src/test-utils/visual-capture-id.ts
rename to packages/@luke-ui/react/src/core/test-utils/visual-capture-id.ts
diff --git a/packages/@luke-ui/react/src/test-utils/visual-setup.ts b/packages/@luke-ui/react/src/core/test-utils/visual-setup.ts
similarity index 100%
rename from packages/@luke-ui/react/src/test-utils/visual-setup.ts
rename to packages/@luke-ui/react/src/core/test-utils/visual-setup.ts
diff --git a/packages/@luke-ui/react/src/test-utils/visual.tsx b/packages/@luke-ui/react/src/core/test-utils/visual.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/test-utils/visual.tsx
rename to packages/@luke-ui/react/src/core/test-utils/visual.tsx
diff --git a/packages/@luke-ui/react/src/test-utils/wait-for-overlay-enter.ts b/packages/@luke-ui/react/src/core/test-utils/wait-for-overlay-enter.ts
similarity index 100%
rename from packages/@luke-ui/react/src/test-utils/wait-for-overlay-enter.ts
rename to packages/@luke-ui/react/src/core/test-utils/wait-for-overlay-enter.ts
diff --git a/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx b/packages/@luke-ui/react/src/core/text-field/text-field.browser.test.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx
rename to packages/@luke-ui/react/src/core/text-field/text-field.browser.test.tsx
index d4677df7..0aa578c8 100644
--- a/packages/@luke-ui/react/src/text-field/text-field.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/text-field/text-field.browser.test.tsx
@@ -2,10 +2,10 @@ import { createRef } from 'react';
import { expect, test } from 'vite-plus/test';
import { page, userEvent } from 'vite-plus/test/context';
import { testConformance, testIntegration } from '../conformance/helpers.js';
-import { InputGroup, InputGroupInput } from '../primitives/input-group/index.js';
+import { InputGroup, InputGroupInput } from '../primitives/input-group/input-group.js';
import { getDescribedText } from '../test-utils/get-described-text.js';
import { render } from '../test-utils/render.js';
-import { TextField } from './index.js';
+import { TextField } from './text-field.js';
testConformance({
path: 'text-field',
diff --git a/packages/@luke-ui/react/src/text-field/text-field.stories.tsx b/packages/@luke-ui/react/src/core/text-field/text-field.stories.tsx
similarity index 98%
rename from packages/@luke-ui/react/src/text-field/text-field.stories.tsx
rename to packages/@luke-ui/react/src/core/text-field/text-field.stories.tsx
index dc8cf070..9b8ed7a6 100644
--- a/packages/@luke-ui/react/src/text-field/text-field.stories.tsx
+++ b/packages/@luke-ui/react/src/core/text-field/text-field.stories.tsx
@@ -11,7 +11,7 @@ import type { CSSProperties } from 'react';
import { useState } from 'react';
import { Form } from 'react-aria-components/Form';
import { userEvent, within } from 'storybook/test';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: TextField,
diff --git a/packages/@luke-ui/react/src/text-field/text-field.tsx b/packages/@luke-ui/react/src/core/text-field/text-field.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/text-field/text-field.tsx
rename to packages/@luke-ui/react/src/core/text-field/text-field.tsx
diff --git a/packages/@luke-ui/react/src/text-field/text-field.visual.test.tsx b/packages/@luke-ui/react/src/core/text-field/text-field.visual.test.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/text-field/text-field.visual.test.tsx
rename to packages/@luke-ui/react/src/core/text-field/text-field.visual.test.tsx
index ac43f7c3..3e443649 100644
--- a/packages/@luke-ui/react/src/text-field/text-field.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/text-field/text-field.visual.test.tsx
@@ -1,13 +1,13 @@
import { test } from 'vite-plus/test';
import { page, userEvent } from 'vite-plus/test/context';
-import { Button } from '../button/index.js';
-import { Icon } from '../icon/index.js';
+import { Button } from '../button/button.js';
+import { Icon } from '../icon/icon.js';
import {
InputGroup,
InputGroupInput,
InputGroupPrefix,
InputGroupSuffix,
-} from '../primitives/input-group/index.js';
+} from '../primitives/input-group/input-group.js';
import { render, visualAppearances } from '../test-utils/render.js';
import {
captureVisual,
@@ -16,7 +16,7 @@ import {
focusViaKeyboard,
Stack,
} from '../test-utils/visual.js';
-import { TextField } from './index.js';
+import { TextField } from './text-field.js';
test('kitchen sink', async () => {
for (const appearance of visualAppearances) {
diff --git a/packages/@luke-ui/react/src/text/recipe.css.ts b/packages/@luke-ui/react/src/core/text/recipe.css.ts
similarity index 96%
rename from packages/@luke-ui/react/src/text/recipe.css.ts
rename to packages/@luke-ui/react/src/core/text/recipe.css.ts
index 79efd439..027248c8 100644
--- a/packages/@luke-ui/react/src/text/recipe.css.ts
+++ b/packages/@luke-ui/react/src/core/text/recipe.css.ts
@@ -1,11 +1,11 @@
import type { ComplexStyleRule } from '@vanilla-extract/css';
import { createVar } from '@vanilla-extract/css';
+import { vars } from '../../theme/contract.css.js';
+import type { FontWeightRole, TypeStyle } from '../../theme/contract.js';
+import { fontWeightRoles, typeStyles } from '../../theme/contract.js';
import { styleInLayer } from '../styles/layered-style.css.js';
import type { RecipeSelection } from '../styles/recipe.js';
import { recipe } from '../styles/recipe.js';
-import { vars } from '../theme/contract.css.js';
-import type { FontWeightRole, TypeStyle } from '../theme/contract.js';
-import { fontWeightRoles, typeStyles } from '../theme/contract.js';
import { visuallyHiddenStyle } from '../visually-hidden/recipe.css.js';
const lineClampNone = {} satisfies ComplexStyleRule;
diff --git a/packages/@luke-ui/react/src/text/text.browser.test.tsx b/packages/@luke-ui/react/src/core/text/text.browser.test.tsx
similarity index 91%
rename from packages/@luke-ui/react/src/text/text.browser.test.tsx
rename to packages/@luke-ui/react/src/core/text/text.browser.test.tsx
index 674c002a..fd67354d 100644
--- a/packages/@luke-ui/react/src/text/text.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/text/text.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { Text } from './index.js';
+import { Text } from './text.js';
testConformance({
path: 'text',
diff --git a/packages/@luke-ui/react/src/text/text.stories.tsx b/packages/@luke-ui/react/src/core/text/text.stories.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/text/text.stories.tsx
rename to packages/@luke-ui/react/src/core/text/text.stories.tsx
index 529ed96a..692db341 100644
--- a/packages/@luke-ui/react/src/text/text.stories.tsx
+++ b/packages/@luke-ui/react/src/core/text/text.stories.tsx
@@ -3,9 +3,9 @@ import { Text } from '@luke-ui/react/text';
import { mergeProps } from '@luke-ui/react/utils';
import { VisuallyHidden as VisuallyHiddenText } from '@luke-ui/react/visually-hidden';
import type { CSSProperties } from 'react';
-import preview from '../../.storybook/preview.js';
-import { createSprinkles } from '../styles/index.js';
-import { typeStyles, vars } from '../theme/index.js';
+import preview from '../../../.storybook/preview.js';
+import { typeStyles, vars } from '../../theme/index.js';
+import { createSprinkles } from '../styles/utilities.css.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
const meta = preview.meta({
diff --git a/packages/@luke-ui/react/src/text/text.tsx b/packages/@luke-ui/react/src/core/text/text.tsx
similarity index 97%
rename from packages/@luke-ui/react/src/text/text.tsx
rename to packages/@luke-ui/react/src/core/text/text.tsx
index 1b92b00f..16537b90 100644
--- a/packages/@luke-ui/react/src/text/text.tsx
+++ b/packages/@luke-ui/react/src/core/text/text.tsx
@@ -1,8 +1,8 @@
import { Text as RacText } from 'react-aria-components/Text';
-import { typeStyleWeightRole } from '../theme/contract.js';
+import { cx } from '../../shared/utils/utils.js';
+import { typeStyleWeightRole } from '../../theme/contract.js';
import type { DistributiveOmit } from '../types/distributive-omit.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import type { TextRecipeVariants } from './recipe.css.js';
import { textRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/text/text.visual.test.tsx b/packages/@luke-ui/react/src/core/text/text.visual.test.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/text/text.visual.test.tsx
rename to packages/@luke-ui/react/src/core/text/text.visual.test.tsx
index 2b489150..c47b7bbf 100644
--- a/packages/@luke-ui/react/src/text/text.visual.test.tsx
+++ b/packages/@luke-ui/react/src/core/text/text.visual.test.tsx
@@ -1,9 +1,9 @@
import type { CSSProperties } from 'react';
import { test } from 'vite-plus/test';
+import { typeStyles } from '../../theme/contract.js';
import { render, visualAppearances } from '../test-utils/render.js';
import { captureVisual, captureVisualAppearance, Stack } from '../test-utils/visual.js';
-import { typeStyles } from '../theme/contract.js';
-import { Text } from './index.js';
+import { Text } from './text.js';
const rowStyle = {
alignItems: 'baseline',
diff --git a/packages/@luke-ui/react/src/types/distributive-omit.ts b/packages/@luke-ui/react/src/core/types/distributive-omit.ts
similarity index 100%
rename from packages/@luke-ui/react/src/types/distributive-omit.ts
rename to packages/@luke-ui/react/src/core/types/distributive-omit.ts
diff --git a/packages/@luke-ui/react/src/types/documented-rac-props.ts b/packages/@luke-ui/react/src/core/types/documented-rac-props.ts
similarity index 100%
rename from packages/@luke-ui/react/src/types/documented-rac-props.ts
rename to packages/@luke-ui/react/src/core/types/documented-rac-props.ts
diff --git a/packages/@luke-ui/react/src/types/icon-size.ts b/packages/@luke-ui/react/src/core/types/icon-size.ts
similarity index 100%
rename from packages/@luke-ui/react/src/types/icon-size.ts
rename to packages/@luke-ui/react/src/core/types/icon-size.ts
diff --git a/packages/@luke-ui/react/src/types/prettify.ts b/packages/@luke-ui/react/src/core/types/prettify.ts
similarity index 100%
rename from packages/@luke-ui/react/src/types/prettify.ts
rename to packages/@luke-ui/react/src/core/types/prettify.ts
diff --git a/packages/@luke-ui/react/src/use-synchronize-animations/use-synchronize-animations.browser.test.tsx b/packages/@luke-ui/react/src/core/use-synchronize-animations/use-synchronize-animations.browser.test.tsx
similarity index 100%
rename from packages/@luke-ui/react/src/use-synchronize-animations/use-synchronize-animations.browser.test.tsx
rename to packages/@luke-ui/react/src/core/use-synchronize-animations/use-synchronize-animations.browser.test.tsx
diff --git a/packages/@luke-ui/react/src/use-synchronize-animations/use-synchronize-animations.ts b/packages/@luke-ui/react/src/core/use-synchronize-animations/use-synchronize-animations.ts
similarity index 100%
rename from packages/@luke-ui/react/src/use-synchronize-animations/use-synchronize-animations.ts
rename to packages/@luke-ui/react/src/core/use-synchronize-animations/use-synchronize-animations.ts
diff --git a/packages/@luke-ui/react/src/visually-hidden/recipe.css.ts b/packages/@luke-ui/react/src/core/visually-hidden/recipe.css.ts
similarity index 100%
rename from packages/@luke-ui/react/src/visually-hidden/recipe.css.ts
rename to packages/@luke-ui/react/src/core/visually-hidden/recipe.css.ts
diff --git a/packages/@luke-ui/react/src/visually-hidden/visually-hidden.browser.test.tsx b/packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.browser.test.tsx
similarity index 89%
rename from packages/@luke-ui/react/src/visually-hidden/visually-hidden.browser.test.tsx
rename to packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.browser.test.tsx
index 11b44990..56e04b58 100644
--- a/packages/@luke-ui/react/src/visually-hidden/visually-hidden.browser.test.tsx
+++ b/packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.browser.test.tsx
@@ -1,6 +1,6 @@
import { testConformance } from '../conformance/helpers.js';
import { render } from '../test-utils/render.js';
-import { VisuallyHidden } from './index.js';
+import { VisuallyHidden } from './visually-hidden.js';
testConformance({
path: 'visually-hidden',
diff --git a/packages/@luke-ui/react/src/visually-hidden/visually-hidden.stories.tsx b/packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.stories.tsx
similarity index 93%
rename from packages/@luke-ui/react/src/visually-hidden/visually-hidden.stories.tsx
rename to packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.stories.tsx
index cc5758d4..97b802e3 100644
--- a/packages/@luke-ui/react/src/visually-hidden/visually-hidden.stories.tsx
+++ b/packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.stories.tsx
@@ -1,5 +1,5 @@
import { VisuallyHidden } from '@luke-ui/react/visually-hidden';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: VisuallyHidden,
diff --git a/packages/@luke-ui/react/src/visually-hidden/visually-hidden.tsx b/packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.tsx
similarity index 96%
rename from packages/@luke-ui/react/src/visually-hidden/visually-hidden.tsx
rename to packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.tsx
index 7b227a45..bfadc937 100644
--- a/packages/@luke-ui/react/src/visually-hidden/visually-hidden.tsx
+++ b/packages/@luke-ui/react/src/core/visually-hidden/visually-hidden.tsx
@@ -1,7 +1,7 @@
import type { ComponentPropsWithRef, JSX } from 'react';
import { Text as RacText } from 'react-aria-components/Text';
+import { cx } from '../../shared/utils/utils.js';
import type { Prettify } from '../types/prettify.js';
-import { cx } from '../utils/utils.js';
import { visuallyHiddenRecipe } from './recipe.css.js';
type _VisuallyHiddenProps = ComponentPropsWithRef;
diff --git a/packages/@luke-ui/react/src/em/index.ts b/packages/@luke-ui/react/src/em/index.ts
deleted file mode 100644
index 8f6e5d70..00000000
--- a/packages/@luke-ui/react/src/em/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Em, type EmProps } from './em.js';
diff --git a/packages/@luke-ui/react/src/emoji/index.ts b/packages/@luke-ui/react/src/emoji/index.ts
deleted file mode 100644
index 4ceb9217..00000000
--- a/packages/@luke-ui/react/src/emoji/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Emoji, type EmojiProps } from './emoji.js';
diff --git a/packages/@luke-ui/react/src/exports/blockquote.ts b/packages/@luke-ui/react/src/exports/blockquote.ts
new file mode 100644
index 00000000..f68fe19c
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/blockquote.ts
@@ -0,0 +1,2 @@
+export { Blockquote, type BlockquoteProps } from '../core/blockquote/blockquote.js';
+export { type BlockquoteRecipeVariants, blockquoteRecipe } from '../core/blockquote/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/exports/box.ts b/packages/@luke-ui/react/src/exports/box.ts
new file mode 100644
index 00000000..d4912d34
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/box.ts
@@ -0,0 +1 @@
+export { Box, type BoxProps } from '../core/box/box.js';
diff --git a/packages/@luke-ui/react/src/exports/button.ts b/packages/@luke-ui/react/src/exports/button.ts
new file mode 100644
index 00000000..cf149aca
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/button.ts
@@ -0,0 +1,2 @@
+export { type ButtonRecipeVariants, buttonRecipe } from '../core/primitives/button/recipe.css.js';
+export { Button, type ButtonProps } from '../core/button/button.js';
diff --git a/packages/@luke-ui/react/src/exports/checkbox.ts b/packages/@luke-ui/react/src/exports/checkbox.ts
new file mode 100644
index 00000000..f61c4edd
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/checkbox.ts
@@ -0,0 +1,5 @@
+export {
+ type CheckboxRecipeVariants,
+ checkboxRecipe,
+} from '../core/primitives/checkbox/recipe.css.js';
+export { Checkbox, type CheckboxProps } from '../core/checkbox/checkbox.js';
diff --git a/packages/@luke-ui/react/src/exports/code.ts b/packages/@luke-ui/react/src/exports/code.ts
new file mode 100644
index 00000000..55f1ef9a
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/code.ts
@@ -0,0 +1,2 @@
+export { Code, type CodeProps } from '../core/code/code.js';
+export { type CodeRecipeVariants, codeRecipe } from '../core/code/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/exports/combobox-field.ts b/packages/@luke-ui/react/src/exports/combobox-field.ts
new file mode 100644
index 00000000..0aa252b3
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/combobox-field.ts
@@ -0,0 +1 @@
+export { ComboboxField, type ComboboxFieldProps } from '../core/combobox-field/combobox-field.js';
diff --git a/packages/@luke-ui/react/src/exports/em.ts b/packages/@luke-ui/react/src/exports/em.ts
new file mode 100644
index 00000000..8456b684
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/em.ts
@@ -0,0 +1 @@
+export { Em, type EmProps } from '../core/em/em.js';
diff --git a/packages/@luke-ui/react/src/exports/emoji.ts b/packages/@luke-ui/react/src/exports/emoji.ts
new file mode 100644
index 00000000..dd700c1b
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/emoji.ts
@@ -0,0 +1 @@
+export { Emoji, type EmojiProps } from '../core/emoji/emoji.js';
diff --git a/packages/@luke-ui/react/src/exports/heading.ts b/packages/@luke-ui/react/src/exports/heading.ts
new file mode 100644
index 00000000..13b861cc
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/heading.ts
@@ -0,0 +1,12 @@
+export {
+ Heading,
+ type HeadingLevel,
+ HeadingLevels,
+ type HeadingProps,
+ type HeadingTag,
+ useHeadingLevel,
+} from '../core/heading/heading.js';
+export type {
+ HeadingLevelsProps,
+ HeadingLevelsRenderProps,
+} from '../core/heading/heading-context.js';
diff --git a/packages/@luke-ui/react/src/exports/icon-button.ts b/packages/@luke-ui/react/src/exports/icon-button.ts
new file mode 100644
index 00000000..c8406e4e
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/icon-button.ts
@@ -0,0 +1,2 @@
+export { IconButton, type IconButtonProps } from '../core/icon-button/icon-button.js';
+export { type IconButtonRecipeVariants, iconButtonRecipe } from '../core/icon-button/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/icon/index.ts b/packages/@luke-ui/react/src/exports/icon.ts
similarity index 65%
rename from packages/@luke-ui/react/src/icon/index.ts
rename to packages/@luke-ui/react/src/exports/icon.ts
index 2ffeb606..7041c70b 100644
--- a/packages/@luke-ui/react/src/icon/index.ts
+++ b/packages/@luke-ui/react/src/exports/icon.ts
@@ -10,5 +10,5 @@ export {
type IconSpritesheetProviderProps,
iconNames,
iconViewBoxes,
-} from './icon.js';
-export { type IconRecipeVariants, iconRecipe } from './recipe.css.js';
+} from '../core/icon/icon.js';
+export { type IconRecipeVariants, iconRecipe } from '../core/icon/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/exports/kbd.ts b/packages/@luke-ui/react/src/exports/kbd.ts
new file mode 100644
index 00000000..74cfb88e
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/kbd.ts
@@ -0,0 +1,2 @@
+export { Kbd, type KbdProps } from '../core/kbd/kbd.js';
+export { type KbdRecipeVariants, kbdRecipe } from '../core/kbd/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/exports/link.ts b/packages/@luke-ui/react/src/exports/link.ts
new file mode 100644
index 00000000..3f1b0ddf
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/link.ts
@@ -0,0 +1,2 @@
+export { Link, type LinkProps } from '../core/link/link.js';
+export { type LinkRecipeVariants, linkRecipe } from '../core/link/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/loading-skeleton/index.ts b/packages/@luke-ui/react/src/exports/loading-skeleton.ts
similarity index 68%
rename from packages/@luke-ui/react/src/loading-skeleton/index.ts
rename to packages/@luke-ui/react/src/exports/loading-skeleton.ts
index d117ff93..e71ae414 100644
--- a/packages/@luke-ui/react/src/loading-skeleton/index.ts
+++ b/packages/@luke-ui/react/src/exports/loading-skeleton.ts
@@ -3,4 +3,4 @@ export {
type LoadingSkeletonProps,
LoadingSkeletonProvider,
type LoadingSkeletonProviderProps,
-} from './loading-skeleton.js';
+} from '../core/loading-skeleton/loading-skeleton.js';
diff --git a/packages/@luke-ui/react/src/exports/loading-spinner.ts b/packages/@luke-ui/react/src/exports/loading-spinner.ts
new file mode 100644
index 00000000..aa3af4a0
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/loading-spinner.ts
@@ -0,0 +1,8 @@
+export {
+ LoadingSpinner,
+ type LoadingSpinnerProps,
+} from '../core/loading-spinner/loading-spinner.js';
+export {
+ type LoadingSpinnerRecipeVariants,
+ loadingSpinnerRecipe,
+} from '../core/loading-spinner/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/numeral/index.ts b/packages/@luke-ui/react/src/exports/numeral.ts
similarity index 75%
rename from packages/@luke-ui/react/src/numeral/index.ts
rename to packages/@luke-ui/react/src/exports/numeral.ts
index 25a589f3..d3efe12f 100644
--- a/packages/@luke-ui/react/src/numeral/index.ts
+++ b/packages/@luke-ui/react/src/exports/numeral.ts
@@ -4,4 +4,4 @@ export {
type NumeralFormat,
type NumeralPrecision,
type NumeralProps,
-} from './numeral.js';
+} from '../core/numeral/numeral.js';
diff --git a/packages/@luke-ui/react/src/exports/primitives/button.ts b/packages/@luke-ui/react/src/exports/primitives/button.ts
new file mode 100644
index 00000000..bceea966
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/primitives/button.ts
@@ -0,0 +1,5 @@
+export { Button, type ButtonProps } from '../../core/primitives/button/button.js';
+export {
+ type ButtonRecipeVariants,
+ buttonRecipe,
+} from '../../core/primitives/button/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/primitives/checkbox/index.ts b/packages/@luke-ui/react/src/exports/primitives/checkbox.ts
similarity index 52%
rename from packages/@luke-ui/react/src/primitives/checkbox/index.ts
rename to packages/@luke-ui/react/src/exports/primitives/checkbox.ts
index ede3d0a0..03aaa1b2 100644
--- a/packages/@luke-ui/react/src/primitives/checkbox/index.ts
+++ b/packages/@luke-ui/react/src/exports/primitives/checkbox.ts
@@ -7,5 +7,8 @@ export {
CheckboxIndicator,
type CheckboxIndicatorProps,
type CheckboxProps,
-} from './checkbox.js';
-export { type CheckboxRecipeVariants, checkboxRecipe } from './recipe.css.js';
+} from '../../core/primitives/checkbox/checkbox.js';
+export {
+ type CheckboxRecipeVariants,
+ checkboxRecipe,
+} from '../../core/primitives/checkbox/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/exports/primitives/combobox.ts b/packages/@luke-ui/react/src/exports/primitives/combobox.ts
new file mode 100644
index 00000000..0e196577
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/primitives/combobox.ts
@@ -0,0 +1,23 @@
+export type { ComboboxClearButtonProps } from '../../core/primitives/combobox/clear-button.js';
+export { ComboboxClearButton } from '../../core/primitives/combobox/clear-button.js';
+export type { ComboboxEmptyStateProps } from '../../core/primitives/combobox/empty-state.js';
+export { ComboboxEmptyState } from '../../core/primitives/combobox/empty-state.js';
+export type { ComboboxInputProps } from '../../core/primitives/combobox/input.js';
+export { ComboboxInput } from '../../core/primitives/combobox/input.js';
+export type { ComboboxInputGroupProps } from '../../core/primitives/combobox/input-group.js';
+export { ComboboxInputGroup } from '../../core/primitives/combobox/input-group.js';
+export type {
+ ComboboxItemProps,
+ ComboboxLoadMoreItemProps,
+} from '../../core/primitives/combobox/item.js';
+export { ComboboxItem, ComboboxLoadMoreItem } from '../../core/primitives/combobox/item.js';
+export type { ComboboxListBoxProps } from '../../core/primitives/combobox/listbox.js';
+export { ComboboxListBox } from '../../core/primitives/combobox/listbox.js';
+export type { ComboboxPopoverProps } from '../../core/primitives/combobox/popover.js';
+export { ComboboxPopover } from '../../core/primitives/combobox/popover.js';
+export type { ComboboxRootProps, ComboboxSize } from '../../core/primitives/combobox/root.js';
+export { ComboboxRoot } from '../../core/primitives/combobox/root.js';
+export type { ComboboxSectionProps } from '../../core/primitives/combobox/section.js';
+export { ComboboxSection } from '../../core/primitives/combobox/section.js';
+export type { ComboboxTriggerProps } from '../../core/primitives/combobox/trigger.js';
+export { ComboboxTrigger } from '../../core/primitives/combobox/trigger.js';
diff --git a/packages/@luke-ui/react/src/primitives/field/index.ts b/packages/@luke-ui/react/src/exports/primitives/field.ts
similarity index 56%
rename from packages/@luke-ui/react/src/primitives/field/index.ts
rename to packages/@luke-ui/react/src/exports/primitives/field.ts
index 9fd8ac64..cd8e99fc 100644
--- a/packages/@luke-ui/react/src/primitives/field/index.ts
+++ b/packages/@luke-ui/react/src/exports/primitives/field.ts
@@ -8,5 +8,5 @@ export {
type FieldLabelProps,
type FieldNecessityIndicator,
type FieldProps,
-} from './field.js';
-export { type FieldRecipeVariants, fieldRecipe } from './recipe.css.js';
+} from '../../core/primitives/field/field.js';
+export { type FieldRecipeVariants, fieldRecipe } from '../../core/primitives/field/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/primitives/input-group/index.ts b/packages/@luke-ui/react/src/exports/primitives/input-group.ts
similarity index 69%
rename from packages/@luke-ui/react/src/primitives/input-group/index.ts
rename to packages/@luke-ui/react/src/exports/primitives/input-group.ts
index 3e9d7a59..2a77b574 100644
--- a/packages/@luke-ui/react/src/primitives/input-group/index.ts
+++ b/packages/@luke-ui/react/src/exports/primitives/input-group.ts
@@ -7,9 +7,9 @@ export {
type InputGroupProps,
InputGroupSuffix,
type InputGroupSuffixProps,
-} from './input-group.js';
+} from '../../core/primitives/input-group/input-group.js';
export {
type InputGroupRecipeVariants,
type InputGroupSize,
inputGroupRecipe,
-} from './recipe.css.js';
+} from '../../core/primitives/input-group/recipe.css.js';
diff --git a/packages/@luke-ui/react/src/exports/quote.ts b/packages/@luke-ui/react/src/exports/quote.ts
new file mode 100644
index 00000000..c5f01da7
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/quote.ts
@@ -0,0 +1 @@
+export { Quote, type QuoteProps } from '../core/quote/quote.js';
diff --git a/packages/@luke-ui/react/src/exports/strong.ts b/packages/@luke-ui/react/src/exports/strong.ts
new file mode 100644
index 00000000..b5364606
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/strong.ts
@@ -0,0 +1 @@
+export { Strong, type StrongProps } from '../core/strong/strong.js';
diff --git a/packages/@luke-ui/react/src/exports/styles.ts b/packages/@luke-ui/react/src/exports/styles.ts
new file mode 100644
index 00000000..dc7d1e81
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/styles.ts
@@ -0,0 +1,2 @@
+export type { SprinklesProps } from '../core/styles/utilities.css.js';
+export { createSprinkles } from '../core/styles/utilities.css.js';
diff --git a/packages/@luke-ui/react/src/exports/text-field.ts b/packages/@luke-ui/react/src/exports/text-field.ts
new file mode 100644
index 00000000..9c5ed61b
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/text-field.ts
@@ -0,0 +1 @@
+export { TextField, type TextFieldProps } from '../core/text-field/text-field.js';
diff --git a/packages/@luke-ui/react/src/exports/text.ts b/packages/@luke-ui/react/src/exports/text.ts
new file mode 100644
index 00000000..4f868a52
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/text.ts
@@ -0,0 +1,2 @@
+export { type TextRecipeVariants, textRecipe } from '../core/text/recipe.css.js';
+export { Text, type TextProps } from '../core/text/text.js';
diff --git a/packages/@luke-ui/react/src/exports/theme.ts b/packages/@luke-ui/react/src/exports/theme.ts
new file mode 100644
index 00000000..a14d2c2d
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/theme.ts
@@ -0,0 +1 @@
+export * from '../theme/index.js';
diff --git a/packages/@luke-ui/react/src/exports/themes/paper.ts b/packages/@luke-ui/react/src/exports/themes/paper.ts
new file mode 100644
index 00000000..0bf587cc
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/themes/paper.ts
@@ -0,0 +1 @@
+export * from '../../theme/bundles/paper/index.js';
diff --git a/packages/@luke-ui/react/src/exports/themes/tactile.ts b/packages/@luke-ui/react/src/exports/themes/tactile.ts
new file mode 100644
index 00000000..c8fcdda7
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/themes/tactile.ts
@@ -0,0 +1 @@
+export * from '../../theme/bundles/tactile/index.js';
diff --git a/packages/@luke-ui/react/src/exports/utils.ts b/packages/@luke-ui/react/src/exports/utils.ts
new file mode 100644
index 00000000..053842ef
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/utils.ts
@@ -0,0 +1 @@
+export * from '../shared/utils/utils.js';
diff --git a/packages/@luke-ui/react/src/exports/visually-hidden.ts b/packages/@luke-ui/react/src/exports/visually-hidden.ts
new file mode 100644
index 00000000..b76b9099
--- /dev/null
+++ b/packages/@luke-ui/react/src/exports/visually-hidden.ts
@@ -0,0 +1,5 @@
+export { visuallyHiddenRecipe } from '../core/visually-hidden/recipe.css.js';
+export {
+ VisuallyHidden,
+ type VisuallyHiddenProps,
+} from '../core/visually-hidden/visually-hidden.js';
diff --git a/packages/@luke-ui/react/src/heading/index.ts b/packages/@luke-ui/react/src/heading/index.ts
deleted file mode 100644
index 71885e3a..00000000
--- a/packages/@luke-ui/react/src/heading/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export {
- Heading,
- type HeadingLevel,
- HeadingLevels,
- type HeadingProps,
- type HeadingTag,
- useHeadingLevel,
-} from './heading.js';
-export type { HeadingLevelsProps, HeadingLevelsRenderProps } from './heading-context.js';
diff --git a/packages/@luke-ui/react/src/icon-button/index.ts b/packages/@luke-ui/react/src/icon-button/index.ts
deleted file mode 100644
index d51997aa..00000000
--- a/packages/@luke-ui/react/src/icon-button/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { IconButton, type IconButtonProps } from './icon-button.js';
-export { type IconButtonRecipeVariants, iconButtonRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/kbd/index.ts b/packages/@luke-ui/react/src/kbd/index.ts
deleted file mode 100644
index f467185e..00000000
--- a/packages/@luke-ui/react/src/kbd/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { Kbd, type KbdProps } from './kbd.js';
-export { type KbdRecipeVariants, kbdRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/link/index.ts b/packages/@luke-ui/react/src/link/index.ts
deleted file mode 100644
index fd4b1850..00000000
--- a/packages/@luke-ui/react/src/link/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { Link, type LinkProps } from './link.js';
-export { type LinkRecipeVariants, linkRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/loading-spinner/index.ts b/packages/@luke-ui/react/src/loading-spinner/index.ts
deleted file mode 100644
index a754ab79..00000000
--- a/packages/@luke-ui/react/src/loading-spinner/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { LoadingSpinner, type LoadingSpinnerProps } from './loading-spinner.js';
-export { type LoadingSpinnerRecipeVariants, loadingSpinnerRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/module-boundaries.test.ts b/packages/@luke-ui/react/src/module-boundaries.test.ts
new file mode 100644
index 00000000..17b825d9
--- /dev/null
+++ b/packages/@luke-ui/react/src/module-boundaries.test.ts
@@ -0,0 +1,203 @@
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { access, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises';
+import { afterEach, expect, test } from 'vite-plus/test';
+
+const sourceRoot = fileURLToPath(new URL('./', import.meta.url));
+const sourceZones = new Set(['core', 'exports', 'shared', 'theme']);
+const sourceFilePattern = /\.(?:ts|tsx)$/;
+const excludedSourcePattern = /(?:\.test\.|\.stories\.|__fixtures__)/;
+const relativeImportPattern = /(?:from\s+|import\s+)(['"])(\.\.?\/[^'"]+)\1/g;
+
+type SourceZone = 'core' | 'exports' | 'shared' | 'theme';
+
+// The zones a source zone may import from. A missing key is a compile error, not a runtime
+// `undefined`, so a new zone cannot silently bypass enforcement.
+const MAY_IMPORT: Record> = {
+ core: ['theme', 'shared'],
+ exports: ['core', 'theme', 'shared'],
+ shared: [],
+ theme: ['shared'],
+};
+
+const testDirectories: Array = [];
+
+afterEach(async () => {
+ await Promise.all(
+ testDirectories.map((directory) => rm(directory, { force: true, recursive: true })),
+ );
+ testDirectories.length = 0;
+});
+
+test('keeps source imports flowing between core, exports, shared, and theme', async () => {
+ const sourceFiles = await collectSourceFiles(sourceRoot);
+ const violations = (
+ await Promise.all(
+ sourceFiles.map((sourceFile) => findBoundaryViolations(sourceRoot, sourceFile)),
+ )
+ ).flat();
+
+ expect(violations).toEqual([]);
+});
+
+// The dependency graph, driven against synthetic zone pairs rather than files on disk, so every
+// forbidden edge is proven to fail if `isForbiddenEdge` stopped reporting it, and every allowed
+// edge is proven not to false-positive. Exhaustive over all 16 zone pairs so no pair can be
+// silently omitted in future.
+test.for([
+ ['shared', 'core'],
+ ['shared', 'theme'],
+ ['shared', 'exports'],
+ ['core', 'exports'],
+ ['theme', 'core'],
+ ['theme', 'exports'],
+] as const)('reports %s -> %s as a violation', ([sourceZone, targetZone]) => {
+ expect(isForbiddenEdge(sourceZone, targetZone)).toBe(true);
+});
+
+test.for([
+ ['exports', 'core'],
+ ['exports', 'theme'],
+ ['exports', 'shared'],
+ ['core', 'shared'],
+ ['core', 'theme'],
+ ['theme', 'shared'],
+] as const)('allows %s -> %s', ([sourceZone, targetZone]) => {
+ expect(isForbiddenEdge(sourceZone, targetZone)).toBe(false);
+});
+
+test.for([
+ ['core', 'core'],
+ ['exports', 'exports'],
+ ['shared', 'shared'],
+ ['theme', 'theme'],
+] as const)('allows %s -> %s (same zone)', ([sourceZone, targetZone]) => {
+ expect(isForbiddenEdge(sourceZone, targetZone)).toBe(false);
+});
+
+// The synthetic cases above call `isForbiddenEdge` directly with string literals, so they cannot
+// catch a regression in the code that actually walks the tree. Drive the real scanning path
+// (`collectSourceFiles`, `resolveRelativeImport`, `sourceZoneFor`) against an on-disk fixture so a
+// broken zone classification (for example `sourceZoneFor` no longer recognising `theme`) fails this
+// test instead of silently reporting zero violations everywhere.
+test('detects a real on-disk forbidden import via the file-walking scanner', async () => {
+ const root = await createFixtureRoot({
+ 'theme/offender.ts': "import { helper } from '../exports/helper.js';\n",
+ 'exports/helper.ts': 'export const helper = 1;\n',
+ });
+
+ const sourceFiles = await collectSourceFiles(root);
+ const violations = (
+ await Promise.all(sourceFiles.map((sourceFile) => findBoundaryViolations(root, sourceFile)))
+ ).flat();
+
+ expect(violations).toEqual([`theme/offender.ts -> ../exports/helper.js`]);
+});
+
+test('reports no violations for a real on-disk allowed import via the file-walking scanner', async () => {
+ const root = await createFixtureRoot({
+ 'theme/consumer.ts': "import { helper } from '../shared/helper.js';\n",
+ 'shared/helper.ts': 'export const helper = 1;\n',
+ });
+
+ const sourceFiles = await collectSourceFiles(root);
+ const violations = (
+ await Promise.all(sourceFiles.map((sourceFile) => findBoundaryViolations(root, sourceFile)))
+ ).flat();
+
+ expect(violations).toEqual([]);
+});
+
+async function createFixtureRoot(files: Record): Promise {
+ const root = await mkdtemp(path.join(tmpdir(), 'module-boundaries-'));
+ testDirectories.push(root);
+
+ await Promise.all(
+ Object.entries(files).map(async ([relativePath, contents]) => {
+ const filePath = path.join(root, relativePath);
+ await mkdir(path.dirname(filePath), { recursive: true });
+ await writeFile(filePath, contents, 'utf8');
+ }),
+ );
+
+ return root;
+}
+
+/**
+ * Whether an import from `sourceZone` to `targetZone` breaks the dependency graph. See
+ * {@link MAY_IMPORT} for the allow-list each zone is checked against.
+ */
+function isForbiddenEdge(sourceZone: SourceZone, targetZone: SourceZone): boolean {
+ return sourceZone !== targetZone && !MAY_IMPORT[sourceZone].includes(targetZone);
+}
+
+async function findBoundaryViolations(root: string, sourceFile: string): Promise> {
+ const sourceZone = sourceZoneFor(root, sourceFile);
+ if (sourceZone === undefined) return [];
+
+ const source = await readFile(sourceFile, 'utf8');
+ const imports = [...source.matchAll(relativeImportPattern)].flatMap((match) => {
+ const specifier = match[2];
+ return specifier === undefined ? [] : [specifier];
+ });
+ const targetFiles = await Promise.all(
+ imports.map((specifier) => resolveRelativeImport(root, sourceFile, specifier)),
+ );
+
+ return targetFiles.flatMap((targetFile, index) => {
+ const targetZone = sourceZoneFor(root, targetFile);
+ if (targetZone === undefined) return [];
+
+ const specifier = imports[index];
+ if (specifier === undefined) return [];
+ if (!isForbiddenEdge(sourceZone, targetZone)) return [];
+
+ return [`${path.relative(root, sourceFile)} -> ${specifier}`];
+ });
+}
+
+async function collectSourceFiles(directory: string): Promise> {
+ const entries = await readdir(directory, { withFileTypes: true });
+ const files = await Promise.all(
+ entries.map(async (entry) => {
+ const entryPath = path.join(directory, entry.name);
+ if (entry.isDirectory()) return collectSourceFiles(entryPath);
+ if (!sourceFilePattern.test(entry.name) || excludedSourcePattern.test(entryPath)) return [];
+ return [entryPath];
+ }),
+ );
+
+ return files.flat();
+}
+
+async function resolveRelativeImport(
+ root: string,
+ sourceFile: string,
+ specifier: string,
+): Promise {
+ const target = path.resolve(path.dirname(sourceFile), specifier.split(/[?#]/)[0] ?? specifier);
+ if (!target.startsWith(`${root}${path.sep}`)) return target;
+
+ const candidates = [target, target.replace(/\.js$/, '.ts'), target.replace(/\.js$/, '.tsx')];
+
+ const resolvedCandidates = await Promise.all(
+ candidates.map(async (candidate) => {
+ try {
+ await access(candidate);
+ return candidate;
+ } catch {
+ return undefined;
+ }
+ }),
+ );
+ const resolved = resolvedCandidates.find((candidate) => candidate !== undefined);
+ if (resolved !== undefined) return resolved;
+
+ throw new Error(`Could not resolve ${specifier} from ${path.relative(root, sourceFile)}.`);
+}
+
+function sourceZoneFor(root: string, filePath: string): SourceZone | undefined {
+ const [zone] = path.relative(root, filePath).split(path.sep);
+ return zone !== undefined && sourceZones.has(zone) ? (zone as SourceZone) : undefined;
+}
diff --git a/packages/@luke-ui/react/src/primitives/button/index.ts b/packages/@luke-ui/react/src/primitives/button/index.ts
deleted file mode 100644
index fcc5457f..00000000
--- a/packages/@luke-ui/react/src/primitives/button/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { Button, type ButtonProps } from './button.js';
-export { type ButtonRecipeVariants, buttonRecipe } from './recipe.css.js';
diff --git a/packages/@luke-ui/react/src/primitives/combobox/index.ts b/packages/@luke-ui/react/src/primitives/combobox/index.ts
deleted file mode 100644
index e8bf209a..00000000
--- a/packages/@luke-ui/react/src/primitives/combobox/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export type { ComboboxClearButtonProps } from './clear-button.js';
-export { ComboboxClearButton } from './clear-button.js';
-export type { ComboboxEmptyStateProps } from './empty-state.js';
-export { ComboboxEmptyState } from './empty-state.js';
-export type { ComboboxInputProps } from './input.js';
-export { ComboboxInput } from './input.js';
-export type { ComboboxInputGroupProps } from './input-group.js';
-export { ComboboxInputGroup } from './input-group.js';
-export type { ComboboxItemProps, ComboboxLoadMoreItemProps } from './item.js';
-export { ComboboxItem, ComboboxLoadMoreItem } from './item.js';
-export type { ComboboxListBoxProps } from './listbox.js';
-export { ComboboxListBox } from './listbox.js';
-export type { ComboboxPopoverProps } from './popover.js';
-export { ComboboxPopover } from './popover.js';
-export type { ComboboxRootProps, ComboboxSize } from './root.js';
-export { ComboboxRoot } from './root.js';
-export type { ComboboxSectionProps } from './section.js';
-export { ComboboxSection } from './section.js';
-export type { ComboboxTriggerProps } from './trigger.js';
-export { ComboboxTrigger } from './trigger.js';
diff --git a/packages/@luke-ui/react/src/quote/index.ts b/packages/@luke-ui/react/src/quote/index.ts
deleted file mode 100644
index 99f84939..00000000
--- a/packages/@luke-ui/react/src/quote/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Quote, type QuoteProps } from './quote.js';
diff --git a/packages/@luke-ui/react/src/shared/class-names.ts b/packages/@luke-ui/react/src/shared/class-names.ts
new file mode 100644
index 00000000..b23e4c59
--- /dev/null
+++ b/packages/@luke-ui/react/src/shared/class-names.ts
@@ -0,0 +1,5 @@
+/** The class-name constants both `core` and `theme` must agree on. */
+export const lukeUiClassNames = {
+ resetRoot: 'luke-ui-reset',
+ themeRoot: 'luke-ui-theme',
+} as const;
diff --git a/packages/@luke-ui/react/src/utils/utils.ts b/packages/@luke-ui/react/src/shared/utils/utils.ts
similarity index 100%
rename from packages/@luke-ui/react/src/utils/utils.ts
rename to packages/@luke-ui/react/src/shared/utils/utils.ts
diff --git a/packages/@luke-ui/react/src/strong/index.ts b/packages/@luke-ui/react/src/strong/index.ts
deleted file mode 100644
index fa0dee63..00000000
--- a/packages/@luke-ui/react/src/strong/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Strong, type StrongProps } from './strong.js';
diff --git a/packages/@luke-ui/react/src/styles/index.ts b/packages/@luke-ui/react/src/styles/index.ts
deleted file mode 100644
index 428a2efa..00000000
--- a/packages/@luke-ui/react/src/styles/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export type { SprinklesProps } from './utilities.css.js';
-export { createSprinkles } from './utilities.css.js';
diff --git a/packages/@luke-ui/react/src/text-field/index.ts b/packages/@luke-ui/react/src/text-field/index.ts
deleted file mode 100644
index 80e7dede..00000000
--- a/packages/@luke-ui/react/src/text-field/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { TextField, type TextFieldProps } from './text-field.js';
diff --git a/packages/@luke-ui/react/src/text/index.ts b/packages/@luke-ui/react/src/text/index.ts
deleted file mode 100644
index 108b0153..00000000
--- a/packages/@luke-ui/react/src/text/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { type TextRecipeVariants, textRecipe } from './recipe.css.js';
-export { Text, type TextProps } from './text.js';
diff --git a/packages/@luke-ui/react/src/themes/paper/index.ts b/packages/@luke-ui/react/src/theme/bundles/paper/index.ts
similarity index 100%
rename from packages/@luke-ui/react/src/themes/paper/index.ts
rename to packages/@luke-ui/react/src/theme/bundles/paper/index.ts
diff --git a/packages/@luke-ui/react/src/themes/paper/theme-class-name.ts b/packages/@luke-ui/react/src/theme/bundles/paper/theme-class-name.ts
similarity index 84%
rename from packages/@luke-ui/react/src/themes/paper/theme-class-name.ts
rename to packages/@luke-ui/react/src/theme/bundles/paper/theme-class-name.ts
index 69fd2c2f..52e76c4e 100644
--- a/packages/@luke-ui/react/src/themes/paper/theme-class-name.ts
+++ b/packages/@luke-ui/react/src/theme/bundles/paper/theme-class-name.ts
@@ -1,6 +1,6 @@
/** Kept apart from `./index.ts` so the class never depends on the foundation. */
-import { getThemeClassName } from '../../theme/theme-class-name.js';
+import { getThemeClassName } from '../../theme-class-name.js';
const PAPER_THEME_NAME = 'paper';
diff --git a/packages/@luke-ui/react/src/themes/paper/theme.ts b/packages/@luke-ui/react/src/theme/bundles/paper/theme.ts
similarity index 65%
rename from packages/@luke-ui/react/src/themes/paper/theme.ts
rename to packages/@luke-ui/react/src/theme/bundles/paper/theme.ts
index 20b2a2a6..63b70e4b 100644
--- a/packages/@luke-ui/react/src/themes/paper/theme.ts
+++ b/packages/@luke-ui/react/src/theme/bundles/paper/theme.ts
@@ -1,5 +1,5 @@
-import type { ThemeInput } from '../../theme/define-theme.js';
-import { paperTheme } from '../../theme/foundations/paper.js';
+import type { ThemeInput } from '../../define-theme.js';
+import { paperTheme } from '../../foundations/paper.js';
/**
* Paper's `defineTheme` input, the materially minimal bundled theme: a flat, hairline-bordered
diff --git a/packages/@luke-ui/react/src/themes/tactile/index.ts b/packages/@luke-ui/react/src/theme/bundles/tactile/index.ts
similarity index 100%
rename from packages/@luke-ui/react/src/themes/tactile/index.ts
rename to packages/@luke-ui/react/src/theme/bundles/tactile/index.ts
diff --git a/packages/@luke-ui/react/src/themes/tactile/theme-class-name.ts b/packages/@luke-ui/react/src/theme/bundles/tactile/theme-class-name.ts
similarity index 84%
rename from packages/@luke-ui/react/src/themes/tactile/theme-class-name.ts
rename to packages/@luke-ui/react/src/theme/bundles/tactile/theme-class-name.ts
index a8fe6704..c0e74aee 100644
--- a/packages/@luke-ui/react/src/themes/tactile/theme-class-name.ts
+++ b/packages/@luke-ui/react/src/theme/bundles/tactile/theme-class-name.ts
@@ -1,6 +1,6 @@
/** Kept apart from `./index.ts` so the class never depends on the foundation. */
-import { getThemeClassName } from '../../theme/theme-class-name.js';
+import { getThemeClassName } from '../../theme-class-name.js';
const TACTILE_THEME_NAME = 'tactile';
diff --git a/packages/@luke-ui/react/src/themes/tactile/theme.ts b/packages/@luke-ui/react/src/theme/bundles/tactile/theme.ts
similarity index 66%
rename from packages/@luke-ui/react/src/themes/tactile/theme.ts
rename to packages/@luke-ui/react/src/theme/bundles/tactile/theme.ts
index 74a4bbaf..02bf08e0 100644
--- a/packages/@luke-ui/react/src/themes/tactile/theme.ts
+++ b/packages/@luke-ui/react/src/theme/bundles/tactile/theme.ts
@@ -1,5 +1,5 @@
-import type { ThemeInput } from '../../theme/define-theme.js';
-import { tactileTheme } from '../../theme/foundations/tactile.js';
+import type { ThemeInput } from '../../define-theme.js';
+import { tactileTheme } from '../../foundations/tactile.js';
/**
* Tactile's `defineTheme` input, the Luke UI default: a teal accent, a neutral near-white light
diff --git a/packages/@luke-ui/react/src/themes/theme-bundle.test.ts b/packages/@luke-ui/react/src/theme/bundles/theme-bundle.test.ts
similarity index 97%
rename from packages/@luke-ui/react/src/themes/theme-bundle.test.ts
rename to packages/@luke-ui/react/src/theme/bundles/theme-bundle.test.ts
index a00a8440..1fa9c014 100644
--- a/packages/@luke-ui/react/src/themes/theme-bundle.test.ts
+++ b/packages/@luke-ui/react/src/theme/bundles/theme-bundle.test.ts
@@ -1,5 +1,5 @@
/**
- * Bundles a one-line entry against the built `dist/themes//index.js`, so it measures what a
+ * Bundles a one-line entry against the built `dist/themes/.js`, so it measures what a
* consumer's bundler keeps. The identity class must not derive from `Theme.name`, which would
* make the foundation a prerequisite of the class string. A multi-layer CSS value must stay a
* concatenated literal, not `[...].join(', ')`, because a joined value survives dead-code
@@ -48,7 +48,7 @@ async function runThemeExportBuild(
themeName: keyof typeof FOUNDATION_MARKERS,
exportName: 'theme' | 'themeClassName',
): Promise {
- const entrypoint = new URL(`../../dist/themes/${themeName}/index.js`, import.meta.url);
+ const entrypoint = new URL(`../../../dist/themes/${themeName}.js`, import.meta.url);
const source = `export { ${exportName} } from ${JSON.stringify(fileURLToPath(entrypoint))};`;
const result = await build({
build: {
diff --git a/packages/@luke-ui/react/src/themes/themes.test.ts b/packages/@luke-ui/react/src/theme/bundles/themes.test.ts
similarity index 82%
rename from packages/@luke-ui/react/src/themes/themes.test.ts
rename to packages/@luke-ui/react/src/theme/bundles/themes.test.ts
index e35af80e..506db912 100644
--- a/packages/@luke-ui/react/src/themes/themes.test.ts
+++ b/packages/@luke-ui/react/src/theme/bundles/themes.test.ts
@@ -1,7 +1,7 @@
import { readFile } from 'node:fs/promises';
import { describe, expect, it } from 'vite-plus/test';
-import packageJson from '../../package.json' with { type: 'json' };
-import { getThemeClassName } from '../theme/theme-class-name.js';
+import packageJson from '../../../package.json' with { type: 'json' };
+import { getThemeClassName } from '../theme-class-name.js';
import { themeClassName as paperThemeClassName, theme as paperThemeInput } from './paper/index.js';
import {
themeClassName as tactileThemeClassName,
@@ -9,19 +9,19 @@ import {
} from './tactile/index.js';
const themeArtifacts = {
- paper: new URL('../../dist/themes/paper/stylesheet.css', import.meta.url),
- tactile: new URL('../../dist/themes/tactile/stylesheet.css', import.meta.url),
+ paper: new URL('../../../dist/themes/paper/stylesheet.css', import.meta.url),
+ tactile: new URL('../../../dist/themes/tactile/stylesheet.css', import.meta.url),
} as const;
const themeEntrypoints = {
- paper: new URL('../../dist/themes/paper/index.js', import.meta.url),
- tactile: new URL('../../dist/themes/tactile/index.js', import.meta.url),
+ paper: new URL('../../../dist/themes/paper.js', import.meta.url),
+ tactile: new URL('../../../dist/themes/tactile.js', import.meta.url),
} as const;
describe('bundled theme package exports', () => {
it('publishes a per-theme entrypoint and stylesheet, with no combined barrel', () => {
- expect(packageJson.exports['./themes/tactile']).toBe('./dist/themes/tactile/index.js');
- expect(packageJson.exports['./themes/paper']).toBe('./dist/themes/paper/index.js');
+ expect(packageJson.exports['./themes/tactile']).toBe('./dist/themes/tactile.js');
+ expect(packageJson.exports['./themes/paper']).toBe('./dist/themes/paper.js');
expect(packageJson.exports['./themes/tactile/stylesheet.css']).toBe(
'./dist/themes/tactile/stylesheet.css',
);
diff --git a/packages/@luke-ui/react/src/theme/cascade.browser.test.ts b/packages/@luke-ui/react/src/theme/cascade.browser.test.ts
index 8bbb877a..a45ff42e 100644
--- a/packages/@luke-ui/react/src/theme/cascade.browser.test.ts
+++ b/packages/@luke-ui/react/src/theme/cascade.browser.test.ts
@@ -12,9 +12,9 @@ import { afterEach, beforeEach, describe, expect, it } from 'vite-plus/test';
import { cdp } from 'vite-plus/test/context';
import paperCss from '../../dist/themes/paper/stylesheet.css?inline';
import tactileCss from '../../dist/themes/tactile/stylesheet.css?inline';
-import { themeClassName as paperThemeClassName } from '../themes/paper/index.js';
-import { themeClassName as tactileThemeClassName } from '../themes/tactile/index.js';
import { extractValue, splitBlocks } from './__fixtures__/theme-css.js';
+import { themeClassName as paperThemeClassName } from './bundles/paper/index.js';
+import { themeClassName as tactileThemeClassName } from './bundles/tactile/index.js';
const themeCss = { paper: paperCss, tactile: tactileCss } as const;
type ThemeName = keyof typeof themeCss;
diff --git a/packages/@luke-ui/react/src/theme/define-theme.ts b/packages/@luke-ui/react/src/theme/define-theme.ts
index 1e90e5b3..9cdd5499 100644
--- a/packages/@luke-ui/react/src/theme/define-theme.ts
+++ b/packages/@luke-ui/react/src/theme/define-theme.ts
@@ -6,7 +6,7 @@
* backdrop) into {@link Oklch} values the foundation carries.
*/
-import { typedEntries, typedFromEntries } from '../utils/index.js';
+import { typedEntries, typedFromEntries } from '../shared/utils/utils.js';
import { buildTheme, ThemeContrastError } from './build-theme.js';
import type { Oklch } from './color.js';
import { gamutMapOklch, parseColor } from './color.js';
diff --git a/packages/@luke-ui/react/src/theme/stylesheet.test.ts b/packages/@luke-ui/react/src/theme/stylesheet.test.ts
index cf7d964a..8f95890e 100644
--- a/packages/@luke-ui/react/src/theme/stylesheet.test.ts
+++ b/packages/@luke-ui/react/src/theme/stylesheet.test.ts
@@ -1,6 +1,4 @@
import { describe, expect, it } from 'vite-plus/test';
-import { themeClassName as paperThemeClassName } from '../themes/paper/index.js';
-import { themeClassName as tactileThemeClassName } from '../themes/tactile/index.js';
import {
extractValue,
paperFoundation,
@@ -8,6 +6,8 @@ import {
tactileFoundation,
} from './__fixtures__/theme-css.js';
import { buildTheme } from './build-theme.js';
+import { themeClassName as paperThemeClassName } from './bundles/paper/index.js';
+import { themeClassName as tactileThemeClassName } from './bundles/tactile/index.js';
import {
flattenThemeContract,
partitionContractPairs,
diff --git a/packages/@luke-ui/react/src/theme/theme-diagnostics-inspector.tsx b/packages/@luke-ui/react/src/theme/theme-diagnostics-inspector.tsx
index d1fe411d..08d6d914 100644
--- a/packages/@luke-ui/react/src/theme/theme-diagnostics-inspector.tsx
+++ b/packages/@luke-ui/react/src/theme/theme-diagnostics-inspector.tsx
@@ -1,6 +1,6 @@
import type { CSSProperties, ReactNode } from 'react';
import { useState } from 'react';
-import { typedKeys } from '../utils/index.js';
+import { typedKeys } from '../shared/utils/utils.js';
import { compileTheme } from './build-theme.js';
import type { Oklch } from './color.js';
import { formatOklch } from './color.js';
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 a9f0b4b1..993ea7e2 100644
--- a/packages/@luke-ui/react/src/theme/theme.browser.test.tsx
+++ b/packages/@luke-ui/react/src/theme/theme.browser.test.tsx
@@ -4,11 +4,11 @@ import type { Root } from 'react-dom/client';
import { createRoot } from 'react-dom/client';
import { afterEach, expect, test } from 'vite-plus/test';
import { cdp, page, userEvent } from 'vite-plus/test/context';
-import { Button } from '../button/index.js';
-import { ComboboxField } from '../combobox-field/index.js';
-import { IconSpritesheetProvider } from '../icon/index.js';
-import { ComboboxItem } from '../primitives/combobox/item.js';
-import { themeClassName as tactileThemeClassName } from '../themes/tactile/index.js';
+import { Button } from '../core/button/button.js';
+import { ComboboxField } from '../core/combobox-field/combobox-field.js';
+import { IconSpritesheetProvider } from '../core/icon/icon.js';
+import { ComboboxItem } from '../core/primitives/combobox/item.js';
+import { themeClassName as tactileThemeClassName } from './bundles/tactile/index.js';
const mounted: Array<{ container: HTMLElement; root: Root }> = [];
const scopes: Array = [];
diff --git a/packages/@luke-ui/react/src/theme/theme.ts b/packages/@luke-ui/react/src/theme/theme.ts
index 0177dead..c58c8ad7 100644
--- a/packages/@luke-ui/react/src/theme/theme.ts
+++ b/packages/@luke-ui/react/src/theme/theme.ts
@@ -1,5 +1,5 @@
-import { lukeUiClassNames } from '../styles/class-names.js';
-import { cx } from '../utils/utils.js';
+import { lukeUiClassNames } from '../shared/class-names.js';
+import { cx } from '../shared/utils/utils.js';
/**
* Applies the descendant CSS reset and the base Luke UI typography and theme layer. It carries no
diff --git a/packages/@luke-ui/react/src/theme/token-board.visual.test.tsx b/packages/@luke-ui/react/src/theme/token-board.visual.test.tsx
index 3b28b135..8d489f4d 100644
--- a/packages/@luke-ui/react/src/theme/token-board.visual.test.tsx
+++ b/packages/@luke-ui/react/src/theme/token-board.visual.test.tsx
@@ -1,6 +1,6 @@
import { expect, test } from 'vite-plus/test';
-import { render, visualAppearances } from '../test-utils/render.js';
-import { captureVisualAppearance } from '../test-utils/visual.js';
+import { render, visualAppearances } from '../core/test-utils/render.js';
+import { captureVisualAppearance } from '../core/test-utils/visual.js';
import { TokenBoard } from './token-board.js';
// Captures every contract leaf, colour and non-colour alike, for both bundled themes and
diff --git a/packages/@luke-ui/react/src/utils/index.ts b/packages/@luke-ui/react/src/utils/index.ts
deleted file mode 100644
index 6ffde591..00000000
--- a/packages/@luke-ui/react/src/utils/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export {
- cx,
- mergeProps,
- type ObjectEntry,
- pxToRem,
- typedEntries,
- typedFromEntries,
- typedKeys,
-} from './utils.js';
diff --git a/packages/@luke-ui/react/src/visually-hidden/index.ts b/packages/@luke-ui/react/src/visually-hidden/index.ts
deleted file mode 100644
index 6a6b9b60..00000000
--- a/packages/@luke-ui/react/src/visually-hidden/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { visuallyHiddenRecipe } from './recipe.css.js';
-export { VisuallyHidden, type VisuallyHiddenProps } from './visually-hidden.js';
diff --git a/packages/@luke-ui/react/vite.config.ts b/packages/@luke-ui/react/vite.config.ts
index d073da50..15ec3b99 100644
--- a/packages/@luke-ui/react/vite.config.ts
+++ b/packages/@luke-ui/react/vite.config.ts
@@ -7,7 +7,9 @@ import { readdir, rm } from 'node:fs/promises';
import { defineConfig } from 'vite-plus';
import packageJson from './package.json' with { type: 'json' };
-const recipeEngineSource = fileURLToPath(new URL('./src/styles/recipe-engine.ts', import.meta.url));
+const recipeEngineSource = fileURLToPath(
+ new URL('./src/core/styles/recipe-engine.ts', import.meta.url),
+);
const workspaceRoot = fileURLToPath(new URL('../../../', import.meta.url));
const distDir = fileURLToPath(new URL('dist/', import.meta.url));
const preservedDistFiles = new Set(['spritesheet.svg', 'docs', 'themes']);
@@ -57,10 +59,10 @@ export default defineConfig({
},
dts: true,
entry: {
- stylesheet: 'src/stylesheet.css.ts',
- '*': ['src/*/index.ts'],
- 'primitives/*': ['src/primitives/*/index.ts'],
- 'themes/*': ['src/themes/*/index.ts'],
+ stylesheet: 'src/core/stylesheet.css.ts',
+ '*': ['src/exports/*.ts'],
+ 'primitives/*': ['src/exports/primitives/*.ts'],
+ 'themes/*': ['src/exports/themes/*.ts'],
},
exports: {
customExports: Object.fromEntries(
diff --git a/packages/@luke-ui/react/vitest.config.ts b/packages/@luke-ui/react/vitest.config.ts
index 64da0be8..eb9f4d64 100644
--- a/packages/@luke-ui/react/vitest.config.ts
+++ b/packages/@luke-ui/react/vitest.config.ts
@@ -8,7 +8,9 @@ import { playwright } from 'vite-plus/test/browser-playwright';
const dirname =
typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
const configDir = path.join(dirname, '.storybook');
-const recipeEngineSource = fileURLToPath(new URL('./src/styles/recipe-engine.ts', import.meta.url));
+const recipeEngineSource = fileURLToPath(
+ new URL('./src/core/styles/recipe-engine.ts', import.meta.url),
+);
// This file is copied into a git worktree at an older revision. It cannot import
// TypeScript that does not exist there, so it has no relative imports of the
// visual-regression contract. Keep the capture-dir literals below in sync with
@@ -71,7 +73,7 @@ export default defineConfig({
},
include: ['src/**/*.browser.test.{ts,tsx}'],
name: 'browser',
- setupFiles: ['./src/test-utils/render-setup.ts'],
+ setupFiles: ['./src/core/test-utils/render-setup.ts'],
},
},
{
@@ -146,7 +148,10 @@ export default defineConfig({
},
include: ['src/**/*.visual.test.{ts,tsx}'],
name: 'visual',
- setupFiles: ['./src/test-utils/render-setup.ts', './src/test-utils/visual-setup.ts'],
+ setupFiles: [
+ './src/core/test-utils/render-setup.ts',
+ './src/core/test-utils/visual-setup.ts',
+ ],
},
},
],
diff --git a/packages/turbo-generators/src/apply-component-creation-plan.test.ts b/packages/turbo-generators/src/apply-component-creation-plan.test.ts
index 1d9edf5e..d5cb99ac 100644
--- a/packages/turbo-generators/src/apply-component-creation-plan.test.ts
+++ b/packages/turbo-generators/src/apply-component-creation-plan.test.ts
@@ -23,16 +23,25 @@ describe('createComponent', () => {
await createComponent(root, answers);
await expect(
- readFile(join(root, 'packages/@luke-ui/react/src/status-badge/index.ts'), 'utf8'),
+ readFile(join(root, 'packages/@luke-ui/react/src/exports/status-badge.ts'), 'utf8'),
).resolves.toBe(
[
- "export { StatusBadge, type StatusBadgeProps } from './status-badge.js';",
- "export { statusBadgeRecipe, type StatusBadgeRecipeVariants } from './recipe.css.js';",
+ "export { StatusBadge, type StatusBadgeProps } from '../core/status-badge/status-badge.js';",
+ 'export {',
+ '\ttype StatusBadgeRecipeVariants,',
+ '\tstatusBadgeRecipe,',
+ "} from '../core/status-badge/recipe.css.js';",
'',
].join('\n'),
);
await expect(
- readFile(join(root, 'packages/@luke-ui/react/src/status-badge/status-badge.tsx'), 'utf8'),
+ readFile(join(root, 'packages/@luke-ui/react/src/core/status-badge/index.ts'), 'utf8'),
+ ).rejects.toMatchObject({ code: 'ENOENT' });
+ await expect(
+ readFile(
+ join(root, 'packages/@luke-ui/react/src/core/status-badge/status-badge.tsx'),
+ 'utf8',
+ ),
).resolves.toContain('export function StatusBadge');
await expect(readJson(root, 'apps/docs/content/docs/components/meta.json')).resolves.toEqual({
pages: ['actions', 'feedback'],
@@ -66,7 +75,7 @@ describe('createComponent', () => {
);
await expect(
readFile(
- join(root, 'packages/@luke-ui/react/src/status-badge/component-test-registration.ts'),
+ join(root, 'packages/@luke-ui/react/src/core/status-badge/component-test-registration.ts'),
'utf8',
),
).rejects.toMatchObject({ code: 'ENOENT' });
@@ -110,7 +119,7 @@ describe('createComponent', () => {
});
const browserTest = await readFile(
- join(root, 'packages/@luke-ui/react/src/date-field/date-field.browser.test.tsx'),
+ join(root, 'packages/@luke-ui/react/src/core/date-field/date-field.browser.test.tsx'),
'utf8',
);
expect(browserTest).toContain('testConformance');
@@ -133,7 +142,7 @@ describe('createComponent', () => {
});
const browserTest = await readFile(
- join(root, 'packages/@luke-ui/react/src/date-field/date-field.browser.test.tsx'),
+ join(root, 'packages/@luke-ui/react/src/core/date-field/date-field.browser.test.tsx'),
'utf8',
);
expect(browserTest).toContain('testConformance');
@@ -154,7 +163,7 @@ describe('createComponent', () => {
});
const browserTest = await readFile(
- join(root, 'packages/@luke-ui/react/src/mark/mark.browser.test.tsx'),
+ join(root, 'packages/@luke-ui/react/src/core/mark/mark.browser.test.tsx'),
'utf8',
);
expect(browserTest).toContain("test('Mark renders its root element'");
@@ -175,7 +184,7 @@ describe('createComponent', () => {
expect(
await readFile(
- join(root, 'packages/@luke-ui/react/src/action-chip/action-chip.browser.test.tsx'),
+ join(root, 'packages/@luke-ui/react/src/core/action-chip/action-chip.browser.test.tsx'),
'utf8',
),
).toContain("testIntegration('action-chip', async");
@@ -195,7 +204,7 @@ describe('createComponent', () => {
await expect(
readFile(
- join(root, 'packages/@luke-ui/react/src/date-field/date-field.visual.test.tsx'),
+ join(root, 'packages/@luke-ui/react/src/core/date-field/date-field.visual.test.tsx'),
'utf8',
),
).rejects.toMatchObject({ code: 'ENOENT' });
@@ -229,16 +238,16 @@ describe('createComponent', () => {
});
});
-const modulesRegistryPath = 'packages/@luke-ui/react/src/styles/modules.css.ts';
-const manifestPath = 'packages/@luke-ui/react/src/conformance/manifest.ts';
+const modulesRegistryPath = 'packages/@luke-ui/react/src/core/styles/modules.css.ts';
+const manifestPath = 'packages/@luke-ui/react/src/core/conformance/manifest.ts';
async function createRepositoryFixture(options?: { modulesRegistry?: string }): Promise {
const root = await mkdtemp(join(tmpdir(), 'component-plan-'));
roots.push(root);
await mkdir(join(root, 'apps/docs/content/docs/components'), { recursive: true });
- await mkdir(join(root, 'packages/@luke-ui/react/src/styles'), { recursive: true });
- await mkdir(join(root, 'packages/@luke-ui/react/src/conformance'), { recursive: true });
+ await mkdir(join(root, 'packages/@luke-ui/react/src/core/styles'), { recursive: true });
+ await mkdir(join(root, 'packages/@luke-ui/react/src/core/conformance'), { recursive: true });
await writeFile(
join(root, 'apps/docs/content/docs/components/meta.json'),
diff --git a/packages/turbo-generators/src/component-creation-plan.test.ts b/packages/turbo-generators/src/component-creation-plan.test.ts
index 340c1c4f..9dac07b7 100644
--- a/packages/turbo-generators/src/component-creation-plan.test.ts
+++ b/packages/turbo-generators/src/component-creation-plan.test.ts
@@ -1,3 +1,6 @@
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { access } from 'node:fs/promises';
import { parseSync } from 'oxc-parser';
import { describe, expect, it } from 'vite-plus/test';
import { ZodError } from 'zod';
@@ -8,6 +11,8 @@ import {
parseComponentAnswers,
} from './component-creation-plan.js';
+const repoRoot = fileURLToPath(new URL('../../..', import.meta.url));
+
const validAnswers = {
docsGroup: 'feedback',
name: 'StatusBadge',
@@ -48,13 +53,16 @@ describe('createComponentPlan', () => {
expect(plan.files.map((file) => file.path).sort()).toEqual([
'apps/docs/content/docs/components/feedback/status-badge.mdx',
'apps/docs/src/examples/status-badge/basic.tsx',
- 'packages/@luke-ui/react/src/status-badge/index.ts',
- 'packages/@luke-ui/react/src/status-badge/recipe.css.ts',
- 'packages/@luke-ui/react/src/status-badge/status-badge.browser.test.tsx',
- 'packages/@luke-ui/react/src/status-badge/status-badge.stories.tsx',
- 'packages/@luke-ui/react/src/status-badge/status-badge.tsx',
- 'packages/@luke-ui/react/src/status-badge/status-badge.visual.test.tsx',
+ 'packages/@luke-ui/react/src/core/status-badge/recipe.css.ts',
+ 'packages/@luke-ui/react/src/core/status-badge/status-badge.browser.test.tsx',
+ 'packages/@luke-ui/react/src/core/status-badge/status-badge.stories.tsx',
+ 'packages/@luke-ui/react/src/core/status-badge/status-badge.tsx',
+ 'packages/@luke-ui/react/src/core/status-badge/status-badge.visual.test.tsx',
+ 'packages/@luke-ui/react/src/exports/status-badge.ts',
]);
+ expect(plan.files.map((file) => file.path)).not.toContainEqual(
+ expect.stringMatching(/core\/status-badge\/index\.ts$/),
+ );
expect(plan).not.toHaveProperty('jsonEdits');
expect(plan).not.toHaveProperty('sortedImportEdits');
expect(plan).not.toHaveProperty('textFileInserts');
@@ -62,26 +70,62 @@ describe('createComponentPlan', () => {
const recipeSource = plan.files.find((file) =>
file.path.endsWith('/status-badge/recipe.css.ts'),
)?.contents;
- const indexSource = plan.files.find((file) =>
+ const componentSource = plan.files.find((file) =>
file.path.endsWith('/status-badge/status-badge.tsx'),
)?.contents;
- const barrelSource = plan.files.find((file) =>
- file.path.endsWith('/status-badge/index.ts'),
+ const packageExportSource = plan.files.find((file) =>
+ file.path.endsWith('/exports/status-badge.ts'),
)?.contents;
- expect(indexSource).not.toContain('export { statusBadgeRecipe');
- expect(barrelSource).toContain(
- "export { StatusBadge, type StatusBadgeProps } from './status-badge.js';",
+ expect(componentSource).not.toContain('export { statusBadgeRecipe');
+ expect(packageExportSource).toContain(
+ "export { StatusBadge, type StatusBadgeProps } from '../core/status-badge/status-badge.js';",
);
- expect(barrelSource).toContain(
- "export { statusBadgeRecipe, type StatusBadgeRecipeVariants } from './recipe.css.js';",
+ expect(packageExportSource).toContain(
+ "export { type StatusBadgeRecipeVariants, statusBadgeRecipe } from '../core/status-badge/recipe.css.js';",
);
+ expect(packageExportSource).not.toContain("from './index.js'");
expect(recipeSource).toContain('export const statusBadgeRecipe = recipe({');
expect(recipeSource).toContain(
'export type StatusBadgeRecipeVariants = RecipeSelection;',
);
});
+ it('emits relative imports that resolve to real files already in the repo', async () => {
+ const plan = createComponentPlan(validAnswers);
+
+ const violations = (
+ await Promise.all(plan.files.map((file) => findUnresolvedImports(file)))
+ ).flat();
+
+ expect(violations).toEqual([]);
+ });
+
+ it('plans no core barrel and names implementation modules directly', () => {
+ const plan = createComponentPlan(validAnswers);
+
+ expect(plan.files.map((file) => file.path)).not.toContainEqual(
+ expect.stringMatching(/\/core\/status-badge\/index\.ts$/),
+ );
+
+ const packageExportSource = plan.files.find((file) =>
+ file.path.endsWith('/exports/status-badge.ts'),
+ )?.contents;
+ if (packageExportSource === undefined) {
+ throw new Error('Expected the scaffold to write a package export module.');
+ }
+ expect(packageExportSource).toContain("from '../core/status-badge/status-badge.js'");
+ expect(packageExportSource).toContain("from '../core/status-badge/recipe.css.js'");
+ expect(packageExportSource).not.toContain('index.js');
+
+ for (const testPath of ['status-badge.browser.test.tsx', 'status-badge.visual.test.tsx']) {
+ const testSource = plan.files.find((file) => file.path.endsWith(testPath))?.contents;
+ if (testSource === undefined) throw new Error(`Expected the scaffold to write ${testPath}.`);
+ expect(testSource).toContain("from './status-badge.js'");
+ expect(testSource).not.toContain("from './index.js'");
+ }
+ });
+
it('rejects invalid component names before file writes', () => {
expect(() => {
return createComponentPlan({
@@ -103,7 +147,7 @@ describe('createComponentPlan', () => {
expect(frontmatter.props).toEqual([
{
name: 'StatusBadgeProps',
- path: 'packages/@luke-ui/react/src/status-badge/status-badge.tsx',
+ path: 'packages/@luke-ui/react/src/core/status-badge/status-badge.tsx',
},
]);
});
@@ -147,3 +191,46 @@ describe('createComponentPlan', () => {
expect(testFile.contents).toContain('getTarget');
});
});
+
+/**
+ * Resolves each relative import in a generated file against the repo tree and reports the ones
+ * that don't exist. Imports into the component's own not-yet-created directory (`./index.js`,
+ * `./recipe.css.js`, and similar) are skipped since the plan writes those files together; every
+ * other relative import must already resolve to a real file on disk.
+ */
+async function findUnresolvedImports(file: {
+ contents: string;
+ path: string;
+}): Promise> {
+ if (!/\.tsx?$/.test(file.path)) return [];
+
+ const generatedDirectory = path.dirname(path.join(repoRoot, file.path));
+ const lang = file.path.endsWith('.tsx') ? 'tsx' : 'ts';
+ const parsed = parseSync(file.path, file.contents, { lang });
+ const specifiers = parsed.module.staticImports
+ .map((staticImport) => staticImport.moduleRequest.value)
+ .filter((specifier) => specifier.startsWith('.'));
+
+ const resolutions = await Promise.all(
+ specifiers.map(async (specifier) => {
+ if (specifier.startsWith('./')) return undefined;
+
+ const target = path.resolve(generatedDirectory, specifier);
+ const candidates = [target, target.replace(/\.js$/, '.ts'), target.replace(/\.js$/, '.tsx')];
+ const found = await Promise.all(
+ candidates.map(async (candidate) => {
+ try {
+ await access(candidate);
+ return true;
+ } catch {
+ return false;
+ }
+ }),
+ );
+ if (found.some(Boolean)) return undefined;
+ return `${file.path} -> ${specifier}`;
+ }),
+ );
+
+ return resolutions.filter((resolution): resolution is string => resolution !== undefined);
+}
diff --git a/packages/turbo-generators/src/component-creation-plan.ts b/packages/turbo-generators/src/component-creation-plan.ts
index 53a28496..77b9abae 100644
--- a/packages/turbo-generators/src/component-creation-plan.ts
+++ b/packages/turbo-generators/src/component-creation-plan.ts
@@ -133,15 +133,15 @@ export function createComponentWork(input: ParsedComponentAnswers): ComponentCre
recipeName,
variantsType,
}),
- path: `packages/@luke-ui/react/src/${name}/${name}.tsx`,
+ path: `packages/@luke-ui/react/src/core/${name}/${name}.tsx`,
},
{
- contents: renderComponentBarrel({ name, pascalName, recipeName, variantsType }),
- path: `packages/@luke-ui/react/src/${name}/index.ts`,
+ contents: renderPackageExport({ name, pascalName, recipeName, variantsType }),
+ path: `packages/@luke-ui/react/src/exports/${name}.ts`,
},
{
contents: renderRecipe({ recipeName, variantsType }),
- path: `packages/@luke-ui/react/src/${name}/recipe.css.ts`,
+ path: `packages/@luke-ui/react/src/core/${name}/recipe.css.ts`,
},
{
contents: renderComponentTest({
@@ -150,11 +150,11 @@ export function createComponentWork(input: ParsedComponentAnswers): ComponentCre
name,
pascalName,
}),
- path: `packages/@luke-ui/react/src/${name}/${name}.browser.test.tsx`,
+ path: `packages/@luke-ui/react/src/core/${name}/${name}.browser.test.tsx`,
},
{
contents: renderPackageStory({ docsGroup, name, pascalName }),
- path: `packages/@luke-ui/react/src/${name}/${name}.stories.tsx`,
+ path: `packages/@luke-ui/react/src/core/${name}/${name}.stories.tsx`,
},
{
contents: renderHostedExample({ name, pascalName }),
@@ -169,7 +169,7 @@ export function createComponentWork(input: ParsedComponentAnswers): ComponentCre
if (input.visualCoverage) {
files.push({
contents: renderVisualTest({ name, pascalName }),
- path: `packages/@luke-ui/react/src/${name}/${name}.visual.test.tsx`,
+ path: `packages/@luke-ui/react/src/core/${name}/${name}.visual.test.tsx`,
});
}
@@ -201,7 +201,7 @@ export function createComponentWork(input: ParsedComponentAnswers): ComponentCre
{
kind: 'sorted-import',
line: `import '../${name}/recipe.css.js';`,
- path: 'packages/@luke-ui/react/src/styles/modules.css.ts',
+ path: 'packages/@luke-ui/react/src/core/styles/modules.css.ts',
},
],
textFileInserts: [
@@ -211,7 +211,7 @@ export function createComponentWork(input: ParsedComponentAnswers): ComponentCre
`\t['${pascalName}', '${name}', ${formatConformanceList(conformance)}, '${integrationTripwire}', '${visualApplicability}'],`,
],
marker: '].map(([name, path, conformance, integrationTripwire, visualApplicability]) => ({',
- path: 'packages/@luke-ui/react/src/conformance/manifest.ts',
+ path: 'packages/@luke-ui/react/src/core/conformance/manifest.ts',
},
],
};
@@ -247,7 +247,7 @@ function renderComponentSource(input: {
variantsType: string;
}): string {
return `import type { ComponentProps, JSX } from 'react';
-import { cx } from '../utils/utils.js';
+import { cx } from '../../shared/utils/utils.js';
import { ${input.recipeName} } from './recipe.css.js';
/** Props for \`${input.pascalName}\`. */
@@ -261,14 +261,14 @@ export function ${input.pascalName}(props: ${input.pascalName}Props): JSX.Elemen
`;
}
-function renderComponentBarrel(input: {
+function renderPackageExport(input: {
name: string;
pascalName: string;
recipeName: string;
variantsType: string;
}): string {
- return `export { ${input.pascalName}, type ${input.pascalName}Props } from './${input.name}.js';
-export { ${input.recipeName}, type ${input.variantsType} } from './recipe.css.js';
+ return `export { ${input.pascalName}, type ${input.pascalName}Props } from '../core/${input.name}/${input.name}.js';
+export { type ${input.variantsType}, ${input.recipeName} } from '../core/${input.name}/recipe.css.js';
`;
}
@@ -278,7 +278,7 @@ function renderPackageStory(input: {
pascalName: string;
}): string {
return `import { ${input.pascalName} } from '@luke-ui/react/${input.name}';
-import preview from '../../.storybook/preview.js';
+import preview from '../../../.storybook/preview.js';
const meta = preview.meta({
component: ${input.pascalName},
@@ -330,7 +330,7 @@ function renderComponentTest(input: {
? [`import { ${helperImports.join(', ')} } from '../conformance/helpers.js';`]
: []),
"import { render } from '../test-utils/render.js';",
- `import { ${input.pascalName} } from './index.js';`,
+ `import { ${input.pascalName} } from './${input.name}.js';`,
];
const renderComponent = `render(<${input.pascalName} {...props}>Content${input.pascalName}>)`;
@@ -386,7 +386,7 @@ function renderVisualTest(input: { name: string; pascalName: string }): string {
return `import { test } from 'vite-plus/test';
import { render } from '../test-utils/render.js';
import { captureVisual, Grid } from '../test-utils/visual.js';
-import { ${input.pascalName} } from './index.js';
+import { ${input.pascalName} } from './${input.name}.js';
test('kitchen sink', async () => {
const { locator } = render(
@@ -408,10 +408,10 @@ function renderHostedDocsPage(input: {
}): string {
return `---
title: ${input.displayName}
-source: packages/@luke-ui/react/src/${input.name}
+source: packages/@luke-ui/react/src/exports/${input.name}.ts
props:
- name: ${input.pascalName}Props
- path: packages/@luke-ui/react/src/${input.name}/${input.name}.tsx
+ path: packages/@luke-ui/react/src/core/${input.name}/${input.name}.tsx
---