@duro-app/ui 2.0.0 → 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.
- package/dist/components/ActionBar/ActionBar.stories.d.ts.map +1 -1
- package/dist/components/Alert/Alert.stories.d.ts.map +1 -1
- package/dist/components/Badge/Badge.stories.d.ts.map +1 -1
- package/dist/components/Button/Button.stories.d.ts.map +1 -1
- package/dist/components/Callout/Callout.stories.d.ts.map +1 -1
- package/dist/components/Card/Card.stories.d.ts.map +1 -1
- package/dist/components/Checkbox/Checkbox.stories.d.ts.map +1 -1
- package/dist/components/Cluster/Cluster.stories.d.ts.map +1 -1
- package/dist/components/ColorMode/ColorMode.stories.d.ts.map +1 -1
- package/dist/components/Field/Field.stories.d.ts.map +1 -1
- package/dist/components/Grid/Grid.d.ts +8 -1
- package/dist/components/Grid/Grid.d.ts.map +1 -1
- package/dist/components/Grid/Grid.meta.d.ts.map +1 -1
- package/dist/components/Grid/Grid.stories.d.ts +1 -0
- package/dist/components/Grid/Grid.stories.d.ts.map +1 -1
- package/dist/components/Grid/styles.css.d.ts +6 -0
- package/dist/components/Grid/styles.css.d.ts.map +1 -1
- package/dist/components/Inline/Inline.stories.d.ts.map +1 -1
- package/dist/components/Input/Input.stories.d.ts.map +1 -1
- package/dist/components/InputGroup/InputGroup.stories.d.ts.map +1 -1
- package/dist/components/LinkButton/LinkButton.stories.d.ts.map +1 -1
- package/dist/components/List/List.stories.d.ts.map +1 -1
- package/dist/components/PageShell/PageShell.meta.d.ts.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
- package/dist/components/SideNav/SideNav.meta.d.ts.map +1 -1
- package/dist/components/Spinner/Spinner.stories.d.ts.map +1 -1
- package/dist/components/Stack/Stack.stories.d.ts.map +1 -1
- package/dist/components/StatusIcon/StatusIcon.stories.d.ts.map +1 -1
- package/dist/components/Switch/Switch.stories.d.ts.map +1 -1
- package/dist/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/components/Tabs/Tabs.meta.d.ts.map +1 -1
- package/dist/components/Tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/Textarea/Textarea.stories.d.ts.map +1 -1
- package/dist/components/Toast/Toast.stories.d.ts.map +1 -1
- package/dist/components/Toggle/Toggle.stories.d.ts.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts.map +1 -1
- package/dist/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
- package/dist/docs/Spacing.stories.d.ts.map +1 -1
- package/dist/docs/Typography.stories.d.ts.map +1 -1
- package/dist/docs/helpers.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +209 -197
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ActionBar/ActionBar.stories.tsx +7 -5
- package/src/components/Alert/Alert.stories.tsx +2 -1
- package/src/components/Badge/Badge.stories.tsx +3 -2
- package/src/components/Button/Button.stories.tsx +3 -2
- package/src/components/Callout/Callout.stories.tsx +2 -1
- package/src/components/Card/Card.stories.tsx +5 -3
- package/src/components/Checkbox/Checkbox.stories.tsx +2 -1
- package/src/components/Cluster/Cluster.stories.tsx +4 -3
- package/src/components/ColorMode/ColorMode.stories.tsx +2 -1
- package/src/components/Field/Field.stories.tsx +3 -2
- package/src/components/Grid/Grid.meta.ts +14 -2
- package/src/components/Grid/Grid.stories.tsx +24 -4
- package/src/components/Grid/Grid.tsx +21 -6
- package/src/components/Grid/styles.css.ts +21 -0
- package/src/components/Inline/Inline.stories.tsx +5 -4
- package/src/components/Input/Input.stories.tsx +2 -1
- package/src/components/InputGroup/InputGroup.stories.tsx +2 -1
- package/src/components/LinkButton/LinkButton.stories.tsx +3 -2
- package/src/components/List/List.stories.tsx +2 -1
- package/src/components/PageShell/PageShell.meta.ts +12 -1
- package/src/components/ScrollArea/ScrollArea.stories.tsx +12 -9
- package/src/components/SideNav/SideNav.meta.ts +5 -0
- package/src/components/Spinner/Spinner.stories.tsx +2 -1
- package/src/components/Stack/Stack.stories.tsx +5 -4
- package/src/components/StatusIcon/StatusIcon.stories.tsx +11 -4
- package/src/components/Switch/Switch.stories.tsx +2 -1
- package/src/components/Table/Table.stories.tsx +7 -2
- package/src/components/Tabs/Tabs.meta.ts +5 -0
- package/src/components/Tag/Tag.stories.tsx +11 -3
- package/src/components/Textarea/Textarea.stories.tsx +2 -1
- package/src/components/ThemeProvider/ThemeProvider.stories.tsx +9 -9
- package/src/components/Toast/Toast.stories.tsx +9 -2
- package/src/components/Toggle/Toggle.stories.tsx +2 -1
- package/src/components/ToggleGroup/ToggleGroup.stories.tsx +2 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +3 -2
- package/src/docs/Spacing.stories.tsx +12 -11
- package/src/docs/Typography.stories.tsx +4 -3
- 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": "
|
|
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": "^
|
|
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:
|
|
44
|
-
padding:
|
|
45
|
+
gap: spacing.xl,
|
|
46
|
+
padding: spacing.lg,
|
|
45
47
|
},
|
|
46
48
|
label: {
|
|
47
|
-
fontSize:
|
|
48
|
-
fontWeight:
|
|
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:
|
|
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:
|
|
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:
|
|
56
|
-
stack: {display: 'flex', flexDirection: 'column', gap:
|
|
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:
|
|
87
|
-
stack: {display: 'flex', flexDirection: 'column', gap:
|
|
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:
|
|
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:
|
|
90
|
+
gap: spacing.md,
|
|
89
91
|
},
|
|
90
|
-
stack: {display: 'flex', flexDirection: 'column', gap:
|
|
91
|
-
muted: {color: colors.textMuted, fontSize:
|
|
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:
|
|
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:
|
|
39
|
-
fontSize:
|
|
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:
|
|
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:
|
|
66
|
-
stackWide: {display: 'flex', flexDirection: 'column', gap:
|
|
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.
|
|
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: `//
|
|
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:
|
|
36
|
+
borderRadius: radii.xs,
|
|
35
37
|
textAlign: 'center',
|
|
36
|
-
fontSize:
|
|
38
|
+
fontSize: typography.fontSizeSm,
|
|
37
39
|
},
|
|
38
40
|
label: {
|
|
39
|
-
fontSize:
|
|
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="split" — 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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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:
|
|
36
|
+
borderRadius: radii.xs,
|
|
36
37
|
textAlign: 'center',
|
|
37
|
-
fontSize:
|
|
38
|
+
fontSize: typography.fontSizeSm,
|
|
38
39
|
},
|
|
39
40
|
iconLg: {
|
|
40
|
-
fontSize:
|
|
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:
|
|
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:
|
|
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:
|
|
65
|
-
stack: {display: 'flex', flexDirection: 'column', gap:
|
|
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:
|
|
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 —
|
|
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:
|
|
19
|
-
borderRadius:
|
|
21
|
+
borderColor: colors.border,
|
|
22
|
+
borderRadius: radii.sm,
|
|
20
23
|
},
|
|
21
24
|
item: {
|
|
22
|
-
paddingTop:
|
|
23
|
-
paddingBottom:
|
|
24
|
-
paddingLeft:
|
|
25
|
-
paddingRight:
|
|
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:
|
|
29
|
-
fontSize:
|
|
30
|
-
color:
|
|
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:
|
|
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:
|
|
32
|
+
borderRadius: radii.xs,
|
|
32
33
|
textAlign: 'center',
|
|
33
|
-
fontSize:
|
|
34
|
+
fontSize: typography.fontSizeSm,
|
|
34
35
|
},
|
|
35
36
|
label: {
|
|
36
|
-
fontSize:
|
|
37
|
+
fontSize: typography.fontSizeXs,
|
|
37
38
|
color: colors.textMuted,
|
|
38
39
|
},
|
|
39
40
|
})
|