@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,638 @@
|
|
|
1
|
+
# Dashboards
|
|
2
|
+
|
|
3
|
+
Summarizes system metrics, such as throughput, in tiles, filters, and charts.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
A dashboard addresses one question, such as how the latest benchmark run performed. [Tempo Performance](/docs/products/tempo-performance) uses the pattern: a run header, filters for the run and scenario, a row of metric tiles, and sections of charts.
|
|
8
|
+
|
|
9
|
+
> [!NOTE]
|
|
10
|
+
> The library has no chart component. The examples are reference layouts with placeholder bars drawn with `style`. Render the plot with your charting library.
|
|
11
|
+
|
|
12
|
+
## Recommended Structure
|
|
13
|
+
|
|
14
|
+
Build the page in this order, from top to bottom.
|
|
15
|
+
|
|
16
|
+
* **Header (1):** The page title, the run's date, commit, and duration, and a [Badge](/docs/components/badge) for its type, such as Nightly.
|
|
17
|
+
* **Filters (2):** One row of labelled controls that apply on change.
|
|
18
|
+
* **Metric Tiles (3):** Four to eight headline numbers in [Surface](/docs/components/surface) tiles, most important first.
|
|
19
|
+
* **Chart Sections (4):** One [Surface](/docs/components/surface) per chart, under section headings such as Throughput.
|
|
20
|
+
* **Detail Tables (5):** A [Table](/docs/components/table) of exact values, such as p50, p95, and p99 timings.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { Badge, Button, ChoiceGroup, Field, Grid, Popover, Select, Skeleton, Stack, style, Surface } from '@tempoxyz/ds'
|
|
24
|
+
import { InfoCircle } from '@tempoxyz/ds/icons'
|
|
25
|
+
import { useState } from 'react'
|
|
26
|
+
|
|
27
|
+
export const fill = true
|
|
28
|
+
const runs = {
|
|
29
|
+
'main-2026-10-17-8c1d04ef': 'main-2026-10-17-8c1d04ef',
|
|
30
|
+
'main-2026-10-18-a41f9e20': 'main-2026-10-18-a41f9e20',
|
|
31
|
+
'main-2026-10-19-296be2ab': 'main-2026-10-19-296be2ab',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const scenarios = { mix: 'Public mix, multi-region', transfers: 'Transfers, single region' }
|
|
35
|
+
|
|
36
|
+
const metrics = [
|
|
37
|
+
['Settled TPS', '14,960', 'TPS', 'Median settled throughput across 142 measured blocks.'],
|
|
38
|
+
['Block time', '633', 'ms', 'Median time between consecutive blocks.'],
|
|
39
|
+
['Blocks', '142', undefined, 'Blocks produced during the measured window.'],
|
|
40
|
+
['Avg gas/s', '1.38', 'Ggas', 'Mean gas processed per second.'],
|
|
41
|
+
['Peak gas/s', '2.00', 'Ggas', 'Highest gas processed in any one second.'],
|
|
42
|
+
['Accounts', '1,000', undefined, 'Accounts sending transactions during the run.'],
|
|
43
|
+
] as const
|
|
44
|
+
|
|
45
|
+
const throughput = [14.2, 14.8, 15.1, 14.6, 15.4, 14.9, 15.0, 14.4, 15.2, 14.7, 15.3, 14.9]
|
|
46
|
+
|
|
47
|
+
function Metric(props: { info: string; label: string; unit?: string | undefined; value: string }) {
|
|
48
|
+
return (
|
|
49
|
+
<Surface size="small">
|
|
50
|
+
<dt {...styles.label()}>
|
|
51
|
+
{props.label}
|
|
52
|
+
<Popover.Root>
|
|
53
|
+
<Popover.Trigger
|
|
54
|
+
aria-label={`About ${props.label}`}
|
|
55
|
+
openOnHover
|
|
56
|
+
render={<Button shape="icon" size="small" variant="tertiary" />}
|
|
57
|
+
>
|
|
58
|
+
<InfoCircle />
|
|
59
|
+
</Popover.Trigger>
|
|
60
|
+
<Popover.Popup>
|
|
61
|
+
<Popover.Title>{props.label}</Popover.Title>
|
|
62
|
+
<Popover.Description>{props.info}</Popover.Description>
|
|
63
|
+
</Popover.Popup>
|
|
64
|
+
</Popover.Root>
|
|
65
|
+
</dt>
|
|
66
|
+
<dd {...styles.value()}>
|
|
67
|
+
{props.value}
|
|
68
|
+
{props.unit && <span {...styles.unit()}> {props.unit}</span>}
|
|
69
|
+
</dd>
|
|
70
|
+
</Surface>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function Demo() {
|
|
75
|
+
const [quantile, setQuantile] = useState('p50')
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Stack gap="24" {...styles.page()}>
|
|
79
|
+
<header {...styles.header()}>
|
|
80
|
+
<Badge size="small">Nightly</Badge>
|
|
81
|
+
<h1 {...styles.title()}>Benchmark run</h1>
|
|
82
|
+
<p {...styles.meta()}>
|
|
83
|
+
Oct 20, 2026, 3:01 AM · Commit <code {...styles.mono()}>296be2a</code> · 2m 35s
|
|
84
|
+
</p>
|
|
85
|
+
</header>
|
|
86
|
+
<div {...styles.filters()}>
|
|
87
|
+
<Field.Root {...styles.filter()}>
|
|
88
|
+
<Field.Label>Run</Field.Label>
|
|
89
|
+
<Select.Root defaultValue="main-2026-10-19-296be2ab" items={runs}>
|
|
90
|
+
<Select.Trigger>
|
|
91
|
+
<Select.Value {...styles.mono()} />
|
|
92
|
+
<Select.Icon />
|
|
93
|
+
</Select.Trigger>
|
|
94
|
+
<Select.Popup size="small">
|
|
95
|
+
{Object.entries(runs).map(([value, label]) => (
|
|
96
|
+
<Select.Item key={value} value={value}>
|
|
97
|
+
<Select.ItemText {...styles.mono()}>{label}</Select.ItemText>
|
|
98
|
+
</Select.Item>
|
|
99
|
+
))}
|
|
100
|
+
</Select.Popup>
|
|
101
|
+
</Select.Root>
|
|
102
|
+
</Field.Root>
|
|
103
|
+
<Field.Root {...styles.filter()}>
|
|
104
|
+
<Field.Label>Scenario</Field.Label>
|
|
105
|
+
<Select.Root defaultValue="mix" items={scenarios}>
|
|
106
|
+
<Select.Trigger>
|
|
107
|
+
<Select.Value />
|
|
108
|
+
<Select.Icon />
|
|
109
|
+
</Select.Trigger>
|
|
110
|
+
<Select.Popup size="small">
|
|
111
|
+
{Object.entries(scenarios).map(([value, label]) => (
|
|
112
|
+
<Select.Item key={value} value={value}>
|
|
113
|
+
<Select.ItemText>{label}</Select.ItemText>
|
|
114
|
+
</Select.Item>
|
|
115
|
+
))}
|
|
116
|
+
</Select.Popup>
|
|
117
|
+
</Select.Root>
|
|
118
|
+
</Field.Root>
|
|
119
|
+
<div {...styles.quantile()}>
|
|
120
|
+
<span id="quantile-label" {...styles.legend()}>
|
|
121
|
+
Timing quantile
|
|
122
|
+
</span>
|
|
123
|
+
<ChoiceGroup.Root
|
|
124
|
+
aria-labelledby="quantile-label"
|
|
125
|
+
onValueChange={(value) => setQuantile(String(value))}
|
|
126
|
+
size="small"
|
|
127
|
+
value={quantile}
|
|
128
|
+
>
|
|
129
|
+
<ChoiceGroup.Item value="p50">p50</ChoiceGroup.Item>
|
|
130
|
+
<ChoiceGroup.Item value="p95">p95</ChoiceGroup.Item>
|
|
131
|
+
<ChoiceGroup.Item value="p99">p99</ChoiceGroup.Item>
|
|
132
|
+
</ChoiceGroup.Root>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<dl {...styles.tiles()}>
|
|
136
|
+
{metrics.map(([label, value, unit, info]) => (
|
|
137
|
+
<Metric info={info} key={label} label={label} unit={unit} value={value} />
|
|
138
|
+
))}
|
|
139
|
+
</dl>
|
|
140
|
+
<Grid fit="large" gap="16">
|
|
141
|
+
<Surface render={<section aria-labelledby="tps-title" />} size="medium">
|
|
142
|
+
<Surface.Header>
|
|
143
|
+
<Surface.Title id="tps-title">Settled TPS</Surface.Title>
|
|
144
|
+
</Surface.Header>
|
|
145
|
+
<div
|
|
146
|
+
aria-label="Settled TPS per block, between 14,200 and 15,400"
|
|
147
|
+
role="img"
|
|
148
|
+
{...styles.plot()}
|
|
149
|
+
>
|
|
150
|
+
{throughput.map((value, index) => (
|
|
151
|
+
<span key={index} {...styles.bar({ style: { height: `${(value - 13) * 40}%` } })} />
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
<p {...styles.caption()}>Thousands of transactions per second, per block</p>
|
|
155
|
+
</Surface>
|
|
156
|
+
<Surface render={<section aria-labelledby="validation-title" />} size="medium">
|
|
157
|
+
<Surface.Header>
|
|
158
|
+
<Surface.Title id="validation-title">Block validation ({quantile})</Surface.Title>
|
|
159
|
+
</Surface.Header>
|
|
160
|
+
<Skeleton {...styles.plot()} />
|
|
161
|
+
<p role="status" {...styles.caption()}>
|
|
162
|
+
Loading chart data
|
|
163
|
+
</p>
|
|
164
|
+
</Surface>
|
|
165
|
+
</Grid>
|
|
166
|
+
</Stack>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
namespace styles {
|
|
171
|
+
export const bar = style({
|
|
172
|
+
backgroundColor: 'accent.blue.background',
|
|
173
|
+
borderTopLeftRadius: '3xs',
|
|
174
|
+
borderTopRightRadius: '3xs',
|
|
175
|
+
flex: '1 !custom',
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
|
|
179
|
+
|
|
180
|
+
export const filter = style({ flex: '1 1 12rem !custom', minWidth: '0' })
|
|
181
|
+
|
|
182
|
+
export const filters = style({
|
|
183
|
+
alignItems: 'flex-end',
|
|
184
|
+
display: 'flex',
|
|
185
|
+
flexWrap: 'wrap',
|
|
186
|
+
gap: '12',
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
export const header = style({
|
|
190
|
+
alignItems: 'flex-start',
|
|
191
|
+
display: 'flex',
|
|
192
|
+
flexDirection: 'column',
|
|
193
|
+
rowGap: '8',
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
export const label = style({
|
|
197
|
+
alignItems: 'center',
|
|
198
|
+
color: 'content.secondary',
|
|
199
|
+
columnGap: '4',
|
|
200
|
+
display: 'flex',
|
|
201
|
+
marginBlockEnd: '8',
|
|
202
|
+
typography: 'body.b3',
|
|
203
|
+
// A 24px info button keeps the label line at its text height.
|
|
204
|
+
selectors: { '& button': { height: '24', marginBlock: '-4px !custom' } },
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
export const legend = style({ color: 'content.primary', typography: 'body.b3Strong' })
|
|
208
|
+
|
|
209
|
+
export const meta = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
|
|
210
|
+
|
|
211
|
+
export const mono = style({ typography: 'mono.m1' })
|
|
212
|
+
|
|
213
|
+
export const page = style({
|
|
214
|
+
backgroundColor: 'background.primary',
|
|
215
|
+
padding: '16',
|
|
216
|
+
'@media 640': { padding: '32' },
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
export const plot = style({
|
|
220
|
+
alignItems: 'flex-end',
|
|
221
|
+
display: 'flex',
|
|
222
|
+
gap: '4',
|
|
223
|
+
height: '160',
|
|
224
|
+
marginBlockEnd: '12',
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
export const quantile = style({ display: 'flex', flexDirection: 'column', rowGap: '8' })
|
|
228
|
+
|
|
229
|
+
export const tiles = style({
|
|
230
|
+
display: 'grid',
|
|
231
|
+
gap: '12',
|
|
232
|
+
gridTemplateColumns: 'repeat(2, minmax(0, 1fr)) !custom',
|
|
233
|
+
margin: '0',
|
|
234
|
+
'@media 768': { gridTemplateColumns: 'repeat(3, minmax(0, 1fr)) !custom' },
|
|
235
|
+
'@media 1280': { gridTemplateColumns: 'repeat(6, minmax(0, 1fr)) !custom' },
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
export const title = style({ margin: '0', typography: 'heading.h2' })
|
|
239
|
+
|
|
240
|
+
export const unit = style({ color: 'content.secondary', typography: 'body.b2' })
|
|
241
|
+
|
|
242
|
+
export const value = style({
|
|
243
|
+
fontVariantNumeric: 'tabular-nums !custom',
|
|
244
|
+
margin: '0',
|
|
245
|
+
typography: 'heading.h2',
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/DashboardsOverview.tsx)
|
|
251
|
+
|
|
252
|
+
## Metric Tiles
|
|
253
|
+
|
|
254
|
+
Each tile holds a label, a value, a unit in a separate smaller span after the value, and an optional info button. Mark up the tiles as a `dl`, with each label in a `dt` and the value and unit together in a `dd`.
|
|
255
|
+
|
|
256
|
+
**Do:** Label every tile and set the unit beside the value.
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
/**
|
|
260
|
+
* Do example for "Dashboards" on the Dashboards page.
|
|
261
|
+
* Label every tile and set the unit beside the value.
|
|
262
|
+
* @module
|
|
263
|
+
*/
|
|
264
|
+
import { style, Surface } from '@tempoxyz/ds'
|
|
265
|
+
|
|
266
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
267
|
+
export function Demo() {
|
|
268
|
+
return (
|
|
269
|
+
<Surface size="small" {...styles.tile()}>
|
|
270
|
+
<p {...styles.label()}>Block time</p>
|
|
271
|
+
<p {...styles.value()}>
|
|
272
|
+
633<span {...styles.unit()}> ms</span>
|
|
273
|
+
</p>
|
|
274
|
+
</Surface>
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
namespace styles {
|
|
279
|
+
export const label = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
|
|
280
|
+
|
|
281
|
+
export const tile = style({
|
|
282
|
+
boxShadow: '0 0 0 1px light-dark(#0000001a, #ffffff1f) !custom',
|
|
283
|
+
width: '10rem !custom',
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
export const unit = style({ color: 'content.secondary', typography: 'body.b2' })
|
|
287
|
+
|
|
288
|
+
export const value = style({
|
|
289
|
+
fontVariantNumeric: 'tabular-nums !custom',
|
|
290
|
+
margin: '0',
|
|
291
|
+
paddingBlockStart: '8',
|
|
292
|
+
typography: 'heading.h2',
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Don't:** Show a bare number without its label or unit.
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
/**
|
|
301
|
+
* Don't example for "Dashboards" on the Dashboards page.
|
|
302
|
+
* Don't show a bare number without its label or unit.
|
|
303
|
+
* @module
|
|
304
|
+
*/
|
|
305
|
+
import { style, Surface } from '@tempoxyz/ds'
|
|
306
|
+
|
|
307
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
308
|
+
export function Demo() {
|
|
309
|
+
return (
|
|
310
|
+
<Surface size="small" {...styles.tile()}>
|
|
311
|
+
<p {...styles.value()}>633</p>
|
|
312
|
+
</Surface>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
namespace styles {
|
|
317
|
+
export const tile = style({
|
|
318
|
+
boxShadow: '0 0 0 1px light-dark(#0000001a, #ffffff1f) !custom',
|
|
319
|
+
width: '10rem !custom',
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
export const value = style({ margin: '0', typography: 'heading.h1' })
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Put Definitions in a Popover
|
|
327
|
+
|
|
328
|
+
A [Tooltip](/docs/components/tooltip) stays closed on touch input, and screen readers do not announce it. Put the definition in a [Popover](/docs/components/popover), and set `openOnHover` on `Popover.Trigger`. Write it in one or two sentences, such as Median settled throughput across 142 measured blocks.
|
|
329
|
+
|
|
330
|
+
### Show Change With Text
|
|
331
|
+
|
|
332
|
+
If a tile compares against a baseline, state the direction and the baseline in text, such as +4.2% vs previous run. Color can reinforce the change but never carries it alone.
|
|
333
|
+
|
|
334
|
+
## Filters
|
|
335
|
+
|
|
336
|
+
* **Short or Medium List (Select):** One choice from a short or medium list, such as the scenario.
|
|
337
|
+
* **Long List (Combobox):** One choice from a long, searchable list, such as 60 run IDs.
|
|
338
|
+
* **Few Short Options (ChoiceGroup):** Two to four short options that readers compare often, such as p50, p95, and p99.
|
|
339
|
+
|
|
340
|
+
**Do:** If a few short options fit, use a [ChoiceGroup](/docs/components/choice-group).
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
/**
|
|
344
|
+
* Do example for "Show Change With Text" on the Dashboards page.
|
|
345
|
+
* If a few short options fit, use a ChoiceGroup.
|
|
346
|
+
* @module
|
|
347
|
+
*/
|
|
348
|
+
import { ChoiceGroup } from '@tempoxyz/ds'
|
|
349
|
+
|
|
350
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
351
|
+
export function Demo() {
|
|
352
|
+
return (
|
|
353
|
+
<ChoiceGroup.Root aria-label="Timing quantile" defaultValue="p95" size="small">
|
|
354
|
+
<ChoiceGroup.Item value="p50">p50</ChoiceGroup.Item>
|
|
355
|
+
<ChoiceGroup.Item value="p95">p95</ChoiceGroup.Item>
|
|
356
|
+
<ChoiceGroup.Item value="p99">p99</ChoiceGroup.Item>
|
|
357
|
+
</ChoiceGroup.Root>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Don't:** Hide three short options behind a [Select](/docs/components/select).
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
/**
|
|
366
|
+
* Don't example for "Show Change With Text" on the Dashboards page.
|
|
367
|
+
* Don't hide three short options behind a Select.
|
|
368
|
+
* @module
|
|
369
|
+
*/
|
|
370
|
+
import { Select, style } from '@tempoxyz/ds'
|
|
371
|
+
|
|
372
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
373
|
+
export function Demo() {
|
|
374
|
+
return (
|
|
375
|
+
<div {...styles.frame()}>
|
|
376
|
+
<Select.Root defaultValue="p95" items={{ p50: 'p50', p95: 'p95', p99: 'p99' }}>
|
|
377
|
+
<Select.Trigger aria-label="Timing quantile">
|
|
378
|
+
<Select.Value />
|
|
379
|
+
<Select.Icon />
|
|
380
|
+
</Select.Trigger>
|
|
381
|
+
</Select.Root>
|
|
382
|
+
</div>
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
namespace styles {
|
|
387
|
+
export const frame = style({ width: '8rem !custom' })
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
* **Visible labels:** Label every filter. Name a [ChoiceGroup](/docs/components/choice-group) with `aria-labelledby`.
|
|
392
|
+
* **Apply on change:** Do not add an Apply button for single-choice controls.
|
|
393
|
+
* **State in the URL:** Keep filter state in the URL, so a shared link opens the same view.
|
|
394
|
+
* **Scope in titles:** Name the active scope in chart titles, such as Block validation (p95).
|
|
395
|
+
* **One control size:** In dense dashboards, use `small` for every control in the row.
|
|
396
|
+
|
|
397
|
+
## Values and Identifiers
|
|
398
|
+
|
|
399
|
+
* **Tabular figures:** Set metric values in tabular figures, so digits keep their width on update. [Amount](/docs/components/amount) and `numeric` table cells apply them already.
|
|
400
|
+
* **Monospace identifiers:** Set commits and run IDs in monospace. [Hex](/docs/components/hex) and `mono` table cells apply it already.
|
|
401
|
+
* **One precision:** Round to the precision readers act on, such as 1.38, not 1.3812. Keep it the same across tiles, charts, and runs.
|
|
402
|
+
* **Larger units:** Switch to a larger unit before values pass four digits, such as 1.38 Ggas.
|
|
403
|
+
|
|
404
|
+
## Chart Containers
|
|
405
|
+
|
|
406
|
+
Frame every chart the same way, whatever library renders it.
|
|
407
|
+
|
|
408
|
+
* **Labelled Section (section):** Render each medium [Surface](/docs/components/surface) as a section, labelled by its title.
|
|
409
|
+
* **Fixed Height (12rem):** Keep the plot area at one height in every state and at every width.
|
|
410
|
+
* **Caption (Units and scope):** Place it below the plot, such as Milliseconds per block, p50.
|
|
411
|
+
* **Legend and Contrast (3:1):** Label series in a legend, and give each color 3:1 contrast. Teal falls short on light surfaces.
|
|
412
|
+
* **Text Alternative (role=img):** Summarize the plot in `aria-label`, such as its range. If readers need the values, add a [Table](/docs/components/table).
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
import { Alert, ChoiceGroup, Empty, Skeleton, Stack, style, Surface } from '@tempoxyz/ds'
|
|
416
|
+
import { ChartBar } from '@tempoxyz/ds/icons'
|
|
417
|
+
import { useState } from 'react'
|
|
418
|
+
|
|
419
|
+
export const fill = true
|
|
420
|
+
const latency = [42, 38, 45, 51, 40, 37, 44, 48, 39, 41, 46, 43, 38, 40]
|
|
421
|
+
|
|
422
|
+
type State = 'empty' | 'error' | 'loaded' | 'loading'
|
|
423
|
+
|
|
424
|
+
export function Demo() {
|
|
425
|
+
const [state, setState] = useState<State>('loaded')
|
|
426
|
+
|
|
427
|
+
return (
|
|
428
|
+
<Stack gap="16">
|
|
429
|
+
<ChoiceGroup.Root
|
|
430
|
+
aria-label="Chart state"
|
|
431
|
+
onValueChange={(value) => setState(value as State)}
|
|
432
|
+
size="small"
|
|
433
|
+
value={state}
|
|
434
|
+
>
|
|
435
|
+
<ChoiceGroup.Item value="loaded">Loaded</ChoiceGroup.Item>
|
|
436
|
+
<ChoiceGroup.Item value="loading">Loading</ChoiceGroup.Item>
|
|
437
|
+
<ChoiceGroup.Item value="empty">Empty</ChoiceGroup.Item>
|
|
438
|
+
<ChoiceGroup.Item value="error">Error</ChoiceGroup.Item>
|
|
439
|
+
</ChoiceGroup.Root>
|
|
440
|
+
<Surface render={<section aria-labelledby="latency-title" />} size="medium">
|
|
441
|
+
<Surface.Header>
|
|
442
|
+
<Surface.Title id="latency-title">New payload validation latency</Surface.Title>
|
|
443
|
+
</Surface.Header>
|
|
444
|
+
{/* The plot area keeps one height in every state, so the grid around it never shifts. */}
|
|
445
|
+
<div {...styles.area()}>
|
|
446
|
+
{state === 'loaded' && (
|
|
447
|
+
<div
|
|
448
|
+
aria-label="Validation latency per block, p50, between 37 and 51 ms"
|
|
449
|
+
role="img"
|
|
450
|
+
{...styles.plot()}
|
|
451
|
+
>
|
|
452
|
+
{latency.map((value, index) => (
|
|
453
|
+
<span key={index} {...styles.bar({ style: { height: `${value * 1.6}%` } })} />
|
|
454
|
+
))}
|
|
455
|
+
</div>
|
|
456
|
+
)}
|
|
457
|
+
{state === 'loading' && <Skeleton {...styles.fill()} />}
|
|
458
|
+
{state === 'empty' && (
|
|
459
|
+
<Empty.Root>
|
|
460
|
+
<Empty.Icon>
|
|
461
|
+
<ChartBar />
|
|
462
|
+
</Empty.Icon>
|
|
463
|
+
<Empty.Title>No blocks in this range</Empty.Title>
|
|
464
|
+
<Empty.Description>Pick a longer range or another run.</Empty.Description>
|
|
465
|
+
</Empty.Root>
|
|
466
|
+
)}
|
|
467
|
+
{state === 'error' && (
|
|
468
|
+
<Alert intent="negative">
|
|
469
|
+
<Alert.Icon />
|
|
470
|
+
<Alert.Content>
|
|
471
|
+
Validator telemetry didn't load.
|
|
472
|
+
<Alert.Action>Retry</Alert.Action>
|
|
473
|
+
</Alert.Content>
|
|
474
|
+
</Alert>
|
|
475
|
+
)}
|
|
476
|
+
</div>
|
|
477
|
+
<p role="status" {...styles.caption()}>
|
|
478
|
+
{state === 'loading' ? 'Loading chart data' : 'Milliseconds per block, p50'}
|
|
479
|
+
</p>
|
|
480
|
+
</Surface>
|
|
481
|
+
</Stack>
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
namespace styles {
|
|
486
|
+
export const area = style({
|
|
487
|
+
alignItems: 'center',
|
|
488
|
+
display: 'flex',
|
|
489
|
+
height: '200',
|
|
490
|
+
justifyContent: 'center',
|
|
491
|
+
marginBlockEnd: '12',
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
export const bar = style({
|
|
495
|
+
backgroundColor: 'accent.blue.background',
|
|
496
|
+
borderTopLeftRadius: '3xs',
|
|
497
|
+
borderTopRightRadius: '3xs',
|
|
498
|
+
flex: '1 !custom',
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
|
|
502
|
+
|
|
503
|
+
export const fill = style({ alignSelf: 'stretch', flex: '1 !custom' })
|
|
504
|
+
|
|
505
|
+
export const plot = style({
|
|
506
|
+
alignItems: 'flex-end',
|
|
507
|
+
alignSelf: 'stretch',
|
|
508
|
+
display: 'flex',
|
|
509
|
+
flex: '1 !custom',
|
|
510
|
+
gap: '4',
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/DashboardsChartStates.tsx)
|
|
516
|
+
|
|
517
|
+
## Grid Reflow
|
|
518
|
+
|
|
519
|
+
Lay out tiles in a [Grid](/docs/components/grid) with `fit="small"` and charts with `fit="large"`, which keeps each chart at least 20rem wide. Neither needs a media query. On narrow screens, stack charts instead of shrinking them. [Responsive Layout](/docs/patterns/responsive-layout#app-shell-with-content) covers the full-width shell.
|
|
520
|
+
|
|
521
|
+
## States
|
|
522
|
+
|
|
523
|
+
* **Empty (Empty):** Inside the plot area, state why and what to try next, such as No blocks in this range.
|
|
524
|
+
* **Error (negative):** Place an [Alert](/docs/components/alert) with a Retry action inside the failed chart. Other charts keep working.
|
|
525
|
+
* **Partial Data:** Name what is missing, such as Loading validator telemetry, instead of hiding the section.
|
|
526
|
+
* **Updating:** Show when data last changed, such as Updated 30 s ago. Update values in place without layout shift.
|
|
527
|
+
|
|
528
|
+
**Do:** Show titles at once, and hold the plot height with a [Skeleton](/docs/components/skeleton) while
|
|
529
|
+
data loads.
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
/**
|
|
533
|
+
* Do example for "Show Change With Text" on the Dashboards page.
|
|
534
|
+
* Show titles at once, and hold the plot height with a Skeleton while data loads.
|
|
535
|
+
* @module
|
|
536
|
+
*/
|
|
537
|
+
import { Skeleton, style, Surface } from '@tempoxyz/ds'
|
|
538
|
+
|
|
539
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
540
|
+
export function Demo() {
|
|
541
|
+
return (
|
|
542
|
+
<Surface size="medium" {...styles.card()}>
|
|
543
|
+
<Surface.Header>
|
|
544
|
+
<Surface.Title>Gas throughput</Surface.Title>
|
|
545
|
+
</Surface.Header>
|
|
546
|
+
<Skeleton {...styles.plot()} />
|
|
547
|
+
</Surface>
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
namespace styles {
|
|
552
|
+
export const card = style({
|
|
553
|
+
boxShadow: '0 0 0 1px light-dark(#0000001a, #ffffff1f) !custom',
|
|
554
|
+
width: '16rem !custom',
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
export const plot = style({ height: '120' })
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Don't:** Collapse the chart to a [Spinner](/docs/components/spinner), which shifts every section
|
|
562
|
+
below it.
|
|
563
|
+
|
|
564
|
+
```tsx
|
|
565
|
+
/**
|
|
566
|
+
* Don't example for "Show Change With Text" on the Dashboards page.
|
|
567
|
+
* Don't collapse the chart to a Spinner, which shifts every section below it.
|
|
568
|
+
* @module
|
|
569
|
+
*/
|
|
570
|
+
import { Spinner, style, Surface } from '@tempoxyz/ds'
|
|
571
|
+
|
|
572
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
573
|
+
export function Demo() {
|
|
574
|
+
return (
|
|
575
|
+
<Surface size="medium" {...styles.card()}>
|
|
576
|
+
<Surface.Header>
|
|
577
|
+
<Surface.Title>Gas throughput</Surface.Title>
|
|
578
|
+
<Spinner aria-label="Loading gas throughput" />
|
|
579
|
+
</Surface.Header>
|
|
580
|
+
</Surface>
|
|
581
|
+
)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
namespace styles {
|
|
585
|
+
export const card = style({
|
|
586
|
+
boxShadow: '0 0 0 1px light-dark(#0000001a, #ffffff1f) !custom',
|
|
587
|
+
width: '16rem !custom',
|
|
588
|
+
})
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
## Accessibility
|
|
593
|
+
|
|
594
|
+
* **Description Lists (dl):** Screen readers pair each value with its label, and read the unit with the value.
|
|
595
|
+
* **Info Buttons (About Block time):** Name each by its metric. Give it more room on touch screens.
|
|
596
|
+
* **Announce Once (status):** Announce loading and errors once, through a status region or a [Alert](/docs/components/alert). Do not announce every live update.
|
|
597
|
+
|
|
598
|
+
## Related
|
|
599
|
+
|
|
600
|
+
[Surface](/docs/components/surface)
|
|
601
|
+
|
|
602
|
+
Use for tiles and chart containers.
|
|
603
|
+
|
|
604
|
+
[Grid](/docs/components/grid)
|
|
605
|
+
|
|
606
|
+
Use for reflowing tiles and charts.
|
|
607
|
+
|
|
608
|
+
[ChoiceGroup](/docs/components/choice-group)
|
|
609
|
+
|
|
610
|
+
Use for a few short filter options.
|
|
611
|
+
|
|
612
|
+
[Select](/docs/components/select)
|
|
613
|
+
|
|
614
|
+
Use for filters with a medium list.
|
|
615
|
+
|
|
616
|
+
[Combobox](/docs/components/combobox)
|
|
617
|
+
|
|
618
|
+
Use for filters with a long list.
|
|
619
|
+
|
|
620
|
+
[Popover](/docs/components/popover)
|
|
621
|
+
|
|
622
|
+
Use for metric definitions.
|
|
623
|
+
|
|
624
|
+
[Skeleton](/docs/components/skeleton)
|
|
625
|
+
|
|
626
|
+
Use for loading plots.
|
|
627
|
+
|
|
628
|
+
[Responsive Layout](/docs/patterns/responsive-layout)
|
|
629
|
+
|
|
630
|
+
Covers the shared breakpoints.
|
|
631
|
+
|
|
632
|
+
[Tables and Lists](/docs/patterns/tables-and-lists)
|
|
633
|
+
|
|
634
|
+
Covers exact values.
|
|
635
|
+
|
|
636
|
+
[Tempo Performance](/docs/products/tempo-performance)
|
|
637
|
+
|
|
638
|
+
Covers the benchmark dashboard.
|