@eifi1/ui-kit 0.48.2 → 0.50.0

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 (377) hide show
  1. package/README.md +34 -16
  2. package/dist/admin/admin-action-confirm.js +1 -1
  3. package/dist/admin/admin-action-confirm.js.map +1 -1
  4. package/dist/admin/invitations-panel.js +5 -1
  5. package/dist/admin/invitations-panel.js.map +1 -1
  6. package/dist/admin/user-roster.d.ts +24 -12
  7. package/dist/admin/user-roster.js +21 -6
  8. package/dist/admin/user-roster.js.map +1 -1
  9. package/dist/auth/accept-invitation.js +2 -2
  10. package/dist/auth/accept-invitation.js.map +1 -1
  11. package/dist/auth/not-found-page.js +1 -1
  12. package/dist/auth/not-found-page.js.map +1 -1
  13. package/dist/auth/reset-password-form.js +2 -2
  14. package/dist/auth/reset-password-form.js.map +1 -1
  15. package/dist/auth/verify-email.d.ts +9 -1
  16. package/dist/auth/verify-email.js +5 -3
  17. package/dist/auth/verify-email.js.map +1 -1
  18. package/dist/billing/plan-card.js +2 -2
  19. package/dist/billing/plan-card.js.map +1 -1
  20. package/dist/components/amount-input.d.ts +2 -1
  21. package/dist/components/amount-input.js +3 -2
  22. package/dist/components/amount-input.js.map +1 -1
  23. package/dist/components/autocomplete.js +1 -0
  24. package/dist/components/autocomplete.js.map +1 -1
  25. package/dist/components/breadcrumbs.d.ts +19 -1
  26. package/dist/components/breadcrumbs.js +11 -4
  27. package/dist/components/breadcrumbs.js.map +1 -1
  28. package/dist/components/button-look.d.ts +37 -7
  29. package/dist/components/button-look.js +33 -6
  30. package/dist/components/button-look.js.map +1 -1
  31. package/dist/components/button.d.ts +4 -1
  32. package/dist/components/button.js +2 -2
  33. package/dist/components/button.js.map +1 -1
  34. package/dist/components/calendar-heatmap.js +2 -1
  35. package/dist/components/calendar-heatmap.js.map +1 -1
  36. package/dist/components/chart-edge.d.ts +10 -0
  37. package/dist/components/chart-edge.js +57 -0
  38. package/dist/components/chart-edge.js.map +1 -0
  39. package/dist/components/chart.js +2 -1
  40. package/dist/components/chart.js.map +1 -1
  41. package/dist/components/chip.d.ts +8 -1
  42. package/dist/components/chip.js +4 -1
  43. package/dist/components/chip.js.map +1 -1
  44. package/dist/components/combobox-core.js +1 -0
  45. package/dist/components/combobox-core.js.map +1 -1
  46. package/dist/components/combobox-suggestions.js +1 -0
  47. package/dist/components/combobox-suggestions.js.map +1 -1
  48. package/dist/components/confirm-context.d.ts +5 -0
  49. package/dist/components/confirm-context.js +7 -0
  50. package/dist/components/confirm-context.js.map +1 -0
  51. package/dist/components/confirm-dialog.js +3 -3
  52. package/dist/components/confirm-dialog.js.map +1 -1
  53. package/dist/components/currency-select.d.ts +28 -2
  54. package/dist/components/currency-select.js +56 -10
  55. package/dist/components/currency-select.js.map +1 -1
  56. package/dist/components/danger-confirm.d.ts +2 -2
  57. package/dist/components/danger-confirm.js +1 -1
  58. package/dist/components/danger-confirm.js.map +1 -1
  59. package/dist/components/data-table-fit.js +21 -3
  60. package/dist/components/data-table-fit.js.map +1 -1
  61. package/dist/components/data-table-mobile.d.ts +58 -3
  62. package/dist/components/data-table-mobile.js +147 -16
  63. package/dist/components/data-table-mobile.js.map +1 -1
  64. package/dist/components/data-table-row-actions.d.ts +2 -1
  65. package/dist/components/data-table-row-actions.js +7 -0
  66. package/dist/components/data-table-row-actions.js.map +1 -1
  67. package/dist/components/data-table-row.d.ts +1 -1
  68. package/dist/components/data-table-row.js.map +1 -1
  69. package/dist/components/data-table.d.ts +34 -1
  70. package/dist/components/data-table.js +37 -10
  71. package/dist/components/data-table.js.map +1 -1
  72. package/dist/components/dropdown.js +1 -0
  73. package/dist/components/dropdown.js.map +1 -1
  74. package/dist/components/error-boundary.js +2 -2
  75. package/dist/components/error-boundary.js.map +1 -1
  76. package/dist/components/floating-panel.d.ts +36 -13
  77. package/dist/components/floating-panel.js +27 -7
  78. package/dist/components/floating-panel.js.map +1 -1
  79. package/dist/components/form-actions.d.ts +1 -1
  80. package/dist/components/form-actions.js +1 -1
  81. package/dist/components/form-actions.js.map +1 -1
  82. package/dist/components/full-bleed-dialog.js +1 -0
  83. package/dist/components/full-bleed-dialog.js.map +1 -1
  84. package/dist/components/icon-button.d.ts +11 -0
  85. package/dist/components/icon-button.js +7 -5
  86. package/dist/components/icon-button.js.map +1 -1
  87. package/dist/components/modal.js +1 -0
  88. package/dist/components/modal.js.map +1 -1
  89. package/dist/components/nav-pills.d.ts +8 -1
  90. package/dist/components/nav-pills.js +61 -38
  91. package/dist/components/nav-pills.js.map +1 -1
  92. package/dist/components/number-input.d.ts +2 -1
  93. package/dist/components/number-input.js +6 -2
  94. package/dist/components/number-input.js.map +1 -1
  95. package/dist/components/numpad-sheet.js +2 -0
  96. package/dist/components/numpad-sheet.js.map +1 -1
  97. package/dist/components/page-header-context.d.ts +8 -0
  98. package/dist/components/page-header-context.js +7 -0
  99. package/dist/components/page-header-context.js.map +1 -0
  100. package/dist/components/page-header.d.ts +14 -2
  101. package/dist/components/page-header.js +10 -1
  102. package/dist/components/page-header.js.map +1 -1
  103. package/dist/components/picker-sheet.js +2 -0
  104. package/dist/components/picker-sheet.js.map +1 -1
  105. package/dist/components/pie-chart.js +2 -1
  106. package/dist/components/pie-chart.js.map +1 -1
  107. package/dist/components/popover.js +1 -0
  108. package/dist/components/popover.js.map +1 -1
  109. package/dist/components/progress-bar.js +5 -4
  110. package/dist/components/progress-bar.js.map +1 -1
  111. package/dist/components/reauth-dialog.js +1 -1
  112. package/dist/components/reauth-dialog.js.map +1 -1
  113. package/dist/components/row-actions-labels.js +3 -1
  114. package/dist/components/row-actions-labels.js.map +1 -1
  115. package/dist/components/row-actions.d.ts +43 -9
  116. package/dist/components/row-actions.js +78 -15
  117. package/dist/components/row-actions.js.map +1 -1
  118. package/dist/components/section-nav.d.ts +37 -0
  119. package/dist/components/section-nav.js +33 -0
  120. package/dist/components/section-nav.js.map +1 -0
  121. package/dist/components/series-chart.js +8 -3
  122. package/dist/components/series-chart.js.map +1 -1
  123. package/dist/components/share-card.js +1 -1
  124. package/dist/components/share-card.js.map +1 -1
  125. package/dist/components/signature-pad.js +1 -1
  126. package/dist/components/signature-pad.js.map +1 -1
  127. package/dist/components/sparkline.js +2 -1
  128. package/dist/components/sparkline.js.map +1 -1
  129. package/dist/components/stat-tile.js +14 -7
  130. package/dist/components/stat-tile.js.map +1 -1
  131. package/dist/components/status-dot.d.ts +5 -1
  132. package/dist/components/status-dot.js +4 -1
  133. package/dist/components/status-dot.js.map +1 -1
  134. package/dist/components/tooltip.js +1 -1
  135. package/dist/components/tooltip.js.map +1 -1
  136. package/dist/components/translation-review-editor.js +2 -2
  137. package/dist/components/translation-review-editor.js.map +1 -1
  138. package/dist/components/translation-review.js +2 -2
  139. package/dist/components/translation-review.js.map +1 -1
  140. package/dist/components/treemap.d.ts +5 -2
  141. package/dist/components/treemap.js +26 -6
  142. package/dist/components/treemap.js.map +1 -1
  143. package/dist/components/use-table-state.js +37 -6
  144. package/dist/components/use-table-state.js.map +1 -1
  145. package/dist/data-table.js.map +1 -1
  146. package/dist/demo/demo-banner.js +1 -1
  147. package/dist/demo/demo-banner.js.map +1 -1
  148. package/dist/demo/demo-ended.js +1 -1
  149. package/dist/demo/demo-ended.js.map +1 -1
  150. package/dist/demo/demo-start.js +1 -1
  151. package/dist/demo/demo-start.js.map +1 -1
  152. package/dist/eslint/index.js +1 -1
  153. package/dist/feedback/feedback-inbox.js +1 -1
  154. package/dist/feedback/feedback-inbox.js.map +1 -1
  155. package/dist/hooks/use-dismiss.d.ts +9 -1
  156. package/dist/hooks/use-dismiss.js +25 -5
  157. package/dist/hooks/use-dismiss.js.map +1 -1
  158. package/dist/hooks/use-document-title.d.ts +14 -0
  159. package/dist/hooks/use-document-title.js +22 -0
  160. package/dist/hooks/use-document-title.js.map +1 -0
  161. package/dist/i18n/defaults.js +2 -0
  162. package/dist/i18n/defaults.js.map +1 -1
  163. package/dist/i18n/german.js +20 -3
  164. package/dist/i18n/german.js.map +1 -1
  165. package/dist/i18n/kit-labels.d.ts +34 -1
  166. package/dist/i18n/kit-labels.js +43 -2
  167. package/dist/i18n/kit-labels.js.map +1 -1
  168. package/dist/i18n/locales/en.js +22 -3
  169. package/dist/i18n/locales/en.js.map +3 -3
  170. package/dist/i18n/locales/es.js +20 -3
  171. package/dist/i18n/locales/es.js.map +1 -1
  172. package/dist/i18n/locales/fr.js +20 -3
  173. package/dist/i18n/locales/fr.js.map +1 -1
  174. package/dist/i18n/locales/hu.js +20 -3
  175. package/dist/i18n/locales/hu.js.map +1 -1
  176. package/dist/i18n/locales/it.js +20 -3
  177. package/dist/i18n/locales/it.js.map +1 -1
  178. package/dist/i18n/locales/zh.js +20 -3
  179. package/dist/i18n/locales/zh.js.map +1 -1
  180. package/dist/i18n/review.js +3 -0
  181. package/dist/i18n/review.js.map +1 -1
  182. package/dist/i18n/shared-labels.js +1 -0
  183. package/dist/i18n/shared-labels.js.map +1 -1
  184. package/dist/index.d.ts +8 -1
  185. package/dist/index.js +11 -0
  186. package/dist/index.js.map +1 -1
  187. package/dist/landing/landing-actions.js +2 -2
  188. package/dist/landing/landing-actions.js.map +1 -1
  189. package/dist/landing/public-header.js +2 -2
  190. package/dist/landing/public-header.js.map +1 -1
  191. package/dist/lib/clipboard-minus.d.ts +27 -0
  192. package/dist/lib/clipboard-minus.js +63 -0
  193. package/dist/lib/clipboard-minus.js.map +1 -0
  194. package/dist/lib/data-attributes.d.ts +11 -0
  195. package/dist/lib/data-attributes.js +10 -0
  196. package/dist/lib/data-attributes.js.map +1 -0
  197. package/dist/lib/document-title.d.ts +17 -0
  198. package/dist/lib/document-title.js +29 -0
  199. package/dist/lib/document-title.js.map +1 -0
  200. package/dist/lib/float-clearance.js +8 -2
  201. package/dist/lib/float-clearance.js.map +1 -1
  202. package/dist/lib/format.d.ts +36 -10
  203. package/dist/lib/format.js +57 -11
  204. package/dist/lib/format.js.map +1 -1
  205. package/dist/lib/minus.d.ts +32 -0
  206. package/dist/lib/minus.js +14 -0
  207. package/dist/lib/minus.js.map +1 -0
  208. package/dist/lib/status-tone.d.ts +30 -0
  209. package/dist/lib/status-tone.js +17 -0
  210. package/dist/lib/status-tone.js.map +1 -0
  211. package/dist/settings/settings-layout.js +3 -2
  212. package/dist/settings/settings-layout.js.map +1 -1
  213. package/dist/shell/account-menu-language.d.ts +1 -1
  214. package/dist/shell/account-menu-language.js +9 -1
  215. package/dist/shell/account-menu-language.js.map +1 -1
  216. package/dist/shell/app-shell-labels.js +2 -1
  217. package/dist/shell/app-shell-labels.js.map +1 -1
  218. package/dist/shell/app-shell.d.ts +58 -16
  219. package/dist/shell/app-shell.js +151 -43
  220. package/dist/shell/app-shell.js.map +1 -1
  221. package/dist/shell/language-code.d.ts +3 -0
  222. package/dist/shell/language-code.js +8 -0
  223. package/dist/shell/language-code.js.map +1 -0
  224. package/dist/shell/topbar-controls.d.ts +14 -6
  225. package/dist/shell/topbar-controls.js +14 -17
  226. package/dist/shell/topbar-controls.js.map +1 -1
  227. package/dist/testing/css-variables.generated.d.ts +1 -1
  228. package/dist/testing/css-variables.generated.js +1 -1
  229. package/dist/testing/css-variables.generated.js.map +1 -1
  230. package/dist/testing/react.d.ts +25 -0
  231. package/dist/testing/react.js +19 -0
  232. package/dist/testing/react.js.map +1 -0
  233. package/dist/theme/chart-palette.d.ts +82 -15
  234. package/dist/theme/chart-palette.js +51 -24
  235. package/dist/theme/chart-palette.js.map +1 -1
  236. package/dist/theme/palette-presets.d.ts +9 -0
  237. package/dist/theme/palette-presets.js +77 -62
  238. package/dist/theme/palette-presets.js.map +1 -1
  239. package/dist/tour/tour-dock.d.ts +42 -0
  240. package/dist/tour/tour-dock.js +80 -0
  241. package/dist/tour/tour-dock.js.map +1 -0
  242. package/dist/tour/tour-gates.d.ts +71 -0
  243. package/dist/tour/tour-gates.js +114 -0
  244. package/dist/tour/tour-gates.js.map +1 -0
  245. package/dist/tour/tour-guide-button.d.ts +27 -0
  246. package/dist/tour/tour-guide-button.js +45 -0
  247. package/dist/tour/tour-guide-button.js.map +1 -0
  248. package/dist/tour/tour-guide-labels.d.ts +8 -0
  249. package/dist/tour/tour-guide-labels.js +9 -0
  250. package/dist/tour/tour-guide-labels.js.map +1 -0
  251. package/dist/tour/tour-labels.js +9 -1
  252. package/dist/tour/tour-labels.js.map +1 -1
  253. package/dist/tour/tour.d.ts +133 -9
  254. package/dist/tour/tour.js +594 -70
  255. package/dist/tour/tour.js.map +1 -1
  256. package/dist/tour/use-tour-url-sync.d.ts +5 -2
  257. package/dist/tour/use-tour-url-sync.js +7 -1
  258. package/dist/tour/use-tour-url-sync.js.map +1 -1
  259. package/dist/tour.d.ts +2 -0
  260. package/dist/tour.js +3 -0
  261. package/dist/tour.js.map +1 -1
  262. package/dist/wizard/stepper-nav.d.ts +1 -1
  263. package/dist/wizard/stepper-nav.js +18 -5
  264. package/dist/wizard/stepper-nav.js.map +1 -1
  265. package/dist/wizard/types.d.ts +11 -0
  266. package/dist/wizard/types.js.map +1 -1
  267. package/dist/wizard/use-wizard.js +11 -5
  268. package/dist/wizard/use-wizard.js.map +1 -1
  269. package/package.json +9 -1
  270. package/src/admin/admin-action-confirm.tsx +1 -1
  271. package/src/admin/invitations-panel.tsx +7 -3
  272. package/src/admin/user-roster.tsx +41 -14
  273. package/src/auth/accept-invitation.tsx +3 -3
  274. package/src/auth/not-found-page.tsx +1 -1
  275. package/src/auth/reset-password-form.tsx +3 -3
  276. package/src/auth/verify-email.tsx +13 -3
  277. package/src/billing/plan-card.tsx +2 -2
  278. package/src/components/amount-input.tsx +5 -4
  279. package/src/components/autocomplete.tsx +2 -0
  280. package/src/components/breadcrumbs.tsx +32 -4
  281. package/src/components/button-look.ts +86 -15
  282. package/src/components/button.tsx +6 -3
  283. package/src/components/calendar-heatmap.tsx +2 -1
  284. package/src/components/chart-edge.ts +73 -0
  285. package/src/components/chart.tsx +3 -1
  286. package/src/components/chip.tsx +10 -1
  287. package/src/components/combobox-core.tsx +2 -0
  288. package/src/components/combobox-suggestions.tsx +2 -0
  289. package/src/components/confirm-context.ts +7 -0
  290. package/src/components/confirm-dialog.tsx +3 -3
  291. package/src/components/currency-select.tsx +95 -6
  292. package/src/components/danger-confirm.tsx +3 -3
  293. package/src/components/data-table-fit.ts +41 -6
  294. package/src/components/data-table-mobile.tsx +259 -17
  295. package/src/components/data-table-row-actions.tsx +9 -1
  296. package/src/components/data-table-row.tsx +3 -0
  297. package/src/components/data-table.tsx +91 -19
  298. package/src/components/dropdown.tsx +2 -0
  299. package/src/components/error-boundary.tsx +2 -2
  300. package/src/components/floating-panel.tsx +67 -16
  301. package/src/components/form-actions.tsx +2 -2
  302. package/src/components/full-bleed-dialog.tsx +3 -0
  303. package/src/components/icon-button.tsx +21 -8
  304. package/src/components/modal.tsx +3 -0
  305. package/src/components/nav-pills.tsx +29 -2
  306. package/src/components/number-input.tsx +8 -3
  307. package/src/components/numpad-sheet.tsx +4 -0
  308. package/src/components/page-header-context.ts +8 -0
  309. package/src/components/page-header.tsx +34 -2
  310. package/src/components/picker-sheet.tsx +4 -0
  311. package/src/components/pie-chart.tsx +2 -1
  312. package/src/components/popover.tsx +2 -0
  313. package/src/components/progress-bar.tsx +6 -3
  314. package/src/components/reauth-dialog.tsx +1 -1
  315. package/src/components/row-actions-labels.ts +2 -0
  316. package/src/components/row-actions.tsx +154 -33
  317. package/src/components/section-nav.tsx +65 -0
  318. package/src/components/series-chart.tsx +12 -3
  319. package/src/components/share-card.tsx +1 -1
  320. package/src/components/signature-pad.tsx +1 -1
  321. package/src/components/sparkline.tsx +2 -1
  322. package/src/components/stat-tile.tsx +19 -7
  323. package/src/components/status-dot.tsx +7 -1
  324. package/src/components/tooltip.tsx +3 -1
  325. package/src/components/translation-review-editor.tsx +2 -2
  326. package/src/components/translation-review.tsx +2 -2
  327. package/src/components/treemap.tsx +34 -8
  328. package/src/components/use-table-state.ts +63 -8
  329. package/src/data-table.ts +3 -3
  330. package/src/demo/demo-banner.tsx +1 -1
  331. package/src/demo/demo-ended.tsx +1 -1
  332. package/src/demo/demo-start.tsx +1 -1
  333. package/src/feedback/feedback-inbox.tsx +1 -1
  334. package/src/hooks/use-dismiss.ts +43 -4
  335. package/src/hooks/use-document-title.ts +31 -0
  336. package/src/i18n/defaults.ts +2 -0
  337. package/src/i18n/german.ts +17 -0
  338. package/src/i18n/kit-labels.tsx +79 -1
  339. package/src/i18n/locales/es.ts +17 -0
  340. package/src/i18n/locales/fr.ts +17 -0
  341. package/src/i18n/locales/hu.ts +17 -0
  342. package/src/i18n/locales/it.ts +17 -0
  343. package/src/i18n/locales/zh.ts +17 -0
  344. package/src/i18n/review.ts +3 -0
  345. package/src/i18n/shared-labels.ts +1 -0
  346. package/src/index.ts +13 -0
  347. package/src/landing/landing-actions.tsx +2 -2
  348. package/src/landing/public-header.tsx +2 -2
  349. package/src/lib/clipboard-minus.ts +94 -0
  350. package/src/lib/data-attributes.ts +14 -0
  351. package/src/lib/document-title.ts +41 -0
  352. package/src/lib/float-clearance.ts +19 -3
  353. package/src/lib/format.ts +117 -30
  354. package/src/lib/minus.ts +50 -0
  355. package/src/lib/status-tone.ts +43 -0
  356. package/src/settings/settings-layout.tsx +3 -2
  357. package/src/shell/account-menu-language.tsx +14 -2
  358. package/src/shell/app-shell-labels.ts +1 -0
  359. package/src/shell/app-shell.tsx +291 -68
  360. package/src/shell/language-code.ts +5 -0
  361. package/src/shell/topbar-controls.tsx +35 -15
  362. package/src/testing/css-variables.generated.ts +6 -6
  363. package/src/testing/react.ts +59 -0
  364. package/src/theme/chart-palette.ts +102 -20
  365. package/src/theme/palette-presets.ts +121 -84
  366. package/src/tour/tour-dock.ts +132 -0
  367. package/src/tour/tour-gates.ts +195 -0
  368. package/src/tour/tour-guide-button.tsx +64 -0
  369. package/src/tour/tour-guide-labels.ts +15 -0
  370. package/src/tour/tour-labels.ts +8 -0
  371. package/src/tour/tour.tsx +949 -86
  372. package/src/tour/use-tour-url-sync.ts +14 -4
  373. package/src/tour.ts +3 -0
  374. package/src/wizard/stepper-nav.tsx +27 -7
  375. package/src/wizard/types.ts +12 -0
  376. package/src/wizard/use-wizard.ts +16 -9
  377. package/tokens.css +28 -18
