@tempoxyz/ds 0.3.10 → 0.3.13

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 (210) hide show
  1. package/README.md +7 -6
  2. package/dist/docs/AGENTS.md +12 -0
  3. package/dist/docs/index.md +145 -0
  4. package/dist/docs/marketing/introduction/getting-started.md +241 -0
  5. package/dist/docs/product/components/accordion.md +489 -0
  6. package/dist/docs/product/components/address-card.md +147 -0
  7. package/dist/docs/product/components/agent-logo.md +194 -0
  8. package/dist/docs/product/components/alert-dialog.md +631 -0
  9. package/dist/docs/product/components/alert.md +448 -0
  10. package/dist/docs/product/components/amount-input.md +305 -0
  11. package/dist/docs/product/components/amount.md +794 -0
  12. package/dist/docs/product/components/app-shell.md +559 -0
  13. package/dist/docs/product/components/asset-chip.md +294 -0
  14. package/dist/docs/product/components/autocomplete.md +620 -0
  15. package/dist/docs/product/components/avatar.md +321 -0
  16. package/dist/docs/product/components/badge.md +339 -0
  17. package/dist/docs/product/components/badged-logo.md +116 -0
  18. package/dist/docs/product/components/banner.md +379 -0
  19. package/dist/docs/product/components/bar-chart.md +208 -0
  20. package/dist/docs/product/components/breadcrumbs.md +380 -0
  21. package/dist/docs/product/components/button-group.md +286 -0
  22. package/dist/docs/product/components/button.md +650 -0
  23. package/dist/docs/product/components/calendar.md +375 -0
  24. package/dist/docs/product/components/callout.md +96 -0
  25. package/dist/docs/product/components/checkbox-group.md +521 -0
  26. package/dist/docs/product/components/checkbox.md +467 -0
  27. package/dist/docs/product/components/choice-group.md +516 -0
  28. package/dist/docs/product/components/choicebox.md +641 -0
  29. package/dist/docs/product/components/code.md +100 -0
  30. package/dist/docs/product/components/combobox.md +939 -0
  31. package/dist/docs/product/components/container.md +257 -0
  32. package/dist/docs/product/components/copy-button.md +642 -0
  33. package/dist/docs/product/components/date-picker.md +514 -0
  34. package/dist/docs/product/components/dialog.md +1332 -0
  35. package/dist/docs/product/components/drawer.md +966 -0
  36. package/dist/docs/product/components/empty.md +340 -0
  37. package/dist/docs/product/components/field.md +570 -0
  38. package/dist/docs/product/components/fieldset.md +398 -0
  39. package/dist/docs/product/components/frame.md +434 -0
  40. package/dist/docs/product/components/gauge.md +299 -0
  41. package/dist/docs/product/components/get-started-visual.md +117 -0
  42. package/dist/docs/product/components/grid.md +288 -0
  43. package/dist/docs/product/components/hero-visual.md +155 -0
  44. package/dist/docs/product/components/hex.md +487 -0
  45. package/dist/docs/product/components/icon-tile.md +292 -0
  46. package/dist/docs/product/components/identicon.md +371 -0
  47. package/dist/docs/product/components/inline.md +233 -0
  48. package/dist/docs/product/components/inset.md +225 -0
  49. package/dist/docs/product/components/kbd.md +423 -0
  50. package/dist/docs/product/components/list-group.md +981 -0
  51. package/dist/docs/product/components/loading-dots.md +237 -0
  52. package/dist/docs/product/components/menu.md +934 -0
  53. package/dist/docs/product/components/native-select.md +204 -0
  54. package/dist/docs/product/components/network-logo.md +248 -0
  55. package/dist/docs/product/components/number-field.md +543 -0
  56. package/dist/docs/product/components/otp-field.md +616 -0
  57. package/dist/docs/product/components/pagination.md +516 -0
  58. package/dist/docs/product/components/popover.md +544 -0
  59. package/dist/docs/product/components/progress.md +388 -0
  60. package/dist/docs/product/components/qr.md +314 -0
  61. package/dist/docs/product/components/radio-group.md +528 -0
  62. package/dist/docs/product/components/secret-value.md +406 -0
  63. package/dist/docs/product/components/select.md +751 -0
  64. package/dist/docs/product/components/separator.md +379 -0
  65. package/dist/docs/product/components/sidebar-menu.md +922 -0
  66. package/dist/docs/product/components/skeleton.md +517 -0
  67. package/dist/docs/product/components/slider.md +539 -0
  68. package/dist/docs/product/components/snippet.md +424 -0
  69. package/dist/docs/product/components/spinner.md +216 -0
  70. package/dist/docs/product/components/split-bar.md +139 -0
  71. package/dist/docs/product/components/split-button.md +464 -0
  72. package/dist/docs/product/components/stack.md +299 -0
  73. package/dist/docs/product/components/status-chip.md +226 -0
  74. package/dist/docs/product/components/status-indicator.md +119 -0
  75. package/dist/docs/product/components/status-row.md +174 -0
  76. package/dist/docs/product/components/stepper.md +451 -0
  77. package/dist/docs/product/components/success-hero.md +157 -0
  78. package/dist/docs/product/components/surface.md +639 -0
  79. package/dist/docs/product/components/switch.md +472 -0
  80. package/dist/docs/product/components/table.md +967 -0
  81. package/dist/docs/product/components/tabs.md +627 -0
  82. package/dist/docs/product/components/tempo-logo.md +185 -0
  83. package/dist/docs/product/components/text-input.md +624 -0
  84. package/dist/docs/product/components/textarea.md +352 -0
  85. package/dist/docs/product/components/toast.md +535 -0
  86. package/dist/docs/product/components/token-logo.md +371 -0
  87. package/dist/docs/product/components/token.md +247 -0
  88. package/dist/docs/product/components/tooltip.md +490 -0
  89. package/dist/docs/product/components/verification-code.md +116 -0
  90. package/dist/docs/product/components/visual.md +249 -0
  91. package/dist/docs/product/components.md +203 -0
  92. package/dist/docs/product/foundations/accessibility.md +377 -0
  93. package/dist/docs/product/foundations/agents.md +150 -0
  94. package/dist/docs/product/foundations/color.md +522 -0
  95. package/dist/docs/product/foundations/content/tone.md +194 -0
  96. package/dist/docs/product/foundations/content/voice.md +194 -0
  97. package/dist/docs/product/foundations/content/writing-style.md +406 -0
  98. package/dist/docs/product/foundations/iconography.md +282 -0
  99. package/dist/docs/product/foundations/intent.md +360 -0
  100. package/dist/docs/product/foundations/layout.md +508 -0
  101. package/dist/docs/product/foundations/linter.md +496 -0
  102. package/dist/docs/product/foundations/motion.md +151 -0
  103. package/dist/docs/product/foundations/radius-and-padding.md +86 -0
  104. package/dist/docs/product/foundations/typography.md +266 -0
  105. package/dist/docs/product/introduction/faq.md +133 -0
  106. package/dist/docs/product/introduction/getting-started.md +377 -0
  107. package/dist/docs/product/patterns/blockchain-values.md +709 -0
  108. package/dist/docs/product/patterns/dashboards.md +638 -0
  109. package/dist/docs/product/patterns/destructive-actions.md +626 -0
  110. package/dist/docs/product/patterns/empty-states.md +612 -0
  111. package/dist/docs/product/patterns/errors-and-validation.md +808 -0
  112. package/dist/docs/product/patterns/feedback-and-status.md +518 -0
  113. package/dist/docs/product/patterns/forms.md +679 -0
  114. package/dist/docs/product/patterns/loading.md +630 -0
  115. package/dist/docs/product/patterns/navigation.md +397 -0
  116. package/dist/docs/product/patterns/payments.md +765 -0
  117. package/dist/docs/product/patterns/responsive-layout.md +997 -0
  118. package/dist/docs/product/patterns/tables-and-lists.md +964 -0
  119. package/dist/docs/product/products/tempo-console.md +671 -0
  120. package/dist/docs/product/products/tempo-performance.md +437 -0
  121. package/dist/docs/product/products/tempo-wallet.md +438 -0
  122. package/dist/marketing/index.d.ts +4 -0
  123. package/dist/marketing/index.d.ts.map +1 -0
  124. package/dist/marketing/index.js +4 -0
  125. package/dist/marketing/index.js.map +1 -0
  126. package/dist/marketing/index.js.zyzz.json +1 -0
  127. package/dist/marketing/vars.d.ts +907 -0
  128. package/dist/marketing/vars.d.ts.map +1 -0
  129. package/dist/marketing/vars.js +15 -0
  130. package/dist/marketing/vars.js.map +1 -0
  131. package/dist/marketing/vars.js.zyzz.json +1 -0
  132. package/dist/marketing/zyzz.config.d.ts +4512 -0
  133. package/dist/marketing/zyzz.config.d.ts.map +1 -0
  134. package/dist/marketing/zyzz.config.js +63 -0
  135. package/dist/marketing/zyzz.config.js.map +1 -0
  136. package/dist/marketing/zyzz.config.js.zyzz.json +1 -0
  137. package/dist/oxlint/index.d.ts +12 -1
  138. package/dist/oxlint/index.d.ts.map +1 -1
  139. package/dist/oxlint/index.js +39 -1
  140. package/dist/oxlint/index.js.map +1 -1
  141. package/dist/oxlint/internal/Docs.d.ts +9 -0
  142. package/dist/oxlint/internal/Docs.d.ts.map +1 -0
  143. package/dist/oxlint/internal/Docs.js +15 -0
  144. package/dist/oxlint/internal/Docs.js.map +1 -0
  145. package/dist/oxlint/internal/Imports.d.ts +1 -1
  146. package/dist/oxlint/internal/Imports.d.ts.map +1 -1
  147. package/dist/oxlint/internal/Imports.js +2 -0
  148. package/dist/oxlint/internal/Imports.js.map +1 -1
  149. package/dist/oxlint/internal/Styles.d.ts +40 -0
  150. package/dist/oxlint/internal/Styles.d.ts.map +1 -0
  151. package/dist/oxlint/internal/Styles.js +194 -0
  152. package/dist/oxlint/internal/Styles.js.map +1 -0
  153. package/dist/oxlint/internal/Tokens.d.ts +16 -0
  154. package/dist/oxlint/internal/Tokens.d.ts.map +1 -0
  155. package/dist/oxlint/internal/Tokens.js +39 -0
  156. package/dist/oxlint/internal/Tokens.js.map +1 -0
  157. package/dist/oxlint/rules/NoCrossSurface.d.ts +2 -2
  158. package/dist/oxlint/rules/NoCrossSurface.js +3 -3
  159. package/dist/oxlint/rules/NoCrossSurface.js.map +1 -1
  160. package/dist/oxlint/rules/NoCustomValues.d.ts +11 -0
  161. package/dist/oxlint/rules/NoCustomValues.d.ts.map +1 -0
  162. package/dist/oxlint/rules/NoCustomValues.js +150 -0
  163. package/dist/oxlint/rules/NoCustomValues.js.map +1 -0
  164. package/dist/oxlint/rules/NoSingleKeyShortcuts.d.ts +11 -0
  165. package/dist/oxlint/rules/NoSingleKeyShortcuts.d.ts.map +1 -0
  166. package/dist/oxlint/rules/NoSingleKeyShortcuts.js +158 -0
  167. package/dist/oxlint/rules/NoSingleKeyShortcuts.js.map +1 -0
  168. package/dist/oxlint/rules/PreferComponent.d.ts +3 -2
  169. package/dist/oxlint/rules/PreferComponent.d.ts.map +1 -1
  170. package/dist/oxlint/rules/PreferComponent.js +11 -4
  171. package/dist/oxlint/rules/PreferComponent.js.map +1 -1
  172. package/dist/oxlint/rules/PreferLayout.d.ts +9 -0
  173. package/dist/oxlint/rules/PreferLayout.d.ts.map +1 -0
  174. package/dist/oxlint/rules/PreferLayout.js +90 -0
  175. package/dist/oxlint/rules/PreferLayout.js.map +1 -0
  176. package/dist/oxlint/rules/PreferProgress.d.ts +9 -0
  177. package/dist/oxlint/rules/PreferProgress.d.ts.map +1 -0
  178. package/dist/oxlint/rules/PreferProgress.js +43 -0
  179. package/dist/oxlint/rules/PreferProgress.js.map +1 -0
  180. package/dist/oxlint/rules/PreferSurface.d.ts +9 -0
  181. package/dist/oxlint/rules/PreferSurface.d.ts.map +1 -0
  182. package/dist/oxlint/rules/PreferSurface.js +55 -0
  183. package/dist/oxlint/rules/PreferSurface.js.map +1 -0
  184. package/dist/oxlint/rules/RequireAccessibleName.d.ts.map +1 -1
  185. package/dist/oxlint/rules/RequireAccessibleName.js +3 -1
  186. package/dist/oxlint/rules/RequireAccessibleName.js.map +1 -1
  187. package/dist/oxlint/rules/RequireScrollRegion.d.ts +11 -0
  188. package/dist/oxlint/rules/RequireScrollRegion.d.ts.map +1 -0
  189. package/dist/oxlint/rules/RequireScrollRegion.js +71 -0
  190. package/dist/oxlint/rules/RequireScrollRegion.js.map +1 -0
  191. package/dist/web/icons/index.js.zyzz.json +1 -0
  192. package/package.json +6 -1
  193. package/src/marketing/README.md +21 -0
  194. package/src/marketing/index.ts +3 -0
  195. package/src/marketing/vars.ts +751 -0
  196. package/src/marketing/zyzz.config.ts +376 -0
  197. package/src/oxlint/index.ts +40 -1
  198. package/src/oxlint/internal/Docs.ts +18 -0
  199. package/src/oxlint/internal/Imports.ts +2 -1
  200. package/src/oxlint/internal/Styles.ts +240 -0
  201. package/src/oxlint/internal/Tokens.ts +43 -0
  202. package/src/oxlint/rules/NoCrossSurface.ts +3 -3
  203. package/src/oxlint/rules/NoCustomValues.ts +170 -0
  204. package/src/oxlint/rules/NoSingleKeyShortcuts.ts +190 -0
  205. package/src/oxlint/rules/PreferComponent.ts +11 -4
  206. package/src/oxlint/rules/PreferLayout.ts +96 -0
  207. package/src/oxlint/rules/PreferProgress.ts +49 -0
  208. package/src/oxlint/rules/PreferSurface.ts +70 -0
  209. package/src/oxlint/rules/RequireAccessibleName.ts +3 -1
  210. package/src/oxlint/rules/RequireScrollRegion.ts +92 -0
