@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,630 @@
1
+ # Loading
2
+
3
+ Show that content or an action is on its way, with an indicator to fit the wait.
4
+
5
+ ## Overview
6
+
7
+ Loading applies at three scales: a region that fetches data, an action that waits for a response, and a transaction that waits for a block. At each scale, keep the layout still, block duplicate requests, and end in a result or an error.
8
+
9
+ * **Shape Known (Skeleton):** The shape is known, such as a balance or a row value. Outline it with [Skeleton](/docs/components/skeleton).
10
+ * **Shape Unknown (Spinner):** Only the arrival is known, such as for search results. Center one named [Spinner](/docs/components/spinner) in the region.
11
+ * **Inside a Control (loading):** An action runs in place. Use a busy [Button](/docs/components/button), or [LoadingDots](/docs/components/loading-dots) inside a sentence.
12
+ * **Progress Known (Progress):** The work is countable, such as months exported or files uploaded. Show [Progress](/docs/components/progress).
13
+
14
+ ## Structure
15
+
16
+ 1. **Frame:** The page frame, headings, and navigation stay on screen. Only the loading parts change.
17
+ 2. **Placeholder:** Each loading region holds a placeholder that matches its shape, if the shape is known.
18
+ 3. **Content:** Each placeholder gives way to its content in place, without moving the content around it.
19
+ 4. **Failure:** If a request fails, Couldn't load and a retry action replace the placeholder.
20
+
21
+ ```tsx
22
+ import { Amount, Button, ListGroup, Skeleton, style, Surface } from '@tempoxyz/ds'
23
+ import { Refresh } from '@tempoxyz/ds/icons'
24
+ import { useEffect, useState } from 'react'
25
+
26
+ export const fill = true
27
+ const tokens = [
28
+ ['USDC', '1000.00'],
29
+ ['PathUSD', '250.00'],
30
+ ] as const
31
+
32
+ export function Demo() {
33
+ const [loading, setLoading] = useState(true)
34
+ const [refreshing, setRefreshing] = useState(false)
35
+
36
+ // The first load shows skeletons. A refresh keeps the old values on screen.
37
+ useEffect(() => {
38
+ if (!loading) return
39
+ const timer = setTimeout(() => setLoading(false), 1500)
40
+ return () => clearTimeout(timer)
41
+ }, [loading])
42
+
43
+ useEffect(() => {
44
+ if (!refreshing) return
45
+ const timer = setTimeout(() => setRefreshing(false), 1000)
46
+ return () => clearTimeout(timer)
47
+ }, [refreshing])
48
+
49
+ return (
50
+ <div {...styles.page()}>
51
+ <Surface {...styles.card()}>
52
+ <Surface.Header>
53
+ <Surface.Title>Balance</Surface.Title>
54
+ <Button
55
+ aria-label="Refresh balance"
56
+ disabled={loading}
57
+ loading={refreshing}
58
+ onClick={() => setRefreshing(true)}
59
+ shape="icon"
60
+ size="small"
61
+ variant="secondary"
62
+ >
63
+ <Refresh />
64
+ </Button>
65
+ </Surface.Header>
66
+ <div {...styles.total()}>
67
+ <Skeleton show={loading}>
68
+ <Amount currency="USD" size="lg" value="1250.00" />
69
+ </Skeleton>
70
+ </div>
71
+ <div {...styles.rows()}>
72
+ <ListGroup.Root>
73
+ {tokens.map(([symbol, value]) => (
74
+ <ListGroup.Row key={symbol}>
75
+ <ListGroup.Label>{symbol}</ListGroup.Label>
76
+ <Skeleton show={loading}>
77
+ <Amount symbol={symbol} value={value} />
78
+ </Skeleton>
79
+ </ListGroup.Row>
80
+ ))}
81
+ </ListGroup.Root>
82
+ </div>
83
+ </Surface>
84
+ <Button onClick={() => setLoading(true)} size="small" variant="tertiary">
85
+ Replay first load
86
+ </Button>
87
+ </div>
88
+ )
89
+ }
90
+
91
+ namespace styles {
92
+ export const card = style({
93
+ boxSizing: 'border-box',
94
+ maxWidth: '24rem !custom',
95
+ width: '100% !custom',
96
+ })
97
+
98
+ export const page = style({
99
+ backgroundColor: 'background.primary',
100
+ display: 'grid',
101
+ gap: '12',
102
+ justifyItems: 'center',
103
+ padding: '16',
104
+ })
105
+
106
+ export const rows = style({ marginBlockStart: '16' })
107
+
108
+ // A flex row lets the skeleton take the width of the amount instead of the card.
109
+ export const total = style({ display: 'flex' })
110
+ }
111
+ ```
112
+
113
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/BalanceSkeleton.tsx)
114
+
115
+ This reference layout uses [Skeleton](/docs/components/skeleton) for the total and for each token value in [ListGroup](/docs/components/list-group), so the token labels stay on screen. A refresh keeps the old values on screen.
116
+
117
+ ## Timing
118
+
119
+ Time the indicator to the wait. An indicator that appears and vanishes within a moment reads as a flicker.
120
+
121
+ * **Show Nothing (Under 300ms):** Most responses land here. An indicator would only flash.
122
+ * **Delay the Indicator (300ms to 1s):** Delay the skeleton or spinner briefly. Once it appears, keep it for at least 500ms.
123
+ * **Name the Work (1 to 10s):** Show a skeleton or spinner, and name the work, such as "Confirming payment".
124
+ * **Measure or Explain (Over 10s):** If the work is measurable, show a labelled progress bar. Otherwise explain the wait, and let people leave.
125
+
126
+ Set `loading` on a pressed button immediately, whatever the wait.
127
+
128
+ ## Skeleton
129
+
130
+ Wrap the real content in a [Skeleton](/docs/components/skeleton) and set `show={false}` on arrival, instead of swapping elements. The skeleton takes the exact size of the content.
131
+
132
+ **Do:** Shape skeletons like the content, so the layout holds still when it arrives.
133
+
134
+ ```tsx
135
+ /**
136
+ * Do example for "Loading" on the Loading page.
137
+ * Shape skeletons like the content, so the layout holds still when it arrives.
138
+ * @module
139
+ */
140
+ import { Skeleton, style, Surface } from '@tempoxyz/ds'
141
+
142
+ /** Renders the static preview, which the guideline card shows inert. */
143
+ export function Demo() {
144
+ return (
145
+ <Surface size="medium" {...styles.card()}>
146
+ <Skeleton style={{ height: 14, width: 64 }} />
147
+ <Skeleton style={{ height: 32, width: 160 }} />
148
+ <div {...styles.row()}>
149
+ <Skeleton shape="circle" style={{ height: 32, width: 32 }} />
150
+ <Skeleton style={{ flex: 1, height: 14 }} />
151
+ </div>
152
+ <div {...styles.row()}>
153
+ <Skeleton shape="circle" style={{ height: 32, width: 32 }} />
154
+ <Skeleton style={{ flex: 1, height: 14 }} />
155
+ </div>
156
+ </Surface>
157
+ )
158
+ }
159
+
160
+ namespace styles {
161
+ export const card = style({
162
+ display: 'flex',
163
+ flexDirection: 'column',
164
+ gap: '12',
165
+ width: '100% !custom',
166
+ })
167
+
168
+ export const row = style({ alignItems: 'center', display: 'flex', gap: '12' })
169
+ }
170
+ ```
171
+
172
+ **Don't:** Put a lone spinner in a region with a known layout.
173
+
174
+ ```tsx
175
+ /**
176
+ * Don't example for "Loading" on the Loading page.
177
+ * Don't put a lone spinner in a region with a known layout.
178
+ * @module
179
+ */
180
+ import { Spinner, style, Surface } from '@tempoxyz/ds'
181
+
182
+ /** Renders the static preview, which the guideline card shows inert. */
183
+ export function Demo() {
184
+ return (
185
+ <Surface size="medium" {...styles.card()}>
186
+ <Spinner aria-label="Loading balance" />
187
+ </Surface>
188
+ )
189
+ }
190
+
191
+ namespace styles {
192
+ export const card = style({
193
+ alignItems: 'center',
194
+ display: 'flex',
195
+ height: '10rem !custom',
196
+ justifyContent: 'center',
197
+ width: '100% !custom',
198
+ })
199
+ }
200
+ ```
201
+
202
+ ### Keep Content On Screen While It Refreshes
203
+
204
+ Show skeletons on the first load only. During a refresh, keep content visible and show progress on the control that started it. On views that poll, such as a live dashboard, update values in place without an indicator.
205
+
206
+ **Do:** Keep the current balance visible and set `loading` on the refresh button.
207
+
208
+ ```tsx
209
+ /**
210
+ * Do example for "Keep Content On Screen While It Refreshes" on the Loading page.
211
+ * Keep the current balance visible and set `loading` on the refresh button.
212
+ * @module
213
+ */
214
+ import { Amount, Button, style } from '@tempoxyz/ds'
215
+ import { Refresh } from '@tempoxyz/ds/icons'
216
+
217
+ /** Renders the static preview, which the guideline card shows inert. */
218
+ export function Demo() {
219
+ return (
220
+ <div {...styles.row()}>
221
+ <div>
222
+ <p {...styles.caption()}>Balance</p>
223
+ <Amount currency="USD" size="md" value="1250.00" />
224
+ </div>
225
+ <Button loading size="small" variant="secondary">
226
+ <Refresh />
227
+ Refresh
228
+ </Button>
229
+ </div>
230
+ )
231
+ }
232
+
233
+ namespace styles {
234
+ export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
235
+
236
+ export const row = style({
237
+ alignItems: 'center',
238
+ columnGap: '12',
239
+ display: 'flex',
240
+ justifyContent: 'space-between',
241
+ width: '100% !custom',
242
+ })
243
+ }
244
+ ```
245
+
246
+ **Don't:** Replace a visible balance with a skeleton on every refresh.
247
+
248
+ ```tsx
249
+ /**
250
+ * Don't example for "Keep Content On Screen While It Refreshes" on the Loading page.
251
+ * Don't replace a visible balance with a skeleton on every refresh.
252
+ * @module
253
+ */
254
+ import { Amount, Button, Skeleton, style } from '@tempoxyz/ds'
255
+ import { Refresh } from '@tempoxyz/ds/icons'
256
+
257
+ /** Renders the static preview, which the guideline card shows inert. */
258
+ export function Demo() {
259
+ return (
260
+ <div {...styles.row()}>
261
+ <div>
262
+ <p {...styles.caption()}>Balance</p>
263
+ <Skeleton>
264
+ <Amount currency="USD" size="md" value="1250.00" />
265
+ </Skeleton>
266
+ </div>
267
+ <Button size="small" variant="secondary">
268
+ <Refresh />
269
+ Refresh
270
+ </Button>
271
+ </div>
272
+ )
273
+ }
274
+
275
+ namespace styles {
276
+ export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
277
+
278
+ export const row = style({
279
+ alignItems: 'center',
280
+ columnGap: '12',
281
+ display: 'flex',
282
+ justifyContent: 'space-between',
283
+ width: '100% !custom',
284
+ })
285
+ }
286
+ ```
287
+
288
+ ## Pending Actions
289
+
290
+ Show the pending state on the pressed [Button](/docs/components/button) itself. Inside a line of text, such as a balance that refreshes beside its label, use [LoadingDots](/docs/components/loading-dots).
291
+
292
+ **Do:** Set `loading` on the button that started the action.
293
+
294
+ ```tsx
295
+ <Button loading>Send payment</Button>
296
+ ```
297
+
298
+ **Don't:** Leave the button active while a spinner shows elsewhere.
299
+
300
+ ```tsx
301
+ <Inline gap="12">
302
+ <Spinner aria-label="Sending" />
303
+ <Button>Send payment</Button>
304
+ </Inline>
305
+ ```
306
+
307
+ ### Update Optimistically Only When You Can Undo It
308
+
309
+ Apply a change before the server confirms it only if the change is low-risk, easy to reverse, and almost always succeeds, such as renaming an API key or flipping a notification [Switch](/docs/components/switch).
310
+
311
+ * **Restore on Failure (Toast):** Restore the previous value and state the failure, such as Couldn't rename key.
312
+ * **Never for Onchain State:** Never update onchain state, such as balances, optimistically. Show the transaction as Pending.
313
+
314
+ ### Show Pending Transactions
315
+
316
+ ```tsx
317
+ import { Amount, Badge, Button, ListGroup, style, Surface, Toast } from '@tempoxyz/ds'
318
+ import { CheckCircle, Pending } from '@tempoxyz/ds/icons'
319
+ import { useRef, useState } from 'react'
320
+
321
+ export const fill = true
322
+ type Payment = { id: number; status: 'confirmed' | 'pending' }
323
+
324
+ function Status(props: { status: Payment['status'] }) {
325
+ if (props.status === 'pending')
326
+ return (
327
+ <Badge>
328
+ <Pending />
329
+ Pending
330
+ </Badge>
331
+ )
332
+
333
+ return (
334
+ <Badge>
335
+ <CheckCircle />
336
+ Confirmed
337
+ </Badge>
338
+ )
339
+ }
340
+
341
+ function Activity() {
342
+ const toast = Toast.useToast()
343
+ const next = useRef(1)
344
+ const [payments, setPayments] = useState<readonly Payment[]>([])
345
+ const [submitting, setSubmitting] = useState(false)
346
+
347
+ const confirmed = payments.filter((payment) => payment.status === 'confirmed').length
348
+ const balance = (1250 - confirmed * 25).toFixed(2)
349
+
350
+ return (
351
+ <Surface {...styles.card()}>
352
+ <Surface.Header>
353
+ <Surface.Title>Activity</Surface.Title>
354
+ <Button
355
+ loading={submitting}
356
+ onClick={() => {
357
+ const id = next.current++
358
+ setSubmitting(true)
359
+
360
+ // The button stays busy only until the network accepts the transaction.
361
+ setTimeout(() => {
362
+ setSubmitting(false)
363
+ setPayments((current) => [{ id, status: 'pending' }, ...current])
364
+ }, 800)
365
+
366
+ setTimeout(() => {
367
+ setPayments((current) =>
368
+ current.map((payment) =>
369
+ payment.id === id ? { ...payment, status: 'confirmed' as const } : payment,
370
+ ),
371
+ )
372
+ toast.add({ intent: 'positive', title: 'Payment confirmed' })
373
+ }, 3000)
374
+ }}
375
+ size="small"
376
+ >
377
+ Send 25 USDC
378
+ </Button>
379
+ </Surface.Header>
380
+ <p {...styles.caption()}>Balance</p>
381
+ <Amount currency="USD" size="md" value={balance} />
382
+ <div {...styles.rows()}>
383
+ <ListGroup.Root>
384
+ {payments.map((payment) => (
385
+ <ListGroup.Row key={payment.id}>
386
+ <ListGroup.Info>
387
+ <ListGroup.Title>Sent 25.00 USDC</ListGroup.Title>
388
+ <ListGroup.Description>To 0x71C7…976F</ListGroup.Description>
389
+ </ListGroup.Info>
390
+ <Status status={payment.status} />
391
+ </ListGroup.Row>
392
+ ))}
393
+ <ListGroup.Row>
394
+ <ListGroup.Info>
395
+ <ListGroup.Title>Received 120.00 USDC</ListGroup.Title>
396
+ <ListGroup.Description>From 0x3C44…93BC</ListGroup.Description>
397
+ </ListGroup.Info>
398
+ <Status status="confirmed" />
399
+ </ListGroup.Row>
400
+ </ListGroup.Root>
401
+ </div>
402
+ </Surface>
403
+ )
404
+ }
405
+
406
+ export function Demo() {
407
+ return (
408
+ <div {...styles.page()}>
409
+ <Toast.Provider>
410
+ <Activity />
411
+ <Toast.Viewport />
412
+ </Toast.Provider>
413
+ </div>
414
+ )
415
+ }
416
+
417
+ namespace styles {
418
+ export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
419
+
420
+ export const card = style({
421
+ boxSizing: 'border-box',
422
+ maxWidth: '26rem !custom',
423
+ width: '100% !custom',
424
+ })
425
+
426
+ export const page = style({
427
+ backgroundColor: 'background.primary',
428
+ display: 'grid',
429
+ justifyItems: 'center',
430
+ padding: '16',
431
+ })
432
+
433
+ export const rows = style({ marginBlockStart: '16' })
434
+ }
435
+ ```
436
+
437
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/PendingPayment.tsx)
438
+
439
+ Press Send 25 USDC to move the payment from Pending to Confirmed. The balance changes only on confirmation.
440
+
441
+ * **Release the Button (loading):** Keep it busy until the network accepts the payment. Then add it to activity as Pending.
442
+ * **Don't Block the Screen:** Don't open a dialog while the transaction waits for a block. Other work stays available.
443
+ * **Design for the Slow Case (Under 1s):** Tempo blocks finalize this fast, but a wallet or RPC can still add delay. Link the Pending row to its details.
444
+
445
+ **Do:** Keep the balance as it is, and show the payment as Pending.
446
+
447
+ ```tsx
448
+ /**
449
+ * Do example for "Show Pending Transactions" on the Loading page.
450
+ * Keep the balance as it is, and show the payment as Pending.
451
+ * @module
452
+ */
453
+ import { Amount, Badge, ListGroup, style } from '@tempoxyz/ds'
454
+ import { Pending } from '@tempoxyz/ds/icons'
455
+
456
+ /** Renders the static preview, which the guideline card shows inert. */
457
+ export function Demo() {
458
+ return (
459
+ <div {...styles.fill()}>
460
+ <p {...styles.caption()}>Balance</p>
461
+ <Amount currency="USD" size="md" value="1250.00" />
462
+ <ListGroup.Root {...styles.rows()}>
463
+ <ListGroup.Row>
464
+ <ListGroup.Info>
465
+ <ListGroup.Title>Sent 25.00 USDC</ListGroup.Title>
466
+ </ListGroup.Info>
467
+ <Badge>
468
+ <Pending />
469
+ Pending
470
+ </Badge>
471
+ </ListGroup.Row>
472
+ </ListGroup.Root>
473
+ </div>
474
+ )
475
+ }
476
+
477
+ namespace styles {
478
+ export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
479
+
480
+ export const fill = style({ width: '100% !custom' })
481
+
482
+ export const rows = style({ marginBlockStart: '12' })
483
+ }
484
+ ```
485
+
486
+ **Don't:** Subtract a payment from the balance before it confirms.
487
+
488
+ ```tsx
489
+ /**
490
+ * Don't example for "Show Pending Transactions" on the Loading page.
491
+ * Don't subtract a payment from the balance before it confirms.
492
+ * @module
493
+ */
494
+ import { Amount, Badge, ListGroup, style } from '@tempoxyz/ds'
495
+ import { Pending } from '@tempoxyz/ds/icons'
496
+
497
+ /** Renders the static preview, which the guideline card shows inert. */
498
+ export function Demo() {
499
+ return (
500
+ <div {...styles.fill()}>
501
+ <p {...styles.caption()}>Balance</p>
502
+ <Amount currency="USD" size="md" value="1225.00" />
503
+ <ListGroup.Root {...styles.rows()}>
504
+ <ListGroup.Row>
505
+ <ListGroup.Info>
506
+ <ListGroup.Title>Sent 25.00 USDC</ListGroup.Title>
507
+ </ListGroup.Info>
508
+ <Badge>
509
+ <Pending />
510
+ Pending
511
+ </Badge>
512
+ </ListGroup.Row>
513
+ </ListGroup.Root>
514
+ </div>
515
+ )
516
+ }
517
+
518
+ namespace styles {
519
+ export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
520
+
521
+ export const fill = style({ width: '100% !custom' })
522
+
523
+ export const rows = style({ marginBlockStart: '12' })
524
+ }
525
+ ```
526
+
527
+ ## Progress
528
+
529
+ Use [Progress](/docs/components/progress) when the work is measurable, such as files uploaded or steps completed. Use `value={null}` only if the remaining work is unknown.
530
+
531
+ **Do:** Show how much of a long task is done.
532
+
533
+ ```tsx
534
+ /** Do example for "Show Pending Transactions" on the Loading page: show how much of a long task is done. @module */
535
+ import { Progress, style } from '@tempoxyz/ds'
536
+
537
+ /** Renders the static preview, which the guideline card shows inert. */
538
+ export function Demo() {
539
+ return (
540
+ <Progress.Root getAriaValueText={() => '3 of 5 months'} value={60} {...styles.fill()}>
541
+ <Progress.Label>Exporting activity</Progress.Label>
542
+ <Progress.Value>{() => '3 of 5 months'}</Progress.Value>
543
+ <Progress.Track>
544
+ <Progress.Indicator />
545
+ </Progress.Track>
546
+ </Progress.Root>
547
+ )
548
+ }
549
+
550
+ namespace styles {
551
+ export const fill = style({ width: '100% !custom' })
552
+ }
553
+ ```
554
+
555
+ **Don't:** Show a spinner for a long, measurable task.
556
+
557
+ ```tsx
558
+ /**
559
+ * Don't example for "Show Pending Transactions" on the Loading page.
560
+ * Don't show a spinner for a long, measurable task.
561
+ * @module
562
+ */
563
+ import { Spinner, style } from '@tempoxyz/ds'
564
+
565
+ /** Renders the static preview, which the guideline card shows inert. */
566
+ export function Demo() {
567
+ return (
568
+ <div {...styles.row()}>
569
+ <Spinner aria-label="Exporting activity" />
570
+ <span {...styles.text()}>Exporting activity…</span>
571
+ </div>
572
+ )
573
+ }
574
+
575
+ namespace styles {
576
+ export const row = style({ alignItems: 'center', display: 'flex', gap: '8' })
577
+
578
+ export const text = style({ typography: 'body.b2' })
579
+ }
580
+ ```
581
+
582
+ ## States
583
+
584
+ * **Error:** Couldn't load in place of the placeholder, with Try again. Never leave an indicator spinning after a failure.
585
+ * **Empty:** An [Empty](/docs/components/empty) state after loading finishes with no content. Never show it while loading.
586
+ * **Disabled:** Controls that depend on the data stay disabled until it arrives, such as Send before balances load.
587
+ * **Success:** The content replaces the placeholder. Confirm finished actions with a [Toast](/docs/components/toast).
588
+
589
+ ## Content
590
+
591
+ * **Present Tense:** Use the present tense: "Confirming payment".
592
+ * **Name the Thing:** Write "Loading transactions", not "Loading".
593
+
594
+ ## Accessibility
595
+
596
+ * **Named Indicators (aria-label):** [Spinner](/docs/components/spinner), [LoadingDots](/docs/components/loading-dots), and the row loaders are named Loading. Pass `aria-label` to name what loads.
597
+ * **Skeleton (aria-busy):** Marks the region busy, blocks pointer input, and hides placeholder text from screen readers.
598
+ * **Busy Buttons (aria-busy):** A loading button is marked busy. Announce the result separately, such as in a toast.
599
+ * **Reduced Motion (prefers-reduced-motion):** Every sweep, rotation, and pulse stops. The indicators still mark work in progress.
600
+ * **Focus:** Don't move focus to an indicator, or to content when it arrives after a background load.
601
+
602
+ ## Related
603
+
604
+ [Skeleton](/docs/components/skeleton)
605
+
606
+ Use for content with a known shape.
607
+
608
+ [Spinner](/docs/components/spinner)
609
+
610
+ Use for content with an unknown shape.
611
+
612
+ [Progress](/docs/components/progress)
613
+
614
+ Use for measurable work.
615
+
616
+ [Button](/docs/components/button)
617
+
618
+ Use for the `loading` state.
619
+
620
+ [Feedback and Status](/docs/patterns/feedback-and-status)
621
+
622
+ Covers Pending and Confirmed.
623
+
624
+ [Empty States](/docs/patterns/empty-states)
625
+
626
+ Covers what follows an empty result.
627
+
628
+ [Payments](/docs/patterns/payments)
629
+
630
+ Covers the send and review flow.