@tempoxyz/ds 0.3.6 → 0.3.9

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 (219) hide show
  1. package/dist/web/components/Accordion.js.zyzz.json +1 -1
  2. package/dist/web/components/AddressCard.css +1 -1
  3. package/dist/web/components/AddressCard.js.zyzz.json +1 -1
  4. package/dist/web/components/AgentLogo.css +1 -1
  5. package/dist/web/components/AgentLogo.js.zyzz.json +1 -1
  6. package/dist/web/components/Alert.js.zyzz.json +1 -1
  7. package/dist/web/components/AlertDialog.js.zyzz.json +1 -1
  8. package/dist/web/components/Amount.js.zyzz.json +1 -1
  9. package/dist/web/components/AmountInput.css +1 -1
  10. package/dist/web/components/AmountInput.js +1 -1
  11. package/dist/web/components/AmountInput.js.map +1 -1
  12. package/dist/web/components/AmountInput.js.zyzz.json +1 -1
  13. package/dist/web/components/AppShell.css +1 -1
  14. package/dist/web/components/AppShell.d.ts +31 -110
  15. package/dist/web/components/AppShell.d.ts.map +1 -1
  16. package/dist/web/components/AppShell.js +40 -44
  17. package/dist/web/components/AppShell.js.map +1 -1
  18. package/dist/web/components/AppShell.js.zyzz.json +1 -1
  19. package/dist/web/components/AssetChip.css +1 -1
  20. package/dist/web/components/AssetChip.js.zyzz.json +1 -1
  21. package/dist/web/components/Autocomplete.js.zyzz.json +1 -1
  22. package/dist/web/components/Avatar.js.zyzz.json +1 -1
  23. package/dist/web/components/Badge.css +1 -1
  24. package/dist/web/components/Badge.d.ts +1 -1
  25. package/dist/web/components/Badge.js.map +1 -1
  26. package/dist/web/components/Badge.js.zyzz.json +1 -1
  27. package/dist/web/components/BadgedLogo.css +1 -0
  28. package/dist/web/components/BadgedLogo.d.ts +44 -0
  29. package/dist/web/components/BadgedLogo.d.ts.map +1 -0
  30. package/dist/web/components/BadgedLogo.js +59 -0
  31. package/dist/web/components/BadgedLogo.js.map +1 -0
  32. package/dist/web/components/BadgedLogo.js.zyzz.json +1 -0
  33. package/dist/web/components/Banner.js.zyzz.json +1 -1
  34. package/dist/web/components/BarChart.css +1 -1
  35. package/dist/web/components/BarChart.d.ts +1 -1
  36. package/dist/web/components/BarChart.js.map +1 -1
  37. package/dist/web/components/BarChart.js.zyzz.json +1 -1
  38. package/dist/web/components/Breadcrumbs.js.zyzz.json +1 -1
  39. package/dist/web/components/Button.css +1 -1
  40. package/dist/web/components/Button.d.ts +40 -31
  41. package/dist/web/components/Button.d.ts.map +1 -1
  42. package/dist/web/components/Button.js +25 -8
  43. package/dist/web/components/Button.js.map +1 -1
  44. package/dist/web/components/Button.js.zyzz.json +1 -1
  45. package/dist/web/components/ButtonGroup.js.zyzz.json +1 -1
  46. package/dist/web/components/Calendar.css +1 -1
  47. package/dist/web/components/Calendar.js.zyzz.json +1 -1
  48. package/dist/web/components/Callout.js.zyzz.json +1 -1
  49. package/dist/web/components/Checkbox.css +1 -1
  50. package/dist/web/components/Checkbox.js.zyzz.json +1 -1
  51. package/dist/web/components/CheckboxGroup.js.zyzz.json +1 -1
  52. package/dist/web/components/ChoiceGroup.css +1 -1
  53. package/dist/web/components/ChoiceGroup.js.zyzz.json +1 -1
  54. package/dist/web/components/Choicebox.css +1 -1
  55. package/dist/web/components/Choicebox.js.zyzz.json +1 -1
  56. package/dist/web/components/Code.js.zyzz.json +1 -1
  57. package/dist/web/components/Combobox.css +1 -1
  58. package/dist/web/components/Combobox.js.zyzz.json +1 -1
  59. package/dist/web/components/Container.js.zyzz.json +1 -1
  60. package/dist/web/components/CopyButton.js.zyzz.json +1 -1
  61. package/dist/web/components/DatePicker.css +1 -1
  62. package/dist/web/components/DatePicker.js.zyzz.json +1 -1
  63. package/dist/web/components/Dialog.css +1 -1
  64. package/dist/web/components/Dialog.d.ts +14 -0
  65. package/dist/web/components/Dialog.d.ts.map +1 -1
  66. package/dist/web/components/Dialog.js +7 -5
  67. package/dist/web/components/Dialog.js.map +1 -1
  68. package/dist/web/components/Dialog.js.zyzz.json +1 -1
  69. package/dist/web/components/Drawer.css +1 -1
  70. package/dist/web/components/Drawer.js.zyzz.json +1 -1
  71. package/dist/web/components/Empty.js.zyzz.json +1 -1
  72. package/dist/web/components/Field.js.zyzz.json +1 -1
  73. package/dist/web/components/Fieldset.js.zyzz.json +1 -1
  74. package/dist/web/components/Frame.js.zyzz.json +1 -1
  75. package/dist/web/components/Gauge.js.zyzz.json +1 -1
  76. package/dist/web/components/GetStartedVisual.js.zyzz.json +1 -1
  77. package/dist/web/components/Grid.js.zyzz.json +1 -1
  78. package/dist/web/components/HeroVisual.js.zyzz.json +1 -1
  79. package/dist/web/components/Hex.js.zyzz.json +1 -1
  80. package/dist/web/components/IconTile.js.zyzz.json +1 -1
  81. package/dist/web/components/Identicon.js.zyzz.json +1 -1
  82. package/dist/web/components/Inline.js.zyzz.json +1 -1
  83. package/dist/web/components/Inset.js.zyzz.json +1 -1
  84. package/dist/web/components/Kbd.js.zyzz.json +1 -1
  85. package/dist/web/components/ListGroup.css +1 -1
  86. package/dist/web/components/ListGroup.d.ts +46 -4
  87. package/dist/web/components/ListGroup.d.ts.map +1 -1
  88. package/dist/web/components/ListGroup.js +11 -9
  89. package/dist/web/components/ListGroup.js.map +1 -1
  90. package/dist/web/components/ListGroup.js.zyzz.json +1 -1
  91. package/dist/web/components/LoadingDots.js.zyzz.json +1 -1
  92. package/dist/web/components/Menu.css +1 -1
  93. package/dist/web/components/Menu.js.zyzz.json +1 -1
  94. package/dist/web/components/NativeSelect.css +1 -1
  95. package/dist/web/components/NativeSelect.js.zyzz.json +1 -1
  96. package/dist/web/components/NetworkLogo.js.zyzz.json +1 -1
  97. package/dist/web/components/NumberField.js.zyzz.json +1 -1
  98. package/dist/web/components/OtpField.css +1 -1
  99. package/dist/web/components/OtpField.js.zyzz.json +1 -1
  100. package/dist/web/components/Pagination.css +1 -1
  101. package/dist/web/components/Pagination.js.zyzz.json +1 -1
  102. package/dist/web/components/Popover.css +1 -1
  103. package/dist/web/components/Popover.js.zyzz.json +1 -1
  104. package/dist/web/components/Progress.js.zyzz.json +1 -1
  105. package/dist/web/components/Qr.js.zyzz.json +1 -1
  106. package/dist/web/components/RadioGroup.css +1 -1
  107. package/dist/web/components/RadioGroup.js.zyzz.json +1 -1
  108. package/dist/web/components/SecretValue.js.zyzz.json +1 -1
  109. package/dist/web/components/Select.css +1 -1
  110. package/dist/web/components/Select.d.ts +14 -7
  111. package/dist/web/components/Select.d.ts.map +1 -1
  112. package/dist/web/components/Select.js +8 -6
  113. package/dist/web/components/Select.js.map +1 -1
  114. package/dist/web/components/Select.js.zyzz.json +1 -1
  115. package/dist/web/components/Separator.js.zyzz.json +1 -1
  116. package/dist/web/components/SidebarMenu.css +1 -1
  117. package/dist/web/components/SidebarMenu.d.ts +102 -11
  118. package/dist/web/components/SidebarMenu.d.ts.map +1 -1
  119. package/dist/web/components/SidebarMenu.js +58 -4
  120. package/dist/web/components/SidebarMenu.js.map +1 -1
  121. package/dist/web/components/SidebarMenu.js.zyzz.json +1 -1
  122. package/dist/web/components/Skeleton.js.zyzz.json +1 -1
  123. package/dist/web/components/Slider.js.zyzz.json +1 -1
  124. package/dist/web/components/Snippet.css +1 -1
  125. package/dist/web/components/Snippet.js.zyzz.json +1 -1
  126. package/dist/web/components/Spinner.css +1 -1
  127. package/dist/web/components/Spinner.d.ts +40 -12
  128. package/dist/web/components/Spinner.d.ts.map +1 -1
  129. package/dist/web/components/Spinner.js +8 -7
  130. package/dist/web/components/Spinner.js.map +1 -1
  131. package/dist/web/components/Spinner.js.zyzz.json +1 -1
  132. package/dist/web/components/SplitBar.js.zyzz.json +1 -1
  133. package/dist/web/components/SplitButton.js.zyzz.json +1 -1
  134. package/dist/web/components/Stack.js.zyzz.json +1 -1
  135. package/dist/web/components/StatusChip.js.zyzz.json +1 -1
  136. package/dist/web/components/StatusIndicator.js.zyzz.json +1 -1
  137. package/dist/web/components/StatusRow.js.zyzz.json +1 -1
  138. package/dist/web/components/Stepper.js.zyzz.json +1 -1
  139. package/dist/web/components/SuccessHero.css +1 -1
  140. package/dist/web/components/SuccessHero.d.ts.map +1 -1
  141. package/dist/web/components/SuccessHero.js +0 -2
  142. package/dist/web/components/SuccessHero.js.map +1 -1
  143. package/dist/web/components/SuccessHero.js.zyzz.json +1 -1
  144. package/dist/web/components/Surface.css +1 -1
  145. package/dist/web/components/Surface.d.ts +1 -1
  146. package/dist/web/components/Surface.js.map +1 -1
  147. package/dist/web/components/Surface.js.zyzz.json +1 -1
  148. package/dist/web/components/Switch.css +1 -1
  149. package/dist/web/components/Switch.d.ts +1 -1
  150. package/dist/web/components/Switch.js.map +1 -1
  151. package/dist/web/components/Switch.js.zyzz.json +1 -1
  152. package/dist/web/components/Table.css +1 -1
  153. package/dist/web/components/Table.js.zyzz.json +1 -1
  154. package/dist/web/components/Tabs.css +1 -1
  155. package/dist/web/components/Tabs.d.ts +9 -18
  156. package/dist/web/components/Tabs.d.ts.map +1 -1
  157. package/dist/web/components/Tabs.js.map +1 -1
  158. package/dist/web/components/Tabs.js.zyzz.json +1 -1
  159. package/dist/web/components/TempoLogo.js.zyzz.json +1 -1
  160. package/dist/web/components/TextInput.js.zyzz.json +1 -1
  161. package/dist/web/components/Textarea.css +1 -1
  162. package/dist/web/components/Textarea.js.zyzz.json +1 -1
  163. package/dist/web/components/Toast.css +1 -1
  164. package/dist/web/components/Toast.d.ts.map +1 -1
  165. package/dist/web/components/Toast.js +6 -7
  166. package/dist/web/components/Toast.js.map +1 -1
  167. package/dist/web/components/Toast.js.zyzz.json +1 -1
  168. package/dist/web/components/Token.css +1 -1
  169. package/dist/web/components/Token.d.ts.map +1 -1
  170. package/dist/web/components/Token.js +1 -1
  171. package/dist/web/components/Token.js.map +1 -1
  172. package/dist/web/components/Token.js.zyzz.json +1 -1
  173. package/dist/web/components/TokenLogo.js.zyzz.json +1 -1
  174. package/dist/web/components/Tooltip.css +1 -1
  175. package/dist/web/components/Tooltip.js.zyzz.json +1 -1
  176. package/dist/web/components/VerificationCode.js.zyzz.json +1 -1
  177. package/dist/web/components/Visual.css +1 -1
  178. package/dist/web/components/Visual.js.zyzz.json +1 -1
  179. package/dist/web/components/internal/Field.css +1 -1
  180. package/dist/web/components/internal/Field.js.zyzz.json +1 -1
  181. package/dist/web/components/internal/InputGroup.css +1 -1
  182. package/dist/web/components/internal/InputGroup.js.zyzz.json +1 -1
  183. package/dist/web/components/internal/Listbox.css +1 -1
  184. package/dist/web/components/internal/Listbox.js.zyzz.json +1 -1
  185. package/dist/web/components/internal/Streaks.js.zyzz.json +1 -1
  186. package/dist/web/components/internal/TabBar.d.ts +1 -1
  187. package/dist/web/index.d.ts +1 -0
  188. package/dist/web/index.d.ts.map +1 -1
  189. package/dist/web/index.js +1 -0
  190. package/dist/web/index.js.map +1 -1
  191. package/dist/web/index.js.zyzz.json +1 -1
  192. package/dist/web/vars.d.ts +50 -4
  193. package/dist/web/vars.d.ts.map +1 -1
  194. package/dist/web/vars.js.map +1 -1
  195. package/dist/web/vars.js.zyzz.json +1 -1
  196. package/dist/web/zyzz.config.d.ts +250 -20
  197. package/dist/web/zyzz.config.d.ts.map +1 -1
  198. package/dist/web/zyzz.config.js.zyzz.json +1 -1
  199. package/package.json +1 -1
  200. package/src/web/components/AmountInput.tsx +6 -1
  201. package/src/web/components/AppShell.tsx +64 -125
  202. package/src/web/components/Badge.tsx +1 -1
  203. package/src/web/components/BadgedLogo.tsx +125 -0
  204. package/src/web/components/BarChart.tsx +1 -1
  205. package/src/web/components/Button.tsx +66 -36
  206. package/src/web/components/Dialog.tsx +11 -0
  207. package/src/web/components/ListGroup.tsx +53 -7
  208. package/src/web/components/Select.tsx +15 -8
  209. package/src/web/components/SidebarMenu.tsx +165 -11
  210. package/src/web/components/Spinner.tsx +34 -11
  211. package/src/web/components/SuccessHero.tsx +2 -4
  212. package/src/web/components/Surface.tsx +1 -1
  213. package/src/web/components/Switch.tsx +1 -1
  214. package/src/web/components/Tabs.tsx +10 -20
  215. package/src/web/components/Toast.tsx +17 -5
  216. package/src/web/components/Token.tsx +2 -4
  217. package/src/web/components/internal/TabBar.ts +1 -1
  218. package/src/web/index.ts +1 -0
  219. package/src/web/vars.ts +43 -5
