@duro-app/ui 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/components/ActionBar/ActionBar.d.ts +3 -1
  2. package/dist/components/ActionBar/ActionBar.d.ts.map +1 -1
  3. package/dist/components/Button/Button.meta.d.ts +3 -0
  4. package/dist/components/Button/Button.meta.d.ts.map +1 -0
  5. package/dist/components/Checkbox/Checkbox.meta.d.ts +3 -0
  6. package/dist/components/Checkbox/Checkbox.meta.d.ts.map +1 -0
  7. package/dist/components/Cluster/Cluster.meta.d.ts +3 -0
  8. package/dist/components/Cluster/Cluster.meta.d.ts.map +1 -0
  9. package/dist/components/Field/Field.meta.d.ts +3 -0
  10. package/dist/components/Field/Field.meta.d.ts.map +1 -0
  11. package/dist/components/Fieldset/Fieldset.meta.d.ts +3 -0
  12. package/dist/components/Fieldset/Fieldset.meta.d.ts.map +1 -0
  13. package/dist/components/Form/Form.meta.d.ts +3 -0
  14. package/dist/components/Form/Form.meta.d.ts.map +1 -0
  15. package/dist/components/Grid/Grid.meta.d.ts +3 -0
  16. package/dist/components/Grid/Grid.meta.d.ts.map +1 -0
  17. package/dist/components/Inline/Inline.meta.d.ts +3 -0
  18. package/dist/components/Inline/Inline.meta.d.ts.map +1 -0
  19. package/dist/components/Input/Input.meta.d.ts +3 -0
  20. package/dist/components/Input/Input.meta.d.ts.map +1 -0
  21. package/dist/components/LinkButton/LinkButton.meta.d.ts +3 -0
  22. package/dist/components/LinkButton/LinkButton.meta.d.ts.map +1 -0
  23. package/dist/components/Menu/Menu.meta.d.ts +3 -0
  24. package/dist/components/Menu/Menu.meta.d.ts.map +1 -0
  25. package/dist/components/PageShell/PageShell.meta.d.ts +3 -0
  26. package/dist/components/PageShell/PageShell.meta.d.ts.map +1 -0
  27. package/dist/components/ScrollArea/ScrollArea.meta.d.ts +3 -0
  28. package/dist/components/ScrollArea/ScrollArea.meta.d.ts.map +1 -0
  29. package/dist/components/Select/Select.meta.d.ts +3 -0
  30. package/dist/components/Select/Select.meta.d.ts.map +1 -0
  31. package/dist/components/SideNav/SideNav.meta.d.ts +3 -0
  32. package/dist/components/SideNav/SideNav.meta.d.ts.map +1 -0
  33. package/dist/components/Stack/Stack.meta.d.ts +3 -0
  34. package/dist/components/Stack/Stack.meta.d.ts.map +1 -0
  35. package/dist/components/Switch/Switch.meta.d.ts +3 -0
  36. package/dist/components/Switch/Switch.meta.d.ts.map +1 -0
  37. package/dist/components/Table/Table.meta.d.ts +3 -0
  38. package/dist/components/Table/Table.meta.d.ts.map +1 -0
  39. package/dist/components/Tabs/Tabs.meta.d.ts +3 -0
  40. package/dist/components/Tabs/Tabs.meta.d.ts.map +1 -0
  41. package/dist/components/Textarea/Textarea.meta.d.ts +3 -0
  42. package/dist/components/Textarea/Textarea.meta.d.ts.map +1 -0
  43. package/dist/components/Toggle/Toggle.meta.d.ts +3 -0
  44. package/dist/components/Toggle/Toggle.meta.d.ts.map +1 -0
  45. package/dist/components/ToggleGroup/ToggleGroup.meta.d.ts +3 -0
  46. package/dist/components/ToggleGroup/ToggleGroup.meta.d.ts.map +1 -0
  47. package/dist/components/Tooltip/Tooltip.meta.d.ts +3 -0
  48. package/dist/components/Tooltip/Tooltip.meta.d.ts.map +1 -0
  49. package/dist/components/component-meta.d.ts +15 -0
  50. package/dist/components/component-meta.d.ts.map +1 -0
  51. package/dist/index.js +158 -162
  52. package/dist/index.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/components/ActionBar/ActionBar.tsx +20 -15
  55. package/src/components/Button/Button.meta.ts +23 -0
  56. package/src/components/Checkbox/Checkbox.meta.ts +21 -0
  57. package/src/components/Cluster/Cluster.meta.ts +25 -0
  58. package/src/components/Field/Field.meta.ts +37 -0
  59. package/src/components/Fieldset/Fieldset.meta.ts +38 -0
  60. package/src/components/Form/Form.meta.ts +38 -0
  61. package/src/components/Grid/Grid.meta.ts +30 -0
  62. package/src/components/Inline/Inline.meta.ts +23 -0
  63. package/src/components/Input/Input.meta.ts +23 -0
  64. package/src/components/LinkButton/LinkButton.meta.ts +24 -0
  65. package/src/components/Menu/Menu.meta.ts +33 -0
  66. package/src/components/PageShell/PageShell.meta.ts +24 -0
  67. package/src/components/ScrollArea/ScrollArea.meta.ts +27 -0
  68. package/src/components/Select/Select.meta.ts +34 -0
  69. package/src/components/SideNav/SideNav.meta.ts +31 -0
  70. package/src/components/Stack/Stack.meta.ts +22 -0
  71. package/src/components/Switch/Switch.meta.ts +25 -0
  72. package/src/components/Table/Table.meta.ts +36 -0
  73. package/src/components/Tabs/Tabs.meta.ts +31 -0
  74. package/src/components/Textarea/Textarea.meta.ts +22 -0
  75. package/src/components/Toggle/Toggle.meta.ts +30 -0
  76. package/src/components/ToggleGroup/ToggleGroup.meta.ts +30 -0
  77. package/src/components/Tooltip/Tooltip.meta.ts +22 -0
  78. package/src/components/component-meta.ts +8 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -52,7 +52,7 @@
