@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,671 @@
1
+ # Tempo Console
2
+
3
+ Tempo Console is the web app for Tempo API projects, keys, usage, and billing.
4
+
5
+ > [!NOTE]
6
+ > The reference layouts on this page are compositions of library components and `style`, not shipped components. `@tempoxyz/ds` exports no `ApiKeysPage` or `CreateKeyForm`. Copy a layout into your app and adapt it.
7
+
8
+ ## Overview
9
+
10
+ Tempo Console runs at [console.tempo.xyz](https://console.tempo.xyz) and is invite-only. You log in with an email address, a Google account, or a passkey, then choose an organization.
11
+
12
+ * **Organization:** Holds the team, projects, billing, and aggregate usage.
13
+ * **Project:** Holds one app or integration and its API keys.
14
+ * **Environment:** Separates production and sandbox keys, usage, and billing.
15
+ * **API Key:** A project-scoped credential for calling Tempo APIs.
16
+
17
+ Roles:
18
+
19
+ * **Developers:** Create keys, test in sandbox, and see project usage.
20
+ * **Owners:** Manage billing, change roles, and remove members.
21
+ * **Admins:** Invite members and review pending invitations.
22
+
23
+ ## Surfaces
24
+
25
+ The console is a web app built with `@tempoxyz/ds`. No mobile app is documented.
26
+
27
+ The Tempo API exposes the same organization resources through management endpoints. Keys with the `management:read` and `management:write` scopes can call them.
28
+
29
+ ## Key Screens
30
+
31
+ ### API Keys
32
+
33
+ ```tsx
34
+ import { AlertDialog, AppShell, Banner, Button, ButtonGroup, Empty, Identicon, Menu, Select, SidebarMenu, style, Surface, Table } from '@tempoxyz/ds'
35
+ import {
36
+ Analytics,
37
+ CreditCard,
38
+ Folder,
39
+ Key,
40
+ LogOut,
41
+ MoreHorizontal,
42
+ Plus,
43
+ Sort,
44
+ Users,
45
+ } from '@tempoxyz/ds/icons'
46
+ import { useState } from 'react'
47
+
48
+ export const fill = true
49
+ type Environment = 'production' | 'sandbox'
50
+
51
+ const prefixes = { production: 'tempo:sk:', sandbox: 'tempo_sandbox:sk:' }
52
+
53
+ const projects = {
54
+ all: 'All projects',
55
+ 'checkout-service': 'checkout-service',
56
+ 'payouts-worker': 'payouts-worker',
57
+ }
58
+
59
+ const keys = [
60
+ {
61
+ created: 'Oct 14, 2026',
62
+ environment: 'production',
63
+ id: 'key_01',
64
+ last4: 'a91c',
65
+ lastUsed: '2 minutes ago',
66
+ name: 'checkout-service production',
67
+ project: 'checkout-service',
68
+ },
69
+ {
70
+ created: 'Sep 30, 2026',
71
+ environment: 'production',
72
+ id: 'key_02',
73
+ last4: '07e1',
74
+ lastUsed: 'Yesterday',
75
+ name: 'payouts-worker production',
76
+ project: 'payouts-worker',
77
+ },
78
+ {
79
+ created: 'Oct 18, 2026',
80
+ environment: 'sandbox',
81
+ id: 'key_03',
82
+ last4: '5a22',
83
+ lastUsed: '1 hour ago',
84
+ name: 'checkout-service CI',
85
+ project: 'checkout-service',
86
+ },
87
+ {
88
+ created: 'Oct 19, 2026',
89
+ environment: 'sandbox',
90
+ id: 'key_04',
91
+ last4: 'c0d9',
92
+ lastUsed: 'Never',
93
+ name: 'jake local development',
94
+ project: 'payouts-worker',
95
+ },
96
+ ]
97
+
98
+ export function Demo() {
99
+ const [environment, setEnvironment] = useState<Environment>('sandbox')
100
+ const [project, setProject] = useState<string | null>('all')
101
+ const [revoked, setRevoked] = useState<readonly string[]>([])
102
+ const [target, setTarget] = useState<(typeof keys)[number]>()
103
+
104
+ const visible = keys.filter(
105
+ (key) =>
106
+ key.environment === environment &&
107
+ !revoked.includes(key.id) &&
108
+ (project === 'all' || key.project === project),
109
+ )
110
+
111
+ return (
112
+ <div {...styles.frame()}>
113
+ <AppShell.Root style={{ height: '100%', minHeight: 0, overflowY: 'auto' }}>
114
+ <AppShell.Sidebar aria-label="Console" style={{ height: '100%' }}>
115
+ <AppShell.SidebarHeader>
116
+ <Menu.Root>
117
+ <Menu.Trigger
118
+ aria-label="Organization and environment"
119
+ render={<SidebarMenu.Item render={<button type="button" />} />}
120
+ >
121
+ <Identicon
122
+ address="0x71C7656EC7ab88b098defB751B7401B5f6d8976F"
123
+ aria-hidden
124
+ size="32"
125
+ />
126
+ <span {...styles.stack()}>
127
+ <span {...styles.name()}>Acme Payments</span>
128
+ <span {...styles.caption()}>
129
+ {environment === 'sandbox' ? 'Sandbox' : 'Production'}
130
+ </span>
131
+ </span>
132
+ <Sort />
133
+ </Menu.Trigger>
134
+ <Menu.Popup align="start">
135
+ <Menu.Group>
136
+ <Menu.GroupLabel>Environment</Menu.GroupLabel>
137
+ <Menu.RadioGroup
138
+ onValueChange={(value) => setEnvironment(value as Environment)}
139
+ value={environment}
140
+ >
141
+ <Menu.RadioItem value="production">Production</Menu.RadioItem>
142
+ <Menu.RadioItem value="sandbox">Sandbox</Menu.RadioItem>
143
+ </Menu.RadioGroup>
144
+ </Menu.Group>
145
+ </Menu.Popup>
146
+ </Menu.Root>
147
+ </AppShell.SidebarHeader>
148
+ <AppShell.SidebarBody>
149
+ <SidebarMenu.Root>
150
+ <SidebarMenu.Group aria-label="Organization">
151
+ <SidebarMenu.Item href="#projects">
152
+ <Folder />
153
+ Projects
154
+ </SidebarMenu.Item>
155
+ <SidebarMenu.Item active href="#api-keys">
156
+ <Key />
157
+ API Keys
158
+ </SidebarMenu.Item>
159
+ <SidebarMenu.Item href="#usage">
160
+ <Analytics />
161
+ Usage
162
+ </SidebarMenu.Item>
163
+ <SidebarMenu.Item href="#billing">
164
+ <CreditCard />
165
+ Billing
166
+ </SidebarMenu.Item>
167
+ <SidebarMenu.Item href="#team">
168
+ <Users />
169
+ Team
170
+ </SidebarMenu.Item>
171
+ </SidebarMenu.Group>
172
+ </SidebarMenu.Root>
173
+ </AppShell.SidebarBody>
174
+ <AppShell.SidebarFooter>
175
+ <Menu.Root>
176
+ <Menu.Trigger
177
+ aria-label="Account menu"
178
+ render={<SidebarMenu.Item render={<button type="button" />} />}
179
+ >
180
+ <Identicon
181
+ address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
182
+ aria-hidden
183
+ size="24"
184
+ />
185
+ <span {...styles.grow()}>jake@acme.dev</span>
186
+ </Menu.Trigger>
187
+ <Menu.Popup side="top">
188
+ <Menu.Item>
189
+ <LogOut />
190
+ Log out
191
+ </Menu.Item>
192
+ </Menu.Popup>
193
+ </Menu.Root>
194
+ </AppShell.SidebarFooter>
195
+ </AppShell.Sidebar>
196
+
197
+ <AppShell.Main>
198
+ <AppShell.Header>
199
+ <AppShell.Title>API Keys</AppShell.Title>
200
+ <div {...styles.actions()}>
201
+ <Select.Root items={projects} onValueChange={setProject} value={project}>
202
+ <Select.Trigger aria-label="Project">
203
+ <Select.Value />
204
+ <Select.Icon />
205
+ </Select.Trigger>
206
+ <Select.Popup size="small">
207
+ {Object.entries(projects).map(([value, label]) => (
208
+ <Select.Item key={value} label={label} value={value}>
209
+ <Select.ItemText>{label}</Select.ItemText>
210
+ </Select.Item>
211
+ ))}
212
+ </Select.Popup>
213
+ </Select.Root>
214
+ <Button size="small">
215
+ <Plus />
216
+ New key
217
+ </Button>
218
+ <AppShell.Trigger />
219
+ </div>
220
+ </AppShell.Header>
221
+
222
+ {environment === 'sandbox' && (
223
+ <Banner intent="warning" {...styles.banner()}>
224
+ <Banner.Content>
225
+ Sandbox. Keys here reach only non-mainnet chains, such as testnet.
226
+ </Banner.Content>
227
+ </Banner>
228
+ )}
229
+
230
+ <Surface>
231
+ {visible.length === 0 ? (
232
+ <Empty.Root>
233
+ <Empty.Icon>
234
+ <Key />
235
+ </Empty.Icon>
236
+ <Empty.Title>No API keys</Empty.Title>
237
+ <Empty.Description>
238
+ Create a key to call Tempo APIs from this project.
239
+ </Empty.Description>
240
+ <Empty.Actions>
241
+ <Button size="small">New key</Button>
242
+ </Empty.Actions>
243
+ </Empty.Root>
244
+ ) : (
245
+ <div {...styles.scroll()}>
246
+ <Table.Root>
247
+ <Table.Caption>
248
+ {environment === 'sandbox' ? 'Sandbox' : 'Production'} keys,{' '}
249
+ {project === 'all' ? 'all projects' : project}
250
+ </Table.Caption>
251
+ <Table.Head>
252
+ <Table.Row>
253
+ <Table.HeaderCell>Name</Table.HeaderCell>
254
+ <Table.HeaderCell>Project</Table.HeaderCell>
255
+ <Table.HeaderCell>Key</Table.HeaderCell>
256
+ <Table.HeaderCell>Created</Table.HeaderCell>
257
+ <Table.HeaderCell>Last used</Table.HeaderCell>
258
+ <Table.HeaderCell justify="end">
259
+ <span {...styles.hidden()}>Actions</span>
260
+ </Table.HeaderCell>
261
+ </Table.Row>
262
+ </Table.Head>
263
+ <Table.Body>
264
+ {visible.map((key) => (
265
+ <Table.Row key={key.id}>
266
+ <Table.Cell>{key.name}</Table.Cell>
267
+ <Table.Cell>{key.project}</Table.Cell>
268
+ <Table.Cell mono>
269
+ {prefixes[environment]}…{key.last4}
270
+ </Table.Cell>
271
+ <Table.Cell>{key.created}</Table.Cell>
272
+ <Table.Cell>{key.lastUsed}</Table.Cell>
273
+ <Table.Cell justify="end">
274
+ <Menu.Root>
275
+ <Menu.Trigger
276
+ aria-label={`Actions for ${key.name}`}
277
+ render={<Button shape="icon" size="small" variant="tertiary" />}
278
+ >
279
+ <MoreHorizontal />
280
+ </Menu.Trigger>
281
+ <Menu.Popup align="end">
282
+ <Menu.Item intent="negative" onClick={() => setTarget(key)}>
283
+ Revoke key
284
+ </Menu.Item>
285
+ </Menu.Popup>
286
+ </Menu.Root>
287
+ </Table.Cell>
288
+ </Table.Row>
289
+ ))}
290
+ </Table.Body>
291
+ </Table.Root>
292
+ </div>
293
+ )}
294
+ </Surface>
295
+ </AppShell.Main>
296
+ </AppShell.Root>
297
+
298
+ <AlertDialog.Root
299
+ onOpenChange={(open) => {
300
+ if (!open) setTarget(undefined)
301
+ }}
302
+ open={target !== undefined}
303
+ >
304
+ <AlertDialog.Popup>
305
+ <AlertDialog.Header>
306
+ <AlertDialog.Title>Revoke {target?.name}?</AlertDialog.Title>
307
+ <AlertDialog.Description>
308
+ Requests that use this key stop authenticating. You can't undo this.
309
+ </AlertDialog.Description>
310
+ </AlertDialog.Header>
311
+ <ButtonGroup>
312
+ <AlertDialog.Close render={<Button size="large" variant="secondary" />}>
313
+ Keep key
314
+ </AlertDialog.Close>
315
+ <AlertDialog.Close
316
+ onClick={() => {
317
+ if (target) setRevoked([...revoked, target.id])
318
+ }}
319
+ render={<Button size="large" />}
320
+ >
321
+ Revoke key
322
+ </AlertDialog.Close>
323
+ </ButtonGroup>
324
+ </AlertDialog.Popup>
325
+ </AlertDialog.Root>
326
+ </div>
327
+ )
328
+ }
329
+
330
+ namespace styles {
331
+ export const actions = style({ alignItems: 'center', columnGap: '8', display: 'flex' })
332
+
333
+ export const banner = style({ marginBlockEnd: '16' })
334
+
335
+ export const caption = style({ color: 'content.secondary', margin: '0', typography: 'body.b3' })
336
+
337
+ // The transform makes the frame the containing block for the mobile drawer.
338
+ export const frame = style({
339
+ borderRadius: 'sm',
340
+ height: '36rem !custom',
341
+ overflow: 'hidden',
342
+ transform: 'translateZ(0) !custom',
343
+ selectors: {
344
+ // The maximized gallery preview shows the shell at full window height.
345
+ ':root[data-maximized] &': { borderRadius: '0px !custom', height: '100vh !custom' },
346
+ },
347
+ })
348
+
349
+ export const grow = style({ flex: '1 !custom', minWidth: '0 !custom' })
350
+
351
+ // Visually hidden text still names the column for screen readers.
352
+ export const hidden = style({
353
+ clipPath: 'inset(50%) !custom',
354
+ height: '1px !custom',
355
+ overflow: 'hidden',
356
+ position: 'absolute',
357
+ whiteSpace: 'nowrap',
358
+ width: '1px !custom',
359
+ })
360
+
361
+ export const name = style({ margin: '0', typography: 'body.b2' })
362
+
363
+ // Cells keep one line and the table scrolls sideways, so key prefixes never break mid-token.
364
+ export const scroll = style({
365
+ overflowX: 'auto',
366
+ selectors: { '& :is(td, th)': { whiteSpace: 'nowrap' } },
367
+ })
368
+
369
+ export const stack = style({ display: 'flex', flex: '1 !custom', flexDirection: 'column' })
370
+ }
371
+ ```
372
+
373
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Products/ConsoleKeys.tsx)
374
+
375
+ API Keys lists the keys of the selected environment, for the whole organization by default. To show the sandbox banner and keys, switch to sandbox in the organization menu.
376
+
377
+ * **Navigation (SidebarMenu):** The app shell holds Projects, API Keys, Usage, Billing, and Team.
378
+ * **Environment (Menu.RadioGroup):** The organization menu switches the environment. In sandbox, a warning banner stays below the header.
379
+ * **Project Filter (Select):** Filters keys by project, beside the New key button in the header.
380
+ * **Key Table (mono):** Lists the name, project, prefix and last four characters, creation date, and last use.
381
+ * **Revoke From the Row (negative):** The row menu holds Revoke key, which opens an alert dialog.
382
+ * **No Matching Keys (Empty):** Replaces the table with an empty state when no key matches the filters.
383
+
384
+ Patterns:
385
+
386
+ * [Tables and Lists](../patterns/tables-and-lists.md) for the key table, its filter, and row actions.
387
+ * [Destructive Actions](../patterns/destructive-actions.md) for the revoke confirmation.
388
+ * [Empty States](../patterns/empty-states.md) for a project or environment with no keys.
389
+
390
+ ### Create Key
391
+
392
+ ```tsx
393
+ import { Accordion, Alert, Badge, Button, Checkbox, CheckboxGroup, Field, ListGroup, SecretValue, Select, style, Surface, TextInput } from '@tempoxyz/ds'
394
+ import { useState } from 'react'
395
+
396
+ export const fill = true
397
+ const projects = { 'checkout-service': 'checkout-service', 'payouts-worker': 'payouts-worker' }
398
+
399
+ // The self-serve scopes and descriptions from the Tempo API scope catalog (`GET /v1/scopes`).
400
+ const scopes = [
401
+ ['data:read', 'Read public chain data: tokens, transactions, transfers, blocks, and receipts.'],
402
+ ['indexer:query', 'Run ad-hoc queries against the raw indexer passthrough.'],
403
+ ['management:read', 'Read organization resources across projects and environments.'],
404
+ [
405
+ 'management:write',
406
+ 'Create, update, and delete organization resources across projects and environments.',
407
+ ],
408
+ ['mpp:write', 'Validate and broadcast MPP credentials through Tempo.'],
409
+ ['routes:read', 'Read organization route transfers, deposit addresses, and deposits.'],
410
+ ['rpc-relay:read', 'Fill and submit transactions through the relay.'],
411
+ ['rpc-relay:sponsor', 'Sponsor transaction fees through the relay as the managed fee payer.'],
412
+ ] as const
413
+
414
+ export function Demo() {
415
+ const [created, setCreated] = useState(false)
416
+ const [keyName, setKeyName] = useState('')
417
+ const [project, setProject] = useState<string | null>('checkout-service')
418
+ const [selected, setSelected] = useState<string[]>(['data:read', 'rpc-relay:read'])
419
+ const [submitted, setSubmitted] = useState(false)
420
+
421
+ const missing = submitted && keyName.trim() === ''
422
+
423
+ const reset = () => {
424
+ setCreated(false)
425
+ setKeyName('')
426
+ setSubmitted(false)
427
+ }
428
+
429
+ return (
430
+ <div {...styles.page()}>
431
+ <Surface {...styles.panel()}>
432
+ <Surface.Header>
433
+ <Surface.Title>{created ? 'Key created' : 'New key'}</Surface.Title>
434
+ <Badge size="small">Sandbox</Badge>
435
+ </Surface.Header>
436
+
437
+ {created ? (
438
+ <div {...styles.form()}>
439
+ <Alert intent="warning">
440
+ <Alert.Icon />
441
+ <Alert.Content>
442
+ Copy this key now and store it in a secret manager. You can't view it again.
443
+ </Alert.Content>
444
+ </Alert>
445
+ <SecretValue label="API key" value="tempo_sandbox:sk:demo_8QnV2kXbT4mL9wRz7cYp3fHs" />
446
+ <ListGroup.Root>
447
+ <ListGroup.Row>
448
+ <ListGroup.Label>Name</ListGroup.Label>
449
+ {keyName}
450
+ </ListGroup.Row>
451
+ <ListGroup.Row>
452
+ <ListGroup.Label>Project</ListGroup.Label>
453
+ {project}
454
+ </ListGroup.Row>
455
+ <ListGroup.Row>
456
+ <ListGroup.Label>Scopes</ListGroup.Label>
457
+ <span {...styles.scope()}>{selected.join(', ') || 'None'}</span>
458
+ </ListGroup.Row>
459
+ </ListGroup.Root>
460
+ <div {...styles.actions()}>
461
+ <Button onClick={reset}>Done</Button>
462
+ </div>
463
+ </div>
464
+ ) : (
465
+ <form
466
+ noValidate
467
+ onSubmit={(event) => {
468
+ event.preventDefault()
469
+ setSubmitted(true)
470
+ if (keyName.trim() !== '') setCreated(true)
471
+ }}
472
+ {...styles.form()}
473
+ >
474
+ <Field.Root>
475
+ <Field.Label>Project</Field.Label>
476
+ <Select.Root items={projects} onValueChange={setProject} value={project}>
477
+ <Select.Trigger>
478
+ <Select.Value placeholder="Select a project" />
479
+ <Select.Icon />
480
+ </Select.Trigger>
481
+ <Select.Popup>
482
+ {Object.entries(projects).map(([value, label]) => (
483
+ <Select.Item key={value} label={label} value={value}>
484
+ <Select.ItemText>{label}</Select.ItemText>
485
+ </Select.Item>
486
+ ))}
487
+ </Select.Popup>
488
+ </Select.Root>
489
+ </Field.Root>
490
+
491
+ <Field.Root invalid={missing}>
492
+ <Field.Label>Name</Field.Label>
493
+ <TextInput.Group>
494
+ <TextInput.Control
495
+ autoComplete="off"
496
+ onChange={(event) => setKeyName(event.target.value)}
497
+ placeholder="checkout-service CI"
498
+ value={keyName}
499
+ />
500
+ </TextInput.Group>
501
+ <Field.Description>
502
+ Name the deployment or owner, so you can find the key when you rotate it.
503
+ </Field.Description>
504
+ <Field.Error match={missing}>Enter a name for the key.</Field.Error>
505
+ </Field.Root>
506
+
507
+ <Accordion.Root>
508
+ <Accordion.Item value="scopes">
509
+ <Accordion.Trigger>
510
+ View scopes ({selected.length} of {scopes.length} selected)
511
+ </Accordion.Trigger>
512
+ <Accordion.Panel>
513
+ <CheckboxGroup aria-label="Scopes" onValueChange={setSelected} value={selected}>
514
+ {scopes.map(([scope, description]) => (
515
+ <Field.Root key={scope}>
516
+ <Field.Item>
517
+ <Checkbox value={scope} />
518
+ <Field.Label>
519
+ <span {...styles.scope()}>{scope}</span>
520
+ </Field.Label>
521
+ </Field.Item>
522
+ <Field.Description {...styles.hint()}>{description}</Field.Description>
523
+ </Field.Root>
524
+ ))}
525
+ </CheckboxGroup>
526
+ </Accordion.Panel>
527
+ </Accordion.Item>
528
+ </Accordion.Root>
529
+
530
+ {selected.length === 0 && (
531
+ <Alert intent="warning">
532
+ <Alert.Icon />
533
+ <Alert.Content>A key with no scopes can't access scoped APIs.</Alert.Content>
534
+ </Alert>
535
+ )}
536
+
537
+ <div {...styles.actions()}>
538
+ <Button type="button" variant="secondary">
539
+ Cancel
540
+ </Button>
541
+ <Button type="submit">Create key</Button>
542
+ </div>
543
+ </form>
544
+ )}
545
+ </Surface>
546
+ </div>
547
+ )
548
+ }
549
+
550
+ namespace styles {
551
+ export const actions = style({
552
+ columnGap: '8',
553
+ display: 'flex',
554
+ justifyContent: 'flex-end',
555
+ '@media (max-width: 40rem)': {
556
+ flexDirection: 'column-reverse',
557
+ rowGap: '8',
558
+ },
559
+ })
560
+
561
+ export const form = style({ display: 'flex', flexDirection: 'column', rowGap: '24' })
562
+
563
+ // Aligns each scope's description with its label, past the 20px checkbox and the 16px gap.
564
+ export const hint = style({ paddingInlineStart: '36' })
565
+
566
+ export const page = style({
567
+ backgroundColor: 'background.primary',
568
+ display: 'flex',
569
+ justifyContent: 'center',
570
+ padding: '16',
571
+ })
572
+
573
+ export const panel = style({ maxWidth: '32rem !custom', width: '100% !custom' })
574
+
575
+ export const scope = style({ typography: 'mono.m1' })
576
+ }
577
+ ```
578
+
579
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Products/ConsoleCreateKey.tsx)
580
+
581
+ New key asks for a project, a name, and scopes. The console shows the token only once, after creation, so the layout switches to a reveal state with the key masked.
582
+
583
+ * **Project and Name (Field):** A select picks the project. An input names the key and shows an error when it is empty.
584
+ * **Scopes (CheckboxGroup):** View scopes opens each scope, its identifier, and its description from the Tempo API catalog.
585
+ * **Warnings (warning):** With no scopes, an alert warns that the key can't reach scoped APIs. A badge names the environment.
586
+ * **Reveal Once (SecretValue):** After Create key, holds the masked token. Copy works without a reveal.
587
+
588
+ Patterns:
589
+
590
+ * [Forms](../patterns/forms.md) for labels, descriptions, and the submit action.
591
+ * [Errors and Validation](../patterns/errors-and-validation.md) for the missing name and the empty scope set.
592
+
593
+ ### Webhooks
594
+
595
+ Webhook subscriptions deliver signed onchain events to an HTTPS URL, a Slack incoming webhook, or a Better Stack source. The Tempo docs describe only the API (`/v1/webhooks`), so this page has no webhook layout yet.
596
+
597
+ To build one, reuse these parts:
598
+
599
+ * **Subscriptions (Table):** Lists subscriptions with their event type, destination, and status.
600
+ * **Signing Secret (SecretValue):** Holds the `whsec_` signing secret, shown once at creation.
601
+ * **Deliveries (numeric):** Lists deliveries with attempt, response status, and response time.
602
+
603
+ ## Content
604
+
605
+ ### Show the Key's Identity, Never the Secret
606
+
607
+ After creation, identify a key by its prefix and last four characters, such as `tempo:sk:…a91c`. Show the full token once, masked in [SecretValue](../components/secret-value.md), and tell people to copy it into a secret manager now.
608
+
609
+ **Do:** Say what happens when the reveal state closes: You can't view this key again.
610
+
611
+ **Don't:** Offer to show an existing key again. The console can't reveal it, so a lost key needs a
612
+ replacement.
613
+
614
+ ### Name the Environment Wherever It Changes Meaning
615
+
616
+ Sandbox and production keys differ in prefix and in the chains they reach. Show the environment on every screen that creates, lists, or bills for keys.
617
+
618
+ * **Production (tempo:sk:):** Reach any chain, including mainnet.
619
+ * **Sandbox (tempo_sandbox:sk:):** Reach non-mainnet chains only.
620
+
621
+ ### Name Keys After a Deployment or Owner
622
+
623
+ Suggest names such as checkout-service production or checkout-service CI in the placeholder and description. A descriptive name identifies the integration that a revoke stops.
624
+
625
+ ### Write Scopes as Identifiers
626
+
627
+ Show each scope as its identifier in monospace, such as `data:read` or `rpc-relay:sponsor`, with its catalog description. Scopes can't change on an existing key, so state this where people pick them.
628
+
629
+ ### State Side Effects, Including Missing Ones
630
+
631
+ A revoke stops the requests that use the key. The removal of a team member does not revoke the member's keys. State both in the confirmation, so an owner checks API Keys next.
632
+
633
+ ### Quote Error Codes Exactly
634
+
635
+ If a request fails, show the code in monospace and the fix beside it. For example, a sandbox key that targets mainnet returns `403 api_key_forbidden`. The fix is `chainId=testnet` or a production key.
636
+
637
+ ## Open Gaps
638
+
639
+ The library has no part for these needs yet. Before you add one, propose it as an issue in the Design System project.
640
+
641
+ * **Charts:** Usage shows request volume, sponsorship, and traffic by route and key. No chart shows volume over time.
642
+ * **Table Tools:** Tables have no sorting, column filters, or selection. Build filters from selects.
643
+ * **Multi-Line Code:** Snippets hold one line. A test request with headers needs a multi-line code block.
644
+ * **Relative Time:** Nothing formats `lastUsedAt` as 2 minutes ago. Use `Intl.RelativeTimeFormat` in your app.
645
+ * **Brand Marks:** The icon set has no Google mark for the Continue with Google button.
646
+
647
+ ## Related
648
+
649
+ [Tables and Lists](../patterns/tables-and-lists.md)
650
+
651
+ Covers key and member tables.
652
+
653
+ [Destructive Actions](../patterns/destructive-actions.md)
654
+
655
+ Covers revoking keys and removing members.
656
+
657
+ [AppShell](../components/app-shell.md)
658
+
659
+ Use for the console frame.
660
+
661
+ [SecretValue](../components/secret-value.md)
662
+
663
+ Use for one-time key reveal.
664
+
665
+ [Table](../components/table.md)
666
+
667
+ Use for key lists.
668
+
669
+ [Banner](../components/banner.md)
670
+
671
+ Use for the sandbox notice.