@duro-app/ui 2.0.1 → 3.0.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 (82) hide show
  1. package/dist/components/ActionBar/ActionBar.stories.d.ts.map +1 -1
  2. package/dist/components/Alert/Alert.stories.d.ts.map +1 -1
  3. package/dist/components/Badge/Badge.stories.d.ts.map +1 -1
  4. package/dist/components/Button/Button.stories.d.ts.map +1 -1
  5. package/dist/components/Callout/Callout.stories.d.ts.map +1 -1
  6. package/dist/components/Card/Card.stories.d.ts.map +1 -1
  7. package/dist/components/Checkbox/Checkbox.stories.d.ts.map +1 -1
  8. package/dist/components/Cluster/Cluster.stories.d.ts.map +1 -1
  9. package/dist/components/ColorMode/ColorMode.stories.d.ts.map +1 -1
  10. package/dist/components/Field/Field.stories.d.ts.map +1 -1
  11. package/dist/components/Grid/Grid.d.ts +8 -1
  12. package/dist/components/Grid/Grid.d.ts.map +1 -1
  13. package/dist/components/Grid/Grid.meta.d.ts.map +1 -1
  14. package/dist/components/Grid/Grid.stories.d.ts +1 -0
  15. package/dist/components/Grid/Grid.stories.d.ts.map +1 -1
  16. package/dist/components/Grid/styles.css.d.ts +6 -0
  17. package/dist/components/Grid/styles.css.d.ts.map +1 -1
  18. package/dist/components/Inline/Inline.stories.d.ts.map +1 -1
  19. package/dist/components/Input/Input.stories.d.ts.map +1 -1
  20. package/dist/components/InputGroup/InputGroup.stories.d.ts.map +1 -1
  21. package/dist/components/LinkButton/LinkButton.stories.d.ts.map +1 -1
  22. package/dist/components/List/List.stories.d.ts.map +1 -1
  23. package/dist/components/PageShell/PageShell.meta.d.ts.map +1 -1
  24. package/dist/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  25. package/dist/components/SideNav/SideNav.meta.d.ts.map +1 -1
  26. package/dist/components/Spinner/Spinner.stories.d.ts.map +1 -1
  27. package/dist/components/Stack/Stack.stories.d.ts.map +1 -1
  28. package/dist/components/StatusIcon/StatusIcon.stories.d.ts.map +1 -1
  29. package/dist/components/Switch/Switch.stories.d.ts.map +1 -1
  30. package/dist/components/Table/Table.stories.d.ts.map +1 -1
  31. package/dist/components/Tabs/Tabs.meta.d.ts.map +1 -1
  32. package/dist/components/Tag/Tag.stories.d.ts.map +1 -1
  33. package/dist/components/Textarea/Textarea.stories.d.ts.map +1 -1
  34. package/dist/components/Toast/Toast.stories.d.ts.map +1 -1
  35. package/dist/components/Toggle/Toggle.stories.d.ts.map +1 -1
  36. package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts.map +1 -1
  37. package/dist/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
  38. package/dist/docs/Spacing.stories.d.ts.map +1 -1
  39. package/dist/docs/Typography.stories.d.ts.map +1 -1
  40. package/dist/docs/helpers.d.ts.map +1 -1
  41. package/dist/index.css +1 -1
  42. package/dist/index.js +209 -197
  43. package/dist/index.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/components/ActionBar/ActionBar.stories.tsx +7 -5
  46. package/src/components/Alert/Alert.stories.tsx +2 -1
  47. package/src/components/Badge/Badge.stories.tsx +3 -2
  48. package/src/components/Button/Button.stories.tsx +3 -2
  49. package/src/components/Callout/Callout.stories.tsx +2 -1
  50. package/src/components/Card/Card.stories.tsx +5 -3
  51. package/src/components/Checkbox/Checkbox.stories.tsx +2 -1
  52. package/src/components/Cluster/Cluster.stories.tsx +4 -3
  53. package/src/components/ColorMode/ColorMode.stories.tsx +2 -1
  54. package/src/components/Field/Field.stories.tsx +3 -2
  55. package/src/components/Grid/Grid.meta.ts +14 -2
  56. package/src/components/Grid/Grid.stories.tsx +24 -4
  57. package/src/components/Grid/Grid.tsx +21 -6
  58. package/src/components/Grid/styles.css.ts +21 -0
  59. package/src/components/Inline/Inline.stories.tsx +5 -4
  60. package/src/components/Input/Input.stories.tsx +2 -1
  61. package/src/components/InputGroup/InputGroup.stories.tsx +2 -1
  62. package/src/components/LinkButton/LinkButton.stories.tsx +3 -2
  63. package/src/components/List/List.stories.tsx +2 -1
  64. package/src/components/PageShell/PageShell.meta.ts +12 -1
  65. package/src/components/ScrollArea/ScrollArea.stories.tsx +12 -9
  66. package/src/components/SideNav/SideNav.meta.ts +5 -0
  67. package/src/components/Spinner/Spinner.stories.tsx +2 -1
  68. package/src/components/Stack/Stack.stories.tsx +5 -4
  69. package/src/components/StatusIcon/StatusIcon.stories.tsx +11 -4
  70. package/src/components/Switch/Switch.stories.tsx +2 -1
  71. package/src/components/Table/Table.stories.tsx +7 -2
  72. package/src/components/Tabs/Tabs.meta.ts +5 -0
  73. package/src/components/Tag/Tag.stories.tsx +11 -3
  74. package/src/components/Textarea/Textarea.stories.tsx +2 -1
  75. package/src/components/ThemeProvider/ThemeProvider.stories.tsx +9 -9
  76. package/src/components/Toast/Toast.stories.tsx +9 -2
  77. package/src/components/Toggle/Toggle.stories.tsx +2 -1
  78. package/src/components/ToggleGroup/ToggleGroup.stories.tsx +2 -1
  79. package/src/components/Tooltip/Tooltip.stories.tsx +3 -2
  80. package/src/docs/Spacing.stories.tsx +12 -11
  81. package/src/docs/Typography.stories.tsx +4 -3
  82. package/src/docs/helpers.tsx +5 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@tanstack/react-virtual": "^3.14.6",
