@eifi1/ui-kit 0.32.2 → 0.33.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 (363) hide show
  1. package/README.md +16 -8
  2. package/dist/account/delete-account-setting.d.ts +10 -10
  3. package/dist/account/delete-account-setting.js +2 -1
  4. package/dist/account/delete-account-setting.js.map +1 -1
  5. package/dist/account/email-change-setting.d.ts +5 -0
  6. package/dist/account/email-change-setting.js +4 -3
  7. package/dist/account/email-change-setting.js.map +1 -1
  8. package/dist/account/sessions-setting.d.ts +4 -0
  9. package/dist/account/sessions-setting.js +3 -2
  10. package/dist/account/sessions-setting.js.map +1 -1
  11. package/dist/admin/admin-action-confirm.d.ts +10 -10
  12. package/dist/admin/admin-action-confirm.js.map +1 -1
  13. package/dist/admin/invitations-panel.d.ts +10 -10
  14. package/dist/admin/invitations-panel.js +4 -3
  15. package/dist/admin/invitations-panel.js.map +1 -1
  16. package/dist/admin/plan-change.d.ts +100 -0
  17. package/dist/admin/plan-change.js +208 -0
  18. package/dist/admin/plan-change.js.map +1 -0
  19. package/dist/admin/reviewer-scope-editor.d.ts +10 -10
  20. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  21. package/dist/admin/role-select.d.ts +7 -7
  22. package/dist/admin/role-select.js.map +1 -1
  23. package/dist/admin/transfer-ownership-dialog.d.ts +10 -10
  24. package/dist/admin/user-roster.d.ts +7 -7
  25. package/dist/admin/user-roster.js +2 -2
  26. package/dist/admin/user-roster.js.map +1 -1
  27. package/dist/auth/auth-errors.d.ts +13 -2
  28. package/dist/auth/auth-errors.js +5 -1
  29. package/dist/auth/auth-errors.js.map +1 -1
  30. package/dist/auth/complete-name-dialog.d.ts +4 -1
  31. package/dist/auth/complete-name-dialog.js +2 -1
  32. package/dist/auth/complete-name-dialog.js.map +1 -1
  33. package/dist/auth/register-form.d.ts +10 -10
  34. package/dist/billing/billing-banner.d.ts +13 -11
  35. package/dist/billing/billing-banner.js.map +1 -1
  36. package/dist/billing/billing-labels.d.ts +10 -10
  37. package/dist/billing/billing-labels.js +10 -1
  38. package/dist/billing/billing-labels.js.map +1 -1
  39. package/dist/billing/billing-lock.d.ts +39 -12
  40. package/dist/billing/billing-lock.js +9 -3
  41. package/dist/billing/billing-lock.js.map +1 -1
  42. package/dist/billing/billing-overview.d.ts +154 -0
  43. package/dist/billing/billing-overview.js +2 -0
  44. package/dist/billing/billing-overview.js.map +1 -0
  45. package/dist/billing/billing-standing.d.ts +10 -10
  46. package/dist/billing/checkout-processing.d.ts +217 -0
  47. package/dist/billing/checkout-processing.js +198 -0
  48. package/dist/billing/checkout-processing.js.map +1 -0
  49. package/dist/billing/checkout-return.d.ts +51 -0
  50. package/dist/billing/checkout-return.js +43 -0
  51. package/dist/billing/checkout-return.js.map +1 -0
  52. package/dist/billing/pay-page.d.ts +210 -0
  53. package/dist/billing/pay-page.js +113 -0
  54. package/dist/billing/pay-page.js.map +1 -0
  55. package/dist/billing/plan-card.d.ts +10 -10
  56. package/dist/billing/plan-limit-notice.d.ts +10 -10
  57. package/dist/billing/plan-limit-toast.d.ts +143 -0
  58. package/dist/billing/plan-limit-toast.js +40 -0
  59. package/dist/billing/plan-limit-toast.js.map +1 -0
  60. package/dist/billing/subscription-actions.d.ts +38 -15
  61. package/dist/billing/subscription-actions.js +9 -2
  62. package/dist/billing/subscription-actions.js.map +1 -1
  63. package/dist/billing/subscription-status-chip.d.ts +9 -9
  64. package/dist/bin/eifi1-pay-page.mjs +166 -0
  65. package/dist/chart.d.ts +10 -10
  66. package/dist/components/account-chips.d.ts +6 -1
  67. package/dist/components/account-settings.d.ts +4 -1
  68. package/dist/components/account-settings.js +2 -1
  69. package/dist/components/account-settings.js.map +1 -1
  70. package/dist/components/amount-input.d.ts +10 -10
  71. package/dist/components/autocomplete.d.ts +2 -1
  72. package/dist/components/autocomplete.js +3 -1
  73. package/dist/components/autocomplete.js.map +1 -1
  74. package/dist/components/button-group.d.ts +10 -10
  75. package/dist/components/button-group.js +1 -1
  76. package/dist/components/button-group.js.map +1 -1
  77. package/dist/components/calculator.d.ts +10 -10
  78. package/dist/components/calculator.js +3 -3
  79. package/dist/components/calculator.js.map +1 -1
  80. package/dist/components/checkbox-group.d.ts +2 -1
  81. package/dist/components/checkbox-group.js.map +1 -1
  82. package/dist/components/checkbox.d.ts +2 -1
  83. package/dist/components/checkbox.js.map +1 -1
  84. package/dist/components/chip.d.ts +2 -1
  85. package/dist/components/chip.js +19 -17
  86. package/dist/components/chip.js.map +1 -1
  87. package/dist/components/choice-card.d.ts +3 -1
  88. package/dist/components/choice-card.js +1 -1
  89. package/dist/components/choice-card.js.map +1 -1
  90. package/dist/components/combobox.d.ts +4 -3
  91. package/dist/components/combobox.js.map +1 -1
  92. package/dist/components/confirm-dialog.d.ts +10 -10
  93. package/dist/components/confirm-dialog.js +6 -3
  94. package/dist/components/confirm-dialog.js.map +1 -1
  95. package/dist/components/copy-button.d.ts +11 -11
  96. package/dist/components/country-select.d.ts +2 -1
  97. package/dist/components/country-select.js.map +1 -1
  98. package/dist/components/danger-confirm.d.ts +10 -10
  99. package/dist/components/danger-confirm.js.map +1 -1
  100. package/dist/components/data-table-cells.d.ts +12 -1
  101. package/dist/components/data-table-filter-popover.d.ts +12 -1
  102. package/dist/components/data-table-filters.d.ts +12 -1
  103. package/dist/components/data-table-labels.d.ts +12 -1
  104. package/dist/components/data-table-pagination.d.ts +12 -1
  105. package/dist/components/data-table.d.ts +12 -1
  106. package/dist/components/data-table.js +3 -3
  107. package/dist/components/data-table.js.map +1 -1
  108. package/dist/components/date-picker.js +6 -4
  109. package/dist/components/date-picker.js.map +1 -1
  110. package/dist/components/entity-combobox.d.ts +3 -2
  111. package/dist/components/entity-combobox.js.map +1 -1
  112. package/dist/components/facing-pair.d.ts +10 -10
  113. package/dist/components/field-parts.d.ts +32 -4
  114. package/dist/components/field-parts.js +54 -19
  115. package/dist/components/field-parts.js.map +1 -1
  116. package/dist/components/file-button.d.ts +10 -10
  117. package/dist/components/file-button.js +3 -2
  118. package/dist/components/file-button.js.map +1 -1
  119. package/dist/components/file-dropzone.d.ts +11 -11
  120. package/dist/components/file-dropzone.js.map +1 -1
  121. package/dist/components/floating-panel.js +5 -5
  122. package/dist/components/floating-panel.js.map +1 -1
  123. package/dist/components/form-actions.d.ts +9 -9
  124. package/dist/components/form-actions.js +3 -3
  125. package/dist/components/form-actions.js.map +1 -1
  126. package/dist/components/iban-input.d.ts +10 -10
  127. package/dist/components/inline-edit-field.d.ts +2 -1
  128. package/dist/components/inline-edit-field.js +6 -6
  129. package/dist/components/inline-edit-field.js.map +1 -1
  130. package/dist/components/language-select.d.ts +10 -10
  131. package/dist/components/legal-page.d.ts +10 -10
  132. package/dist/components/legal.d.ts +10 -10
  133. package/dist/components/list.d.ts +2 -1
  134. package/dist/components/list.js +4 -5
  135. package/dist/components/list.js.map +1 -1
  136. package/dist/components/money-field.d.ts +10 -10
  137. package/dist/components/month-picker.js +54 -44
  138. package/dist/components/month-picker.js.map +1 -1
  139. package/dist/components/multi-entity-combobox.d.ts +3 -2
  140. package/dist/components/multi-entity-combobox.js.map +1 -1
  141. package/dist/components/number-field.d.ts +10 -10
  142. package/dist/components/number-input.d.ts +10 -10
  143. package/dist/components/numpad-sheet.d.ts +10 -10
  144. package/dist/components/numpad-sheet.js +2 -2
  145. package/dist/components/numpad-sheet.js.map +1 -1
  146. package/dist/components/phone-input.d.ts +10 -10
  147. package/dist/components/row-actions.d.ts +5 -5
  148. package/dist/components/row-actions.js +130 -32
  149. package/dist/components/row-actions.js.map +1 -1
  150. package/dist/components/series-chart.d.ts +10 -10
  151. package/dist/components/settings-fields.d.ts +10 -10
  152. package/dist/components/share-card.d.ts +7 -7
  153. package/dist/components/share-card.js +26 -10
  154. package/dist/components/share-card.js.map +1 -1
  155. package/dist/components/status-dot.d.ts +1 -0
  156. package/dist/components/status-dot.js +2 -2
  157. package/dist/components/status-dot.js.map +1 -1
  158. package/dist/components/swatch-picker.d.ts +2 -1
  159. package/dist/components/swatch-picker.js.map +1 -1
  160. package/dist/components/swipeable-row.d.ts +88 -17
  161. package/dist/components/swipeable-row.js +46 -9
  162. package/dist/components/swipeable-row.js.map +1 -1
  163. package/dist/components/switch.d.ts +2 -1
  164. package/dist/components/switch.js.map +1 -1
  165. package/dist/components/table.d.ts +2 -1
  166. package/dist/components/table.js +6 -0
  167. package/dist/components/table.js.map +1 -1
  168. package/dist/components/text-link.d.ts +9 -9
  169. package/dist/components/time-input.d.ts +10 -10
  170. package/dist/components/toggle-group.d.ts +2 -1
  171. package/dist/components/toggle-group.js +1 -1
  172. package/dist/components/toggle-group.js.map +1 -1
  173. package/dist/components/translation-review-editor.d.ts +4 -0
  174. package/dist/components/translation-review-editor.js +4 -3
  175. package/dist/components/translation-review-editor.js.map +1 -1
  176. package/dist/components/translation-review-parts.d.ts +1 -0
  177. package/dist/components/translation-review.d.ts +1 -0
  178. package/dist/components/translation-review.js +15 -18
  179. package/dist/components/translation-review.js.map +1 -1
  180. package/dist/components/ui.d.ts +9 -9
  181. package/dist/components/ui.js +47 -27
  182. package/dist/components/ui.js.map +1 -1
  183. package/dist/components/use-table-state.d.ts +12 -1
  184. package/dist/components/user-avatar.d.ts +1 -0
  185. package/dist/components/write-lock.d.ts +101 -13
  186. package/dist/components/write-lock.js +37 -13
  187. package/dist/components/write-lock.js.map +1 -1
  188. package/dist/{data-table-labels-kpAAVi6g.d.ts → data-table-labels-BJ5JE_X2.d.ts} +17 -0
  189. package/dist/data-table.d.ts +12 -1
  190. package/dist/{delete-account-setting-Dy4kJgUm.d.ts → delete-account-setting-DiPX4Vue.d.ts} +355 -41
  191. package/dist/feedback/feedback-attachment.d.ts +10 -10
  192. package/dist/feedback/feedback-attachment.js +7 -7
  193. package/dist/feedback/feedback-attachment.js.map +1 -1
  194. package/dist/feedback/feedback-context.d.ts +10 -10
  195. package/dist/feedback/feedback-crash.d.ts +10 -10
  196. package/dist/feedback/feedback-dialog.d.ts +10 -10
  197. package/dist/feedback/feedback-inbox.d.ts +9 -9
  198. package/dist/feedback/feedback-inbox.js +7 -3
  199. package/dist/feedback/feedback-inbox.js.map +1 -1
  200. package/dist/feedback/feedback-labels.d.ts +10 -10
  201. package/dist/feedback/feedback-menu.d.ts +3 -3
  202. package/dist/feedback/feedback-record.d.ts +10 -10
  203. package/dist/feedback/feedback-row-detail.d.ts +10 -10
  204. package/dist/feedback/feedback-row-detail.js.map +1 -1
  205. package/dist/feedback/feedback-status-undo.d.ts +10 -10
  206. package/dist/feedback/feedback-submit.d.ts +10 -10
  207. package/dist/feedback/feedback-swipe.d.ts +10 -10
  208. package/dist/feedback/feedback-swipe.js +3 -6
  209. package/dist/feedback/feedback-swipe.js.map +1 -1
  210. package/dist/feedback/feedback-table.d.ts +10 -10
  211. package/dist/feedback/feedback-table.js.map +1 -1
  212. package/dist/feedback/feedback-thread.d.ts +10 -10
  213. package/dist/feedback.d.ts +10 -10
  214. package/dist/hooks/use-file-drop.d.ts +10 -10
  215. package/dist/i18n/defaults.d.ts +10 -10
  216. package/dist/i18n/defaults.js +2 -0
  217. package/dist/i18n/defaults.js.map +1 -1
  218. package/dist/i18n/german.d.ts +10 -10
  219. package/dist/i18n/german.js +34 -1
  220. package/dist/i18n/german.js.map +1 -1
  221. package/dist/i18n/kit-labels.d.ts +4 -4
  222. package/dist/i18n/kit-labels.js.map +1 -1
  223. package/dist/i18n/languages.d.ts +10 -10
  224. package/dist/i18n/locales/de-CH.d.ts +10 -10
  225. package/dist/i18n/locales/en.d.ts +10 -10
  226. package/dist/i18n/locales/es.d.ts +10 -10
  227. package/dist/i18n/locales/es.js +34 -1
  228. package/dist/i18n/locales/es.js.map +1 -1
  229. package/dist/i18n/locales/fr.d.ts +10 -10
  230. package/dist/i18n/locales/fr.js +35 -1
  231. package/dist/i18n/locales/fr.js.map +1 -1
  232. package/dist/i18n/locales/hu.d.ts +10 -10
  233. package/dist/i18n/locales/hu.js +36 -1
  234. package/dist/i18n/locales/hu.js.map +1 -1
  235. package/dist/i18n/locales/it.d.ts +10 -10
  236. package/dist/i18n/locales/it.js +34 -1
  237. package/dist/i18n/locales/it.js.map +1 -1
  238. package/dist/i18n/locales/zh.d.ts +10 -10
  239. package/dist/i18n/locales/zh.js +34 -1
  240. package/dist/i18n/locales/zh.js.map +1 -1
  241. package/dist/i18n/review.d.ts +10 -10
  242. package/dist/i18n/review.js +8 -1
  243. package/dist/i18n/review.js.map +1 -1
  244. package/dist/index.d.ts +10 -5
  245. package/dist/index.js +40 -2
  246. package/dist/index.js.map +1 -1
  247. package/dist/landing/landing-actions.d.ts +10 -10
  248. package/dist/landing/landing-sections.d.ts +10 -10
  249. package/dist/landing/public-header.d.ts +10 -10
  250. package/dist/landing/routing.d.ts +10 -10
  251. package/dist/pay/.well-known/apple-developer-merchantid-domain-association +1 -0
  252. package/dist/pay/index.html +30 -0
  253. package/dist/pay/pay.css +126 -0
  254. package/dist/pay/pay.js +198 -0
  255. package/dist/rhf/fields.d.ts +10 -10
  256. package/dist/rhf/form.d.ts +10 -10
  257. package/dist/rhf.d.ts +10 -10
  258. package/dist/settings/settings-section.d.ts +10 -10
  259. package/dist/shell/app-shell.d.ts +9 -9
  260. package/dist/shell/top-bar-brand.d.ts +10 -10
  261. package/dist/shell/topbar-action-menu.d.ts +1 -0
  262. package/dist/shell.d.ts +10 -10
  263. package/dist/testing/css-variables.d.ts +74 -0
  264. package/dist/testing/css-variables.generated.d.ts +7 -0
  265. package/dist/testing/css-variables.generated.js +8 -0
  266. package/dist/testing/css-variables.generated.js.map +1 -0
  267. package/dist/testing/css-variables.js +93 -0
  268. package/dist/testing/css-variables.js.map +1 -0
  269. package/dist/testing/index.d.ts +1 -0
  270. package/dist/testing/index.js +12 -0
  271. package/dist/testing/index.js.map +1 -0
  272. package/dist/theme/contrast-tokens.d.ts +44 -1
  273. package/dist/theme/contrast-tokens.js +21 -0
  274. package/dist/theme/contrast-tokens.js.map +1 -1
  275. package/dist/theme/contrast.d.ts +2 -2
  276. package/dist/theme/palette-derive.js +60 -11
  277. package/dist/theme/palette-derive.js.map +1 -1
  278. package/dist/theme/palette-presets.js +45 -43
  279. package/dist/theme/palette-presets.js.map +1 -1
  280. package/dist/wizard/stepper-nav.d.ts +11 -11
  281. package/dist/wizard/stepper-nav.js.map +1 -1
  282. package/dist/wizard.d.ts +10 -10
  283. package/package.json +10 -2
  284. package/src/account/delete-account-setting.tsx +6 -1
  285. package/src/account/email-change-setting.tsx +9 -3
  286. package/src/account/sessions-setting.tsx +7 -2
  287. package/src/admin/admin-action-confirm.tsx +2 -1
  288. package/src/admin/invitations-panel.tsx +9 -3
  289. package/src/admin/plan-change.tsx +459 -0
  290. package/src/admin/reviewer-scope-editor.tsx +2 -1
  291. package/src/admin/role-select.tsx +2 -1
  292. package/src/admin/user-roster.tsx +5 -3
  293. package/src/auth/auth-errors.ts +25 -2
  294. package/src/auth/complete-name-dialog.tsx +6 -2
  295. package/src/billing/billing-banner.tsx +3 -1
  296. package/src/billing/billing-labels.ts +45 -0
  297. package/src/billing/billing-lock.ts +36 -3
  298. package/src/billing/billing-overview.ts +58 -0
  299. package/src/billing/checkout-processing.ts +391 -0
  300. package/src/billing/checkout-return.ts +90 -0
  301. package/src/billing/pay-page.ts +242 -0
  302. package/src/billing/plan-limit-toast.tsx +91 -0
  303. package/src/billing/subscription-actions.tsx +41 -8
  304. package/src/components/account-settings.tsx +6 -2
  305. package/src/components/autocomplete.tsx +5 -2
  306. package/src/components/button-group.tsx +4 -1
  307. package/src/components/calculator.tsx +10 -6
  308. package/src/components/checkbox-group.tsx +2 -2
  309. package/src/components/checkbox.tsx +2 -1
  310. package/src/components/chip.tsx +37 -32
  311. package/src/components/choice-card.tsx +4 -2
  312. package/src/components/combobox.tsx +4 -4
  313. package/src/components/confirm-dialog.tsx +13 -6
  314. package/src/components/country-select.tsx +2 -2
  315. package/src/components/danger-confirm.tsx +2 -2
  316. package/src/components/data-table.tsx +20 -9
  317. package/src/components/date-picker.tsx +10 -7
  318. package/src/components/entity-combobox.tsx +3 -3
  319. package/src/components/field-parts.tsx +61 -14
  320. package/src/components/file-button.tsx +5 -4
  321. package/src/components/file-dropzone.tsx +2 -2
  322. package/src/components/floating-panel.tsx +7 -5
  323. package/src/components/form-actions.tsx +12 -5
  324. package/src/components/inline-edit-field.tsx +13 -13
  325. package/src/components/list.tsx +10 -9
  326. package/src/components/month-picker.tsx +8 -2
  327. package/src/components/multi-entity-combobox.tsx +3 -3
  328. package/src/components/numpad-sheet.tsx +8 -4
  329. package/src/components/row-actions.tsx +208 -58
  330. package/src/components/share-card.tsx +67 -13
  331. package/src/components/status-dot.tsx +7 -7
  332. package/src/components/swatch-picker.tsx +2 -2
  333. package/src/components/swipeable-row.tsx +138 -27
  334. package/src/components/switch.tsx +2 -1
  335. package/src/components/table.tsx +8 -1
  336. package/src/components/toggle-group.tsx +5 -3
  337. package/src/components/translation-review-editor.tsx +8 -3
  338. package/src/components/translation-review.tsx +18 -22
  339. package/src/components/ui.tsx +92 -43
  340. package/src/components/write-lock.tsx +150 -24
  341. package/src/feedback/feedback-attachment.tsx +13 -9
  342. package/src/feedback/feedback-inbox.tsx +10 -6
  343. package/src/feedback/feedback-row-detail.tsx +3 -2
  344. package/src/feedback/feedback-swipe.tsx +6 -11
  345. package/src/feedback/feedback-table.tsx +2 -1
  346. package/src/i18n/defaults.ts +2 -0
  347. package/src/i18n/german.ts +38 -0
  348. package/src/i18n/kit-labels.tsx +4 -0
  349. package/src/i18n/locales/es.ts +34 -0
  350. package/src/i18n/locales/fr.ts +39 -0
  351. package/src/i18n/locales/hu.ts +37 -0
  352. package/src/i18n/locales/it.ts +35 -0
  353. package/src/i18n/locales/zh.ts +33 -0
  354. package/src/i18n/review.ts +7 -0
  355. package/src/index.ts +48 -4
  356. package/src/testing/css-variables.generated.ts +112 -0
  357. package/src/testing/css-variables.ts +191 -0
  358. package/src/testing/index.ts +13 -0
  359. package/src/theme/contrast-tokens.ts +53 -2
  360. package/src/theme/palette-derive.ts +87 -10
  361. package/src/theme/palette-presets.ts +45 -43
  362. package/src/wizard/stepper-nav.tsx +2 -1
  363. package/tokens.css +306 -46
