@duro-app/ui 0.12.1 → 0.14.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 (134) hide show
  1. package/dist/components/PageShell/PageShell.d.ts +15 -0
  2. package/dist/components/PageShell/PageShell.d.ts.map +1 -0
  3. package/dist/components/PageShell/index.d.ts +3 -0
  4. package/dist/components/PageShell/index.d.ts.map +1 -0
  5. package/dist/components/PageShell/styles.css.d.ts +41 -0
  6. package/dist/components/PageShell/styles.css.d.ts.map +1 -0
  7. package/dist/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  8. package/dist/index.css +1 -1
  9. package/dist/index.d.ts +1 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3283 -3434
  12. package/dist/index.js.map +1 -1
  13. package/package.json +4 -4
  14. package/src/components/Alert/Alert.stories.tsx +76 -0
  15. package/src/components/Alert/Alert.tsx +45 -0
  16. package/src/components/Alert/styles.css.ts +50 -0
  17. package/src/components/Badge/Badge.stories.tsx +94 -0
  18. package/src/components/Badge/Badge.tsx +21 -0
  19. package/src/components/Badge/styles.css.ts +51 -0
  20. package/src/components/Button/Button.stories.tsx +130 -0
  21. package/src/components/Button/Button.tsx +48 -0
  22. package/src/components/Button/styles.css.ts +107 -0
  23. package/src/components/Callout/Callout.stories.tsx +97 -0
  24. package/src/components/Callout/Callout.tsx +39 -0
  25. package/src/components/Callout/index.ts +1 -0
  26. package/src/components/Callout/styles.css.ts +45 -0
  27. package/src/components/Card/Card.stories.tsx +119 -0
  28. package/src/components/Card/Card.tsx +35 -0
  29. package/src/components/Card/styles.css.ts +67 -0
  30. package/src/components/Checkbox/Checkbox.stories.tsx +88 -0
  31. package/src/components/Checkbox/Checkbox.tsx +73 -0
  32. package/src/components/Checkbox/styles.css.ts +57 -0
  33. package/src/components/Cluster/Cluster.stories.tsx +92 -0
  34. package/src/components/Cluster/Cluster.tsx +43 -0
  35. package/src/components/Cluster/styles.css.ts +25 -0
  36. package/src/components/EmptyState/EmptyState.stories.tsx +54 -0
  37. package/src/components/EmptyState/EmptyState.tsx +19 -0
  38. package/src/components/EmptyState/styles.css.ts +25 -0
  39. package/src/components/Field/Field.stories.tsx +92 -0
  40. package/src/components/Field/Field.tsx +80 -0
  41. package/src/components/Field/FieldContext.ts +14 -0
  42. package/src/components/Field/styles.css.ts +25 -0
  43. package/src/components/Fieldset/Fieldset.stories.tsx +85 -0
  44. package/src/components/Fieldset/Fieldset.tsx +48 -0
  45. package/src/components/Fieldset/index.ts +1 -0
  46. package/src/components/Fieldset/styles.css.ts +33 -0
  47. package/src/components/Grid/Grid.stories.tsx +107 -0
  48. package/src/components/Grid/Grid.tsx +41 -0
  49. package/src/components/Grid/styles.css.ts +25 -0
  50. package/src/components/Heading/Heading.tsx +48 -0
  51. package/src/components/Heading/styles.css.ts +26 -0
  52. package/src/components/Icon/Icon.tsx +168 -0
  53. package/src/components/Icon/index.ts +2 -0
  54. package/src/components/Inline/Inline.stories.tsx +88 -0
  55. package/src/components/Inline/Inline.tsx +45 -0
  56. package/src/components/Inline/styles.css.ts +27 -0
  57. package/src/components/Input/Input.stories.tsx +89 -0
  58. package/src/components/Input/Input.tsx +77 -0
  59. package/src/components/Input/styles.css.ts +60 -0
  60. package/src/components/InputGroup/InputGroup.stories.tsx +119 -0
  61. package/src/components/InputGroup/InputGroup.tsx +60 -0
  62. package/src/components/InputGroup/InputGroupContext.ts +11 -0
  63. package/src/components/InputGroup/styles.css.ts +61 -0
  64. package/src/components/LinkButton/LinkButton.stories.tsx +91 -0
  65. package/src/components/LinkButton/LinkButton.tsx +42 -0
  66. package/src/components/LinkButton/styles.css.ts +56 -0
  67. package/src/components/Menu/Menu.stories.tsx +146 -0
  68. package/src/components/Menu/Menu.tsx +151 -0
  69. package/src/components/Menu/MenuContext.ts +20 -0
  70. package/src/components/Menu/styles.css.ts +89 -0
  71. package/src/components/Menu/useMenuRoot.ts +136 -0
  72. package/src/components/PageShell/PageShell.tsx +45 -0
  73. package/src/components/PageShell/index.ts +2 -0
  74. package/src/components/PageShell/styles.css.ts +26 -0
  75. package/src/components/ScrollArea/ScrollArea.stories.tsx +82 -0
  76. package/src/components/ScrollArea/ScrollArea.tsx +170 -0
  77. package/src/components/ScrollArea/ScrollAreaContext.ts +21 -0
  78. package/src/components/ScrollArea/styles.css.ts +81 -0
  79. package/src/components/ScrollArea/useScrollAreaRoot.ts +72 -0
  80. package/src/components/Select/Select.stories.tsx +144 -0
  81. package/src/components/Select/Select.tsx +183 -0
  82. package/src/components/Select/SelectContext.ts +24 -0
  83. package/src/components/Select/styles.css.ts +97 -0
  84. package/src/components/Select/useSelectRoot.ts +178 -0
  85. package/src/components/SideNav/SideNav.stories.tsx +77 -0
  86. package/src/components/SideNav/SideNav.tsx +172 -0
  87. package/src/components/SideNav/SideNavContext.ts +18 -0
  88. package/src/components/SideNav/styles.css.ts +95 -0
  89. package/src/components/Spinner/Spinner.stories.tsx +59 -0
  90. package/src/components/Spinner/Spinner.tsx +24 -0
  91. package/src/components/Spinner/styles.css.ts +47 -0
  92. package/src/components/Stack/Stack.stories.tsx +103 -0
  93. package/src/components/Stack/Stack.tsx +33 -0
  94. package/src/components/Stack/styles.css.ts +21 -0
  95. package/src/components/StatusIcon/StatusIcon.stories.tsx +81 -0
  96. package/src/components/StatusIcon/StatusIcon.tsx +24 -0
  97. package/src/components/StatusIcon/styles.css.ts +27 -0
  98. package/src/components/Switch/Switch.stories.tsx +88 -0
  99. package/src/components/Switch/Switch.tsx +78 -0
  100. package/src/components/Switch/styles.css.ts +71 -0
  101. package/src/components/Table/Table.stories.tsx +308 -0
  102. package/src/components/Table/Table.tsx +179 -0
  103. package/src/components/Table/styles.css.ts +97 -0
  104. package/src/components/Tabs/Tabs.stories.tsx +142 -0
  105. package/src/components/Tabs/Tabs.tsx +210 -0
  106. package/src/components/Tabs/TabsContext.ts +20 -0
  107. package/src/components/Tabs/styles.css.ts +98 -0
  108. package/src/components/Tabs/useTabsRoot.ts +42 -0
  109. package/src/components/Text/Text.tsx +52 -0
  110. package/src/components/Text/styles.css.ts +57 -0
  111. package/src/components/Textarea/Textarea.stories.tsx +80 -0
  112. package/src/components/Textarea/Textarea.tsx +50 -0
  113. package/src/components/Textarea/styles.css.ts +56 -0
  114. package/src/components/ThemeProvider/ThemeProvider.stories.tsx +163 -0
  115. package/src/components/ThemeProvider/ThemeProvider.tsx +33 -0
  116. package/src/components/Toggle/Toggle.stories.tsx +84 -0
  117. package/src/components/Toggle/Toggle.tsx +85 -0
  118. package/src/components/Toggle/styles.css.ts +66 -0
  119. package/src/components/ToggleGroup/ToggleGroup.stories.tsx +159 -0
  120. package/src/components/ToggleGroup/ToggleGroup.tsx +63 -0
  121. package/src/components/ToggleGroup/ToggleGroupContext.ts +18 -0
  122. package/src/components/ToggleGroup/styles.css.ts +17 -0
  123. package/src/components/Tooltip/Tooltip.stories.tsx +127 -0
  124. package/src/components/Tooltip/Tooltip.tsx +97 -0
  125. package/src/components/Tooltip/styles.css.ts +56 -0
  126. package/src/docs/Spacing.mdx +80 -0
  127. package/src/docs/Spacing.stories.tsx +202 -0
  128. package/src/docs/Typography.mdx +93 -0
  129. package/src/docs/Typography.stories.tsx +211 -0
  130. package/src/docs/helpers.tsx +135 -0
  131. package/src/hooks/useContainerQuery.ts +54 -0
  132. package/src/hooks/useControllableValue.ts +18 -0
  133. package/src/index.ts +56 -0
  134. package/src/stubs/assets-registry.ts +3 -0