67
- "@duro-app/tokens": "^2.0.1"
67
+ "@duro-app/tokens": "^3.0.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@babel/preset-typescript": "^7.28.0",
@@ -6,6 +6,8 @@ import {ActionBar} from './ActionBar'
6
6
  import {Button} from '../Button/Button'
7
7
  import {Checkbox} from '../Checkbox/Checkbox'
8
8
  import {Table} from '../Table/Table'
9
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
10
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
9
11
 
10
12
  interface ActionBarStoryArgs {
11
13
  onClearSelection: () => void
@@ -40,16 +42,16 @@ const storyStyles = css.create({
40
42
  wrapper: {
41
43
  display: 'flex',
42
44
  flexDirection: 'column',
43
- gap: 32,
44
- padding: 24,
45
+ gap: spacing.xl,
46
+ padding: spacing.lg,
45
47
  },
46
48
  label: {
47
- fontSize: '0.75rem',
48
- fontWeight: 600,
49
+ fontSize: typography.fontSizeXs,
50
+ fontWeight: typography.fontWeightSemibold,
49
51
  textTransform: 'uppercase' as const,
50
52
  letterSpacing: '0.05em',
51
53
  opacity: 0.6,
52
- marginBottom: 8,
54
+ marginBottom: spacing.sm,
53
55
  },
54
56
  })
55
57
 
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Alert} from './Alert'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof Alert> = {
7
8
  title: 'Components/Alert',
@@ -57,7 +58,7 @@ export const NoIcon: Story = {
57
58
  }
58
59
 
59
60
  const stackStyles = css.create({
60
- stack: {display: 'flex', flexDirection: 'column', gap: 12},
61
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.ms},
61
62
  })
62
63
 