@@ -38,8 +38,9 @@ import { DEFAULT_USER_ROSTER_LABELS } from "./user-roster-labels";
38
38
  *
39
39
  * The column keys ARE the server's sort keys (§3.1): `name`, `email`, `role`, `created`,
40
40
  * `last_login` — so {@link userRosterSort} turns the table's sort state into the query
41
- * as it is. Phone cards come from the same columns: the identity is the card's headline,
42
- * the role, states, last login and the actions are its fields, created is left off.
41
+ * as it is. Phone cards come from the same columns: the identity is the card's headline
42
+ * with the account's states as chips beside it, the role, last login and the actions are
43
+ * its three fields, created is left off.
43
44
  */
44
45
 
45
46
  /* ── Rows and states ────────────────────────────────────────────────────────── */
@@ -327,16 +328,27 @@ function StateChips({ states }: { states: readonly RosterState[] }) {
327
328
  /**
328
329
  * The roster's columns, in the contract's order (§3.3):
329
330
  *
330
- * | key | cell | sort | phone |
331
- * |---|---|---|---|
332
- * | `name` | {@link UserIdentityCell} | last, first | the card's headline |
333
- * | `email` | the address (`emailColumn` only) | address | hidden |
334
- * | `role` | `RoleChip`(s), or {@link RoleSelect} where `editableRole` | role | field |
335
- * | `state` | `AccountStateChip`s from `stateOf` | — | field |
336
- * | `created` | `dateColumn` | instant | hidden |
337
- * | `last_login` | `dateColumn`, relative, "Never" | instant | field |
338
- * | …`extra` | the app's | the app's | the app's |
339
- * | `actions` | {@link UserRowActions} | — | field |
331
+ * | key | cell | sort | phone | fit (0.50) |
332
+ * |---|---|---|---|---|
333
+ * | `name` | {@link UserIdentityCell} | last, first | the card's headline | `grow` |
334
+ * | `email` | the address (`emailColumn` only) | address | hidden | — |
335
+ * | `role` | `RoleChip`(s), or {@link RoleSelect} where `editableRole` | role | field | — |
336
+ * | `state` | `AccountStateChip`s from `stateOf` | — | chips beside the headline | — |
337
+ * | `created` | `dateColumn` | instant | hidden | `fitPriority: 1` |
338
+ * | `last_login` | `dateColumn`, relative, "Never" | instant | field | `fitPriority: 2` |
339
+ * | …`extra` | the app's | the app's | off the card unless marked `mobile: "secondary"` | the app's |
340
+ * | `actions` | {@link UserRowActions} | — | the card's actions row (`role: "actions"`) | — |
341
+ *
342
+ * The fit (0.50, kastlan K2): Name takes the rest of the row, and a table narrower than
343
+ * its columns leaves out Created, then Last login (DataTable's `grow` / `fitPriority`).
344
+ * The role and state chips still wrap in their cells (0.48), and the other cells keep one
345
+ * line at Normal. `{ ...col, grow: undefined, fitPriority: undefined }` restores the 0.49
346
+ * layout; an `extra` column takes a `fitPriority` of its own to leave too.
347
+ *
348
+ * The card holds three fields — `role`, `last_login` and one more: an `extra` column marked
349
+ * `mobile: "secondary"` takes that place. The actions are `role: "actions"` (0.49), drawn
350
+ * after the fields and never one of the three. Drop them from the card with
351
+ * `mobile: "hidden"`, or draw a `mobileCard`.
340
352
  *
341
353
  * Pure: hand it resolved words, or use {@link useUserRosterColumns}. The keys are stable,
342
354
  * so an app may drop or move a column (`columns.filter((c) => c.key !== "created")`).
@@ -379,6 +391,9 @@ export function userRosterColumns<T extends UserRosterRow, R extends string = st
379
391
  .filter(Boolean)
380
392
  .join(" "),
381
393
  mobilePrimary: true,
394
+ // Takes the rest of the row and ellipsises (0.50, kastlan K2): the roster fits its
395
+ // pane — a settings column, an admin page — instead of scrolling sideways.
396
+ grow: true,
382
397
  },
383
398
  ];
384
399
  if (emailColumn) {
@@ -437,17 +452,26 @@ export function userRosterColumns<T extends UserRosterRow, R extends string = st
437
452
  getValue: (row) => rolesOf(row, roleOf)[0] ?? "",
438
453
  options: (Object.keys(roles) as R[]).map((value) => ({ value, label: roles[value].label })),
439
454
  },
455
+ mobile: "secondary",
440
456
  },
441
457
  {
442
458
  key: "state",
443
459
  header: labels.state,
444
- cell: (row) => <StateChips states={stateOf(row)} />,
460
+ // No states, no cell: the phone card then shows no chip beside the name.
461
+ cell: (row) => {
462
+ const states = stateOf(row);
463
+ return states.length === 0 ? null : <StateChips states={states} />;
464
+ },
465
+ // Beside the name on the phone card, so the role, last login and the actions keep
466
+ // the card's three fields.
467
+ role: "status",
445
468
  },
446
469
  dateColumn<T>({
447
470
  key: "created",
448
471
  header: labels.created,
449
472
  value: (row) => row.created_at ?? null,
450
- column: { mobileHidden: true },
473
+ // The first to leave a narrow table (0.50, kastlan K2), then last login.
474
+ column: { mobileHidden: true, fitPriority: 1 },
451
475
  }),
452
476
  // How long ago rather than a date — the question is "recently?" — with the exact
453
477
  // time beside it (keksdose's last-seen column, Kurvenschmiede's; 0.32 §4: never only
@@ -458,6 +482,7 @@ export function userRosterColumns<T extends UserRosterRow, R extends string = st
458
482
  value: (row) => row.last_login_at ?? null,
459
483
  display: "relative",
460
484
  empty: labels.never,
485
+ column: { mobile: "secondary", fitPriority: 2 },
461
486
  }),
462
487
  ...extra,
463
488
  );
@@ -479,6 +504,8 @@ export function userRosterColumns<T extends UserRosterRow, R extends string = st
479
504
  headClassName: "w-px text-end",
480
505
  // The menu button keeps its own click: a row link must not wrap it.
481
506
  noRowLink: true,
507
+ // Always on the phone card, after the fields and never one of the three (0.49).
508
+ role: "actions",
482
509
  });
483
510
  }
484
511
  return columns;
@@ -147,7 +147,7 @@ export function AcceptInvitation({
147
147
  ? "accepting"
148
148
  : "signIn";
149
149
 
150
- const continueButton = (variant: "brand" | "secondary") => (
150
+ const continueButton = (variant: "accent" | "secondary") => (
151
151
  <Button href={continueHref} replace variant={variant}>
152
152
  {labels.continue}
153
153
  </Button>
@@ -162,7 +162,7 @@ export function AcceptInvitation({
162
162
  tone="success"
163
163
  icon={<CheckCircle2 />}
164
164
  title={settled?.name ? labels.joined(settled.name) : labels.accepted}
165
- action={continueButton("brand")}
165
+ action={continueButton("accent")}
166
166
  className={OUTCOME_CLASS}
167
167
  />
168
168
  );
@@ -174,7 +174,7 @@ export function AcceptInvitation({
174
174
  hint={labels.signInHint}
175
175
  action={
176
176
  <>
177
- <Button href={signInHref} variant="brand">
177
+ <Button href={signInHref} variant="accent">
178
178
  {labels.signIn}
179
179
  </Button>
180
180
  {registerHref !== undefined && (
@@ -81,7 +81,7 @@ export function NotFoundPage({
81
81
  <p className="max-w-prose text-sm text-secondary">{labels.body}</p>
82
82
  {children}
83
83
  <div className="mt-4 flex flex-wrap justify-center gap-3">
84
- <Button href={homeHref} variant="brand">
84
+ <Button href={homeHref} variant="accent">
85
85
  {labels.home}
86
86
  </Button>
87
87
  {appHref !== undefined && (
@@ -232,7 +232,7 @@ export function ResetPasswordForm({
232
232
  title={token === "" ? labels.noToken : labels.invalid}
233
233
  hint={labels.invalidHint}
234
234
  action={
235
- <Button href={forgotHref} variant="brand">
235
+ <Button href={forgotHref} variant="accent">
236
236
  {labels.requestNew}
237
237
  </Button>
238
238
  }
@@ -253,11 +253,11 @@ export function ResetPasswordForm({
253
253
  {notices}
254
254
  <p className="text-xs text-muted">{labels.sessionsEnded}</p>
255
255
  {onSignIn ? (
256
- <Button type="button" variant="brand" className="w-full" onClick={() => onSignIn(email)}>
256
+ <Button type="button" variant="accent" className="w-full" onClick={() => onSignIn(email)}>
257
257
  {labels.signIn}
258
258
  </Button>
259
259
  ) : (
260
- <Button href={email ? withQuery(signInHref, "email", email) : signInHref} variant="brand" className="w-full">
260
+ <Button href={email ? withQuery(signInHref, "email", email) : signInHref} variant="accent" className="w-full">
261
261
  {labels.signIn}
262
262
  </Button>
263
263
  )}
@@ -119,7 +119,7 @@ function ResendButton({
119
119
  }: {
120
120
  state: ReturnType<typeof useResend>;
121
121
  labels: VerifyEmailLabels;
122
- variant: "secondary" | "brand";
122
+ variant: "secondary" | "accent";
123
123
  }) {
124
124
  return (
125
125
  <Button
@@ -269,7 +269,7 @@ export function VerifyEmailStatus({
269
269
  const href = continueHref ?? (signedIn ? "/" : "/login");
270
270
  // `replace`: the token is spent, so Back must not land on this page again (keksdose F1).
271
271
  const continueButton = (
272
- <Button href={href} replace variant={outcome === "verified" ? "brand" : "secondary"}>
272
+ <Button href={href} replace variant={outcome === "verified" ? "accent" : "secondary"}>
273
273
  {labels.continue}
274
274
  </Button>
275
275
  );
@@ -304,7 +304,7 @@ export function VerifyEmailStatus({
304
304
  hint={hint}
305
305
  action={
306
306
  <>
307
- {canResend && <ResendButton state={resend} labels={labels} variant="brand" />}
307
+ {canResend && <ResendButton state={resend} labels={labels} variant="accent" />}
308
308
  {continueButton}
309
309
  </>
310
310
  }
@@ -366,6 +366,14 @@ export interface EmailVerificationBannerProps {
366
366
  * is never hidden behind "More". Default `false`: the outcome stays in the message.
367
367
  */
368
368
  clampMobile?: boolean;
369
+ /**
370
+ * 0.50 (kastlan K1): where "Send again" goes on a phone in the `box` variant —
371
+ * {@link AlertBanner}'s `actionPlacement`, passed through. `"flow"` keeps it on the
372
+ * message's line while the message keeps 10rem, else at the end of the next line,
373
+ * instead of a line of its own (kastlan's ≤ 56px banner on a 390px phone, KX-5).
374
+ * Default `"own-line"`, as before. The `strip` variant flows like that already.
375
+ */
376
+ actionPlacement?: "own-line" | "flow";
369
377
  }