@@ -0,0 +1,93 @@
1
+ {/* packages/ui/src/docs/Typography.mdx */}
2
+ import { Meta } from '@storybook/addon-docs/blocks'
3
+
4
+ <Meta title="Foundations/Typography/Overview" />
5
+
6
+ # Typography System
7
+
8
+ The typography system uses **three layers** — a primitive type scale, semantic presets, and components — to create consistent, expressive typography throughout the UI.
9
+
10
+ ## Philosophy
11
+
12
+ - **9-step scale** — from 12px captions to 36px headings, each step has a matched line-height for vertical rhythm.
13
+ - **24px baseline grid** — body text (16px/24px) and headings snap to multiples of the grid.
14
+ - **Semantic presets** give meaning to the scale: `bodyMd`, `headingLg`, `caption` tell you _what_ the text is, not just _how big_.
15
+ - **`Text` and `Heading` components** apply presets so you rarely write raw font styles.
16
+
17
+ ## Primitive Type Scale
18
+
19
+ The base 9-step scale, defined in `typeScale`:
20
+
21
+ | Step | Token | Size | Line-height | Use case |
22
+ | ---- | ----------- | ---- | ----------- | -------------------- |
23
+ | 1 | `fontSize1` | 12px | 16px | Captions, badges |
24
+ | 2 | `fontSize2` | 13px | 20px | Small UI |
25
+ | 3 | `fontSize3` | 14px | 20px | Default UI (buttons) |
26
+ | 4 | `fontSize4` | 16px | 24px | Body text |
27
+ | 5 | `fontSize5` | 18px | 24px | Body large |
28
+ | 6 | `fontSize6` | 20px | 28px | Subheading |
29
+ | 7 | `fontSize7` | 24px | 32px | Heading small |
30
+ | 8 | `fontSize8` | 30px | 36px | Heading medium |
31
+ | 9 | `fontSize9` | 36px | 44px | Heading large |
32
+
33
+ See the **Primitive Scale** story for live samples at each size.
34
+
35
+ ## Semantic Presets
36
+
37
+ Presets compose scale tokens + weight + tracking into named styles via `typePresets`:
38
+
39
+ ### Body
40
+
41
+ | Preset | Size | Weight | Use case |
42
+ | -------- | ----- | ------ | --------------------------- |
43
+ | `bodySm` | 14/20 | Normal | Compact UI text |
44
+ | `bodyMd` | 16/24 | Normal | Standard paragraphs |
45
+ | `bodyLg` | 18/24 | Normal | Emphasized intros, callouts |
46
+
47
+ ### UI
48
+
49
+ | Preset | Size | Weight | Notes |
50
+ | ---------- | ----- | -------- | ------------------------ |
51
+ | `caption` | 12/16 | Normal | Wide tracking |
52
+ | `label` | 14/20 | Medium | Form labels |
53
+ | `code` | 14/20 | Normal | Monospace font |
54
+ | `overline` | 12/16 | Semibold | Uppercase, wide tracking |
55
+
56
+ ### Heading
57
+
58
+ | Preset | Size | Weight | Tracking |
59
+ | ----------- | ----- | -------- | -------- |
60
+ | `headingSm` | 20/28 | Semibold | Tight |
61
+ | `headingMd` | 24/32 | Semibold | Tight |
62
+ | `headingLg` | 30/36 | Bold | Tight |
63
+ | `headingXl` | 36/44 | Bold | Tight |
64
+
65
+ ### Display (fluid)
66
+
67
+ | Preset | Range | Weight | Notes |
68
+ | ----------- | ------- | ------ | ------------------- |
69
+ | `displaySm` | 36–48px | Bold | Fluid via `clamp()` |
70
+ | `displayMd` | 44–60px | Bold | Fluid via `clamp()` |
71
+ | `displayLg` | 56–72px | Bold | Fluid via `clamp()` |
72
+
73
+ See the **Fluid Display** story for a resizable demo.
74
+
75
+ ## Letter Spacing
76
+
77
+ | Token | Value | Use case |
78
+ | --------------------- | ------- | ---------------------- |
79
+ | `letterSpacingTight` | -0.02em | Headings, display text |
80
+ | `letterSpacingNormal` | 0 | Body text |
81
+ | `letterSpacingWide` | 0.04em | Captions, overlines |
82
+
83
+ ## When to use which layer
84
+
85
+ | Scenario | Use |
86
+ | ---------------------------------------------- | ----------------------------------------- |
87
+ | Body text in a page or card | `<Text variant="bodyMd">` |
88
+ | Section heading | `<Heading level={2}>` |
89
+ | Hero or landing page title | `<Heading level={1} variant="displayLg">` |
90
+ | Form label | `<Text variant="label">` |
91
+ | Inline code | `<Text variant="code">` |
92
+ | Custom component needing raw font size | `typeScale.fontSize4` token |
93
+ | Composing a one-off style with preset defaults | `typePresets.bodyMd` in `css.create()` |
@@ -0,0 +1,211 @@
1
+ import type {Meta, StoryObj} from '@storybook/react'
2
+ import {css, html} from 'react-strict-dom'
3
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
4
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
+ import {typePresets} from '@duro-app/tokens/tokens/type-presets.css'
6
+ import {Stack} from '../components/Stack/Stack'
7
+ import {Text} from '../components/Text/Text'
8
+ import {Heading} from '../components/Heading/Heading'
9
+ import {TypeScaleTable} from './helpers'
10
+
11
+ const meta: Meta = {
12
+ title: 'Foundations/Typography',
13
+ }
14
+
15
+ export default meta
16
+
17
+ const scaleRows = [
18
+ {step: 1, token: 'fontSize1', size: '0.75rem', lineHeight: '1rem'},
19
+ {step: 2, token: 'fontSize2', size: '0.8125rem', lineHeight: '1.25rem'},
20
+ {step: 3, token: 'fontSize3', size: '0.875rem', lineHeight: '1.25rem'},
21
+ {step: 4, token: 'fontSize4', size: '1rem', lineHeight: '1.5rem'},
22
+ {step: 5, token: 'fontSize5', size: '1.125rem', lineHeight: '1.5rem'},
23
+ {step: 6, token: 'fontSize6', size: '1.25rem', lineHeight: '1.75rem'},
24
+ {step: 7, token: 'fontSize7', size: '1.5rem', lineHeight: '2rem'},
25
+ {step: 8, token: 'fontSize8', size: '1.875rem', lineHeight: '2.25rem'},
26
+ {step: 9, token: 'fontSize9', size: '2.25rem', lineHeight: '2.75rem'},
27
+ ]
28
+
29
+ export const PrimitiveScale: StoryObj = {
30
+ render: () => (
31
+ <html.div style={localStyles.wrapper}>
32
+ <TypeScaleTable rows={scaleRows} label="9-Step Type Scale" />
33
+ </html.div>
34
+ ),
35
+ }
36
+
37
+ const presetEntries: Array<{name: string; key: keyof typeof typePresets; sample: string}> = [
38
+ {name: 'bodySm', key: 'bodySm', sample: 'Body small — default UI text at 14px.'},
39
+ {name: 'bodyMd', key: 'bodyMd', sample: 'Body medium — standard body text at 16px.'},
40
+ {name: 'bodyLg', key: 'bodyLg', sample: 'Body large — emphasized body text at 18px.'},
41
+ {name: 'caption', key: 'caption', sample: 'Caption — supplementary info at 12px.'},
42
+ {name: 'label', key: 'label', sample: 'Label — form labels and UI labels at 14px medium.'},
43
+ {name: 'code', key: 'code', sample: 'const x = "Code — monospace for inline code"'},
44
+ {name: 'overline', key: 'overline', sample: 'Overline — section labels, uppercase'},
45
+ {name: 'headingSm', key: 'headingSm', sample: 'Heading Small'},
46
+ {name: 'headingMd', key: 'headingMd', sample: 'Heading Medium'},
47
+ {name: 'headingLg', key: 'headingLg', sample: 'Heading Large'},
48
+ {name: 'headingXl', key: 'headingXl', sample: 'Heading Extra Large'},
49
+ {name: 'displaySm', key: 'displaySm', sample: 'Display Small'},
50
+ {name: 'displayMd', key: 'displayMd', sample: 'Display Medium'},
51
+ {name: 'displayLg', key: 'displayLg', sample: 'Display Large'},
52
+ ]
53
+
54
+ export const SemanticPresets: StoryObj = {
55
+ render: () => (
56
+ <html.div style={localStyles.wrapper}>
57
+ <Stack gap="lg">
58
+ {presetEntries.map(({name, key, sample}) => (
59
+ <html.div key={name}>
60
+ <html.span style={localStyles.presetLabel}>{name}</html.span>
61
+ <html.div style={typePresets[key]}>{sample}</html.div>
62
+ </html.div>
63
+ ))}
64
+ </Stack>
65
+ </html.div>
66
+ ),
67
+ }
68
+
69
+ export const BaselineGrid: StoryObj = {
70
+ render: () => {
71
+ const gridLines = Array.from({length: 8}, (_, i) => i)
72
+ return (
73
+ <html.div style={localStyles.wrapper}>
74
+ <Stack gap="md">
75
+ <html.p style={localStyles.hint}>
76
+ Text overlaid on a 24px baseline grid showing vertical rhythm alignment.
77
+ </html.p>
78
+ <html.div style={localStyles.gridContainer}>
79
+ {gridLines.map((i) => (
80
+ <html.div key={i} style={localStyles.gridLine} />
81
+ ))}
82
+ <html.div style={localStyles.gridOverlay}>
83
+ <html.div style={typePresets.bodyMd}>Body medium sits on the 24px grid.</html.div>
84
+ <html.div style={typePresets.headingMd}>Heading medium aligns too.</html.div>
85
+ <html.div style={typePresets.bodySm}>Body small text flows naturally below.</html.div>
86
+ </html.div>
87
+ </html.div>
88
+ </Stack>
89
+ </html.div>
90
+ )
91
+ },
92
+ }
93
+
94
+ export const FluidDisplay: StoryObj = {
95
+ render: () => (
96
+ <html.div style={localStyles.wrapper}>
97
+ <Stack gap="md">
98
+ <html.p style={localStyles.hint}>
99
+ Drag the right edge of the container to resize. Display headings scale fluidly between
100
+ their min and max sizes using clamp().
101
+ </html.p>
102
+ <html.div style={localStyles.resizableContainer}>
103
+ <Stack gap="md">
104
+ <html.div style={typePresets.displayLg}>Display Large</html.div>
105
+ <html.div style={typePresets.displayMd}>Display Medium</html.div>
106
+ <html.div style={typePresets.displaySm}>Display Small</html.div>
107
+ </Stack>
108
+ </html.div>
109
+ </Stack>
110
+ </html.div>
111
+ ),
112
+ }
113
+
114
+ export const HeadingLevels: StoryObj = {
115
+ render: () => (
116
+ <html.div style={localStyles.wrapper}>
117
+ <Stack gap="lg">
118
+ <Heading level={1}>Heading Level 1 — headingXl</Heading>
119
+ <Heading level={2}>Heading Level 2 — headingLg</Heading>
120
+ <Heading level={3}>Heading Level 3 — headingMd</Heading>
121
+ <Heading level={4}>Heading Level 4 — headingSm</Heading>
122
+ <Heading level={5}>Heading Level 5 — headingSm</Heading>
123
+ <Heading level={6}>Heading Level 6 — headingSm</Heading>
124
+ </Stack>
125
+ </html.div>
126
+ ),
127
+ }
128
+
129
+ export const TextVariants: StoryObj = {
130
+ render: () => (
131
+ <html.div style={localStyles.wrapper}>
132
+ <Stack gap="lg">
133
+ <Text variant="bodyMd" as="p">
134
+ Body medium — the default paragraph text.
135
+ </Text>
136
+ <Text variant="bodySm" as="p">
137
+ Body small — compact UI text for secondary content.
138
+ </Text>
139
+ <Text variant="bodyLg" as="p">
140
+ Body large — emphasized text for intros or callouts.
141
+ </Text>
142
+ <Text variant="caption" color="muted">
143
+ Caption — timestamps, footnotes, metadata
144
+ </Text>
145
+ <Text variant="label">Label — form labels and input descriptions</Text>
146
+ <Text variant="code">const greeting = &quot;Hello, world!&quot;</Text>
147
+ <Text variant="overline" color="muted">
148
+ Overline — section dividers
149
+ </Text>
150
+ <Stack gap="sm">
151
+ <Text variant="bodyMd" color="accent">
152
+ Accent colored text
153
+ </Text>
154
+ <Text variant="bodyMd" color="error">
155
+ Error colored text
156
+ </Text>
157
+ <Text variant="bodyMd" color="success">
158
+ Success colored text
159
+ </Text>
160
+ <Text variant="bodyMd" color="warning">
161
+ Warning colored text
162
+ </Text>
163
+ </Stack>
164
+ </Stack>
165
+ </html.div>
166
+ ),
167
+ }
168
+
169
+ const localStyles = css.create({
170
+ wrapper: {
171
+ maxWidth: 720,
172
+ },
173
+ hint: {
174
+ fontSize: '0.75rem',
175
+ color: colors.textMuted,
176
+ fontStyle: 'italic',
177
+ },
178
+ presetLabel: {
179
+ display: 'block',
180
+ fontSize: '0.6875rem',
181
+ fontFamily: 'monospace',
182
+ color: colors.textMuted,
183
+ marginBottom: 4,
184
+ },
185
+ gridContainer: {
186
+ position: 'relative',
187
+ },
188
+ gridLine: {
189
+ height: 24,
190
+ borderBottomWidth: 1,
191
+ borderBottomStyle: 'dashed',
192
+ borderBottomColor: 'rgba(0,0,0,0.08)',
193
+ },
194
+ gridOverlay: {
195
+ position: 'absolute',
196
+ top: 0,
197
+ left: 0,
198
+ right: 0,
199
+ },
200
+ resizableContainer: {
201
+ resize: 'horizontal',
202
+ overflow: 'auto',
203
+ borderWidth: 2,
204
+ borderStyle: 'dashed',
205
+ borderColor: colors.border,
206
+ padding: spacing.md,
207
+ minWidth: 280,
208
+ maxWidth: '100%',
209
+ width: 700,
210
+ },
211
+ })
@@ -0,0 +1,135 @@
1
+ import {css, html} from 'react-strict-dom'
2
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
+ import {typography, typeScale} from '@duro-app/tokens/tokens/typography.css'
4
+ import {Table} from '../components/Table/Table'
5
+
6
+ const styles = css.create({
7
+ bar: {
8
+ height: 12,
9
+ borderRadius: 2,
10
+ backgroundColor: colors.accent,
11
+ },
12
+ label: {
13
+ fontSize: '1rem',
14
+ fontWeight: 600,
15
+ marginBottom: 4,
16
+ },
17
+ mono: {
18
+ fontFamily: 'monospace',
19
+ },
20
+ muted: {
21
+ fontFamily: 'monospace',
22
+ color: colors.textMuted,
23
+ },
24
+ })
25
+
26
+ const dynamicStyles = css.create({
27
+ barWidth: (width: string) => ({
28
+ width,
29
+ }),
30
+ typeSample: (fontSize: string, lineHeight: string) => ({
31
+ fontFamily: typography.fontFamily,
32
+ whiteSpace: 'nowrap' as const,
33
+ fontSize,
34
+ lineHeight,
35
+ }),
36
+ })
37
+
38
+ interface SpacingBarProps {
39
+ value: string
40
+ }
41
+
42
+ export function SpacingBar({value}: SpacingBarProps) {
43
+ return <html.div style={[styles.bar, dynamicStyles.barWidth(value)]} />
44
+ }
45
+
46
+ interface TokenTableProps {
47
+ tokens: Record<string, string>
48
+ label?: string
49
+ }
50
+
51
+ interface TypeScaleRow {
52
+ step: number
53
+ token: string
54
+ size: string
55
+ lineHeight: string
56
+ }
57
+
58
+ interface TypeScaleTableProps {
59
+ rows: TypeScaleRow[]
60
+ label?: string
61
+ }
62
+
63
+ export function TypeScaleTable({rows, label}: TypeScaleTableProps) {
64
+ return (
65
+ <html.div>
66
+ {label && <html.h3 style={styles.label}>{label}</html.h3>}
67
+ <Table.Root columns={4} variant="striped" size="sm">
68
+ <Table.Header>
69
+ <Table.Row>
70
+ <Table.HeaderCell>Step</Table.HeaderCell>
71
+ <Table.HeaderCell>Token</Table.HeaderCell>
72
+ <Table.HeaderCell>Size / Line-height</Table.HeaderCell>
73
+ <Table.HeaderCell>Sample</Table.HeaderCell>
74
+ </Table.Row>
75
+ </Table.Header>
76
+ <Table.Body>
77
+ {rows.map((row) => (
78
+ <Table.Row key={row.step}>
79
+ <Table.Cell>
80
+ <html.span style={styles.mono}>{row.step}</html.span>
81
+ </Table.Cell>
82
+ <Table.Cell>
83
+ <html.span style={styles.mono}>{row.token}</html.span>
84
+ </Table.Cell>
85
+ <Table.Cell>
86
+ <html.span style={styles.muted}>
87
+ {row.size} / {row.lineHeight}
88
+ </html.span>
89
+ </Table.Cell>
90
+ <Table.Cell>
91
+ <html.span style={dynamicStyles.typeSample(row.size, row.lineHeight)}>
92
+ The quick brown fox
93
+ </html.span>
94
+ </Table.Cell>
95
+ </Table.Row>
96
+ ))}
97
+ </Table.Body>
98
+ </Table.Root>
99
+ </html.div>
100
+ )
101
+ }
102
+
103
+ export function TokenTable({tokens, label}: TokenTableProps) {
104
+ const entries = Object.entries(tokens)
105
+
106
+ return (
107
+ <html.div>
108
+ {label && <html.h3 style={styles.label}>{label}</html.h3>}
109
+ <Table.Root columns={3} variant="striped" size="sm">
110
+ <Table.Header>
111
+ <Table.Row>
112
+ <Table.HeaderCell>Token</Table.HeaderCell>
113
+ <Table.HeaderCell>Value</Table.HeaderCell>
114
+ <Table.HeaderCell>Preview</Table.HeaderCell>
115
+ </Table.Row>
116
+ </Table.Header>
117
+ <Table.Body>
118
+ {entries.map(([name, value]) => (
119
+ <Table.Row key={name}>
120
+ <Table.Cell>
121
+ <html.span style={styles.mono}>{name}</html.span>
122
+ </Table.Cell>
123
+ <Table.Cell>
124
+ <html.span style={styles.muted}>{value}</html.span>
125
+ </Table.Cell>
126
+ <Table.Cell>
127
+ <SpacingBar value={value} />
128
+ </Table.Cell>
129
+ </Table.Row>
130
+ ))}
131
+ </Table.Body>
132
+ </Table.Root>
133
+ </html.div>
134
+ )
135
+ }
@@ -0,0 +1,54 @@
1
+ import {useRef, useState, useEffect} from 'react'
2
+
3
+ export type ContainerSize = 'compact' | 'default' | 'spacious'
4
+
5
+ interface UseContainerQueryOptions {
6
+ compactBelow?: number
7
+ spaciousAbove?: number
8
+ }
9
+
10
+ export function useContainerQuery<T extends HTMLElement = HTMLElement>(
11
+ options: UseContainerQueryOptions = {},
12
+ ): {
13
+ ref: React.RefObject<T | null>
14
+ size: ContainerSize
15
+ } {
16
+ const {compactBelow = 480, spaciousAbove = 768} = options
17
+ const ref = useRef<T | null>(null)
18
+ const [size, setSize] = useState<ContainerSize>('default')
19
+
20
+ useEffect(() => {
21
+ const element = ref.current
22
+ if (!element) return
23
+
24
+ let rafId: number | null = null
25
+
26
+ const observer = new ResizeObserver((entries) => {
27
+ if (rafId !== null) cancelAnimationFrame(rafId)
28
+
29
+ rafId = requestAnimationFrame(() => {
30
+ const entry = entries[0]
31
+ if (!entry) return
32
+
33
+ const width = entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width
34
+
35
+ if (width < compactBelow) {
36
+ setSize('compact')
37
+ } else if (width >= spaciousAbove) {
38
+ setSize('spacious')
39
+ } else {
40
+ setSize('default')
41
+ }
42
+ })
43
+ })
44
+
45
+ observer.observe(element)
46
+
47
+ return () => {
48
+ observer.disconnect()
49
+ if (rafId !== null) cancelAnimationFrame(rafId)
50
+ }
51
+ }, [compactBelow, spaciousAbove])
52
+
53
+ return {ref, size}
54
+ }
@@ -0,0 +1,18 @@
1
+ import {useState, useCallback} from 'react'
2
+
3
+ export function useControllableValue<T>(
4
+ controlledValue: T | undefined,
5
+ defaultValue: T,
6
+ onChange?: (value: T) => void,
7
+ ) {
8
+ const [internal, setInternal] = useState(defaultValue)
9
+ const value = controlledValue !== undefined ? controlledValue : internal
10
+ const setValue = useCallback(
11
+ (v: T) => {
12
+ if (controlledValue === undefined) setInternal(v)
13
+ onChange?.(v)
14
+ },
15
+ [controlledValue, onChange],
16
+ )
17
+ return [value, setValue] as const
18
+ }
package/src/index.ts ADDED
@@ -0,0 +1,56 @@
1
+ import './strict.css'
2
+
3
+ // Components
4
+ export {Alert, type AlertVariant} from './components/Alert/Alert'
5
+ export {Icon, type IconName} from './components/Icon'
6
+ export {Badge, type BadgeVariant, type BadgeSize} from './components/Badge/Badge'
7
+ export {Button, type ButtonVariant, type ButtonSize} from './components/Button/Button'
8
+ export {Callout, type CalloutVariant} from './components/Callout/Callout'
9
+ export {Card, type CardVariant, type CardSize} from './components/Card/Card'
10
+ export {Checkbox} from './components/Checkbox/Checkbox'
11
+ export {EmptyState} from './components/EmptyState/EmptyState'
12
+ export {Heading, type HeadingVariant} from './components/Heading/Heading'
13
+ export {Field} from './components/Field/Field'
14
+ export {Fieldset, type FieldsetGap} from './components/Fieldset/Fieldset'
15
+ export {Input, type InputVariant} from './components/Input/Input'
16
+ export {InputGroup} from './components/InputGroup/InputGroup'
17
+ export {
18
+ LinkButton,
19
+ type LinkButtonVariant,
20
+ type LinkButtonSize,
21
+ } from './components/LinkButton/LinkButton'
22
+ export {Menu} from './components/Menu/Menu'
23
+ export {ScrollArea} from './components/ScrollArea/ScrollArea'
24
+ export {Select} from './components/Select/Select'
25
+ export {SideNav} from './components/SideNav/SideNav'
26
+ export {Spinner, type SpinnerSize} from './components/Spinner/Spinner'
27
+ export {
28
+ StatusIcon,
29
+ type StatusIconName,
30
+ type StatusIconVariant,
31
+ } from './components/StatusIcon/StatusIcon'
32
+ export {Table, type TableVariant, type TableSize} from './components/Table/Table'
33
+ export {Tabs} from './components/Tabs/Tabs'
34
+ export {Textarea, type TextareaVariant} from './components/Textarea/Textarea'
35
+ export {ThemeProvider, type ThemeName} from './components/ThemeProvider/ThemeProvider'
36
+ export {Text, type TextVariant, type TextColor} from './components/Text/Text'
37
+ export {Toggle, type ToggleSize} from './components/Toggle/Toggle'
38
+ export {ToggleGroup} from './components/ToggleGroup/ToggleGroup'
39
+ export {Tooltip} from './components/Tooltip/Tooltip'
40
+ export {Switch} from './components/Switch/Switch'
41
+
42
+ // Layout primitives
43
+ export {Stack, type SpacingKey} from './components/Stack/Stack'
44
+ export {Inline} from './components/Inline/Inline'
45
+ export {Cluster} from './components/Cluster/Cluster'
46
+ export {Grid} from './components/Grid/Grid'
47
+ export {PageShell, type PageShellMaxWidth, type PageShellPadding} from './components/PageShell'
48
+
49
+ // Hooks
50
+ export {useContainerQuery, type ContainerSize} from './hooks/useContainerQuery'
51
+
52
+ // Tokens — import directly from @duro-app/tokens with deep imports:
53
+ // import { colors } from '@duro-app/tokens/tokens/colors.css'
54
+ // import { spacing, radii } from '@duro-app/tokens/tokens/spacing.css'
55
+ // Barrel re-exports removed: StyleX's babel plugin cannot resolve
56
+ // css.defineVars through barrel re-exports.
@@ -0,0 +1,3 @@
1
+ export function getAssetByID(_id: number) {
2
+ return null
3
+ }