@duro-app/ui 0.18.0 → 0.20.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.
Files changed (91) hide show
  1. package/dist/components/Checkbox/Checkbox.meta.d.ts.map +1 -1
  2. package/dist/components/Cluster/Cluster.meta.d.ts.map +1 -1
  3. package/dist/components/DetailPanel/DetailPanel.d.ts +53 -0
  4. package/dist/components/DetailPanel/DetailPanel.d.ts.map +1 -0
  5. package/dist/components/DetailPanel/DetailPanel.meta.d.ts +3 -0
  6. package/dist/components/DetailPanel/DetailPanel.meta.d.ts.map +1 -0
  7. package/dist/components/DetailPanel/DetailPanel.stories.d.ts +9 -0
  8. package/dist/components/DetailPanel/DetailPanel.stories.d.ts.map +1 -0
  9. package/dist/components/DetailPanel/styles.css.d.ts +126 -0
  10. package/dist/components/DetailPanel/styles.css.d.ts.map +1 -0
  11. package/dist/components/Dialog/Dialog.d.ts +58 -0
  12. package/dist/components/Dialog/Dialog.d.ts.map +1 -0
  13. package/dist/components/Dialog/Dialog.meta.d.ts +3 -0
  14. package/dist/components/Dialog/Dialog.meta.d.ts.map +1 -0
  15. package/dist/components/Dialog/Dialog.stories.d.ts +13 -0
  16. package/dist/components/Dialog/Dialog.stories.d.ts.map +1 -0
  17. package/dist/components/Dialog/styles.css.d.ts +140 -0
  18. package/dist/components/Dialog/styles.css.d.ts.map +1 -0
  19. package/dist/components/Drawer/Drawer.d.ts +62 -0
  20. package/dist/components/Drawer/Drawer.d.ts.map +1 -0
  21. package/dist/components/Drawer/Drawer.meta.d.ts +3 -0
  22. package/dist/components/Drawer/Drawer.meta.d.ts.map +1 -0
  23. package/dist/components/Drawer/Drawer.stories.d.ts +15 -0
  24. package/dist/components/Drawer/Drawer.stories.d.ts.map +1 -0
  25. package/dist/components/Drawer/styles.css.d.ts +184 -0
  26. package/dist/components/Drawer/styles.css.d.ts.map +1 -0
  27. package/dist/components/Drawer/useSwipeDismiss.d.ts +17 -0
  28. package/dist/components/Drawer/useSwipeDismiss.d.ts.map +1 -0
  29. package/dist/components/Field/Field.meta.d.ts.map +1 -1
  30. package/dist/components/Fieldset/Fieldset.meta.d.ts.map +1 -1
  31. package/dist/components/Form/Form.meta.d.ts.map +1 -1
  32. package/dist/components/Grid/Grid.meta.d.ts.map +1 -1
  33. package/dist/components/Inline/Inline.meta.d.ts.map +1 -1
  34. package/dist/components/Input/Input.meta.d.ts.map +1 -1
  35. package/dist/components/LinkButton/LinkButton.meta.d.ts.map +1 -1
  36. package/dist/components/Menu/Menu.meta.d.ts.map +1 -1
  37. package/dist/components/Panel/Panel.d.ts +27 -0
  38. package/dist/components/Panel/Panel.d.ts.map +1 -0
  39. package/dist/components/Panel/Panel.meta.d.ts +3 -0
  40. package/dist/components/Panel/Panel.meta.d.ts.map +1 -0
  41. package/dist/components/Panel/Panel.stories.d.ts +10 -0
  42. package/dist/components/Panel/Panel.stories.d.ts.map +1 -0
  43. package/dist/components/Panel/styles.css.d.ts +50 -0
  44. package/dist/components/Panel/styles.css.d.ts.map +1 -0
  45. package/dist/components/SideNav/SideNav.meta.d.ts.map +1 -1
  46. package/dist/components/Stack/Stack.meta.d.ts.map +1 -1
  47. package/dist/components/Switch/Switch.meta.d.ts.map +1 -1
  48. package/dist/components/Table/Table.d.ts.map +1 -1
  49. package/dist/components/Table/Table.stories.d.ts +1 -0
  50. package/dist/components/Table/Table.stories.d.ts.map +1 -1
  51. package/dist/components/Textarea/Textarea.meta.d.ts.map +1 -1
  52. package/dist/index.css +1 -1
  53. package/dist/index.d.ts +4 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +3286 -2007
  56. package/dist/index.js.map +1 -1
  57. package/package.json +2 -2
  58. package/src/components/Checkbox/Checkbox.meta.ts +4 -1
  59. package/src/components/Cluster/Cluster.meta.ts +2 -6
  60. package/src/components/DetailPanel/DetailPanel.meta.ts +41 -0
  61. package/src/components/DetailPanel/DetailPanel.stories.tsx +270 -0
  62. package/src/components/DetailPanel/DetailPanel.tsx +312 -0
  63. package/src/components/DetailPanel/styles.css.ts +187 -0
  64. package/src/components/Dialog/Dialog.meta.ts +44 -0
  65. package/src/components/Dialog/Dialog.stories.tsx +325 -0
  66. package/src/components/Dialog/Dialog.tsx +310 -0
  67. package/src/components/Dialog/styles.css.ts +197 -0
  68. package/src/components/Drawer/Drawer.meta.ts +45 -0
  69. package/src/components/Drawer/Drawer.stories.tsx +364 -0
  70. package/src/components/Drawer/Drawer.tsx +370 -0
  71. package/src/components/Drawer/styles.css.ts +242 -0
  72. package/src/components/Drawer/useSwipeDismiss.ts +295 -0
  73. package/src/components/Field/Field.meta.ts +1 -3
  74. package/src/components/Fieldset/Fieldset.meta.ts +1 -3
  75. package/src/components/Form/Form.meta.ts +1 -4
  76. package/src/components/Grid/Grid.meta.ts +1 -3
  77. package/src/components/Inline/Inline.meta.ts +1 -2
  78. package/src/components/Input/Input.meta.ts +2 -7
  79. package/src/components/LinkButton/LinkButton.meta.ts +1 -3
  80. package/src/components/Menu/Menu.meta.ts +1 -4
  81. package/src/components/Panel/Panel.meta.ts +37 -0
  82. package/src/components/Panel/Panel.stories.tsx +124 -0
  83. package/src/components/Panel/Panel.tsx +56 -0
  84. package/src/components/Panel/styles.css.ts +55 -0
  85. package/src/components/SideNav/SideNav.meta.ts +1 -4
  86. package/src/components/Stack/Stack.meta.ts +1 -2
  87. package/src/components/Switch/Switch.meta.ts +4 -1
  88. package/src/components/Table/Table.stories.tsx +107 -0
  89. package/src/components/Table/Table.tsx +0 -1
  90. package/src/components/Textarea/Textarea.meta.ts +2 -6
  91. package/src/index.ts +4 -0
