@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.
- package/dist/docs/AGENTS.md +9 -0
- package/dist/docs/components/accordion.md +489 -0
- package/dist/docs/components/address-card.md +147 -0
- package/dist/docs/components/agent-logo.md +194 -0
- package/dist/docs/components/alert-dialog.md +631 -0
- package/dist/docs/components/alert.md +448 -0
- package/dist/docs/components/amount-input.md +305 -0
- package/dist/docs/components/amount.md +794 -0
- package/dist/docs/components/app-shell.md +559 -0
- package/dist/docs/components/asset-chip.md +294 -0
- package/dist/docs/components/autocomplete.md +619 -0
- package/dist/docs/components/avatar.md +321 -0
- package/dist/docs/components/badge.md +339 -0
- package/dist/docs/components/badged-logo.md +116 -0
- package/dist/docs/components/banner.md +379 -0
- package/dist/docs/components/bar-chart.md +208 -0
- package/dist/docs/components/breadcrumbs.md +380 -0
- package/dist/docs/components/button-group.md +286 -0
- package/dist/docs/components/button.md +650 -0
- package/dist/docs/components/calendar.md +375 -0
- package/dist/docs/components/callout.md +96 -0
- package/dist/docs/components/checkbox-group.md +521 -0
- package/dist/docs/components/checkbox.md +467 -0
- package/dist/docs/components/choice-group.md +516 -0
- package/dist/docs/components/choicebox.md +641 -0
- package/dist/docs/components/code.md +100 -0
- package/dist/docs/components/combobox.md +939 -0
- package/dist/docs/components/container.md +257 -0
- package/dist/docs/components/copy-button.md +642 -0
- package/dist/docs/components/date-picker.md +514 -0
- package/dist/docs/components/dialog.md +1332 -0
- package/dist/docs/components/drawer.md +966 -0
- package/dist/docs/components/empty.md +340 -0
- package/dist/docs/components/field.md +570 -0
- package/dist/docs/components/fieldset.md +398 -0
- package/dist/docs/components/frame.md +434 -0
- package/dist/docs/components/gauge.md +299 -0
- package/dist/docs/components/get-started-visual.md +117 -0
- package/dist/docs/components/grid.md +288 -0
- package/dist/docs/components/hero-visual.md +155 -0
- package/dist/docs/components/hex.md +487 -0
- package/dist/docs/components/icon-tile.md +292 -0
- package/dist/docs/components/identicon.md +371 -0
- package/dist/docs/components/inline.md +233 -0
- package/dist/docs/components/inset.md +225 -0
- package/dist/docs/components/kbd.md +423 -0
- package/dist/docs/components/list-group.md +981 -0
- package/dist/docs/components/loading-dots.md +237 -0
- package/dist/docs/components/menu.md +934 -0
- package/dist/docs/components/native-select.md +204 -0
- package/dist/docs/components/network-logo.md +248 -0
- package/dist/docs/components/number-field.md +543 -0
- package/dist/docs/components/otp-field.md +616 -0
- package/dist/docs/components/pagination.md +516 -0
- package/dist/docs/components/popover.md +544 -0
- package/dist/docs/components/progress.md +388 -0
- package/dist/docs/components/qr.md +314 -0
- package/dist/docs/components/radio-group.md +528 -0
- package/dist/docs/components/secret-value.md +406 -0
- package/dist/docs/components/select.md +751 -0
- package/dist/docs/components/separator.md +379 -0
- package/dist/docs/components/sidebar-menu.md +922 -0
- package/dist/docs/components/skeleton.md +517 -0
- package/dist/docs/components/slider.md +539 -0
- package/dist/docs/components/snippet.md +424 -0
- package/dist/docs/components/spinner.md +216 -0
- package/dist/docs/components/split-bar.md +139 -0
- package/dist/docs/components/split-button.md +464 -0
- package/dist/docs/components/stack.md +299 -0
- package/dist/docs/components/status-chip.md +226 -0
- package/dist/docs/components/status-indicator.md +119 -0
- package/dist/docs/components/status-row.md +174 -0
- package/dist/docs/components/stepper.md +451 -0
- package/dist/docs/components/success-hero.md +157 -0
- package/dist/docs/components/surface.md +639 -0
- package/dist/docs/components/switch.md +472 -0
- package/dist/docs/components/table.md +967 -0
- package/dist/docs/components/tabs.md +627 -0
- package/dist/docs/components/tempo-logo.md +185 -0
- package/dist/docs/components/text-input.md +624 -0
- package/dist/docs/components/textarea.md +352 -0
- package/dist/docs/components/toast.md +535 -0
- package/dist/docs/components/token-logo.md +371 -0
- package/dist/docs/components/token.md +247 -0
- package/dist/docs/components/tooltip.md +490 -0
- package/dist/docs/components/verification-code.md +116 -0
- package/dist/docs/components/visual.md +249 -0
- package/dist/docs/components.md +203 -0
- package/dist/docs/foundations/accessibility.md +377 -0
- package/dist/docs/foundations/agents.md +150 -0
- package/dist/docs/foundations/color.md +522 -0
- package/dist/docs/foundations/content/tone.md +194 -0
- package/dist/docs/foundations/content/voice.md +194 -0
- package/dist/docs/foundations/content/writing-style.md +406 -0
- package/dist/docs/foundations/iconography.md +282 -0
- package/dist/docs/foundations/intent.md +360 -0
- package/dist/docs/foundations/layout.md +508 -0
- package/dist/docs/foundations/linter.md +296 -0
- package/dist/docs/foundations/motion.md +151 -0
- package/dist/docs/foundations/radius-and-padding.md +86 -0
- package/dist/docs/foundations/typography.md +266 -0
- package/dist/docs/index.md +137 -0
- package/dist/docs/introduction/faq.md +132 -0
- package/dist/docs/introduction/getting-started.md +377 -0
- package/dist/docs/patterns/blockchain-values.md +709 -0
- package/dist/docs/patterns/dashboards.md +638 -0
- package/dist/docs/patterns/destructive-actions.md +626 -0
- package/dist/docs/patterns/empty-states.md +612 -0
- package/dist/docs/patterns/errors-and-validation.md +808 -0
- package/dist/docs/patterns/feedback-and-status.md +518 -0
- package/dist/docs/patterns/forms.md +679 -0
- package/dist/docs/patterns/loading.md +630 -0
- package/dist/docs/patterns/navigation.md +397 -0
- package/dist/docs/patterns/payments.md +765 -0
- package/dist/docs/patterns/responsive-layout.md +997 -0
- package/dist/docs/patterns/tables-and-lists.md +964 -0
- package/dist/docs/products/tempo-console.md +671 -0
- package/dist/docs/products/tempo-performance.md +437 -0
- package/dist/docs/products/tempo-wallet.md +438 -0
- package/dist/web/components/OtpField.css +1 -1
- package/dist/web/components/OtpField.d.ts +1 -1
- package/dist/web/components/OtpField.d.ts.map +1 -1
- package/dist/web/components/OtpField.js +3 -4
- package/dist/web/components/OtpField.js.map +1 -1
- package/dist/web/components/OtpField.js.zyzz.json +1 -1
- package/dist/web/index.js.zyzz.json +1 -1
- package/package.json +1 -1
- package/src/web/components/OtpField.tsx +2 -24
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
# Responsive Layout
|
|
2
|
+
|
|
3
|
+
Adapt layouts from a 320px phone to a wide desktop with shared breakpoints.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Components change at fixed widths. For example, [AppShell](/docs/components/app-shell) moves its sidebar into a drawer below 1024px. Change page layouts at the same widths, so the page and its components switch together.
|
|
8
|
+
|
|
9
|
+
[Container](/docs/components/container) sets the content width, [AppShell](/docs/components/app-shell) holds the sidebar and content column, and [Grid](/docs/components/grid), [Stack](/docs/components/stack), and [Inline](/docs/components/inline) arrange the content inside.
|
|
10
|
+
|
|
11
|
+
> [!NOTE]
|
|
12
|
+
> The live layouts on this page are reference layouts built from library components and `style`. They are not shipped components. Copy and adapt them in your app.
|
|
13
|
+
|
|
14
|
+
## Breakpoints
|
|
15
|
+
|
|
16
|
+
Use these five breakpoints for every layout change.
|
|
17
|
+
|
|
18
|
+
* **Small (640 (40rem)):** Dialogs stop rendering as bottom sheets, toasts move to the end corner, and the app shell widens its padding.
|
|
19
|
+
* **Medium (768 (48rem)):** Containers widen their side gutters at this breakpoint and above.
|
|
20
|
+
* **Large (1024 (64rem)):** The app shell sidebar becomes sticky, and its menu button hides.
|
|
21
|
+
* **Extra Large (1280 (80rem)):** A wide container reaches its maximum width and stops growing past it.
|
|
22
|
+
* **Wide Displays (1536 (96rem)):** No component changes here. Reserve it for layouts that add a column on very wide displays.
|
|
23
|
+
|
|
24
|
+
Components write these widths as pixel literals, because media queries cannot read CSS variables. At the default 16px font size, the rem and pixel values match.
|
|
25
|
+
|
|
26
|
+
### Write Breakpoints With Aliases
|
|
27
|
+
|
|
28
|
+
A breakpoint name in a `style` key expands to a width query, so the numbers never repeat.
|
|
29
|
+
|
|
30
|
+
* **From a Width ('@media 768'):** Applies from the breakpoint up.
|
|
31
|
+
* **Below a Width ('@media <1024'):** Applies below the breakpoint.
|
|
32
|
+
* **Between Widths ('@media 640..1024'):** Applies from the first breakpoint to the second.
|
|
33
|
+
|
|
34
|
+
Write the narrow layout as the base style. Add wider layouts with minimum-width keys.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
namespace styles {
|
|
38
|
+
export const layout = style({
|
|
39
|
+
display: 'grid',
|
|
40
|
+
gap: '24',
|
|
41
|
+
gridTemplateColumns: 'minmax(0, 1fr) !custom',
|
|
42
|
+
'@media 1024': { gridTemplateColumns: '20rem minmax(0, 1fr) !custom' },
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Content Widths
|
|
48
|
+
|
|
49
|
+
[Container](/docs/components/container) centers content at a maximum width and adds side gutters of 24px, or 32px from 768px. Use the narrowest size that fits the content.
|
|
50
|
+
|
|
51
|
+
* **Narrow (48rem (768px)):** Settings, forms, focused flows, and long text.
|
|
52
|
+
* **Medium (64rem (1024px)):** List and detail pages, and tables with a few columns.
|
|
53
|
+
* **Wide (80rem (1280px)):** The default, for dashboards and wide tables.
|
|
54
|
+
* **Full (None):** Viewport-wide content, such as a block explorer feed.
|
|
55
|
+
|
|
56
|
+
Inside [AppShell](/docs/components/app-shell), `AppShell.Main` limits the column to 1024px and adds its own padding. Set `gutter={false}` on a nested [Container](/docs/components/container), or the padding doubles. Outside [AppShell](/docs/components/app-shell), render the page landmark through [Container](/docs/components/container) with `render={<main />}`.
|
|
57
|
+
|
|
58
|
+
## Page Structures
|
|
59
|
+
|
|
60
|
+
Most Tempo pages use one of four structures. Each changes only at the shared breakpoints.
|
|
61
|
+
|
|
62
|
+
### App Shell With Content
|
|
63
|
+
|
|
64
|
+
Use [AppShell](/docs/components/app-shell) for products with persistent navigation, such as [Tempo Console](/docs/products/tempo-console). [Navigation](/docs/patterns/navigation#move-the-sidebar-into-a-drawer-below-1024px) covers its drawer below 1024px. From 1024px, the sidebar is sticky and 16.25rem wide, which `--sidebar-width` overrides. Set `align="start"` on `AppShell.Root` so a dashboard fills the width beside the sidebar.
|
|
65
|
+
|
|
66
|
+
### List and Detail
|
|
67
|
+
|
|
68
|
+
Use list and detail for records that people scan and then inspect, such as payments, API keys, or webhooks.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Amount, Badge, Button, Grid, Hex, ListGroup, style, Surface } from '@tempoxyz/ds'
|
|
72
|
+
import { ArrowLeft } from '@tempoxyz/ds/icons'
|
|
73
|
+
import { useRef, useState } from 'react'
|
|
74
|
+
import { flushSync } from 'react-dom'
|
|
75
|
+
|
|
76
|
+
export const fill = true
|
|
77
|
+
const payments = [
|
|
78
|
+
{
|
|
79
|
+
amount: '1200.00',
|
|
80
|
+
hash: '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
|
|
81
|
+
id: 'payroll',
|
|
82
|
+
status: 'Confirmed',
|
|
83
|
+
time: 'Today, 09:41',
|
|
84
|
+
title: 'Payroll',
|
|
85
|
+
to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
amount: '48.50',
|
|
89
|
+
hash: '0x91bd07e1c3e05a2277a1c0d94f2a91c3b6a900fa2b585dd299e03d12fa4293bc',
|
|
90
|
+
id: 'hosting',
|
|
91
|
+
status: 'Confirmed',
|
|
92
|
+
time: 'Yesterday, 18:02',
|
|
93
|
+
title: 'Hosting',
|
|
94
|
+
to: '0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
amount: '310.00',
|
|
98
|
+
hash: '0xc3e05a2291bd07e14f2a91c3b6a900fa2b585dd299e03d12fa4293bc77a1c0d9',
|
|
99
|
+
id: 'refund',
|
|
100
|
+
status: 'Pending',
|
|
101
|
+
time: 'Oct 18, 14:30',
|
|
102
|
+
title: 'Refund to Ada',
|
|
103
|
+
to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
|
|
104
|
+
},
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
export function Demo() {
|
|
108
|
+
const [selected, setSelected] = useState('payroll')
|
|
109
|
+
// Narrow layouts show one pane at a time. From 1024px both panes show and `view` has no effect.
|
|
110
|
+
const [view, setView] = useState<'detail' | 'list'>('list')
|
|
111
|
+
const payment = payments.find((entry) => entry.id === selected) ?? payments[0]!
|
|
112
|
+
const heading = useRef<HTMLHeadingElement>(null)
|
|
113
|
+
const list = useRef<HTMLElement>(null)
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Grid data-view={view} gap="24" {...styles.layout()}>
|
|
117
|
+
<section aria-labelledby="payments-heading" data-pane="list" ref={list} {...styles.pane()}>
|
|
118
|
+
<h2 id="payments-heading" {...styles.heading()}>
|
|
119
|
+
Payments
|
|
120
|
+
</h2>
|
|
121
|
+
<Surface size="small" {...styles.list()}>
|
|
122
|
+
<ListGroup.Root>
|
|
123
|
+
{payments.map((entry) => (
|
|
124
|
+
<ListGroup.Row
|
|
125
|
+
action="navigation"
|
|
126
|
+
aria-current={entry.id === selected || undefined}
|
|
127
|
+
key={entry.id}
|
|
128
|
+
onClick={() => {
|
|
129
|
+
flushSync(() => {
|
|
130
|
+
setSelected(entry.id)
|
|
131
|
+
setView('detail')
|
|
132
|
+
})
|
|
133
|
+
// When the list hides, focus moves to the detail so it isn't lost.
|
|
134
|
+
if (list.current?.offsetParent === null) heading.current?.focus()
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<ListGroup.Info>
|
|
138
|
+
<ListGroup.Title>{entry.title}</ListGroup.Title>
|
|
139
|
+
<ListGroup.Description>{entry.time}</ListGroup.Description>
|
|
140
|
+
</ListGroup.Info>
|
|
141
|
+
<Amount currency="USD" value={entry.amount} />
|
|
142
|
+
</ListGroup.Row>
|
|
143
|
+
))}
|
|
144
|
+
</ListGroup.Root>
|
|
145
|
+
</Surface>
|
|
146
|
+
</section>
|
|
147
|
+
<section aria-labelledby="payment-heading" data-pane="detail" {...styles.pane()}>
|
|
148
|
+
<div {...styles.back()}>
|
|
149
|
+
<Button
|
|
150
|
+
onClick={() => {
|
|
151
|
+
flushSync(() => setView('list'))
|
|
152
|
+
list.current?.querySelector<HTMLElement>('[aria-current="true"]')?.focus()
|
|
153
|
+
}}
|
|
154
|
+
size="small"
|
|
155
|
+
variant="tertiary"
|
|
156
|
+
>
|
|
157
|
+
<ArrowLeft />
|
|
158
|
+
Payments
|
|
159
|
+
</Button>
|
|
160
|
+
</div>
|
|
161
|
+
<Surface>
|
|
162
|
+
<Surface.Header>
|
|
163
|
+
<Surface.Title id="payment-heading" ref={heading} tabIndex={-1}>
|
|
164
|
+
{payment.title}
|
|
165
|
+
</Surface.Title>
|
|
166
|
+
<Badge>{payment.status}</Badge>
|
|
167
|
+
</Surface.Header>
|
|
168
|
+
<ListGroup.Root>
|
|
169
|
+
<ListGroup.Row>
|
|
170
|
+
<ListGroup.Label>Amount</ListGroup.Label>
|
|
171
|
+
<Amount currency="USD" value={payment.amount} />
|
|
172
|
+
</ListGroup.Row>
|
|
173
|
+
<ListGroup.Row>
|
|
174
|
+
<ListGroup.Label>To</ListGroup.Label>
|
|
175
|
+
<Hex copy value={payment.to} />
|
|
176
|
+
</ListGroup.Row>
|
|
177
|
+
<ListGroup.Row>
|
|
178
|
+
<ListGroup.Label>Date</ListGroup.Label>
|
|
179
|
+
{payment.time}
|
|
180
|
+
</ListGroup.Row>
|
|
181
|
+
<ListGroup.Row>
|
|
182
|
+
<ListGroup.Label>Transaction</ListGroup.Label>
|
|
183
|
+
<Hex copy value={payment.hash} />
|
|
184
|
+
</ListGroup.Row>
|
|
185
|
+
</ListGroup.Root>
|
|
186
|
+
</Surface>
|
|
187
|
+
</section>
|
|
188
|
+
</Grid>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
namespace styles {
|
|
193
|
+
export const back = style({
|
|
194
|
+
marginBlockEnd: '8',
|
|
195
|
+
marginInlineStart: '-12px !custom',
|
|
196
|
+
'@media 1024': { display: 'none' },
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
export const heading = style({ margin: '0', marginBlockEnd: '12', typography: 'heading.h3' })
|
|
200
|
+
|
|
201
|
+
export const layout = style({
|
|
202
|
+
alignItems: 'start',
|
|
203
|
+
backgroundColor: 'background.primary',
|
|
204
|
+
gridTemplateColumns: 'minmax(0, 1fr) !custom',
|
|
205
|
+
padding: '16',
|
|
206
|
+
'@media <1024': {
|
|
207
|
+
selectors: {
|
|
208
|
+
'&[data-view="detail"] > [data-pane="list"]': { display: 'none' },
|
|
209
|
+
'&[data-view="list"] > [data-pane="detail"]': { display: 'none' },
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
'@media 1024': { gridTemplateColumns: '20rem minmax(0, 1fr) !custom' },
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
export const list = style({
|
|
216
|
+
selectors: { '& [aria-current="true"]': { backgroundColor: 'container.regular' } },
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
export const pane = style({ minWidth: '0' })
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/ResponsiveListDetail.tsx)
|
|
224
|
+
|
|
225
|
+
* **From 1024px:** Show the list beside the detail, and mark the selected row with `aria-current`.
|
|
226
|
+
* **Below 1024px:** Show the list first. A row replaces it with the detail and a Back action.
|
|
227
|
+
* **URLs:** Give each detail its own URL, so browser Back and shared links work at every width.
|
|
228
|
+
|
|
229
|
+
### Settings
|
|
230
|
+
|
|
231
|
+
Settings stay in one `narrow` column at every width, never several columns. Put each section of related settings in a [Surface](/docs/components/surface), with a `Surface.Title` and [ListGroup](/docs/components/list-group) or [Field](/docs/components/field) inside.
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { Container, Field, Inset, ListGroup, style, Surface, Switch } from '@tempoxyz/ds'
|
|
235
|
+
|
|
236
|
+
export const fill = true
|
|
237
|
+
export function Demo() {
|
|
238
|
+
return (
|
|
239
|
+
<Inset block="32" {...styles.page()}>
|
|
240
|
+
<Container size="narrow" {...styles.column()}>
|
|
241
|
+
<h1 {...styles.title()}>Settings</h1>
|
|
242
|
+
<Surface render={<section aria-labelledby="account-heading" />}>
|
|
243
|
+
<Surface.Header>
|
|
244
|
+
<Surface.Title id="account-heading">Account</Surface.Title>
|
|
245
|
+
</Surface.Header>
|
|
246
|
+
<ListGroup.Root>
|
|
247
|
+
<ListGroup.Row action="navigation" render={<a href="#name" />}>
|
|
248
|
+
<ListGroup.Label>Name</ListGroup.Label>
|
|
249
|
+
Tempo Labs
|
|
250
|
+
</ListGroup.Row>
|
|
251
|
+
<ListGroup.Row action="navigation" render={<a href="#email" />}>
|
|
252
|
+
<ListGroup.Label>Email</ListGroup.Label>
|
|
253
|
+
ops@tempo.xyz
|
|
254
|
+
</ListGroup.Row>
|
|
255
|
+
</ListGroup.Root>
|
|
256
|
+
</Surface>
|
|
257
|
+
<Surface render={<section aria-labelledby="notifications-heading" />}>
|
|
258
|
+
<Surface.Header>
|
|
259
|
+
<Surface.Title id="notifications-heading">Notifications</Surface.Title>
|
|
260
|
+
</Surface.Header>
|
|
261
|
+
<Field.Root>
|
|
262
|
+
<Field.Label>Payment received</Field.Label>
|
|
263
|
+
<Switch defaultChecked />
|
|
264
|
+
<Field.Description>Email you when funds arrive.</Field.Description>
|
|
265
|
+
</Field.Root>
|
|
266
|
+
<Field.Root>
|
|
267
|
+
<Field.Label>Failed webhooks</Field.Label>
|
|
268
|
+
<Switch />
|
|
269
|
+
<Field.Description>Email you after three failed deliveries.</Field.Description>
|
|
270
|
+
</Field.Root>
|
|
271
|
+
</Surface>
|
|
272
|
+
</Container>
|
|
273
|
+
</Inset>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
namespace styles {
|
|
278
|
+
export const column = style({ display: 'flex', flexDirection: 'column', rowGap: '24' })
|
|
279
|
+
|
|
280
|
+
export const page = style({ backgroundColor: 'background.primary' })
|
|
281
|
+
|
|
282
|
+
export const title = style({ margin: '0', typography: 'heading.h2' })
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/ResponsiveSettings.tsx)
|
|
287
|
+
|
|
288
|
+
### Focused Flow
|
|
289
|
+
|
|
290
|
+
Send, deposit, and onboarding flows remove global navigation and center one task in a column of about 28rem (`maxWidth: '448'`). Show progress with [Stepper](/docs/components/stepper). Keep the actions at the end of the form.
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import { AmountInput, Button, Choicebox, Field, Inset, Stepper, style, Token } from '@tempoxyz/ds'
|
|
294
|
+
|
|
295
|
+
export const fill = true
|
|
296
|
+
export function Demo() {
|
|
297
|
+
return (
|
|
298
|
+
<Inset block="32" inline="16" {...styles.page()}>
|
|
299
|
+
<form onSubmit={(event) => event.preventDefault()} {...styles.column()}>
|
|
300
|
+
<div {...styles.header()}>
|
|
301
|
+
<Stepper.Root aria-label="Add funds" count={3} value={1} />
|
|
302
|
+
<p {...styles.step()}>Step 1 of 3</p>
|
|
303
|
+
<h1 {...styles.title()}>Add funds</h1>
|
|
304
|
+
</div>
|
|
305
|
+
<Field.Root>
|
|
306
|
+
<AmountInput.Root defaultValue="250">
|
|
307
|
+
<Field.Label>Amount</Field.Label>
|
|
308
|
+
<AmountInput.Control />
|
|
309
|
+
<Token readOnly symbol="USDC" />
|
|
310
|
+
</AmountInput.Root>
|
|
311
|
+
</Field.Root>
|
|
312
|
+
<Choicebox.RadioGroup aria-label="Funding method" defaultValue="bank">
|
|
313
|
+
<Choicebox.Radio value="bank">
|
|
314
|
+
<Choicebox.Title>Bank transfer</Choicebox.Title>
|
|
315
|
+
<Choicebox.Description>Arrives in one to two business days</Choicebox.Description>
|
|
316
|
+
</Choicebox.Radio>
|
|
317
|
+
<Choicebox.Radio value="card">
|
|
318
|
+
<Choicebox.Title>Debit card</Choicebox.Title>
|
|
319
|
+
<Choicebox.Description>Arrives in minutes</Choicebox.Description>
|
|
320
|
+
</Choicebox.Radio>
|
|
321
|
+
</Choicebox.RadioGroup>
|
|
322
|
+
<div {...styles.actions()}>
|
|
323
|
+
<Button type="button" variant="secondary">
|
|
324
|
+
Cancel
|
|
325
|
+
</Button>
|
|
326
|
+
<Button type="submit">Continue</Button>
|
|
327
|
+
</div>
|
|
328
|
+
</form>
|
|
329
|
+
</Inset>
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
namespace styles {
|
|
334
|
+
// Below 640px the actions stack at full width with the primary action on top. The DOM keeps
|
|
335
|
+
// Cancel first, so the inline layout's tab order matches its visual order.
|
|
336
|
+
export const actions = style({
|
|
337
|
+
display: 'flex',
|
|
338
|
+
flexDirection: 'column-reverse',
|
|
339
|
+
gap: '12',
|
|
340
|
+
'@media 640': { flexDirection: 'row', justifyContent: 'flex-end' },
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
export const column = style({
|
|
344
|
+
display: 'flex',
|
|
345
|
+
flexDirection: 'column',
|
|
346
|
+
marginInline: 'auto !custom',
|
|
347
|
+
maxWidth: '448',
|
|
348
|
+
rowGap: '24',
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
export const header = style({ display: 'flex', flexDirection: 'column', rowGap: '8' })
|
|
352
|
+
|
|
353
|
+
export const page = style({ backgroundColor: 'background.primary' })
|
|
354
|
+
|
|
355
|
+
export const step = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
|
|
356
|
+
|
|
357
|
+
export const title = style({ margin: '0', typography: 'heading.h2' })
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/ResponsiveFocusedFlow.tsx)
|
|
362
|
+
|
|
363
|
+
## Guidelines
|
|
364
|
+
|
|
365
|
+
### Start From 320px
|
|
366
|
+
|
|
367
|
+
Write the base styles for a 320px viewport. Add wider layouts with `'@media 640'` and larger keys. Check every page at 320px and at 200 percent zoom.
|
|
368
|
+
|
|
369
|
+
**Do:** Let every row fit 320px, so long values wrap inside their card.
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
/**
|
|
373
|
+
* Do example for "Start From 320px" on the Responsive Layout page.
|
|
374
|
+
* Let every row fit 320px, so long values wrap inside their card.
|
|
375
|
+
* @module
|
|
376
|
+
*/
|
|
377
|
+
import { Amount, style } from '@tempoxyz/ds'
|
|
378
|
+
|
|
379
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
380
|
+
export function Demo() {
|
|
381
|
+
return (
|
|
382
|
+
<div {...styles.phone()}>
|
|
383
|
+
<div {...styles.row()}>
|
|
384
|
+
<span {...styles.label()}>Amount</span>
|
|
385
|
+
<Amount symbol="USDC" value="250.00" />
|
|
386
|
+
</div>
|
|
387
|
+
<div {...styles.row()}>
|
|
388
|
+
<span {...styles.label()}>To</span>
|
|
389
|
+
<span {...styles.value()}>0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045</span>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
namespace styles {
|
|
396
|
+
export const label = style({
|
|
397
|
+
color: 'content.secondary',
|
|
398
|
+
flexShrink: '0 !custom',
|
|
399
|
+
typography: 'body.b3',
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
export const phone = style({
|
|
403
|
+
borderColor: 'line.secondary',
|
|
404
|
+
borderRadius: 'sm',
|
|
405
|
+
borderStyle: 'solid',
|
|
406
|
+
borderWidth: 'regular',
|
|
407
|
+
boxSizing: 'border-box',
|
|
408
|
+
display: 'flex',
|
|
409
|
+
flexDirection: 'column',
|
|
410
|
+
gap: '12',
|
|
411
|
+
overflow: 'hidden',
|
|
412
|
+
padding: '16',
|
|
413
|
+
width: '15rem !custom',
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
export const row = style({ display: 'flex', gap: '16', justifyContent: 'space-between' })
|
|
417
|
+
|
|
418
|
+
export const value = style({
|
|
419
|
+
minWidth: '0',
|
|
420
|
+
overflowWrap: 'anywhere !custom',
|
|
421
|
+
textAlign: 'end',
|
|
422
|
+
typography: 'body.b3',
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Don't:** Size a row for desktop, so it runs past the edge of a phone.
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
/**
|
|
431
|
+
* Don't example for "Start From 320px" on the Responsive Layout page.
|
|
432
|
+
* Don't size a row for desktop, so it runs past the edge of a phone.
|
|
433
|
+
* @module
|
|
434
|
+
*/
|
|
435
|
+
import { Amount, style } from '@tempoxyz/ds'
|
|
436
|
+
|
|
437
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
438
|
+
export function Demo() {
|
|
439
|
+
return (
|
|
440
|
+
<div {...styles.phone()}>
|
|
441
|
+
<div {...styles.row()}>
|
|
442
|
+
<span {...styles.label()}>Amount</span>
|
|
443
|
+
<Amount symbol="USDC" value="250.00" />
|
|
444
|
+
</div>
|
|
445
|
+
<div {...styles.row()}>
|
|
446
|
+
<span {...styles.label()}>To</span>
|
|
447
|
+
<span {...styles.value()}>0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045</span>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
namespace styles {
|
|
454
|
+
export const label = style({
|
|
455
|
+
color: 'content.secondary',
|
|
456
|
+
flexShrink: '0 !custom',
|
|
457
|
+
typography: 'body.b3',
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
export const phone = style({
|
|
461
|
+
borderColor: 'line.secondary',
|
|
462
|
+
borderRadius: 'sm',
|
|
463
|
+
borderStyle: 'solid',
|
|
464
|
+
borderWidth: 'regular',
|
|
465
|
+
boxSizing: 'border-box',
|
|
466
|
+
display: 'flex',
|
|
467
|
+
flexDirection: 'column',
|
|
468
|
+
gap: '12',
|
|
469
|
+
overflow: 'hidden',
|
|
470
|
+
padding: '16',
|
|
471
|
+
width: '15rem !custom',
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
export const row = style({ display: 'flex', gap: '16', justifyContent: 'space-between' })
|
|
475
|
+
|
|
476
|
+
export const value = style({ typography: 'body.b3', whiteSpace: 'nowrap' })
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Let Content Reflow Before Adding Breakpoints
|
|
481
|
+
|
|
482
|
+
Many layouts adapt without a media query:
|
|
483
|
+
|
|
484
|
+
* **[Grid](/docs/components/grid) with `fit`:** Fits as many columns as the width allows.
|
|
485
|
+
* **[Inline](/docs/components/inline) with `wrap`:** Moves items to a new line.
|
|
486
|
+
* **[Hex](/docs/components/hex):** Truncates to the available space.
|
|
487
|
+
|
|
488
|
+
**Do:** Use [Grid](/docs/components/grid) with `fit`, so tiles drop to fewer columns as space runs out.
|
|
489
|
+
|
|
490
|
+
```tsx
|
|
491
|
+
/**
|
|
492
|
+
* Do example for "Let Content Reflow Before Adding Breakpoints" on the Responsive Layout page.
|
|
493
|
+
* Use Grid with `fit`, so tiles drop to fewer columns as space runs out.
|
|
494
|
+
* @module
|
|
495
|
+
*/
|
|
496
|
+
import { Grid, style } from '@tempoxyz/ds'
|
|
497
|
+
|
|
498
|
+
const tiles = [
|
|
499
|
+
['Balance', '$12,480.00'],
|
|
500
|
+
['Sent', '$3,210.50'],
|
|
501
|
+
['Received', '$4,005.00'],
|
|
502
|
+
['Fees', '$0.04'],
|
|
503
|
+
] as const
|
|
504
|
+
|
|
505
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
506
|
+
export function Demo() {
|
|
507
|
+
return (
|
|
508
|
+
<Grid fit="small" gap="12" {...styles.grid()}>
|
|
509
|
+
{tiles.map(([label, value]) => (
|
|
510
|
+
<div key={label} {...styles.tile()}>
|
|
511
|
+
<p {...styles.label()}>{label}</p>
|
|
512
|
+
<p {...styles.value()}>{value}</p>
|
|
513
|
+
</div>
|
|
514
|
+
))}
|
|
515
|
+
</Grid>
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
namespace styles {
|
|
520
|
+
export const grid = style({ width: '17rem !custom' })
|
|
521
|
+
|
|
522
|
+
export const label = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
|
|
523
|
+
|
|
524
|
+
export const tile = style({
|
|
525
|
+
backgroundColor: 'container.regular',
|
|
526
|
+
borderRadius: 'xs',
|
|
527
|
+
minWidth: '0',
|
|
528
|
+
padding: '12',
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
export const value = style({
|
|
532
|
+
fontVariantNumeric: 'tabular-nums !custom',
|
|
533
|
+
margin: '0',
|
|
534
|
+
typography: 'body.b2Strong',
|
|
535
|
+
})
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Don't:** Force a fixed `columns` count that squeezes values until they break.
|
|
540
|
+
|
|
541
|
+
```tsx
|
|
542
|
+
/**
|
|
543
|
+
* Don't example for "Let Content Reflow Before Adding Breakpoints" on the Responsive Layout page.
|
|
544
|
+
* Don't force a fixed `columns` count that squeezes values until they break.
|
|
545
|
+
* @module
|
|
546
|
+
*/
|
|
547
|
+
import { Grid, style } from '@tempoxyz/ds'
|
|
548
|
+
|
|
549
|
+
const tiles = [
|
|
550
|
+
['Balance', '$12,480.00'],
|
|
551
|
+
['Sent', '$3,210.50'],
|
|
552
|
+
['Received', '$4,005.00'],
|
|
553
|
+
['Fees', '$0.04'],
|
|
554
|
+
] as const
|
|
555
|
+
|
|
556
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
557
|
+
export function Demo() {
|
|
558
|
+
return (
|
|
559
|
+
<Grid columns="4" gap="12" {...styles.grid()}>
|
|
560
|
+
{tiles.map(([label, value]) => (
|
|
561
|
+
<div key={label} {...styles.tile()}>
|
|
562
|
+
<p {...styles.label()}>{label}</p>
|
|
563
|
+
<p {...styles.value()}>{value}</p>
|
|
564
|
+
</div>
|
|
565
|
+
))}
|
|
566
|
+
</Grid>
|
|
567
|
+
)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
namespace styles {
|
|
571
|
+
export const grid = style({ width: '17rem !custom' })
|
|
572
|
+
|
|
573
|
+
export const label = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
|
|
574
|
+
|
|
575
|
+
export const tile = style({
|
|
576
|
+
backgroundColor: 'container.regular',
|
|
577
|
+
borderRadius: 'xs',
|
|
578
|
+
minWidth: '0',
|
|
579
|
+
padding: '12',
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
export const value = style({
|
|
583
|
+
fontVariantNumeric: 'tabular-nums !custom',
|
|
584
|
+
margin: '0',
|
|
585
|
+
overflowWrap: 'anywhere !custom',
|
|
586
|
+
typography: 'body.b2Strong',
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Change Structure Only at Shared Breakpoints
|
|
592
|
+
|
|
593
|
+
Switch between one and two panes at 1024px, the same width as [AppShell](/docs/components/app-shell). Components do not change at one-off widths, so the page drifts out of step with them.
|
|
594
|
+
|
|
595
|
+
**Do:** Split into two panes at 1024px, where the app shell also changes.
|
|
596
|
+
|
|
597
|
+
```tsx
|
|
598
|
+
/**
|
|
599
|
+
* Do example for "Change Structure Only at Shared Breakpoints" on the Responsive Layout page.
|
|
600
|
+
* Split into two panes at 1024px, where the app shell also changes.
|
|
601
|
+
* @module
|
|
602
|
+
*/
|
|
603
|
+
import { Badge, ListGroup, style, Surface } from '@tempoxyz/ds'
|
|
604
|
+
|
|
605
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
606
|
+
export function Demo() {
|
|
607
|
+
return (
|
|
608
|
+
<div {...styles.root()}>
|
|
609
|
+
<Badge size="small">From 1024px</Badge>
|
|
610
|
+
<div {...styles.panes()}>
|
|
611
|
+
<ListGroup.Root {...styles.list()}>
|
|
612
|
+
<ListGroup.Row aria-current="true" action="navigation">
|
|
613
|
+
Alice
|
|
614
|
+
</ListGroup.Row>
|
|
615
|
+
<ListGroup.Row action="navigation">Bob</ListGroup.Row>
|
|
616
|
+
</ListGroup.Root>
|
|
617
|
+
<Surface size="small" {...styles.detail()}>
|
|
618
|
+
Payment to Alice
|
|
619
|
+
</Surface>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
namespace styles {
|
|
626
|
+
export const detail = style({ flex: '1 !custom', minWidth: '0' })
|
|
627
|
+
|
|
628
|
+
export const list = style({ width: '7rem !custom' })
|
|
629
|
+
|
|
630
|
+
export const panes = style({
|
|
631
|
+
alignItems: 'flex-start',
|
|
632
|
+
display: 'flex',
|
|
633
|
+
gap: '8',
|
|
634
|
+
width: '17rem !custom',
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
export const root = style({
|
|
638
|
+
alignItems: 'flex-start',
|
|
639
|
+
display: 'flex',
|
|
640
|
+
flexDirection: 'column',
|
|
641
|
+
gap: '12',
|
|
642
|
+
})
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**Don't:** Add a one-off breakpoint, such as 900px, for a single page.
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
/**
|
|
650
|
+
* Don't example for "Change Structure Only at Shared Breakpoints" on the Responsive Layout page.
|
|
651
|
+
* Don't add a one-off breakpoint, such as 900px, for a single page.
|
|
652
|
+
* @module
|
|
653
|
+
*/
|
|
654
|
+
import { Badge, ListGroup, style, Surface } from '@tempoxyz/ds'
|
|
655
|
+
|
|
656
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
657
|
+
export function Demo() {
|
|
658
|
+
return (
|
|
659
|
+
<div {...styles.root()}>
|
|
660
|
+
<Badge size="small">From 900px</Badge>
|
|
661
|
+
<div {...styles.panes()}>
|
|
662
|
+
<ListGroup.Root {...styles.list()}>
|
|
663
|
+
<ListGroup.Row aria-current="true" action="navigation">
|
|
664
|
+
Alice
|
|
665
|
+
</ListGroup.Row>
|
|
666
|
+
<ListGroup.Row action="navigation">Bob</ListGroup.Row>
|
|
667
|
+
</ListGroup.Root>
|
|
668
|
+
<Surface size="small" {...styles.detail()}>
|
|
669
|
+
Payment to Alice
|
|
670
|
+
</Surface>
|
|
671
|
+
</div>
|
|
672
|
+
</div>
|
|
673
|
+
)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
namespace styles {
|
|
677
|
+
export const detail = style({ flex: '1 !custom', minWidth: '0' })
|
|
678
|
+
|
|
679
|
+
export const list = style({ width: '7rem !custom' })
|
|
680
|
+
|
|
681
|
+
export const panes = style({
|
|
682
|
+
alignItems: 'flex-start',
|
|
683
|
+
display: 'flex',
|
|
684
|
+
gap: '8',
|
|
685
|
+
width: '17rem !custom',
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
export const root = style({
|
|
689
|
+
alignItems: 'flex-start',
|
|
690
|
+
display: 'flex',
|
|
691
|
+
flexDirection: 'column',
|
|
692
|
+
gap: '12',
|
|
693
|
+
})
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Keep the Primary Action Within Reach
|
|
698
|
+
|
|
699
|
+
Below 640px, stack form and dialog actions at full width, primary action first. From 640px, they align to the end with the primary action last.
|
|
700
|
+
|
|
701
|
+
**Do:** Stack actions at full width on narrow screens, with the primary action first.
|
|
702
|
+
|
|
703
|
+
```tsx
|
|
704
|
+
/**
|
|
705
|
+
* Do example for "Keep the Primary Action Within Reach" on the Responsive Layout page.
|
|
706
|
+
* Stack actions at full width on narrow screens, with the primary action first.
|
|
707
|
+
* @module
|
|
708
|
+
*/
|
|
709
|
+
import { Button, style } from '@tempoxyz/ds'
|
|
710
|
+
|
|
711
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
712
|
+
export function Demo() {
|
|
713
|
+
return (
|
|
714
|
+
<div {...styles.phone()}>
|
|
715
|
+
<Button>Send payment</Button>
|
|
716
|
+
<Button variant="secondary">Save as draft</Button>
|
|
717
|
+
<Button variant="tertiary">Cancel</Button>
|
|
718
|
+
</div>
|
|
719
|
+
)
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
namespace styles {
|
|
723
|
+
export const phone = style({
|
|
724
|
+
display: 'flex',
|
|
725
|
+
flexDirection: 'column',
|
|
726
|
+
gap: '12',
|
|
727
|
+
width: '15rem !custom',
|
|
728
|
+
})
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
**Don't:** Squeeze several actions into one row at phone width.
|
|
733
|
+
|
|
734
|
+
```tsx
|
|
735
|
+
/**
|
|
736
|
+
* Don't example for "Keep the Primary Action Within Reach" on the Responsive Layout page.
|
|
737
|
+
* Don't squeeze several actions into one row at phone width.
|
|
738
|
+
* @module
|
|
739
|
+
*/
|
|
740
|
+
import { Button, style } from '@tempoxyz/ds'
|
|
741
|
+
|
|
742
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
743
|
+
export function Demo() {
|
|
744
|
+
return (
|
|
745
|
+
<div {...styles.phone()}>
|
|
746
|
+
<Button size="small" variant="tertiary">
|
|
747
|
+
Cancel
|
|
748
|
+
</Button>
|
|
749
|
+
<Button size="small" variant="secondary">
|
|
750
|
+
Save as draft
|
|
751
|
+
</Button>
|
|
752
|
+
<Button size="small">Send payment</Button>
|
|
753
|
+
</div>
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
namespace styles {
|
|
758
|
+
export const phone = style({
|
|
759
|
+
display: 'flex',
|
|
760
|
+
gap: '4',
|
|
761
|
+
justifyContent: 'flex-end',
|
|
762
|
+
overflow: 'hidden',
|
|
763
|
+
width: '15rem !custom',
|
|
764
|
+
})
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### Keep Features and Order the Same
|
|
769
|
+
|
|
770
|
+
A narrow screen shows the same content and actions as a wide one, in a different arrangement. Move secondary actions into a [Menu](/docs/components/menu) instead of removing them.
|
|
771
|
+
|
|
772
|
+
**Do:** Keep secondary actions behind a [Menu](/docs/components/menu) button on narrow screens.
|
|
773
|
+
|
|
774
|
+
```tsx
|
|
775
|
+
/**
|
|
776
|
+
* Do example for "Keep Features and Order the Same" on the Responsive Layout page.
|
|
777
|
+
* Keep secondary actions behind a Menu button on narrow screens.
|
|
778
|
+
* @module
|
|
779
|
+
*/
|
|
780
|
+
import { Button, style } from '@tempoxyz/ds'
|
|
781
|
+
import { MoreHorizontal } from '@tempoxyz/ds/icons'
|
|
782
|
+
|
|
783
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
784
|
+
export function Demo() {
|
|
785
|
+
return (
|
|
786
|
+
<div {...styles.phone()}>
|
|
787
|
+
<span {...styles.title()}>API keys</span>
|
|
788
|
+
<Button size="small">New key</Button>
|
|
789
|
+
<Button aria-label="More actions" shape="icon" size="small" variant="secondary">
|
|
790
|
+
<MoreHorizontal />
|
|
791
|
+
</Button>
|
|
792
|
+
</div>
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
namespace styles {
|
|
797
|
+
export const phone = style({
|
|
798
|
+
alignItems: 'center',
|
|
799
|
+
display: 'flex',
|
|
800
|
+
gap: '8',
|
|
801
|
+
width: '15rem !custom',
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
export const title = style({ flex: '1 !custom', typography: 'body.b1Strong' })
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
**Don't:** Drop actions on narrow screens that the wide layout offers.
|
|
809
|
+
|
|
810
|
+
```tsx
|
|
811
|
+
/**
|
|
812
|
+
* Don't example for "Keep Features and Order the Same" on the Responsive Layout page.
|
|
813
|
+
* Don't drop actions on narrow screens that the wide layout offers.
|
|
814
|
+
* @module
|
|
815
|
+
*/
|
|
816
|
+
import { Button, style } from '@tempoxyz/ds'
|
|
817
|
+
|
|
818
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
819
|
+
export function Demo() {
|
|
820
|
+
return (
|
|
821
|
+
<div {...styles.phone()}>
|
|
822
|
+
<span {...styles.title()}>API keys</span>
|
|
823
|
+
<Button size="small">New key</Button>
|
|
824
|
+
</div>
|
|
825
|
+
)
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
namespace styles {
|
|
829
|
+
export const phone = style({
|
|
830
|
+
alignItems: 'center',
|
|
831
|
+
display: 'flex',
|
|
832
|
+
gap: '8',
|
|
833
|
+
width: '15rem !custom',
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
export const title = style({ flex: '1 !custom', typography: 'body.b1Strong' })
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### Scroll Wide Content Inside Its Own Region
|
|
841
|
+
|
|
842
|
+
Tables, code, and charts that cannot reflow scroll horizontally inside their own container. The page itself never scrolls sideways. To stack table rows instead, see [Tables and Lists](/docs/patterns/tables-and-lists#below-640px).
|
|
843
|
+
|
|
844
|
+
**Do:** Scroll a wide table inside its card, and keep the page at the viewport width.
|
|
845
|
+
|
|
846
|
+
```tsx
|
|
847
|
+
/**
|
|
848
|
+
* Do example for "Scroll Wide Content Inside Its Own Region" on the Responsive Layout page.
|
|
849
|
+
* Scroll a wide table inside its card, and keep the page at the viewport width.
|
|
850
|
+
* @module
|
|
851
|
+
*/
|
|
852
|
+
import { style, Surface, Table } from '@tempoxyz/ds'
|
|
853
|
+
|
|
854
|
+
const runs = [
|
|
855
|
+
['296be2a', '219.1', '412.7', '588.0'],
|
|
856
|
+
['8c41f0d', '224.8', '430.2', '601.3'],
|
|
857
|
+
] as const
|
|
858
|
+
|
|
859
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
860
|
+
export function Demo() {
|
|
861
|
+
return (
|
|
862
|
+
<Surface size="small" {...styles.card()}>
|
|
863
|
+
<div aria-label="Benchmark runs" role="region" tabIndex={0} {...styles.scroll()}>
|
|
864
|
+
<Table.Root size="small" {...styles.table()}>
|
|
865
|
+
<Table.Head>
|
|
866
|
+
<Table.Row>
|
|
867
|
+
<Table.HeaderCell>Run</Table.HeaderCell>
|
|
868
|
+
<Table.HeaderCell justify="end">p50 (ms)</Table.HeaderCell>
|
|
869
|
+
<Table.HeaderCell justify="end">p95 (ms)</Table.HeaderCell>
|
|
870
|
+
<Table.HeaderCell justify="end">p99 (ms)</Table.HeaderCell>
|
|
871
|
+
</Table.Row>
|
|
872
|
+
</Table.Head>
|
|
873
|
+
<Table.Body>
|
|
874
|
+
{runs.map(([run, ...values]) => (
|
|
875
|
+
<Table.Row key={run}>
|
|
876
|
+
<Table.Cell mono>{run}</Table.Cell>
|
|
877
|
+
{values.map((value) => (
|
|
878
|
+
<Table.Cell justify="end" key={value} numeric>
|
|
879
|
+
{value}
|
|
880
|
+
</Table.Cell>
|
|
881
|
+
))}
|
|
882
|
+
</Table.Row>
|
|
883
|
+
))}
|
|
884
|
+
</Table.Body>
|
|
885
|
+
</Table.Root>
|
|
886
|
+
</div>
|
|
887
|
+
</Surface>
|
|
888
|
+
)
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
namespace styles {
|
|
892
|
+
export const card = style({ boxSizing: 'border-box', width: '15rem !custom' })
|
|
893
|
+
|
|
894
|
+
export const scroll = style({ overflowX: 'auto' })
|
|
895
|
+
|
|
896
|
+
export const table = style({ minWidth: '22rem !custom' })
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
**Don't:** Let a wide table push past its card and widen the page.
|
|
901
|
+
|
|
902
|
+
```tsx
|
|
903
|
+
/**
|
|
904
|
+
* Don't example for "Scroll Wide Content Inside Its Own Region" on the Responsive Layout page.
|
|
905
|
+
* Don't let a wide table push past its card and widen the page.
|
|
906
|
+
* @module
|
|
907
|
+
*/
|
|
908
|
+
import { style, Surface, Table } from '@tempoxyz/ds'
|
|
909
|
+
|
|
910
|
+
const runs = [
|
|
911
|
+
['296be2a', '219.1', '412.7', '588.0'],
|
|
912
|
+
['8c41f0d', '224.8', '430.2', '601.3'],
|
|
913
|
+
] as const
|
|
914
|
+
|
|
915
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
916
|
+
export function Demo() {
|
|
917
|
+
return (
|
|
918
|
+
<Surface size="small" {...styles.card()}>
|
|
919
|
+
<Table.Root size="small" {...styles.table()}>
|
|
920
|
+
<Table.Head>
|
|
921
|
+
<Table.Row>
|
|
922
|
+
<Table.HeaderCell>Run</Table.HeaderCell>
|
|
923
|
+
<Table.HeaderCell justify="end">p50 (ms)</Table.HeaderCell>
|
|
924
|
+
<Table.HeaderCell justify="end">p95 (ms)</Table.HeaderCell>
|
|
925
|
+
<Table.HeaderCell justify="end">p99 (ms)</Table.HeaderCell>
|
|
926
|
+
</Table.Row>
|
|
927
|
+
</Table.Head>
|
|
928
|
+
<Table.Body>
|
|
929
|
+
{runs.map(([run, ...values]) => (
|
|
930
|
+
<Table.Row key={run}>
|
|
931
|
+
<Table.Cell mono>{run}</Table.Cell>
|
|
932
|
+
{values.map((value) => (
|
|
933
|
+
<Table.Cell justify="end" key={value} numeric>
|
|
934
|
+
{value}
|
|
935
|
+
</Table.Cell>
|
|
936
|
+
))}
|
|
937
|
+
</Table.Row>
|
|
938
|
+
))}
|
|
939
|
+
</Table.Body>
|
|
940
|
+
</Table.Root>
|
|
941
|
+
</Surface>
|
|
942
|
+
)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
namespace styles {
|
|
946
|
+
export const card = style({
|
|
947
|
+
boxSizing: 'border-box',
|
|
948
|
+
overflow: 'visible',
|
|
949
|
+
width: '15rem !custom',
|
|
950
|
+
})
|
|
951
|
+
|
|
952
|
+
export const table = style({ minWidth: '22rem !custom' })
|
|
953
|
+
}
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
## States
|
|
957
|
+
|
|
958
|
+
* **Nothing Selected (Empty):** From 1024px, select the first record or show an empty detail pane, such as Select a payment.
|
|
959
|
+
* **Loading (Skeleton):** Render a [Skeleton](/docs/components/skeleton) in the shape of each pane, so the layout does not jump.
|
|
960
|
+
|
|
961
|
+
## Accessibility
|
|
962
|
+
|
|
963
|
+
* **Scroll Regions (tabIndex={0}):** Give each scrolling region a name and make it focusable, so keyboards can scroll it.
|
|
964
|
+
* **Matching Order:** Match the DOM order to the visual order. Stacked actions that reverse on phones keep Cancel first.
|
|
965
|
+
* **Hidden Panes (display: none):** Hidden panes leave the accessibility tree. Move focus to the replacement pane.
|
|
966
|
+
* **Landmarks (main):** The app shell or a container renders one per page. Label each `section` with `aria-labelledby`.
|
|
967
|
+
* **Rem Widths (rem):** Size widths in rem, so layouts follow the font size setting.
|
|
968
|
+
|
|
969
|
+
## Related
|
|
970
|
+
|
|
971
|
+
[Container](/docs/components/container)
|
|
972
|
+
|
|
973
|
+
Use for content width and gutters.
|
|
974
|
+
|
|
975
|
+
[Grid](/docs/components/grid)
|
|
976
|
+
|
|
977
|
+
Use for collections that reflow.
|
|
978
|
+
|
|
979
|
+
[AppShell](/docs/components/app-shell)
|
|
980
|
+
|
|
981
|
+
Use for the sidebar and content column.
|
|
982
|
+
|
|
983
|
+
[Layout](/docs/foundations/layout)
|
|
984
|
+
|
|
985
|
+
Covers spacing and alignment.
|
|
986
|
+
|
|
987
|
+
[Navigation](/docs/patterns/navigation)
|
|
988
|
+
|
|
989
|
+
Covers moving between pages.
|
|
990
|
+
|
|
991
|
+
[Tables and Lists](/docs/patterns/tables-and-lists)
|
|
992
|
+
|
|
993
|
+
Covers tables on narrow screens.
|
|
994
|
+
|
|
995
|
+
[Dashboards](/docs/patterns/dashboards)
|
|
996
|
+
|
|
997
|
+
Covers metric grids that reflow.
|