@@ -7,8 +7,9 @@ import { useLargeText } from "../hooks/use-large-text";
7
7
  import { useKitLabels } from "../i18n/kit-labels";
8
8
  import { Popover } from "./popover";
9
9
  import { Button, CompactControls, IconButton } from "./ui";
10
- import { useCommitReason } from "./write-lock";
11
- import type { IconButtonSize } from "./ui";
10
+ import { useCommitReason, type CommitScope } from "./write-lock";
11
+ import type { IconButtonGlyphSize, IconButtonSize } from "./ui";
12
+ import type { TooltipSide } from "./tooltip";
12
13
  import type { DataTableColumn } from "./data-table";
13
14
 
14
15
  /**
@@ -24,8 +25,17 @@ import type { DataTableColumn } from "./data-table";
24
25
  *
25
26
  * The data is the roster's `UserRowActionList`, generalised: entries, and `false` /
26
27
  * `null` for the ones a condition left out (`row.locked || {…}`).
28
+ *
29
+ * 0.33 (§10.16): an action carries its state at both sizes — a toggle (`pressed`), a
30
+ * disclosure (`expanded`, `controls`), a tone, a tour anchor (`dataTour`) — so a row
31
+ * with an "on" share button or a tour step no longer keeps hand-rolled IconButtons at
32
+ * Normal and this only at Large (keksdose k15).
27
33
  */
