@tempoxyz/ds 0.3.10 → 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 (120) 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/package.json +1 -1
@@ -0,0 +1,559 @@
1
+ # AppShell
2
+
3
+ Places a sticky sidebar beside the main content column.
4
+
5
+ [View source and API](https://github.com/tempoxyz/ds/blob/main/src/web/components/AppShell.tsx)
6
+
7
+ Figma: [Layout/App Shell](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-5593) (`AppShell.Root`), [Layout/Page Header](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-5633) (`AppShell.Header`), [Navigation/Tab Bar](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5265-2292) (`AppShell.Root`)
8
+
9
+ ## Overview
10
+
11
+ The outer frame of an application. Below 1024px, the sidebar moves into a drawer, which a menu button in a bottom bar opens. With `collapse`, the sidebar collapses to its icons until 768px instead. The bottom bar also repeats the items you choose.
12
+
13
+ ## Examples
14
+
15
+ ### Basic
16
+
17
+ ```tsx
18
+ /** Composes the Tempo Wallet shell: the account row, the wallet pages, the theme item and a page header. @module */
19
+ import { AppShell, Avatar, Hex, Kbd, Menu, SidebarMenu, StatusChip, style, Surface } from '@tempoxyz/ds'
20
+ import { Clock, DarkMode, Home, Link, LogOut, Plus, Wallet } from '@tempoxyz/ds/icons'
21
+
22
+ export const fill = true
23
+ const address = '0x7a3f2c19b0e4d85a6f1c3e9b2d7a40c58e6f91c2'
24
+
25
+ const balances = ['pathUSD', 'USDC.e', 'AlphaUSD']
26
+
27
+ export function Demo() {
28
+ return (
29
+ <div {...styles.frame()}>
30
+ <AppShell.Root
31
+ align="start"
32
+ collapse
33
+ style={{ height: '100%', minHeight: 0, overflowY: 'auto' }}
34
+ >
35
+ <AppShell.Sidebar aria-label="Wallet" style={{ height: '100%' }}>
36
+ <AppShell.SidebarHeader>
37
+ <Menu.Root>
38
+ <Menu.Trigger
39
+ render={<SidebarMenu.AccountRow description="Tempo Wallet" label="Piril Akay" />}
40
+ >
41
+ <Avatar size="32">
42
+ <Avatar.Fallback>PA</Avatar.Fallback>
43
+ </Avatar>
44
+ </Menu.Trigger>
45
+ <Menu.Popup>
46
+ <Menu.Item>
47
+ <Plus />
48
+ Add account
49
+ </Menu.Item>
50
+ <Menu.Separator />
51
+ <Menu.Item intent="negative">
52
+ <LogOut />
53
+ Sign out
54
+ </Menu.Item>
55
+ </Menu.Popup>
56
+ </Menu.Root>
57
+ </AppShell.SidebarHeader>
58
+ <AppShell.SidebarBody>
59
+ <SidebarMenu.Root>
60
+ <SidebarMenu.Group aria-label="Wallet">
61
+ <SidebarMenu.Item active href="#" tabBar="tab">
62
+ <Home />
63
+ Home
64
+ </SidebarMenu.Item>
65
+ <SidebarMenu.Item href="#" tabBar="tab">
66
+ <Clock />
67
+ Activity
68
+ </SidebarMenu.Item>
69
+ <SidebarMenu.Item href="#" tabBar="tab">
70
+ <Wallet />
71
+ Assets
72
+ </SidebarMenu.Item>
73
+ <SidebarMenu.Item href="#">
74
+ <Link />
75
+ Connections
76
+ </SidebarMenu.Item>
77
+ </SidebarMenu.Group>
78
+ </SidebarMenu.Root>
79
+ </AppShell.SidebarBody>
80
+ <AppShell.SidebarFooter>
81
+ <SidebarMenu.Root aria-label="Preferences">
82
+ <SidebarMenu.Item render={<button type="button" />}>
83
+ <DarkMode />
84
+ <span {...styles.grow()}>Dark mode</span>
85
+ <Kbd alt size="small">
86
+ M
87
+ </Kbd>
88
+ </SidebarMenu.Item>
89
+ </SidebarMenu.Root>
90
+ </AppShell.SidebarFooter>
91
+ </AppShell.Sidebar>
92
+ <AppShell.Main>
93
+ <AppShell.Header>
94
+ <AppShell.Title>Home</AppShell.Title>
95
+ <div {...styles.actions()}>
96
+ <StatusChip intent="positive" network>
97
+ Mainnet
98
+ </StatusChip>
99
+ <Hex.Root size="small" value={address} variant="filled" {...styles.address()}>
100
+ <Hex.Text />
101
+ <Hex.Copy aria-label="Copy wallet address" />
102
+ </Hex.Root>
103
+ </div>
104
+ <AppShell.Trigger />
105
+ </AppShell.Header>
106
+ <div {...styles.list()}>
107
+ {balances.map((balance) => (
108
+ <Surface key={balance}>
109
+ <p {...styles.name()}>{balance}</p>
110
+ <p {...styles.caption()}>Available to send</p>
111
+ </Surface>
112
+ ))}
113
+ </div>
114
+ </AppShell.Main>
115
+ </AppShell.Root>
116
+ </div>
117
+ )
118
+ }
119
+
120
+ namespace styles {
121
+ // The bottom bar takes over below 768px, so the header keeps only its title.
122
+ export const actions = style({
123
+ alignItems: 'center',
124
+ columnGap: '8',
125
+ display: 'flex',
126
+ '@media (max-width: 767px)': { display: 'none' },
127
+ })
128
+
129
+ export const address = style({ maxWidth: '10rem !custom' })
130
+
131
+ export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
132
+
133
+ // The transform makes the frame the containing block for the mobile drawer.
134
+ export const frame = style({
135
+ borderRadius: 'sm',
136
+ height: '32rem !custom',
137
+ overflow: 'hidden',
138
+ transform: 'translateZ(0) !custom',
139
+ selectors: {
140
+ // The maximized gallery preview shows the shell at full window height.
141
+ ':root[data-maximized] &': { borderRadius: '0px !custom', height: '100vh !custom' },
142
+ },
143
+ })
144
+
145
+ export const grow = style({ flex: '1 !custom', minWidth: '0 !custom' })
146
+
147
+ export const list = style({ display: 'flex', flexDirection: 'column', rowGap: '12' })
148
+
149
+ export const name = style({ margin: '0', typography: 'body.b2' })
150
+ }
151
+ ```
152
+
153
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/AppShell/Basic.tsx)
154
+
155
+ ## Anatomy
156
+
157
+ Import the component and assemble its parts.
158
+
159
+ ```tsx
160
+ import { AppShell } from '@tempoxyz/ds'
161
+
162
+ <AppShell.Root>
163
+ <AppShell.Sidebar>
164
+ <AppShell.SidebarHeader />
165
+ <AppShell.SidebarBody />
166
+ <AppShell.SidebarFooter />
167
+ </AppShell.Sidebar>
168
+ <AppShell.Main>
169
+ <AppShell.Header>
170
+ <AppShell.Title />
171
+ </AppShell.Header>
172
+ </AppShell.Main>
173
+ <AppShell.Trigger />
174
+ </AppShell.Root>
175
+ ```
176
+
177
+ ## Guidelines
178
+
179
+ ### When to Use
180
+
181
+ * Frame a product with persistent navigation, such as Tempo Console or the web wallet. Render it once, in the root layout.
182
+ * Pair a [SidebarMenu](/docs/components/sidebar-menu) with a content column of tables, cards, or settings.
183
+
184
+ ### When Not to Use
185
+
186
+ * Use [Container](/docs/components/container) for pages without persistent navigation, such as sign-in, onboarding, or a public receipt.
187
+ * Use [Dialog](/docs/components/dialog), or a full-screen flow in a [Container](/docs/components/container), for focused tasks such as Send payment.
188
+ * Use [Drawer](/docs/components/drawer) for a details or filter panel that opens on demand. The shell's drawer holds only primary navigation.
189
+ * Use [Frame](/docs/components/frame) for a header, body, and footer inside a card or popup.
190
+
191
+ ### Plan for Each Layout
192
+
193
+ The shell switches layouts at 768px and 1024px (the `'768'` and `'1024'` breakpoints). Design and test each.
194
+
195
+ * **Below 768px (Bottom bar):** The sidebar becomes a drawer from the right edge, and a bottom bar holds the chosen items and the menu button.
196
+ * **768px to 1023px (collapse):** The drawer and bottom bar by default. With `collapse`, a 64px column of icons that keep their labels as names.
197
+ * **1024px and Up (28px padding):** The sidebar sticks beside the content with its own scroll, and the bottom bar hides.
198
+
199
+ The drawer is `min(18rem, 85vw)` wide. Server rendering emits the inline sidebar, and CSS hides it below 1024px, or collapses it until 768px with `collapse`.
200
+
201
+ ### Always Render the Trigger
202
+
203
+ Render `AppShell.Trigger` once inside `AppShell.Root`. It adds the menu button to the end of the bottom bar, so the header keeps its full width for the title and actions.
204
+
205
+ **Do:** Render the trigger once in the shell.
206
+
207
+ ```tsx
208
+ /**
209
+ * Do example for "Always Render the Trigger" on the AppShell page.
210
+ * Render the trigger once in the shell.
211
+ * @module
212
+ */
213
+ import { Button, style, Surface } from '@tempoxyz/ds'
214
+ import { Menu } from '@tempoxyz/ds/icons'
215
+
216
+ /** Renders the static preview, which the guideline card shows inert. */
217
+ export function Demo() {
218
+ return (
219
+ <Surface size="small" {...styles.frame()}>
220
+ <span {...styles.title()}>Balances</span>
221
+ <div {...styles.bar()}>
222
+ <Button aria-label="Open menu" shape="icon" size="small">
223
+ <Menu />
224
+ </Button>
225
+ </div>
226
+ </Surface>
227
+ )
228
+ }
229
+
230
+ namespace styles {
231
+ export const bar = style({ display: 'flex', justifyContent: 'flex-end' })
232
+
233
+ export const frame = style({
234
+ display: 'flex',
235
+ flexDirection: 'column',
236
+ gap: '24',
237
+ width: '18rem !custom',
238
+ })
239
+
240
+ export const title = style({ color: 'content.primary', typography: 'heading.h3' })
241
+ }
242
+ ```
243
+
244
+ **Don't:** Leave it out. Below 1024px, or 768px with `collapse`, it is the only way to reach every
245
+ destination.
246
+
247
+ ```tsx
248
+ /**
249
+ * Don't example for "Always Render the Trigger" on the AppShell page.
250
+ * Don't leave it out. Below 1024px, it is the only way to reach navigation.
251
+ * @module
252
+ */
253
+ import { style, Surface } from '@tempoxyz/ds'
254
+
255
+ /** Renders the static preview, which the guideline card shows inert. */
256
+ export function Demo() {
257
+ return (
258
+ <Surface size="small" {...styles.frame()}>
259
+ <span {...styles.title()}>Balances</span>
260
+ <div {...styles.bar()} />
261
+ </Surface>
262
+ )
263
+ }
264
+
265
+ namespace styles {
266
+ // Matches the height of the bottom bar in the Do example, so only the missing button differs.
267
+ export const bar = style({ minHeight: '32' })
268
+
269
+ export const frame = style({
270
+ display: 'flex',
271
+ flexDirection: 'column',
272
+ gap: '24',
273
+ width: '18rem !custom',
274
+ })
275
+
276
+ export const title = style({ color: 'content.primary', typography: 'heading.h3' })
277
+ }
278
+ ```
279
+
280
+ ### Choose an Alignment
281
+
282
+ * **Centered (center):** The default. Centers the sidebar beside a content column up to 1024px wide.
283
+ * **Start (start):** Pins the sidebar left, and the content fills the row. Use it for wide data views.
284
+
285
+ ### Structure the Sidebar
286
+
287
+ * **AppShell.SidebarHeader:** Holds the product or project switcher at the top of the sidebar.
288
+ * **AppShell.SidebarBody:** Holds the sidebar menu. It scrolls on its own and fades the edge with more content.
289
+ * **AppShell.SidebarFooter:** Holds the account row and other controls, pinned to the bottom edge.
290
+
291
+ Keep the header and footer short so the menu keeps most of the height. Set `--sidebar-width` to change the width of the wide sidebar.
292
+
293
+ ### Choose the Bottom Bar Items
294
+
295
+ Wherever the drawer replaces the sidebar, a bottom bar repeats the [SidebarMenu](/docs/components/sidebar-menu) items that set `tabBar`, in sidebar order, and ends with the menu button. Keep the rest in the drawer.
296
+
297
+ * **Tab (tabBar="tab"):** Shows the icon in the tab bar pill. Choose up to three top destinations.
298
+ * **Action (tabBar="action"):** Shows the icon in a floating button beside the pill. Choose one main action.
299
+
300
+ ```tsx
301
+ <SidebarMenu.Item active href="/" tabBar="tab">
302
+ <Home />
303
+ Home
304
+ </SidebarMenu.Item>
305
+ ```
306
+
307
+ ### Use One Title per Page
308
+
309
+ `AppShell.Title` renders the page's `h1` in the header bar across the top of the content column. Keep one primary action in the header.
310
+
311
+ **Do:** Put the title first, then page actions, such as a `small` [Button](/docs/components/button).
312
+
313
+ ```tsx
314
+ /**
315
+ * Do example for "Use One Title per Page" on the AppShell page.
316
+ * Put the title first, then page actions, such as a `small` Button.
317
+ * @module
318
+ */
319
+ import { Button, style, Surface } from '@tempoxyz/ds'
320
+
321
+ /** Renders the static preview, which the guideline card shows inert. */
322
+ export function Demo() {
323
+ return (
324
+ <Surface size="small" {...styles.frame()}>
325
+ <div {...styles.header()}>
326
+ <span {...styles.title()}>API keys</span>
327
+ <Button size="small">New key</Button>
328
+ </div>
329
+ </Surface>
330
+ )
331
+ }
332
+
333
+ namespace styles {
334
+ export const frame = style({ width: '18rem !custom' })
335
+
336
+ export const header = style({ alignItems: 'center', display: 'flex', gap: '12' })
337
+
338
+ export const title = style({
339
+ color: 'content.primary',
340
+ flex: '1 !custom',
341
+ typography: 'heading.h3',
342
+ })
343
+ }
344
+ ```
345
+
346
+ **Don't:** Place actions before the title.
347
+
348
+ ```tsx
349
+ /** Don't example for "Use One Title per Page" on the AppShell page. Don't place actions before the title. @module */
350
+ import { Button, style, Surface } from '@tempoxyz/ds'
351
+
352
+ /** Renders the static preview, which the guideline card shows inert. */
353
+ export function Demo() {
354
+ return (
355
+ <Surface size="small" {...styles.frame()}>
356
+ <div {...styles.header()}>
357
+ <Button size="small">New key</Button>
358
+ <span {...styles.title()}>API keys</span>
359
+ </div>
360
+ </Surface>
361
+ )
362
+ }
363
+
364
+ namespace styles {
365
+ export const frame = style({ width: '18rem !custom' })
366
+
367
+ export const header = style({ alignItems: 'center', display: 'flex', gap: '12' })
368
+
369
+ export const title = style({
370
+ color: 'content.primary',
371
+ flex: '1 !custom',
372
+ typography: 'heading.h3',
373
+ })
374
+ }
375
+ ```
376
+
377
+ ### Close the Drawer After Navigating
378
+
379
+ With a client-side router, the drawer stays open when the route changes. Control `AppShell.Root` with `open` and `onOpenChange`, and set `open` to `false` when the location changes.
380
+
381
+ **Do:** Close the drawer, so the new page shows.
382
+
383
+ ```tsx
384
+ /**
385
+ * Do example for "Close the Drawer After Navigating" on the AppShell page.
386
+ * Close the drawer, so the new page shows.
387
+ * @module
388
+ */
389
+ import { Button, style } from '@tempoxyz/ds'
390
+ import { Menu } from '@tempoxyz/ds/icons'
391
+
392
+ /** Renders the static preview, which the guideline card shows inert. */
393
+ export function Demo() {
394
+ return (
395
+ <div {...styles.screen()}>
396
+ <div {...styles.header()}>
397
+ <span {...styles.title()}>API keys</span>
398
+ <Button aria-label="Open menu" shape="icon" size="small" variant="secondary">
399
+ <Menu />
400
+ </Button>
401
+ </div>
402
+ <span {...styles.meta()}>2 active keys</span>
403
+ </div>
404
+ )
405
+ }
406
+
407
+ namespace styles {
408
+ export const header = style({
409
+ alignItems: 'center',
410
+ display: 'flex',
411
+ gap: '12',
412
+ justifyContent: 'space-between',
413
+ })
414
+
415
+ export const meta = style({ color: 'content.secondary', typography: 'body.b2' })
416
+
417
+ export const screen = style({
418
+ display: 'flex',
419
+ flexDirection: 'column',
420
+ height: '9rem !custom',
421
+ position: 'relative',
422
+ rowGap: '8',
423
+ width: '18rem !custom',
424
+ })
425
+
426
+ export const title = style({ color: 'content.primary', typography: 'heading.h3' })
427
+ }
428
+ ```
429
+
430
+ **Don't:** Leave the drawer over the new page.
431
+
432
+ ```tsx
433
+ /**
434
+ * Don't example for "Close the Drawer After Navigating" on the AppShell page.
435
+ * Don't leave the drawer over the new page.
436
+ * @module
437
+ */
438
+ import { Button, SidebarMenu, style, Surface } from '@tempoxyz/ds'
439
+ import { Home, Key, Menu } from '@tempoxyz/ds/icons'
440
+
441
+ /** Renders the static preview, which the guideline card shows inert. */
442
+ export function Demo() {
443
+ return (
444
+ <div {...styles.screen()}>
445
+ <div {...styles.header()}>
446
+ <span {...styles.title()}>API keys</span>
447
+ <Button aria-label="Open menu" shape="icon" size="small" variant="secondary">
448
+ <Menu />
449
+ </Button>
450
+ </div>
451
+ <span {...styles.meta()}>2 active keys</span>
452
+ <Surface size="small" {...styles.drawer()}>
453
+ <SidebarMenu.Root aria-label="Primary" size="small">
454
+ <SidebarMenu.Item href="#">
455
+ <Home />
456
+ Home
457
+ </SidebarMenu.Item>
458
+ <SidebarMenu.Item active href="#">
459
+ <Key />
460
+ API keys
461
+ </SidebarMenu.Item>
462
+ </SidebarMenu.Root>
463
+ </Surface>
464
+ </div>
465
+ )
466
+ }
467
+
468
+ namespace styles {
469
+ export const drawer = style({
470
+ insetBlock: '0 !custom',
471
+ insetInlineEnd: '0 !custom',
472
+ position: 'absolute',
473
+ width: '11rem !custom',
474
+ })
475
+
476
+ export const header = style({
477
+ alignItems: 'center',
478
+ display: 'flex',
479
+ gap: '12',
480
+ justifyContent: 'space-between',
481
+ })
482
+
483
+ export const meta = style({ color: 'content.secondary', typography: 'body.b2' })
484
+
485
+ export const screen = style({
486
+ display: 'flex',
487
+ flexDirection: 'column',
488
+ height: '9rem !custom',
489
+ position: 'relative',
490
+ rowGap: '8',
491
+ width: '18rem !custom',
492
+ })
493
+
494
+ export const title = style({ color: 'content.primary', typography: 'heading.h3' })
495
+ }
496
+ ```
497
+
498
+ ## Content
499
+
500
+ * Match `AppShell.Title` to the label of the active [SidebarMenu](/docs/components/sidebar-menu) item, such as API keys.
501
+ * Keep titles short enough to share the header row with its actions at 360px.
502
+
503
+ ## Accessibility
504
+
505
+ Provides the page landmarks and the `h1`. Below 1024px, the sidebar drawer follows the [WAI-ARIA Dialog (Modal) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/).
506
+
507
+ ### Semantics
508
+
509
+ * **AppShell.Main (main):** The page's main landmark.
510
+ * **AppShell.Header (header):** Holds the menu trigger and the page title.
511
+ * **AppShell.Title (h1):** The page's top-level heading.
512
+ * **Wide AppShell.Sidebar (complementary):** From 1024px, a landmark named by `aria-label`.
513
+ * **Narrow AppShell.Sidebar (dialog):** Below 1024px, a dialog with the same name.
514
+ * **AppShell.Trigger (button):** Named "Open menu" or "Close menu" by state.
515
+
516
+ ### Keyboard
517
+
518
+ * **Move Through the Shell (Tab):** Moves to the trigger, or cycles the open drawer.
519
+ * **Open the Drawer (Enter or Space):** Opens the drawer from the trigger.
520
+ * **Close the Drawer (Escape):** Closes the drawer.
521
+
522
+ ### Focus
523
+
524
+ * **Opening (Tab or Shift+Tab):** The drawer is modal. Opening it moves focus inside and traps it.
525
+ * **While Open (aria-hidden):** Hides the rest of the page, including the trigger, from assistive technology.
526
+ * **Closing:** Escape, the backdrop, or a swipe right closes the drawer and returns focus to the trigger.
527
+
528
+ ### Motion and Size
529
+
530
+ * **Reduced Motion (prefers-reduced-motion):** The drawer fades, and the trigger switches instantly.
531
+ * **Minimum Width (360px):** The shell never shrinks below this width.
532
+
533
+ ### Your Responsibilities
534
+
535
+ * **Name the Sidebar (aria-label):** Name the sidebar, such as "Primary". The name labels the landmark and the drawer.
536
+ * **Use One Title (h1):** Use one page title. Start the headings of the content at `h2`.
537
+ * **Add a Skip Link (SC 2.4.1):** If the sidebar holds many links, add a [skip link](https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html). The shell adds none.
538
+
539
+ ## Related
540
+
541
+ [SidebarMenu](/docs/components/sidebar-menu)
542
+
543
+ Use for the navigation inside the sidebar.
544
+
545
+ [Container](/docs/components/container)
546
+
547
+ Use for pages without a sidebar.
548
+
549
+ [Drawer](/docs/components/drawer)
550
+
551
+ Use for panels that open on demand.
552
+
553
+ [Responsive Layout](/docs/patterns/responsive-layout)
554
+
555
+ Covers adapting pages across breakpoints.
556
+
557
+ [Navigation](/docs/patterns/navigation)
558
+
559
+ Covers choosing between navigation components.