63
64
  export const AllVariants: Story = {
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Badge} from './Badge'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof Badge> = {
7
8
  title: 'Components/Badge',
@@ -52,8 +53,8 @@ export const Small: Story = {
52
53
  }
53
54
 
54
55
  const stackStyles = css.create({
55
- row: {display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap'},
56
- stack: {display: 'flex', flexDirection: 'column', gap: 16},
56
+ row: {display: 'flex', alignItems: 'center', gap: spacing.sm, flexWrap: 'wrap'},
57
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.md},
57
58
  })
58
59
 
59
60
  export const AllVariants: Story = {
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect, fn} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Button} from './Button'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof Button> = {
7
8
  title: 'Components/Button',
@@ -83,8 +84,8 @@ export const Disabled: Story = {
83
84
  }
84
85
 
85
86
  const rowStyles = css.create({
86
- row: {display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap'},
87
- stack: {display: 'flex', flexDirection: 'column', gap: 16},
87
+ row: {display: 'flex', gap: spacing.ms, alignItems: 'center', flexWrap: 'wrap'},
88
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.md},
88
89
  })
89
90
 
90
91
  export const AllVariants: Story = {
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Callout} from './Callout'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof Callout> = {
7
8
  title: 'Components/Callout',
@@ -82,7 +83,7 @@ export const NoIcon: Story = {
82
83
  }
83
84
 
84
85
  const stackStyles = css.create({
85
- stack: {display: 'flex', flexDirection: 'column', gap: 12},
86
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.ms},
86
87
  })
87
88
 
88
89
  export const AllVariants: Story = {
@@ -3,6 +3,8 @@ import {expect} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Card} from './Card'
5
5
  import {colors} from '@duro-app/tokens/tokens/colors.css'
6
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
7
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
6
8
 
7
9
  const meta: Meta<typeof Card> = {
8
10
  title: 'Components/Card',
@@ -85,10 +87,10 @@ const gridStyles = css.create({
85
87
  grid: {
86
88
  display: 'grid',
87
89
  gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
88
- gap: 16,
90
+ gap: spacing.md,
89
91
  },
90
- stack: {display: 'flex', flexDirection: 'column', gap: 16},
91
- muted: {color: colors.textMuted, fontSize: '0.875rem'},
92
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.md},
93
+ muted: {color: colors.textMuted, fontSize: typography.fontSizeSm},
92
94
  })
93
95
 
94
96
  export const AllVariants: Story = {
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect, fn} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Checkbox} from './Checkbox'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof Checkbox> = {
7
8
  title: 'Components/Checkbox',
@@ -67,7 +68,7 @@ export const WithoutLabel: Story = {
67
68
  }
68
69
 
69
70
  const stackStyles = css.create({
70
- stack: {display: 'flex', flexDirection: 'column', gap: 12},
71
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.ms},
71
72
  })
72
73
 