@@ -42,6 +42,7 @@ export function Button(props: Button.Props) {
42
42
  loading = false,
43
43
  shape,
44
44
  size,
45
+ underline,
45
46
  variant,
46
47
  vars,
47
48
  ...attributes
@@ -50,13 +51,22 @@ export function Button(props: Button.Props) {
50
51
  return (
51
52
  <BaseUi.Button
52
53
  {...attributes}
53
- {...styles.button({ appearance, className, shape, size, style: props.style, variant, vars })}
54
+ {...styles.button({
55
+ appearance,
56
+ className,
57
+ shape,
58
+ size,
59
+ style: props.style,
60
+ underline,
61
+ variant,
62
+ vars,
63
+ })}
54
64
  aria-busy={loading || undefined}
55
65
  data-loading={loading || undefined}
56
66
  disabled={disabled || loading}
57
67
  >
58
- <span {...styles.content({ loading })}>{children}</span>
59
- {loading && <Spinner aria-label="Loading" {...styles.spinner()} />}
68
+ {loading && <Spinner aria-label="Loading" />}
69
+ <span {...styles.content()}>{children}</span>
60
70
  </BaseUi.Button>
61
71
  )
62
72
  }
@@ -66,8 +76,8 @@ export namespace Button {
66
76
  zyzz.Props.Variants<typeof styles.button> & {
67
77
  disabled?: boolean | undefined
68
78
  /**
69
- * Disables the button, sets `aria-busy`, and swaps the label for a `Spinner` labelled
70
- * "Loading" while keeping the button's width.
79
+ * Disables the button, sets `aria-busy`, and renders a `Spinner` labelled "Loading" before
80
+ * the label, which stays visible.
71
81
  *
72
82
  * @default false
73
83
  */
@@ -87,6 +97,7 @@ export namespace Button {
87
97
  render,
88
98
  shape,
89
99
  size,
100
+ underline,
90
101
  variant,
91
102
  vars,
92
103
  ...attributes
@@ -96,8 +107,17 @@ export namespace Button {
96
107
  defaultTagName: 'a',
97
108
  props: {
98
109
  ...attributes,
99
- ...styles.button({ appearance, className, shape, size, style: props.style, variant, vars }),
100
- children: <span {...styles.content({ loading: false })}>{children}</span>,
110
+ ...styles.button({
111
+ appearance,
112
+ className,
113
+ shape,
114
+ size,
115
+ style: props.style,
116
+ underline,
117
+ variant,
118
+ vars,
119
+ }),
120
+ children: <span {...styles.content()}>{children}</span>,
101
121
  },
102
122
  ref,
103
123
  render,
@@ -163,9 +183,18 @@ namespace styles {
163
183
  flexShrink: '0 !custom',
164
184
  minWidth: '0',
165
185
  paddingInline: '0',
166
- selectors: { '& > span:first-child': { minWidth: '0 !custom' } },
186
+ // A loading icon button shows the spinner alone in the center of the circle.
187
+ selectors: {
188
+ '& > span:last-child': { minWidth: '0 !custom' },
189
+ '&[data-loading] > span:last-child': { display: 'none' },
190
+ },
167
191
  },
168
192
  },
193
+ // The large Platform Icon Button draws a 20px icon.
194
+ {
195
+ when: { shape: 'icon', size: 'large' },
196
+ style: { selectors: { '& > span > svg': { fontSize: '20' } } },
197
+ },
169
198
  // The Platform Icon Button fills its primary circle a step lighter than the primary
170
199
  // Button in light mode.
171
200
  {
@@ -261,7 +290,13 @@ namespace styles {
261
290
  },
262
291
  },
263
292
  ],
264
- defaultVariants: { appearance: 'base', shape: 'default', size: 'medium', variant: 'primary' },
293
+ defaultVariants: {
294
+ appearance: 'base',
295
+ shape: 'default',
296
+ size: 'medium',
297
+ underline: false,
298
+ variant: 'primary',
299
+ },
265
300
  variants: {
266
301
  /**
267
302
  * The surface under the button: the page for `base`, an inverse surface, such as
@@ -284,18 +319,18 @@ namespace styles {
284
319
  borderRadius: 'sm',
285
320
  height: '48',
286
321
  paddingInline: '32',
287
- selectors: { '& > span:first-child': { minWidth: '1rem !custom' } },
322
+ selectors: { '& > span:last-child': { minWidth: '1rem !custom' } },
288
323
  },
289
324
  medium: {
290
325
  borderRadius: 'sm',
291
326
  height: '40',
292
327
  paddingInline: '24',
293
- selectors: { '& > span:first-child': { minWidth: '2rem !custom' } },
328
+ selectors: { '& > span:last-child': { minWidth: '2rem !custom' } },
294
329
  },
295
330
  small: {
296
331
  height: '32',
297
332
  paddingInline: '16',
298
- selectors: { '& > span:first-child': { minWidth: '3rem !custom' } },
333
+ selectors: { '& > span:last-child': { minWidth: '3rem !custom' } },
299
334
  },
300
335
  xsmall: {
301
336
  borderRadius: 'full',
@@ -303,9 +338,21 @@ namespace styles {
303
338
  height: '24',
304
339
  paddingInline: '12',
305
340
  typography: 'body.b3Strong',
306
- selectors: { '& > span > svg': { fontSize: '12' } },
341
+ selectors: {
342
+ '& > [role="progressbar"]': { height: '12', width: '12' },
343
+ '& > span > svg': { fontSize: '12' },
344
+ },
307
345
  },
308
346
  },
347
+ /**
348
+ * Underlines the label, such as a `text` button inside running content.
349
+ *
350
+ * @default false
351
+ */
352
+ underline: {
353
+ false: {},
354
+ true: { textDecoration: 'underline !custom', textUnderlineOffset: '0.2em !custom' },
355
+ },
309
356
  // Each pressed fill follows its hover fill, so it wins while the pointer stays over the
310
357
  // button.
311
358
  variant: {
@@ -354,7 +401,7 @@ namespace styles {
354
401
  padding: '0',
355
402
  typography: 'body.b2Strong',
356
403
  selectors: {
357
- '& > span:first-child': { minWidth: '0 !custom' },
404
+ '& > span:last-child': { minWidth: '0 !custom' },
358
405
  '& > span > svg': { fontSize: '12' },
359
406
  '&:focus-visible': { outlineOffset: '2' },
360
407
  '&:hover:not(:disabled)': { color: 'content.primary' },
@@ -366,30 +413,13 @@ namespace styles {
366
413
  { id: 'tds/web/button' },
367
414
  )
368
415
 
369
- export const content = variants(
416
+ export const content = style(
370
417
  {
371
- base: {
372
- alignItems: 'inherit !custom',
373
- columnGap: 'inherit !custom',
374
- display: 'inline-flex',
375
- justifyContent: 'center',
376
- transitionDuration: '160',
377
- transitionProperty: 'opacity, visibility !custom',
378
- transitionTimingFunction: 'snappy',
379
- },
380
- defaultVariants: { loading: false },
381
- variants: { loading: { false: {}, true: { opacity: '0 !custom', visibility: 'hidden' } } },
418
+ alignItems: 'inherit !custom',
419
+ columnGap: 'inherit !custom',
420
+ display: 'inline-flex',
421
+ justifyContent: 'center',
382
422
  },
383
423
  { id: 'tds/web/button/content' },
384
424
  )
385
-
386
- export const spinner = style(
387
- {
388
- insetBlockStart: '50% !custom',
389
- insetInlineStart: '50% !custom',
390
- position: 'absolute',
391
- translate: '-50% -50% !custom',
392
- },
393
- { id: 'tds/web/button/spinner' },
394
- )
395
425
  }
@@ -114,6 +114,8 @@ export namespace Dialog {
114
114
  * - `stacked`: puts a `Dialog.Nav` above left-aligned text, for step 2 onward of a flow.
115
115
  * - `centered`: centers the hero and the text below a `Dialog.Nav`, for acknowledgments,
116
116
  * onboarding, progress and success.
117
+ * - `hero`: centers the parts as `centered` does, without the 16px below the `Dialog.Nav`, for
118
+ * a hero that brings its own space above it, such as a `Visual`.
117
119
  */
118
120
  export function Header(props: Header.Props) {
119
121
  const { className, layout, vars, ...attributes } = props
@@ -328,6 +330,15 @@ export namespace styles {
328
330
  textAlign: 'center',
329
331
  selectors: { '& > :is(div, h2, p)': { alignSelf: 'stretch' } },
330
332
  },
333
+ // A hero header centers its parts as a centered one does, directly below the nav row.
334
+ hero: {
335
+ alignItems: 'center',
336
+ textAlign: 'center',
337
+ selectors: {
338
+ '& > :is(div, h2, p)': { alignSelf: 'stretch' },
339
+ '& > [data-dialog-nav]': { marginBottom: '0' },
340
+ },
341
+ },
331
342
  // The text keeps 16px clear of the 40px close button, and starts 8px lower to meet it.
332
343
  inline: {
333
344
  selectors: { '[data-close-button] &': { paddingInlineEnd: '56', paddingTop: '8' } },
@@ -211,8 +211,10 @@ export namespace ListGroup {
211
211
 
212
212
  /**
213
213
  * Holds the selected `value` of the rows inside it, each a radio with its own `value`, in a
214
- * `radiogroup` element. The rows stack 12px apart, each in its own bordered block. The group is
215
- * one Tab stop, and arrow keys move focus and selection together. Pass the choices as a type
214
+ * `radiogroup` element. The rows stack 12px apart. The group is one Tab stop, and arrow keys move
215
+ * focus and selection together. `variant` sets how a row shows its selection: `radio` rows sit in
216
+ * bordered blocks with a ring at the end, and `option` rows, such as the accounts of a picker, pad
217
+ * 12px without a border or ring and fill when selected. Defaults to `variant="radio"`. Pass the choices as a type
216
218
  * argument, such as `ListGroup.RadioGroup<'maya' | 'acme'>`, to narrow `value`, `defaultValue`
217
219
  * and `onValueChange`.
218
220
  *
@@ -230,12 +232,13 @@ export namespace ListGroup {
230
232
  * </ListGroup.RadioGroup>
231
233
  */
232
234
  export function RadioGroup<const value extends string = string>(props: RadioGroup.Props<value>) {
233
- const { className, vars, ...attributes } = props
235
+ const { className, variant, vars, ...attributes } = props
234
236
 
235
237
  return (
236
238
  <BaseUiRadioGroup.RadioGroup
237
239
  {...attributes}
238
- {...styles.radioGroup({ className, style: props.style, vars })}
240
+ data-variant={variant ?? 'radio'}
241
+ {...styles.radioGroup({ className, style: props.style, variant, vars })}
239
242
  />
240
243
  )
241
244
  }
@@ -466,7 +469,7 @@ namespace styles {
466
469
  paddingInline: '8',
467
470
  width: 'auto !custom',
468
471
  },
469
- '& [data-select-trigger]:not(:disabled, [data-disabled], [data-invalid], [aria-invalid="true"])':
472
+ '& [data-select-trigger][role="combobox"]:is(*, :hover, [data-popup-open]):not(:disabled, [data-disabled], [data-invalid], [aria-invalid="true"])':
470
473
  {
471
474
  backgroundColor: 'transparent !custom',
472
475
  },
@@ -612,6 +615,12 @@ namespace styles {
612
615
  boxShadow: 'inset 0 0 0 5px currentColor !custom',
613
616
  color: 'content.primary',
614
617
  },
618
+ // An option row shows its selection with its fill, so its radio stays focusable but unseen.
619
+ '[data-variant="option"] > [data-radio] > &': {
620
+ opacity: '0 !custom',
621
+ pointerEvents: 'none',
622
+ position: 'absolute',
623
+ },
615
624
  },
616
625
  '@media (prefers-reduced-motion: reduce)': { transitionProperty: 'color' },
617
626
  },
@@ -627,6 +636,16 @@ namespace styles {
627
636
  rowGap: '12',
628
637
  typography: 'body.b2',
629
638
  },
639
+ defaultVariants: { variant: 'radio' },
640
+ variants: {
641
+ /**
642
+ * How a row shows its selection: `radio` with a bordered block and a ring, or `option` with
643
+ * a fill and no border.
644
+ *
645
+ * @default 'radio'
646
+ */
647
+ variant: { option: {}, radio: {} },
648
+ },
630
649
  },
631
650
  { id: 'tds/web/list-group/radio-group' },
632
651
  )
@@ -672,7 +691,7 @@ namespace styles {
672
691
  // The block keeps a 16px radius with 16px sides, below the usual one and a half times.
673
692
  variant: {
674
693
  boxed: {
675
- backgroundColor: 'background.secondary',
694
+ backgroundColor: 'background.elevated',
676
695
  borderColor: 'line.secondary',
677
696
  borderRadius: 'sm',
678
697
  borderStyle: 'solid',
@@ -682,7 +701,7 @@ namespace styles {
682
701
  },
683
702
  plain: {},
684
703
  spaced: {
685
- backgroundColor: 'background.secondary',
704
+ backgroundColor: 'background.elevated',
686
705
  borderColor: 'line.secondary',
687
706
  borderRadius: 'sm',
688
707
  borderStyle: 'solid',
@@ -804,10 +823,37 @@ namespace styles {
804
823
  cursor: 'default',
805
824
  opacity: '0.4 !custom',
806
825
  },
826
+ // An option row keeps a transparent 1px border inside its 12px padding. It fills on hover
827
+ // and when selected, and draws the focus ring as an outline.
828
+ '[data-variant="option"] > &[data-radio]': {
829
+ backgroundColor: 'transparent !custom',
830
+ borderColor: 'transparent !custom',
831
+ paddingBlock: 'calc(0.75rem - 1px) !custom',
832
+ paddingInline: 'calc(0.75rem - 1px) !custom',
833
+ transitionProperty: 'background-color, opacity, outline-color, scale !custom',
834
+ },
835
+ '[data-variant="option"] > &[data-radio]:hover:not(:has(> [data-disabled]))': {
836
+ backgroundColor: 'action.tertiary.fill.hover',
837
+ borderColor: 'transparent !custom',
838
+ },
839
+ '[data-variant="option"] > &[data-radio]:has(> [data-checked]):is(*, :hover)': {
840
+ backgroundColor: 'container.subtle',
841
+ borderColor: 'transparent !custom',
842
+ borderWidth: 'regular',
843
+ },
844
+ '[data-variant="option"] > &[data-radio]:has(> [role="radio"]:focus-visible)': {
845
+ borderColor: 'transparent !custom',
846
+ borderWidth: 'regular',
847
+ outlineColor: 'border.focus',
848
+ outlineOffset: '-2',
849
+ },
807
850
  },
808
851
  '@media (prefers-reduced-motion: reduce)': {
809
852
  selectors: {
810
853
  '&[data-radio]': { transitionProperty: 'background-color, border-color, opacity' },
854
+ '[data-variant="option"] > &[data-radio]': {
855
+ transitionProperty: 'background-color, opacity, outline-color',
856
+ },
811
857
  '&[data-radio]:active': { scale: 'none !custom' },
812
858
  },
813
859
  },
@@ -255,9 +255,10 @@ export namespace Select {
255
255
  }
256
256
 
257
257
  /**
258
- * Matches `TextInput.Group` in height, variant, fill, radius and invalid treatment, and spaces
259
- * its parts 6px apart. Focus keeps the fill and draws a 2px ring, which adds 2px to the padding
260
- * on each side. A disabled trigger dims to 40% opacity, and an open one takes the hover fill.
258
+ * Matches `TextInput.Group` in height, variant, radius and invalid treatment, and spaces its parts
259
+ * 6px apart. The `default` trigger takes the quiet control fill, which darkens on hover. Focus
260
+ * keeps the fill and draws a 2px ring, which adds 2px to the padding on each side. A disabled
261
+ * trigger dims to 40% opacity, and an open one takes the hover fill.
261
262
  */
262
263
  export function Trigger(props: Trigger.Props) {
263
264
  const { className, invalid, variant, vars, ...attributes } = props
@@ -338,13 +339,14 @@ namespace styles {
338
339
 
339
340
  // The trigger also takes the shared field surface, whose stylesheet can land anywhere in the
340
341
  // cascade, so each rule here outranks it with an attribute that Base UI sets. Unlike that
341
- // surface, focus keeps the fill and a disabled trigger keeps its colors at 40% opacity.
342
+ // surface, the trigger fills with the quiet control fill, focus keeps the fill, and a disabled
343
+ // trigger keeps its colors at 40% opacity.
342
344
  export const trigger = variants(
343
345
  {
344
346
  base: {
345
347
  selectors: {
346
- '&[data-popup-open]:not([data-invalid], :disabled, [data-disabled])': {
347
- backgroundColor: 'container.strong',
348
+ '&[role="combobox"][data-popup-open]:not([data-invalid], :disabled, [data-disabled])': {
349
+ backgroundColor: 'action.secondary.fill.hover',
348
350
  },
349
351
  '&[role="combobox"]': { columnGap: '6' },
350
352
  // The 2px focus ring adds its width to the padding, so the content moves 2px inward and
@@ -363,11 +365,16 @@ namespace styles {
363
365
  variant: {
364
366
  default: {
365
367
  selectors: {
368
+ '&[role="combobox"]:not([data-invalid], [data-invalid] *)': {
369
+ backgroundColor: 'container.control',
370
+ },
371
+ '&[role="combobox"]:hover:not([data-invalid], [data-invalid] *, :disabled, [data-disabled])':
372
+ { backgroundColor: 'action.secondary.fill.hover' },
366
373
  '&[role="combobox"]:focus-visible:not([data-invalid], [data-invalid] *)': {
367
- backgroundColor: 'container.regular',
374
+ backgroundColor: 'container.control',
368
375
  },
369
376
  '&[role="combobox"]:is(:disabled, [data-disabled])': {
370
- backgroundColor: 'container.regular',
377
+ backgroundColor: 'container.control',
371
378
  },
372
379
  },
373
380
  },
@@ -12,7 +12,9 @@ import {
12
12
  import type * as zyzz from 'zyzz'
13
13
  import { ChevronDown } from '../icons/ChevronDown.js'
14
14
  import { ChevronRight } from '../icons/ChevronRight.js'
15
+ import { Close } from '../icons/Close.js'
15
16
  import { style, variants } from '../zyzz.config.js'
17
+ import { Button } from './Button.js'
16
18
  import * as TabBar from './internal/TabBar.js'
17
19
  import * as Text from './internal/Text.js'
18
20
 
@@ -183,7 +185,8 @@ export namespace SidebarMenu {
183
185
  * with `aria-current="page"`, and sets its label in the strong weight. Keyboard focus draws a
184
186
  * 2px ring inside the row and moves its content 2px inward.
185
187
  *
186
- * Inside an `AppShell`, `tabBar` repeats the item in the bottom bar below 768px, with the same
188
+ * Inside an `AppShell`, `tabBar` repeats the item in the bottom bar wherever the drawer replaces
189
+ * the sidebar (below 1024px, or below 768px with `collapse`), with the same
187
190
  * `href`, `render` and handlers. In an `AppShell` with `collapse`, the sidebar shows only the
188
191
  * first element of each item from 768px to 1023px, such as its icon, and keeps its text as the
189
192
  * accessible name.
@@ -232,7 +235,7 @@ export namespace SidebarMenu {
232
235
  Omit<zyzz.Props.Variants<typeof styles.item>, 'active'> & {
233
236
  active?: boolean | undefined
234
237
  /**
235
- * Repeats the item in the bottom bar of an `AppShell` below 768px: `tab` as an icon in the
238
+ * Repeats the item in the bottom bar of an `AppShell` wherever the drawer replaces the sidebar: `tab` as an icon in the
236
239
  * tab bar, or `action` as the floating action button beside it. Give the tab bar up to
237
240
  * three items and one action. Absent, the item stays in the sidebar drawer only.
238
241
  */
@@ -262,6 +265,101 @@ export namespace SidebarMenu {
262
265
  export type Props = ComponentPropsWithRef<'span'> & zyzz.Props.Variants<typeof styles.logo>
263
266
  }
264
267
 
268
+ /**
269
+ * Renders the brand at the top of a sidebar, such as a 20px `TempoLogo`, in the primary content
270
+ * color and 12px from the start edge of the items. Put it in `AppShell.SidebarHeader`. In an
271
+ * `AppShell` with `collapse`, it centers its content from 768px to 1023px, which fits
272
+ * `TempoLogo` with `type="mark"`.
273
+ *
274
+ * @example
275
+ * <AppShell.SidebarHeader>
276
+ * <SidebarMenu.Mark>
277
+ * <TempoLogo aria-label="Tempo" />
278
+ * </SidebarMenu.Mark>
279
+ * </AppShell.SidebarHeader>
280
+ */
281
+ export function Mark(props: Mark.Props) {
282
+ const { className, vars, ...attributes } = props
283
+
284
+ return <div {...attributes} {...styles.mark({ className, style: props.style, vars })} />
285
+ }
286
+
287
+ export namespace Mark {
288
+ export type Props = ComponentPropsWithRef<'div'> & zyzz.Props.Variants<typeof styles.mark>
289
+ }
290
+
291
+ /**
292
+ * Renders a card at the bottom of a sidebar that promotes one task, such as setting up a
293
+ * passkey: `title`, an optional dismiss button, `children` as the description, then `action`,
294
+ * which spans the card. The card is a `group` named by its title. Put it in
295
+ * `AppShell.SidebarFooter`, above the account row. The collapsed sidebar of an `AppShell` with
296
+ * `collapse` hides it from 768px to 1023px.
297
+ *
298
+ * @example
299
+ * <SidebarMenu.Promo
300
+ * action={
301
+ * <Button size="small" variant="secondary">
302
+ * Set up
303
+ * </Button>
304
+ * }
305
+ * onDismiss={dismiss}
306
+ * title="Set up a passkey"
307
+ * >
308
+ * Sign in with Face ID or Touch ID instead of a code.
309
+ * </SidebarMenu.Promo>
310
+ */
311
+ export function Promo(props: Promo.Props) {
312
+ const { action, children, className, onDismiss, title, vars, ...attributes } = props
313
+ const id = useId()
314
+
315
+ return (
316
+ <div
317
+ aria-labelledby={id}
318
+ role="group"
319
+ {...attributes}
320
+ {...styles.promo({ className, style: props.style, vars })}
321
+ >
322
+ <div {...styles.promoHeader()}>
323
+ <span id={id} {...styles.promoTitle()}>
324
+ {title}
325
+ </span>
326
+ {onDismiss && (
327
+ <Button
328
+ aria-label="Dismiss"
329
+ onClick={onDismiss}
330
+ shape="icon"
331
+ size="small"
332
+ variant="tertiary"
333
+ >
334
+ <Close />
335
+ </Button>
336
+ )}
337
+ </div>
338
+ {children !== undefined && children !== null && (
339
+ <p {...styles.promoDescription()}>{children}</p>
340
+ )}
341
+ {action}
342
+ </div>
343
+ )
344
+ }
345
+
346
+ export namespace Promo {
347
+ export type Props = Omit<ComponentPropsWithRef<'div'>, 'title'> &
348
+ zyzz.Props.Variants<typeof styles.promo> & {
349
+ /** The call to action under the description, such as a small `secondary` `Button`. */
350
+ action?: ReactNode | undefined
351
+ /** The description in the secondary color. Absent, the action follows the title. */
352
+ children?: ReactNode | undefined
353
+ /**
354
+ * Called when the dismiss button is pressed. Remove the card in it. Absent, the card
355
+ * renders no dismiss button.
356
+ */
357
+ onDismiss?: (() => void) | undefined
358
+ /** The heading of the card, which names its group. */
359
+ title: ReactNode
360
+ }
361
+ }
362
+
265
363
  /**
266
364
  * Renders the `<nav>` landmark, labelled `Main` unless you pass `aria-label`. Give each
267
365
  * landmark on a page a distinct label. `size` sets 32, 36 or 56px rows, and `small` also uses
@@ -504,12 +602,12 @@ namespace styles {
504
602
  base: {
505
603
  display: 'flex',
506
604
  flexDirection: 'column',
507
- rowGap: '2',
605
+ rowGap: '4',
508
606
  selectors: {
509
607
  // With the row gap of the menu, groups sit 24px below preceding content, or 16px in a
510
608
  // small menu.
511
- '* + &': { marginTop: '1.375rem !custom' },
512
- '[data-sidebar-menu-size="large"] * + &': { marginTop: '20' },
609
+ '* + &': { marginTop: '20' },
610
+ '[data-sidebar-menu-size="small"] &': { rowGap: '2' },
513
611
  '[data-sidebar-menu-size="small"] * + &': { marginTop: '0.875rem !custom' },
514
612
  },
515
613
  },
@@ -654,11 +752,64 @@ namespace styles {
654
752
  { id: 'tds/web/sidebar-menu/logo' },
655
753
  )
656
754
 
755
+ export const mark = variants(
756
+ {
757
+ base: {
758
+ alignItems: 'center',
759
+ color: 'content.primary',
760
+ display: 'flex',
761
+ paddingInlineStart: '12',
762
+ '@media (min-width: 768px) and (max-width: 1023px)': {
763
+ selectors: {
764
+ '[data-app-shell-rail] &': { justifyContent: 'center', paddingInlineStart: '0' },
765
+ },
766
+ },
767
+ },
768
+ },
769
+ { id: 'tds/web/sidebar-menu/mark' },
770
+ )
771
+
772
+ // A box at the `sm` radius pads by 16px.
773
+ export const promo = variants(
774
+ {
775
+ base: {
776
+ backgroundColor: 'container.regular',
777
+ borderRadius: 'sm',
778
+ boxSizing: 'border-box',
779
+ display: 'flex',
780
+ flexDirection: 'column',
781
+ padding: '16',
782
+ rowGap: '12',
783
+ '@media (min-width: 768px) and (max-width: 1023px)': {
784
+ selectors: { '[data-app-shell-rail] &': { display: 'none !custom' } },
785
+ },
786
+ },
787
+ },
788
+ { id: 'tds/web/sidebar-menu/promo' },
789
+ )
790
+
791
+ export const promoDescription = style(
792
+ { color: 'content.secondary', margin: '0', typography: 'body.b3' },
793
+ { id: 'tds/web/sidebar-menu/promo-description' },
794
+ )
795
+
796
+ // The dismiss button aligns its top edge with the first line of the title.
797
+ export const promoHeader = style(
798
+ { alignItems: 'flex-start', columnGap: '8', display: 'flex' },
799
+ { id: 'tds/web/sidebar-menu/promo-header' },
800
+ )
801
+
802
+ export const promoTitle = style(
803
+ { color: 'content.primary', flex: '1 !custom', minWidth: '0', typography: 'body.b2Strong' },
804
+ { id: 'tds/web/sidebar-menu/promo-title' },
805
+ )
806
+
657
807
  export const root = variants(
658
808
  {
659
- base: { display: 'flex', flexDirection: 'column', minWidth: '0', rowGap: '2' },
809
+ // The Platform Wallet Sidebar spaces its items by 4px. Small rows keep 2px.
810
+ base: { display: 'flex', flexDirection: 'column', minWidth: '0', rowGap: '4' },
660
811
  defaultVariants: { size: 'medium' },
661
- variants: { size: { large: { rowGap: '4' }, medium: {}, small: {} } },
812
+ variants: { size: { large: {}, medium: {}, small: { rowGap: '2' } } },
662
813
  },
663
814
  { id: 'tds/web/sidebar-menu' },
664
815
  )
@@ -669,11 +820,14 @@ namespace styles {
669
820
  flexDirection: 'column',
670
821
  // Nested labels line up with the trigger's label: icon width plus the icon gap.
671
822
  paddingInlineStart: '32',
672
- paddingTop: '2',
673
- rowGap: '2',
823
+ paddingTop: '4',
824
+ rowGap: '4',
674
825
  selectors: {
675
- '[data-sidebar-menu-size="large"] &': { paddingTop: '4', rowGap: '4' },
676
- '[data-sidebar-menu-size="small"] &': { paddingInlineStart: '24' },
826
+ '[data-sidebar-menu-size="small"] &': {
827
+ paddingInlineStart: '24',
828
+ paddingTop: '2',
829
+ rowGap: '2',
830
+ },
677
831
  },
678
832
  '@media (min-width: 768px) and (max-width: 1023px)': {
679
833
  selectors: { '[data-app-shell-rail] &': { paddingInlineStart: '0' } },