@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
@@ -1,4 +1,4 @@
1
- import { isValidElement } from "react";
1
+ import { isValidElement, useContext } from "react";
2
2
  import type { KeyboardEvent, MouseEvent, ReactElement, ReactNode } from "react";
3
3
  import { MoreHorizontal } from "lucide-react";
4
4
  import type { LucideIcon } from "lucide-react";
@@ -15,6 +15,8 @@ import type { TooltipSide } from "./tooltip";
15
15
  import type { DataTableColumn } from "./data-table";
16
16
  import { TableHeaderCell, type TableHeaderCellProps } from "./table";
17
17
  import { DEFAULT_ROW_ACTIONS_LABELS } from "./row-actions-labels";
18
+ import { ConfirmContext } from "./confirm-context";
19
+ import { ConfirmProvider } from "./confirm-dialog";
18
20
 
19
21
  /**
20
22
  * A row's actions — inline icons at Normal, one "⋯" menu at Large and above
@@ -30,6 +32,11 @@ import { DEFAULT_ROW_ACTIONS_LABELS } from "./row-actions-labels";
30
32
  * The data is the roster's `UserRowActionList`, generalised: entries, and `false` /
31
33
  * `null` for the ones a condition left out (`row.locked || {…}`).
32
34
  *
35
+ * Destructive actions belong in the menu (0.49 KIT-12): a `tone: "danger"` action goes
36
+ * into the "⋯" menu while the others stay inline (`dangerPlacement`), and choosing it
37
+ * opens a `ConfirmDialog` before `onSelect` runs (`confirm`; `false` to opt out). A
38
+ * delete is then never a bin icon in every row, one stray tap from gone.
39
+ *
33
40
  * 0.33 (§10.16): an action carries its state at both sizes — a toggle (`pressed`), a
34
41
  * disclosure (`expanded`, `controls`), a tone, a tour anchor (`dataTour`) — so a row
35
42
  * with an "on" share button or a tour step no longer keeps hand-rolled IconButtons at
@@ -47,11 +54,21 @@ export interface RowAction {
47
54
  /** The action's name: the inline icon's name and tooltip, the menu entry's words. */
48
55
  label: string;
49
56
  /**
50
- * Runs it. The kit sends nothing — the app opens its confirmation or calls its API.
51
- * Optional only beside `href`; an action has one or the other (or both: `onSelect`
52
- * then runs as the link is followed).
57
+ * Runs it. The kit sends nothing — it calls this, after a `tone: "danger"` action's
58
+ * confirmation (see {@link confirm}). Optional only beside `href`; an action has one or
59
+ * the other (or both: `onSelect` then runs as the link is followed).
53
60
  */
54
61
  onSelect?: () => void;
62
+ /**
63
+ * 0.49 KIT-12: ask before `onSelect`. A `tone: "danger"` action asks by default — a
64
+ * `ConfirmDialog` (the provider's `useConfirm`, else one of its own) titled with the
65
+ * action and the row's name, "Delete “Ada Example”?", the consequence under it
66
+ * (`rowActions.confirmBody`), the action's label on the danger button and focus on
67
+ * Cancel. Pass the words to say more, or `false` when the app confirms in `onSelect`
68
+ * itself or offers an undo instead. Any other tone asks only when given one. Not for
69
+ * an `href` action: a link only navigates.
70
+ */
71
+ confirm?: boolean | RowActionConfirm;
55
72
  /**
56
73
  * The action NAVIGATES — an edit page, a mail to compose (0.32.1, kastlan's 0.32
57
74
  * report): a real link inline and in the menu, so open in a new tab, a middle click
@@ -133,6 +150,13 @@ export interface RowAction {
133
150
  dataTour?: string;
134
151
  }
135
152
 
153
+ /** The words of a {@link RowAction.confirm}; each defaults as described there. */
154
+ export interface RowActionConfirm {
155
+ title?: ReactNode;
156
+ body?: ReactNode;
157
+ confirmLabel?: ReactNode;
158
+ }
159
+
136
160
  /** What a row's actions may be: entries, and `false` / `null` / `undefined` for the ones
137
161
  * a condition left out. */