28
34
 
35
+ /** An action's colour: IconButton's tone inline; in the menu the glyph takes it and the
36
+ * words keep the text colour, except `danger`, whose words are the danger text. */
37
+ export type RowActionTone = "default" | "muted" | "info" | "warning" | "danger";
38
+
29
39
  /** One action of a row. */
30
40
  export interface RowAction {
31
41
  /** React key. Default: the label. */
@@ -57,9 +67,15 @@ export interface RowAction {
57
67
  * without one is a small text button; in the menu it is the words alone.
58
68
  */
59
69
  icon?: LucideIcon | ReactElement;
60
- /** `danger` for delete, revoke, erase: the quiet red of IconButton's `tone="danger"`
61
- * inline, the danger text tone in the menu. */
62
- tone?: "default" | "danger";
70
+ /**
71
+ * `danger` for delete, revoke, erase: the quiet red of IconButton's `tone="danger"`
72
+ * inline, the danger text tone in the menu. `muted` (an open or add that should not
73
+ * compete), `info` (a notice-worthy, harmless action: keksdose's reconcile) and
74
+ * `warning` (a flag that wants attention): IconButton's tone inline; in the menu the
75
+ * glyph takes it and the words keep the text colour, so `muted` looks like `default`
76
+ * there (0.33).
77
+ */
78
+ tone?: RowActionTone;
63
79
  /** Why it is not available for this row. It stays listed, focusable, and says so — an
64
80
  * action that silently vanished would leave the reader looking for it. */
65
81
  disabledReason?: ReactNode;
@@ -68,7 +84,7 @@ export interface RowAction {
68
84
  * {@link WriteLockProvider} it is refused with the lock's reason, as {@link Button}'s
69
85
  * `commit` is, inline and in the menu alike. No provider, or an unlocked one: no effect.
70
86
  */
71
- commit?: boolean;
87
+ commit?: CommitScope;
72
88
  /**
73
89
  * In flight — the request it started has not answered. Inline, the icon turns into
74
90
  * IconButton's spinner (`pending`); collapsed, the "⋯" button does, since the menu
@@ -84,6 +100,33 @@ export interface RowAction {
84
100
  disabled?: boolean;
85
101
  /** Leave it out for this row. */
86
102
  hidden?: boolean;
103
+ /**
104
+ * A toggle (0.33): `aria-pressed` and IconButton's "on" look inline, `aria-pressed` on
105
+ * the menu entry. Keep the label the same in both states ("Share budget"): the pressed
106
+ * state already says whether it is on. Not on an `href` action — a link is not a
107
+ * toggle — nor with `expanded`.
108
+ */
109
+ pressed?: boolean;
110
+ /**
111
+ * Opens or closes something IN THE PAGE (0.33) — a share card under the row, an
112
+ * add-category row: `aria-expanded`, inline and on the menu entry. Not with `pressed`:
113
+ * a disclosure says open, not on. Not on an `href` action.
114
+ */
115
+ expanded?: boolean;
116
+ /** The `id` of what `expanded` opens: `aria-controls`, inline and on the menu entry. */
117
+ controls?: string;
118
+ /**
119
+ * A tour anchor (0.33, §10.18): `data-tour` on ONE element per action at a time.
120
+ * Inline, on the action's own control (the IconButton, the small text button, the
121
+ * link). Collapsed, on an `aria-hidden` box laid over the "⋯" button — `[data-tour=x]`
122
+ * resolves to a box the size of the "⋯", whose `data-slot="row-actions"` box holds the
123
+ * button, two anchored actions in one row don't fight over one attribute, and a tap
124
+ * still reaches the button. Never on the entry in the open menu: the kit's tour takes
125
+ * the first visible match and the menu is portalled after the row, so a copy there
126
+ * would never be found, only make the selector match twice. The kit's name for it, as
127
+ * `AppShellNavItem.dataTour`.
128
+ */
129
+ dataTour?: string;
87
130
  }
88
131
 
89
132
  /** What a row's actions may be: entries, and `false` / `null` / `undefined` for the ones
@@ -130,8 +173,14 @@ export interface RowActionsProps {
130
173
  size?: RowActionsSize;
131
174
  /** The menu's width. Default `"15rem"`, in rem so it grows with the words (§3.2). */
132
175
  menuWidth?: `${number}rem`;
133
- /** On the wrapper: the inline strip, or the "⋯" button's box. */
176
+ /** On the wrapper: the inline strip, or the box round the "⋯" button. */
134
177
  className?: string;
178
+ /** The inline glyphs' size, IconButton's `glyphSize` — e.g. 14 in a dense header strip
179
+ * (keksdose's category group header). The "⋯" button keeps its box's own. */
180
+ glyphSize?: IconButtonGlyphSize;
181
+ /** The inline tooltips' side, IconButton's `tooltipSide` — `"start"` for a table's last
182
+ * column, so a bubble does not run off the row's end. Per row: every inline action. */
183
+ tooltipSide?: TooltipSide;
135
184
  labels?: Partial<RowActionsLabels>;
136
185
  }
137
186
 
@@ -147,6 +196,30 @@ function glyphOf(icon: RowAction["icon"]): ReactNode {
147
196
  return <Icon />;
148
197
  }
149
198
 
199
+ /** The menu glyph's tint per tone (0.33): the words keep the text colour, so the glyph
200
+ * carries `info` and `warning` — IconButton's toned colours. `danger` colours the words
201
+ * through Button's tone, and `muted` is the default look in a menu. */
202
+ const MENU_GLYPH_TONE: Partial<Record<RowActionTone, string>> = {
203
+ info: "text-info",
204
+ warning: "text-warning",
205
+ };
206
+
207
+ /** The state an action's control says, inline and in the menu alike: `aria-expanded` and
208
+ * `aria-controls` for a disclosure (a toggle's `aria-pressed` goes through the button's
209
+ * own `pressed`, which also draws the "on" look). */
210
+ function stateAttrs(action: RowAction) {
211
+ return {
212
+ "aria-expanded": action.expanded,
213
+ "aria-controls": action.expanded === undefined ? undefined : action.controls,
214
+ };
215
+ }
216
+
217
+ /** An action's tone on its inline control, for a caller's CSS and a test: `data-tone`,
218
+ * left off for the default look. */
219
+ function toneAttr(tone: RowActionTone | undefined) {
220
+ return { "data-tone": tone === undefined || tone === "default" ? undefined : tone };
221
+ }
222
+
150
223
  /**
151
224
  * A row's actions, inline or as a "⋯" menu — see {@link RowActionsCollapse}. Every
152
225
  * control stops its click (and the Enter/Space it stands for) from reaching the row, so
@@ -170,6 +243,8 @@ export function RowActions({
170
243
  size = "xs",
171
244
  menuWidth = "15rem",
172
245
  className,
246
+ glyphSize,
247
+ tooltipSide,
173
248
  labels: labelsProp,
174
249
  }: RowActionsProps) {
175
250
  const labels = useKitLabels("rowActions", DEFAULT_ROW_ACTIONS_LABELS, labelsProp);
@@ -181,7 +256,13 @@ export function RowActions({
181
256
  const strip = (
182
257
  <div data-slot="row-actions" className={cn("inline-flex items-center gap-0.5", className)}>
183
258
  {entries.map((action) => (
184
- <InlineAction key={action.key ?? action.label} action={action} size={size} />
259
+ <InlineAction
260
+ key={action.key ?? action.label}
261
+ action={action}
262
+ size={size}
263
+ glyphSize={glyphSize}
264
+ tooltipSide={tooltipSide}
265
+ />
185
266
  ))}
186
267
  </div>
187
268
  );
@@ -192,48 +273,62 @@ export function RowActions({
192
273
  const title = name ? labels.actionsFor(name) : labels.actions;
193
274
  // The menu closed when the action was chosen, so what is in flight shows on "⋯".
194
275
  const busy = entries.some((action) => action.pending);
276
+ const anchored = entries.filter((action) => action.dataTour);
195
277
  return (
196
- <Popover
197
- width={menuWidth}
198
- aria-label={title}
199
- className="p-1"
200
- trigger={({ open, toggle, ref }) => (
201
- <IconButton
202
- ref={ref}
203
- size={size}
204
- label={title}
205
- // "⋯" is read by everyone as "more actions"; the menu it opens is the words.
206
- labelVisible={false}
207
- aria-haspopup="dialog"
208
- aria-expanded={open}
209
- pending={busy}
210
- stopPropagation
211
- onClick={toggle}
212
- className={className}
213
- >
214
- <MoreHorizontal />
215
- </IconButton>
216
- )}
217
- // React carries an event out of a portal along the COMPONENT tree, so a click in
218
- // the menu reached a clickable row or a DataTable `onRowClick` the menu was opened
219
- // from, though the panel is nowhere near it in the page — only the "⋯" trigger
220
- // stopped its own (keksdose's 0.32 report). Stopped on the PANEL itself, its padding
221
- // and border included (0.32.2: 0.32.1 stopped it on an inner box, and a click on
222
- // the panel's `p-1` still went through): clicks, and the Enter / Space a row also
223
- // listens for. Escape goes on, to the popover's own handler that closes it.
224
- onClick={stopClick}
225
- onKeyDown={stopActivationKeys}
226
- >
227
- {(close) => (
228
- <ul data-slot="row-actions-menu" className="space-y-0.5">
229
- {entries.map((action) => (
230
- <li key={action.key ?? action.label}>
231
- <MenuAction action={action} close={close} />
232
- </li>
233
- ))}
234
- </ul>
235
- )}
236
- </Popover>
278
+ // The "⋯" in a box of its own, the inline strip's slot: the tour anchors of the
279
+ // collapsed actions lie over the button in it (see `dataTour`), and `className`
280
+ // places the box as it places the strip.
281
+ <div data-slot="row-actions" className={cn("relative inline-flex", className)}>
282
+ <Popover
283
+ width={menuWidth}
284
+ aria-label={title}
285
+ className="p-1"
286
+ trigger={({ open, toggle, ref }) => (
287
+ <IconButton
288
+ ref={ref}
289
+ size={size}
290
+ label={title}
291
+ // "⋯" is read by everyone as "more actions"; the menu it opens is the words.
292
+ labelVisible={false}
293
+ aria-haspopup="dialog"
294
+ aria-expanded={open}
295
+ pending={busy}
296
+ stopPropagation
297
+ onClick={toggle}
298
+ >
299
+ <MoreHorizontal />
300
+ </IconButton>
301
+ )}
302
+ // React carries an event out of a portal along the COMPONENT tree, so a click in
303
+ // the menu reached a clickable row or a DataTable `onRowClick` the menu was opened
304
+ // from, though the panel is nowhere near it in the page — only the "⋯" trigger
305
+ // stopped its own (keksdose's 0.32 report). Stopped on the PANEL itself, its padding
306
+ // and border included (0.32.2: 0.32.1 stopped it on an inner box, and a click on
307
+ // the panel's `p-1` still went through): clicks, and the Enter / Space a row also
308
+ // listens for. Escape goes on, to the popover's own handler that closes it.
309
+ onClick={stopClick}
310
+ onKeyDown={stopActivationKeys}
311
+ >
312
+ {(close) => (
313
+ <ul data-slot="row-actions-menu" className="space-y-0.5">
314
+ {entries.map((action) => (
315
+ <li key={action.key ?? action.label}>
316
+ <MenuAction action={action} close={close} />
317
+ </li>
318
+ ))}
319
+ </ul>
320
+ )}
321
+ </Popover>
322
+ {anchored.map((action) => (
323
+ // The More cell's TourAnchor: decoration over the button, which keeps every tap.
324
+ <span
325
+ key={action.key ?? action.label}
326
+ aria-hidden
327
+ data-tour={action.dataTour}
328
+ className="pointer-events-none absolute inset-0"
329
+ />
330
+ ))}
331
+ </div>
237
332
  );
238
333
  }
239
334
 
@@ -261,10 +356,15 @@ function useActionHref(action: RowAction): string | undefined {
261
356
  function MenuAction({ action, close }: { action: RowAction; close: () => void }) {
262
357
  const href = useActionHref(action);
263
358
  const glyph = glyphOf(action.icon);
359
+ const glyphTone = action.tone === undefined ? undefined : MENU_GLYPH_TONE[action.tone];
264
360
  const words = (
265
361
  <>
266
362
  {glyph && (
267
- <span aria-hidden className="flex shrink-0 [&_svg]:size-4">
363
+ <span
364
+ aria-hidden
365
+ data-tone={glyphTone ? action.tone : undefined}
366
+ className={cn("flex shrink-0 [&_svg]:size-4", glyphTone)}
367
+ >
268
368
  {glyph}
269
369
  </span>
270
370
  )}
@@ -295,11 +395,13 @@ function MenuAction({ action, close }: { action: RowAction; close: () => void })
295
395
  }
296
396
  return (
297
397
  <Button
398
+ {...stateAttrs(action)}
298
399
  type="button"
299
400
  variant="ghost"
300
401
  size="sm"
301
402
  stretch
302
403
  tone={tone}
404
+ pressed={action.pressed}
303
405
  disabledReason={action.disabledReason}
304
406
  commit={action.commit}
305
407
  pending={action.pending}
@@ -313,20 +415,37 @@ function MenuAction({ action, close }: { action: RowAction; close: () => void })
313
415
  }
314
416
 
315
417
  /** One inline action: an IconButton with its icon, else a small text button — each a
316
- * link for an `href` (see {@link useActionHref}). */
317
- function InlineAction({ action, size }: { action: RowAction; size: RowActionsSize }) {
418
+ * link for an `href` (see {@link useActionHref}). The control carries the action's
419
+ * state, its tone and its tour anchor. */
420
+ function InlineAction({
421
+ action,
422
+ size,
423
+ glyphSize,
424
+ tooltipSide,
425
+ }: {
426
+ action: RowAction;
427
+ size: RowActionsSize;
428
+ glyphSize?: IconButtonGlyphSize;
429
+ tooltipSide?: TooltipSide;
430
+ }) {
318
431
  const href = useActionHref(action);
319
432
  const glyph = glyphOf(action.icon);
320
- const tone = action.tone === "danger" ? "danger" : undefined;
433
+ const iconTone = action.tone === "default" ? undefined : action.tone;
434
+ // A text button has Button's two tones; `info` and `warning` are a glyph's colours.
435
+ const textTone = action.tone === "danger" || action.tone === "muted" ? action.tone : undefined;
436
+ const marks = { "data-tour": action.dataTour, ...toneAttr(action.tone) };
321
437
  if (glyph) {
322
438
  if (href !== undefined) {
323
439
  return (
324
440
  <IconButton
441
+ {...marks}
325
442
  href={href}
326
443
  external={action.external}
327
444
  size={size}
445
+ glyphSize={glyphSize}
328
446
  label={action.label}
329
- tone={tone}
447
+ tooltipSide={tooltipSide}
448
+ tone={iconTone}
330
449
  disabled={action.disabled}
331
450
  stopPropagation
332
451
  onClick={() => action.onSelect?.()}
@@ -337,9 +456,14 @@ function InlineAction({ action, size }: { action: RowAction; size: RowActionsSiz
337
456
  }
338
457
  return (
339
458
  <IconButton
459
+ {...marks}
460
+ {...stateAttrs(action)}
340
461
  size={size}
462
+ glyphSize={glyphSize}
341
463
  label={action.label}
342
- tone={tone}
464
+ tooltipSide={tooltipSide}
465
+ tone={iconTone}
466
+ pressed={action.pressed}
343
467
  disabledReason={action.disabledReason}
344
468
  commit={action.commit}
345
469
  pending={action.pending}
@@ -363,17 +487,29 @@ function InlineAction({ action, size }: { action: RowAction; size: RowActionsSiz
363
487
  };
364
488
  if (href !== undefined) {
365
489
  return (
366
- <Button href={href} external={action.external} variant="ghost" size="sm" tone={tone} disabled={action.disabled} {...stop}>
490
+ <Button
491
+ {...marks}
492
+ href={href}
493
+ external={action.external}
494
+ variant="ghost"
495
+ size="sm"
496
+ tone={textTone}
497
+ disabled={action.disabled}
498
+ {...stop}
499
+ >
367
500
  {action.label}
368
501
  </Button>
369
502
  );
370
503
  }
371
504
  return (
372
505
  <Button
506
+ {...marks}
507
+ {...stateAttrs(action)}
373
508
  type="button"
374
509
  variant="ghost"
375
510
  size="sm"
376
- tone={tone}
511
+ tone={textTone}
512
+ pressed={action.pressed}
377
513
  disabledReason={action.disabledReason}
378
514
  commit={action.commit}
379
515
  pending={action.pending}
@@ -402,6 +538,10 @@ export interface RowActionsColumnOptions<T> {
402
538
  collapse?: RowActionsCollapse;
403
539
  /** Default `xs`. */
404
540
  size?: RowActionsSize;
541
+ /** See {@link RowActionsProps.glyphSize}. */
542
+ glyphSize?: IconButtonGlyphSize;
543
+ /** See {@link RowActionsProps.tooltipSide} — `"start"` in a table's last column. */
544
+ tooltipSide?: TooltipSide;
405
545
  /**
406
546
  * The column's name as plain text — the column-settings checklist and the phone
407
547
  * card's label read it (`DataTableColumn.headerText`). Pass the app's own word; left
@@ -432,6 +572,8 @@ export function rowActionsColumn<T>({
432
572
  name,
433
573
  collapse,
434
574
  size,
575
+ glyphSize,
576
+ tooltipSide,
435
577
  headerText,
436
578
  labels,
437
579
  column,
@@ -441,7 +583,15 @@ export function rowActionsColumn<T>({
441
583
  header: <RowActionsHeader text={headerText} labels={labels} />,
442
584
  headerText,
443
585
  cell: (row) => (
444
- <RowActions actions={actions(row)} name={name?.(row)} collapse={collapse} size={size} labels={labels} />
586
+ <RowActions
587
+ actions={actions(row)}
588
+ name={name?.(row)}
589
+ collapse={collapse}
590
+ size={size}
591
+ glyphSize={glyphSize}
592
+ tooltipSide={tooltipSide}
593
+ labels={labels}
594
+ />
445
595
  ),
446
596
  className: "w-px whitespace-nowrap text-end",
447
597
  headClassName: "w-px",
@@ -18,7 +18,8 @@ import { Caption, SectionLabel } from "./text";
18
18
  import { ToggleGroup } from "./toggle-group";
19
19
  import { Tooltip } from "./tooltip";
20
20
  import { Button, Card, IconButton, Spinner } from "./ui";
21
- import { useWriteLock } from "./write-lock";
21
+ import { COMMIT_EXCEPT_BILLING, useWriteLock } from "./write-lock";
22
+ import type { WriteLock } from "./write-lock";
22
23
  import type { CardProps } from "./ui";
23
24
  import type { DisabledReasonDisplay } from "./field-parts";
24
25
 
@@ -174,8 +175,11 @@ type MaybePromise = void | Promise<unknown>;
174
175
 
175
176
  export interface SharePanelProps {
176
177
  grantees: ShareGrantee[];
177
- /** The role vocabulary, in display order. One role: no choice is offered and each
178
- * row shows its chip. */
178
+ /** The role vocabulary, in display order, NARROWEST FIRST — viewer, then editor, as
179
+ * Kurvenschmiede lists them. One role: no choice is offered and each row shows its
180
+ * chip. The order is what tells a lowering from a raise under a write lock: a lapsed
181
+ * plan lets a grantee's role be lowered, which removes access, and never raised
182
+ * (docs/billing-harmonization.md decision 26 as amended, §12.36). */
179
183
  roles: ShareRole[];
180
184
  pending?: SharePendingGrant[];
181
185
  /** Completions for the field. Left out: the field is a plain address field. */
@@ -244,6 +248,40 @@ function LockedRoleChoice({ reason, children }: { reason: ReactNode; children: R
244
248
  return reason === undefined ? children : <Tooltip label={reason}>{children}</Tooltip>;
245
249
  }
246
250
 
251
+ /** What a grantee's role choice may do under the write lock: `group` locks the whole
252
+ * choice with that reason; `raise` leaves it live for a lowering and refuses a raise
253
+ * with that reason. Neither: the choice is free. */
254
+ interface RoleChoiceLock {
255
+ group?: ReactNode;
256
+ raise?: ReactNode;
257
+ }
258
+
259
+ /**
260
+ * A grantee's role choice under the write lock (decision 26 as amended,
261
+ * docs/billing-harmonization.md §12.36). `roles` is read narrowest first, so an option
262
+ * before the grantee's role LOWERS it: that removes access, which a lapsed plan allows
263
+ * (§12.13), so it sees the lock as `COMMIT_EXCEPT_BILLING` does (`removal`). An option
264
+ * after it RAISES it — a role grant, which every lock refuses (`every`), billing's with
265
+ * its own reason. Kurvenschmiede's server refuses exactly that: a widening while lapsed,
266
+ * not a narrowing (`shares_router.py:229-234`).
267
+ *
268
+ * A segmented choice is locked as a whole, so per grantee: under a demo's lock nothing
269
+ * moves; under billing's, a grantee at the narrowest role has only raises (locked), one
270
+ * at the widest only lowerings (live), and one in between keeps its lowerings while its
271
+ * raises are dimmed and refused. A role the vocabulary does not list cannot be told
272
+ * either way and stays locked.
273
+ */
274
+ function roleChoiceLock(index: number, count: number, every: WriteLock, removal: WriteLock): RoleChoiceLock {
275
+ if (removal.locked) return { group: removal.reason };
276
+ if (!every.locked) return {};
277
+ if (index <= 0) return { group: every.reason };
278
+ return index < count - 1 ? { raise: every.reason } : {};
279
+ }
280
+
281
+ /** The look of a raise refused while the lowerings beside it stay live — the dimmed
282
+ * segment of a locked group (ToggleGroup's `dimmed`), on that segment alone. */
283
+ const RAISE_REFUSED = "cursor-not-allowed opacity-50 hover:bg-transparent dark:hover:bg-transparent";
284
+
247
285
  /**
248
286
  * The body both forms share: the add form, then the list. Exported for an app that
249
287
  * frames it itself (a settings section, a sheet).
@@ -272,8 +310,12 @@ export function SharePanel({
272
310
  const labels = useKitLabels("shareCard", DEFAULT_SHARE_CARD_LABELS, labelsProp);
273
311
  const confirm = useConfirm();
274
312
  // Every commit below opts into the surrounding WriteLockProvider with `commit`; the
275
- // role choices save on change, cannot take it, and read the lock themselves.
313
+ // role choices save on change, cannot take it, and read the lock themselves. Adding
314
+ // and raising a role see every lock (`lock`); removing access, withdrawing an
315
+ // invitation and lowering a role see it as a lapsed plan's lock allows them
316
+ // (`removal`, COMMIT_EXCEPT_BILLING; docs/billing-harmonization.md §12.13, §12.36).
276
317
  const lock = useWriteLock();
318
+ const removal = useWriteLock(COMMIT_EXCEPT_BILLING);
277
319
  const [email, setEmail] = useState("");
278
320
  // A candidate taken from the completion. Typing again lets it go: the field then
279
321
  // names an address again, and a chosen team beside free text would be two answers
@@ -454,6 +496,8 @@ export function SharePanel({
454
496
  const editable = !readOnly && !g.locked;
455
497
  const changeable = editable && onRoleChange !== undefined && choosable;
456
498
  const key = `grantee:${g.id}`;
499
+ const rank = roles.findIndex((r) => r.key === g.role);
500
+ const roleLock = roleChoiceLock(rank, roles.length, lock, removal);
457
501
  return (
458
502
  <li key={key} className="space-y-1.5 py-2 first:pt-0 last:pb-0">
459
503
  {/* Wraps rather than squeezes: on a phone the controls drop under
@@ -462,13 +506,13 @@ export function SharePanel({
462
506
  <div className="min-w-0 flex-1 basis-40">
463
507
  <div className="flex min-w-0 items-center gap-1.5 text-sm font-medium text-[var(--text-primary)]">
464
508
  {g.kind === "team" && <UsersRound className="size-4 shrink-0" aria-hidden />}
465
- <span data-private className="truncate">
509
+ <span data-private className="truncate-until-large">
466
510
  {g.name}
467
511
  </span>
468
512
  {!changeable && <ShareRoleChip role={roleOf(g.role)} />}
469
513
  </div>
470
514
  {(g.kind === "team" || g.email) && (
471
- <div data-private className="truncate text-xs text-[var(--text-muted)]">
515
+ <div data-private className="truncate-until-large text-xs text-[var(--text-muted)]">
472
516
  {g.kind === "team" ? labels.team : g.email}
473
517
  </div>
474
518
  )}
@@ -476,15 +520,23 @@ export function SharePanel({
476
520
  {(changeable || (editable && onRemove)) && (
477
521
  <div className={ROW_CONTROLS}>
478
522
  {changeable && (
479
- <LockedRoleChoice reason={lock.locked ? lock.reason : undefined}>
523
+ <LockedRoleChoice reason={roleLock.group ?? roleLock.raise}>
480
524
  <ToggleGroup<string>
481
525
  size="sm"
482
526
  aria-label={labels.roleOf(g.name)}
483
- options={roles.map((r) => ({ value: r.key, label: r.label }))}
527
+ options={roles.map((r, i) => ({
528
+ value: r.key,
529
+ label: r.label,
530
+ className: roleLock.raise !== undefined && i > rank ? RAISE_REFUSED : undefined,
531
+ }))}
484
532
  value={g.role}
485
- disabled={busy !== null || lock.locked}
533
+ disabled={busy !== null || roleLock.group !== undefined}
486
534
  onChange={(next) => {
487
- if (next !== g.role) void run(`role:${g.id}`, () => onRoleChange(g, next));
535
+ if (next === g.role) return;
536
+ // A raise under a lock that allows only lowerings: refused,
537
+ // and the tooltip round the choice says why.
538
+ if (roleLock.raise !== undefined && roles.findIndex((r) => r.key === next) > rank) return;
539
+ void run(`role:${g.id}`, () => onRoleChange(g, next));
488
540
  }}
489
541
  />
490
542
  </LockedRoleChoice>
@@ -493,7 +545,8 @@ export function SharePanel({
493
545
  <IconButton
494
546
  tone="danger"
495
547
  size="xs"
496
- commit
548
+ // Removing access: a lapsed plan allows it (§12.13).
549
+ commit={COMMIT_EXCEPT_BILLING}
497
550
  label={labels.remove}
498
551
  // The icon alone at every text size (§10.8): the row's one
499
552
  // control beside its role toggle, where "Remove access" as
@@ -533,7 +586,7 @@ export function SharePanel({
533
586
  <div
534
587
  data-private={p.email ? true : undefined}
535
588
  className={cn(
536
- "truncate text-sm",
589
+ "truncate-until-large text-sm",
537
590
  p.email ? "text-[var(--text-primary)]" : "text-[var(--text-muted)]",
538
591
  )}
539
592
  >
@@ -553,7 +606,8 @@ export function SharePanel({
553
606
  <IconButton
554
607
  tone="danger"
555
608
  size="xs"
556
- commit
609
+ // Withdrawing an invitation removes access too (§12.13).
610
+ commit={COMMIT_EXCEPT_BILLING}
557
611
  label={labels.revokePending}
558
612
  // As the grantee's remove above: one action beside the
559
613
  // copy icon (which is an icon at every size), and the
@@ -75,13 +75,13 @@ const SIZE: Record<StatusDotSize, string> = { sm: "size-2", md: "size-2.5", lg:
75
75
  * (`1lh`, the label's own line height, which the dot inherits) leaves around it.
76
76
  * `large:` classes, so Normal keeps its one centred line.
77
77
  *
78
- * `break-words`, not `[overflow-wrap:anywhere]`: a legend often sits in a table, and
79
- * `anywhere` lowers the label's min-content width to one letter, so an auto-sized column
80
- * squeezed it to a letter per line (the showcase's tone table at 360 px, Extra large).
81
- * `break-word` keeps each word whole wherever the width is the content's to choose, and
82
- * still breaks one that cannot fit its line at all.
78
+ * 0.33 (§10.17): the kit's one `truncate-until-large` (tokens.css) in place of this
79
+ * file's own `truncate` + `large:whitespace-normal large:break-words`. The utility breaks
80
+ * with `overflow-wrap: anywhere`, which lowers the label's min-content width: a legend in
81
+ * an auto-sized table column (the showcase's tone table at 360 px, Extra large) wants the
82
+ * column given a width, or it is squeezed to a letter per line.
83
83
  */
84
- const LABEL_WRAP_AT_LARGE = "large:whitespace-normal large:break-words";
84
+ const LABEL_TRUNCATE = "truncate-until-large";
85
85
  const DOT_ON_FIRST_LINE: Record<StatusDotSize, string> = {
86
86
  sm: "large:mt-[calc((1lh_-_0.5rem)/2)]",
87
87
  md: "large:mt-[calc((1lh_-_0.625rem)/2)]",
@@ -155,7 +155,7 @@ export function StatusDot({
155
155
  // span is one ARIA forbids and screen readers ignore.
156
156
  <span {...rest} className={cn("inline-flex min-w-0 items-center gap-1.5 text-sm large:items-start", className)}>
157
157
  {dot}
158
- <span className={cn("min-w-0 truncate", LABEL_WRAP_AT_LARGE)}>{label}</span>
158
+ <span className={cn("min-w-0", LABEL_TRUNCATE)}>{label}</span>
159
159
  </span>
160
160
  );
161
161
  }
@@ -5,7 +5,7 @@ import { useKitLabels } from "../i18n/kit-labels";
5
5
  import { TILE_SIZE, TileRadioGroup } from "./tile-radio";
6
6
  import type { TileItem, TileSize } from "./tile-radio";
7
7
  import { Tooltip } from "./tooltip";
8
- import { useCommitReason } from "./write-lock";
8
+ import { useCommitReason, type CommitScope } from "./write-lock";
9
9
  import { hasContent, LabelStrip } from "./field-parts";
10
10
 
11
11
  export interface SwatchPickerLabels {
@@ -115,7 +115,7 @@ export interface SwatchPickerProps<T extends string>
115
115
  /** This picker COMMITS — choosing a colour saves it. Under a locked
116
116
  * {@link WriteLockProvider} it takes the `disabledReason` path with the lock's
117
117
  * reason, as {@link Button}'s `commit` does. */
118
- commit?: boolean;
118
+ commit?: CommitScope;
119
119
  }
120
120
 
121
121
  /**