370
378
 
371
379
  /**
@@ -388,6 +396,7 @@ export function EmailVerificationBanner({
388
396
  className,
389
397
  labels: labelsProp,
390
398
  clampMobile = false,
399
+ actionPlacement,
391
400
  }: EmailVerificationBannerProps) {
392
401
  const labels = useKitLabels("verifyEmail", DEFAULT_VERIFY_EMAIL_LABELS, labelsProp);
393
402
  const resend = useResend(onResend, cooldown);
@@ -421,6 +430,7 @@ export function EmailVerificationBanner({
421
430
  onDismiss={onDismiss}
422
431
  dismissLabel={onDismiss ? labels.dismiss : undefined}
423
432
  clampMobile={clampMobile}
433
+ actionPlacement={actionPlacement}
424
434
  className={className}
425
435
  >
426
436
  {labels.banner}
@@ -471,7 +471,7 @@ export function PlanPicker({
471
471
  <div className="flex justify-end">
472
472
  <Button
473
473
  type="button"
474
- variant="primary"
474
+ variant="default"
475
475
  className="max-sm:w-full"
476
476
  disabledReason={blocked}
477
477
  pending={pending}
@@ -538,7 +538,7 @@ export function GrantWarningConfirm({ open, onOpenChange, onConfirm, labels: lab
538
538
  {common.back}
539
539
  </Button>
540
540
  <Button
541
- variant="primary"
541
+ variant="default"
542
542
  onClick={() => {
543
543
  onConfirm();
544
544
  onOpenChange(false);
@@ -41,10 +41,11 @@ import {
41
41
  } from "./field-parts";
42
42
  import { useEndWidth, useNumericField } from "./numeric-field";
43
43
  import type { CommitScope } from "./write-lock";
44
+ import { dataAttributes, type DataAttributes } from "../lib/data-attributes";
44
45
 
45
46
  /** {@link AmountInput}'s props — exported since 0.34 (refactor audit 2026-10, API-9): a
46
47
  * wrapper typed itself off `ComponentProps<typeof AmountInput>` until then. */
