@eifi1/ui-kit 0.32.1 → 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 (373) 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/page-header.js +6 -1
  147. package/dist/components/page-header.js.map +1 -1
  148. package/dist/components/phone-input.d.ts +10 -10
  149. package/dist/components/popover.d.ts +1 -1
  150. package/dist/components/popover.js +2 -0
  151. package/dist/components/popover.js.map +1 -1
  152. package/dist/components/row-actions.d.ts +5 -5
  153. package/dist/components/row-actions.js +130 -38
  154. package/dist/components/row-actions.js.map +1 -1
  155. package/dist/components/series-chart.d.ts +10 -10
  156. package/dist/components/settings-fields.d.ts +10 -10
  157. package/dist/components/share-card.d.ts +7 -7
  158. package/dist/components/share-card.js +26 -10
  159. package/dist/components/share-card.js.map +1 -1
  160. package/dist/components/status-dot.d.ts +1 -0
  161. package/dist/components/status-dot.js +2 -2
  162. package/dist/components/status-dot.js.map +1 -1
  163. package/dist/components/swatch-picker.d.ts +2 -1
  164. package/dist/components/swatch-picker.js.map +1 -1
  165. package/dist/components/swipeable-row.d.ts +88 -17
  166. package/dist/components/swipeable-row.js +46 -9
  167. package/dist/components/swipeable-row.js.map +1 -1
  168. package/dist/components/switch.d.ts +2 -1
  169. package/dist/components/switch.js.map +1 -1
  170. package/dist/components/table.d.ts +2 -1
  171. package/dist/components/table.js +6 -0
  172. package/dist/components/table.js.map +1 -1
  173. package/dist/components/text-link.d.ts +9 -9
  174. package/dist/components/time-input.d.ts +10 -10
  175. package/dist/components/toggle-group.d.ts +2 -1
  176. package/dist/components/toggle-group.js +1 -1
  177. package/dist/components/toggle-group.js.map +1 -1
  178. package/dist/components/translation-review-editor.d.ts +4 -0
  179. package/dist/components/translation-review-editor.js +4 -3
  180. package/dist/components/translation-review-editor.js.map +1 -1
  181. package/dist/components/translation-review-parts.d.ts +1 -0
  182. package/dist/components/translation-review.d.ts +1 -0
  183. package/dist/components/translation-review.js +15 -18
  184. package/dist/components/translation-review.js.map +1 -1
  185. package/dist/components/ui.d.ts +9 -9
  186. package/dist/components/ui.js +48 -28
  187. package/dist/components/ui.js.map +1 -1
  188. package/dist/components/use-table-state.d.ts +12 -1
  189. package/dist/components/user-avatar.d.ts +1 -0
  190. package/dist/components/write-lock.d.ts +101 -13
  191. package/dist/components/write-lock.js +37 -13
  192. package/dist/components/write-lock.js.map +1 -1
  193. package/dist/{data-table-labels-kpAAVi6g.d.ts → data-table-labels-BJ5JE_X2.d.ts} +17 -0
  194. package/dist/data-table.d.ts +12 -1
  195. package/dist/{delete-account-setting-FLyJf-kZ.d.ts → delete-account-setting-DiPX4Vue.d.ts} +365 -47
  196. package/dist/feedback/feedback-attachment.d.ts +10 -10
  197. package/dist/feedback/feedback-attachment.js +7 -7
  198. package/dist/feedback/feedback-attachment.js.map +1 -1
  199. package/dist/feedback/feedback-context.d.ts +10 -10
  200. package/dist/feedback/feedback-crash.d.ts +10 -10
  201. package/dist/feedback/feedback-dialog.d.ts +10 -10
  202. package/dist/feedback/feedback-inbox.d.ts +9 -9
  203. package/dist/feedback/feedback-inbox.js +7 -3
  204. package/dist/feedback/feedback-inbox.js.map +1 -1
  205. package/dist/feedback/feedback-labels.d.ts +10 -10
  206. package/dist/feedback/feedback-menu.d.ts +3 -3
  207. package/dist/feedback/feedback-record.d.ts +10 -10
  208. package/dist/feedback/feedback-row-detail.d.ts +10 -10
  209. package/dist/feedback/feedback-row-detail.js.map +1 -1
  210. package/dist/feedback/feedback-status-undo.d.ts +10 -10
  211. package/dist/feedback/feedback-submit.d.ts +10 -10
  212. package/dist/feedback/feedback-swipe.d.ts +10 -10
  213. package/dist/feedback/feedback-swipe.js +3 -6
  214. package/dist/feedback/feedback-swipe.js.map +1 -1
  215. package/dist/feedback/feedback-table.d.ts +10 -10
  216. package/dist/feedback/feedback-table.js.map +1 -1
  217. package/dist/feedback/feedback-thread.d.ts +10 -10
  218. package/dist/feedback.d.ts +10 -10
  219. package/dist/hooks/use-file-drop.d.ts +10 -10
  220. package/dist/i18n/defaults.d.ts +10 -10
  221. package/dist/i18n/defaults.js +2 -0
  222. package/dist/i18n/defaults.js.map +1 -1
  223. package/dist/i18n/german.d.ts +10 -10
  224. package/dist/i18n/german.js +34 -1
  225. package/dist/i18n/german.js.map +1 -1
  226. package/dist/i18n/kit-labels.d.ts +4 -4
  227. package/dist/i18n/kit-labels.js.map +1 -1
  228. package/dist/i18n/languages.d.ts +10 -10
  229. package/dist/i18n/locales/de-CH.d.ts +10 -10
  230. package/dist/i18n/locales/en.d.ts +10 -10
  231. package/dist/i18n/locales/es.d.ts +10 -10
  232. package/dist/i18n/locales/es.js +34 -1
  233. package/dist/i18n/locales/es.js.map +1 -1
  234. package/dist/i18n/locales/fr.d.ts +10 -10
  235. package/dist/i18n/locales/fr.js +35 -1
  236. package/dist/i18n/locales/fr.js.map +1 -1
  237. package/dist/i18n/locales/hu.d.ts +10 -10
  238. package/dist/i18n/locales/hu.js +36 -1
  239. package/dist/i18n/locales/hu.js.map +1 -1
  240. package/dist/i18n/locales/it.d.ts +10 -10
  241. package/dist/i18n/locales/it.js +34 -1
  242. package/dist/i18n/locales/it.js.map +1 -1
  243. package/dist/i18n/locales/zh.d.ts +10 -10
  244. package/dist/i18n/locales/zh.js +34 -1
  245. package/dist/i18n/locales/zh.js.map +1 -1
  246. package/dist/i18n/review.d.ts +10 -10
  247. package/dist/i18n/review.js +8 -1
  248. package/dist/i18n/review.js.map +1 -1
  249. package/dist/index.d.ts +10 -5
  250. package/dist/index.js +40 -2
  251. package/dist/index.js.map +1 -1
  252. package/dist/landing/landing-actions.d.ts +10 -10
  253. package/dist/landing/landing-sections.d.ts +10 -10
  254. package/dist/landing/public-header.d.ts +10 -10
  255. package/dist/landing/routing.d.ts +10 -10
  256. package/dist/pay/.well-known/apple-developer-merchantid-domain-association +1 -0
  257. package/dist/pay/index.html +30 -0
  258. package/dist/pay/pay.css +126 -0
  259. package/dist/pay/pay.js +198 -0
  260. package/dist/rhf/fields.d.ts +10 -10
  261. package/dist/rhf/form.d.ts +10 -10
  262. package/dist/rhf.d.ts +10 -10
  263. package/dist/settings/settings-section.d.ts +10 -10
  264. package/dist/shell/app-shell.d.ts +9 -9
  265. package/dist/shell/app-shell.js +2 -1
  266. package/dist/shell/app-shell.js.map +1 -1
  267. package/dist/shell/top-bar-brand.d.ts +10 -10
  268. package/dist/shell/topbar-action-menu.d.ts +1 -0
  269. package/dist/shell.d.ts +10 -10
  270. package/dist/testing/css-variables.d.ts +74 -0
  271. package/dist/testing/css-variables.generated.d.ts +7 -0
  272. package/dist/testing/css-variables.generated.js +8 -0
  273. package/dist/testing/css-variables.generated.js.map +1 -0
  274. package/dist/testing/css-variables.js +93 -0
  275. package/dist/testing/css-variables.js.map +1 -0
  276. package/dist/testing/index.d.ts +1 -0
  277. package/dist/testing/index.js +12 -0
  278. package/dist/testing/index.js.map +1 -0
  279. package/dist/theme/contrast-tokens.d.ts +44 -1
  280. package/dist/theme/contrast-tokens.js +21 -0
  281. package/dist/theme/contrast-tokens.js.map +1 -1
  282. package/dist/theme/contrast.d.ts +2 -2
  283. package/dist/theme/palette-derive.js +60 -11
  284. package/dist/theme/palette-derive.js.map +1 -1
  285. package/dist/theme/palette-presets.js +45 -43
  286. package/dist/theme/palette-presets.js.map +1 -1
  287. package/dist/wizard/stepper-nav.d.ts +11 -11
  288. package/dist/wizard/stepper-nav.js.map +1 -1
  289. package/dist/wizard.d.ts +10 -10
  290. package/package.json +10 -2
  291. package/src/account/delete-account-setting.tsx +6 -1
  292. package/src/account/email-change-setting.tsx +9 -3
  293. package/src/account/sessions-setting.tsx +7 -2
  294. package/src/admin/admin-action-confirm.tsx +2 -1
  295. package/src/admin/invitations-panel.tsx +9 -3
  296. package/src/admin/plan-change.tsx +459 -0
  297. package/src/admin/reviewer-scope-editor.tsx +2 -1
  298. package/src/admin/role-select.tsx +2 -1
  299. package/src/admin/user-roster.tsx +5 -3
  300. package/src/auth/auth-errors.ts +25 -2
  301. package/src/auth/complete-name-dialog.tsx +6 -2
  302. package/src/billing/billing-banner.tsx +3 -1
  303. package/src/billing/billing-labels.ts +45 -0
  304. package/src/billing/billing-lock.ts +36 -3
  305. package/src/billing/billing-overview.ts +58 -0
  306. package/src/billing/checkout-processing.ts +391 -0
  307. package/src/billing/checkout-return.ts +90 -0
  308. package/src/billing/pay-page.ts +242 -0
  309. package/src/billing/plan-limit-toast.tsx +91 -0
  310. package/src/billing/subscription-actions.tsx +41 -8
  311. package/src/components/account-settings.tsx +6 -2
  312. package/src/components/autocomplete.tsx +5 -2
  313. package/src/components/button-group.tsx +4 -1
  314. package/src/components/calculator.tsx +10 -6
  315. package/src/components/checkbox-group.tsx +2 -2
  316. package/src/components/checkbox.tsx +2 -1
  317. package/src/components/chip.tsx +37 -32
  318. package/src/components/choice-card.tsx +4 -2
  319. package/src/components/combobox.tsx +4 -4
  320. package/src/components/confirm-dialog.tsx +13 -6
  321. package/src/components/country-select.tsx +2 -2
  322. package/src/components/danger-confirm.tsx +2 -2
  323. package/src/components/data-table.tsx +20 -9
  324. package/src/components/date-picker.tsx +10 -7
  325. package/src/components/entity-combobox.tsx +3 -3
  326. package/src/components/field-parts.tsx +61 -14
  327. package/src/components/file-button.tsx +5 -4
  328. package/src/components/file-dropzone.tsx +2 -2
  329. package/src/components/floating-panel.tsx +7 -5
  330. package/src/components/form-actions.tsx +12 -5
  331. package/src/components/inline-edit-field.tsx +13 -13
  332. package/src/components/list.tsx +10 -9
  333. package/src/components/month-picker.tsx +8 -2
  334. package/src/components/multi-entity-combobox.tsx +3 -3
  335. package/src/components/numpad-sheet.tsx +8 -4
  336. package/src/components/page-header.tsx +15 -1
  337. package/src/components/popover.tsx +4 -0
  338. package/src/components/row-actions.tsx +201 -50
  339. package/src/components/share-card.tsx +67 -13
  340. package/src/components/status-dot.tsx +7 -7
  341. package/src/components/swatch-picker.tsx +2 -2
  342. package/src/components/swipeable-row.tsx +138 -27
  343. package/src/components/switch.tsx +2 -1
  344. package/src/components/table.tsx +8 -1
  345. package/src/components/toggle-group.tsx +5 -3
  346. package/src/components/translation-review-editor.tsx +8 -3
  347. package/src/components/translation-review.tsx +18 -22
  348. package/src/components/ui.tsx +98 -44
  349. package/src/components/write-lock.tsx +150 -24
  350. package/src/feedback/feedback-attachment.tsx +13 -9
  351. package/src/feedback/feedback-inbox.tsx +10 -6
  352. package/src/feedback/feedback-row-detail.tsx +3 -2
  353. package/src/feedback/feedback-swipe.tsx +6 -11
  354. package/src/feedback/feedback-table.tsx +2 -1
  355. package/src/i18n/defaults.ts +2 -0
  356. package/src/i18n/german.ts +38 -0
  357. package/src/i18n/kit-labels.tsx +4 -0
  358. package/src/i18n/locales/es.ts +34 -0
  359. package/src/i18n/locales/fr.ts +39 -0
  360. package/src/i18n/locales/hu.ts +37 -0
  361. package/src/i18n/locales/it.ts +35 -0
  362. package/src/i18n/locales/zh.ts +33 -0
  363. package/src/i18n/review.ts +7 -0
  364. package/src/index.ts +48 -4
  365. package/src/shell/app-shell.tsx +11 -6
  366. package/src/testing/css-variables.generated.ts +112 -0
  367. package/src/testing/css-variables.ts +191 -0
  368. package/src/testing/index.ts +13 -0
  369. package/src/theme/contrast-tokens.ts +53 -2
  370. package/src/theme/palette-derive.ts +87 -10
  371. package/src/theme/palette-presets.ts +45 -43
  372. package/src/wizard/stepper-nav.tsx +2 -1
  373. 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,37 +273,43 @@ 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