73
74
  export const AllVariants: Story = {
@@ -2,8 +2,9 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {css, html} from 'react-strict-dom'
3
3
  import {Cluster} from './Cluster'
4
4
  import {colors} from '@duro-app/tokens/tokens/colors.css'
5
- import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
+ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
6
6
  import {SPACING_KEYS} from '@duro-app/tokens/keys'
7
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
7
8
 
8
9
  const meta: Meta<typeof Cluster> = {
9
10
  title: 'Layout/Cluster',
@@ -35,8 +36,8 @@ const localStyles = css.create({
35
36
  paddingBottom: spacing.xs,
36
37
  paddingLeft: spacing.sm,
37
38
  paddingRight: spacing.sm,
38
- borderRadius: 12,
39
- fontSize: '0.75rem',
39
+ borderRadius: radii.md,
40
+ fontSize: typography.fontSizeXs,
40
41
  whiteSpace: 'nowrap',
41
42
  },
42
43
  narrow: {
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect, fn} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {ColorModeProvider, ColorModeToggle, useColorMode} from './ColorMode'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof ColorModeToggle> = {
7
8
  title: 'Components/ColorMode',
@@ -58,7 +59,7 @@ export const AllModes: Story = {
58
59
  }
59
60
 
60
61
  const readout = css.create({
61
- row: {display: 'flex', alignItems: 'center', gap: 12},
62
+ row: {display: 'flex', alignItems: 'center', gap: spacing.ms},
62
63
  })
63
64
 
64
65
  function Readout() {
@@ -3,6 +3,7 @@ import {expect} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Field} from './Field'
5
5
  import {Input} from '../Input/Input'
6
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
6
7
 
7
8
  const meta: Meta = {
8
9
  title: 'Components/Field',
@@ -62,8 +63,8 @@ export const WithError: Story = {
62
63
  }
63
64
 
64
65
  const stackStyles = css.create({
65
- stack: {display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 400},
66
- stackWide: {display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 600},
66
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.md, maxWidth: 400},
67
+ stackWide: {display: 'flex', flexDirection: 'column', gap: spacing.md, maxWidth: 600},
67
68
  })
68
69
 
69
70
  export const FormExample: Story = {
@@ -2,10 +2,11 @@ import type {ComponentMeta} from '../component-meta'
2
2
 
3
3
  export const meta: ComponentMeta = {
4
4
  description:
5
- 'CSS grid layout. Supports fixed column count (1-6) or responsive auto-fit via minColumnWidth.',
5
+ 'CSS grid layout. Fixed column count (1-6), responsive auto-fit via minColumnWidth, or a named split layout (list/detail, nav/content) that collapses below the md breakpoint.',
6
6
  whenToUse: [
7
7
  'Card grids, dashboard layouts, multi-column forms',
8
8
  'Responsive layouts that should auto-adjust column count',
9
+ 'A list/detail or nav/content screen — layout="split" | "split-wide", never a hand-rolled gridTemplateColumns with its own @media',
9
10
  ],
10
11
  whenNotToUse: [
11
12
  'Single-column vertical layout — use Stack',
@@ -17,8 +18,19 @@ export const meta: ComponentMeta = {
17
18
  kind: 'contrast',
18
19
  relationship: 'Grid for multi-column layouts; Stack for a single vertical column',
19
20
  },
21
+ {
22
+ component: 'split-pane',
23
+ kind: 'composition',
24
+ relationship: 'Grid layout="split" with a List and a Panel is the split-pane recipe',
25
+ },
20
26
  ],
21
- example: `// Responsive: columns auto-fit based on min width
27
+ example: `// Split: list ≥ 240px beside a detail pane, one column below md
28
+ <Grid layout="split" gap="lg">
29
+ <List.Root>…</List.Root>
30
+ <Panel.Root>…</Panel.Root>
31
+ </Grid>
32
+
33
+ // Responsive: columns auto-fit based on min width
22
34
  <Grid minColumnWidth="280px" gap="md">
23
35
  <Card>A</Card>
24
36
  <Card>B</Card>
@@ -3,9 +3,10 @@ import {css, html} from 'react-strict-dom'
3
3
  import {Grid} from './Grid'
4
4
  import {Stack} from '../Stack/Stack'
5
5
  import {colors} from '@duro-app/tokens/tokens/colors.css'
6
- import {spacing} from '@duro-app/tokens/tokens/spacing.css'
6
+ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
7
7
  import {SPACING_KEYS} from '@duro-app/tokens/keys'
8
8
  import {useContainerQuery} from '../../hooks/useContainerQuery'
9
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
9
10
 
10
11
  const meta: Meta<typeof Grid> = {
11
12
  title: 'Layout/Grid',
@@ -20,6 +21,7 @@ const meta: Meta<typeof Grid> = {
20
21
  options: [1, 2, 3, 4, 5, 6],
21
22
  },
22
23
  minColumnWidth: {control: 'text'},
24
+ layout: {control: 'select', options: [undefined, 'split', 'split-wide']},
23
25
  },
24
26
  }
25
27
 
@@ -31,12 +33,12 @@ const localStyles = css.create({
31
33
  backgroundColor: colors.accent,
32
34
  color: colors.accentContrast,
33
35
  padding: spacing.md,
34
- borderRadius: 4,
36
+ borderRadius: radii.xs,
35
37
  textAlign: 'center',
36
- fontSize: '0.875rem',
38
+ fontSize: typography.fontSizeSm,
37
39
  },
38
40
  label: {
39
- fontSize: '0.75rem',
41
+ fontSize: typography.fontSizeXs,
40
42
  color: colors.textMuted,
41
43
  },
42
44
  })
@@ -83,6 +85,24 @@ export const TwoColumns: Story = {
83
85
  ),
84
86
  }
85
87
 
88
+ export const Split: Story = {
89
+ render: () => (
90
+ <Stack gap="sm">
91
+ <html.span style={localStyles.label}>
92
+ layout=&quot;split&quot; — list ≥ 240px beside the detail, one column below md
93
+ </html.span>
94
+ <Grid layout="split" gap="lg">
95
+ <Cell>list</Cell>
96
+ <Cell>detail</Cell>
97
+ </Grid>
98
+ <Grid layout="split-wide" gap="lg">
99
+ <Cell>nav</Cell>
100
+ <Cell>content</Cell>
101
+ </Grid>
102
+ </Stack>
103
+ ),
104
+ }
105
+
86
106
  export const WithContainerQuery: Story = {
87
107
  render: function Render() {
88
108
  const {ref, size} = useContainerQuery<HTMLDivElement>()
@@ -3,10 +3,18 @@ import {html} from 'react-strict-dom'
3
3
  import {styles} from './styles.css'
4
4
  import type {SpacingToken} from '@duro-app/tokens/keys'
5
5
 
6
+ export type GridLayout = 'split' | 'split-wide'
7
+
6
8
  interface GridProps {
7
9
  gap?: SpacingToken
8
10
  columns?: 1 | 2 | 3 | 4 | 5 | 6
9
11
  minColumnWidth?: string
12
+ /**
13
+ * A named responsive layout. `split` is list/detail (1:2, list ≥ 240px),
14
+ * `split-wide` is nav/content (1:3, nav ≥ 280px); both collapse to one
15
+ * column below the `md` breakpoint. Wins over `columns` / `minColumnWidth`.
16
+ */
17
+ layout?: GridLayout
10
18
  children: ReactNode
11
19
  }
12
20
 
@@ -30,12 +38,19 @@ const columnsMap = {
30
38
  6: styles.col6,
31
39
  } as const
32
40
 
33
- export function Grid({gap = 'md', columns, minColumnWidth, children}: GridProps) {
34
- const columnStyle = minColumnWidth
35
- ? styles.autoFit(minColumnWidth)
36
- : columns
37
- ? columnsMap[columns]
38
- : undefined
41
+ const layoutMap = {
42
+ split: styles.split,
43
+ 'split-wide': styles.splitWide,
44
+ } as const satisfies Record<GridLayout, unknown>
45
+
46
+ export function Grid({gap = 'md', columns, minColumnWidth, layout, children}: GridProps) {
47
+ const columnStyle = layout
48
+ ? layoutMap[layout]
49
+ : minColumnWidth
50
+ ? styles.autoFit(minColumnWidth)
51
+ : columns
52
+ ? columnsMap[columns]
53
+ : undefined
39
54
 
40
55
  return <html.div style={[styles.base, gapMap[gap], columnStyle]}>{children}</html.div>
41
56
  }
@@ -1,5 +1,10 @@
1
1
  import {css} from 'react-strict-dom'
2
2
  import {spacing} from '@duro-app/tokens/tokens/spacing.css'
3
+ import {breakpoints} from '@duro-app/tokens/tokens/breakpoints.css'
4
+
5
+ // Where a split collapses to one column. A defineConsts string, inlined into
6
+ // the @media text at build time.
7
+ const SPLIT_BP = breakpoints.md
3
8
 
4
9
  export const styles = css.create({
5
10
  base: {
@@ -14,6 +19,22 @@ export const styles = css.create({
14
19
  autoFit: (minWidth: string) => ({
15
20
  gridTemplateColumns: `repeat(auto-fill, minmax(${minWidth}, 1fr))`,
16
21
  }),
22
+ // Asymmetric list/detail splits: a bounded first column, the rest for the
23
+ // detail; one column below the md breakpoint. Static entries rather than a
24
+ // parameter because the pair (widths + collapse point) is the design
25
+ // decision — a screen picks a split, it does not tune one.
26
+ split: {
27
+ gridTemplateColumns: {
28
+ default: '1fr',
29
+ [`@media (min-width: ${SPLIT_BP})`]: 'minmax(240px, 1fr) minmax(0, 2fr)',
30
+ },
31
+ },
32
+ splitWide: {
33
+ gridTemplateColumns: {
34
+ default: '1fr',
35
+ [`@media (min-width: ${SPLIT_BP})`]: 'minmax(280px, 1fr) minmax(0, 3fr)',
36
+ },
37
+ },
17
38
  gapXs: {gap: spacing.xs},
18
39
  gapSm: {gap: spacing.sm},
19
40
  gapMs: {gap: spacing.ms},
@@ -2,8 +2,9 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {css, html} from 'react-strict-dom'
3
3
  import {Inline} from './Inline'
4
4
  import {colors} from '@duro-app/tokens/tokens/colors.css'
5
- import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
+ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
6
6
  import {SPACING_KEYS} from '@duro-app/tokens/keys'
7
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
7
8
 
8
9
  const meta: Meta<typeof Inline> = {
9
10
  title: 'Layout/Inline',
@@ -32,12 +33,12 @@ const localStyles = css.create({
32
33
  backgroundColor: colors.accent,
33
34
  color: colors.accentContrast,
34
35
  padding: spacing.sm,
35
- borderRadius: 4,
36
+ borderRadius: radii.xs,
36
37
  textAlign: 'center',
37
- fontSize: '0.875rem',
38
+ fontSize: typography.fontSizeSm,
38
39
  },
39
40
  iconLg: {
40
- fontSize: '1.25rem',
41
+ fontSize: typography.fontSizeXl,
41
42
  },
42
43
  })
43
44
 
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect, fn} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Input} from './Input'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof Input> = {
7
8
  title: 'Components/Input',
@@ -66,7 +67,7 @@ export const Disabled: Story = {
66
67
  }
67
68
 
68
69
  const stackStyles = css.create({
69
- stack: {display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 320},
70
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.ms, maxWidth: 320},
70
71
  })
71
72
 
72
73
  export const AllVariants: Story = {
@@ -3,6 +3,7 @@ import {expect, fn} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {InputGroup} from './InputGroup'
5
5
  import {Input} from '../Input/Input'
6
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
6
7
 
7
8
  const meta: Meta = {
8
9
  title: 'Components/InputGroup',
@@ -94,7 +95,7 @@ export const StandaloneInput: Story = {
94
95
 
95
96
  const layoutStyles = css.create({
96
97
  container: {maxWidth: 400},
97
- stack: {display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 400},
98
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.md, maxWidth: 400},
98
99
  })
99
100
 
100
101
  export const AllVariants: Story = {
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {LinkButton} from './LinkButton'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof LinkButton> = {
7
8
  title: 'Components/LinkButton',
@@ -61,8 +62,8 @@ export const ExternalLink: Story = {
61
62
  }
62
63
 
63
64
  const rowStyles = css.create({
64
- row: {display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap'},
65
- stack: {display: 'flex', flexDirection: 'column', gap: 16},
65
+ row: {display: 'flex', gap: spacing.ms, alignItems: 'center', flexWrap: 'wrap'},
66
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.md},
66
67
  })
67
68
 
68
69
  export const AllVariants: Story = {
@@ -7,6 +7,7 @@ import {Badge} from '../Badge/Badge'
7
7
  import {Button} from '../Button/Button'
8
8
  import {Checkbox} from '../Checkbox/Checkbox'
9
9
  import {colors} from '@duro-app/tokens/tokens/colors.css'
10
+ import {radii} from '@duro-app/tokens/tokens/spacing.css'
10
11
 
11
12
  const meta: Meta = {
12
13
  title: 'Components/List',
@@ -76,7 +77,7 @@ const narrowContainer = css.create({
76
77
  borderWidth: 1,
77
78
  borderStyle: 'solid',
78
79
  borderColor: colors.border,
79
- borderRadius: 8,
80
+ borderRadius: radii.sm,
80
81
  overflow: 'hidden',
81
82
  },
82
83
  })
@@ -9,7 +9,7 @@ export const meta: ComponentMeta = {
9
9
  ],
10
10
  whenNotToUse: [
11
11
  'Nested section layouts — use Stack or Card',
12
- 'Sidebar layouts — combine with SideNav manually',
12
+ 'Sidebar layouts — start from the page-with-sidenav recipe (PageShell + Grid layout="split-wide" + SideNav)',
13
13
  ],
14
14
  relatedTo: [
15
15
  {
@@ -18,6 +18,17 @@ export const meta: ComponentMeta = {
18
18
  relationship: 'Use Stack for content within PageShell',
19
19
  },
20
20
  {component: 'SideNav', kind: 'composition', relationship: 'Often placed alongside PageShell'},
21
+ {
22
+ component: 'page-with-sidenav',
23
+ kind: 'composition',
24
+ relationship:
25
+ 'PageShell with a SideNav rail is the page-with-sidenav recipe — copy it, do not lay it out by hand',
26
+ },
27
+ {
28
+ component: 'admin-detail-page',
29
+ kind: 'composition',
30
+ relationship: 'PageShell with a record heading and Tabs is the admin-detail-page recipe',
31
+ },
21
32
  ],
22
33
  example: `<PageShell maxWidth="lg" padding="md" header={<Heading level={1}>Dashboard</Heading>}>
23
34
  <Stack gap="lg">
@@ -2,6 +2,9 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {ScrollArea} from './ScrollArea'
5
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
6
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
7
+ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
5
8
 
6
9
  const meta: Meta = {
7
10
  title: 'Components/ScrollArea',
@@ -15,19 +18,19 @@ const demoStyles = css.create({
15
18
  width: 300,
16
19
  borderWidth: 1,
17
20
  borderStyle: 'solid',
18
- borderColor: '#333333',
19
- borderRadius: 8,
21
+ borderColor: colors.border,
22
+ borderRadius: radii.sm,
20
23
  },
21
24
  item: {
22
- paddingTop: 12,
23
- paddingBottom: 12,
24
- paddingLeft: 16,
25
- paddingRight: 16,
25
+ paddingTop: spacing.ms,
26
+ paddingBottom: spacing.ms,
27
+ paddingLeft: spacing.md,
28
+ paddingRight: spacing.md,
26
29
  borderBottomWidth: 1,
27
30
  borderBottomStyle: 'solid',
28
- borderBottomColor: '#1a1a1a',
29
- fontSize: 14,
30
- color: '#e5e5e5',
31
+ borderBottomColor: colors.bgCard,
32
+ fontSize: typography.fontSizeSm,
33
+ color: colors.text,
31
34
  },
32
35
  })
33
36
 
@@ -26,6 +26,11 @@ export const meta: ComponentMeta = {
26
26
  relationship: 'Tabs switch content in-place; SideNav navigates pages',
27
27
  },
28
28
  {component: 'PageShell', kind: 'composition', relationship: 'Often placed alongside PageShell'},
29
+ {
30
+ component: 'page-with-sidenav',
31
+ kind: 'composition',
32
+ relationship: 'SideNav beside routed content is the page-with-sidenav recipe',
33
+ },
29
34
  ],
30
35
  example: `<SideNav.Root defaultValue="identities">
31
36
  {/* Default: always-open sections. Nothing is a click away from being seen. */}
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {expect} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Spinner} from './Spinner'
5
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
6
 
6
7
  const meta: Meta<typeof Spinner> = {
7
8
  title: 'Components/Spinner',
@@ -41,7 +42,7 @@ export const CustomLabel: Story = {
41
42
  }
42
43
 
43
44
  const rowStyles = css.create({
44
- row: {display: 'flex', gap: 24, alignItems: 'center'},
45
+ row: {display: 'flex', gap: spacing.lg, alignItems: 'center'},
45
46
  })
46
47
 
47
48
  export const AllSizes: Story = {
@@ -2,8 +2,9 @@ import type {Meta, StoryObj} from '@storybook/react'
2
2
  import {css, html} from 'react-strict-dom'
3
3
  import {Stack} from './Stack'
4
4
  import {colors} from '@duro-app/tokens/tokens/colors.css'
5
- import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
+ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
6
6
  import {SPACING_KEYS} from '@duro-app/tokens/keys'
7
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
7
8
 
8
9
  const meta: Meta<typeof Stack> = {
9
10
  title: 'Layout/Stack',
@@ -28,12 +29,12 @@ const localStyles = css.create({
28
29
  backgroundColor: colors.accent,
29
30
  color: colors.accentContrast,
30
31
  padding: spacing.sm,
31
- borderRadius: 4,
32
+ borderRadius: radii.xs,
32
33
  textAlign: 'center',
33
- fontSize: '0.875rem',
34
+ fontSize: typography.fontSizeSm,
34
35
  },
35
36
  label: {
36
- fontSize: '0.75rem',
37
+ fontSize: typography.fontSizeXs,
37
38
  color: colors.textMuted,
38
39
  },
39
40
  })