138
162
  export type RowActionList = readonly (RowAction | false | null | undefined)[];
@@ -143,6 +167,11 @@ export interface RowActionsLabels {
143
167
  actions: string;
144
168
  /** The menu button's name, given the row's: "Actions for Ada Example". */
145
169
  actionsFor: (name: string) => string;
170
+ /** 0.49 KIT-12: a danger action's confirmation title, given the action's label and the
171
+ * row's name when it has one: "Delete “Ada Example”?", "Delete?". */
172
+ confirmTitle: (action: string, name?: string) => string;
173
+ /** 0.49 KIT-12: the line under it — "This cannot be undone." */
174
+ confirmBody: string;
146
175
  }
147
176
 
148
177
  // Defined in ./row-actions-labels, which holds no component code (0.34, KS U8).
@@ -166,6 +195,13 @@ export type RowActionsSize = Extract<IconButtonSize, "md" | "sm" | "xs" | "2xs">
166
195
 
167
196
  export interface RowActionsProps {
168
197
  actions: RowActionList;
198
+ /**
199
+ * 0.49 KIT-12: where `tone: "danger"` actions go while the others are inline. `"menu"`
200
+ * (default): into the row's "⋯" menu, so a delete is never one stray tap in every
201
+ * row — the other actions stay icons beside it. `"inline"`: an icon like the rest.
202
+ * `collapse="inline"` keeps every action inline, `collapse="menu"` every one in the menu.
203
+ */
204
+ dangerPlacement?: "menu" | "inline";
169
205
  /** The row's name, for the menu button's name — "Actions for Ada Example". Without it
170
206
  * (or `null`, as API fields often are) the button is "Actions". */
171
207
  name?: string | null;
@@ -226,7 +262,7 @@ function toneAttr(tone: RowActionTone | undefined) {
226
262
  * A row's actions, inline or as a "⋯" menu — see {@link RowActionsCollapse}. Every
227
263
  * control stops its click (and the Enter/Space it stands for) from reaching the row, so
228
264
  * a clickable row or a DataTable `rowLink` does not open as well; in a DataTable column
229
- * also set `noRowLink: true`, as {@link rowActionsColumn} does.
265
+ * also set `noRowLink: true` and `role: "actions"`, as {@link rowActionsColumn} does.
230
266
  *
231
267
  * Inline, the icons sit in a {@link CompactControls} region at Normal: their labels
232
268
  * stay the names and the tooltips, and a locked one keeps its reason in the tooltip — a
@@ -238,10 +274,54 @@ function toneAttr(tone: RowActionTone | undefined) {
238
274
  * press close it, and focus goes back to the "⋯" button, which is where a confirmation
239
275
  * dialog then returns it.
240
276
  */
241
- export function RowActions({
277
+ export function RowActions(props: RowActionsProps) {
278
+ const outer = useContext(ConfirmContext);
279
+ // A danger action asks first (0.49 KIT-12). Under the app's ConfirmProvider through
280
+ // it; without one, through a provider of this row's own — only when there is
281
+ // something to ask, so a plain row adds nothing.
282
+ if (outer === null && visibleActions(props.actions).some(asksFirst)) {
283
+ return (
284
+ <ConfirmProvider>
285
+ <RowActionsBody {...props} />
286
+ </ConfirmProvider>
287
+ );
288
+ }
289
+ return <RowActionsBody {...props} />;
290
+ }
291
+
292
+ /** Whether choosing `action` opens a confirmation first: a danger action unless it says
293
+ * `confirm: false`, any other when it passes one; never a link. */
294
+ function asksFirst(action: RowAction): boolean {
295
+ if (action.href !== undefined || action.confirm === false) return false;
296
+ return action.confirm !== undefined || action.tone === "danger";
297
+ }
298
+
299
+ /** `action.onSelect`, after its confirmation when it {@link asksFirst}. */
300
+ function useRunAction(name: string | null | undefined, labels: RowActionsLabels) {
301
+ const confirm = useContext(ConfirmContext);
302
+ return (action: RowAction) => {
303
+ if (!asksFirst(action) || confirm === null) {
304
+ action.onSelect?.();
305
+ return;
306
+ }
307
+ const words = typeof action.confirm === "object" ? action.confirm : {};
308
+ void confirm({
309
+ title: words.title ?? labels.confirmTitle(action.label, name ?? undefined),
310
+ body: words.body ?? labels.confirmBody,
311
+ confirmLabel: words.confirmLabel ?? action.label,
312
+ tone: "danger",
313
+ commit: action.commit,
314
+ }).then((ok) => {
315
+ if (ok) action.onSelect?.();
316
+ });
317
+ };
318
+ }
319
+
320
+ function RowActionsBody({
242
321
  actions,
243
322
  name,
244
323
  collapse = "auto",
324
+ dangerPlacement = "menu",
245
325
  size = "xs",
246
326
  menuWidth = "15rem",
247
327
  className,
@@ -251,28 +331,64 @@ export function RowActions({
251
331
  }: RowActionsProps) {
252
332
  const labels = useKitLabels("rowActions", DEFAULT_ROW_ACTIONS_LABELS, labelsProp);
253
333
  const large = useLargeText();
334
+ const run = useRunAction(name, labels);
254
335
  const entries = visibleActions(actions);
255
336
  if (entries.length === 0) return null;
256
337
  const asMenu = collapse === "menu" || (collapse === "auto" && large && entries.length > 1);
257
- if (!asMenu) {
258
- const strip = (
259
- <div data-slot="row-actions" className={cn("inline-flex items-center gap-0.5", className)}>
260
- {entries.map((action) => (
261
- <InlineAction
262
- key={action.key ?? action.label}
263
- action={action}
264
- size={size}
265
- glyphSize={glyphSize}
266
- tooltipSide={tooltipSide}
267
- />
268
- ))}
269
- </div>
270
- );
271
- // Compact at Normal (no room for a reason line in a row); at Large an inline action
272
- // is the app's choice ("inline") or the row's only one, and shows its words.
273
- return large ? strip : <CompactControls>{strip}</CompactControls>;
274
- }
338
+ // Inline, a danger action still goes into the "⋯" menu (KIT-12) unless the app keeps it
339
+ // inline: the other actions stay icons, the menu holds the destructive ones.
340
+ const toMenu = (action: RowAction) =>
341
+ asMenu || (collapse === "auto" && dangerPlacement === "menu" && action.tone === "danger");
342
+ const inline = entries.filter((action) => !toMenu(action));
343
+ const menu = entries.filter(toMenu);
275
344
  const title = name ? labels.actionsFor(name) : labels.actions;
345
+ const menuButton = menu.length > 0 && (
346
+ <RowActionsMenu
347
+ entries={menu}
348
+ title={title}
349
+ size={size}
350
+ menuWidth={menuWidth}
351
+ run={run}
352
+ className={inline.length === 0 ? className : undefined}
353
+ />
354
+ );
355
+ if (inline.length === 0) return menuButton || null;
356
+ const strip = (
357
+ <div data-slot="row-actions" className={cn("inline-flex items-center gap-0.5", className)}>
358
+ {inline.map((action) => (
359
+ <InlineAction
360
+ key={action.key ?? action.label}
361
+ action={action}
362
+ size={size}
363
+ glyphSize={glyphSize}
364
+ tooltipSide={tooltipSide}
365
+ run={run}
366
+ />
367
+ ))}
368
+ {menuButton}
369
+ </div>
370
+ );
371
+ // Compact at Normal (no room for a reason line in a row); at Large an inline action
372
+ // is the app's choice ("inline") or the row's only one, and shows its words.
373
+ return large ? strip : <CompactControls>{strip}</CompactControls>;
374
+ }
375
+
376
+ /** The "⋯" button and its menu of `entries`. */
377
+ function RowActionsMenu({
378
+ entries,
379
+ title,
380
+ size,
381
+ menuWidth,
382
+ run,
383
+ className,
384
+ }: {
385
+ entries: RowAction[];
386
+ title: string;
387
+ size: RowActionsSize;
388
+ menuWidth: `${number}rem`;
389
+ run: (action: RowAction) => void;
390
+ className?: string;
391
+ }) {
276
392
  // The menu closed when the action was chosen, so what is in flight shows on "⋯".
277
393
  const busy = entries.some((action) => action.pending);
278
394
  const anchored = entries.filter((action) => action.dataTour);
@@ -315,7 +431,7 @@ export function RowActions({
315
431
  <ul data-slot="row-actions-menu" className="space-y-0.5">
316
432
  {entries.map((action) => (
317
433
  <li key={action.key ?? action.label}>
318
- <MenuAction action={action} close={close} />
434
+ <MenuAction action={action} close={close} run={run} />
319
435
  </li>
320
436
  ))}
321
437
  </ul>
@@ -355,7 +471,7 @@ function useActionHref(action: RowAction): string | undefined {
355
471
  }
356
472
 
357
473
  /** One entry of the "⋯" menu: the words, as a link for an `href`, else a button. */
358
- function MenuAction({ action, close }: { action: RowAction; close: () => void }) {
474
+ function MenuAction({ action, close, run }: { action: RowAction; close: () => void; run: (action: RowAction) => void }) {
359
475
  const href = useActionHref(action);
360
476
  const glyph = glyphOf(action.icon);
361
477
  const glyphTone = action.tone === undefined ? undefined : MENU_GLYPH_TONE[action.tone];
@@ -376,7 +492,7 @@ function MenuAction({ action, close }: { action: RowAction; close: () => void })
376
492
  const tone = action.tone === "danger" ? "danger" : undefined;
377
493
  const select = () => {
378
494
  close();
379
- action.onSelect?.();
495
+ run(action);
380
496
  };
381
497
  if (href !== undefined) {
382
498
  return (
@@ -424,11 +540,13 @@ function InlineAction({
424
540
  size,
425
541
  glyphSize,
426
542
  tooltipSide,
543
+ run,
427
544
  }: {
428
545
  action: RowAction;
429
546
  size: RowActionsSize;
430
547
  glyphSize?: IconButtonGlyphSize;
431
548
  tooltipSide?: TooltipSide;
549
+ run: (action: RowAction) => void;
432
550
  }) {
433
551
  const href = useActionHref(action);
434
552
  const glyph = glyphOf(action.icon);
@@ -471,7 +589,7 @@ function InlineAction({
471
589
  pending={action.pending}
472
590
  disabled={action.disabled}
473
591
  stopPropagation
474
- onClick={() => action.onSelect?.()}
592
+ onClick={() => run(action)}
475
593
  >
476
594
  {glyph}
477
595
  </IconButton>
@@ -481,7 +599,7 @@ function InlineAction({
481
599
  const stop = {
482
600
  onClick: (e: MouseEvent) => {
483
601
  e.stopPropagation();
484
- action.onSelect?.();
602
+ run(action);
485
603
  },
486
604
  onKeyDown: (e: KeyboardEvent) => {
487
605
  if (e.key === "Enter" || e.key === " ") e.stopPropagation();
@@ -606,7 +724,7 @@ export interface RowActionsColumnOptions<T> {
606
724
  */
607
725
  header?: ReactNode | false;
608
726
  labels?: LabelOverride<RowActionsLabels>;
609
- /** Anything else for the column (`mobileHidden`, `cellProps`…), merged last. */
727
+ /** Anything else for the column (`mobileHidden`, `cellProps`, another `role`…), merged last. */
610
728
  column?: Partial<DataTableColumn<T>>;
611
729
  }
612
730
 
@@ -618,9 +736,10 @@ export interface RowActionsColumnOptions<T> {
618
736
  *
619
737
  * The heading says "Actions" on screen (0.46.1; `header` changes or hides it), the
620
738
  * column hugs its content at the row's end, and `noRowLink` keeps a `rowHref` anchor off it, so the
621
- * buttons keep their own clicks. On a phone it is a line of the card like any column;
622
- * set `column: { mobileHidden: true }` to drop it there (with `mobileDetailsInRow` it
623
- * then shows in the opened row instead).
739
+ * buttons keep their own clicks. It is `role: "actions"` (0.49 KIT-7): DataTable's default
740
+ * phone card draws it after its three fields, at the card's end, and never counts it as
741
+ * one of them. Set `column: { mobileHidden: true }` to drop it there (with
742
+ * `mobileDetailsInRow` it then shows in the opened row instead).
624
743
  */
625
744
  export function rowActionsColumn<T>({
626
745
  key = "actions",
@@ -660,6 +779,8 @@ export function rowActionsColumn<T>({
660
779
  // `RowActionsHead` (0.47).
661
780
  headClassName: ROW_ACTIONS_HEAD_CLASS,
662
781
  noRowLink: true,
782
+ // After the phone card's fields, never one of them (0.49 KIT-7).
783
+ role: "actions",
663
784
  ...column,
664
785
  };
665
786
  }
@@ -0,0 +1,65 @@
1
+ import { cn } from "../lib/cn";
2
+ import { NavPills } from "./nav-pills";
3
+ import type { NavPillItem, NavPillLinkProps } from "./nav-pills";
4
+ import type { ReactElement, Ref } from "react";
5
+
6
+ export interface SectionNavProps<T extends string> {
7
+ /** The section's pages, each with its `href` (a pill without one is a button that
8
+ * calls `onSelect`). Labels short: the row is one line. */
9
+ items: NavPillItem<T>[];
10
+ /** The page that is open. `null`: none of them. */
11
+ current: T | null;
12
+ /** The section's name — the landmark's accessible name ("Accounting"). */
13
+ label: string;
14
+ /** Called with a pill's value on a click, before the router follows its link. */
15
+ onSelect?: (value: T) => void;
16
+ /** Your router's link. Default: the `<UiKitProvider linkComponent>`, then `<a>`. */
17
+ renderLink?: (props: NavPillLinkProps) => ReactElement;
18
+ /** Default `true`: only below `md`, where the sidebar that lists the same pages is
19
+ * gone. `false` shows it at every width. */
20
+ phoneOnly?: boolean;
21
+ className?: string;
22
+ ref?: Ref<HTMLElement>;
23
+ }
24
+
25
+ /**
26
+ * The pages of a section on a phone, as one row of pills under the page title (0.49
27
+ * KIT-6) — kastlan's section pills, which each section page drew by hand.
28
+ *
29
+ * On a desktop the sidebar lists a group's pages; on a phone the AppShell's bar holds
30
+ * the groups, and a group's pages are a sheet away. This row puts them on the page
31
+ * itself: {@link NavPills} with `layout="scroll"` — one line that scrolls sideways, faded
32
+ * where it is cut, the open page scrolled into view (the row only, never the page). At
33
+ * most 48 px high: 44 px pills, the touch target a finger needs, and 2 px above and
34
+ * below. Each pill is a link (`aria-current="page"` on the open one) and its own tab
35
+ * stop; the row is a `<nav>` named by `label`.
36
+ *
37
+ * Place it right under the page's title (`PageHeader`); the AppShell's own row of a
38
+ * group's pages above the bar (`mobileSubNav`) is the shell-wide alternative — use one,
39
+ * not both, for the same pages.
40
+ */
41
+ export function SectionNav<T extends string>({
42
+ items,
43
+ current,
44
+ label,
45
+ onSelect,
46
+ renderLink,
47
+ phoneOnly = true,
48
+ className,
49
+ ref,
50
+ }: SectionNavProps<T>) {
51
+ return (
52
+ <NavPills
53
+ ref={ref}
54
+ aria-label={label}
55
+ items={items}
56
+ current={current}
57
+ onSelect={onSelect}
58
+ renderLink={renderLink}
59
+ layout="scroll"
60
+ // The ring inside the pill: the row scrolls, so it clips what leaves its box.
61
+ itemClassName="min-h-11 focus-visible:ring-inset"
62
+ className={cn("py-0.5 large:py-0", phoneOnly && "md:hidden", className)}
63
+ />
64
+ );
65
+ }
@@ -116,7 +116,9 @@ import {
116
116
  import { useBreakpoint } from "../hooks/use-breakpoint";
117
117
  import { useTextSize } from "../theme/text-size";
118
118
  import { chartHeightProps, type ChartHeight } from "./chart-height";
119
+ import { useChartEdge } from "./chart-edge";
119
120
  import { cn } from "../lib/cn";
121
+ import { formatWithMinus } from "../lib/minus";
120
122
 
121
123
  export interface SeriesChartSeries {
122
124
  /** The row key this line reads — and the `--color-<key>` it is painted with, so it
@@ -764,7 +766,8 @@ function useDefaultFormat(localeProp: string | undefined): (value: number) => st
764
766
  const locale = useKitLocale(localeProp);
765
767
  return useMemo(() => {
766
768
  const format = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });
767
- return (value: number) => format.format(value);
769
+ // U+2212 on a negative tick and in the tooltip (0.49, KIT-1).
770
+ return (value: number) => formatWithMinus(format, value);
768
771
  }, [locale]);
769
772
  }
770
773
 
@@ -1019,6 +1022,7 @@ function SeriesPlot({
1019
1022
  // grows by itself — and the axes aim at three ticks instead of five from Large up.
1020
1023
  // Strokes, dashes and the grid stay px.
1021
1024
  const { scale } = useTextSize();
1025
+ const edgeFor = useChartEdge();
1022
1026
  const ticksAim = tickTarget(scale);
1023
1027
  const grow = (px: number) => (scale === 1 ? px : Math.round(px * scale));
1024
1028
  // The keyboard stops' roving tab stop, and the one that has focus (see `PointKeys`).
@@ -1552,6 +1556,9 @@ function SeriesPlot({
1552
1556
  const type = entry.type ?? "line";
1553
1557
  const yAxisId = entry.axis ?? DEFAULT_Y_AXIS;
1554
1558
  const color = `var(--color-${entry.key})`;
1559
+ // A filled mark in a hue under 3:1 against the ground gets a 1px ink edge
1560
+ // (WCAG 1.4.11, anyplot's rule) — measured on the colour the config paints.
1561
+ const edge = edgeFor(config[entry.key]?.color);
1555
1562
  const own = entry.dash ?? (entry.dashed ? 1 : 0);
1556
1563
  const dash = entry.step ? strokeDash(STEP_DASH) : typeof own === "string" ? own : strokeDash(own);
1557
1564
  const curve = entry.step ? "stepAfter" : (entry.curve ?? "monotone");
@@ -1566,6 +1573,7 @@ function SeriesPlot({
1566
1573
  stackId={entry.stack}
1567
1574
  fill={color}
1568
1575
  fillOpacity={entry.fillOpacity}
1576
+ {...(edge ? { stroke: edge, strokeWidth: 1 } : {})}
1569
1577
  radius={entry.stack === undefined ? BAR_RADIUS : 0}
1570
1578
  // Read off the row rather than recharts' `value`: in a stack that is the
1571
1579
  // layer's running top, which a hole has too — and a hole gets no stub.
@@ -1596,8 +1604,9 @@ function SeriesPlot({
1596
1604
  type={curve}
1597
1605
  dataKey={entry.key}
1598
1606
  stackId={entry.stack}
1599
- stroke={color}
1600
- strokeWidth={width}
1607
+ // The edge replaces the hue on the area's outline only; the fill keeps it.
1608
+ stroke={edge ?? color}
1609
+ strokeWidth={edge ? 1 : width}
1601
1610
  strokeDasharray={dash}
1602
1611
  fill={color}
1603
1612
  fillOpacity={entry.fillOpacity ?? (entry.stack !== undefined ? 0.55 : 0.2)}
@@ -448,7 +448,7 @@ export function SharePanel({
448
448
  {captionNode && <Caption as="div">{captionNode}</Caption>}
449
449
  </div>
450
450
  <Button
451
- variant="brand"
451
+ variant="accent"
452
452
  commit
453
453
  pending={busy === "add"}
454
454
  disabled={busy !== null || (empty && !addWithoutEmail)}
@@ -616,7 +616,7 @@ export function SignaturePad({
616
616
  </Button>
617
617
  )}
618
618
  {onSave && (
619
- <Button type="button" variant="brand" onClick={save} disabled={disabled || isEmpty} className="ms-auto">
619
+ <Button type="button" variant="accent" onClick={save} disabled={disabled || isEmpty} className="ms-auto">
620
620
  {labels.save}
621
621
  </Button>
622
622
  )}
@@ -3,6 +3,7 @@ import { cn } from "../lib/cn";
3
3
  import { useKitLabels, useKitLocale, type LabelOverride } from "../i18n/kit-labels";
4
4
  import { useTextSize } from "../theme/text-size";
5
5
  import { DEFAULT_SPARKLINE_LABELS } from "./sparkline-labels";
6
+ import { formatWithMinus } from "../lib/minus";
6
7
 
7
8
  /**
8
9
  * The words a sparkline speaks. It has no axes, no tooltip and no legend, so its
@@ -118,7 +119,7 @@ function defaultFormat(locale: string | undefined): (v: number) => string {
118
119
  f = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });
119
120
  formatters.set(key, f);
120
121
  }
121
- return (v) => f.format(v);
122
+ return (v) => formatWithMinus(f, v);
122
123
  }
123
124
 
124
125
  const isValue = (v: number | null | undefined): v is number => typeof v === "number" && Number.isFinite(v);
@@ -16,6 +16,7 @@ import { Tooltip } from "./tooltip";
16
16
  // tile's private copy of the string did not.
17
17
  import { SKELETON_CLASS } from "./skeleton";
18
18
  import { DEFAULT_STAT_TILE_LABELS } from "./stat-tile-labels";
19
+ import { formatWithMinus } from "../lib/minus";
19
20
 
20
21
  /**
21
22
  * The words a stat tile speaks. The delta's arrow is an icon, and an icon is not a
@@ -352,11 +353,15 @@ export function StatTile({
352
353
  const fmt =
353
354
  format ??
354
355
  ((n: number) =>
355
- numberFormatter(kitLocale, {
356
- ...(currency ? { style: "currency", currency } : null),
357
- ...(compact ? { notation: "compact" } : null),
358
- ...numberFormat,
359
- }).format(n));
356
+ // U+2212 for a negative headline (0.49, KIT-1).
357
+ formatWithMinus(
358
+ numberFormatter(kitLocale, {
359
+ ...(currency ? { style: "currency", currency } : null),
360
+ ...(compact ? { notation: "compact" } : null),
361
+ ...numberFormat,
362
+ }),
363
+ n,
364
+ ));
360
365
  const show = (v: ReactNode) => (typeof v === "number" ? fmt(v) : v);
361
366
 
362
367
  const d = typeof delta === "number" ? { value: delta } : delta;
@@ -592,14 +597,21 @@ function FigureRow({
592
597
  // next row stayed on one — two layouts in one list, and a figure no longer beside
593
598
  // the period it belongs to. Now the label breaks within itself and the amount stays
594
599
  // on the label's first line, end-aligned like every other row.
600
+ //
601
+ // 0.49: between WORDS only. `overflow-wrap:anywhere` (with `min-w-0`) cut a single
602
+ // word at any letter — "PROJECT|ED" in a 160px KPI tile at 360px. The label now keeps
603
+ // a flex item's own floor, its longest word; only a word that cannot share the line
604
+ // with the figure sends the figure to the next line, still end-aligned. `break-word`
605
+ // is the last resort for a word wider than the whole tile, and does not lower that
606
+ // floor the way `anywhere` did.
595
607
  return (
596
608
  <div
597
609
  className={cn(
598
610
  "flex text-xs tabular-nums text-muted",
599
- stacked ? "flex-col items-start" : "items-baseline gap-x-2",
611
+ stacked ? "flex-col items-start" : "flex-wrap items-baseline gap-x-2",
600
612
  )}
601
613
  >
602
- <dt className="min-w-0 uppercase tracking-wide [overflow-wrap:anywhere]">{figure.label}</dt>
614
+ <dt className="max-w-full uppercase tracking-wide [overflow-wrap:break-word]">{figure.label}</dt>
603
615
  <dd
604
616
  data-private={priv}
605
617
  className={cn(
@@ -1,5 +1,6 @@
1
1
  import type { ComponentPropsWithRef, ReactNode } from "react";
2
2
  import { cn } from "../lib/cn";
3
+ import { statusTone, type StatusKind } from "../lib/status-tone";
3
4
  import type { ProgressBarTone } from "./progress-bar";
4
5
  import type { ChipHue } from "./chip";
5
6
 
@@ -90,6 +91,9 @@ const DOT_ON_FIRST_LINE: Record<StatusDotSize, string> = {
90
91
 
91
92
  export interface StatusDotProps extends Omit<ComponentPropsWithRef<"span">, "children"> {
92
93
  tone?: StatusDotTone;
94
+ /** What the dot means as a status (0.49 KIT-8), instead of a `tone` — see Chip's
95
+ * `status` and {@link statusTone}. An explicit `tone` wins. */
96
+ status?: StatusKind;
93
97
  size?: StatusDotSize;
94
98
  /**
95
99
  * Visible text beside the dot — a legend entry ("Rented") or a status ("Online"). It
@@ -122,7 +126,8 @@ export interface StatusDotProps extends Omit<ComponentPropsWithRef<"span">, "chi
122
126
  * this.
123
127
  */
124
128
  export function StatusDot({
125
- tone = "neutral",
129
+ tone: toneProp,
130
+ status,
126
131
  size = "sm",
127
132
  label,
128
133
  ring = false,
@@ -131,6 +136,7 @@ export function StatusDot({
131
136
  "aria-label": ariaLabel,
132
137
  ...rest
133
138
  }: StatusDotProps) {
139
+ const tone: StatusDotTone = toneProp ?? (status ? statusTone(status) : "neutral");
134
140
  const dot = (
135
141
  <span
136
142
  // The tone as data (0.34, refactor audit 2026-10, TST-9): a stable hook for a test or
@@ -580,7 +580,9 @@ function useTooltipTrigger(
580
580
  // A layer on the Escape stack while up (Wave 2, DUP-1): over a dialog, the first
581
581
  // Escape dismisses the bubble — WCAG 1.4.13, without moving focus — and the next one
582
582
  // the dialog. Inside a Modal the bubble used to be beyond reach of the key.
583
- useDismissLayer(open, () => setDismissed(true));
583
+ // Not an overlay: a tooltip annotates what it is on, so a floating launcher with one
584
+ // does not step aside for its own bubble (`hideWhenOverlay`, 0.49 KIT-11).
585
+ useDismissLayer(open, () => setDismissed(true), { overlay: false });
584
586
  // A press anywhere else closes it — the case a blur does not cover: Safari moves no
585
587
  // focus on a tap, and a tap on plain text moves it nowhere.
586
588
  useOutsideClick(triggerRef, close, open);
@@ -241,7 +241,7 @@ export function TranslationReviewEditor({
241
241
  <div className="flex flex-wrap gap-2">
242
242
  {!missing && (
243
243
  <Button
244
- variant="primary"
244
+ variant="default"
245
245
  commit={COMMIT_EXCEPT_BILLING}
246
246
  pending={busy === "approve"}
247
247
  disabled={busy !== null}
@@ -251,7 +251,7 @@ export function TranslationReviewEditor({
251
251
  </Button>
252
252
  )}
253
253
  <Button
254
- variant={missing ? "primary" : "danger"}
254
+ variant={missing ? "default" : "danger"}
255
255
  commit={COMMIT_EXCEPT_BILLING}
256
256
  pending={busy === "flag"}
257
257
  disabled={busy !== null || !canFlag}
@@ -1088,7 +1088,7 @@ export function TranslationReviewPanel({
1088
1088
  el.focus();
1089
1089
  }
1090
1090
  }}
1091
- variant="primary"
1091
+ variant="default"
1092
1092
  size="sm"
1093
1093
  commit={COMMIT_EXCEPT_BILLING}
1094
1094
  aria-describedby={questionId}
@@ -1204,7 +1204,7 @@ export function TranslationReviewPanel({
1204
1204
  {editable && (
1205
1205
  <BulkActionBar count={selectedRows.length} onClear={() => setSelected(new Set())}>
1206
1206
  <Button
1207
- variant="primary"
1207
+ variant="default"
1208
1208
  size="sm"
1209
1209
  commit={COMMIT_EXCEPT_BILLING}
1210
1210
  pending={isQueued("bulk-approve")}