52
52
  }
53
53
  },
54
54
  "dependencies": {
55
- "@duro-app/tokens": "^0.16.0"
55
+ "@duro-app/tokens": "^0.17.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@babel/preset-typescript": "^7.28.0",
@@ -13,6 +13,8 @@ export interface ActionBarProps {
13
13
  isEmphasized?: boolean
14
14
  /** Called when the clear/close button is pressed. */
15
15
  onClearSelection: () => void
16
+ /** Whether the bar can be dismissed. When false, the close button is hidden. @default true */
17
+ dismissible?: boolean
16
18
  /** Action buttons to display. */
17
19
  children: ReactNode
18
20
  }
@@ -22,6 +24,7 @@ function ActionBarInner({
22
24
  selectedLabel,
23
25
  isEmphasized = false,
24
26
  onClearSelection,
27
+ dismissible = true,
25
28
  children,
26
29
  }: ActionBarProps) {
27
30
  const container = useOverlayContainer()
@@ -42,21 +45,23 @@ function ActionBarInner({
42
45
  </html.span>
43
46
  <html.div style={[styles.separator, isEmphasized && styles.separatorEmphasized]} />
44
47
  <html.div style={styles.actions}>{children}</html.div>
45
- <html.button
46
- type="button"
47
- aria-label="Clear selection"
48
- onClick={onClearSelection}
49
- style={[styles.closeButton, isEmphasized && styles.closeButtonEmphasized]}
50
- >
51
- <svg width={10} height={10} viewBox="0 0 10 10" fill="none" aria-hidden="true">
52
- <path
53
- d="M1 1l8 8M9 1l-8 8"
54
- stroke="currentColor"
55
- strokeWidth={1.5}
56
- strokeLinecap="round"
57
- />
58
- </svg>
59
- </html.button>
48
+ {dismissible && (
49
+ <html.button
50
+ type="button"
51
+ aria-label="Clear selection"
52
+ onClick={onClearSelection}
53
+ style={[styles.closeButton, isEmphasized && styles.closeButtonEmphasized]}
54
+ >
55
+ <svg width={10} height={10} viewBox="0 0 10 10" fill="none" aria-hidden="true">
56
+ <path
57
+ d="M1 1l8 8M9 1l-8 8"
58
+ stroke="currentColor"
59
+ strokeWidth={1.5}
60
+ strokeLinecap="round"
61
+ />
62
+ </svg>
63
+ </html.button>
64
+ )}
60
65
  </html.div>
61
66
  )
62
67
 
@@ -0,0 +1,23 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Standard interactive button. For navigation (links styled as buttons), use LinkButton instead.',
6
+ whenToUse: [
7
+ 'Triggering actions: submit, save, delete, open dialog',
8
+ 'Primary and secondary actions in forms and toolbars',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Navigating to a URL — use LinkButton (renders <a>)',
12
+ 'Toggling state — use Toggle',
13
+ ],
14
+ relatedTo: [
15
+ {component: 'LinkButton', relationship: 'LinkButton for navigation; Button for actions'},
16
+ {component: 'Toggle', relationship: 'Toggle for pressed/unpressed state'},
17
+ ],
18
+ example: `<Inline gap="sm">
19
+ <Button variant="primary" type="submit">Save</Button>
20
+ <Button variant="secondary">Cancel</Button>
21
+ <Button variant="danger">Delete</Button>
22
+ </Inline>`,
23
+ }
@@ -0,0 +1,21 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Checkbox input with optional visible label. Supports controlled and uncontrolled modes.',
6
+ whenToUse: [
7
+ 'Boolean opt-in/opt-out (terms acceptance, feature toggle)',
8
+ 'Multi-select from a list (inside a Fieldset)',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Binary toggle in settings — prefer Switch for on/off semantics',
12
+ 'Selecting one of several options — use Select or ToggleGroup',
13
+ ],
14
+ relatedTo: [
15
+ {component: 'Switch', relationship: 'Switch for on/off settings; Checkbox for opt-in/selection'},
16
+ {component: 'Toggle', relationship: 'Toggle for toolbar actions; Checkbox for form input'},
17
+ ],
18
+ example: `<Checkbox name="terms" onChange={(e) => console.log(e.target.checked)}>
19
+ I agree to the terms and conditions
20
+ </Checkbox>`,
21
+ }
@@ -0,0 +1,25 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Horizontal flex layout that WRAPS to the next line when items overflow.',
6
+ whenToUse: [
7
+ 'Tags, badges, or chips that should wrap when they exceed the container width',
8
+ 'Any set of inline elements with unknown/dynamic count',
9
+ 'Filter pills, keyword lists, avatar groups',
10
+ ],
11
+ whenNotToUse: [
12
+ 'Items that must stay on one line — use Inline',
13
+ 'Vertical stacking — use Stack',
14
+ ],
15
+ relatedTo: [
16
+ {component: 'Inline', relationship: 'Same but does NOT wrap (nowrap)'},
17
+ {component: 'Stack', relationship: 'Vertical equivalent'},
18
+ ],
19
+ example: `<Cluster gap="xs">
20
+ <Badge>React</Badge>
21
+ <Badge>TypeScript</Badge>
22
+ <Badge>Design Systems</Badge>
23
+ <Badge>Accessibility</Badge>
24
+ </Cluster>`,
25
+ }
@@ -0,0 +1,37 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Compound form field with label, description, and error display. Inside Form, auto-binds to react-hook-form via name prop. Works standalone with manual invalid prop.',
6
+ whenToUse: [
7
+ 'Any form input that needs a label and/or error message',
8
+ 'Standalone labeled input (without Form) with manual error display',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Bare input without label — use Input directly (rare)',
12
+ ],
13
+ anatomy: {
14
+ required: ['Root'],
15
+ optional: ['Label', 'Description', 'Error'],
16
+ },
17
+ relatedTo: [
18
+ {component: 'Form', relationship: 'Field.Root auto-binds when name matches schema key'},
19
+ {component: 'Input', relationship: 'Place Input inside Field.Root'},
20
+ {component: 'Textarea', relationship: 'Place Textarea inside Field.Root'},
21
+ {component: 'Select', relationship: 'Place Select inside Field.Root'},
22
+ ],
23
+ example: `// Inside Form (auto-binds validation)
24
+ <Field.Root name="email">
25
+ <Field.Label>Email</Field.Label>
26
+ <Input type="email" placeholder="you@example.com" />
27
+ <Field.Description>We'll never share your email.</Field.Description>
28
+ <Field.Error />
29
+ </Field.Root>
30
+
31
+ // Standalone (manual error)
32
+ <Field.Root invalid>
33
+ <Field.Label>Email</Field.Label>
34
+ <Input variant="error" />
35
+ <Field.Error>This email is already taken.</Field.Error>
36
+ </Field.Root>`,
37
+ }
@@ -0,0 +1,38 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Groups related form controls with consistent gap spacing and an optional legend. Supports disabled cascade.',
6
+ whenToUse: [
7
+ 'Grouping related fields within a form (e.g., "Personal Info", "Address")',
8
+ 'Applying consistent vertical gap between form fields',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Non-form content grouping — use Stack or Card',
12
+ ],
13
+ anatomy: {
14
+ required: ['Root'],
15
+ optional: ['Legend'],
16
+ },
17
+ relatedTo: [
18
+ {component: 'Form', relationship: 'Fieldset.Root goes inside Form'},
19
+ {component: 'Field', relationship: 'Field.Root goes inside Fieldset.Root'},
20
+ {component: 'Stack', relationship: 'Similar vertical spacing, but Fieldset has form semantics'},
21
+ ],
22
+ example: `<Form schema={schema} defaultValues={defaults} onSubmit={onSubmit}>
23
+ <Fieldset.Root gap="md">
24
+ <Fieldset.Legend>Account Details</Fieldset.Legend>
25
+ <Field.Root name="email">
26
+ <Field.Label>Email</Field.Label>
27
+ <Input type="email" />
28
+ <Field.Error />
29
+ </Field.Root>
30
+ <Field.Root name="password">
31
+ <Field.Label>Password</Field.Label>
32
+ <Input type="password" />
33
+ <Field.Error />
34
+ </Field.Root>
35
+ <Button type="submit">Create account</Button>
36
+ </Fieldset.Root>
37
+ </Form>`,
38
+ }
@@ -0,0 +1,38 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Form wrapper with Effect Schema validation and react-hook-form integration. Provides FormContext to child Field components.',
6
+ whenToUse: [
7
+ 'Any form that needs validation',
8
+ 'Multi-field forms with submit handling',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Single standalone field without validation — use Field.Root directly',
12
+ 'Non-form interactive UIs — use individual components',
13
+ ],
14
+ relatedTo: [
15
+ {component: 'Field', relationship: 'Field.Root auto-binds to Form via name prop'},
16
+ {component: 'Fieldset', relationship: 'Groups fields visually within a Form'},
17
+ ],
18
+ example: `import {Schema} from 'effect'
19
+
20
+ const MySchema = Schema.Struct({
21
+ email: Schema.String.pipe(
22
+ Schema.pattern(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/, {message: () => 'Invalid email'}),
23
+ ),
24
+ })
25
+
26
+ <Form schema={MySchema} defaultValues={{email: ''}} onSubmit={handleSubmit}>
27
+ {({formState}) => (
28
+ <Fieldset.Root gap="md">
29
+ <Field.Root name="email">
30
+ <Field.Label>Email</Field.Label>
31
+ <Input type="email" />
32
+ <Field.Error />
33
+ </Field.Root>
34
+ <Button type="submit" disabled={!formState.isValid}>Submit</Button>
35
+ </Fieldset.Root>
36
+ )}
37
+ </Form>`,
38
+ }
@@ -0,0 +1,30 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'CSS grid layout. Supports fixed column count (1-6) or responsive auto-fit via minColumnWidth.',
6
+ whenToUse: [
7
+ 'Card grids, dashboard layouts, multi-column forms',
8
+ 'Responsive layouts that should auto-adjust column count',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Single-column vertical layout — use Stack',
12
+ 'Simple horizontal row — use Inline or Cluster',
13
+ ],
14
+ relatedTo: [
15
+ {component: 'Stack', relationship: 'Single-column vertical layout'},
16
+ ],
17
+ example: `// Responsive: columns auto-fit based on min width
18
+ <Grid minColumnWidth="280px" gap="md">
19
+ <Card>A</Card>
20
+ <Card>B</Card>
21
+ <Card>C</Card>
22
+ </Grid>
23
+
24
+ // Fixed: always 3 columns
25
+ <Grid columns={3} gap="md">
26
+ <Card>A</Card>
27
+ <Card>B</Card>
28
+ <Card>C</Card>
29
+ </Grid>`,
30
+ }
@@ -0,0 +1,23 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Horizontal flex layout with NO wrapping. Items stay on one line and may overflow.',
6
+ whenToUse: [
7
+ 'Toolbar or button group that must stay on one row',
8
+ 'Horizontal alignment of a known, small number of items',
9
+ 'Icon + text pairs, breadcrumbs, action rows',
10
+ ],
11
+ whenNotToUse: [
12
+ 'Items that should wrap to the next line — use Cluster',
13
+ 'Vertical stacking — use Stack',
14
+ ],
15
+ relatedTo: [
16
+ {component: 'Cluster', relationship: 'Same but wraps to next line'},
17
+ {component: 'Stack', relationship: 'Vertical equivalent'},
18
+ ],
19
+ example: `<Inline gap="sm" align="center">
20
+ <Button>Save</Button>
21
+ <Button variant="secondary">Cancel</Button>
22
+ </Inline>`,
23
+ }
@@ -0,0 +1,23 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Text input with automatic Field/Form integration. Variant auto-switches to error when inside an invalid Field.',
6
+ whenToUse: [
7
+ 'Single-line text entry (text, email, password, url, tel, number, search)',
8
+ ],
9
+ whenNotToUse: [
10
+ 'Multi-line text — use Textarea',
11
+ 'Picking from predefined options — use Select',
12
+ ],
13
+ relatedTo: [
14
+ {component: 'Field', relationship: 'Wrap in Field.Root for label + error'},
15
+ {component: 'InputGroup', relationship: 'Wrap in InputGroup.Root for prefix/suffix addons'},
16
+ {component: 'Textarea', relationship: 'Multi-line variant'},
17
+ ],
18
+ example: `<Field.Root name="email">
19
+ <Field.Label>Email</Field.Label>
20
+ <Input type="email" placeholder="you@example.com" autoComplete="email" />
21
+ <Field.Error />
22
+ </Field.Root>`,
23
+ }
@@ -0,0 +1,24 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Button-styled hyperlink. Renders an <a> tag, not a <button>. Use for navigation, not actions.',
6
+ whenToUse: [
7
+ 'Navigation that should look like a button (e.g., "Get started", "View all")',
8
+ 'Links to external pages with button styling',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Triggering in-page actions — use Button',
12
+ 'Plain text links — use a styled <a> tag',
13
+ ],
14
+ relatedTo: [
15
+ {component: 'Button', relationship: 'Button for actions; LinkButton for navigation'},
16
+ ],
17
+ example: `<LinkButton href="/signup" variant="primary">
18
+ Get started
19
+ </LinkButton>
20
+
21
+ <LinkButton href="https://docs.example.com" target="_blank" variant="secondary">
22
+ Read the docs
23
+ </LinkButton>`,
24
+ }
@@ -0,0 +1,33 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Dropdown action menu. Triggers actions (not value selection). Compound component — Root is required (throws without it).',
6
+ whenToUse: [
7
+ 'Context menu or "more actions" dropdown',
8
+ 'Navigation links in a dropdown',
9
+ 'Overflow menu for toolbar actions',
10
+ ],
11
+ whenNotToUse: [
12
+ 'Picking a value — use Select',
13
+ 'Primary navigation — use SideNav or Tabs',
14
+ ],
15
+ anatomy: {
16
+ required: ['Root', 'Trigger', 'Popup', 'Item'],
17
+ optional: ['LinkItem'],
18
+ },
19
+ relatedTo: [
20
+ {component: 'Select', relationship: 'Select picks values; Menu triggers actions'},
21
+ {component: 'Button', relationship: 'Often used as Menu.Trigger content'},
22
+ ],
23
+ example: `<Menu.Root>
24
+ <Menu.Trigger>
25
+ <Button variant="secondary">Actions</Button>
26
+ </Menu.Trigger>
27
+ <Menu.Popup>
28
+ <Menu.Item onClick={() => console.log('edit')}>Edit</Menu.Item>
29
+ <Menu.Item onClick={() => console.log('duplicate')}>Duplicate</Menu.Item>
30
+ <Menu.LinkItem href="/settings">Settings</Menu.LinkItem>
31
+ </Menu.Popup>
32
+ </Menu.Root>`,
33
+ }
@@ -0,0 +1,24 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Page-level layout wrapper. Centers content with max-width, padding, and an optional sticky header.',
6
+ whenToUse: [
7
+ 'Top-level page layout',
8
+ 'Any full-page view that needs centered content with consistent padding',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Nested section layouts — use Stack or Card',
12
+ 'Sidebar layouts — combine with SideNav manually',
13
+ ],
14
+ relatedTo: [
15
+ {component: 'Stack', relationship: 'Use Stack for content within PageShell'},
16
+ {component: 'SideNav', relationship: 'Often placed alongside PageShell'},
17
+ ],
18
+ example: `<PageShell maxWidth="lg" padding="md" header={<Heading level={1}>Dashboard</Heading>}>
19
+ <Stack gap="lg">
20
+ <Card>Content section 1</Card>
21
+ <Card>Content section 2</Card>
22
+ </Stack>
23
+ </PageShell>`,
24
+ }
@@ -0,0 +1,27 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Custom scrollbar region with draggable thumb. Supports vertical and horizontal scrolling. Compound component — Root is required.',
6
+ whenToUse: [
7
+ 'Custom-styled scrollbars for content overflow areas',
8
+ 'Chat logs, code blocks, or long lists with constrained height',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Simple page-level scrolling — browser scroll is fine',
12
+ 'Very short content that never overflows',
13
+ ],
14
+ anatomy: {
15
+ required: ['Root', 'Viewport', 'Content', 'Scrollbar', 'Thumb'],
16
+ },
17
+ example: `<ScrollArea.Root>
18
+ <ScrollArea.Viewport maxHeight={300}>
19
+ <ScrollArea.Content>
20
+ {/* Long content here */}
21
+ </ScrollArea.Content>
22
+ </ScrollArea.Viewport>
23
+ <ScrollArea.Scrollbar orientation="vertical">
24
+ <ScrollArea.Thumb orientation="vertical" />
25
+ </ScrollArea.Scrollbar>
26
+ </ScrollArea.Root>`,
27
+ }
@@ -0,0 +1,34 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Dropdown select for choosing one value from a list. Compound component — Root is required (throws without it).',
6
+ whenToUse: [
7
+ 'User must pick one option from a predefined list',
8
+ 'Form field for country, category, role selection',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Triggering actions — use Menu',
12
+ 'Multiple selections — use ToggleGroup or Checkbox group',
13
+ 'Fewer than 3 options — consider Toggle or ToggleGroup',
14
+ ],
15
+ anatomy: {
16
+ required: ['Root', 'Trigger', 'Popup', 'Item'],
17
+ optional: ['Value', 'Icon', 'ItemText'],
18
+ },
19
+ relatedTo: [
20
+ {component: 'Menu', relationship: 'Menu triggers actions; Select picks a value'},
21
+ {component: 'ToggleGroup', relationship: 'For visible, small option sets'},
22
+ ],
23
+ example: `<Select.Root name="role" defaultValue="viewer">
24
+ <Select.Trigger>
25
+ <Select.Value placeholder="Select role" />
26
+ <Select.Icon />
27
+ </Select.Trigger>
28
+ <Select.Popup>
29
+ <Select.Item value="admin">Admin</Select.Item>
30
+ <Select.Item value="editor">Editor</Select.Item>
31
+ <Select.Item value="viewer">Viewer</Select.Item>
32
+ </Select.Popup>
33
+ </Select.Root>`,
34
+ }
@@ -0,0 +1,31 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Vertical side navigation with collapsible groups. Supports controlled and uncontrolled active item. Compound component — Root is required.',
6
+ whenToUse: [
7
+ 'App-level sidebar navigation',
8
+ 'Settings navigation with grouped sections',
9
+ ],
10
+ whenNotToUse: [
11
+ 'In-page content switching — use Tabs',
12
+ 'Top-level horizontal nav — use Inline with LinkButton',
13
+ ],
14
+ anatomy: {
15
+ required: ['Root', 'Group', 'Item'],
16
+ },
17
+ relatedTo: [
18
+ {component: 'Tabs', relationship: 'Tabs switch content in-place; SideNav navigates pages'},
19
+ {component: 'PageShell', relationship: 'Often placed alongside PageShell'},
20
+ ],
21
+ example: `<SideNav.Root defaultValue="dashboard">
22
+ <SideNav.Group label="Main">
23
+ <SideNav.Item value="dashboard">Dashboard</SideNav.Item>
24
+ <SideNav.Item value="analytics">Analytics</SideNav.Item>
25
+ </SideNav.Group>
26
+ <SideNav.Group label="Settings" defaultExpanded={false}>
27
+ <SideNav.Item value="profile">Profile</SideNav.Item>
28
+ <SideNav.Item value="billing">Billing</SideNav.Item>
29
+ </SideNav.Group>
30
+ </SideNav.Root>`,
31
+ }
@@ -0,0 +1,22 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Vertical flex layout. Stacks children top-to-bottom with consistent gap.',
6
+ whenToUse: [
7
+ 'Vertically stacking form fields, cards, or sections',
8
+ 'Any column-direction layout with uniform spacing',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Horizontal layouts — use Inline (no wrap) or Cluster (wraps)',
12
+ 'Grid layouts — use Grid',
13
+ ],
14
+ relatedTo: [
15
+ {component: 'Inline', relationship: 'Horizontal equivalent (no wrap)'},
16
+ {component: 'Cluster', relationship: 'Horizontal equivalent (wraps)'},
17
+ ],
18
+ example: `<Stack gap="md" align="stretch">
19
+ <Card>First</Card>
20
+ <Card>Second</Card>
21
+ </Stack>`,
22
+ }
@@ -0,0 +1,25 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Toggle switch for on/off settings. Renders a hidden checkbox for form submission + a styled switch button.',
6
+ whenToUse: [
7
+ 'On/off toggles in settings (notifications, dark mode, feature flags)',
8
+ 'Immediate-effect toggles where the change applies instantly',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Form opt-in checkboxes — use Checkbox',
12
+ 'Selecting between multiple options — use Select or ToggleGroup',
13
+ ],
14
+ relatedTo: [
15
+ {component: 'Checkbox', relationship: 'Checkbox for selection/opt-in; Switch for on/off settings'},
16
+ {component: 'Toggle', relationship: 'Toggle for toolbar pressed state; Switch for settings'},
17
+ ],
18
+ example: `<Switch
19
+ name="notifications"
20
+ defaultChecked={true}
21
+ onCheckedChange={(checked) => console.log(checked)}
22
+ >
23
+ Enable notifications
24
+ </Switch>`,
25
+ }
@@ -0,0 +1,36 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Data table with CSS grid layout. Supports striped, bordered variants. Compound component — Root is required (throws without it). Requires `columns` prop on Root.',
6
+ whenToUse: [
7
+ 'Displaying tabular data with rows and columns',
8
+ 'Data lists with sortable/filterable columns',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Simple key-value pairs — use Stack with Text',
12
+ 'Card-based layouts — use Grid with Card',
13
+ ],
14
+ anatomy: {
15
+ required: ['Root', 'Header', 'Body', 'Row', 'HeaderCell', 'Cell'],
16
+ },
17
+ relatedTo: [
18
+ {component: 'ScrollArea', relationship: 'Wrap Table in ScrollArea for horizontal overflow'},
19
+ ],
20
+ example: `<Table.Root variant="striped" size="md" columns={3}>
21
+ <Table.Header>
22
+ <Table.Row>
23
+ <Table.HeaderCell>Name</Table.HeaderCell>
24
+ <Table.HeaderCell>Role</Table.HeaderCell>
25
+ <Table.HeaderCell>Status</Table.HeaderCell>
26
+ </Table.Row>
27
+ </Table.Header>
28
+ <Table.Body>
29
+ <Table.Row>
30
+ <Table.Cell>Alice</Table.Cell>
31
+ <Table.Cell>Admin</Table.Cell>
32
+ <Table.Cell>Active</Table.Cell>
33
+ </Table.Row>
34
+ </Table.Body>
35
+ </Table.Root>`,
36
+ }
@@ -0,0 +1,31 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Tabbed interface with keyboard navigation. Supports horizontal and vertical orientation. Compound component — Root is required.',
6
+ whenToUse: [
7
+ 'Switching between content panels in the same view',
8
+ 'Settings pages with multiple sections',
9
+ 'Organizing related content without page navigation',
10
+ ],
11
+ whenNotToUse: [
12
+ 'Primary site navigation — use SideNav or links',
13
+ 'Step-by-step wizard — use a custom stepper',
14
+ ],
15
+ anatomy: {
16
+ required: ['Root', 'List', 'Tab', 'Panel'],
17
+ },
18
+ relatedTo: [
19
+ {component: 'SideNav', relationship: 'For persistent navigation, not content switching'},
20
+ ],
21
+ example: `<Tabs.Root defaultValue="general">
22
+ <Tabs.List>
23
+ <Tabs.Tab value="general">General</Tabs.Tab>
24
+ <Tabs.Tab value="security">Security</Tabs.Tab>
25
+ <Tabs.Tab value="billing" disabled>Billing</Tabs.Tab>
26
+ </Tabs.List>
27
+ <Tabs.Panel value="general">General settings content</Tabs.Panel>
28
+ <Tabs.Panel value="security">Security settings content</Tabs.Panel>
29
+ <Tabs.Panel value="billing">Billing content</Tabs.Panel>
30
+ </Tabs.Root>`,
31
+ }