- >
218
- {(close) => (
219
- // React carries an event out of a portal along the COMPONENT tree, so a click on
220
- // an entry (or between two) reached a clickable row or a DataTable `onRowClick`
221
- // the menu was opened from, though the panel is nowhere near it in the page —
222
- // only the "⋯" trigger stopped its own (0.32.1, keksdose's 0.32 report). Stopped
223
- // here for the whole panel: clicks, and the Enter / Space a row also listens for.
224
- // Escape goes on, to the popover that closes on it.
225
- <div role="presentation" onClick={stopClick} onKeyDown={stopActivationKeys}>
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) => (
226
313
  <ul data-slot="row-actions-menu" className="space-y-0.5">
227
314
  {entries.map((action) => (
228
315
  <li key={action.key ?? action.label}>
@@ -230,9 +317,18 @@ export function RowActions({
230
317
  </li>
231
318
  ))}
232
319
  </ul>
233
- </div>
234
- )}
235
- </Popover>
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>
236
332
  );
237
333
  }
238
334
 
@@ -260,10 +356,15 @@ function useActionHref(action: RowAction): string | undefined {
260
356
  function MenuAction({ action, close }: { action: RowAction; close: () => void }) {
261
357
  const href = useActionHref(action);
262
358
  const glyph = glyphOf(action.icon);
359
+ const glyphTone = action.tone === undefined ? undefined : MENU_GLYPH_TONE[action.tone];
263
360
  const words = (
264
361
  <>
265
362
  {glyph && (
266
- <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
+ >
267
368
  {glyph}
268
369
  </span>
269
370
  )}
@@ -294,11 +395,13 @@ function MenuAction({ action, close }: { action: RowAction; close: () => void })
294
395
  }
295
396
  return (
296
397
  <Button
398
+ {...stateAttrs(action)}
297
399
  type="button"
298
400
  variant="ghost"
299
401
  size="sm"
300
402
  stretch
301
403
  tone={tone}
404
+ pressed={action.pressed}
302
405
  disabledReason={action.disabledReason}
303
406
  commit={action.commit}
304
407
  pending={action.pending}
@@ -312,20 +415,37 @@ function MenuAction({ action, close }: { action: RowAction; close: () => void })
312
415
  }
313
416
 
314
417
  /** One inline action: an IconButton with its icon, else a small text button — each a
315
- * link for an `href` (see {@link useActionHref}). */
316
- 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
+ }) {
317
431
  const href = useActionHref(action);
318
432
  const glyph = glyphOf(action.icon);
319
- 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) };
320
437
  if (glyph) {
321
438
  if (href !== undefined) {
322
439
  return (
323
440
  <IconButton
441
+ {...marks}
324
442
  href={href}
325
443
  external={action.external}
326
444
  size={size}
445
+ glyphSize={glyphSize}
327
446
  label={action.label}
328
- tone={tone}
447
+ tooltipSide={tooltipSide}
448
+ tone={iconTone}
329
449
  disabled={action.disabled}
330
450
  stopPropagation
331
451
  onClick={() => action.onSelect?.()}
@@ -336,9 +456,14 @@ function InlineAction({ action, size }: { action: RowAction; size: RowActionsSiz
336
456
  }
337
457
  return (
338
458
  <IconButton
459
+ {...marks}
460
+ {...stateAttrs(action)}
339
461
  size={size}
462
+ glyphSize={glyphSize}
340
463
  label={action.label}
341
- tone={tone}
464
+ tooltipSide={tooltipSide}
465
+ tone={iconTone}
466
+ pressed={action.pressed}
342
467
  disabledReason={action.disabledReason}
343
468
  commit={action.commit}
344
469
  pending={action.pending}
@@ -362,17 +487,29 @@ function InlineAction({ action, size }: { action: RowAction; size: RowActionsSiz
362
487
  };
363
488
  if (href !== undefined) {
364
489
  return (
365
- <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
+ >
366
500
  {action.label}
367
501
  </Button>
368
502
  );
369
503
  }
370
504
  return (
371
505
  <Button
506
+ {...marks}
507
+ {...stateAttrs(action)}
372
508
  type="button"
373
509
  variant="ghost"
374
510
  size="sm"
375
- tone={tone}
511
+ tone={textTone}
512
+ pressed={action.pressed}
376
513
  disabledReason={action.disabledReason}
377
514
  commit={action.commit}
378
515
  pending={action.pending}
@@ -401,6 +538,10 @@ export interface RowActionsColumnOptions<T> {
401
538
  collapse?: RowActionsCollapse;
402
539
  /** Default `xs`. */
403
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;
404
545
  /**
405
546
  * The column's name as plain text — the column-settings checklist and the phone
406
547
  * card's label read it (`DataTableColumn.headerText`). Pass the app's own word; left
@@ -431,6 +572,8 @@ export function rowActionsColumn<T>({
431
572
  name,
432
573
  collapse,
433
574
  size,
575
+ glyphSize,
576
+ tooltipSide,
434
577
  headerText,
435
578
  labels,
436
579
  column,
@@ -440,7 +583,15 @@ export function rowActionsColumn<T>({
440
583
  header: <RowActionsHeader text={headerText} labels={labels} />,
441
584
  headerText,
442
585
  cell: (row) => (
443
- <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
+ />
444
595
  ),
445
596
  className: "w-px whitespace-nowrap text-end",
446
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
  /**