@duro-app/ui 4.4.0 → 5.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/{Table-JTgcR_Fh.js → Table-BKN_d-mp.js} +506 -410
- package/dist/Table-BKN_d-mp.js.map +1 -0
- package/dist/components/ActionBar/ActionBar.stories.d.ts.map +1 -1
- package/dist/components/ActionBar/styles.css.d.ts +7 -7
- package/dist/components/ActionBar/styles.css.d.ts.map +1 -1
- package/dist/components/Alert/styles.css.d.ts +2 -2
- package/dist/components/Alert/styles.css.d.ts.map +1 -1
- package/dist/components/Badge/styles.css.d.ts +2 -2
- package/dist/components/Button/styles.css.d.ts +5 -5
- package/dist/components/Button/styles.css.d.ts.map +1 -1
- package/dist/components/Callout/styles.css.d.ts +1 -1
- package/dist/components/Callout/styles.css.d.ts.map +1 -1
- package/dist/components/Card/styles.css.d.ts +3 -3
- package/dist/components/Card/styles.css.d.ts.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/styles.css.d.ts +7 -11
- package/dist/components/Checkbox/styles.css.d.ts.map +1 -1
- package/dist/components/Cluster/Cluster.stories.d.ts.map +1 -1
- package/dist/components/ColorInput/styles.css.d.ts +6 -6
- package/dist/components/ColorInput/styles.css.d.ts.map +1 -1
- package/dist/components/ColorMode/styles.css.d.ts +3 -3
- package/dist/components/ColorMode/styles.css.d.ts.map +1 -1
- package/dist/components/Combobox/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox/Combobox.meta.d.ts.map +1 -1
- package/dist/components/Combobox/styles.css.d.ts +3 -3
- package/dist/components/Combobox/styles.css.d.ts.map +1 -1
- package/dist/components/DetailPanel/DetailPanel.d.ts.map +1 -1
- package/dist/components/DetailPanel/DetailPanel.stories.d.ts.map +1 -1
- package/dist/components/DetailPanel/styles.css.d.ts +19 -10
- package/dist/components/DetailPanel/styles.css.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.stories.d.ts.map +1 -1
- package/dist/components/Dialog/styles.css.d.ts +18 -9
- package/dist/components/Dialog/styles.css.d.ts.map +1 -1
- package/dist/components/DragDrop/DragDrop.d.ts.map +1 -1
- package/dist/components/DragDrop/DragDrop.stories.d.ts.map +1 -1
- package/dist/components/DragDrop/styles.css.d.ts +0 -6
- package/dist/components/DragDrop/styles.css.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerScroll.stories.d.ts +13 -0
- package/dist/components/Drawer/DrawerScroll.stories.d.ts.map +1 -0
- package/dist/components/Drawer/styles.css.d.ts +19 -10
- package/dist/components/Drawer/styles.css.d.ts.map +1 -1
- package/dist/components/Drawer/useSwipeDismiss.d.ts +7 -2
- package/dist/components/Drawer/useSwipeDismiss.d.ts.map +1 -1
- package/dist/components/Field/Field.stories.d.ts.map +1 -1
- package/dist/components/Field/styles.css.d.ts +1 -1
- package/dist/components/Field/styles.css.d.ts.map +1 -1
- package/dist/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/components/Grid/Grid.d.ts +6 -4
- package/dist/components/Grid/Grid.d.ts.map +1 -1
- package/dist/components/Grid/Grid.stories.d.ts.map +1 -1
- package/dist/components/Grid/styles.css.d.ts +3 -3
- package/dist/components/Input/Input.stories.d.ts.map +1 -1
- package/dist/components/Input/styles.css.d.ts +3 -3
- package/dist/components/Input/styles.css.d.ts.map +1 -1
- package/dist/components/InputGroup/InputGroup.d.ts +4 -2
- package/dist/components/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/components/InputGroup/InputGroup.stories.d.ts.map +1 -1
- package/dist/components/InputGroup/styles.css.d.ts +3 -3
- package/dist/components/InputGroup/styles.css.d.ts.map +1 -1
- package/dist/components/LinkButton/styles.css.d.ts +1 -1
- package/dist/components/LinkButton/styles.css.d.ts.map +1 -1
- package/dist/components/List/List.stories.d.ts.map +1 -1
- package/dist/components/List/styles.css.d.ts +3 -3
- package/dist/components/List/styles.css.d.ts.map +1 -1
- package/dist/components/Menu/styles.css.d.ts +4 -4
- package/dist/components/Menu/styles.css.d.ts.map +1 -1
- package/dist/components/Menu/useMenuRoot.d.ts.map +1 -1
- package/dist/components/PageShell/styles.css.d.ts +3 -3
- package/dist/components/PageShell/styles.css.d.ts.map +1 -1
- package/dist/components/Panel/styles.css.d.ts +3 -3
- package/dist/components/Panel/styles.css.d.ts.map +1 -1
- package/dist/components/Popover/Popover.d.ts.map +1 -1
- package/dist/components/Popover/Popover.meta.d.ts.map +1 -1
- package/dist/components/Popover/Popover.stories.d.ts.map +1 -1
- package/dist/components/Popover/PopoverLayerContext.d.ts +10 -0
- package/dist/components/Popover/PopoverLayerContext.d.ts.map +1 -0
- package/dist/components/Popover/PopoverLayers.stories.d.ts +13 -0
- package/dist/components/Popover/PopoverLayers.stories.d.ts.map +1 -0
- package/dist/components/Popover/styles.css.d.ts +2 -2
- package/dist/components/Popover/styles.css.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/components/RadioGroup/styles.css.d.ts +5 -9
- package/dist/components/RadioGroup/styles.css.d.ts.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.d.ts +10 -3
- package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
- package/dist/components/ScrollArea/styles.css.d.ts +8 -8
- package/dist/components/ScrollArea/styles.css.d.ts.map +1 -1
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/Select.meta.d.ts.map +1 -1
- package/dist/components/Select/styles.css.d.ts +4 -4
- package/dist/components/Select/styles.css.d.ts.map +1 -1
- package/dist/components/Select/useSelectRoot.d.ts.map +1 -1
- package/dist/components/SideNav/styles.css.d.ts +7 -7
- package/dist/components/SideNav/styles.css.d.ts.map +1 -1
- package/dist/components/Spinner/Spinner.d.ts.map +1 -1
- package/dist/components/Spinner/styles.css.d.ts +10 -14
- package/dist/components/Spinner/styles.css.d.ts.map +1 -1
- package/dist/components/Switch/Switch.d.ts.map +1 -1
- package/dist/components/Switch/styles.css.d.ts +10 -14
- package/dist/components/Switch/styles.css.d.ts.map +1 -1
- package/dist/components/Table/ColumnFilter.d.ts.map +1 -1
- package/dist/components/Table/FromTanstack.d.ts +2 -1
- package/dist/components/Table/FromTanstack.d.ts.map +1 -1
- package/dist/components/Table/Pagination.d.ts.map +1 -1
- package/dist/components/Table/SortIndicator.d.ts.map +1 -1
- package/dist/components/Table/Table.d.ts +11 -9
- package/dist/components/Table/Table.d.ts.map +1 -1
- package/dist/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/components/Table/styles.css.d.ts +6 -6
- package/dist/components/Table/styles.css.d.ts.map +1 -1
- package/dist/components/Tabs/styles.css.d.ts +8 -8
- package/dist/components/Tabs/styles.css.d.ts.map +1 -1
- package/dist/components/Tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/Tag/styles.css.d.ts +4 -4
- package/dist/components/Tag/styles.css.d.ts.map +1 -1
- package/dist/components/TagGroup/TagGroup.d.ts.map +1 -1
- package/dist/components/TagGroup/styles.css.d.ts +4 -10
- package/dist/components/TagGroup/styles.css.d.ts.map +1 -1
- package/dist/components/Textarea/Textarea.stories.d.ts.map +1 -1
- package/dist/components/Textarea/styles.css.d.ts +3 -3
- package/dist/components/Textarea/styles.css.d.ts.map +1 -1
- package/dist/components/ThemeProvider/ThemeProvider.stories.d.ts.map +1 -1
- package/dist/components/Toast/Toast.stories.d.ts.map +1 -1
- package/dist/components/Toast/styles.css.d.ts +12 -12
- package/dist/components/Toast/styles.css.d.ts.map +1 -1
- package/dist/components/Toggle/Toggle.d.ts.map +1 -1
- package/dist/components/Toggle/styles.css.d.ts +14 -4
- package/dist/components/Toggle/styles.css.d.ts.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +7 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.meta.d.ts.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts +4 -0
- package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroupContext.d.ts +6 -0
- package/dist/components/ToggleGroup/ToggleGroupContext.d.ts.map +1 -1
- package/dist/components/ToggleGroup/styles.css.d.ts +20 -1
- package/dist/components/ToggleGroup/styles.css.d.ts.map +1 -1
- package/dist/components/ToggleGroup/useRovingFocus.d.ts +17 -0
- package/dist/components/ToggleGroup/useRovingFocus.d.ts.map +1 -0
- package/dist/components/Tooltip/styles.css.d.ts +1 -1
- package/dist/components/Tooltip/styles.css.d.ts.map +1 -1
- package/dist/components/Tree/styles.css.d.ts +4 -4
- package/dist/components/Tree/styles.css.d.ts.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.d.ts +2 -1
- package/dist/components/VirtualTable/VirtualTable.d.ts.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.stories.d.ts.map +1 -1
- package/dist/components/VirtualTable/styles.css.d.ts +7 -7
- package/dist/components/VirtualTable/styles.css.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 +3788 -3515
- package/dist/index.js.map +1 -1
- package/dist/shared/length.d.ts +24 -0
- package/dist/shared/length.d.ts.map +1 -0
- package/dist/shared/length.typecheck.d.ts +28 -0
- package/dist/shared/length.typecheck.d.ts.map +1 -0
- package/dist/styles/visually-hidden.css.d.ts +10 -0
- package/dist/styles/visually-hidden.css.d.ts.map +1 -0
- package/dist/table.js +166 -166
- package/dist/table.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ActionBar/ActionBar.stories.tsx +4 -2
- package/src/components/ActionBar/styles.css.ts +11 -9
- package/src/components/Alert/styles.css.ts +4 -3
- package/src/components/Badge/styles.css.ts +3 -3
- package/src/components/Button/styles.css.ts +8 -7
- package/src/components/Callout/styles.css.ts +2 -1
- package/src/components/Card/styles.css.ts +4 -3
- package/src/components/Checkbox/Checkbox.tsx +2 -1
- package/src/components/Checkbox/styles.css.ts +10 -11
- package/src/components/Cluster/Cluster.stories.tsx +2 -1
- package/src/components/ColorInput/styles.css.ts +9 -7
- package/src/components/ColorMode/styles.css.ts +4 -3
- package/src/components/Combobox/Combobox.meta.ts +6 -0
- package/src/components/Combobox/Combobox.tsx +5 -0
- package/src/components/Combobox/styles.css.ts +5 -3
- package/src/components/DetailPanel/DetailPanel.stories.tsx +5 -3
- package/src/components/DetailPanel/DetailPanel.tsx +3 -0
- package/src/components/DetailPanel/styles.css.ts +17 -11
- package/src/components/Dialog/Dialog.stories.tsx +2 -1
- package/src/components/Dialog/Dialog.tsx +3 -0
- package/src/components/Dialog/styles.css.ts +14 -9
- package/src/components/DragDrop/DragDrop.stories.tsx +2 -1
- package/src/components/DragDrop/DragDrop.tsx +2 -1
- package/src/components/DragDrop/styles.css.ts +0 -7
- package/src/components/Drawer/Drawer.meta.ts +1 -1
- package/src/components/Drawer/Drawer.tsx +15 -2
- package/src/components/Drawer/DrawerScroll.stories.tsx +292 -0
- package/src/components/Drawer/styles.css.ts +15 -10
- package/src/components/Drawer/useSwipeDismiss.ts +53 -8
- package/src/components/Field/Field.stories.tsx +3 -2
- package/src/components/Field/styles.css.ts +2 -1
- package/src/components/Form/Form.stories.tsx +4 -3
- package/src/components/Grid/Grid.meta.ts +2 -2
- package/src/components/Grid/Grid.stories.tsx +3 -2
- package/src/components/Grid/Grid.tsx +8 -6
- package/src/components/Grid/styles.css.ts +2 -2
- package/src/components/Input/Input.stories.tsx +2 -1
- package/src/components/Input/styles.css.ts +4 -3
- package/src/components/InputGroup/InputGroup.stories.tsx +3 -2
- package/src/components/InputGroup/InputGroup.tsx +5 -3
- package/src/components/InputGroup/styles.css.ts +4 -3
- package/src/components/LinkButton/styles.css.ts +2 -1
- package/src/components/List/List.stories.tsx +4 -2
- package/src/components/List/styles.css.ts +6 -4
- package/src/components/Menu/styles.css.ts +6 -4
- package/src/components/Menu/useMenuRoot.ts +5 -1
- package/src/components/PageShell/styles.css.ts +4 -3
- package/src/components/Panel/styles.css.ts +4 -3
- package/src/components/Popover/Popover.meta.ts +7 -0
- package/src/components/Popover/Popover.stories.tsx +6 -4
- package/src/components/Popover/Popover.tsx +30 -24
- package/src/components/Popover/PopoverLayerContext.ts +20 -0
- package/src/components/Popover/PopoverLayers.stories.tsx +234 -0
- package/src/components/Popover/styles.css.ts +3 -2
- package/src/components/RadioGroup/RadioGroup.tsx +2 -1
- package/src/components/RadioGroup/styles.css.ts +8 -9
- package/src/components/ScrollArea/ScrollArea.meta.ts +2 -2
- package/src/components/ScrollArea/ScrollArea.stories.tsx +7 -5
- package/src/components/ScrollArea/ScrollArea.tsx +18 -7
- package/src/components/ScrollArea/styles.css.ts +10 -9
- package/src/components/Select/Select.meta.ts +6 -0
- package/src/components/Select/Select.tsx +8 -1
- package/src/components/Select/styles.css.ts +6 -4
- package/src/components/Select/useSelectRoot.ts +6 -2
- package/src/components/SideNav/styles.css.ts +10 -8
- package/src/components/Spinner/Spinner.tsx +2 -1
- package/src/components/Spinner/styles.css.ts +14 -14
- package/src/components/Switch/Switch.tsx +2 -1
- package/src/components/Switch/styles.css.ts +15 -15
- package/src/components/Table/ColumnFilter.tsx +5 -4
- package/src/components/Table/FromTanstack.tsx +2 -1
- package/src/components/Table/Pagination.tsx +6 -4
- package/src/components/Table/SortIndicator.tsx +4 -2
- package/src/components/Table/Table.stories.tsx +8 -8
- package/src/components/Table/Table.tsx +19 -15
- package/src/components/Table/styles.css.ts +9 -7
- package/src/components/Tabs/styles.css.ts +11 -9
- package/src/components/Tag/Tag.stories.tsx +4 -2
- package/src/components/Tag/styles.css.ts +6 -5
- package/src/components/TagGroup/TagGroup.tsx +2 -1
- package/src/components/TagGroup/styles.css.ts +6 -10
- package/src/components/Textarea/Textarea.stories.tsx +2 -1
- package/src/components/Textarea/styles.css.ts +4 -3
- package/src/components/ThemeProvider/ThemeProvider.stories.tsx +7 -5
- package/src/components/Toast/Toast.stories.tsx +2 -1
- package/src/components/Toast/styles.css.ts +16 -14
- package/src/components/Toggle/Toggle.meta.ts +1 -1
- package/src/components/Toggle/Toggle.tsx +19 -2
- package/src/components/Toggle/styles.css.ts +18 -4
- package/src/components/ToggleGroup/ToggleGroup.meta.ts +11 -1
- package/src/components/ToggleGroup/ToggleGroup.stories.tsx +157 -0
- package/src/components/ToggleGroup/ToggleGroup.tsx +87 -10
- package/src/components/ToggleGroup/ToggleGroupContext.ts +6 -0
- package/src/components/ToggleGroup/styles.css.ts +32 -2
- package/src/components/ToggleGroup/useRovingFocus.ts +121 -0
- package/src/components/Tooltip/styles.css.ts +2 -1
- package/src/components/Tree/styles.css.ts +8 -6
- package/src/components/VirtualTable/VirtualTable.stories.tsx +4 -3
- package/src/components/VirtualTable/VirtualTable.tsx +5 -3
- package/src/components/VirtualTable/styles.css.ts +9 -8
- package/src/docs/Spacing.stories.tsx +11 -10
- package/src/docs/Typography.stories.tsx +8 -6
- package/src/docs/helpers.tsx +3 -2
- package/src/shared/length.ts +51 -0
- package/src/shared/length.typecheck.tsx +47 -0
- package/src/styles/visually-hidden.css.ts +16 -0
- package/dist/Table-JTgcR_Fh.js.map +0 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react'
|
|
2
|
+
import {expect, waitFor} from 'storybook/test'
|
|
3
|
+
import {cdp, page} from 'vitest/browser'
|
|
4
|
+
import {Drawer} from './Drawer'
|
|
5
|
+
import {ToggleGroup} from '../ToggleGroup/ToggleGroup'
|
|
6
|
+
import {Toggle} from '../Toggle/Toggle'
|
|
7
|
+
import {Icon, type IconName} from '../Icon'
|
|
8
|
+
import {SIZES_PX} from '@duro-app/tokens/keys'
|
|
9
|
+
|
|
10
|
+
const meta: Meta = {
|
|
11
|
+
title: 'Components/Drawer/Scroll',
|
|
12
|
+
parameters: {layout: 'fullscreen'},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default meta
|
|
16
|
+
type Story = StoryObj
|
|
17
|
+
|
|
18
|
+
const ICON_NAMES: IconName[] = [
|
|
19
|
+
'x-circle',
|
|
20
|
+
'check-circle',
|
|
21
|
+
'check-done',
|
|
22
|
+
'clock',
|
|
23
|
+
'forbidden',
|
|
24
|
+
'info-circle',
|
|
25
|
+
'alert-triangle',
|
|
26
|
+
'shield',
|
|
27
|
+
'lock',
|
|
28
|
+
'key',
|
|
29
|
+
'map',
|
|
30
|
+
'layers',
|
|
31
|
+
'repeat',
|
|
32
|
+
'database',
|
|
33
|
+
'shield-check',
|
|
34
|
+
'route',
|
|
35
|
+
'git-branch',
|
|
36
|
+
'menu',
|
|
37
|
+
'pin',
|
|
38
|
+
'server',
|
|
39
|
+
'hard-drive',
|
|
40
|
+
'box',
|
|
41
|
+
'image',
|
|
42
|
+
'tag',
|
|
43
|
+
'pie-chart',
|
|
44
|
+
'users',
|
|
45
|
+
'user-plus',
|
|
46
|
+
'mail',
|
|
47
|
+
'file-text',
|
|
48
|
+
'plug',
|
|
49
|
+
'search',
|
|
50
|
+
'mic',
|
|
51
|
+
'sun',
|
|
52
|
+
'moon',
|
|
53
|
+
'monitor',
|
|
54
|
+
'contrast',
|
|
55
|
+
'info-circle-filled',
|
|
56
|
+
'alert-triangle-filled',
|
|
57
|
+
'check-circle-filled',
|
|
58
|
+
'x-circle-filled',
|
|
59
|
+
'shield-filled',
|
|
60
|
+
'lock-filled',
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
function Scene({pressed}: {pressed?: string} = {}) {
|
|
64
|
+
return (
|
|
65
|
+
<Drawer.Root anchor="bottom" defaultOpen>
|
|
66
|
+
<Drawer.Portal>
|
|
67
|
+
<Drawer.Header>
|
|
68
|
+
<Drawer.Title>Pick an icon</Drawer.Title>
|
|
69
|
+
</Drawer.Header>
|
|
70
|
+
<Drawer.Body>
|
|
71
|
+
<ToggleGroup
|
|
72
|
+
wrap
|
|
73
|
+
maxRows={3}
|
|
74
|
+
size="small"
|
|
75
|
+
aria-label="Icon"
|
|
76
|
+
defaultValue={pressed ? [pressed] : []}
|
|
77
|
+
>
|
|
78
|
+
{ICON_NAMES.map((name) => (
|
|
79
|
+
<Toggle key={name} value={name} aria-label={name}>
|
|
80
|
+
<Icon name={name} size="sm" />
|
|
81
|
+
</Toggle>
|
|
82
|
+
))}
|
|
83
|
+
</ToggleGroup>
|
|
84
|
+
</Drawer.Body>
|
|
85
|
+
</Drawer.Portal>
|
|
86
|
+
</Drawer.Root>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type Point = {x: number; y: number}
|
|
91
|
+
|
|
92
|
+
/** `CDPSession` is empty until the provider's typings load; `send` exists at runtime. */
|
|
93
|
+
const cdpSend = (method: string, params: object) =>
|
|
94
|
+
(cdp() as unknown as {send: (method: string, params: object) => Promise<unknown>}).send(
|
|
95
|
+
method,
|
|
96
|
+
params,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const pause = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* CDP input lands in top-level page coordinates; the story runs in an iframe
|
|
103
|
+
* the test runner offsets and may scale to fit. Map a point up every frame.
|
|
104
|
+
*/
|
|
105
|
+
function toPage(p: Point): Point {
|
|
106
|
+
let {x, y} = p
|
|
107
|
+
let win: Window = window
|
|
108
|
+
while (win.frameElement && win.parent !== win) {
|
|
109
|
+
const rect = win.frameElement.getBoundingClientRect()
|
|
110
|
+
const scale = rect.width / win.innerWidth
|
|
111
|
+
x = rect.left + x * scale
|
|
112
|
+
y = rect.top + y * scale
|
|
113
|
+
win = win.parent
|
|
114
|
+
}
|
|
115
|
+
return {x, y}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A page-coordinate touch point with a 1px radius: Chrome's touch adjustment
|
|
120
|
+
* snaps a fat default touch onto the nearest control, which would turn every
|
|
121
|
+
* drag in the list into a drag on a toggle.
|
|
122
|
+
*/
|
|
123
|
+
const touchAt = (p: Point) => ({...toPage(p), radiusX: 1, radiusY: 1})
|
|
124
|
+
|
|
125
|
+
/** Real touch drag through CDP; synthetic pointer events don't scroll. */
|
|
126
|
+
async function touchDrag(from: Point, to: Point) {
|
|
127
|
+
const steps = 12
|
|
128
|
+
const send = (type: string, touchPoints: Point[]) =>
|
|
129
|
+
cdpSend('Input.dispatchTouchEvent', {type, touchPoints: touchPoints.map(touchAt)})
|
|
130
|
+
await send('touchStart', [from])
|
|
131
|
+
for (let i = 1; i <= steps; i++) {
|
|
132
|
+
await send('touchMove', [
|
|
133
|
+
{x: from.x + ((to.x - from.x) * i) / steps, y: from.y + ((to.y - from.y) * i) / steps},
|
|
134
|
+
])
|
|
135
|
+
await pause(16)
|
|
136
|
+
}
|
|
137
|
+
await send('touchEnd', [])
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const getViewport = () => document.querySelector<HTMLElement>('[data-duro-scroll]')!
|
|
141
|
+
const getDialog = () => document.querySelector('[role="dialog"]')
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A point inside the list that is not on a toggle, so a drag starting there
|
|
145
|
+
* is the case the deferral decides (a drag starting on a toggle is decided
|
|
146
|
+
* the same way, but on origin/main it was left to the native scroll).
|
|
147
|
+
*/
|
|
148
|
+
function freePoint(el: HTMLElement, fy: number): Point {
|
|
149
|
+
const r = el.getBoundingClientRect()
|
|
150
|
+
const y = r.top + r.height * fy
|
|
151
|
+
for (let dy = 0; dy < 12; dy++) {
|
|
152
|
+
for (let x = r.left + 2; x < r.right; x += 2) {
|
|
153
|
+
const hit = document.elementFromPoint(x, y + dy)
|
|
154
|
+
if (hit && el.contains(hit) && !hit.closest('button')) return {x, y: y + dy}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw new Error('no free point in the list')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function setup() {
|
|
161
|
+
await page.viewport(390, 844)
|
|
162
|
+
await cdpSend('Emulation.setTouchEmulationEnabled', {enabled: true, maxTouchPoints: 1})
|
|
163
|
+
await waitFor(() => expect(getViewport()).toBeTruthy())
|
|
164
|
+
const viewport = getViewport()
|
|
165
|
+
await expect(viewport.scrollHeight).toBeGreaterThan(viewport.clientHeight)
|
|
166
|
+
// let the slide-in animation finish
|
|
167
|
+
await pause(500)
|
|
168
|
+
return viewport
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const DragUpAtTopScrolls: Story = {
|
|
172
|
+
render: () => <Scene />,
|
|
173
|
+
play: async () => {
|
|
174
|
+
const viewport = await setup()
|
|
175
|
+
await expect(viewport.scrollTop).toBe(0)
|
|
176
|
+
const from = freePoint(viewport, 0.7)
|
|
177
|
+
await touchDrag(from, {x: from.x, y: from.y - 60})
|
|
178
|
+
await pause(300)
|
|
179
|
+
await expect(viewport.scrollTop).toBeGreaterThan(0)
|
|
180
|
+
await expect(getDialog()).not.toBeNull()
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const DragDownMidListScrolls: Story = {
|
|
185
|
+
render: () => <Scene />,
|
|
186
|
+
play: async () => {
|
|
187
|
+
const viewport = await setup()
|
|
188
|
+
viewport.scrollTop = 40
|
|
189
|
+
await pause(100)
|
|
190
|
+
const before = viewport.scrollTop
|
|
191
|
+
await expect(before).toBeGreaterThan(0)
|
|
192
|
+
const from = freePoint(viewport, 0.5)
|
|
193
|
+
await touchDrag(from, {x: from.x, y: from.y + 20})
|
|
194
|
+
await pause(300)
|
|
195
|
+
await expect(viewport.scrollTop).toBeLessThan(before)
|
|
196
|
+
await expect(getDialog()).not.toBeNull()
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const DragDownAtTopDismisses: Story = {
|
|
201
|
+
render: () => <Scene />,
|
|
202
|
+
play: async () => {
|
|
203
|
+
const viewport = await setup()
|
|
204
|
+
await expect(viewport.scrollTop).toBe(0)
|
|
205
|
+
const from = freePoint(viewport, 0.5)
|
|
206
|
+
await touchDrag(from, {x: from.x, y: from.y + 300})
|
|
207
|
+
await waitFor(() => expect(getDialog()).toBeNull())
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const DragDownOnHeaderDismisses: Story = {
|
|
212
|
+
render: () => <Scene />,
|
|
213
|
+
play: async () => {
|
|
214
|
+
await setup()
|
|
215
|
+
const title = [...document.querySelectorAll('[role="dialog"] *')].find(
|
|
216
|
+
(el) => el.textContent === 'Pick an icon',
|
|
217
|
+
) as HTMLElement
|
|
218
|
+
const r = title.getBoundingClientRect()
|
|
219
|
+
const from = {x: r.left + r.width / 2, y: r.top + r.height / 2}
|
|
220
|
+
await touchDrag(from, {x: from.x, y: from.y + 300})
|
|
221
|
+
await waitFor(() => expect(getDialog()).toBeNull())
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const TapOnToggleStillPresses: Story = {
|
|
226
|
+
render: () => <Scene />,
|
|
227
|
+
play: async () => {
|
|
228
|
+
await setup()
|
|
229
|
+
const key = document.querySelector<HTMLElement>('[role="dialog"] button[aria-label="key"]')!
|
|
230
|
+
const r = key.getBoundingClientRect()
|
|
231
|
+
const at = toPage({x: r.left + r.width / 2, y: r.top + r.height / 2})
|
|
232
|
+
await cdpSend('Input.dispatchTouchEvent', {
|
|
233
|
+
type: 'touchStart',
|
|
234
|
+
touchPoints: [{...at, radiusX: 1, radiusY: 1}],
|
|
235
|
+
})
|
|
236
|
+
await cdpSend('Input.dispatchTouchEvent', {type: 'touchEnd', touchPoints: []})
|
|
237
|
+
await waitFor(() => expect(key).toHaveAttribute('aria-pressed', 'true'))
|
|
238
|
+
await expect(getDialog()).not.toBeNull()
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export const MountKeepsTheDrawerStill: Story = {
|
|
243
|
+
// size the page before the render: a drawer mounts at its final width
|
|
244
|
+
beforeEach: async () => {
|
|
245
|
+
await page.viewport(390, 844)
|
|
246
|
+
},
|
|
247
|
+
render: () => <Scene pressed={ICON_NAMES[39]} />,
|
|
248
|
+
play: async () => {
|
|
249
|
+
const pageScroll = document.scrollingElement?.scrollTop
|
|
250
|
+
await waitFor(() => expect(getViewport()).toBeTruthy())
|
|
251
|
+
await pause(500)
|
|
252
|
+
const viewport = getViewport()
|
|
253
|
+
const body = viewport.closest('[role="dialog"]')!
|
|
254
|
+
// the list scrolled itself to the pressed icon …
|
|
255
|
+
await expect(viewport.scrollTop).toBeGreaterThan(0)
|
|
256
|
+
const pressed = viewport.querySelector('[aria-pressed="true"]')!.getBoundingClientRect()
|
|
257
|
+
const v = viewport.getBoundingClientRect()
|
|
258
|
+
await expect(pressed.top).toBeGreaterThanOrEqual(v.top - 1)
|
|
259
|
+
await expect(pressed.bottom).toBeLessThanOrEqual(v.bottom + 1)
|
|
260
|
+
// … and nothing outside it moved
|
|
261
|
+
for (const el of body.querySelectorAll<HTMLElement>('*')) {
|
|
262
|
+
if (el !== viewport && !viewport.contains(el)) await expect(el.scrollTop).toBe(0)
|
|
263
|
+
}
|
|
264
|
+
await expect(document.scrollingElement?.scrollTop).toBe(pageScroll)
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** The coarse-pointer rows of a wrapping group, measured, not inferred. */
|
|
269
|
+
export const CoarsePointerRows: Story = {
|
|
270
|
+
render: () => (
|
|
271
|
+
<ToggleGroup wrap maxRows={3} size="small" aria-label="Icon">
|
|
272
|
+
{ICON_NAMES.map((name) => (
|
|
273
|
+
<Toggle key={name} value={name} aria-label={name}>
|
|
274
|
+
<Icon name={name} size="sm" />
|
|
275
|
+
</Toggle>
|
|
276
|
+
))}
|
|
277
|
+
</ToggleGroup>
|
|
278
|
+
),
|
|
279
|
+
play: async () => {
|
|
280
|
+
// phone width, so 42 icons overflow three rows
|
|
281
|
+
await page.viewport(390, 844)
|
|
282
|
+
await cdpSend('Emulation.setTouchEmulationEnabled', {enabled: true, maxTouchPoints: 1})
|
|
283
|
+
await waitFor(() => expect(matchMedia('(pointer: coarse)').matches).toBe(true))
|
|
284
|
+
const viewport = getViewport()
|
|
285
|
+
const expected = 3.5 * SIZES_PX.touchTarget + 3 * 4 + 2 * 4
|
|
286
|
+
await waitFor(() => expect(Math.abs(viewport.clientHeight - expected)).toBeLessThanOrEqual(2))
|
|
287
|
+
const toggle = viewport.querySelector('button')!.getBoundingClientRect()
|
|
288
|
+
// exact: Toggle's styles read the same size tokens, and a drift must fail here
|
|
289
|
+
await expect(Math.abs(toggle.height - SIZES_PX.touchTarget)).toBeLessThanOrEqual(0.5)
|
|
290
|
+
await expect(toggle.width).toBeGreaterThanOrEqual(SIZES_PX.touchTarget)
|
|
291
|
+
},
|
|
292
|
+
}
|
|
@@ -4,13 +4,15 @@ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
|
|
|
4
4
|
import {typography} from '@duro-app/tokens/tokens/typography.css'
|
|
5
5
|
import {shadows} from '@duro-app/tokens/tokens/shadows.css'
|
|
6
6
|
import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
|
|
7
|
+
import {sizes} from '@duro-app/tokens/tokens/sizes.css'
|
|
8
|
+
import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
7
9
|
|
|
8
10
|
export const styles = css.create({
|
|
9
11
|
// --- Backdrop ---
|
|
10
12
|
backdrop: {
|
|
11
13
|
position: 'fixed',
|
|
12
14
|
inset: 0,
|
|
13
|
-
backgroundColor:
|
|
15
|
+
backgroundColor: colors.scrim,
|
|
14
16
|
zIndex: 1000,
|
|
15
17
|
// Portaled into the ThemeProvider mount, which is pointer-events: none:
|
|
16
18
|
// the backdrop must take clicks back (outside click dismisses).
|
|
@@ -77,13 +79,13 @@ export const styles = css.create({
|
|
|
77
79
|
panelHorizontal: {
|
|
78
80
|
width: '100%',
|
|
79
81
|
height: '100%',
|
|
80
|
-
borderLeftWidth:
|
|
82
|
+
borderLeftWidth: borders.hairline,
|
|
81
83
|
borderLeftStyle: 'solid',
|
|
82
84
|
borderLeftColor: colors.border,
|
|
83
85
|
},
|
|
84
86
|
panelVertical: {
|
|
85
87
|
width: '100%',
|
|
86
|
-
borderTopWidth:
|
|
88
|
+
borderTopWidth: borders.hairline,
|
|
87
89
|
borderTopStyle: 'solid',
|
|
88
90
|
borderTopColor: colors.border,
|
|
89
91
|
borderTopLeftRadius: radii.lg,
|
|
@@ -92,15 +94,15 @@ export const styles = css.create({
|
|
|
92
94
|
},
|
|
93
95
|
panelLeftBorder: {
|
|
94
96
|
borderLeftWidth: 0,
|
|
95
|
-
borderRightWidth:
|
|
97
|
+
borderRightWidth: borders.hairline,
|
|
96
98
|
borderRightStyle: 'solid',
|
|
97
99
|
borderRightColor: colors.border,
|
|
98
100
|
},
|
|
99
101
|
|
|
100
102
|
// --- Panel widths (horizontal anchors) ---
|
|
101
|
-
sm: {maxWidth:
|
|
102
|
-
md: {maxWidth:
|
|
103
|
-
lg: {maxWidth:
|
|
103
|
+
sm: {maxWidth: sizes.panelSm},
|
|
104
|
+
md: {maxWidth: sizes.panelMd},
|
|
105
|
+
lg: {maxWidth: sizes.panelLg},
|
|
104
106
|
|
|
105
107
|
// --- Slide animations: Right ---
|
|
106
108
|
slideInRight: {
|
|
@@ -166,7 +168,10 @@ export const styles = css.create({
|
|
|
166
168
|
// The closeAnimationDuration prop token drives both these and the unmount
|
|
167
169
|
// timeout, so the exit animation can never race the unmount.
|
|
168
170
|
closeDurationInstant: {animationDuration: duration.instant},
|
|
171
|
+
closeDurationMinimal: {animationDuration: duration.minimal},
|
|
172
|
+
closeDurationQuick: {animationDuration: duration.quick},
|
|
169
173
|
closeDurationFast: {animationDuration: duration.fast},
|
|
174
|
+
closeDurationBrisk: {animationDuration: duration.brisk},
|
|
170
175
|
closeDurationBase: {animationDuration: duration.base},
|
|
171
176
|
closeDurationSlow: {animationDuration: duration.slow},
|
|
172
177
|
|
|
@@ -221,7 +226,7 @@ export const styles = css.create({
|
|
|
221
226
|
paddingLeft: spacing.lg,
|
|
222
227
|
paddingRight: spacing.lg,
|
|
223
228
|
paddingBottom: spacing.lg,
|
|
224
|
-
borderTopWidth:
|
|
229
|
+
borderTopWidth: borders.hairline,
|
|
225
230
|
borderTopStyle: 'solid',
|
|
226
231
|
borderTopColor: colors.border,
|
|
227
232
|
},
|
|
@@ -231,8 +236,8 @@ export const styles = css.create({
|
|
|
231
236
|
display: 'flex',
|
|
232
237
|
alignItems: 'center',
|
|
233
238
|
justifyContent: 'center',
|
|
234
|
-
width:
|
|
235
|
-
height:
|
|
239
|
+
width: sizes.iconButton,
|
|
240
|
+
height: sizes.iconButton,
|
|
236
241
|
borderRadius: radii.sm,
|
|
237
242
|
borderWidth: 0,
|
|
238
243
|
backgroundColor: {
|
|
@@ -21,7 +21,12 @@ interface UseSwipeDismissOptions {
|
|
|
21
21
|
anchor: DrawerAnchor
|
|
22
22
|
enabled: boolean
|
|
23
23
|
onDismiss: () => void
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The panel element itself, not a ref: a Drawer open from its first render
|
|
26
|
+
* draws the panel inline, then remounts it into the portal once the mount
|
|
27
|
+
* exists, and listeners must follow the element that is actually shown.
|
|
28
|
+
*/
|
|
29
|
+
panel: HTMLDivElement | null
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
interface SwipeState {
|
|
@@ -32,6 +37,9 @@ interface SwipeState {
|
|
|
32
37
|
startTime: number
|
|
33
38
|
swiping: boolean
|
|
34
39
|
panelSize: number
|
|
40
|
+
/** Inner Duro scroll viewport the gesture started in; capture is deferred until the axis is decided */
|
|
41
|
+
deferredScroll: HTMLElement | null
|
|
42
|
+
pointerId: number
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
/**
|
|
@@ -41,7 +49,7 @@ interface SwipeState {
|
|
|
41
49
|
* Opposite direction: iOS-style — panel stays anchored, shows subtle
|
|
42
50
|
* overscroll feedback (box-shadow intensifies), then snaps back.
|
|
43
51
|
*/
|
|
44
|
-
export function useSwipeDismiss({anchor, enabled, onDismiss,
|
|
52
|
+
export function useSwipeDismiss({anchor, enabled, onDismiss, panel}: UseSwipeDismissOptions) {
|
|
45
53
|
const stateRef = useRef<SwipeState>({
|
|
46
54
|
startX: 0,
|
|
47
55
|
startY: 0,
|
|
@@ -50,6 +58,8 @@ export function useSwipeDismiss({anchor, enabled, onDismiss, panelRef}: UseSwipe
|
|
|
50
58
|
startTime: 0,
|
|
51
59
|
swiping: false,
|
|
52
60
|
panelSize: 0,
|
|
61
|
+
deferredScroll: null,
|
|
62
|
+
pointerId: 0,
|
|
53
63
|
})
|
|
54
64
|
|
|
55
65
|
const isHorizontal = anchor === 'left' || anchor === 'right'
|
|
@@ -169,14 +179,18 @@ export function useSwipeDismiss({anchor, enabled, onDismiss, panelRef}: UseSwipe
|
|
|
169
179
|
}, [])
|
|
170
180
|
|
|
171
181
|
useEffect(() => {
|
|
172
|
-
if (!enabled) return
|
|
173
|
-
const panel = panelRef.current
|
|
174
|
-
if (!panel) return
|
|
182
|
+
if (!enabled || !panel) return
|
|
175
183
|
|
|
176
184
|
function handlePointerDown(e: PointerEvent) {
|
|
177
185
|
if (e.button !== 0) return
|
|
178
186
|
const target = e.target as HTMLElement
|
|
179
|
-
|
|
187
|
+
const scrollEl = target.closest<HTMLElement>('[data-duro-scroll]')
|
|
188
|
+
// Outside a ScrollArea a control keeps its press. Inside one the gesture
|
|
189
|
+
// is decided on the first move even from a control: a list of toggles is
|
|
190
|
+
// all buttons, and touch adjustment snaps a finger onto one. A tap
|
|
191
|
+
// (under the 8px slop) still presses it; a drag never captures before
|
|
192
|
+
// the decision, so the release lands on the panel, not the control.
|
|
193
|
+
if (!scrollEl && target.closest('button, a, input, textarea, select, [role="button"]')) {
|
|
180
194
|
return
|
|
181
195
|
}
|
|
182
196
|
|
|
@@ -190,14 +204,30 @@ export function useSwipeDismiss({anchor, enabled, onDismiss, panelRef}: UseSwipe
|
|
|
190
204
|
state.swiping = false
|
|
191
205
|
state.panelSize = isHorizontal ? rect.width : rect.height
|
|
192
206
|
|
|
207
|
+
state.pointerId = e.pointerId
|
|
208
|
+
state.deferredScroll = scrollEl
|
|
209
|
+
|
|
193
210
|
panel!.dataset.panelSize = String(state.panelSize)
|
|
194
|
-
|
|
211
|
+
|
|
212
|
+
// Inside a Duro ScrollArea the scroll may own the gesture: wait for the first move.
|
|
213
|
+
if (!state.deferredScroll) beginCapture(e.pointerId)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function beginCapture(pointerId: number) {
|
|
217
|
+
panel!.setPointerCapture(pointerId)
|
|
195
218
|
|
|
196
219
|
// Freeze CSS animation — we take over positioning.
|
|
197
220
|
// Never restore it; the panel is already at its final resting position.
|
|
198
221
|
panel!.style.animation = 'none'
|
|
199
222
|
}
|
|
200
223
|
|
|
224
|
+
/** True when the viewport can't scroll further against the dismiss direction */
|
|
225
|
+
function isAtDismissEdge(el: HTMLElement) {
|
|
226
|
+
if (!isHorizontal) return el.scrollTop <= 0
|
|
227
|
+
if (anchor === 'right') return el.scrollLeft <= 0
|
|
228
|
+
return el.scrollLeft + el.clientWidth >= el.scrollWidth - 1
|
|
229
|
+
}
|
|
230
|
+
|
|
201
231
|
function handlePointerMove(e: PointerEvent) {
|
|
202
232
|
const state = stateRef.current
|
|
203
233
|
if (state.startTime === 0) return
|
|
@@ -219,8 +249,21 @@ export function useSwipeDismiss({anchor, enabled, onDismiss, panelRef}: UseSwipe
|
|
|
219
249
|
} else {
|
|
220
250
|
// Wrong axis — abort gesture
|
|
221
251
|
state.startTime = 0
|
|
252
|
+
state.deferredScroll = null
|
|
222
253
|
return
|
|
223
254
|
}
|
|
255
|
+
|
|
256
|
+
const scrollEl = state.deferredScroll
|
|
257
|
+
if (scrollEl) {
|
|
258
|
+
state.deferredScroll = null
|
|
259
|
+
if (getMovement(state) <= 0 || !isAtDismissEdge(scrollEl)) {
|
|
260
|
+
// The scroll owns this gesture: no swiping, no capture, no preventDefault
|
|
261
|
+
state.swiping = false
|
|
262
|
+
state.startTime = 0
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
beginCapture(state.pointerId)
|
|
266
|
+
}
|
|
224
267
|
}
|
|
225
268
|
|
|
226
269
|
if (state.swiping) {
|
|
@@ -239,6 +282,7 @@ export function useSwipeDismiss({anchor, enabled, onDismiss, panelRef}: UseSwipe
|
|
|
239
282
|
const velocity = movement / elapsed
|
|
240
283
|
|
|
241
284
|
state.startTime = 0
|
|
285
|
+
state.deferredScroll = null
|
|
242
286
|
|
|
243
287
|
if (!state.swiping) {
|
|
244
288
|
return
|
|
@@ -266,6 +310,7 @@ export function useSwipeDismiss({anchor, enabled, onDismiss, panelRef}: UseSwipe
|
|
|
266
310
|
const state = stateRef.current
|
|
267
311
|
state.startTime = 0
|
|
268
312
|
state.swiping = false
|
|
313
|
+
state.deferredScroll = null
|
|
269
314
|
resetStyles(panel!)
|
|
270
315
|
}
|
|
271
316
|
|
|
@@ -290,7 +335,7 @@ export function useSwipeDismiss({anchor, enabled, onDismiss, panelRef}: UseSwipe
|
|
|
290
335
|
}
|
|
291
336
|
}, [
|
|
292
337
|
enabled,
|
|
293
|
-
|
|
338
|
+
panel,
|
|
294
339
|
anchor,
|
|
295
340
|
isHorizontal,
|
|
296
341
|
getMovement,
|
|
@@ -11,6 +11,7 @@ import {Checkbox} from '../Checkbox/Checkbox'
|
|
|
11
11
|
import {Stack} from '../Stack/Stack'
|
|
12
12
|
import {Fieldset} from '../Fieldset/Fieldset'
|
|
13
13
|
import {spacing} from '@duro-app/tokens/tokens/spacing.css'
|
|
14
|
+
import {sizes} from '@duro-app/tokens/tokens/sizes.css'
|
|
14
15
|
|
|
15
16
|
const meta: Meta = {
|
|
16
17
|
title: 'Components/Field',
|
|
@@ -70,8 +71,8 @@ export const WithError: Story = {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const stackStyles = css.create({
|
|
73
|
-
stack: {display: 'flex', flexDirection: 'column', gap: spacing.md, maxWidth:
|
|
74
|
-
stackWide: {display: 'flex', flexDirection: 'column', gap: spacing.md, maxWidth:
|
|
74
|
+
stack: {display: 'flex', flexDirection: 'column', gap: spacing.md, maxWidth: sizes.dialogSm},
|
|
75
|
+
stackWide: {display: 'flex', flexDirection: 'column', gap: spacing.md, maxWidth: sizes.pageSm},
|
|
75
76
|
})
|
|
76
77
|
|
|
77
78
|
export const FormExample: Story = {
|
|
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
|
|
|
2
2
|
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
3
3
|
import {spacing} from '@duro-app/tokens/tokens/spacing.css'
|
|
4
4
|
import {typography} from '@duro-app/tokens/tokens/typography.css'
|
|
5
|
+
import {sizes} from '@duro-app/tokens/tokens/sizes.css'
|
|
5
6
|
|
|
6
7
|
export const styles = css.create({
|
|
7
8
|
root: {
|
|
@@ -21,7 +22,7 @@ export const styles = css.create({
|
|
|
21
22
|
color: colors.text,
|
|
22
23
|
},
|
|
23
24
|
labelSide: {
|
|
24
|
-
minWidth:
|
|
25
|
+
minWidth: sizes.labelMinW,
|
|
25
26
|
paddingTop: spacing.sm,
|
|
26
27
|
flexShrink: 0,
|
|
27
28
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {useState} from 'react'
|
|
2
2
|
import type {Meta, StoryObj} from '@storybook/react'
|
|
3
|
-
import {expect, fn, userEvent
|
|
3
|
+
import {expect, fn, userEvent} from 'storybook/test'
|
|
4
4
|
import {css, html} from 'react-strict-dom'
|
|
5
5
|
import {Schema} from 'effect'
|
|
6
6
|
import {Form} from './Form'
|
|
@@ -9,6 +9,7 @@ import {Input} from '../Input/Input'
|
|
|
9
9
|
import {Textarea} from '../Textarea/Textarea'
|
|
10
10
|
import {Fieldset} from '../Fieldset/Fieldset'
|
|
11
11
|
import {Button} from '../Button/Button'
|
|
12
|
+
import {sizes} from '@duro-app/tokens/tokens/sizes.css'
|
|
12
13
|
|
|
13
14
|
const meta: Meta = {
|
|
14
15
|
title: 'Components/Form',
|
|
@@ -48,8 +49,8 @@ const ProfileSchema = Schema.Struct({
|
|
|
48
49
|
})
|
|
49
50
|
|
|
50
51
|
const wrapStyles = css.create({
|
|
51
|
-
wrap: {maxWidth:
|
|
52
|
-
wrapWide: {maxWidth:
|
|
52
|
+
wrap: {maxWidth: sizes.dialogSm},
|
|
53
|
+
wrapWide: {maxWidth: sizes.pageSm},
|
|
53
54
|
})
|
|
54
55
|
|
|
55
56
|
// --- Stories ---
|
|
@@ -24,14 +24,14 @@ export const meta: ComponentMeta = {
|
|
|
24
24
|
relationship: 'Grid layout="split" with a List and a Panel is the split-pane recipe',
|
|
25
25
|
},
|
|
26
26
|
],
|
|
27
|
-
example: `// Split: list ≥ 240px beside a detail pane, one column below md
|
|
27
|
+
example: `// Split: list ≥ sizes.gridColSm (240px) beside a detail pane, one column below md
|
|
28
28
|
<Grid layout="split" gap="lg">
|
|
29
29
|
<List.Root>…</List.Root>
|
|
30
30
|
<Panel.Root>…</Panel.Root>
|
|
31
31
|
</Grid>
|
|
32
32
|
|
|
33
33
|
// Responsive: columns auto-fit based on min width
|
|
34
|
-
<Grid minColumnWidth="
|
|
34
|
+
<Grid minColumnWidth="gridColMd" gap="md">
|
|
35
35
|
<Card>A</Card>
|
|
36
36
|
<Card>B</Card>
|
|
37
37
|
<Card>C</Card>
|
|
@@ -8,6 +8,7 @@ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
|
|
|
8
8
|
import {SPACING_KEYS} from '@duro-app/tokens/keys'
|
|
9
9
|
import {useContainerQuery} from '../../hooks/useContainerQuery'
|
|
10
10
|
import {typography} from '@duro-app/tokens/tokens/typography.css'
|
|
11
|
+
import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
11
12
|
|
|
12
13
|
const meta: Meta<typeof Grid> = {
|
|
13
14
|
title: 'Layout/Grid',
|
|
@@ -45,7 +46,7 @@ const localStyles = css.create({
|
|
|
45
46
|
},
|
|
46
47
|
frame: (width: number) => ({
|
|
47
48
|
width,
|
|
48
|
-
borderWidth:
|
|
49
|
+
borderWidth: borders.hairline,
|
|
49
50
|
borderStyle: 'dashed',
|
|
50
51
|
borderColor: colors.border,
|
|
51
52
|
}),
|
|
@@ -74,7 +75,7 @@ export const FixedColumns: Story = {
|
|
|
74
75
|
|
|
75
76
|
export const AutoFit: Story = {
|
|
76
77
|
render: () => (
|
|
77
|
-
<Grid gap="md" minColumnWidth="
|
|
78
|
+
<Grid gap="md" minColumnWidth="gridColSm">
|
|
78
79
|
<Cell>Card A</Cell>
|
|
79
80
|
<Cell>Card B</Cell>
|
|
80
81
|
<Cell>Card C</Cell>
|
|
@@ -4,6 +4,7 @@ import {styles} from './styles.css'
|
|
|
4
4
|
import {isNative} from '../../platform'
|
|
5
5
|
import {cellBasis, cellPosition, columnWeights, gridTemplate, type GridColumns} from './columns'
|
|
6
6
|
import type {SpacingToken} from '@duro-app/tokens/keys'
|
|
7
|
+
import {resolveLength, type Length} from '../../shared/length'
|
|
7
8
|
|
|
8
9
|
export type GridLayout = 'split' | 'split-wide'
|
|
9
10
|
|
|
@@ -13,10 +14,11 @@ interface GridProps {
|
|
|
13
14
|
* two-thirds split. Weights are the portable form of a track list — a CSS
|
|
14
15
|
* template string would be silently ignored on native. */
|
|
15
16
|
columns?: GridColumns
|
|
16
|
-
/** Responsive auto-fit: as many columns of at least this width as fit
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
17
|
+
/** Responsive auto-fit: as many columns of at least this width as fit — a
|
|
18
|
+
* size token key (`"gridColMd"`) or a relative length. Wins over `columns`
|
|
19
|
+
* when both are set. On native this approximates to cells of that minimum
|
|
20
|
+
* width that grow and wrap. */
|
|
21
|
+
minColumnWidth?: Length
|
|
20
22
|
/**
|
|
21
23
|
* A named responsive layout. `split` is list/detail (1:2, list ≥ 240px),
|
|
22
24
|
* `split-wide` is nav/content (1:3, nav ≥ 280px). Each collapses to one
|
|
@@ -101,7 +103,7 @@ export function Grid({gap = 'md', columns, minColumnWidth, layout, children}: Gr
|
|
|
101
103
|
const columnStyle = layout
|
|
102
104
|
? layoutMap[layout]
|
|
103
105
|
: minColumnWidth
|
|
104
|
-
? styles.autoFit(minColumnWidth)
|
|
106
|
+
? styles.autoFit(resolveLength(minColumnWidth))
|
|
105
107
|
: typeof columns === 'number'
|
|
106
108
|
? columnsMap[columns]
|
|
107
109
|
: columns
|
|
@@ -136,7 +138,7 @@ function NativeGrid({gap = 'md', columns, minColumnWidth, children}: GridProps)
|
|
|
136
138
|
return (
|
|
137
139
|
<html.div style={[styles.nativeRow, gapMap[gap]]}>
|
|
138
140
|
{items.map((child, index) => (
|
|
139
|
-
<html.div key={index} style={styles.nativeMinCell(minColumnWidth)}>
|
|
141
|
+
<html.div key={index} style={styles.nativeMinCell(resolveLength(minColumnWidth))}>
|
|
140
142
|
{child}
|
|
141
143
|
</html.div>
|
|
142
144
|
))}
|
|
@@ -22,7 +22,7 @@ export const styles = css.create({
|
|
|
22
22
|
col4: {gridTemplateColumns: 'repeat(4, 1fr)'},
|
|
23
23
|
col5: {gridTemplateColumns: 'repeat(5, 1fr)'},
|
|
24
24
|
col6: {gridTemplateColumns: 'repeat(6, 1fr)'},
|
|
25
|
-
autoFit: (minWidth: string) => ({
|
|
25
|
+
autoFit: (minWidth: string | number) => ({
|
|
26
26
|
gridTemplateColumns: `repeat(auto-fill, minmax(${minWidth}, 1fr))`,
|
|
27
27
|
}),
|
|
28
28
|
// Hosts the @container query for a named layout. A container cannot query
|
|
@@ -86,7 +86,7 @@ export const styles = css.create({
|
|
|
86
86
|
// Auto-fit approximation: start at the minimum width, grow to fill. flexGrow
|
|
87
87
|
// is fine here: this style is native-only, so RSD-web's forced flex-grow: 0
|
|
88
88
|
// never applies.
|
|
89
|
-
nativeMinCell: (minWidth: string) => ({
|
|
89
|
+
nativeMinCell: (minWidth: string | number) => ({
|
|
90
90
|
flexBasis: minWidth,
|
|
91
91
|
flexGrow: 1,
|
|
92
92
|
boxSizing: 'border-box',
|