@@ -0,0 +1,310 @@
1
+ import {
2
+ type ReactNode,
3
+ type RefObject,
4
+ createContext,
5
+ useContext,
6
+ useState,
7
+ useEffect,
8
+ useCallback,
9
+ useId,
10
+ useRef,
11
+ } from 'react'
12
+ import {html} from 'react-strict-dom'
13
+ import {styles} from './styles.css'
14
+
15
+ // --- Types ---
16
+
17
+ export type DialogSize = 'sm' | 'md' | 'lg'
18
+
19
+ // --- Context ---
20
+
21
+ interface DialogContextValue {
22
+ open: boolean
23
+ closing: boolean
24
+ dismissable: boolean
25
+ requestOpen: () => void
26
+ requestClose: () => void
27
+ titleId: string
28
+ descriptionId: string
29
+ popupRef: RefObject<HTMLDivElement | null>
30
+ }
31
+
32
+ const DialogContext = createContext<DialogContextValue | null>(null)
33
+
34
+ function useDialog() {
35
+ const ctx = useContext(DialogContext)
36
+ if (!ctx) throw new Error('Dialog compound components must be used within Dialog.Root')
37
+ return ctx
38
+ }
39
+
40
+ // --- Root ---
41
+
42
+ interface RootProps {
43
+ children: ReactNode
44
+ /** Controlled open state */
45
+ open?: boolean
46
+ /** Default open state (uncontrolled) */
47
+ defaultOpen?: boolean
48
+ /** Called when dialog open state changes */
49
+ onOpenChange?: (open: boolean) => void
50
+ /** Whether clicking backdrop closes the dialog. Default: true */
51
+ dismissable?: boolean
52
+ /** Duration of the close animation in ms. Default: 140 */
53
+ closeAnimationDuration?: number
54
+ }
55
+
56
+ function Root({
57
+ children,
58
+ open: controlledOpen,
59
+ defaultOpen = false,
60
+ onOpenChange,
61
+ dismissable = true,
62
+ closeAnimationDuration = 140,
63
+ }: RootProps) {
64
+ const isControlled = controlledOpen !== undefined
65
+ const [internalOpen, setInternalOpen] = useState(defaultOpen)
66
+ const [closing, setClosing] = useState(false)
67
+ const closingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
68
+ const popupRef = useRef<HTMLDivElement>(null)
69
+
70
+ const isOpen = isControlled ? controlledOpen : internalOpen
71
+
72
+ const titleId = useId()
73
+ const descriptionId = useId()
74
+
75
+ const requestOpen = useCallback(() => {
76
+ if (closingTimerRef.current) {
77
+ clearTimeout(closingTimerRef.current)
78
+ closingTimerRef.current = null
79
+ }
80
+ setClosing(false)
81
+ if (!isControlled) setInternalOpen(true)
82
+ onOpenChange?.(true)
83
+ }, [isControlled, onOpenChange])
84
+
85
+ const requestClose = useCallback(() => {
86
+ setClosing(true)
87
+ closingTimerRef.current = setTimeout(() => {
88
+ setClosing(false)
89
+ closingTimerRef.current = null
90
+ if (!isControlled) setInternalOpen(false)
91
+ onOpenChange?.(false)
92
+ }, closeAnimationDuration)
93
+ }, [isControlled, onOpenChange, closeAnimationDuration])
94
+
95
+ // If controlled open goes false externally, trigger close animation
96
+ useEffect(() => {
97
+ if (isControlled && !controlledOpen && !closing) {
98
+ // Already closed, nothing to animate
99
+ }
100
+ }, [isControlled, controlledOpen, closing])
101
+
102
+ // Clean up timer on unmount
103
+ useEffect(() => {
104
+ return () => {
105
+ if (closingTimerRef.current) clearTimeout(closingTimerRef.current)
106
+ }
107
+ }, [])
108
+
109
+ // Handle Escape key
110
+ useEffect(() => {
111
+ if (!isOpen && !closing) return
112
+ if (!dismissable) return
113
+
114
+ function onKeyDown(e: KeyboardEvent) {
115
+ if (e.key === 'Escape') {
116
+ e.stopPropagation()
117
+ requestClose()
118
+ }
119
+ }
120
+ document.addEventListener('keydown', onKeyDown)
121
+ return () => document.removeEventListener('keydown', onKeyDown)
122
+ }, [isOpen, closing, dismissable, requestClose])
123
+
124
+ // Lock body scroll when open
125
+ useEffect(() => {
126
+ if (isOpen || closing) {
127
+ const prev = document.body.style.overflow
128
+ document.body.style.overflow = 'hidden'
129
+ return () => {
130
+ document.body.style.overflow = prev
131
+ }
132
+ }
133
+ }, [isOpen, closing])
134
+
135
+ return (
136
+ <DialogContext.Provider
137
+ value={{
138
+ open: isOpen || closing,
139
+ closing,
140
+ dismissable,
141
+ requestOpen,
142
+ requestClose,
143
+ titleId,
144
+ descriptionId,
145
+ popupRef,
146
+ }}
147
+ >
148
+ {children}
149
+ </DialogContext.Provider>
150
+ )
151
+ }
152
+
153
+ // --- Trigger ---
154
+
155
+ interface TriggerProps {
156
+ children: ReactNode
157
+ }
158
+
159
+ function Trigger({children}: TriggerProps) {
160
+ const {requestOpen} = useDialog()
161
+
162
+ return (
163
+ <html.div onClick={requestOpen} style={styles.inlineWrapper}>
164
+ {children}
165
+ </html.div>
166
+ )
167
+ }
168
+
169
+ // --- Portal (renders backdrop + viewport + popup) ---
170
+
171
+ interface PortalProps {
172
+ children: ReactNode
173
+ size?: DialogSize
174
+ }
175
+
176
+ function Portal({children, size = 'md'}: PortalProps) {
177
+ const {open, closing, dismissable, requestClose, titleId, descriptionId, popupRef} = useDialog()
178
+
179
+ const handleBackdropClick = useCallback(() => {
180
+ if (dismissable) {
181
+ requestClose()
182
+ }
183
+ }, [dismissable, requestClose])
184
+
185
+ if (!open) return null
186
+
187
+ return (
188
+ <>
189
+ {/* Backdrop — click to dismiss */}
190
+ <html.div
191
+ style={[
192
+ styles.backdrop,
193
+ closing && styles.backdropClosing,
194
+ !closing && styles.backdropOpen,
195
+ ]}
196
+ aria-hidden
197
+ onClick={handleBackdropClick}
198
+ />
199
+
200
+ {/* Viewport (centering container, pointer-events: none) */}
201
+ <html.div style={styles.viewport}>
202
+ <html.div
203
+ ref={popupRef}
204
+ role="dialog"
205
+ aria-modal="true"
206
+ aria-labelledby={titleId}
207
+ aria-describedby={descriptionId}
208
+ style={[
209
+ styles.popup,
210
+ styles[size],
211
+ closing && styles.popupClosing,
212
+ !closing && styles.popupOpen,
213
+ ]}
214
+ >
215
+ {children}
216
+ </html.div>
217
+ </html.div>
218
+ </>
219
+ )
220
+ }
221
+
222
+ // --- Header ---
223
+
224
+ function Header({children}: {children: ReactNode}) {
225
+ return <html.div style={styles.header}>{children}</html.div>
226
+ }
227
+
228
+ // --- Title ---
229
+
230
+ function Title({children}: {children: ReactNode}) {
231
+ const {titleId} = useDialog()
232
+ return (
233
+ <html.h2 id={titleId} style={styles.title}>
234
+ {children}
235
+ </html.h2>
236
+ )
237
+ }
238
+
239
+ // --- Description ---
240
+
241
+ function Description({children}: {children: ReactNode}) {
242
+ const {descriptionId} = useDialog()
243
+ return (
244
+ <html.p id={descriptionId} style={styles.description}>
245
+ {children}
246
+ </html.p>
247
+ )
248
+ }
249
+
250
+ // --- Body ---
251
+
252
+ function Body({children}: {children: ReactNode}) {
253
+ return <html.div style={styles.body}>{children}</html.div>
254
+ }
255
+
256
+ // --- Footer ---
257
+
258
+ function Footer({children}: {children: ReactNode}) {
259
+ return <html.div style={styles.footer}>{children}</html.div>
260
+ }
261
+
262
+ // --- Close ---
263
+
264
+ interface CloseProps {
265
+ children?: ReactNode
266
+ 'aria-label'?: string
267
+ }
268
+
269
+ function Close({children, 'aria-label': ariaLabel = 'Close'}: CloseProps) {
270
+ const {requestClose} = useDialog()
271
+
272
+ if (children) {
273
+ return (
274
+ <html.div onClick={requestClose} style={styles.inlineWrapper}>
275
+ {children}
276
+ </html.div>
277
+ )
278
+ }
279
+
280
+ return (
281
+ <html.button
282
+ onClick={requestClose}
283
+ aria-label={ariaLabel}
284
+ style={styles.closeButton}
285
+ >
286
+ <svg width={16} height={16} viewBox="0 0 16 16" fill="none">
287
+ <path
288
+ d="M4 4l8 8M12 4l-8 8"
289
+ stroke="currentColor"
290
+ strokeWidth={1.5}
291
+ strokeLinecap="round"
292
+ />
293
+ </svg>
294
+ </html.button>
295
+ )
296
+ }
297
+
298
+ // --- Export ---
299
+
300
+ export const Dialog = {
301
+ Root,
302
+ Trigger,
303
+ Portal,
304
+ Header,
305
+ Title,
306
+ Description,
307
+ Body,
308
+ Footer,
309
+ Close,
310
+ }
@@ -0,0 +1,197 @@
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 {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
+
7
+ export const styles = css.create({
8
+ // --- Backdrop ---
9
+ backdrop: {
10
+ position: 'fixed',
11
+ inset: 0,
12
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
13
+ zIndex: 1000,
14
+ },
15
+ backdropOpen: {
16
+ opacity: {
17
+ default: 1,
18
+ },
19
+ animationName: css.keyframes({
20
+ from: {opacity: 0},
21
+ to: {opacity: 1},
22
+ }),
23
+ animationDuration: '180ms',
24
+ animationTimingFunction: 'ease-out',
25
+ animationFillMode: 'both',
26
+ },
27
+ backdropClosing: {
28
+ opacity: {
29
+ default: 0,
30
+ },
31
+ animationName: css.keyframes({
32
+ from: {opacity: 1},
33
+ to: {opacity: 0},
34
+ }),
35
+ animationDuration: '140ms',
36
+ animationTimingFunction: 'ease-in',
37
+ animationFillMode: 'both',
38
+ },
39
+
40
+ // --- Viewport (centering container) ---
41
+ viewport: {
42
+ position: 'fixed',
43
+ inset: 0,
44
+ zIndex: 1001,
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ padding: spacing.lg,
49
+ // Allow clicking backdrop through viewport when clicking outside popup
50
+ pointerEvents: 'none',
51
+ },
52
+
53
+ // --- Popup ---
54
+ popup: {
55
+ position: 'relative',
56
+ backgroundColor: colors.bgCard,
57
+ borderRadius: radii.lg,
58
+ boxShadow: shadows.lg,
59
+ width: '100%',
60
+ maxHeight: '85vh',
61
+ display: 'flex',
62
+ flexDirection: 'column',
63
+ overflow: 'hidden',
64
+ pointerEvents: 'auto',
65
+ borderWidth: 1,
66
+ borderStyle: 'solid',
67
+ borderColor: colors.border,
68
+ },
69
+ popupOpen: {
70
+ opacity: {
71
+ default: 1,
72
+ },
73
+ animationName: css.keyframes({
74
+ from: {
75
+ opacity: 0,
76
+ transform: 'scale(0.97) translateY(8px)',
77
+ },
78
+ to: {
79
+ opacity: 1,
80
+ transform: 'scale(1) translateY(0)',
81
+ },
82
+ }),
83
+ animationDuration: '180ms',
84
+ animationTimingFunction: 'ease-out',
85
+ animationFillMode: 'both',
86
+ },
87
+ popupClosing: {
88
+ opacity: {
89
+ default: 0,
90
+ },
91
+ animationName: css.keyframes({
92
+ from: {
93
+ opacity: 1,
94
+ transform: 'scale(1) translateY(0)',
95
+ },
96
+ to: {
97
+ opacity: 0,
98
+ transform: 'scale(0.97) translateY(8px)',
99
+ },
100
+ }),
101
+ animationDuration: '140ms',
102
+ animationTimingFunction: 'ease-in',
103
+ animationFillMode: 'both',
104
+ },
105
+
106
+ // --- Sizes ---
107
+ sm: {maxWidth: 400},
108
+ md: {maxWidth: 520},
109
+ lg: {maxWidth: 680},
110
+
111
+ // --- Header ---
112
+ header: {
113
+ display: 'flex',
114
+ alignItems: 'flex-start',
115
+ justifyContent: 'space-between',
116
+ paddingTop: spacing.lg,
117
+ paddingLeft: spacing.lg,
118
+ paddingRight: spacing.lg,
119
+ paddingBottom: spacing.md,
120
+ gap: spacing.md,
121
+ },
122
+
123
+ // --- Title ---
124
+ title: {
125
+ fontSize: typography.fontSizeLg,
126
+ fontWeight: typography.fontWeightSemibold,
127
+ lineHeight: typography.lineHeight,
128
+ color: colors.text,
129
+ margin: 0,
130
+ },
131
+
132
+ // --- Description ---
133
+ description: {
134
+ fontSize: typography.fontSizeSm,
135
+ lineHeight: typography.lineHeight,
136
+ color: colors.textMuted,
137
+ margin: 0,
138
+ paddingLeft: spacing.lg,
139
+ paddingRight: spacing.lg,
140
+ paddingBottom: spacing.md,
141
+ },
142
+
143
+ // --- Body ---
144
+ body: {
145
+ paddingLeft: spacing.lg,
146
+ paddingRight: spacing.lg,
147
+ paddingBottom: spacing.lg,
148
+ overflowY: 'auto',
149
+ flex: 1,
150
+ },
151
+
152
+ // --- Footer ---
153
+ footer: {
154
+ display: 'flex',
155
+ alignItems: 'center',
156
+ justifyContent: 'flex-end',
157
+ gap: spacing.sm,
158
+ paddingTop: spacing.md,
159
+ paddingLeft: spacing.lg,
160
+ paddingRight: spacing.lg,
161
+ paddingBottom: spacing.lg,
162
+ borderTopWidth: 1,
163
+ borderTopStyle: 'solid',
164
+ borderTopColor: colors.border,
165
+ },
166
+
167
+ // --- Close button ---
168
+ closeButton: {
169
+ display: 'flex',
170
+ alignItems: 'center',
171
+ justifyContent: 'center',
172
+ width: 32,
173
+ height: 32,
174
+ borderRadius: radii.sm,
175
+ borderWidth: 0,
176
+ backgroundColor: {
177
+ default: 'transparent',
178
+ ':hover': colors.bgCardHover,
179
+ },
180
+ color: colors.textMuted,
181
+ cursor: 'pointer',
182
+ padding: 0,
183
+ flexShrink: 0,
184
+ transitionProperty: 'background-color',
185
+ transitionDuration: '150ms',
186
+ },
187
+
188
+ // --- Inline wrapper (for Trigger / Close children) ---
189
+ inlineWrapper: {
190
+ display: 'inline-flex',
191
+ },
192
+
193
+ // --- Reduced motion ---
194
+ reducedMotion: {
195
+ animationDuration: '1ms',
196
+ },
197
+ })
@@ -0,0 +1,45 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'Modal sliding panel from a screen edge (right, left, or bottom). Slides in with backdrop overlay. Supports swipe-to-dismiss gestures on mobile, backdrop click dismiss, and Escape key. Use for secondary workflows, settings panels, or mobile-friendly detail views.',
6
+ whenToUse: [
7
+ 'Settings or configuration panels that slide in from the side',
8
+ 'Mobile-friendly detail views (bottom sheet pattern)',
9
+ 'Secondary workflows that need more space than a dialog',
10
+ 'Filters, navigation drawers, or form panels',
11
+ ],
12
+ whenNotToUse: [
13
+ 'Centered modal confirmations — use Dialog',
14
+ 'Non-modal side inspection panels — use DetailPanel',
15
+ 'Inline content expansion — use disclosure patterns',
16
+ 'Primary navigation — use SideNav',
17
+ ],
18
+ anatomy: {
19
+ required: ['Root', 'Portal'],
20
+ optional: ['Trigger', 'Header', 'Title', 'Description', 'Body', 'Footer', 'Close'],
21
+ },
22
+ relatedTo: [
23
+ {component: 'Dialog', relationship: 'Dialog is centered modal; Drawer slides from an edge'},
24
+ {component: 'DetailPanel', relationship: 'DetailPanel is non-modal and in-flow; Drawer is modal with backdrop'},
25
+ ],
26
+ example: `<Drawer.Root anchor="right" open={open} onOpenChange={setOpen}>
27
+ <Drawer.Trigger>
28
+ <Button>Open drawer</Button>
29
+ </Drawer.Trigger>
30
+ <Drawer.Portal size="md">
31
+ <Drawer.Header>
32
+ <Drawer.Title>Settings</Drawer.Title>
33
+ <Drawer.Close />
34
+ </Drawer.Header>
35
+ <Drawer.Body>
36
+ <Stack gap="md">
37
+ <Text>Drawer content goes here.</Text>
38
+ </Stack>
39
+ </Drawer.Body>
40
+ <Drawer.Footer>
41
+ <Button variant="secondary" onClick={() => setOpen(false)}>Done</Button>
42
+ </Drawer.Footer>
43
+ </Drawer.Portal>
44
+ </Drawer.Root>`,
45
+ }