47
- export interface AmountInputProps extends FieldHelpProps {
48
+ export interface AmountInputProps extends FieldHelpProps, DataAttributes {
48
49
  value: string;
49
50
  onChange: (value: string) => void;
50
51
  currency?: string;
@@ -427,8 +428,8 @@ export function AmountInput(props: AmountInputProps) {
427
428
  const { lock, locked, off, isMobile, asDisplay, showCalc, showNumpad, setFocused, mark, draft, setDraft, derived } =
428
429
  numeric;
429
430
  const invalid = numeric.isInvalid;
430
- // A locked chip is a read-out: picking a currency is a change too.
431
- const editable = !!onCurrencyChange && !locked;
431
+ // A locked or disabled chip is a read-out: picking a currency is a change too.
432
+ const editable = !!onCurrencyChange && !locked && !off;
432
433
  // Room for the trailing controls — the calculator and the currency chip — once
433
434
  // measured, as NumberInput reserves its own (keksdose live #367, DUP-10): their
434
435
  // width, the track's `end-1` inset in the boxed shape, and NumberInput's 4px gap.
@@ -533,7 +534,7 @@ export function AmountInput(props: AmountInputProps) {
533
534
  const selected = getCurrency(currency);
534
535
 
535
536
  return (
536
- <div className={cn("w-full", className)}>
537
+ <div {...dataAttributes(props)} className={cn("w-full", className)}>
537
538
  {/* Inner relative box holds the input + its absolutely-positioned
538
539
  trailing controls (calculator / currency), so those stay anchored to
539
540
  the INPUT even when the mobile math bar is rendered below (#334). It is
@@ -519,6 +519,8 @@ export function Autocomplete<V extends string | number = string>(props: Autocomp
519
519
  // Every press inside the list keeps focus in the field — rows, the status
520
520
  // line, the scrollbar — or the input's blur would close the list first.
521
521
  role="presentation"
522
+ // A kit layer, for the tour's layer order (0.50, docs/tours-harmonization.md §11.6).
523
+ data-kit-layer=""
522
524
  // Portalled out of the form's `dir`; the field's is put back. The list is
523
525
  // exactly the field's width, so `left` places it in either direction.
524
526
  dir={dir}
@@ -1,4 +1,4 @@
1
- import { Fragment, useEffect, useRef, useState } from "react";
1
+ import { Fragment, useContext, useEffect, useRef, useState } from "react";
2
2
  import type { ComponentPropsWithRef, ReactElement, ReactNode } from "react";
3
3
  import { ChevronRight, MoreHorizontal } from "lucide-react";
4
4
  import { cn } from "../lib/cn";
@@ -6,6 +6,7 @@ import { FOCUS_RING } from "./focus-ring";
6
6
  import { useKitLabels, useKitLink, type LabelOverride } from "../i18n/kit-labels";
7
7
  import { pickLinkRenderer, RenderedKitLink } from "./text-link";
8
8
  import { DEFAULT_BREADCRUMBS_LABELS } from "./breadcrumbs-labels";
9
+ import { PageHeaderTrailContext } from "./page-header-context";
9
10
 
10
11
  export interface BreadcrumbsLabels {
11
12
  /** The `nav` landmark's name — what a screen reader lists it as. */
@@ -24,6 +25,12 @@ export interface BreadcrumbItem {
24
25
  href?: string;
25
26
  /** A stable React key, when `label` is not a string. */
26
27
  key?: string;
28
+ /**
29
+ * The app's home entry (0.49 KIT-9). Default: an entry whose `href` is `"/"`. No trail
30
+ * is drawn when only home entries are left — inside a `PageHeader`, or wherever the
31
+ * current entry is hidden (`hideCurrent`): "Home" above a page's title says nothing.
32
+ */
33
+ home?: boolean;
27
34
  }
28
35
 
29
36
  /** What {@link BreadcrumbsProps.renderLink} is handed. Spread it onto your router's
@@ -57,6 +64,18 @@ export interface BreadcrumbsProps extends Omit<ComponentPropsWithRef<"nav">, "ch
57
64
  keepEnd?: number;
58
65
  /** Overrides the {@link UiKitProvider}'s `breadcrumbs` labels. */
59
66
  labels?: LabelOverride<BreadcrumbsLabels>;
67
+ /**
68
+ * Leave out the last entry, the current page (0.49 KIT-9). The entry before it stays a
69
+ * link, and no entry carries `aria-current`: the page's title names where you are.
70
+ *
71
+ * Default `true` inside a `PageHeader`'s `breadcrumbs` (the title is right below, so
72
+ * the trail would repeat it), `false` elsewhere. With the current entry hidden — and
73
+ * inside a `PageHeader` either way — the trail is drawn only when at least one entry
74
+ * that is not home ({@link BreadcrumbItem.home}) is left, so a trail drawn by the
75
+ * layout above the page shows no lone "Home" link either. `false` keeps the current
76
+ * entry as text with `aria-current="page"`.
77
+ */
78
+ hideCurrent?: boolean;
60
79
  }
61
80
 
62
81
  /**
@@ -69,16 +88,18 @@ export interface BreadcrumbsProps extends Omit<ComponentPropsWithRef<"nav">, "ch
69
88
  * the server's first paint is already right.
70
89
  */
71
90
  export function Breadcrumbs({
72
- items,
91
+ items: allItems,
73
92
  renderLink,
74
93
  separator,
75
94
  collapse = true,
76
95
  keepEnd = 2,
77
96
  labels: labelsProp,
97
+ hideCurrent,
78
98
  className,
79
99
  "aria-label": ariaLabel,
80
100
  ...rest
81
101
  }: BreadcrumbsProps) {
102
+ const host = useContext(PageHeaderTrailContext);
82
103
  const labels = useKitLabels("breadcrumbs", DEFAULT_BREADCRUMBS_LABELS, labelsProp);
83
104
  const kitLink = useKitLink();
84
105
  const [expanded, setExpanded] = useState(false);
@@ -92,7 +113,13 @@ export function Breadcrumbs({
92
113
  const li = revealedRef.current;
93
114
  (li?.querySelector<HTMLElement>("a[href]") ?? li)?.focus();
94
115
  }, [expanded]);
95
- if (items.length === 0) return null;
116
+ const hides = hideCurrent ?? host?.titled ?? false;
117
+ const shown = hides ? allItems.slice(0, -1) : allItems;
118
+ if (shown.length === 0) return null;
119
+ // A trail of home entries alone repeats nothing worth a line: in a PageHeader, and
120
+ // wherever the current entry is hidden (a layout's trail above the page's title).
121
+ if ((host || hides) && !shown.some((item) => !(item.home ?? item.href === "/"))) return null;
122
+ const items = shown;
96
123
 
97
124
  const end = Math.max(1, keepEnd);
98
125
  // Crumbs [1, firstKept) are the collapsible middle. Collapsing a single crumb into a
@@ -116,7 +143,8 @@ export function Breadcrumbs({
116
143
  <nav {...rest} aria-label={ariaLabel ?? labels.label} className={cn("min-w-0 text-sm", className)}>
117
144
  <ol className="m-0 flex min-w-0 list-none flex-wrap items-center gap-x-1.5 gap-y-1 p-0 text-muted">
118
145
  {items.map((item, i) => {
119
- const last = i === items.length - 1;
146
+ // With the current entry hidden, the last one shown is a parent: a link.
147
+ const last = !hides && i === items.length - 1;
120
148
  const key = item.key ?? (typeof item.label === "string" ? `${i}-${item.label}` : String(i));
121
149
  let content: ReactNode;
122
150
  if (last) {
@@ -13,7 +13,75 @@ import type { KitLinkComponent, KitLinkProps } from "../i18n/kit-labels";
13
13
  * The other exports are the two buttons' shared parts (`@internal`).
14
14
  */
15
15
 
16
- export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "brand" | "link";
16
+ /**
17
+ * A button's look.
18
+ *
19
+ * - `default`: the quiet action — a warm filled chip with a hairline border. What a
20
+ * Button with no `variant` draws.
21
+ * - `accent`: the strong one, solid `--brand` with `--brand-contrast` text — the one
22
+ * call to action a view leads with. "Accent" is the button's role here, painted in
23
+ * `--brand`; it is not the palette's second anchor colour, also called `accent`
24
+ * (theme/palette-derive.ts), which no button paints.
25
+ * - `secondary`: an outline. `ghost`: text or glyph only. `danger`: solid `--danger`,
26
+ * for an action that destroys something. `link`: a text link that acts (a `<button>`).
27
+ * - `primary` and `brand` are **deprecated** aliases of `default` and `accent` (0.49,
28
+ * same look; a development build warns once per name). They are removed in 0.51, and
29
+ * `primary` will not come back with another meaning.
30
+ */
31
+ export type ButtonVariant =
32
+ | "default"
33
+ | "accent"
34
+ | "secondary"
35
+ | "ghost"
36
+ | "danger"
37
+ | "link"
38
+ /** @deprecated 0.49 — use `default` (same look); removed in 0.51. */
39
+ | "primary"
40
+ /** @deprecated 0.49 — use `accent` (same look); removed in 0.51. */
41
+ | "brand";
42
+
43
+ /** @internal A {@link ButtonVariant} without the deprecated aliases. */
44
+ export type CanonicalButtonVariant = Exclude<ButtonVariant, "primary" | "brand">;
45
+
46
+ /** The deprecated names and what they stand for — see {@link ButtonVariant}. */
47
+ const DEPRECATED_BUTTON_VARIANTS: Readonly<Record<string, CanonicalButtonVariant>> = {
48
+ primary: "default",
49
+ brand: "accent",
50
+ };
51
+
52
+ /** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a
53
+ * production one (the consumer's bundler makes `import.meta.env.DEV` false) and never
54
+ * under vitest. Read per call so a test can switch it on with `vi.stubEnv`. */
55
+ function devWarnings(): boolean {
56
+ return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
57
+ }
58
+
59
+ const warnedVariants = new Set<string>();
60
+
61
+ /**
62
+ * @internal A deprecated variant name warns once (per `component` and name) in a
63
+ * development build, then reads as the name it stands for. `component` is the prop's
64
+ * owner in the message ("Button variant", "FormActions submitVariant", …).
65
+ */
66
+ export function warnDeprecatedVariant(component: string, name: string, replacement: string): void {
67
+ if (!devWarnings()) return;
68
+ const key = `${component}|${name}`;
69
+ if (warnedVariants.has(key)) return;
70
+ warnedVariants.add(key);
71
+ console.warn(
72
+ `[ui-kit] ${component}="${name}" is deprecated: use "${replacement}" (same look). ` +
73
+ `"${name}" is removed in 0.51.`,
74
+ );
75
+ }
76
+
77
+ /** @internal `primary` → `default`, `brand` → `accent` (warning once in development);
78
+ * every other variant as it is. Runs before every lookup keyed by the variant. */
79
+ export function resolveButtonVariant(variant: ButtonVariant, component = "Button variant"): CanonicalButtonVariant {
80
+ const replacement = DEPRECATED_BUTTON_VARIANTS[variant];
81
+ if (replacement === undefined) return variant as CanonicalButtonVariant;
82
+ warnDeprecatedVariant(component, variant, replacement);
83
+ return replacement;
84
+ }
17
85
 
18
86
  // Shared base ring for every button-styled element. Kept as a named const so the
19
87
  // <Button> component and the {@link buttonClasses} helper draw from one source and
@@ -83,13 +151,14 @@ const BUTTON_SIZES_LARGE: Record<ButtonSize, string> = {
83
151
  export const HOVER_INK = "hover:bg-[color-mix(in_srgb,var(--text-primary)_7%,transparent)]";
84
152
 
85
153
  // Warm, palette-token-driven so buttons blend with the fields + cards in every theme.
86
- // Actions default to a warm bordered look (primary = filled warm chip, secondary =
87
- // outline); `brand` stays the solid accent for the rare strong CTA; `danger` takes the
154
+ // Actions default to a warm bordered look (`default` = filled warm chip, secondary =
155
+ // outline); `accent` is the solid brand fill for the rare strong CTA; `danger` takes the
88
156
  // semantic `--danger` family (destructive semantics), so a consumer can re-point the
89
157
  // destructive hue instead of inheriting a hard-coded red. Focus rings follow each
90
- // variant's own accent: brand for the four neutral ones, danger for `danger`.
91
- export const buttonVariantClasses: Record<ButtonVariant, string> = {
92
- primary:
158
+ // variant's own accent: brand for the four neutral ones, danger for `danger`. Keyed by
159
+ // the canonical names: {@link resolveButtonVariant} maps the deprecated ones first.
160
+ export const buttonVariantClasses: Record<CanonicalButtonVariant, string> = {
161
+ default:
93
162
  // Hovers onto `--bg-active`, not `--border` (0.33, §12.9): More contrast steps the
94
163
  // border until it is a 3:1 LINE, and as a fill under the label that left 2.7:1.
95
164
  "border border-subtle bg-surface-2 text-primary hover:bg-active focus-visible:ring-brand",
@@ -97,7 +166,7 @@ export const buttonVariantClasses: Record<ButtonVariant, string> = {
97
166
  ghost: `bg-transparent text-primary ${HOVER_INK} focus-visible:ring-brand`,
98
167
  danger:
99
168
  "bg-danger text-danger-contrast hover:bg-danger-hover focus-visible:ring-danger-strong",
100
- brand:
169
+ accent:
101
170
  "bg-brand text-brand-contrast hover:bg-brand-hover focus-visible:ring-brand",
102
171
  // A text link that is still a `<button>` — keksdose's six hand-rolled
103
172
  // `<button className="text-brand underline">` sites ("Resend code", "Show all",
@@ -132,14 +201,14 @@ export const buttonVariantClasses: Record<ButtonVariant, string> = {
132
201
  *
133
202
  * `danger` also reaches the two NEUTRAL boxed variants, where the box carries it:
134
203
  * `secondary` becomes an outlined destructive button (`--danger` text and a
135
- * `--danger-border` outline, the quiet danger fill on hover) and `primary` a soft one
204
+ * `--danger-border` outline, the quiet danger fill on hover) and `default` a soft one
136
205
  * (the quiet fill at rest, solid `--danger` on hover). kastlan's meeting-invitations-
137
206
  * tab.tsx "Remove all" is `variant="secondary" className="text-destructive"` — a
138
207
  * destructive action that must sit in a row of secondary buttons without shouting
139
208
  * like the solid `variant="danger"` would, and whose red text alone left the border
140
209
  * and hover saying "neutral". `muted` stays a text look and does nothing on a box, and
141
- * `danger`, `brand` and `link`'s own colours are left alone: a tone that recoloured a
142
- * solid danger or brand button would be a second variant under another name.
210
+ * `danger`, `accent` and `link`'s own colours are left alone: a tone that recoloured a
211
+ * solid danger or accent button would be a second variant under another name.
143
212
  */
144
213
  export type ButtonTone = "default" | "muted" | "danger";
145
214
 
@@ -154,19 +223,19 @@ const BUTTON_TONES: Record<Exclude<ButtonTone, "default">, string> = {
154
223
  };
155
224
 
156
225
  /** Only the two transparent variants take every tone; see {@link ButtonTone}. */
157
- const TONED_VARIANTS = new Set<ButtonVariant>(["link", "ghost"]);
226
+ const TONED_VARIANTS = new Set<CanonicalButtonVariant>(["link", "ghost"]);
158
227
 
159
228
  // `tone="danger"` on the neutral boxed variants — see {@link ButtonTone}. Each string
160
229
  // restates every colour the variant sets (border, fill, text, hover, ring) so none of
161
230
  // the neutral ones survives the merge.
162
- const BUTTON_BOXED_DANGER: Partial<Record<ButtonVariant, string>> = {
231
+ const BUTTON_BOXED_DANGER: Partial<Record<CanonicalButtonVariant, string>> = {
163
232
  // Not boxed, but its hover is a fill: the danger wash under the danger text, as
164
233
  // IconButton's danger tone (0.33). On B′'s translucent ink `--danger` fell to 4.2:1
165
234
  // over a light page.
166
235
  ghost: "hover:bg-danger-soft",
167
236
  secondary:
168
237
  "border-danger bg-transparent text-danger hover:bg-danger-soft focus-visible:ring-danger-strong",
169
- primary:
238
+ default:
170
239
  "border-danger bg-danger-soft text-danger hover:border-[var(--danger)] hover:bg-danger hover:text-danger-contrast focus-visible:ring-danger-strong disabled:hover:border-danger disabled:hover:bg-danger-soft disabled:hover:text-danger",
171
240
  };
172
241
 
@@ -197,11 +266,12 @@ export interface ButtonClassesOptions {
197
266
  * compact toolbar links.
198
267
  */
199
268
  export function buttonClasses(
200
- variant: ButtonVariant = "primary",
269
+ given: ButtonVariant = "default",
201
270
  classNameOrOptions?: string | ButtonClassesOptions,
202
271
  ): string {
203
272
  const { size = "md", className } =
204
273
  typeof classNameOrOptions === "object" ? classNameOrOptions : { className: classNameOrOptions };
274
+ const variant = resolveButtonVariant(given, "buttonClasses variant");
205
275
  return cn(
206
276
  BUTTON_BASE,
207
277
  BUTTON_SIZES[size],
@@ -221,7 +291,8 @@ export const ICON_BUTTON_PRESSED =
221
291
  "bg-brand-soft text-brand hover:bg-brand-soft-hover hover:text-brand-muted disabled:hover:bg-brand-soft disabled:hover:text-brand";
222
292
 
223
293
  /** @internal The button look shared by the `<button>` and the `<a>` form. */
224
- export function buttonLook(variant: ButtonVariant, size: ButtonSize, stretch: boolean | undefined, tone: ButtonTone) {
294
+ export function buttonLook(given: ButtonVariant, size: ButtonSize, stretch: boolean | undefined, tone: ButtonTone) {
295
+ const variant = resolveButtonVariant(given);
225
296
  return cn(
226
297
  BUTTON_BASE,
227
298
  BUTTON_SIZES[size],
@@ -24,6 +24,9 @@ import type { ButtonSize, ButtonTone, ButtonVariant } from "./button-look";
24
24
  * link buttons submitting their form).
25
25
  */
26
26
  export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
27
+ /** `default` (the quiet action, what a Button without `variant` draws), `accent` (the
28
+ * strong call to action, solid brand), `secondary`, `ghost`, `danger` or `link`.
29
+ * `primary` / `brand` are deprecated aliases of `default` / `accent`. See {@link ButtonVariant}. */
27
30
  variant?: ButtonVariant;
28
31
  /** `md` (default) is the page's action button. `sm` is the compact one — 12px text
29
32
  * and `px-2 py-1` — for the secondary actions in a toolbar, a card header or a
@@ -39,7 +42,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
39
42
  ref?: Ref<HTMLButtonElement>;
40
43
  /** Text colour for `link` and `ghost`: `muted` (quiet, body colour on hover) or
41
44
  * `danger` (quiet, `--danger` on hover). `danger` also makes `secondary` an
42
- * outlined destructive button and `primary` a soft one; ignored by the other
45
+ * outlined destructive button and `default` a soft one; ignored by the other
43
46
  * variants. See {@link ButtonTone}. */
44
47
  tone?: ButtonTone;
45
48
  /**
@@ -181,7 +184,7 @@ function ButtonLink({
181
184
  replace,
182
185
  reloadDocument,
183
186
  disabled,
184
- variant = "primary",
187
+ variant = "default",
185
188
  size = "md",
186
189
  stretch,
187
190
  tone = "default",
@@ -257,7 +260,7 @@ function ButtonLink({
257
260
  }
258
261
 
259
262
  function ButtonElement({
260
- variant = "primary",
263
+ variant = "default",
261
264
  size = "md",
262
265
  stretch,
263
266
  tone = "default",
@@ -9,6 +9,7 @@ import { parseHex } from "../theme/color";
9
9
  import { Tooltip } from "./tooltip";
10
10
  import type { WeekDay } from "./mini-calendar";
11
11
  import { DEFAULT_CALENDAR_HEATMAP_LABELS } from "./calendar-heatmap-labels";
12
+ import { formatWithMinus } from "../lib/minus";
12
13
 
13
14
  /**
14
15
  * Every string the heatmap speaks — the `calendarHeatmap` namespace of
@@ -248,7 +249,7 @@ export function CalendarHeatmap({
248
249
  const providerWeekStart = useKitWeekStart();
249
250
  const weekStart: WeekDay = weekStartsOn ?? providerWeekStart ?? localeWeekStart(locale);
250
251
  const numberFormat = useMemo(() => new Intl.NumberFormat(locale), [locale]);
251
- const formatValue = formatValueProp ?? ((v: number) => numberFormat.format(v));
252
+ const formatValue = formatValueProp ?? ((v: number) => formatWithMinus(numberFormat, v));
252
253
 
253
254
  const values = useMemo(() => {
254
255
  const m = new Map<string, number>();