@duro-app/ui 5.11.0 → 5.12.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-ColfpgKV.js.map +1 -1
- package/dist/compiler-runtime-BuugS4D0.js.map +1 -1
- package/dist/components/Alert/Alert.d.ts.map +1 -1
- package/dist/components/Button/Button.d.ts +2 -1
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/Button/styles.css.d.ts +1 -0
- package/dist/components/Button/styles.css.d.ts.map +1 -1
- package/dist/components/Callout/Callout.d.ts.map +1 -1
- package/dist/components/Callout/styles.css.d.ts +3 -0
- package/dist/components/Callout/styles.css.d.ts.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -1
- package/dist/components/DetailPanel/DetailPanel.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +2 -17
- package/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.stories.d.ts +4 -0
- package/dist/components/Dialog/Dialog.stories.d.ts.map +1 -1
- package/dist/components/Dialog/native.css.d.ts +82 -0
- package/dist/components/Dialog/native.css.d.ts.map +1 -0
- package/dist/components/Dialog/parts.css.d.ts +82 -0
- package/dist/components/Dialog/parts.css.d.ts.map +1 -0
- package/dist/components/Dialog/parts.d.ts +35 -0
- package/dist/components/Dialog/parts.d.ts.map +1 -0
- package/dist/components/Dialog/styles.css.d.ts +1 -74
- package/dist/components/Dialog/styles.css.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/styles.css.d.ts +2 -0
- package/dist/components/Drawer/styles.css.d.ts.map +1 -1
- package/dist/components/Grid/styles.css.d.ts +1 -0
- package/dist/components/Grid/styles.css.d.ts.map +1 -1
- package/dist/components/Icon/Icon.d.ts +3 -9
- package/dist/components/Icon/Icon.d.ts.map +1 -1
- package/dist/components/Icon/geometry.d.ts +60 -0
- package/dist/components/Icon/geometry.d.ts.map +1 -0
- package/dist/components/Icon/types.d.ts +15 -0
- package/dist/components/Icon/types.d.ts.map +1 -0
- package/dist/components/Input/Input.d.ts +5 -1
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/Input/styles.css.d.ts +1 -0
- package/dist/components/Input/styles.css.d.ts.map +1 -1
- package/dist/components/LinkButton/styles.css.d.ts +1 -0
- package/dist/components/LinkButton/styles.css.d.ts.map +1 -1
- package/dist/components/Meter/styles.css.d.ts +1 -0
- package/dist/components/Meter/styles.css.d.ts.map +1 -1
- package/dist/components/PageShell/styles.css.d.ts +2 -0
- package/dist/components/PageShell/styles.css.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +1 -17
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.stories.d.ts +3 -0
- package/dist/components/RadioGroup/RadioGroup.stories.d.ts.map +1 -1
- package/dist/components/RadioGroup/group.d.ts +31 -0
- package/dist/components/RadioGroup/group.d.ts.map +1 -0
- package/dist/components/RadioGroup/styles.css.d.ts +10 -0
- package/dist/components/RadioGroup/styles.css.d.ts.map +1 -1
- package/dist/components/ScrollArea/styles.css.d.ts +1 -0
- package/dist/components/ScrollArea/styles.css.d.ts.map +1 -1
- package/dist/components/Spinner/Spinner.d.ts +2 -6
- package/dist/components/Spinner/Spinner.d.ts.map +1 -1
- package/dist/components/Spinner/Spinner.types.d.ts +6 -0
- package/dist/components/Spinner/Spinner.types.d.ts.map +1 -0
- package/dist/components/Spinner/ring.css.d.ts +39 -0
- package/dist/components/Spinner/ring.css.d.ts.map +1 -0
- package/dist/components/Spinner/styles.css.d.ts +1 -28
- package/dist/components/Spinner/styles.css.d.ts.map +1 -1
- package/dist/components/StatusIcon/StatusIcon.d.ts.map +1 -1
- package/dist/components/StatusIcon/styles.css.d.ts +3 -0
- package/dist/components/StatusIcon/styles.css.d.ts.map +1 -1
- package/dist/components/Switch/Switch.d.ts +2 -18
- package/dist/components/Switch/Switch.d.ts.map +1 -1
- package/dist/components/Switch/Switch.types.d.ts +18 -0
- package/dist/components/Switch/Switch.types.d.ts.map +1 -0
- package/dist/components/TableOfContents/styles.css.d.ts +1 -0
- package/dist/components/TableOfContents/styles.css.d.ts.map +1 -1
- package/dist/components/Textarea/styles.css.d.ts +1 -0
- package/dist/components/Textarea/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 +11 -2
- package/dist/components/Toggle/styles.css.d.ts.map +1 -1
- package/dist/components/VirtualTable/styles.css.d.ts +6 -0
- package/dist/components/VirtualTable/styles.css.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +4299 -3566
- package/dist/index.js.map +1 -1
- package/dist/shared/portal/hostRegistry.d.ts +33 -0
- package/dist/shared/portal/hostRegistry.d.ts.map +1 -0
- package/dist/shared/useOverlayOpenState.d.ts +40 -0
- package/dist/shared/useOverlayOpenState.d.ts.map +1 -0
- package/dist/table.js +141 -135
- package/dist/table.js.map +1 -1
- package/package.json +34 -4
- package/src/components/Alert/Alert.tsx +14 -2
- package/src/components/Button/Button.tsx +3 -2
- package/src/components/Button/styles.css.ts +2 -0
- package/src/components/Callout/Callout.tsx +17 -3
- package/src/components/Callout/styles.css.ts +5 -0
- package/src/components/Checkbox/Checkbox.native.tsx +67 -0
- package/src/components/Checkbox/Checkbox.tsx +1 -50
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +3 -0
- package/src/components/DetailPanel/DetailPanel.tsx +9 -45
- package/src/components/Dialog/Dialog.native.tsx +296 -0
- package/src/components/Dialog/Dialog.stories.tsx +71 -0
- package/src/components/Dialog/Dialog.tsx +41 -130
- package/src/components/Dialog/native.css.ts +78 -0
- package/src/components/Dialog/parts.css.ts +101 -0
- package/src/components/Dialog/parts.tsx +61 -0
- package/src/components/Dialog/styles.css.ts +2 -89
- package/src/components/Drawer/Drawer.tsx +20 -49
- package/src/components/Drawer/styles.css.ts +4 -0
- package/src/components/Grid/styles.css.ts +2 -0
- package/src/components/Icon/Icon.native.tsx +54 -0
- package/src/components/Icon/Icon.tsx +19 -430
- package/src/components/Icon/geometry.ts +456 -0
- package/src/components/Icon/types.ts +74 -0
- package/src/components/Input/Input.tsx +8 -0
- package/src/components/Input/styles.css.ts +2 -0
- package/src/components/LinkButton/styles.css.ts +2 -0
- package/src/components/Meter/styles.css.ts +2 -0
- package/src/components/PageShell/styles.css.ts +4 -0
- package/src/components/RadioGroup/RadioGroup.meta.ts +1 -1
- package/src/components/RadioGroup/RadioGroup.native.tsx +48 -0
- package/src/components/RadioGroup/RadioGroup.stories.tsx +64 -1
- package/src/components/RadioGroup/RadioGroup.tsx +8 -83
- package/src/components/RadioGroup/group.tsx +85 -0
- package/src/components/RadioGroup/styles.css.ts +13 -0
- package/src/components/ScrollArea/styles.css.ts +2 -0
- package/src/components/Spinner/Spinner.native.tsx +37 -0
- package/src/components/Spinner/Spinner.tsx +6 -15
- package/src/components/Spinner/Spinner.types.ts +6 -0
- package/src/components/Spinner/ring.css.ts +45 -0
- package/src/components/Spinner/styles.css.ts +2 -32
- package/src/components/StatusIcon/StatusIcon.tsx +13 -2
- package/src/components/StatusIcon/styles.css.ts +5 -0
- package/src/components/Switch/Switch.native.tsx +45 -0
- package/src/components/Switch/Switch.tsx +22 -44
- package/src/components/Switch/Switch.types.ts +18 -0
- package/src/components/TableOfContents/styles.css.ts +2 -0
- package/src/components/Textarea/styles.css.ts +2 -0
- package/src/components/ThemeProvider/ThemeProvider.native.tsx +70 -10
- package/src/components/Toggle/Toggle.tsx +2 -0
- package/src/components/Toggle/styles.css.ts +14 -2
- package/src/components/VirtualTable/styles.css.ts +12 -0
- package/src/index.native.ts +28 -1
- package/src/native-guard/globals.d.ts +10 -0
- package/src/native-guard/react-dom.d.ts +3 -0
- package/src/shared/accessibilityFocus.native.ts +23 -0
- package/src/shared/portal/OverlayModal.native.tsx +51 -0
- package/src/shared/portal/PortalHost.native.tsx +102 -0
- package/src/shared/portal/hostRegistry.ts +76 -0
- package/src/shared/portal/themeScope.native.ts +18 -0
- package/src/shared/safeArea.native.ts +22 -0
- package/src/shared/useOverlayOpenState.ts +128 -0
- package/dist/components/ThemeProvider/ThemeProvider.native.d.ts +0 -10
- package/dist/components/ThemeProvider/ThemeProvider.native.d.ts.map +0 -1
- package/dist/index.native.d.ts +0 -33
- package/dist/index.native.d.ts.map +0 -1
- package/dist/platform.native.d.ts +0 -2
- package/dist/platform.native.d.ts.map +0 -1
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import {createPortal} from 'react-dom'
|
|
2
|
-
import {
|
|
3
|
-
type ReactNode,
|
|
4
|
-
type RefObject,
|
|
5
|
-
createContext,
|
|
6
|
-
useContext,
|
|
7
|
-
useState,
|
|
8
|
-
useEffect,
|
|
9
|
-
useCallback,
|
|
10
|
-
useId,
|
|
11
|
-
useRef,
|
|
12
|
-
} from 'react'
|
|
2
|
+
import {type ReactNode, type RefObject, useEffect, useCallback, useId, useRef} from 'react'
|
|
13
3
|
import {html} from 'react-strict-dom'
|
|
14
|
-
import
|
|
4
|
+
import type {DurationToken} from '@duro-app/tokens/keys'
|
|
15
5
|
import {styles} from './styles.css'
|
|
6
|
+
import {partStyles} from './parts.css'
|
|
7
|
+
import {
|
|
8
|
+
Body,
|
|
9
|
+
Description,
|
|
10
|
+
DialogContext,
|
|
11
|
+
Footer,
|
|
12
|
+
Header,
|
|
13
|
+
Title,
|
|
14
|
+
useDialog,
|
|
15
|
+
type DialogSize,
|
|
16
|
+
} from './parts'
|
|
16
17
|
import {usePortalMount} from '../ThemeProvider/ThemeProvider'
|
|
17
18
|
import {ControlContextBoundary} from '../Toolbar/ControlContextBoundary'
|
|
18
19
|
import {ModalContext} from '../../shared/ModalContext'
|
|
19
20
|
import {devWarnOnce} from '../../shared/devWarnOnce'
|
|
21
|
+
import {useOverlayOpenState} from '../../shared/useOverlayOpenState'
|
|
20
22
|
|
|
21
23
|
// --- Types ---
|
|
22
24
|
|
|
25
|
+
export type {DialogSize}
|
|
26
|
+
|
|
23
27
|
const FOCUSABLE =
|
|
24
28
|
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
25
29
|
|
|
26
|
-
export type DialogSize = 'sm' | 'md' | 'lg'
|
|
27
|
-
|
|
28
30
|
const closeDurationMap = {
|
|
29
31
|
instant: styles.closeDurationInstant,
|
|
30
32
|
minimal: styles.closeDurationMinimal,
|
|
@@ -35,30 +37,6 @@ const closeDurationMap = {
|
|
|
35
37
|
slow: styles.closeDurationSlow,
|
|
36
38
|
} as const satisfies Record<DurationToken, unknown>
|
|
37
39
|
|
|
38
|
-
// --- Context ---
|
|
39
|
-
|
|
40
|
-
interface DialogContextValue {
|
|
41
|
-
open: boolean
|
|
42
|
-
closing: boolean
|
|
43
|
-
closeDuration: DurationToken
|
|
44
|
-
dismissable: boolean
|
|
45
|
-
requestOpen: () => void
|
|
46
|
-
requestClose: () => void
|
|
47
|
-
titleId: string
|
|
48
|
-
descriptionId: string
|
|
49
|
-
popupRef: RefObject<HTMLDivElement | null>
|
|
50
|
-
/** Dialog.Close parts currently rendered (for the closeOnEscape dev warning). */
|
|
51
|
-
registerClose: () => () => void
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const DialogContext = createContext<DialogContextValue | null>(null)
|
|
55
|
-
|
|
56
|
-
function useDialog() {
|
|
57
|
-
const ctx = useContext(DialogContext)
|
|
58
|
-
if (!ctx) throw new Error('Dialog compound components must be used within Dialog.Root')
|
|
59
|
-
return ctx
|
|
60
|
-
}
|
|
61
|
-
|
|
62
40
|
// --- Root ---
|
|
63
41
|
|
|
64
42
|
interface RootProps {
|
|
@@ -93,55 +71,28 @@ function Root({
|
|
|
93
71
|
initialFocus,
|
|
94
72
|
closeAnimationDuration = 'fast',
|
|
95
73
|
}: RootProps) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
74
|
+
const {
|
|
75
|
+
open: isOpen,
|
|
76
|
+
present,
|
|
77
|
+
closing,
|
|
78
|
+
requestOpen,
|
|
79
|
+
requestClose,
|
|
80
|
+
} = useOverlayOpenState({
|
|
81
|
+
open: controlledOpen,
|
|
82
|
+
defaultOpen,
|
|
83
|
+
onOpenChange,
|
|
84
|
+
exitDuration: closeAnimationDuration,
|
|
85
|
+
closeNotice: 'after-exit',
|
|
86
|
+
})
|
|
100
87
|
const popupRef = useRef<HTMLDivElement>(null)
|
|
101
88
|
|
|
102
|
-
const isOpen = isControlled ? controlledOpen : internalOpen
|
|
103
|
-
|
|
104
89
|
const titleId = useId()
|
|
105
90
|
const descriptionId = useId()
|
|
106
91
|
|
|
107
|
-
const requestOpen = useCallback(() => {
|
|
108
|
-
if (closingTimerRef.current) {
|
|
109
|
-
clearTimeout(closingTimerRef.current)
|
|
110
|
-
closingTimerRef.current = null
|
|
111
|
-
}
|
|
112
|
-
setClosing(false)
|
|
113
|
-
if (!isControlled) setInternalOpen(true)
|
|
114
|
-
onOpenChange?.(true)
|
|
115
|
-
}, [isControlled, onOpenChange])
|
|
116
|
-
|
|
117
|
-
const requestClose = useCallback(() => {
|
|
118
|
-
setClosing(true)
|
|
119
|
-
closingTimerRef.current = setTimeout(() => {
|
|
120
|
-
setClosing(false)
|
|
121
|
-
closingTimerRef.current = null
|
|
122
|
-
if (!isControlled) setInternalOpen(false)
|
|
123
|
-
onOpenChange?.(false)
|
|
124
|
-
}, DURATION_MS[closeAnimationDuration])
|
|
125
|
-
}, [isControlled, onOpenChange, closeAnimationDuration])
|
|
126
|
-
|
|
127
|
-
// If controlled open goes false externally, trigger close animation
|
|
128
|
-
useEffect(() => {
|
|
129
|
-
if (isControlled && !controlledOpen && !closing) {
|
|
130
|
-
// Already closed, nothing to animate
|
|
131
|
-
}
|
|
132
|
-
}, [isControlled, controlledOpen, closing])
|
|
133
|
-
|
|
134
|
-
// Clean up timer on unmount
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
return () => {
|
|
137
|
-
if (closingTimerRef.current) clearTimeout(closingTimerRef.current)
|
|
138
|
-
}
|
|
139
|
-
}, [])
|
|
140
|
-
|
|
141
92
|
// Handle Escape key. A layer inside the dialog (a Menu, a Select) that
|
|
142
93
|
// already handled it marks the event, and the dialog stays open.
|
|
143
94
|
useEffect(() => {
|
|
144
|
-
if (!
|
|
95
|
+
if (!present) return
|
|
145
96
|
if (!closeOnEscape) return
|
|
146
97
|
|
|
147
98
|
function onKeyDown(e: KeyboardEvent) {
|
|
@@ -151,7 +102,7 @@ function Root({
|
|
|
151
102
|
}
|
|
152
103
|
document.addEventListener('keydown', onKeyDown)
|
|
153
104
|
return () => document.removeEventListener('keydown', onKeyDown)
|
|
154
|
-
}, [
|
|
105
|
+
}, [present, closeOnEscape, requestClose])
|
|
155
106
|
|
|
156
107
|
// Focus: into the dialog on open (initialFocus, else the first focusable
|
|
157
108
|
// element, else the dialog itself), back to what had it on close. The
|
|
@@ -180,29 +131,29 @@ function Root({
|
|
|
180
131
|
'Dialog.Root has closeOnEscape={false} and no Dialog.Close: a keyboard user cannot leave this dialog. Render a Dialog.Close, or keep Escape.',
|
|
181
132
|
)
|
|
182
133
|
}
|
|
183
|
-
} else if (!
|
|
134
|
+
} else if (!present && wasOpenRef.current) {
|
|
184
135
|
const target = previousFocusRef.current
|
|
185
136
|
previousFocusRef.current = null
|
|
186
137
|
if (target?.isConnected) target.focus({preventScroll: true})
|
|
187
138
|
}
|
|
188
|
-
wasOpenRef.current =
|
|
189
|
-
}, [isOpen,
|
|
139
|
+
wasOpenRef.current = present
|
|
140
|
+
}, [isOpen, present, initialFocus, closeOnEscape])
|
|
190
141
|
|
|
191
142
|
// Lock body scroll when open
|
|
192
143
|
useEffect(() => {
|
|
193
|
-
if (
|
|
144
|
+
if (present) {
|
|
194
145
|
const prev = document.body.style.overflow
|
|
195
146
|
document.body.style.overflow = 'hidden'
|
|
196
147
|
return () => {
|
|
197
148
|
document.body.style.overflow = prev
|
|
198
149
|
}
|
|
199
150
|
}
|
|
200
|
-
}, [
|
|
151
|
+
}, [present])
|
|
201
152
|
|
|
202
153
|
return (
|
|
203
154
|
<DialogContext.Provider
|
|
204
155
|
value={{
|
|
205
|
-
open:
|
|
156
|
+
open: present,
|
|
206
157
|
closing,
|
|
207
158
|
closeDuration: closeAnimationDuration,
|
|
208
159
|
dismissable,
|
|
@@ -229,7 +180,7 @@ function Trigger({children}: TriggerProps) {
|
|
|
229
180
|
const {requestOpen} = useDialog()
|
|
230
181
|
|
|
231
182
|
return (
|
|
232
|
-
<html.div onClick={requestOpen} style={
|
|
183
|
+
<html.div onClick={requestOpen} style={partStyles.inlineWrapper}>
|
|
233
184
|
{children}
|
|
234
185
|
</html.div>
|
|
235
186
|
)
|
|
@@ -288,7 +239,7 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
288
239
|
aria-describedby={descriptionId}
|
|
289
240
|
style={[
|
|
290
241
|
styles.popup,
|
|
291
|
-
|
|
242
|
+
partStyles[size],
|
|
292
243
|
closing && styles.popupClosing,
|
|
293
244
|
closing && closeDurationMap[closeDuration],
|
|
294
245
|
!closing && styles.popupOpen,
|
|
@@ -310,46 +261,6 @@ function Portal({children, size = 'md'}: PortalProps) {
|
|
|
310
261
|
return mount ? createPortal(node, mount) : node
|
|
311
262
|
}
|
|
312
263
|
|
|
313
|
-
// --- Header ---
|
|
314
|
-
|
|
315
|
-
function Header({children}: {children: ReactNode}) {
|
|
316
|
-
return <html.div style={styles.header}>{children}</html.div>
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// --- Title ---
|
|
320
|
-
|
|
321
|
-
function Title({children}: {children: ReactNode}) {
|
|
322
|
-
const {titleId} = useDialog()
|
|
323
|
-
return (
|
|
324
|
-
<html.h2 id={titleId} style={styles.title}>
|
|
325
|
-
{children}
|
|
326
|
-
</html.h2>
|
|
327
|
-
)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// --- Description ---
|
|
331
|
-
|
|
332
|
-
function Description({children}: {children: ReactNode}) {
|
|
333
|
-
const {descriptionId} = useDialog()
|
|
334
|
-
return (
|
|
335
|
-
<html.p id={descriptionId} style={styles.description}>
|
|
336
|
-
{children}
|
|
337
|
-
</html.p>
|
|
338
|
-
)
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// --- Body ---
|
|
342
|
-
|
|
343
|
-
function Body({children}: {children: ReactNode}) {
|
|
344
|
-
return <html.div style={styles.body}>{children}</html.div>
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// --- Footer ---
|
|
348
|
-
|
|
349
|
-
function Footer({children}: {children: ReactNode}) {
|
|
350
|
-
return <html.div style={styles.footer}>{children}</html.div>
|
|
351
|
-
}
|
|
352
|
-
|
|
353
264
|
// --- Close ---
|
|
354
265
|
|
|
355
266
|
interface CloseProps {
|
|
@@ -363,14 +274,14 @@ function Close({children, 'aria-label': ariaLabel = 'Close'}: CloseProps) {
|
|
|
363
274
|
|
|
364
275
|
if (children) {
|
|
365
276
|
return (
|
|
366
|
-
<html.div onClick={requestClose} style={
|
|
277
|
+
<html.div onClick={requestClose} style={partStyles.inlineWrapper}>
|
|
367
278
|
{children}
|
|
368
279
|
</html.div>
|
|
369
280
|
)
|
|
370
281
|
}
|
|
371
282
|
|
|
372
283
|
return (
|
|
373
|
-
<html.button onClick={requestClose} aria-label={ariaLabel} style={
|
|
284
|
+
<html.button onClick={requestClose} aria-label={ariaLabel} style={partStyles.closeButton}>
|
|
374
285
|
<svg width={16} height={16} viewBox="0 0 16 16" fill="none">
|
|
375
286
|
<path
|
|
376
287
|
d="M4 4l8 8M12 4l-8 8"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {css} from 'react-strict-dom'
|
|
2
|
+
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
3
|
+
import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
|
|
4
|
+
import {shadows} from '@duro-app/tokens/tokens/shadows.css'
|
|
5
|
+
import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
|
|
6
|
+
import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Native Dialog surfaces. RSD has no CSS animations on native, so enter and
|
|
10
|
+
* exit are transitions between a hidden and a shown state (polyfilled with
|
|
11
|
+
* Animated).
|
|
12
|
+
*/
|
|
13
|
+
export const nativeStyles = css.create({
|
|
14
|
+
fill: {
|
|
15
|
+
position: 'absolute',
|
|
16
|
+
top: 0,
|
|
17
|
+
left: 0,
|
|
18
|
+
right: 0,
|
|
19
|
+
bottom: 0,
|
|
20
|
+
},
|
|
21
|
+
backdrop: {
|
|
22
|
+
backgroundColor: colors.scrim,
|
|
23
|
+
transitionProperty: 'opacity',
|
|
24
|
+
transitionDuration: duration.base,
|
|
25
|
+
transitionTimingFunction: easing.easeOut,
|
|
26
|
+
},
|
|
27
|
+
// Padding around the panel inside the RN viewport view.
|
|
28
|
+
inset: {
|
|
29
|
+
display: 'flex',
|
|
30
|
+
padding: spacing.lg,
|
|
31
|
+
width: '100%',
|
|
32
|
+
// Native has no border-box reset (the web's global-reset.css); declare it.
|
|
33
|
+
boxSizing: 'border-box',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
},
|
|
36
|
+
popup: {
|
|
37
|
+
backgroundColor: colors.bgCard,
|
|
38
|
+
borderRadius: radii.lg,
|
|
39
|
+
boxShadow: shadows.lg,
|
|
40
|
+
width: '100%',
|
|
41
|
+
// Native has no border-box reset (the web's global-reset.css); declare it.
|
|
42
|
+
boxSizing: 'border-box',
|
|
43
|
+
display: 'flex',
|
|
44
|
+
flexDirection: 'column',
|
|
45
|
+
overflow: 'hidden',
|
|
46
|
+
borderWidth: borders.hairline,
|
|
47
|
+
borderStyle: 'solid',
|
|
48
|
+
borderColor: colors.border,
|
|
49
|
+
transitionProperty: 'opacity, transform',
|
|
50
|
+
transitionDuration: duration.base,
|
|
51
|
+
transitionTimingFunction: easing.easeOut,
|
|
52
|
+
},
|
|
53
|
+
// The Body's padding inside its ScrollView (no flex: it sizes to content).
|
|
54
|
+
bodyContent: {
|
|
55
|
+
paddingLeft: spacing.lg,
|
|
56
|
+
paddingRight: spacing.lg,
|
|
57
|
+
paddingBottom: spacing.lg,
|
|
58
|
+
},
|
|
59
|
+
// 85% of the window, as the web caps the panel at 85vh: a percentage
|
|
60
|
+
// max-height has no definite parent to resolve against here. The Body
|
|
61
|
+
// (a ScrollView) yields.
|
|
62
|
+
maxHeight: (height: number) => ({maxHeight: height}),
|
|
63
|
+
shown: {
|
|
64
|
+
opacity: 1,
|
|
65
|
+
transform: 'scale(1)',
|
|
66
|
+
},
|
|
67
|
+
hidden: {
|
|
68
|
+
opacity: 0,
|
|
69
|
+
transform: 'scale(0.97)',
|
|
70
|
+
},
|
|
71
|
+
exitInstant: {transitionDuration: duration.instant},
|
|
72
|
+
exitMinimal: {transitionDuration: duration.minimal},
|
|
73
|
+
exitQuick: {transitionDuration: duration.quick},
|
|
74
|
+
exitFast: {transitionDuration: duration.fast},
|
|
75
|
+
exitBrisk: {transitionDuration: duration.brisk},
|
|
76
|
+
exitBase: {transitionDuration: duration.base},
|
|
77
|
+
exitSlow: {transitionDuration: duration.slow},
|
|
78
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {css} from 'react-strict-dom'
|
|
2
|
+
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
3
|
+
import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
|
|
4
|
+
import {typography} from '@duro-app/tokens/tokens/typography.css'
|
|
5
|
+
import {duration} from '@duro-app/tokens/tokens/motion.css'
|
|
6
|
+
import {sizes} from '@duro-app/tokens/tokens/sizes.css'
|
|
7
|
+
import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
8
|
+
|
|
9
|
+
/** Styles of the Dialog parts both platforms draw (no animations: native has none). */
|
|
10
|
+
export const partStyles = css.create({
|
|
11
|
+
// --- Sizes ---
|
|
12
|
+
sm: {maxWidth: sizes.dialogSm},
|
|
13
|
+
md: {maxWidth: sizes.dialogMd},
|
|
14
|
+
lg: {maxWidth: sizes.dialogLg},
|
|
15
|
+
|
|
16
|
+
// --- Header ---
|
|
17
|
+
header: {
|
|
18
|
+
// Header, Description and Footer keep their size; only the Body yields.
|
|
19
|
+
flexShrink: 0,
|
|
20
|
+
display: 'flex',
|
|
21
|
+
alignItems: 'flex-start',
|
|
22
|
+
justifyContent: 'space-between',
|
|
23
|
+
paddingTop: spacing.lg,
|
|
24
|
+
paddingLeft: spacing.lg,
|
|
25
|
+
paddingRight: spacing.lg,
|
|
26
|
+
paddingBottom: spacing.md,
|
|
27
|
+
gap: spacing.md,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// --- Title ---
|
|
31
|
+
title: {
|
|
32
|
+
fontSize: typography.fontSizeLg,
|
|
33
|
+
fontWeight: typography.fontWeightSemibold,
|
|
34
|
+
lineHeight: typography.lineHeight,
|
|
35
|
+
color: colors.text,
|
|
36
|
+
margin: 0,
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// --- Description ---
|
|
40
|
+
description: {
|
|
41
|
+
flexShrink: 0,
|
|
42
|
+
fontSize: typography.fontSizeSm,
|
|
43
|
+
lineHeight: typography.lineHeight,
|
|
44
|
+
color: colors.textMuted,
|
|
45
|
+
margin: 0,
|
|
46
|
+
paddingLeft: spacing.lg,
|
|
47
|
+
paddingRight: spacing.lg,
|
|
48
|
+
paddingBottom: spacing.md,
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// --- Body ---
|
|
52
|
+
body: {
|
|
53
|
+
paddingLeft: spacing.lg,
|
|
54
|
+
paddingRight: spacing.lg,
|
|
55
|
+
paddingBottom: spacing.lg,
|
|
56
|
+
overflowY: 'auto',
|
|
57
|
+
flex: 1,
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// --- Footer ---
|
|
61
|
+
footer: {
|
|
62
|
+
flexShrink: 0,
|
|
63
|
+
display: 'flex',
|
|
64
|
+
alignItems: 'center',
|
|
65
|
+
justifyContent: 'flex-end',
|
|
66
|
+
gap: spacing.sm,
|
|
67
|
+
paddingTop: spacing.md,
|
|
68
|
+
paddingLeft: spacing.lg,
|
|
69
|
+
paddingRight: spacing.lg,
|
|
70
|
+
paddingBottom: spacing.lg,
|
|
71
|
+
borderTopWidth: borders.hairline,
|
|
72
|
+
borderTopStyle: 'solid',
|
|
73
|
+
borderTopColor: colors.border,
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// --- Close button ---
|
|
77
|
+
closeButton: {
|
|
78
|
+
display: 'flex',
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
justifyContent: 'center',
|
|
81
|
+
width: sizes.iconButton,
|
|
82
|
+
height: sizes.iconButton,
|
|
83
|
+
borderRadius: radii.sm,
|
|
84
|
+
borderWidth: 0,
|
|
85
|
+
backgroundColor: {
|
|
86
|
+
default: 'transparent',
|
|
87
|
+
':hover': colors.bgCardHover,
|
|
88
|
+
},
|
|
89
|
+
color: colors.textMuted,
|
|
90
|
+
cursor: 'pointer',
|
|
91
|
+
padding: 0,
|
|
92
|
+
flexShrink: 0,
|
|
93
|
+
transitionProperty: 'background-color',
|
|
94
|
+
transitionDuration: duration.fast,
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// --- Inline wrapper (for Trigger / Close children) ---
|
|
98
|
+
inlineWrapper: {
|
|
99
|
+
display: 'inline-flex',
|
|
100
|
+
},
|
|
101
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {createContext, type ReactNode, type RefObject, useContext} from 'react'
|
|
2
|
+
import {html} from 'react-strict-dom'
|
|
3
|
+
import type {DurationToken} from '@duro-app/tokens/keys'
|
|
4
|
+
import {partStyles} from './parts.css'
|
|
5
|
+
|
|
6
|
+
export type DialogSize = 'sm' | 'md' | 'lg'
|
|
7
|
+
|
|
8
|
+
export interface DialogContextValue {
|
|
9
|
+
/** Rendered: open, or playing the exit animation. */
|
|
10
|
+
open: boolean
|
|
11
|
+
closing: boolean
|
|
12
|
+
closeDuration: DurationToken
|
|
13
|
+
dismissable: boolean
|
|
14
|
+
requestOpen: () => void
|
|
15
|
+
requestClose: () => void
|
|
16
|
+
titleId: string
|
|
17
|
+
descriptionId: string
|
|
18
|
+
popupRef: RefObject<HTMLDivElement | null>
|
|
19
|
+
/** Dialog.Close parts currently rendered (for the closeOnEscape dev warning). */
|
|
20
|
+
registerClose: () => () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DialogContext = createContext<DialogContextValue | null>(null)
|
|
24
|
+
|
|
25
|
+
export function useDialog() {
|
|
26
|
+
const ctx = useContext(DialogContext)
|
|
27
|
+
if (!ctx) throw new Error('Dialog compound components must be used within Dialog.Root')
|
|
28
|
+
return ctx
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The parts below are pure react-strict-dom, drawn the same on both platforms.
|
|
32
|
+
|
|
33
|
+
export function Header({children}: {children: ReactNode}) {
|
|
34
|
+
return <html.div style={partStyles.header}>{children}</html.div>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Title({children}: {children: ReactNode}) {
|
|
38
|
+
const {titleId} = useDialog()
|
|
39
|
+
return (
|
|
40
|
+
<html.h2 id={titleId} style={partStyles.title}>
|
|
41
|
+
{children}
|
|
42
|
+
</html.h2>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function Description({children}: {children: ReactNode}) {
|
|
47
|
+
const {descriptionId} = useDialog()
|
|
48
|
+
return (
|
|
49
|
+
<html.p id={descriptionId} style={partStyles.description}>
|
|
50
|
+
{children}
|
|
51
|
+
</html.p>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function Body({children}: {children: ReactNode}) {
|
|
56
|
+
return <html.div style={partStyles.body}>{children}</html.div>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function Footer({children}: {children: ReactNode}) {
|
|
60
|
+
return <html.div style={partStyles.footer}>{children}</html.div>
|
|
61
|
+
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import {css} from 'react-strict-dom'
|
|
2
2
|
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
3
3
|
import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
|
|
4
|
-
import {typography} from '@duro-app/tokens/tokens/typography.css'
|
|
5
4
|
import {shadows} from '@duro-app/tokens/tokens/shadows.css'
|
|
6
5
|
import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
|
|
7
|
-
import {sizes} from '@duro-app/tokens/tokens/sizes.css'
|
|
8
6
|
import {borders} from '@duro-app/tokens/tokens/borders.css'
|
|
9
7
|
import {layers} from '@duro-app/tokens/tokens/layers.css'
|
|
10
8
|
|
|
@@ -60,6 +58,8 @@ export const styles = css.create({
|
|
|
60
58
|
borderRadius: radii.lg,
|
|
61
59
|
boxShadow: shadows.lg,
|
|
62
60
|
width: '100%',
|
|
61
|
+
// Native has no border-box reset (the web's global-reset.css); declare it.
|
|
62
|
+
boxSizing: 'border-box',
|
|
63
63
|
maxHeight: '85vh',
|
|
64
64
|
display: 'flex',
|
|
65
65
|
flexDirection: 'column',
|
|
@@ -115,93 +115,6 @@ export const styles = css.create({
|
|
|
115
115
|
closeDurationBase: {animationDuration: duration.base},
|
|
116
116
|
closeDurationSlow: {animationDuration: duration.slow},
|
|
117
117
|
|
|
118
|
-
// --- Sizes ---
|
|
119
|
-
sm: {maxWidth: sizes.dialogSm},
|
|
120
|
-
md: {maxWidth: sizes.dialogMd},
|
|
121
|
-
lg: {maxWidth: sizes.dialogLg},
|
|
122
|
-
|
|
123
|
-
// --- Header ---
|
|
124
|
-
header: {
|
|
125
|
-
display: 'flex',
|
|
126
|
-
alignItems: 'flex-start',
|
|
127
|
-
justifyContent: 'space-between',
|
|
128
|
-
paddingTop: spacing.lg,
|
|
129
|
-
paddingLeft: spacing.lg,
|
|
130
|
-
paddingRight: spacing.lg,
|
|
131
|
-
paddingBottom: spacing.md,
|
|
132
|
-
gap: spacing.md,
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
// --- Title ---
|
|
136
|
-
title: {
|
|
137
|
-
fontSize: typography.fontSizeLg,
|
|
138
|
-
fontWeight: typography.fontWeightSemibold,
|
|
139
|
-
lineHeight: typography.lineHeight,
|
|
140
|
-
color: colors.text,
|
|
141
|
-
margin: 0,
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
// --- Description ---
|
|
145
|
-
description: {
|
|
146
|
-
fontSize: typography.fontSizeSm,
|
|
147
|
-
lineHeight: typography.lineHeight,
|
|
148
|
-
color: colors.textMuted,
|
|
149
|
-
margin: 0,
|
|
150
|
-
paddingLeft: spacing.lg,
|
|
151
|
-
paddingRight: spacing.lg,
|
|
152
|
-
paddingBottom: spacing.md,
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
// --- Body ---
|
|
156
|
-
body: {
|
|
157
|
-
paddingLeft: spacing.lg,
|
|
158
|
-
paddingRight: spacing.lg,
|
|
159
|
-
paddingBottom: spacing.lg,
|
|
160
|
-
overflowY: 'auto',
|
|
161
|
-
flex: 1,
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
// --- Footer ---
|
|
165
|
-
footer: {
|
|
166
|
-
display: 'flex',
|
|
167
|
-
alignItems: 'center',
|
|
168
|
-
justifyContent: 'flex-end',
|
|
169
|
-
gap: spacing.sm,
|
|
170
|
-
paddingTop: spacing.md,
|
|
171
|
-
paddingLeft: spacing.lg,
|
|
172
|
-
paddingRight: spacing.lg,
|
|
173
|
-
paddingBottom: spacing.lg,
|
|
174
|
-
borderTopWidth: borders.hairline,
|
|
175
|
-
borderTopStyle: 'solid',
|
|
176
|
-
borderTopColor: colors.border,
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
// --- Close button ---
|
|
180
|
-
closeButton: {
|
|
181
|
-
display: 'flex',
|
|
182
|
-
alignItems: 'center',
|
|
183
|
-
justifyContent: 'center',
|
|
184
|
-
width: sizes.iconButton,
|
|
185
|
-
height: sizes.iconButton,
|
|
186
|
-
borderRadius: radii.sm,
|
|
187
|
-
borderWidth: 0,
|
|
188
|
-
backgroundColor: {
|
|
189
|
-
default: 'transparent',
|
|
190
|
-
':hover': colors.bgCardHover,
|
|
191
|
-
},
|
|
192
|
-
color: colors.textMuted,
|
|
193
|
-
cursor: 'pointer',
|
|
194
|
-
padding: 0,
|
|
195
|
-
flexShrink: 0,
|
|
196
|
-
transitionProperty: 'background-color',
|
|
197
|
-
transitionDuration: duration.fast,
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
// --- Inline wrapper (for Trigger / Close children) ---
|
|
201
|
-
inlineWrapper: {
|
|
202
|
-
display: 'inline-flex',
|
|
203
|
-
},
|
|
204
|
-
|
|
205
118
|
// --- Reduced motion ---
|
|
206
119
|
reducedMotion: {
|
|
207
120
|
animationDuration: duration.minimal,
|