@@ -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](../products/tempo-performance.md) 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](../components/badge.md) 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](../components/surface.md) tiles, most important first.
19
+ * **Chart Sections (4):** One [Surface](../components/surface.md) per chart, under section headings such as Throughput.
20
+ * **Detail Tables (5):** A [Table](../components/table.md) 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](../components/tooltip.md) stays closed on touch input, and screen readers do not announce it. Put the definition in a [Popover](../components/popover.md), 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](../components/choice-group.md).
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](../components/select.md).
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](../components/choice-group.md) 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](../components/amount.md) and `numeric` table cells apply them already.
400
+ * **Monospace identifiers:** Set commits and run IDs in monospace. [Hex](../components/hex.md) 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](../components/surface.md) 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](../components/table.md).
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](../components/grid.md) 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](responsive-layout.md#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](../components/alert.md) 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](../components/skeleton.md) 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](../components/spinner.md), 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](../components/alert.md). Do not announce every live update.
597
+
598
+ ## Related
599
+
600
+ [Surface](../components/surface.md)
601
+
602
+ Use for tiles and chart containers.
603
+
604
+ [Grid](../components/grid.md)
605
+
606
+ Use for reflowing tiles and charts.
607
+
608
+ [ChoiceGroup](../components/choice-group.md)
609
+
610
+ Use for a few short filter options.
611
+
612
+ [Select](../components/select.md)
613
+
614
+ Use for filters with a medium list.
615
+
616
+ [Combobox](../components/combobox.md)
617
+
618
+ Use for filters with a long list.
619
+
620
+ [Popover](../components/popover.md)
621
+
622
+ Use for metric definitions.
623
+
624
+ [Skeleton](../components/skeleton.md)
625
+
626
+ Use for loading plots.
627
+
628
+ [Responsive Layout](responsive-layout.md)
629
+
630
+ Covers the shared breakpoints.
631
+
632
+ [Tables and Lists](tables-and-lists.md)
633
+
634
+ Covers exact values.
635
+
636
+ [Tempo Performance](../products/tempo-performance.md)
637
+
638
+ Covers the benchmark dashboard.