@tempoxyz/ds 0.3.9 → 0.3.11

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 (128) hide show
  1. package/dist/docs/AGENTS.md +9 -0
  2. package/dist/docs/components/accordion.md +489 -0
  3. package/dist/docs/components/address-card.md +147 -0
  4. package/dist/docs/components/agent-logo.md +194 -0
  5. package/dist/docs/components/alert-dialog.md +631 -0
  6. package/dist/docs/components/alert.md +448 -0
  7. package/dist/docs/components/amount-input.md +305 -0
  8. package/dist/docs/components/amount.md +794 -0
  9. package/dist/docs/components/app-shell.md +559 -0
  10. package/dist/docs/components/asset-chip.md +294 -0
  11. package/dist/docs/components/autocomplete.md +619 -0
  12. package/dist/docs/components/avatar.md +321 -0
  13. package/dist/docs/components/badge.md +339 -0
  14. package/dist/docs/components/badged-logo.md +116 -0
  15. package/dist/docs/components/banner.md +379 -0
  16. package/dist/docs/components/bar-chart.md +208 -0
  17. package/dist/docs/components/breadcrumbs.md +380 -0
  18. package/dist/docs/components/button-group.md +286 -0
  19. package/dist/docs/components/button.md +650 -0
  20. package/dist/docs/components/calendar.md +375 -0
  21. package/dist/docs/components/callout.md +96 -0
  22. package/dist/docs/components/checkbox-group.md +521 -0
  23. package/dist/docs/components/checkbox.md +467 -0
  24. package/dist/docs/components/choice-group.md +516 -0
  25. package/dist/docs/components/choicebox.md +641 -0
  26. package/dist/docs/components/code.md +100 -0
  27. package/dist/docs/components/combobox.md +939 -0
  28. package/dist/docs/components/container.md +257 -0
  29. package/dist/docs/components/copy-button.md +642 -0
  30. package/dist/docs/components/date-picker.md +514 -0
  31. package/dist/docs/components/dialog.md +1332 -0
  32. package/dist/docs/components/drawer.md +966 -0
  33. package/dist/docs/components/empty.md +340 -0
  34. package/dist/docs/components/field.md +570 -0
  35. package/dist/docs/components/fieldset.md +398 -0
  36. package/dist/docs/components/frame.md +434 -0
  37. package/dist/docs/components/gauge.md +299 -0
  38. package/dist/docs/components/get-started-visual.md +117 -0
  39. package/dist/docs/components/grid.md +288 -0
  40. package/dist/docs/components/hero-visual.md +155 -0
  41. package/dist/docs/components/hex.md +487 -0
  42. package/dist/docs/components/icon-tile.md +292 -0
  43. package/dist/docs/components/identicon.md +371 -0
  44. package/dist/docs/components/inline.md +233 -0
  45. package/dist/docs/components/inset.md +225 -0
  46. package/dist/docs/components/kbd.md +423 -0
  47. package/dist/docs/components/list-group.md +981 -0
  48. package/dist/docs/components/loading-dots.md +237 -0
  49. package/dist/docs/components/menu.md +934 -0
  50. package/dist/docs/components/native-select.md +204 -0
  51. package/dist/docs/components/network-logo.md +248 -0
  52. package/dist/docs/components/number-field.md +543 -0
  53. package/dist/docs/components/otp-field.md +616 -0
  54. package/dist/docs/components/pagination.md +516 -0
  55. package/dist/docs/components/popover.md +544 -0
  56. package/dist/docs/components/progress.md +388 -0
  57. package/dist/docs/components/qr.md +314 -0
  58. package/dist/docs/components/radio-group.md +528 -0
  59. package/dist/docs/components/secret-value.md +406 -0
  60. package/dist/docs/components/select.md +751 -0
  61. package/dist/docs/components/separator.md +379 -0
  62. package/dist/docs/components/sidebar-menu.md +922 -0
  63. package/dist/docs/components/skeleton.md +517 -0
  64. package/dist/docs/components/slider.md +539 -0
  65. package/dist/docs/components/snippet.md +424 -0
  66. package/dist/docs/components/spinner.md +216 -0
  67. package/dist/docs/components/split-bar.md +139 -0
  68. package/dist/docs/components/split-button.md +464 -0
  69. package/dist/docs/components/stack.md +299 -0
  70. package/dist/docs/components/status-chip.md +226 -0
  71. package/dist/docs/components/status-indicator.md +119 -0
  72. package/dist/docs/components/status-row.md +174 -0
  73. package/dist/docs/components/stepper.md +451 -0
  74. package/dist/docs/components/success-hero.md +157 -0
  75. package/dist/docs/components/surface.md +639 -0
  76. package/dist/docs/components/switch.md +472 -0
  77. package/dist/docs/components/table.md +967 -0
  78. package/dist/docs/components/tabs.md +627 -0
  79. package/dist/docs/components/tempo-logo.md +185 -0
  80. package/dist/docs/components/text-input.md +624 -0
  81. package/dist/docs/components/textarea.md +352 -0
  82. package/dist/docs/components/toast.md +535 -0
  83. package/dist/docs/components/token-logo.md +371 -0
  84. package/dist/docs/components/token.md +247 -0
  85. package/dist/docs/components/tooltip.md +490 -0
  86. package/dist/docs/components/verification-code.md +116 -0
  87. package/dist/docs/components/visual.md +249 -0
  88. package/dist/docs/components.md +203 -0
  89. package/dist/docs/foundations/accessibility.md +377 -0
  90. package/dist/docs/foundations/agents.md +150 -0
  91. package/dist/docs/foundations/color.md +522 -0
  92. package/dist/docs/foundations/content/tone.md +194 -0
  93. package/dist/docs/foundations/content/voice.md +194 -0
  94. package/dist/docs/foundations/content/writing-style.md +406 -0
  95. package/dist/docs/foundations/iconography.md +282 -0
  96. package/dist/docs/foundations/intent.md +360 -0
  97. package/dist/docs/foundations/layout.md +508 -0
  98. package/dist/docs/foundations/linter.md +296 -0
  99. package/dist/docs/foundations/motion.md +151 -0
  100. package/dist/docs/foundations/radius-and-padding.md +86 -0
  101. package/dist/docs/foundations/typography.md +266 -0
  102. package/dist/docs/index.md +137 -0
  103. package/dist/docs/introduction/faq.md +132 -0
  104. package/dist/docs/introduction/getting-started.md +377 -0
  105. package/dist/docs/patterns/blockchain-values.md +709 -0
  106. package/dist/docs/patterns/dashboards.md +638 -0
  107. package/dist/docs/patterns/destructive-actions.md +626 -0
  108. package/dist/docs/patterns/empty-states.md +612 -0
  109. package/dist/docs/patterns/errors-and-validation.md +808 -0
  110. package/dist/docs/patterns/feedback-and-status.md +518 -0
  111. package/dist/docs/patterns/forms.md +679 -0
  112. package/dist/docs/patterns/loading.md +630 -0
  113. package/dist/docs/patterns/navigation.md +397 -0
  114. package/dist/docs/patterns/payments.md +765 -0
  115. package/dist/docs/patterns/responsive-layout.md +997 -0
  116. package/dist/docs/patterns/tables-and-lists.md +964 -0
  117. package/dist/docs/products/tempo-console.md +671 -0
  118. package/dist/docs/products/tempo-performance.md +437 -0
  119. package/dist/docs/products/tempo-wallet.md +438 -0
  120. package/dist/web/components/OtpField.css +1 -1
  121. package/dist/web/components/OtpField.d.ts +1 -1
  122. package/dist/web/components/OtpField.d.ts.map +1 -1
  123. package/dist/web/components/OtpField.js +3 -4
  124. package/dist/web/components/OtpField.js.map +1 -1
  125. package/dist/web/components/OtpField.js.zyzz.json +1 -1
  126. package/dist/web/index.js.zyzz.json +1 -1
  127. package/package.json +1 -1
  128. package/src/web/components/OtpField.tsx +2 -24
