@duro-app/ui 0.52.0 → 1.1.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-xlMa_Kqe.js → Table-P-UHkEqJ.js} +333 -329
- package/dist/Table-P-UHkEqJ.js.map +1 -0
- package/dist/components/ActionBar/ActionBar.d.ts +6 -4
- package/dist/components/ActionBar/ActionBar.d.ts.map +1 -1
- package/dist/components/ButtonGroup/ButtonGroup.d.ts +3 -1
- package/dist/components/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts.map +1 -1
- package/dist/components/Cluster/Cluster.d.ts +2 -2
- package/dist/components/Cluster/Cluster.d.ts.map +1 -1
- package/dist/components/Cluster/Cluster.stories.d.ts.map +1 -1
- package/dist/components/ColorMode/ColorMode.d.ts +3 -2
- package/dist/components/ColorMode/ColorMode.d.ts.map +1 -1
- package/dist/components/Combobox/Combobox.stories.d.ts.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.d.ts +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -1
- package/dist/components/DetailPanel/DetailPanel.d.ts +3 -2
- package/dist/components/DetailPanel/DetailPanel.d.ts.map +1 -1
- package/dist/components/DetailPanel/styles.css.d.ts +15 -3
- package/dist/components/DetailPanel/styles.css.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +3 -2
- package/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/styles.css.d.ts +16 -4
- package/dist/components/Dialog/styles.css.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.d.ts +3 -2
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/styles.css.d.ts +16 -4
- package/dist/components/Drawer/styles.css.d.ts.map +1 -1
- package/dist/components/Fieldset/Fieldset.d.ts +2 -1
- package/dist/components/Fieldset/Fieldset.d.ts.map +1 -1
- package/dist/components/Grid/Grid.d.ts +2 -2
- package/dist/components/Grid/Grid.d.ts.map +1 -1
- package/dist/components/Grid/Grid.stories.d.ts.map +1 -1
- package/dist/components/Icon/Icon.d.ts +5 -3
- package/dist/components/Icon/Icon.d.ts.map +1 -1
- package/dist/components/Icon/index.d.ts +1 -1
- package/dist/components/Icon/index.d.ts.map +1 -1
- package/dist/components/Inline/Inline.d.ts +2 -2
- package/dist/components/Inline/Inline.d.ts.map +1 -1
- package/dist/components/Inline/Inline.stories.d.ts.map +1 -1
- package/dist/components/Stack/Stack.d.ts +2 -1
- package/dist/components/Stack/Stack.d.ts.map +1 -1
- package/dist/components/Stack/Stack.stories.d.ts.map +1 -1
- package/dist/components/StatusIcon/StatusIcon.d.ts +2 -2
- package/dist/components/StatusIcon/StatusIcon.d.ts.map +1 -1
- package/dist/components/Table/Table.d.ts +1 -7
- package/dist/components/Table/Table.d.ts.map +1 -1
- package/dist/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/components/Tabs/TabsContext.d.ts +2 -1
- package/dist/components/Tabs/TabsContext.d.ts.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroupContext.d.ts +2 -1
- package/dist/components/ToggleGroup/ToggleGroupContext.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/hooks/useContainerQuery.d.ts +3 -2
- package/dist/hooks/useContainerQuery.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +2270 -2177
- package/dist/index.js.map +1 -1
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/table.js +137 -139
- package/dist/table.js.map +1 -1
- package/package.json +3 -2
- package/src/components/ActionBar/ActionBar.tsx +10 -5
- package/src/components/Alert/Alert.tsx +2 -2
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +3 -2
- package/src/components/ButtonGroup/ButtonGroup.tsx +5 -2
- package/src/components/Callout/Callout.tsx +2 -2
- package/src/components/Cluster/Cluster.stories.tsx +2 -1
- package/src/components/Cluster/Cluster.tsx +3 -3
- package/src/components/ColorMode/ColorMode.tsx +4 -4
- package/src/components/Combobox/Combobox.stories.tsx +7 -3
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +1 -3
- package/src/components/DetailPanel/DetailPanel.tsx +23 -6
- package/src/components/DetailPanel/styles.css.ts +14 -7
- package/src/components/Dialog/Dialog.tsx +26 -5
- package/src/components/Dialog/styles.css.ts +12 -4
- package/src/components/Drawer/Drawer.tsx +17 -4
- package/src/components/Drawer/styles.css.ts +12 -4
- package/src/components/EmptyState/EmptyState.meta.ts +1 -1
- package/src/components/EmptyState/EmptyState.stories.tsx +2 -2
- package/src/components/Fieldset/Fieldset.tsx +3 -2
- package/src/components/Grid/Grid.stories.tsx +2 -1
- package/src/components/Grid/Grid.tsx +3 -3
- package/src/components/Icon/Icon.meta.ts +3 -3
- package/src/components/Icon/Icon.tsx +7 -2
- package/src/components/Icon/index.ts +1 -1
- package/src/components/Inline/Inline.stories.tsx +2 -1
- package/src/components/Inline/Inline.tsx +3 -3
- package/src/components/SideNav/SideNav.meta.ts +3 -3
- package/src/components/SideNav/SideNav.stories.tsx +8 -8
- package/src/components/Stack/Stack.stories.tsx +3 -2
- package/src/components/Stack/Stack.tsx +3 -2
- package/src/components/StatusIcon/StatusIcon.stories.tsx +1 -1
- package/src/components/StatusIcon/StatusIcon.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +29 -32
- package/src/components/Table/Table.tsx +0 -13
- package/src/components/Tabs/TabsContext.ts +2 -1
- package/src/components/Toast/ToastProvider.tsx +2 -2
- package/src/components/Toggle/Toggle.meta.ts +1 -1
- package/src/components/ToggleGroup/ToggleGroupContext.ts +2 -1
- package/src/components/VirtualTable/VirtualTable.tsx +6 -5
- package/src/hooks/useContainerQuery.ts +10 -3
- package/src/shared/types.ts +3 -0
- package/dist/Table-xlMa_Kqe.js.map +0 -1
|
@@ -10,10 +10,18 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
} from 'react'
|
|
12
12
|
import {html} from 'react-strict-dom'
|
|
13
|
+
import {DURATION_MS, type DurationToken} from '@duro-app/tokens/keys'
|
|
13
14
|
import {styles} from './styles.css'
|
|
14
15
|
|
|
15
16
|
// --- Types ---
|
|
16
17
|
|
|
18
|
+
const closeDurationMap = {
|
|
19
|
+
instant: styles.closeDurationInstant,
|
|
20
|
+
fast: styles.closeDurationFast,
|
|
21
|
+
base: styles.closeDurationBase,
|
|
22
|
+
slow: styles.closeDurationSlow,
|
|
23
|
+
} as const satisfies Record<DurationToken, unknown>
|
|
24
|
+
|
|
17
25
|
export type DetailPanelSize = 'sm' | 'md'
|
|
18
26
|
|
|
19
27
|
// --- Context ---
|
|
@@ -21,6 +29,7 @@ export type DetailPanelSize = 'sm' | 'md'
|
|
|
21
29
|
interface DetailPanelContextValue {
|
|
22
30
|
present: boolean
|
|
23
31
|
closing: boolean
|
|
32
|
+
closeDuration: DurationToken
|
|
24
33
|
requestOpen: () => void
|
|
25
34
|
requestClose: () => void
|
|
26
35
|
titleId: string
|
|
@@ -48,8 +57,8 @@ interface RootProps {
|
|
|
48
57
|
defaultOpen?: boolean
|
|
49
58
|
/** Called when open state changes */
|
|
50
59
|
onOpenChange?: (open: boolean) => void
|
|
51
|
-
/**
|
|
52
|
-
closeAnimationDuration?:
|
|
60
|
+
/** Motion token for the close animation. Default: 'base' (200ms) */
|
|
61
|
+
closeAnimationDuration?: DurationToken
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
function Root({
|
|
@@ -57,7 +66,7 @@ function Root({
|
|
|
57
66
|
open: controlledOpen,
|
|
58
67
|
defaultOpen = false,
|
|
59
68
|
onOpenChange,
|
|
60
|
-
closeAnimationDuration =
|
|
69
|
+
closeAnimationDuration = 'base',
|
|
61
70
|
}: RootProps) {
|
|
62
71
|
const isControlled = controlledOpen !== undefined
|
|
63
72
|
const [internalOpen, setInternalOpen] = useState(defaultOpen)
|
|
@@ -83,7 +92,7 @@ function Root({
|
|
|
83
92
|
setClosing(false)
|
|
84
93
|
setPresent(false)
|
|
85
94
|
closingTimerRef.current = null
|
|
86
|
-
}, closeAnimationDuration)
|
|
95
|
+
}, DURATION_MS[closeAnimationDuration])
|
|
87
96
|
}
|
|
88
97
|
}, [isOpen, present, closing, closeAnimationDuration])
|
|
89
98
|
|
|
@@ -154,6 +163,7 @@ function Root({
|
|
|
154
163
|
value={{
|
|
155
164
|
present,
|
|
156
165
|
closing,
|
|
166
|
+
closeDuration: closeAnimationDuration,
|
|
157
167
|
requestOpen,
|
|
158
168
|
requestClose,
|
|
159
169
|
titleId,
|
|
@@ -189,12 +199,18 @@ const wrapperCloseMap = {
|
|
|
189
199
|
} as const
|
|
190
200
|
|
|
191
201
|
function Content({children, size = 'sm', label}: ContentProps) {
|
|
192
|
-
const {present, closing, labelId, panelRef} = useDetailPanel()
|
|
202
|
+
const {present, closing, closeDuration, labelId, panelRef} = useDetailPanel()
|
|
193
203
|
|
|
194
204
|
if (!present) return null
|
|
195
205
|
|
|
196
206
|
return (
|
|
197
|
-
<html.div
|
|
207
|
+
<html.div
|
|
208
|
+
style={[
|
|
209
|
+
styles.wrapper,
|
|
210
|
+
closing ? wrapperCloseMap[size] : wrapperOpenMap[size],
|
|
211
|
+
closing && closeDurationMap[closeDuration],
|
|
212
|
+
]}
|
|
213
|
+
>
|
|
198
214
|
<html.div
|
|
199
215
|
ref={panelRef}
|
|
200
216
|
role="complementary"
|
|
@@ -205,6 +221,7 @@ function Content({children, size = 'sm', label}: ContentProps) {
|
|
|
205
221
|
styles.content,
|
|
206
222
|
size === 'sm' ? styles.contentSm : styles.contentMd,
|
|
207
223
|
closing ? styles.slideOut : styles.slideIn,
|
|
224
|
+
closing && closeDurationMap[closeDuration],
|
|
208
225
|
]}
|
|
209
226
|
>
|
|
210
227
|
{children}
|
|
@@ -4,10 +4,9 @@ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
|
|
|
4
4
|
import {shadows} from '@duro-app/tokens/tokens/shadows.css'
|
|
5
5
|
import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
|
|
6
6
|
|
|
7
|
-
// The "Apple ease" curves and the 280ms open
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
const CLOSE_DURATION = '220ms'
|
|
7
|
+
// The "Apple ease" curves and the 280ms open live in the shared motion tokens
|
|
8
|
+
// (easing.easeOut/easeIn, duration.slow). The close is duration.base, and the
|
|
9
|
+
// closeAnimationDuration prop can override it via the closeDuration* styles.
|
|
11
10
|
|
|
12
11
|
export const styles = css.create({
|
|
13
12
|
// --- Outer wrapper: animates width from 0 → target ---
|
|
@@ -32,7 +31,7 @@ export const styles = css.create({
|
|
|
32
31
|
from: {width: 360},
|
|
33
32
|
to: {width: 0},
|
|
34
33
|
}),
|
|
35
|
-
animationDuration:
|
|
34
|
+
animationDuration: duration.base,
|
|
36
35
|
animationTimingFunction: easing.easeIn,
|
|
37
36
|
animationFillMode: 'both',
|
|
38
37
|
},
|
|
@@ -52,7 +51,7 @@ export const styles = css.create({
|
|
|
52
51
|
from: {width: 480},
|
|
53
52
|
to: {width: 0},
|
|
54
53
|
}),
|
|
55
|
-
animationDuration:
|
|
54
|
+
animationDuration: duration.base,
|
|
56
55
|
animationTimingFunction: easing.easeIn,
|
|
57
56
|
animationFillMode: 'both',
|
|
58
57
|
},
|
|
@@ -101,11 +100,19 @@ export const styles = css.create({
|
|
|
101
100
|
opacity: 0,
|
|
102
101
|
},
|
|
103
102
|
}),
|
|
104
|
-
animationDuration:
|
|
103
|
+
animationDuration: duration.base,
|
|
105
104
|
animationTimingFunction: easing.easeIn,
|
|
106
105
|
animationFillMode: 'both',
|
|
107
106
|
},
|
|
108
107
|
|
|
108
|
+
// --- Close-duration overrides ---
|
|
109
|
+
// The closeAnimationDuration prop token drives both these and the unmount
|
|
110
|
+
// timeout, so the exit animation can never race the unmount.
|
|
111
|
+
closeDurationInstant: {animationDuration: duration.instant},
|
|
112
|
+
closeDurationFast: {animationDuration: duration.fast},
|
|
113
|
+
closeDurationBase: {animationDuration: duration.base},
|
|
114
|
+
closeDurationSlow: {animationDuration: duration.slow},
|
|
115
|
+
|
|
109
116
|
// --- Header ---
|
|
110
117
|
header: {
|
|
111
118
|
display: 'flex',
|
|
@@ -10,17 +10,26 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
} from 'react'
|
|
12
12
|
import {html} from 'react-strict-dom'
|
|
13
|
+
import {DURATION_MS, type DurationToken} from '@duro-app/tokens/keys'
|
|
13
14
|
import {styles} from './styles.css'
|
|
14
15
|
|
|
15
16
|
// --- Types ---
|
|
16
17
|
|
|
17
18
|
export type DialogSize = 'sm' | 'md' | 'lg'
|
|
18
19
|
|
|
20
|
+
const closeDurationMap = {
|
|
21
|
+
instant: styles.closeDurationInstant,
|
|
22
|
+
fast: styles.closeDurationFast,
|
|
23
|
+
base: styles.closeDurationBase,
|
|
24
|
+
slow: styles.closeDurationSlow,
|
|
25
|
+
} as const satisfies Record<DurationToken, unknown>
|
|
26
|
+
|
|
19
27
|
// --- Context ---
|
|
20
28
|
|
|
21
29
|
interface DialogContextValue {
|
|
22
30
|
open: boolean
|
|
23
31
|
closing: boolean
|
|
32
|
+
closeDuration: DurationToken
|
|
24
33
|
dismissable: boolean
|
|
25
34
|
requestOpen: () => void
|
|
26
35
|
requestClose: () => void
|
|
@@ -49,8 +58,8 @@ interface RootProps {
|
|
|
49
58
|
onOpenChange?: (open: boolean) => void
|
|
50
59
|
/** Whether clicking backdrop closes the dialog. Default: true */
|
|
51
60
|
dismissable?: boolean
|
|
52
|
-
/**
|
|
53
|
-
closeAnimationDuration?:
|
|
61
|
+
/** Motion token for the close animation. Default: 'fast' (150ms) */
|
|
62
|
+
closeAnimationDuration?: DurationToken
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
function Root({
|
|
@@ -59,7 +68,7 @@ function Root({
|
|
|
59
68
|
defaultOpen = false,
|
|
60
69
|
onOpenChange,
|
|
61
70
|
dismissable = true,
|
|
62
|
-
closeAnimationDuration =
|
|
71
|
+
closeAnimationDuration = 'fast',
|
|
63
72
|
}: RootProps) {
|
|
64
73
|
const isControlled = controlledOpen !== undefined
|
|
65
74
|
const [internalOpen, setInternalOpen] = useState(defaultOpen)
|
|
@@ -89,7 +98,7 @@ function Root({
|
|
|
89
98
|
closingTimerRef.current = null
|
|
90
99
|
if (!isControlled) setInternalOpen(false)
|
|
91
100
|
onOpenChange?.(false)
|
|
92
|
-
}, closeAnimationDuration)
|
|
101
|
+
}, DURATION_MS[closeAnimationDuration])
|
|
93
102
|
}, [isControlled, onOpenChange, closeAnimationDuration])
|
|
94
103
|
|
|
95
104
|
// If controlled open goes false externally, trigger close animation
|
|
@@ -137,6 +146,7 @@ function Root({
|
|
|
137
146
|
value={{
|
|
138
147
|
open: isOpen || closing,
|
|
139
148
|
closing,
|
|
149
|
+
closeDuration: closeAnimationDuration,
|
|
140
150
|
dismissable,
|
|
141
151
|
requestOpen,
|
|
142
152
|
requestClose,
|
|
@@ -174,7 +184,16 @@ interface PortalProps {
|
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
function Portal({children, size = 'md'}: PortalProps) {
|
|
177
|
-
const {
|
|
187
|
+
const {
|
|
188
|
+
open,
|
|
189
|
+
closing,
|
|
190
|
+
closeDuration,
|
|
191
|
+
dismissable,
|
|
192
|
+
requestClose,
|
|
193
|
+
titleId,
|
|
194
|
+
descriptionId,
|
|
195
|
+
popupRef,
|
|
196
|
+
} = useDialog()
|
|
178
197
|
|
|
179
198
|
const handleBackdropClick = useCallback(() => {
|
|
180
199
|
if (dismissable) {
|
|
@@ -191,6 +210,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
191
210
|
style={[
|
|
192
211
|
styles.backdrop,
|
|
193
212
|
closing && styles.backdropClosing,
|
|
213
|
+
closing && closeDurationMap[closeDuration],
|
|
194
214
|
!closing && styles.backdropOpen,
|
|
195
215
|
]}
|
|
196
216
|
aria-hidden
|
|
@@ -209,6 +229,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
209
229
|
styles.popup,
|
|
210
230
|
styles[size],
|
|
211
231
|
closing && styles.popupClosing,
|
|
232
|
+
closing && closeDurationMap[closeDuration],
|
|
212
233
|
!closing && styles.popupOpen,
|
|
213
234
|
]}
|
|
214
235
|
>
|
|
@@ -19,7 +19,7 @@ export const styles = css.create({
|
|
|
19
19
|
from: {opacity: 0},
|
|
20
20
|
to: {opacity: 1},
|
|
21
21
|
}),
|
|
22
|
-
animationDuration:
|
|
22
|
+
animationDuration: duration.base,
|
|
23
23
|
animationTimingFunction: easing.easeOut,
|
|
24
24
|
animationFillMode: 'both',
|
|
25
25
|
},
|
|
@@ -29,7 +29,7 @@ export const styles = css.create({
|
|
|
29
29
|
from: {opacity: 1},
|
|
30
30
|
to: {opacity: 0},
|
|
31
31
|
}),
|
|
32
|
-
animationDuration:
|
|
32
|
+
animationDuration: duration.fast,
|
|
33
33
|
animationTimingFunction: easing.easeIn,
|
|
34
34
|
animationFillMode: 'both',
|
|
35
35
|
},
|
|
@@ -75,7 +75,7 @@ export const styles = css.create({
|
|
|
75
75
|
transform: 'scale(1) translateY(0)',
|
|
76
76
|
},
|
|
77
77
|
}),
|
|
78
|
-
animationDuration:
|
|
78
|
+
animationDuration: duration.base,
|
|
79
79
|
animationTimingFunction: easing.easeOut,
|
|
80
80
|
animationFillMode: 'both',
|
|
81
81
|
},
|
|
@@ -91,11 +91,19 @@ export const styles = css.create({
|
|
|
91
91
|
transform: 'scale(0.97) translateY(8px)',
|
|
92
92
|
},
|
|
93
93
|
}),
|
|
94
|
-
animationDuration:
|
|
94
|
+
animationDuration: duration.fast,
|
|
95
95
|
animationTimingFunction: easing.easeIn,
|
|
96
96
|
animationFillMode: 'both',
|
|
97
97
|
},
|
|
98
98
|
|
|
99
|
+
// --- Close-duration overrides ---
|
|
100
|
+
// The closeAnimationDuration prop token drives both these and the unmount
|
|
101
|
+
// timeout, so the exit animation can never race the unmount.
|
|
102
|
+
closeDurationInstant: {animationDuration: duration.instant},
|
|
103
|
+
closeDurationFast: {animationDuration: duration.fast},
|
|
104
|
+
closeDurationBase: {animationDuration: duration.base},
|
|
105
|
+
closeDurationSlow: {animationDuration: duration.slow},
|
|
106
|
+
|
|
99
107
|
// --- Sizes ---
|
|
100
108
|
sm: {maxWidth: 400},
|
|
101
109
|
md: {maxWidth: 520},
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
} from 'react'
|
|
12
12
|
import {html} from 'react-strict-dom'
|
|
13
|
+
import {DURATION_MS, type DurationToken} from '@duro-app/tokens/keys'
|
|
13
14
|
import {styles} from './styles.css'
|
|
14
15
|
import {useSwipeDismiss} from './useSwipeDismiss'
|
|
15
16
|
|
|
@@ -18,11 +19,19 @@ import {useSwipeDismiss} from './useSwipeDismiss'
|
|
|
18
19
|
export type DrawerAnchor = 'right' | 'left' | 'bottom'
|
|
19
20
|
export type DrawerSize = 'sm' | 'md' | 'lg'
|
|
20
21
|
|
|
22
|
+
const closeDurationMap = {
|
|
23
|
+
instant: styles.closeDurationInstant,
|
|
24
|
+
fast: styles.closeDurationFast,
|
|
25
|
+
base: styles.closeDurationBase,
|
|
26
|
+
slow: styles.closeDurationSlow,
|
|
27
|
+
} as const satisfies Record<DurationToken, unknown>
|
|
28
|
+
|
|
21
29
|
// --- Context ---
|
|
22
30
|
|
|
23
31
|
interface DrawerContextValue {
|
|
24
32
|
open: boolean
|
|
25
33
|
closing: boolean
|
|
34
|
+
closeDuration: DurationToken
|
|
26
35
|
anchor: DrawerAnchor
|
|
27
36
|
dismissable: boolean
|
|
28
37
|
swipeDismiss: boolean
|
|
@@ -59,8 +68,8 @@ interface RootProps {
|
|
|
59
68
|
dismissable?: boolean
|
|
60
69
|
/** Whether swipe gesture dismisses the drawer. Default: true */
|
|
61
70
|
swipeDismiss?: boolean
|
|
62
|
-
/**
|
|
63
|
-
closeAnimationDuration?:
|
|
71
|
+
/** Motion token for the close animation. Default: 'fast' (150ms) */
|
|
72
|
+
closeAnimationDuration?: DurationToken
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
function Root({
|
|
@@ -71,7 +80,7 @@ function Root({
|
|
|
71
80
|
anchor = 'right',
|
|
72
81
|
dismissable = true,
|
|
73
82
|
swipeDismiss = true,
|
|
74
|
-
closeAnimationDuration =
|
|
83
|
+
closeAnimationDuration = 'fast',
|
|
75
84
|
}: RootProps) {
|
|
76
85
|
const isControlled = controlledOpen !== undefined
|
|
77
86
|
const [internalOpen, setInternalOpen] = useState(defaultOpen)
|
|
@@ -101,7 +110,7 @@ function Root({
|
|
|
101
110
|
closingTimerRef.current = null
|
|
102
111
|
if (!isControlled) setInternalOpen(false)
|
|
103
112
|
onOpenChange?.(false)
|
|
104
|
-
}, closeAnimationDuration)
|
|
113
|
+
}, DURATION_MS[closeAnimationDuration])
|
|
105
114
|
}, [isControlled, onOpenChange, closeAnimationDuration])
|
|
106
115
|
|
|
107
116
|
const requestCloseImmediate = useCallback(() => {
|
|
@@ -152,6 +161,7 @@ function Root({
|
|
|
152
161
|
value={{
|
|
153
162
|
open: isOpen || closing,
|
|
154
163
|
closing,
|
|
164
|
+
closeDuration: closeAnimationDuration,
|
|
155
165
|
anchor,
|
|
156
166
|
dismissable,
|
|
157
167
|
swipeDismiss,
|
|
@@ -210,6 +220,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
210
220
|
const {
|
|
211
221
|
open,
|
|
212
222
|
closing,
|
|
223
|
+
closeDuration,
|
|
213
224
|
anchor,
|
|
214
225
|
dismissable,
|
|
215
226
|
swipeDismiss: swipeEnabled,
|
|
@@ -245,6 +256,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
245
256
|
style={[
|
|
246
257
|
styles.backdrop,
|
|
247
258
|
closing && styles.backdropClosing,
|
|
259
|
+
closing && closeDurationMap[closeDuration],
|
|
248
260
|
!closing && styles.backdropOpen,
|
|
249
261
|
]}
|
|
250
262
|
aria-hidden
|
|
@@ -266,6 +278,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
266
278
|
!isHorizontal && styles.panelVertical,
|
|
267
279
|
anchor === 'left' && styles.panelLeftBorder,
|
|
268
280
|
closing && slideOutMap[anchor],
|
|
281
|
+
closing && closeDurationMap[closeDuration],
|
|
269
282
|
!closing && slideInMap[anchor],
|
|
270
283
|
]}
|
|
271
284
|
>
|
|
@@ -27,7 +27,7 @@ export const styles = css.create({
|
|
|
27
27
|
from: {opacity: 1},
|
|
28
28
|
to: {opacity: 0},
|
|
29
29
|
}),
|
|
30
|
-
animationDuration:
|
|
30
|
+
animationDuration: duration.fast,
|
|
31
31
|
animationTimingFunction: easing.easeIn,
|
|
32
32
|
animationFillMode: 'both',
|
|
33
33
|
},
|
|
@@ -114,7 +114,7 @@ export const styles = css.create({
|
|
|
114
114
|
from: {transform: 'translateX(0)'},
|
|
115
115
|
to: {transform: 'translateX(100%)'},
|
|
116
116
|
}),
|
|
117
|
-
animationDuration:
|
|
117
|
+
animationDuration: duration.fast,
|
|
118
118
|
animationTimingFunction: easing.easeIn,
|
|
119
119
|
animationFillMode: 'both',
|
|
120
120
|
},
|
|
@@ -134,7 +134,7 @@ export const styles = css.create({
|
|
|
134
134
|
from: {transform: 'translateX(0)'},
|
|
135
135
|
to: {transform: 'translateX(-100%)'},
|
|
136
136
|
}),
|
|
137
|
-
animationDuration:
|
|
137
|
+
animationDuration: duration.fast,
|
|
138
138
|
animationTimingFunction: easing.easeIn,
|
|
139
139
|
animationFillMode: 'both',
|
|
140
140
|
},
|
|
@@ -154,11 +154,19 @@ export const styles = css.create({
|
|
|
154
154
|
from: {transform: 'translateY(0)'},
|
|
155
155
|
to: {transform: 'translateY(100%)'},
|
|
156
156
|
}),
|
|
157
|
-
animationDuration:
|
|
157
|
+
animationDuration: duration.fast,
|
|
158
158
|
animationTimingFunction: easing.easeIn,
|
|
159
159
|
animationFillMode: 'both',
|
|
160
160
|
},
|
|
161
161
|
|
|
162
|
+
// --- Close-duration overrides ---
|
|
163
|
+
// The closeAnimationDuration prop token drives both these and the unmount
|
|
164
|
+
// timeout, so the exit animation can never race the unmount.
|
|
165
|
+
closeDurationInstant: {animationDuration: duration.instant},
|
|
166
|
+
closeDurationFast: {animationDuration: duration.fast},
|
|
167
|
+
closeDurationBase: {animationDuration: duration.base},
|
|
168
|
+
closeDurationSlow: {animationDuration: duration.slow},
|
|
169
|
+
|
|
162
170
|
// --- Header ---
|
|
163
171
|
header: {
|
|
164
172
|
display: 'flex',
|
|
@@ -11,7 +11,7 @@ export const meta: ComponentMeta = {
|
|
|
11
11
|
whenNotToUse: ['Loading states — use Spinner', 'Error states — use Alert or Callout'],
|
|
12
12
|
example: `<Card>
|
|
13
13
|
<EmptyState
|
|
14
|
-
icon={<Icon name="info-circle" size=
|
|
14
|
+
icon={<Icon name="info-circle" size="xxl" />}
|
|
15
15
|
message="No results found"
|
|
16
16
|
action={<Button variant="secondary">Clear filters</Button>}
|
|
17
17
|
/>
|
|
@@ -23,7 +23,7 @@ export const Default: Story = {
|
|
|
23
23
|
export const WithIcon: Story = {
|
|
24
24
|
args: {
|
|
25
25
|
message: 'Nothing to show here',
|
|
26
|
-
icon: <StatusIcon name="forbidden" variant="muted" size=
|
|
26
|
+
icon: <StatusIcon name="forbidden" variant="muted" size="xxl" />,
|
|
27
27
|
},
|
|
28
28
|
play: async ({canvas}) => {
|
|
29
29
|
await expect(canvas.getByText('Nothing to show here')).toBeInTheDocument()
|
|
@@ -44,7 +44,7 @@ export const WithAction: Story = {
|
|
|
44
44
|
export const WithIconAndAction: Story = {
|
|
45
45
|
args: {
|
|
46
46
|
message: 'Your inbox is empty',
|
|
47
|
-
icon: <StatusIcon name="check-done" variant="success" size=
|
|
47
|
+
icon: <StatusIcon name="check-done" variant="success" size="xxl" />,
|
|
48
48
|
action: <Button variant="primary">Refresh</Button>,
|
|
49
49
|
},
|
|
50
50
|
play: async ({canvas}) => {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
import {html} from 'react-strict-dom'
|
|
3
|
+
import type {SpacingToken} from '@duro-app/tokens/keys'
|
|
3
4
|
import {styles} from './styles.css'
|
|
4
5
|
|
|
5
|
-
export type FieldsetGap =
|
|
6
|
+
export type FieldsetGap = Exclude<SpacingToken, 'xxl' | 'xxxl'>
|
|
6
7
|
|
|
7
8
|
const gapMap = {
|
|
8
9
|
xs: styles.gapXs,
|
|
@@ -11,7 +12,7 @@ const gapMap = {
|
|
|
11
12
|
md: styles.gapMd,
|
|
12
13
|
lg: styles.gapLg,
|
|
13
14
|
xl: styles.gapXl,
|
|
14
|
-
} as const
|
|
15
|
+
} as const satisfies Record<FieldsetGap, unknown>
|
|
15
16
|
|
|
16
17
|
// --- Root ---
|
|
17
18
|
interface RootProps {
|
|
@@ -4,6 +4,7 @@ import {Grid} from './Grid'
|
|
|
4
4
|
import {Stack} from '../Stack/Stack'
|
|
5
5
|
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
6
6
|
import {spacing} from '@duro-app/tokens/tokens/spacing.css'
|
|
7
|
+
import {SPACING_KEYS} from '@duro-app/tokens/keys'
|
|
7
8
|
import {useContainerQuery} from '../../hooks/useContainerQuery'
|
|
8
9
|
|
|
9
10
|
const meta: Meta<typeof Grid> = {
|
|
@@ -12,7 +13,7 @@ const meta: Meta<typeof Grid> = {
|
|
|
12
13
|
argTypes: {
|
|
13
14
|
gap: {
|
|
14
15
|
control: 'select',
|
|
15
|
-
options: [
|
|
16
|
+
options: [...SPACING_KEYS],
|
|
16
17
|
},
|
|
17
18
|
columns: {
|
|
18
19
|
control: 'select',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
import {html} from 'react-strict-dom'
|
|
3
3
|
import {styles} from './styles.css'
|
|
4
|
-
import type {
|
|
4
|
+
import type {SpacingToken} from '@duro-app/tokens/keys'
|
|
5
5
|
|
|
6
6
|
interface GridProps {
|
|
7
|
-
gap?:
|
|
7
|
+
gap?: SpacingToken
|
|
8
8
|
columns?: 1 | 2 | 3 | 4 | 5 | 6
|
|
9
9
|
minColumnWidth?: string
|
|
10
10
|
children: ReactNode
|
|
@@ -19,7 +19,7 @@ const gapMap = {
|
|
|
19
19
|
xl: styles.gapXl,
|
|
20
20
|
xxl: styles.gapXxl,
|
|
21
21
|
xxxl: styles.gapXxxl,
|
|
22
|
-
} as const
|
|
22
|
+
} as const satisfies Record<SpacingToken, unknown>
|
|
23
23
|
|
|
24
24
|
const columnsMap = {
|
|
25
25
|
1: styles.col1,
|
|
@@ -16,8 +16,8 @@ export const meta: ComponentMeta = {
|
|
|
16
16
|
},
|
|
17
17
|
],
|
|
18
18
|
example: `<Inline gap="sm" align="center">
|
|
19
|
-
<Icon name="check-circle" size=
|
|
20
|
-
<Icon name="alert-triangle" size=
|
|
21
|
-
<Icon name="shield-filled" size=
|
|
19
|
+
<Icon name="check-circle" size="md" />
|
|
20
|
+
<Icon name="alert-triangle" size="md" />
|
|
21
|
+
<Icon name="shield-filled" size="md" />
|
|
22
22
|
</Inline>`,
|
|
23
23
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
|
+
import {ICON_SIZES, type IconSize} from '@duro-app/tokens/keys'
|
|
3
|
+
|
|
4
|
+
export type {IconSize}
|
|
2
5
|
|
|
3
6
|
export type IconName =
|
|
4
7
|
| 'x-circle'
|
|
@@ -350,10 +353,12 @@ const filledIcons: Partial<Record<IconName, ReactNode>> = {
|
|
|
350
353
|
|
|
351
354
|
interface IconProps {
|
|
352
355
|
name: IconName
|
|
353
|
-
size
|
|
356
|
+
/** Token from the icon size scale: sm 16, md 18, lg 24, xl 36, xxl 48. */
|
|
357
|
+
size?: IconSize
|
|
354
358
|
}
|
|
355
359
|
|
|
356
|
-
export function Icon({name, size =
|
|
360
|
+
export function Icon({name, size: sizeToken = 'lg'}: IconProps) {
|
|
361
|
+
const size = ICON_SIZES[sizeToken]
|
|
357
362
|
const filled = filledIcons[name]
|
|
358
363
|
if (filled) {
|
|
359
364
|
return (
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export {Icon} from './Icon'
|
|
2
|
-
export type {IconName} from './Icon'
|
|
2
|
+
export type {IconName, IconSize} from './Icon'
|
|
@@ -3,6 +3,7 @@ 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
5
|
import {spacing} from '@duro-app/tokens/tokens/spacing.css'
|
|
6
|
+
import {SPACING_KEYS} from '@duro-app/tokens/keys'
|
|
6
7
|
|
|
7
8
|
const meta: Meta<typeof Inline> = {
|
|
8
9
|
title: 'Layout/Inline',
|
|
@@ -10,7 +11,7 @@ const meta: Meta<typeof Inline> = {
|
|
|
10
11
|
argTypes: {
|
|
11
12
|
gap: {
|
|
12
13
|
control: 'select',
|
|
13
|
-
options: [
|
|
14
|
+
options: [...SPACING_KEYS],
|
|
14
15
|
},
|
|
15
16
|
align: {
|
|
16
17
|
control: 'select',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
import {html} from 'react-strict-dom'
|
|
3
3
|
import {styles} from './styles.css'
|
|
4
|
-
import type {
|
|
4
|
+
import type {SpacingToken} from '@duro-app/tokens/keys'
|
|
5
5
|
|
|
6
6
|
interface InlineProps {
|
|
7
|
-
gap?:
|
|
7
|
+
gap?: SpacingToken
|
|
8
8
|
align?: 'start' | 'center' | 'end' | 'baseline' | 'stretch'
|
|
9
9
|
justify?: 'start' | 'center' | 'end' | 'between'
|
|
10
10
|
children: ReactNode
|
|
@@ -19,7 +19,7 @@ const gapMap = {
|
|
|
19
19
|
xl: styles.gapXl,
|
|
20
20
|
xxl: styles.gapXxl,
|
|
21
21
|
xxxl: styles.gapXxxl,
|
|
22
|
-
} as const
|
|
22
|
+
} as const satisfies Record<SpacingToken, unknown>
|
|
23
23
|
|
|
24
24
|
const alignMap = {
|
|
25
25
|
start: styles.alignStart,
|
|
@@ -26,12 +26,12 @@ export const meta: ComponentMeta = {
|
|
|
26
26
|
example: `<SideNav.Root defaultValue="identities">
|
|
27
27
|
{/* Default: always-open sections. Nothing is a click away from being seen. */}
|
|
28
28
|
<SideNav.Section label="People & access">
|
|
29
|
-
<SideNav.Item value="identities" icon={<Icon name="users" size=
|
|
30
|
-
<SideNav.Item value="grants" icon={<Icon name="key" size=
|
|
29
|
+
<SideNav.Item value="identities" icon={<Icon name="users" size="md" />}>Identities</SideNav.Item>
|
|
30
|
+
<SideNav.Item value="grants" icon={<Icon name="key" size="md" />}>Grants</SideNav.Item>
|
|
31
31
|
</SideNav.Section>
|
|
32
32
|
{/* Disclosure, earned: rarely visited, so it starts collapsed. */}
|
|
33
33
|
<SideNav.Group label="Advanced" defaultExpanded={false}>
|
|
34
|
-
<SideNav.Item value="plugins" icon={<Icon name="plug" size=
|
|
34
|
+
<SideNav.Item value="plugins" icon={<Icon name="plug" size="md" />}>Plugins</SideNav.Item>
|
|
35
35
|
</SideNav.Group>
|
|
36
36
|
</SideNav.Root>`,
|
|
37
37
|
}
|
|
@@ -58,20 +58,20 @@ export const SectionsWithOneDisclosedGroup: Story = {
|
|
|
58
58
|
render: (args) => (
|
|
59
59
|
<SideNav.Root {...args} defaultValue="identities">
|
|
60
60
|
<SideNav.Section label="People & access">
|
|
61
|
-
<SideNav.Item value="identities" icon={<Icon name="users" size=
|
|
61
|
+
<SideNav.Item value="identities" icon={<Icon name="users" size="md" />}>
|
|
62
62
|
Identities
|
|
63
63
|
</SideNav.Item>
|
|
64
|
-
<SideNav.Item value="grants" icon={<Icon name="key" size=
|
|
64
|
+
<SideNav.Item value="grants" icon={<Icon name="key" size="md" />}>
|
|
65
65
|
Grants
|
|
66
66
|
</SideNav.Item>
|
|
67
67
|
</SideNav.Section>
|
|
68
68
|
<SideNav.Section label="Audit">
|
|
69
|
-
<SideNav.Item value="audit" icon={<Icon name="file-text" size=
|
|
69
|
+
<SideNav.Item value="audit" icon={<Icon name="file-text" size="md" />}>
|
|
70
70
|
Audit log
|
|
71
71
|
</SideNav.Item>
|
|
72
72
|
</SideNav.Section>
|
|
73
73
|
<SideNav.Group label="Advanced">
|
|
74
|
-
<SideNav.Item value="plugins" icon={<Icon name="plug" size=
|
|
74
|
+
<SideNav.Item value="plugins" icon={<Icon name="plug" size="md" />}>
|
|
75
75
|
Plugins
|
|
76
76
|
</SideNav.Item>
|
|
77
77
|
</SideNav.Group>
|
|
@@ -165,18 +165,18 @@ export const FlatWithIcons: Story = {
|
|
|
165
165
|
render: (args) => (
|
|
166
166
|
<SideNav.Root {...args} defaultValue="grants">
|
|
167
167
|
<SideNav.Section label="People & access">
|
|
168
|
-
<SideNav.Item value="identities" icon={<Icon name="users" size=
|
|
168
|
+
<SideNav.Item value="identities" icon={<Icon name="users" size="md" />}>
|
|
169
169
|
Identities
|
|
170
170
|
</SideNav.Item>
|
|
171
|
-
<SideNav.Item value="grants" icon={<Icon name="key" size=
|
|
171
|
+
<SideNav.Item value="grants" icon={<Icon name="key" size="md" />}>
|
|
172
172
|
Grants
|
|
173
173
|
</SideNav.Item>
|
|
174
174
|
</SideNav.Section>
|
|
175
175
|
<SideNav.Section label="Requests & invites">
|
|
176
|
-
<SideNav.Item value="invitations" icon={<Icon name="mail" size=
|
|
176
|
+
<SideNav.Item value="invitations" icon={<Icon name="mail" size="md" />}>
|
|
177
177
|
Invitations
|
|
178
178
|
</SideNav.Item>
|
|
179
|
-
<SideNav.Item value="invites" icon={<Icon name="user-plus" size=
|
|
179
|
+
<SideNav.Item value="invites" icon={<Icon name="user-plus" size="md" />}>
|
|
180
180
|
User Invites
|
|
181
181
|
</SideNav.Item>
|
|
182
182
|
</SideNav.Section>
|