@duro-app/ui 0.12.2 → 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.
- package/dist/components/PageShell/PageShell.d.ts +15 -0
- package/dist/components/PageShell/PageShell.d.ts.map +1 -0
- package/dist/components/PageShell/index.d.ts +3 -0
- package/dist/components/PageShell/index.d.ts.map +1 -0
- package/dist/components/PageShell/styles.css.d.ts +41 -0
- package/dist/components/PageShell/styles.css.d.ts.map +1 -0
- package/dist/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3283 -3434
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/Alert/Alert.stories.tsx +76 -0
- package/src/components/Alert/Alert.tsx +45 -0
- package/src/components/Alert/styles.css.ts +50 -0
- package/src/components/Badge/Badge.stories.tsx +94 -0
- package/src/components/Badge/Badge.tsx +21 -0
- package/src/components/Badge/styles.css.ts +51 -0
- package/src/components/Button/Button.stories.tsx +130 -0
- package/src/components/Button/Button.tsx +48 -0
- package/src/components/Button/styles.css.ts +107 -0
- package/src/components/Callout/Callout.stories.tsx +97 -0
- package/src/components/Callout/Callout.tsx +39 -0
- package/src/components/Callout/index.ts +1 -0
- package/src/components/Callout/styles.css.ts +45 -0
- package/src/components/Card/Card.stories.tsx +119 -0
- package/src/components/Card/Card.tsx +35 -0
- package/src/components/Card/styles.css.ts +67 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +88 -0
- package/src/components/Checkbox/Checkbox.tsx +73 -0
- package/src/components/Checkbox/styles.css.ts +57 -0
- package/src/components/Cluster/Cluster.stories.tsx +92 -0
- package/src/components/Cluster/Cluster.tsx +43 -0
- package/src/components/Cluster/styles.css.ts +25 -0
- package/src/components/EmptyState/EmptyState.stories.tsx +54 -0
- package/src/components/EmptyState/EmptyState.tsx +19 -0
- package/src/components/EmptyState/styles.css.ts +25 -0
- package/src/components/Field/Field.stories.tsx +92 -0
- package/src/components/Field/Field.tsx +80 -0
- package/src/components/Field/FieldContext.ts +14 -0
- package/src/components/Field/styles.css.ts +25 -0
- package/src/components/Fieldset/Fieldset.stories.tsx +85 -0
- package/src/components/Fieldset/Fieldset.tsx +48 -0
- package/src/components/Fieldset/index.ts +1 -0
- package/src/components/Fieldset/styles.css.ts +33 -0
- package/src/components/Grid/Grid.stories.tsx +107 -0
- package/src/components/Grid/Grid.tsx +41 -0
- package/src/components/Grid/styles.css.ts +25 -0
- package/src/components/Heading/Heading.tsx +48 -0
- package/src/components/Heading/styles.css.ts +26 -0
- package/src/components/Icon/Icon.tsx +168 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Inline/Inline.stories.tsx +88 -0
- package/src/components/Inline/Inline.tsx +45 -0
- package/src/components/Inline/styles.css.ts +27 -0
- package/src/components/Input/Input.stories.tsx +89 -0
- package/src/components/Input/Input.tsx +77 -0
- package/src/components/Input/styles.css.ts +60 -0
- package/src/components/InputGroup/InputGroup.stories.tsx +119 -0
- package/src/components/InputGroup/InputGroup.tsx +60 -0
- package/src/components/InputGroup/InputGroupContext.ts +11 -0
- package/src/components/InputGroup/styles.css.ts +61 -0
- package/src/components/LinkButton/LinkButton.stories.tsx +91 -0
- package/src/components/LinkButton/LinkButton.tsx +42 -0
- package/src/components/LinkButton/styles.css.ts +56 -0
- package/src/components/Menu/Menu.stories.tsx +146 -0
- package/src/components/Menu/Menu.tsx +151 -0
- package/src/components/Menu/MenuContext.ts +20 -0
- package/src/components/Menu/styles.css.ts +89 -0
- package/src/components/Menu/useMenuRoot.ts +136 -0
- package/src/components/PageShell/PageShell.tsx +45 -0
- package/src/components/PageShell/index.ts +2 -0
- package/src/components/PageShell/styles.css.ts +26 -0
- package/src/components/ScrollArea/ScrollArea.stories.tsx +82 -0
- package/src/components/ScrollArea/ScrollArea.tsx +170 -0
- package/src/components/ScrollArea/ScrollAreaContext.ts +21 -0
- package/src/components/ScrollArea/styles.css.ts +81 -0
- package/src/components/ScrollArea/useScrollAreaRoot.ts +72 -0
- package/src/components/Select/Select.stories.tsx +144 -0
- package/src/components/Select/Select.tsx +183 -0
- package/src/components/Select/SelectContext.ts +24 -0
- package/src/components/Select/styles.css.ts +97 -0
- package/src/components/Select/useSelectRoot.ts +178 -0
- package/src/components/SideNav/SideNav.stories.tsx +77 -0
- package/src/components/SideNav/SideNav.tsx +172 -0
- package/src/components/SideNav/SideNavContext.ts +18 -0
- package/src/components/SideNav/styles.css.ts +95 -0
- package/src/components/Spinner/Spinner.stories.tsx +59 -0
- package/src/components/Spinner/Spinner.tsx +24 -0
- package/src/components/Spinner/styles.css.ts +47 -0
- package/src/components/Stack/Stack.stories.tsx +103 -0
- package/src/components/Stack/Stack.tsx +33 -0
- package/src/components/Stack/styles.css.ts +21 -0
- package/src/components/StatusIcon/StatusIcon.stories.tsx +81 -0
- package/src/components/StatusIcon/StatusIcon.tsx +24 -0
- package/src/components/StatusIcon/styles.css.ts +27 -0
- package/src/components/Switch/Switch.stories.tsx +88 -0
- package/src/components/Switch/Switch.tsx +78 -0
- package/src/components/Switch/styles.css.ts +71 -0
- package/src/components/Table/Table.stories.tsx +308 -0
- package/src/components/Table/Table.tsx +179 -0
- package/src/components/Table/styles.css.ts +97 -0
- package/src/components/Tabs/Tabs.stories.tsx +142 -0
- package/src/components/Tabs/Tabs.tsx +210 -0
- package/src/components/Tabs/TabsContext.ts +20 -0
- package/src/components/Tabs/styles.css.ts +98 -0
- package/src/components/Tabs/useTabsRoot.ts +42 -0
- package/src/components/Text/Text.tsx +52 -0
- package/src/components/Text/styles.css.ts +57 -0
- package/src/components/Textarea/Textarea.stories.tsx +80 -0
- package/src/components/Textarea/Textarea.tsx +50 -0
- package/src/components/Textarea/styles.css.ts +56 -0
- package/src/components/ThemeProvider/ThemeProvider.stories.tsx +163 -0
- package/src/components/ThemeProvider/ThemeProvider.tsx +33 -0
- package/src/components/Toggle/Toggle.stories.tsx +84 -0
- package/src/components/Toggle/Toggle.tsx +85 -0
- package/src/components/Toggle/styles.css.ts +66 -0
- package/src/components/ToggleGroup/ToggleGroup.stories.tsx +159 -0
- package/src/components/ToggleGroup/ToggleGroup.tsx +63 -0
- package/src/components/ToggleGroup/ToggleGroupContext.ts +18 -0
- package/src/components/ToggleGroup/styles.css.ts +17 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +127 -0
- package/src/components/Tooltip/Tooltip.tsx +97 -0
- package/src/components/Tooltip/styles.css.ts +56 -0
- package/src/docs/Spacing.mdx +80 -0
- package/src/docs/Spacing.stories.tsx +202 -0
- package/src/docs/Typography.mdx +93 -0
- package/src/docs/Typography.stories.tsx +211 -0
- package/src/docs/helpers.tsx +135 -0
- package/src/hooks/useContainerQuery.ts +54 -0
- package/src/hooks/useControllableValue.ts +18 -0
- package/src/index.ts +56 -0
- 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 = "Hello, world!"</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.
|