@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.
- 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/package.json +1 -1
|
@@ -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.
|