@@ -0,0 +1,966 @@
1
+ # Drawer
2
+
3
+ Slides a modal panel in from an edge of the viewport.
4
+
5
+ [View source and API](https://github.com/tempoxyz/ds/blob/main/src/web/components/Drawer.tsx)
6
+
7
+ Figma: [Modal/Drawer](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3740) (`Drawer.Popup`)
8
+
9
+ ## Overview
10
+
11
+ A drawer is a modal panel that slides in from a viewport edge. A swipe closes it.
12
+
13
+ ## Examples
14
+
15
+ ### Basic
16
+
17
+ ```tsx
18
+ import { Button, Drawer, style } from '@tempoxyz/ds'
19
+
20
+ export function Demo() {
21
+ return (
22
+ <Drawer.Root>
23
+ <Drawer.Trigger render={<Button variant="secondary" />}>Open drawer</Drawer.Trigger>
24
+ <Drawer.Popup closeButton>
25
+ <Drawer.Content>
26
+ <Drawer.Title>Filters</Drawer.Title>
27
+ <Drawer.Description>Swipe down or press Escape to close.</Drawer.Description>
28
+ <div {...styles.actions()}>
29
+ <Drawer.Close render={<Button variant="secondary" />}>Close</Drawer.Close>
30
+ </div>
31
+ </Drawer.Content>
32
+ </Drawer.Popup>
33
+ </Drawer.Root>
34
+ )
35
+ }
36
+
37
+ namespace styles {
38
+ export const actions = style({ display: 'flex', justifyContent: 'flex-end', marginTop: '32' })
39
+ }
40
+ ```
41
+
42
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Drawer/Basic.tsx)
43
+
44
+ ### SwipeDirection
45
+
46
+ Set `swipeDirection` on `Drawer.Root` to `down` (default, opens from the bottom), `up`, `left`, or `right`.
47
+
48
+ ```tsx
49
+ import { Button, Drawer, style } from '@tempoxyz/ds'
50
+
51
+ const edges = { down: 'bottom', left: 'left', right: 'right', up: 'top' } as const
52
+
53
+ export function Demo() {
54
+ return (
55
+ <div {...styles.row()}>
56
+ {(['down', 'right', 'left', 'up'] as const).map((direction) => (
57
+ <Drawer.Root key={direction} swipeDirection={direction}>
58
+ <Drawer.Trigger render={<Button variant="secondary" />}>
59
+ Open from the {edges[direction]}
60
+ </Drawer.Trigger>
61
+ <Drawer.Popup closeButton>
62
+ <Drawer.Content>
63
+ <Drawer.Title>Filters</Drawer.Title>
64
+ <Drawer.Description>Swipe {direction} or press Escape to close.</Drawer.Description>
65
+ <div {...styles.actions()}>
66
+ <Drawer.Close render={<Button variant="secondary" />}>Close</Drawer.Close>
67
+ </div>
68
+ </Drawer.Content>
69
+ </Drawer.Popup>
70
+ </Drawer.Root>
71
+ ))}
72
+ </div>
73
+ )
74
+ }
75
+
76
+ namespace styles {
77
+ export const actions = style({ display: 'flex', justifyContent: 'flex-end', marginTop: '32' })
78
+
79
+ export const row = style({ alignItems: 'center', display: 'flex', flexWrap: 'wrap', gap: '12' })
80
+ }
81
+ ```
82
+
83
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Drawer/SwipeDirection.tsx)
84
+
85
+ ## Anatomy
86
+
87
+ Import the component and assemble its parts.
88
+
89
+ ```tsx
90
+ import { Drawer } from '@tempoxyz/ds'
91
+
92
+ <Drawer.Root>
93
+ <Drawer.Trigger />
94
+ <Drawer.Popup>
95
+ <Drawer.Content>
96
+ <Drawer.Title />
97
+ <Drawer.Description />
98
+ <Drawer.Close />
99
+ </Drawer.Content>
100
+ </Drawer.Popup>
101
+ </Drawer.Root>
102
+ ```
103
+
104
+ ## Guidelines
105
+
106
+ ### When to Use
107
+
108
+ * Show a record's details beside its list, such as a payment from Activity.
109
+ * Hold filters for a table or dashboard.
110
+ * Show choices, a short form, or details as a bottom sheet on narrow screens.
111
+
112
+ ### When Not to Use
113
+
114
+ * Use a [Dialog](/docs/components/dialog) for a short, focused task, such as Rename wallet.
115
+ * Use an [AlertDialog](/docs/components/alert-dialog) to confirm a destructive action.
116
+ * Use a [Popover](/docs/components/popover) for content anchored to its trigger on wide screens.
117
+ * Use [AppShell](/docs/components/app-shell) for navigation. Below 1024px, its sidebar already opens in a drawer.
118
+ * Use a page for content you'd link to, such as a full transaction record.
119
+
120
+ ### Pick an Edge
121
+
122
+ `swipeDirection` on `Drawer.Root` sets the edge the drawer opens from and the swipe that closes it.
123
+
124
+ * **Bottom (down):** The default, for bottom sheets on narrow screens.
125
+ * **Right Edge (right):** Details and filters beside a list on wide screens.
126
+ * **Left Edge (left):** Rarely. Use the right edge across a product.
127
+ * **Top (up):** Avoid. It covers the header and sits far from the thumb.
128
+
129
+ Below 640px, change a `right` drawer to `swipeDirection="down"`, to match a [Dialog](/docs/components/dialog) at that width.
130
+
131
+ ### Give Drawers Content That Needs Room
132
+
133
+ Use a drawer for content taller than a dialog, such as a payment's full details. A two-button confirmation leaves the panel empty.
134
+
135
+ **Do:** Open record details in a `right` drawer beside the list they came from.
136
+
137
+ ```tsx
138
+ /**
139
+ * Do example for "Give Drawers Content That Needs Room" on the Drawer page.
140
+ * Open record details in a `right` drawer beside the list they came from.
141
+ * @module
142
+ */
143
+ import { style } from '@tempoxyz/ds'
144
+
145
+ const activity = [
146
+ { amount: '−250.00', name: 'Alex' },
147
+ { amount: '+1,200.00', name: 'Payroll' },
148
+ { amount: '−18.40', name: 'Ramen Bar' },
149
+ { amount: '−75.00', name: 'Sam' },
150
+ ]
151
+
152
+ const details = [
153
+ { label: 'Amount', value: '250.00 USDC' },
154
+ { label: 'To', value: '0x3c44…93bc' },
155
+ { label: 'Status', value: 'Confirmed' },
156
+ ]
157
+
158
+ /** Renders the static preview, which the guideline card shows inert. */
159
+ export function Demo() {
160
+ return (
161
+ <div {...styles.frame()}>
162
+ <div {...styles.list()}>
163
+ {activity.map((entry) => (
164
+ <div key={entry.name} {...styles.row()}>
165
+ <span>{entry.name}</span>
166
+ <span>{entry.amount}</span>
167
+ </div>
168
+ ))}
169
+ </div>
170
+ <div {...styles.scrim()} />
171
+ <div {...styles.panel()}>
172
+ <span {...styles.title()}>Payment to Alex</span>
173
+ {details.map((entry) => (
174
+ <div key={entry.label} {...styles.detail()}>
175
+ <span {...styles.label()}>{entry.label}</span>
176
+ <span {...styles.value()}>{entry.value}</span>
177
+ </div>
178
+ ))}
179
+ </div>
180
+ </div>
181
+ )
182
+ }
183
+
184
+ namespace styles {
185
+ export const detail = style({ display: 'flex', flexDirection: 'column', rowGap: '2px !custom' })
186
+
187
+ export const frame = style({
188
+ backgroundColor: 'background.primary',
189
+ borderColor: 'line.secondary',
190
+ borderRadius: 'xs',
191
+ borderStyle: 'solid',
192
+ borderWidth: 'regular',
193
+ boxSizing: 'border-box',
194
+ height: '12rem !custom',
195
+ overflow: 'hidden',
196
+ position: 'relative',
197
+ width: '18rem !custom',
198
+ })
199
+
200
+ export const label = style({ color: 'content.secondary', typography: 'body.b3' })
201
+
202
+ export const list = style({
203
+ display: 'flex',
204
+ flexDirection: 'column',
205
+ padding: '12',
206
+ rowGap: '12',
207
+ })
208
+
209
+ export const panel = style({
210
+ backgroundColor: 'background.elevated',
211
+ borderBottomLeftRadius: 'sm',
212
+ borderTopLeftRadius: 'sm',
213
+ boxSizing: 'border-box',
214
+ display: 'flex',
215
+ flexDirection: 'column',
216
+ insetBlock: '0',
217
+ insetInlineEnd: '0',
218
+ padding: '16',
219
+ position: 'absolute',
220
+ rowGap: '12',
221
+ width: '10.5rem !custom',
222
+ })
223
+
224
+ export const row = style({
225
+ color: 'content.primary',
226
+ display: 'flex',
227
+ justifyContent: 'space-between',
228
+ typography: 'body.b3',
229
+ })
230
+
231
+ export const scrim = style({
232
+ backgroundColor: 'neutral.100',
233
+ inset: '0',
234
+ opacity: 'disabled',
235
+ position: 'absolute',
236
+ })
237
+
238
+ export const title = style({ color: 'content.primary', typography: 'body.b2Strong' })
239
+
240
+ export const value = style({ color: 'content.primary', typography: 'body.b3Strong' })
241
+ }
242
+ ```
243
+
244
+ **Don't:** Put a short confirmation in a drawer. Use an [AlertDialog](/docs/components/alert-dialog).
245
+
246
+ ```tsx
247
+ /**
248
+ * Don't example for "Give Drawers Content That Needs Room" on the Drawer page.
249
+ * Don't put a short confirmation in a drawer. Use an AlertDialog.
250
+ * @module
251
+ */
252
+ import { Button, style } from '@tempoxyz/ds'
253
+
254
+ const keys = ['Production server', 'Staging server', 'Local testing', 'CI pipeline']
255
+
256
+ /** Renders the static preview, which the guideline card shows inert. */
257
+ export function Demo() {
258
+ return (
259
+ <div {...styles.frame()}>
260
+ <div {...styles.list()}>
261
+ {keys.map((key) => (
262
+ <span key={key} {...styles.row()}>
263
+ {key}
264
+ </span>
265
+ ))}
266
+ </div>
267
+ <div {...styles.scrim()} />
268
+ <div {...styles.panel()}>
269
+ <span {...styles.title()}>Revoke this API key?</span>
270
+ <Button size="small">Revoke key</Button>
271
+ <Button size="small" variant="secondary">
272
+ Cancel
273
+ </Button>
274
+ </div>
275
+ </div>
276
+ )
277
+ }
278
+
279
+ namespace styles {
280
+ export const frame = style({
281
+ backgroundColor: 'background.primary',
282
+ borderColor: 'line.secondary',
283
+ borderRadius: 'xs',
284
+ borderStyle: 'solid',
285
+ borderWidth: 'regular',
286
+ boxSizing: 'border-box',
287
+ height: '12rem !custom',
288
+ overflow: 'hidden',
289
+ position: 'relative',
290
+ width: '18rem !custom',
291
+ })
292
+
293
+ export const list = style({
294
+ display: 'flex',
295
+ flexDirection: 'column',
296
+ padding: '12',
297
+ rowGap: '12',
298
+ })
299
+
300
+ export const panel = style({
301
+ backgroundColor: 'background.elevated',
302
+ borderBottomLeftRadius: 'sm',
303
+ borderTopLeftRadius: 'sm',
304
+ boxSizing: 'border-box',
305
+ display: 'flex',
306
+ flexDirection: 'column',
307
+ insetBlock: '0',
308
+ insetInlineEnd: '0',
309
+ padding: '16',
310
+ position: 'absolute',
311
+ rowGap: '8',
312
+ width: '10.5rem !custom',
313
+ })
314
+
315
+ export const row = style({ color: 'content.primary', typography: 'body.b3' })
316
+
317
+ export const scrim = style({
318
+ backgroundColor: 'neutral.100',
319
+ inset: '0',
320
+ opacity: 'disabled',
321
+ position: 'absolute',
322
+ })
323
+
324
+ export const title = style({
325
+ color: 'content.primary',
326
+ marginBottom: '8',
327
+ typography: 'body.b2Strong',
328
+ })
329
+ }
330
+ ```
331
+
332
+ ### Always Render a Close Control
333
+
334
+ Swipes aren't discoverable, and some people can't drag. Set `closeButton` on `Drawer.Popup`, or add a `Drawer.Close` action. A read-only details drawer needs no other action.
335
+
336
+ **Do:** Render a close button, even when a swipe closes the drawer.
337
+
338
+ ```tsx
339
+ /**
340
+ * Do example for "Always Render a Close Control" on the Drawer page.
341
+ * Render a close button, even when a swipe closes the drawer.
342
+ * @module
343
+ */
344
+ import { Button, style } from '@tempoxyz/ds'
345
+ import { Close } from '@tempoxyz/ds/icons'
346
+
347
+ const activity = ['Alex', 'Payroll', 'Ramen Bar', 'Sam']
348
+
349
+ const details = [
350
+ { label: 'Amount', value: '250.00 USDC' },
351
+ { label: 'To', value: '0x3c44…93bc' },
352
+ ]
353
+
354
+ /** Renders the static preview, which the guideline card shows inert. */
355
+ export function Demo() {
356
+ return (
357
+ <div {...styles.frame()}>
358
+ <div {...styles.list()}>
359
+ {activity.map((name) => (
360
+ <span key={name} {...styles.row()}>
361
+ {name}
362
+ </span>
363
+ ))}
364
+ </div>
365
+ <div {...styles.scrim()} />
366
+ <div {...styles.panel()}>
367
+ <div {...styles.header()}>
368
+ <span {...styles.title()}>Payment to Alex</span>
369
+ <Button aria-label="Close" shape="icon" size="small" variant="tertiary">
370
+ <Close />
371
+ </Button>
372
+ </div>
373
+ {details.map((entry) => (
374
+ <div key={entry.label} {...styles.detail()}>
375
+ <span {...styles.label()}>{entry.label}</span>
376
+ <span {...styles.value()}>{entry.value}</span>
377
+ </div>
378
+ ))}
379
+ </div>
380
+ </div>
381
+ )
382
+ }
383
+
384
+ namespace styles {
385
+ export const detail = style({ display: 'flex', flexDirection: 'column', rowGap: '2px !custom' })
386
+
387
+ export const frame = style({
388
+ backgroundColor: 'background.primary',
389
+ borderColor: 'line.secondary',
390
+ borderRadius: 'xs',
391
+ borderStyle: 'solid',
392
+ borderWidth: 'regular',
393
+ boxSizing: 'border-box',
394
+ height: '12rem !custom',
395
+ overflow: 'hidden',
396
+ position: 'relative',
397
+ width: '18rem !custom',
398
+ })
399
+
400
+ export const header = style({
401
+ alignItems: 'center',
402
+ columnGap: '4',
403
+ display: 'flex',
404
+ justifyContent: 'space-between',
405
+ minHeight: '32',
406
+ })
407
+
408
+ export const label = style({ color: 'content.secondary', typography: 'body.b3' })
409
+
410
+ export const list = style({
411
+ display: 'flex',
412
+ flexDirection: 'column',
413
+ padding: '12',
414
+ rowGap: '12',
415
+ })
416
+
417
+ export const panel = style({
418
+ backgroundColor: 'background.elevated',
419
+ borderBottomLeftRadius: 'sm',
420
+ borderTopLeftRadius: 'sm',
421
+ boxSizing: 'border-box',
422
+ display: 'flex',
423
+ flexDirection: 'column',
424
+ insetBlock: '0',
425
+ insetInlineEnd: '0',
426
+ padding: '16',
427
+ position: 'absolute',
428
+ rowGap: '12',
429
+ width: '11rem !custom',
430
+ })
431
+
432
+ export const row = style({ color: 'content.primary', typography: 'body.b3' })
433
+
434
+ export const scrim = style({
435
+ backgroundColor: 'neutral.100',
436
+ inset: '0',
437
+ opacity: 'disabled',
438
+ position: 'absolute',
439
+ })
440
+
441
+ export const title = style({ color: 'content.primary', typography: 'body.b2Strong' })
442
+
443
+ export const value = style({ color: 'content.primary', typography: 'body.b3Strong' })
444
+ }
445
+ ```
446
+
447
+ **Don't:** Make a swipe or a backdrop press the only way out.
448
+
449
+ ```tsx
450
+ /**
451
+ * Don't example for "Always Render a Close Control" on the Drawer page.
452
+ * Don't make a swipe or a backdrop press the only way out.
453
+ * @module
454
+ */
455
+ import { style } from '@tempoxyz/ds'
456
+
457
+ const activity = ['Alex', 'Payroll', 'Ramen Bar', 'Sam']
458
+
459
+ const details = [
460
+ { label: 'Amount', value: '250.00 USDC' },
461
+ { label: 'To', value: '0x3c44…93bc' },
462
+ ]
463
+
464
+ /** Renders the static preview, which the guideline card shows inert. */
465
+ export function Demo() {
466
+ return (
467
+ <div {...styles.frame()}>
468
+ <div {...styles.list()}>
469
+ {activity.map((name) => (
470
+ <span key={name} {...styles.row()}>
471
+ {name}
472
+ </span>
473
+ ))}
474
+ </div>
475
+ <div {...styles.scrim()} />
476
+ <div {...styles.panel()}>
477
+ <div {...styles.header()}>
478
+ <span {...styles.title()}>Payment to Alex</span>
479
+ </div>
480
+ {details.map((entry) => (
481
+ <div key={entry.label} {...styles.detail()}>
482
+ <span {...styles.label()}>{entry.label}</span>
483
+ <span {...styles.value()}>{entry.value}</span>
484
+ </div>
485
+ ))}
486
+ </div>
487
+ </div>
488
+ )
489
+ }
490
+
491
+ namespace styles {
492
+ export const detail = style({ display: 'flex', flexDirection: 'column', rowGap: '2px !custom' })
493
+
494
+ export const frame = style({
495
+ backgroundColor: 'background.primary',
496
+ borderColor: 'line.secondary',
497
+ borderRadius: 'xs',
498
+ borderStyle: 'solid',
499
+ borderWidth: 'regular',
500
+ boxSizing: 'border-box',
501
+ height: '12rem !custom',
502
+ overflow: 'hidden',
503
+ position: 'relative',
504
+ width: '18rem !custom',
505
+ })
506
+
507
+ export const header = style({
508
+ alignItems: 'center',
509
+ columnGap: '4',
510
+ display: 'flex',
511
+ justifyContent: 'space-between',
512
+ minHeight: '32',
513
+ })
514
+
515
+ export const label = style({ color: 'content.secondary', typography: 'body.b3' })
516
+
517
+ export const list = style({
518
+ display: 'flex',
519
+ flexDirection: 'column',
520
+ padding: '12',
521
+ rowGap: '12',
522
+ })
523
+
524
+ export const panel = style({
525
+ backgroundColor: 'background.elevated',
526
+ borderBottomLeftRadius: 'sm',
527
+ borderTopLeftRadius: 'sm',
528
+ boxSizing: 'border-box',
529
+ display: 'flex',
530
+ flexDirection: 'column',
531
+ insetBlock: '0',
532
+ insetInlineEnd: '0',
533
+ padding: '16',
534
+ position: 'absolute',
535
+ rowGap: '12',
536
+ width: '11rem !custom',
537
+ })
538
+
539
+ export const row = style({ color: 'content.primary', typography: 'body.b3' })
540
+
541
+ export const scrim = style({
542
+ backgroundColor: 'neutral.100',
543
+ inset: '0',
544
+ opacity: 'disabled',
545
+ position: 'absolute',
546
+ })
547
+
548
+ export const title = style({ color: 'content.primary', typography: 'body.b2Strong' })
549
+
550
+ export const value = style({ color: 'content.primary', typography: 'body.b3Strong' })
551
+ }
552
+ ```
553
+
554
+ ### Center the Body of a Bottom Sheet
555
+
556
+ A `down` drawer spans the viewport width. Wrap its body in `Drawer.Content` to center it at up to 32rem. Mouse and pen drags inside it select text and don't move the drawer.
557
+
558
+ **Do:** Center the body of a wide bottom sheet with `Drawer.Content`.
559
+
560
+ ```tsx
561
+ /**
562
+ * Do example for "Center the Body of a Bottom Sheet" on the Drawer page.
563
+ * Center the body of a wide bottom sheet with `Drawer.Content`.
564
+ * @module
565
+ */
566
+ import { Button, style } from '@tempoxyz/ds'
567
+
568
+ const details = [
569
+ { label: 'Amount', value: '250.00 USDC' },
570
+ { label: 'To', value: '0x3c44…93bc' },
571
+ ]
572
+
573
+ /** Renders the static preview, which the guideline card shows inert. */
574
+ export function Demo() {
575
+ return (
576
+ <div {...styles.frame()}>
577
+ <div {...styles.scrim()} />
578
+ <div {...styles.sheet()}>
579
+ <div {...styles.content()}>
580
+ <span {...styles.title()}>Payment to Alex</span>
581
+ {details.map((entry) => (
582
+ <div key={entry.label} {...styles.detail()}>
583
+ <span {...styles.label()}>{entry.label}</span>
584
+ <span {...styles.value()}>{entry.value}</span>
585
+ </div>
586
+ ))}
587
+ <Button size="small" variant="secondary">
588
+ Share receipt
589
+ </Button>
590
+ </div>
591
+ </div>
592
+ </div>
593
+ )
594
+ }
595
+
596
+ namespace styles {
597
+ // Drawer.Content centers the body at up to 32rem, drawn here at the preview's scale.
598
+ export const content = style({
599
+ display: 'flex',
600
+ flexDirection: 'column',
601
+ marginInline: 'auto !custom',
602
+ maxWidth: '11rem !custom',
603
+ rowGap: '8',
604
+ })
605
+
606
+ export const detail = style({
607
+ display: 'flex',
608
+ justifyContent: 'space-between',
609
+ })
610
+
611
+ export const frame = style({
612
+ backgroundColor: 'background.primary',
613
+ borderColor: 'line.secondary',
614
+ borderRadius: 'xs',
615
+ borderStyle: 'solid',
616
+ borderWidth: 'regular',
617
+ boxSizing: 'border-box',
618
+ height: '12rem !custom',
619
+ overflow: 'hidden',
620
+ position: 'relative',
621
+ width: '20rem !custom',
622
+ })
623
+
624
+ export const label = style({ color: 'content.secondary', typography: 'body.b3' })
625
+
626
+ export const scrim = style({
627
+ backgroundColor: 'neutral.100',
628
+ inset: '0',
629
+ opacity: 'disabled',
630
+ position: 'absolute',
631
+ })
632
+
633
+ export const sheet = style({
634
+ backgroundColor: 'background.elevated',
635
+ borderTopLeftRadius: 'sm',
636
+ borderTopRightRadius: 'sm',
637
+ bottom: '0',
638
+ boxSizing: 'border-box',
639
+ insetInline: '0',
640
+ padding: '16',
641
+ position: 'absolute',
642
+ })
643
+
644
+ export const title = style({ color: 'content.primary', typography: 'body.b2Strong' })
645
+
646
+ export const value = style({ color: 'content.primary', typography: 'body.b3Strong' })
647
+ }
648
+ ```
649
+
650
+ **Don't:** Stretch the body across the full width of the viewport.
651
+
652
+ ```tsx
653
+ /**
654
+ * Don't example for "Center the Body of a Bottom Sheet" on the Drawer page.
655
+ * Don't stretch the body across the full width of the viewport.
656
+ * @module
657
+ */
658
+ import { Button, style } from '@tempoxyz/ds'
659
+
660
+ const details = [
661
+ { label: 'Amount', value: '250.00 USDC' },
662
+ { label: 'To', value: '0x3c44…93bc' },
663
+ ]
664
+
665
+ /** Renders the static preview, which the guideline card shows inert. */
666
+ export function Demo() {
667
+ return (
668
+ <div {...styles.frame()}>
669
+ <div {...styles.scrim()} />
670
+ <div {...styles.sheet()}>
671
+ <div {...styles.content()}>
672
+ <span {...styles.title()}>Payment to Alex</span>
673
+ {details.map((entry) => (
674
+ <div key={entry.label} {...styles.detail()}>
675
+ <span {...styles.label()}>{entry.label}</span>
676
+ <span {...styles.value()}>{entry.value}</span>
677
+ </div>
678
+ ))}
679
+ <Button size="small" variant="secondary">
680
+ Share receipt
681
+ </Button>
682
+ </div>
683
+ </div>
684
+ </div>
685
+ )
686
+ }
687
+
688
+ namespace styles {
689
+ export const content = style({ display: 'flex', flexDirection: 'column', rowGap: '8' })
690
+
691
+ export const detail = style({
692
+ display: 'flex',
693
+ justifyContent: 'space-between',
694
+ })
695
+
696
+ export const frame = style({
697
+ backgroundColor: 'background.primary',
698
+ borderColor: 'line.secondary',
699
+ borderRadius: 'xs',
700
+ borderStyle: 'solid',
701
+ borderWidth: 'regular',
702
+ boxSizing: 'border-box',
703
+ height: '12rem !custom',
704
+ overflow: 'hidden',
705
+ position: 'relative',
706
+ width: '20rem !custom',
707
+ })
708
+
709
+ export const label = style({ color: 'content.secondary', typography: 'body.b3' })
710
+
711
+ export const scrim = style({
712
+ backgroundColor: 'neutral.100',
713
+ inset: '0',
714
+ opacity: 'disabled',
715
+ position: 'absolute',
716
+ })
717
+
718
+ export const sheet = style({
719
+ backgroundColor: 'background.elevated',
720
+ borderTopLeftRadius: 'sm',
721
+ borderTopRightRadius: 'sm',
722
+ bottom: '0',
723
+ boxSizing: 'border-box',
724
+ insetInline: '0',
725
+ padding: '16',
726
+ position: 'absolute',
727
+ })
728
+
729
+ export const title = style({ color: 'content.primary', typography: 'body.b2Strong' })
730
+
731
+ export const value = style({ color: 'content.primary', typography: 'body.b3Strong' })
732
+ }
733
+ ```
734
+
735
+ ### Keep the Main Action in Reach
736
+
737
+ The popup scrolls on overflow. Put the main action at the end, and keep a bottom sheet short enough to show it without scrolling.
738
+
739
+ **Do:** Keep the bottom sheet short enough to show its main action.
740
+
741
+ ```tsx
742
+ /**
743
+ * Do example for "Keep the Main Action in Reach" on the Drawer page.
744
+ * Keep the bottom sheet short enough to show its main action.
745
+ * @module
746
+ */
747
+ import { Button, style } from '@tempoxyz/ds'
748
+
749
+ const details = [
750
+ { label: 'Amount', value: '250.00 USDC' },
751
+ { label: 'To', value: '0x3c44…93bc' },
752
+ { label: 'Fee', value: '0.01 USDC' },
753
+ ]
754
+
755
+ /** Renders the static preview, which the guideline card shows inert. */
756
+ export function Demo() {
757
+ return (
758
+ <div {...styles.frame()}>
759
+ <div {...styles.scrim()} />
760
+ <div {...styles.sheet()}>
761
+ <span {...styles.title()}>Review payment</span>
762
+ {details.map((entry) => (
763
+ <div key={entry.label} {...styles.detail()}>
764
+ <span {...styles.label()}>{entry.label}</span>
765
+ <span {...styles.value()}>{entry.value}</span>
766
+ </div>
767
+ ))}
768
+ <Button size="small">Send payment</Button>
769
+ </div>
770
+ </div>
771
+ )
772
+ }
773
+
774
+ namespace styles {
775
+ export const detail = style({ display: 'flex', justifyContent: 'space-between' })
776
+
777
+ export const frame = style({
778
+ backgroundColor: 'background.primary',
779
+ borderColor: 'line.secondary',
780
+ borderRadius: 'xs',
781
+ borderStyle: 'solid',
782
+ borderWidth: 'regular',
783
+ boxSizing: 'border-box',
784
+ height: '14rem !custom',
785
+ overflow: 'hidden',
786
+ position: 'relative',
787
+ width: '12rem !custom',
788
+ })
789
+
790
+ export const label = style({ color: 'content.secondary', typography: 'body.b3' })
791
+
792
+ export const scrim = style({
793
+ backgroundColor: 'neutral.100',
794
+ inset: '0',
795
+ opacity: 'disabled',
796
+ position: 'absolute',
797
+ })
798
+
799
+ export const sheet = style({
800
+ backgroundColor: 'background.elevated',
801
+ borderTopLeftRadius: 'sm',
802
+ borderTopRightRadius: 'sm',
803
+ bottom: '0',
804
+ boxSizing: 'border-box',
805
+ display: 'flex',
806
+ flexDirection: 'column',
807
+ insetInline: '0',
808
+ padding: '16',
809
+ position: 'absolute',
810
+ rowGap: '8',
811
+ })
812
+
813
+ export const title = style({ color: 'content.primary', typography: 'body.b2Strong' })
814
+
815
+ export const value = style({ color: 'content.primary', typography: 'body.b3Strong' })
816
+ }
817
+ ```
818
+
819
+ **Don't:** Push the main action below the fold of a tall sheet.
820
+
821
+ ```tsx
822
+ /**
823
+ * Don't example for "Keep the Main Action in Reach" on the Drawer page.
824
+ * Don't push the main action below the fold of a tall sheet.
825
+ * @module
826
+ */
827
+ import { Button, style } from '@tempoxyz/ds'
828
+
829
+ const details = [
830
+ { label: 'Amount', value: '250.00 USDC' },
831
+ { label: 'To', value: '0x3c44…93bc' },
832
+ { label: 'Fee', value: '0.01 USDC' },
833
+ { label: 'From', value: 'Main wallet' },
834
+ { label: 'Network', value: 'Tempo' },
835
+ { label: 'Memo', value: 'Invoice 1042' },
836
+ { label: 'Arrives', value: 'Instantly' },
837
+ ]
838
+
839
+ /** Renders the static preview, which the guideline card shows inert. */
840
+ export function Demo() {
841
+ return (
842
+ <div {...styles.frame()}>
843
+ <div {...styles.scrim()} />
844
+ <div {...styles.sheet()}>
845
+ <span {...styles.title()}>Review payment</span>
846
+ {details.map((entry) => (
847
+ <div key={entry.label} {...styles.detail()}>
848
+ <span {...styles.label()}>{entry.label}</span>
849
+ <span {...styles.value()}>{entry.value}</span>
850
+ </div>
851
+ ))}
852
+ <Button size="small">Send payment</Button>
853
+ </div>
854
+ </div>
855
+ )
856
+ }
857
+
858
+ namespace styles {
859
+ export const detail = style({ display: 'flex', justifyContent: 'space-between' })
860
+
861
+ export const frame = style({
862
+ backgroundColor: 'background.primary',
863
+ borderColor: 'line.secondary',
864
+ borderRadius: 'xs',
865
+ borderStyle: 'solid',
866
+ borderWidth: 'regular',
867
+ boxSizing: 'border-box',
868
+ height: '14rem !custom',
869
+ overflow: 'hidden',
870
+ position: 'relative',
871
+ width: '12rem !custom',
872
+ })
873
+
874
+ export const label = style({ color: 'content.secondary', typography: 'body.b3' })
875
+
876
+ export const scrim = style({
877
+ backgroundColor: 'neutral.100',
878
+ inset: '0',
879
+ opacity: 'disabled',
880
+ position: 'absolute',
881
+ })
882
+
883
+ export const sheet = style({
884
+ backgroundColor: 'background.elevated',
885
+ borderTopLeftRadius: 'sm',
886
+ borderTopRightRadius: 'sm',
887
+ boxSizing: 'border-box',
888
+ display: 'flex',
889
+ flexDirection: 'column',
890
+ insetInline: '0',
891
+ padding: '16',
892
+ position: 'absolute',
893
+ rowGap: '8',
894
+ top: '24',
895
+ })
896
+
897
+ export const title = style({ color: 'content.primary', typography: 'body.b2Strong' })
898
+
899
+ export const value = style({ color: 'content.primary', typography: 'body.b3Strong' })
900
+ }
901
+ ```
902
+
903
+ ## Content
904
+
905
+ ### Name the Record or Task
906
+
907
+ Render a `Drawer.Title` that names the record or task, such as Payment to Alex or Filters.
908
+
909
+ ## Accessibility
910
+
911
+ A drawer is a modal dialog. It follows the [WAI-ARIA Authoring Practices modal dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/).
912
+
913
+ ### Semantics
914
+
915
+ * **Drawer.Trigger (button):** Announces the dialog and whether it is open.
916
+ * **Drawer.Popup (dialog):** Modal by default.
917
+ * **Drawer.Title (h2):** Names the drawer.
918
+ * **Drawer.Description (p):** Describes the drawer.
919
+ * **Close Button (closeButton):** Adds an icon button named "Close".
920
+ * **Page Behind (inert):** Ignores input and never scrolls with the drawer.
921
+
922
+ ### Keyboard
923
+
924
+ * **Open or Activate (Enter or Space):** Opens the drawer, or activates the focused control.
925
+ * **Move to the Next Element (Tab):** Moves to the next element, and wraps.
926
+ * **Move to the Previous Element (Shift+Tab):** Moves to the previous element, and wraps.
927
+ * **Close the Drawer (Escape):** Closes the drawer.
928
+
929
+ ### Focus
930
+
931
+ * **Opening (initialFocus):** Moves to the panel, so a screen reader starts at its title.
932
+ * **Trapping:** Focus stays inside while open.
933
+ * **Backdrop and Swipe:** An outside press or a swipe to its edge closes the drawer.
934
+ * **Closing (finalFocus):** Returns to the trigger, or to `finalFocus`.
935
+
936
+ ### Motion
937
+
938
+ * **Slide and Swipe (swipeDirection):** Slides in from its edge and follows a swipe.
939
+ * **Reduced Motion (reduce):** Fades in and out instead of sliding.
940
+
941
+ ### Your Responsibilities
942
+
943
+ * **Render a Title (Drawer.Title):** Without it, the drawer has no accessible name.
944
+ * **Name an Alternative to Gestures:** If instructions name a gesture, name a button too.
945
+
946
+ ## Related
947
+
948
+ [Dialog](/docs/components/dialog)
949
+
950
+ Use for short, focused tasks.
951
+
952
+ [Popover](/docs/components/popover)
953
+
954
+ Use for anchored content on wide screens.
955
+
956
+ [AppShell](/docs/components/app-shell)
957
+
958
+ Use for navigation that collapses into a drawer.
959
+
960
+ [Responsive Layout](/docs/patterns/responsive-layout)
961
+
962
+ Covers switching layouts at narrow widths.
963
+
964
+ [Tables and Lists](/docs/patterns/tables-and-lists)
965
+
966
+ Covers opening record details.