@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
@@ -0,0 +1,112 @@
1
+ // GENERATED by scripts/gen-kit-css-variables.mjs — do not edit by hand. Re-run it after
2
+ // changing tokens.css or upgrading Tailwind; src/__tests__/token-vars-declared.test.ts
3
+ // fails while this is stale.
4
+
5
+ /** Every custom property tokens.css declares, `@theme inline` names and `--app-nav-h`
6
+ * left out (100 names). */
7
+ export const KIT_NAMES =
8
+ "--bg-active --bg-hover --bg-inverse --bg-page --bg-surface --bg-surface-2 --border " +
9
+ "--border-strong --brand --brand-bg --brand-bg-hover --brand-contrast --brand-hover --brand-muted " +
10
+ "--breakpoint-3xl --chart-1 --chart-2 --chart-3 --chart-4 --chart-5 --chart-6 --chart-7 --chart-8 " +
11
+ "--chart-9 --danger --danger-bg --danger-border --danger-border-strong --danger-contrast " +
12
+ "--danger-hover --flyout-from --focus-ring-width --hue-blue --hue-blue-bg --hue-blue-border " +
13
+ "--hue-blue-contrast --hue-indigo --hue-indigo-bg --hue-indigo-border --hue-indigo-contrast " +
14
+ "--hue-orange --hue-orange-bg --hue-orange-border --hue-orange-contrast --hue-purple " +
15
+ "--hue-purple-bg --hue-purple-border --hue-purple-contrast --hue-teal --hue-teal-bg " +
16
+ "--hue-teal-border --hue-teal-contrast --info --info-bg --info-border --info-contrast --media-ink " +
17
+ "--media-scrim --media-scrim-hover --money-expense --money-expense-contrast --money-income " +
18
+ "--money-income-contrast --money-net --money-net-contrast --money-neutral " +
19
+ "--money-neutral-contrast --neutral --neutral-contrast --status-edited --status-error " +
20
+ "--status-pending --status-synced --success --success-bg --success-border --success-contrast " +
21
+ "--text-caption --text-inverse --text-micro --text-muted --text-placeholder --text-primary " +
22
+ "--text-secondary --toast-action-bg --toast-action-fg --toast-danger-bg --toast-danger-border " +
23
+ "--toast-info-bg --toast-info-border --toast-success-bg --toast-success-border --toast-warning-bg " +
24
+ "--toast-warning-border --warning --warning-bg --warning-border --warning-border-strong " +
25
+ "--warning-contrast --z-toast";
26
+
27
+ /** The theme variables of Tailwind 4.3.3's `theme.css` (413 names). */
28
+ export const TAILWIND_THEME_NAMES =
29
+ "--animate-bounce --animate-ping --animate-pulse --animate-spin --aspect-video --blur-2xl " +
30
+ "--blur-3xl --blur-lg --blur-md --blur-sm --blur-xl --blur-xs --breakpoint-2xl --breakpoint-lg " +
31
+ "--breakpoint-md --breakpoint-sm --breakpoint-xl --color-amber-100 --color-amber-200 " +
32
+ "--color-amber-300 --color-amber-400 --color-amber-50 --color-amber-500 --color-amber-600 " +
33
+ "--color-amber-700 --color-amber-800 --color-amber-900 --color-amber-950 --color-black " +
34
+ "--color-blue-100 --color-blue-200 --color-blue-300 --color-blue-400 --color-blue-50 " +
35
+ "--color-blue-500 --color-blue-600 --color-blue-700 --color-blue-800 --color-blue-900 " +
36
+ "--color-blue-950 --color-cyan-100 --color-cyan-200 --color-cyan-300 --color-cyan-400 " +
37
+ "--color-cyan-50 --color-cyan-500 --color-cyan-600 --color-cyan-700 --color-cyan-800 " +
38
+ "--color-cyan-900 --color-cyan-950 --color-emerald-100 --color-emerald-200 --color-emerald-300 " +
39
+ "--color-emerald-400 --color-emerald-50 --color-emerald-500 --color-emerald-600 " +
40
+ "--color-emerald-700 --color-emerald-800 --color-emerald-900 --color-emerald-950 " +
41
+ "--color-fuchsia-100 --color-fuchsia-200 --color-fuchsia-300 --color-fuchsia-400 " +
42
+ "--color-fuchsia-50 --color-fuchsia-500 --color-fuchsia-600 --color-fuchsia-700 " +
43
+ "--color-fuchsia-800 --color-fuchsia-900 --color-fuchsia-950 --color-gray-100 --color-gray-200 " +
44
+ "--color-gray-300 --color-gray-400 --color-gray-50 --color-gray-500 --color-gray-600 " +
45
+ "--color-gray-700 --color-gray-800 --color-gray-900 --color-gray-950 --color-green-100 " +
46
+ "--color-green-200 --color-green-300 --color-green-400 --color-green-50 --color-green-500 " +
47
+ "--color-green-600 --color-green-700 --color-green-800 --color-green-900 --color-green-950 " +
48
+ "--color-indigo-100 --color-indigo-200 --color-indigo-300 --color-indigo-400 --color-indigo-50 " +
49
+ "--color-indigo-500 --color-indigo-600 --color-indigo-700 --color-indigo-800 --color-indigo-900 " +
50
+ "--color-indigo-950 --color-lime-100 --color-lime-200 --color-lime-300 --color-lime-400 " +
51
+ "--color-lime-50 --color-lime-500 --color-lime-600 --color-lime-700 --color-lime-800 " +
52
+ "--color-lime-900 --color-lime-950 --color-mauve-100 --color-mauve-200 --color-mauve-300 " +
53
+ "--color-mauve-400 --color-mauve-50 --color-mauve-500 --color-mauve-600 --color-mauve-700 " +
54
+ "--color-mauve-800 --color-mauve-900 --color-mauve-950 --color-mist-100 --color-mist-200 " +
55
+ "--color-mist-300 --color-mist-400 --color-mist-50 --color-mist-500 --color-mist-600 " +
56
+ "--color-mist-700 --color-mist-800 --color-mist-900 --color-mist-950 --color-neutral-100 " +
57
+ "--color-neutral-200 --color-neutral-300 --color-neutral-400 --color-neutral-50 " +
58
+ "--color-neutral-500 --color-neutral-600 --color-neutral-700 --color-neutral-800 " +
59
+ "--color-neutral-900 --color-neutral-950 --color-olive-100 --color-olive-200 --color-olive-300 " +
60
+ "--color-olive-400 --color-olive-50 --color-olive-500 --color-olive-600 --color-olive-700 " +
61
+ "--color-olive-800 --color-olive-900 --color-olive-950 --color-orange-100 --color-orange-200 " +
62
+ "--color-orange-300 --color-orange-400 --color-orange-50 --color-orange-500 --color-orange-600 " +
63
+ "--color-orange-700 --color-orange-800 --color-orange-900 --color-orange-950 --color-pink-100 " +
64
+ "--color-pink-200 --color-pink-300 --color-pink-400 --color-pink-50 --color-pink-500 " +
65
+ "--color-pink-600 --color-pink-700 --color-pink-800 --color-pink-900 --color-pink-950 " +
66
+ "--color-purple-100 --color-purple-200 --color-purple-300 --color-purple-400 --color-purple-50 " +
67
+ "--color-purple-500 --color-purple-600 --color-purple-700 --color-purple-800 --color-purple-900 " +
68
+ "--color-purple-950 --color-red-100 --color-red-200 --color-red-300 --color-red-400 " +
69
+ "--color-red-50 --color-red-500 --color-red-600 --color-red-700 --color-red-800 --color-red-900 " +
70
+ "--color-red-950 --color-rose-100 --color-rose-200 --color-rose-300 --color-rose-400 " +
71
+ "--color-rose-50 --color-rose-500 --color-rose-600 --color-rose-700 --color-rose-800 " +
72
+ "--color-rose-900 --color-rose-950 --color-sky-100 --color-sky-200 --color-sky-300 " +
73
+ "--color-sky-400 --color-sky-50 --color-sky-500 --color-sky-600 --color-sky-700 --color-sky-800 " +
74
+ "--color-sky-900 --color-sky-950 --color-slate-100 --color-slate-200 --color-slate-300 " +
75
+ "--color-slate-400 --color-slate-50 --color-slate-500 --color-slate-600 --color-slate-700 " +
76
+ "--color-slate-800 --color-slate-900 --color-slate-950 --color-stone-100 --color-stone-200 " +
77
+ "--color-stone-300 --color-stone-400 --color-stone-50 --color-stone-500 --color-stone-600 " +
78
+ "--color-stone-700 --color-stone-800 --color-stone-900 --color-stone-950 --color-taupe-100 " +
79
+ "--color-taupe-200 --color-taupe-300 --color-taupe-400 --color-taupe-50 --color-taupe-500 " +
80
+ "--color-taupe-600 --color-taupe-700 --color-taupe-800 --color-taupe-900 --color-taupe-950 " +
81
+ "--color-teal-100 --color-teal-200 --color-teal-300 --color-teal-400 --color-teal-50 " +
82
+ "--color-teal-500 --color-teal-600 --color-teal-700 --color-teal-800 --color-teal-900 " +
83
+ "--color-teal-950 --color-violet-100 --color-violet-200 --color-violet-300 --color-violet-400 " +
84
+ "--color-violet-50 --color-violet-500 --color-violet-600 --color-violet-700 --color-violet-800 " +
85
+ "--color-violet-900 --color-violet-950 --color-white --color-yellow-100 --color-yellow-200 " +
86
+ "--color-yellow-300 --color-yellow-400 --color-yellow-50 --color-yellow-500 --color-yellow-600 " +
87
+ "--color-yellow-700 --color-yellow-800 --color-yellow-900 --color-yellow-950 --color-zinc-100 " +
88
+ "--color-zinc-200 --color-zinc-300 --color-zinc-400 --color-zinc-50 --color-zinc-500 " +
89
+ "--color-zinc-600 --color-zinc-700 --color-zinc-800 --color-zinc-900 --color-zinc-950 " +
90
+ "--container-2xl --container-2xs --container-3xl --container-3xs --container-4xl --container-5xl " +
91
+ "--container-6xl --container-7xl --container-lg --container-md --container-sm --container-xl " +
92
+ "--container-xs --default-font-family --default-font-feature-settings " +
93
+ "--default-font-variation-settings --default-mono-font-family " +
94
+ "--default-mono-font-feature-settings --default-mono-font-variation-settings " +
95
+ "--default-transition-duration --default-transition-timing-function --drop-shadow-2xl " +
96
+ "--drop-shadow-lg --drop-shadow-md --drop-shadow-sm --drop-shadow-xl --drop-shadow-xs --ease-in " +
97
+ "--ease-in-out --ease-out --font-mono --font-sans --font-serif --font-weight-black " +
98
+ "--font-weight-bold --font-weight-extrabold --font-weight-extralight --font-weight-light " +
99
+ "--font-weight-medium --font-weight-normal --font-weight-semibold --font-weight-thin " +
100
+ "--inset-shadow-2xs --inset-shadow-sm --inset-shadow-xs --leading-loose --leading-normal " +
101
+ "--leading-relaxed --leading-snug --leading-tight --perspective-distant --perspective-dramatic " +
102
+ "--perspective-midrange --perspective-near --perspective-normal --radius-2xl --radius-3xl " +
103
+ "--radius-4xl --radius-lg --radius-md --radius-sm --radius-xl --radius-xs --shadow-2xl " +
104
+ "--shadow-2xs --shadow-lg --shadow-md --shadow-sm --shadow-xl --shadow-xs --spacing --text-2xl " +
105
+ "--text-2xl--line-height --text-3xl --text-3xl--line-height --text-4xl --text-4xl--line-height " +
106
+ "--text-5xl --text-5xl--line-height --text-6xl --text-6xl--line-height --text-7xl " +
107
+ "--text-7xl--line-height --text-8xl --text-8xl--line-height --text-9xl --text-9xl--line-height " +
108
+ "--text-base --text-base--line-height --text-lg --text-lg--line-height --text-shadow-2xs " +
109
+ "--text-shadow-lg --text-shadow-md --text-shadow-sm --text-shadow-xs --text-sm " +
110
+ "--text-sm--line-height --text-xl --text-xl--line-height --text-xs --text-xs--line-height " +
111
+ "--tracking-normal --tracking-tight --tracking-tighter --tracking-wide --tracking-wider " +
112
+ "--tracking-widest";
@@ -0,0 +1,191 @@
1
+ import { KIT_NAMES, TAILWIND_THEME_NAMES } from "./css-variables.generated";
2
+
3
+ /**
4
+ * The guard against an undeclared CSS custom property (0.33,
5
+ * docs/colour-roles-harmonization.md §8, §12.5).
6
+ *
7
+ * A `var(--name)` with no fallback, where nothing declares `--name`, makes the whole
8
+ * declaration invalid at computed-value time: `fill` inherits, `color` inherits, a
9
+ * background goes transparent. Kurvenschmiede's control diagram painted
10
+ * `fill-[var(--surface)]` — a token that never existed (the kit's is `--bg-surface`) —
11
+ * and drew black boxes on a public page. The class compiled, the build passed, the type
12
+ * checker had no opinion, and nothing in any suite noticed. This is the check that
13
+ * notices, run from an app's own test over the source texts the app collects.
14
+ *
15
+ * Browser-safe on purpose, like the rest of the kit: it takes TEXTS, not paths, so an app
16
+ * reads its files however its setup allows (`import.meta.glob` with `?raw`, or `node:fs`)
17
+ * and the kit carries no `node:fs`.
18
+ */
19
+
20
+ /** Every custom property the installed kit declares or publishes to apps.
21
+ *
22
+ * Generated from `tokens.css` when the kit is built (scripts/gen-kit-css-variables.mjs),
23
+ * so it is always the installed version's, and an app never searches `node_modules`.
24
+ * It leaves out two kinds of name on purpose:
25
+ * - a name declared only in an `@theme inline` block (`--text-color-muted`,
26
+ * `--background-color-surface`): Tailwind emits no variable for it, so
27
+ * `var(--text-color-muted)` would pass a guard that counted it and paint nothing;
28
+ * - AppShell's measured `--app-nav-h`, which exists only while an AppShell is mounted —
29
+ * on a landing, legal, sign-in or pay page it is undefined. Read it with a fallback,
30
+ * `var(--app-nav-h,0px)`, as every kit read does.
31
+ * Everything `applyTokenSet` and More contrast write on `<html>` is in, through the
32
+ * stylesheet's `:root` / `.dark` mirror. Component internals (`--slider-fill`, `--fp-*`,
33
+ * `--icon-button-tone`, `--label-strip-edge`) are not: an app that reads one depends on
34
+ * the kit's internals, and the guard says so. */
35
+ export const KIT_CSS_VARIABLES: ReadonlySet<string> = new Set(KIT_NAMES.split(" "));
36
+
37
+ /** Tailwind's own theme variables (`--color-rose-500`, `--spacing`, `--text-sm` …), from
38
+ * the `tailwindcss/theme.css` the kit builds with. Tailwind emits one when a scanned file
39
+ * names it, so a `var()` of it resolves. */
40
+ const TAILWIND_THEME: ReadonlySet<string> = new Set(TAILWIND_THEME_NAMES.split(" "));
41
+
42
+ /** A Tailwind PALETTE colour: a hue with a shade, black or white. `refusePalette` reports
43
+ * these although they resolve — an app that has no palette lint asks this test to be
44
+ * its refusal. */
45
+ const PALETTE_VARIABLE = /^--color-(?:[a-z]+-\d{2,3}|black|white)$/;
46
+
47
+ /** Read only where it is set: the guard accepts it in a fallback use, never bare. */
48
+ const APP_NAV_H = "--app-nav-h";
49
+
50
+ export interface UndeclaredCssVariablesOptions {
51
+ /** Variables a library writes at runtime, as names or patterns:
52
+ * `["--radix-collapsible-content-height"]`. An app's own writes are found in its
53
+ * source and need no listing. */
54
+ runtime?: readonly (string | RegExp)[];
55
+ /** Also check `var(--x, …)`. Off by default: a fallback says what happens when the
56
+ * name is missing. `--app-nav-h` counts as declared only here. */
57
+ checkFallbacks?: boolean;
58
+ /** Report Tailwind palette variables (`--color-red-500`, `--color-white`) although
59
+ * they resolve. Default false. */
60
+ refusePalette?: boolean;
61
+ }
62
+
63
+ /** Comments are not CSS. `/* *\/` in both; `//` in TS only (not after a `:`, so a URL in a
64
+ * string survives). Replaced by spaces of the same shape, so line numbers hold. */
65
+ function stripComments(text: string, isCss: boolean): string {
66
+ const blank = (m: string) => m.replace(/[^\n]/g, " ");
67
+ const noBlocks = text.replace(/\/\*[\s\S]*?\*\//g, blank);
68
+ return isCss ? noBlocks : noBlocks.replace(/(^|[^:])(\/\/[^\n]*)/g, (_, pre: string, c: string) => pre + blank(c));
69
+ }
70
+
71
+ /** The text with every `@theme … inline … { … }` block blanked out, braces and all. */
72
+ function withoutInlineTheme(css: string): string {
73
+ let out = css;
74
+ for (const m of css.matchAll(/@theme\b[^{;]*\binline\b[^{;]*\{/g)) {
75
+ let depth = 0;
76
+ let end = m.index + m[0].length - 1;
77
+ for (; end < css.length; end++) {
78
+ if (css[end] === "{") depth++;
79
+ else if (css[end] === "}" && --depth === 0) break;
80
+ }
81
+ out = out.slice(0, m.index) + css.slice(m.index, end + 1).replace(/[^\n]/g, " ") + out.slice(end + 1);
82
+ }
83
+ return out;
84
+ }
85
+
86
+ /**
87
+ * Every custom property a stylesheet declares (`--name:`), in any block — except a name
88
+ * declared only inside `@theme inline`, which Tailwind never emits as a variable. Comments
89
+ * are skipped. The same rule builds {@link KIT_CSS_VARIABLES} from `tokens.css`.
90
+ */
91
+ export function declaredCustomProperties(css: string): Set<string> {
92
+ const code = withoutInlineTheme(stripComments(css, true));
93
+ return new Set([...code.matchAll(/(--[a-zA-Z0-9_-]+)\s*:/g)].map((m) => m[1]));
94
+ }
95
+
96
+ /** What an app's source writes at runtime: `setProperty("--name"`, a React style key
97
+ * `"--name":`, a Tailwind arbitrary property `[--name:…]`. */
98
+ function runtimeWrites(code: string): string[] {
99
+ return [
100
+ ...code.matchAll(/setProperty\(\s*["'`](--[a-zA-Z0-9_-]+)["'`]/g),
101
+ ...code.matchAll(/["'](--[a-zA-Z0-9_-]+)["']\s*:/g),
102
+ ...code.matchAll(/\[(--[a-zA-Z0-9_-]+):/g),
103
+ ].map((m) => m[1]);
104
+ }
105
+
106
+ /** A chart series' colour (`var(--color-total)`) is declared in a file with a `total: {`
107
+ * config entry: the kit's ChartContainer writes `--color-<key>` for each key. */
108
+ function chartKeys(code: string): Set<string> {
109
+ return new Set([...code.matchAll(/(?:^|[\s{,])["']?([A-Za-z_][\w-]*)["']?\s*:\s*\{/g)].map((m) => m[1]));
110
+ }
111
+
112
+ const isCssPath = (path: string) => /\.css(?:\?|$)/.test(path);
113
+
114
+ /**
115
+ * Every `var(--name)` without a fallback that nothing declares, as `"path:line --name"`.
116
+ *
117
+ * `sources` maps a path to its text: the app's `.ts`, `.tsx` and `.css` source, tests
118
+ * left out. A name counts as declared when it is:
119
+ * 1. the kit's ({@link KIT_CSS_VARIABLES});
120
+ * 2. declared in any `.css` entry of `sources` (outside `@theme inline`);
121
+ * 3. written by the source at runtime (`setProperty`, a style key, `[--name:…]`);
122
+ * 4. one of Tailwind's theme variables — unless `refusePalette` and it is a palette colour;
123
+ * 5. a chart series' `--color-<key>`, in a file with a `<key>: {` entry;
124
+ * 6. listed in `options.runtime`.
125
+ *
126
+ * Not counted: comments; a use with a fallback, unless `checkFallbacks`; a name built at
127
+ * runtime (`` `var(--chart-${n})` `` — a name must be followed by `)` or `,`).
128
+ *
129
+ * It REFUSES A VACUOUS RUN: it throws when `sources` holds fewer than ten files, or when
130
+ * a `.css` entry is empty. Vitest stubs every stylesheet with an empty string by default,
131
+ * `?raw` included, so an app that reads its CSS that way without opting it out of the stub
132
+ * (`test.css.include`) would otherwise lose every declaration and every use in it, and
133
+ * this would pass or fail for the wrong reason.
134
+ */
135
+ export function undeclaredCssVariables(
136
+ sources: Record<string, string>,
137
+ options: UndeclaredCssVariablesOptions = {},
138
+ ): string[] {
139
+ const { runtime = [], checkFallbacks = false, refusePalette = false } = options;
140
+ const entries = Object.entries(sources);
141
+ if (entries.length < 10) {
142
+ throw new Error(
143
+ `undeclaredCssVariables: only ${entries.length} source file(s) — the glob or the file list found ` +
144
+ `(almost) nothing, and a check over nothing passes. Pass the app's .ts/.tsx/.css source.`,
145
+ );
146
+ }
147
+ const emptyCss = entries.filter(([path, text]) => isCssPath(path) && text.trim() === "").map(([path]) => path);
148
+ if (emptyCss.length) {
149
+ throw new Error(
150
+ `undeclaredCssVariables: ${emptyCss.join(", ")} read as empty. Vitest stubs stylesheets with "" ` +
151
+ `(\`?raw\` too) unless the file is listed in \`test.css.include\`.`,
152
+ );
153
+ }
154
+
155
+ const code = entries.map(([path, text]) => [path, stripComments(text, isCssPath(path))] as const);
156
+ const declared = new Set<string>();
157
+ for (const [path, text] of code) {
158
+ if (isCssPath(path)) for (const name of declaredCustomProperties(text)) declared.add(name);
159
+ for (const name of runtimeWrites(text)) declared.add(name);
160
+ }
161
+ const listed = (name: string) =>
162
+ runtime.some((r) => (typeof r === "string" ? r === name : r.test(name)));
163
+
164
+ const out: string[] = [];
165
+ for (const [path, text] of code) {
166
+ let keys: Set<string> | null = null;
167
+ for (const m of text.matchAll(/var\(\s*(--[a-zA-Z0-9_-]+)\s*([,)])/g)) {
168
+ const [, name, next] = m;
169
+ const fallback = next === ",";
170
+ if (fallback && !checkFallbacks) continue;
171
+ if (KIT_CSS_VARIABLES.has(name) || declared.has(name) || listed(name)) continue;
172
+ if (fallback && name === APP_NAV_H) continue;
173
+ if (TAILWIND_THEME.has(name)) {
174
+ if (refusePalette && PALETTE_VARIABLE.test(name)) {
175
+ out.push(`${path}:${lineOf(text, m.index)} ${name} (a Tailwind palette colour; refusePalette is on)`);
176
+ }
177
+ continue;
178
+ }
179
+ const chart = /^--color-([A-Za-z_][\w-]*)$/.exec(name);
180
+ if (chart && (keys ??= chartKeys(text)).has(chart[1])) continue;
181
+ out.push(`${path}:${lineOf(text, m.index)} ${name}`);
182
+ }
183
+ }
184
+ return out;
185
+ }
186
+
187
+ function lineOf(text: string, index: number): number {
188
+ let line = 1;
189
+ for (let i = 0; i < index; i++) if (text.charCodeAt(i) === 10) line++;
190
+ return line;
191
+ }
@@ -0,0 +1,13 @@
1
+ // `@eifi1/ui-kit/testing` — helpers an app runs from its OWN test suite (0.33).
2
+ //
3
+ // Not part of the main barrel: nothing here renders, and an app's production bundle has
4
+ // no business carrying it. Browser-safe like the rest of the kit (no `node:fs`): each
5
+ // helper takes texts, so an app collects them with `import.meta.glob` or `node:fs`, as
6
+ // its setup allows. The first content is the guard against undeclared CSS custom
7
+ // properties (docs/colour-roles-harmonization.md §8).
8
+ export {
9
+ KIT_CSS_VARIABLES,
10
+ declaredCustomProperties,
11
+ undeclaredCssVariables,
12
+ type UndeclaredCssVariablesOptions,
13
+ } from "./css-variables";
@@ -49,8 +49,9 @@ function toLab(hex: string): Lab | null {
49
49
 
50
50
  /** `color-mix(in oklab, a p, b)` as a hex — the same formula tokens.css's derived roles
51
51
  * use, so a stepped value sits where the stylesheet would have put it. A colour that is
52
- * not hex (a consumer's own token set) comes back unmixed. */
53
- function mix(a: string, b: string, p: number): string {
52
+ * not hex (a consumer's own token set) comes back unmixed. The deriver mixes with it
53
+ * too (`--bg-hover`, `--brand-muted`, `--brand-bg`, `--brand-bg-hover`). */
54
+ export function mix(a: string, b: string, p: number): string {
54
55
  const A = toLab(a);
55
56
  const B = toLab(b);
56
57
  if (!A || !B) return a;
@@ -62,6 +63,56 @@ function mix(a: string, b: string, p: number): string {
62
63
  return toHex(oklchToRgb({ l, c, h }));
63
64
  }
64
65
 
66
+ /**
67
+ * `--bg-hover` in tokens.css: this share of `--text-primary` mixed into `--bg-surface`.
68
+ *
69
+ * 5.5 %, down from 7 % in 0.33 (docs/colour-roles-harmonization.md §12.10): a hovered row
70
+ * on a card is the darkest fill (in dark, the lightest) that text sits on, and at 7 % the
71
+ * light status colours, the money colours and Ink/Moss/Plum's muted text fell to
72
+ * 4.25–4.49:1 on it. The status literals are fixed, the same in every preset, so the
73
+ * hover moved instead: text wins over the hover's step. At 5.5 % the step from the card
74
+ * is still 1.09–1.13:1, above the 1.08:1 the kit's hover rule asks (§12.2). The deriver
75
+ * solves the money colours against this fill, and tokens-css-audit.test.ts measures
76
+ * every text role on it in every shipped preset, so the two cannot part.
77
+ */
78
+ export const HOVER_INK = 0.055;
79
+
80
+ /** The card's hover fill (`--bg-hover`) for `t`, as tokens.css mixes it. */
81
+ export function hoverFill(t: Pick<TokenSet, "textPrimary" | "bgSurface">): string {
82
+ return mix(t.textPrimary, t.bgSurface, HOVER_INK);
83
+ }
84
+
85
+ /**
86
+ * `--brand-muted` in tokens.css: this share of `--brand` mixed into `--text-primary`.
87
+ *
88
+ * 62 %, down from 70 % in 0.33 (docs/colour-roles-harmonization.md §12.11): the brand as
89
+ * text sits on its soft chip HOVERED too (`--brand-bg-hover` — ButtonGroupLink's current
90
+ * page, a selected day), and at 70 % it read 4.08–4.42:1 there in Default and Moss light
91
+ * and in Ink, Moss, Plum and Contrast dark. The brand could not move instead: the dark
92
+ * brands would have flipped their white label to the dark ink. Nor could the chip's hover
93
+ * lighten: by the time the text cleared 4.5:1 the hover sat 1.00:1 from the chip it
94
+ * hovers. So the text moves toward the ink, as `--bg-hover` moved for its text.
95
+ */
96
+ export const BRAND_MUTED_SHARE = 0.62;
97
+ /** `--brand-bg` and `--brand-bg-hover` in tokens.css: these shares of `--brand` mixed
98
+ * into `--bg-surface`. */
99
+ export const BRAND_BG_SHARE = 0.14;
100
+ export const BRAND_BG_HOVER_SHARE = 0.24;
101
+
102
+ /** `--brand-muted`, `--brand-bg` and `--brand-bg-hover` for `t`, as tokens.css mixes them.
103
+ * tokens-css-audit.test.ts holds the three shares equal to the stylesheet's. */
104
+ export function brandSoft(t: Pick<TokenSet, "brand" | "textPrimary" | "bgSurface">): {
105
+ brandMuted: string;
106
+ brandBg: string;
107
+ brandBgHover: string;
108
+ } {
109
+ return {
110
+ brandMuted: mix(t.brand, t.textPrimary, BRAND_MUTED_SHARE),
111
+ brandBg: mix(t.brand, t.bgSurface, BRAND_BG_SHARE),
112
+ brandBgHover: mix(t.brand, t.bgSurface, BRAND_BG_HOVER_SHARE),
113
+ };
114
+ }
115
+
65
116
  /** WCAG 1.4.11 for a component's boundary. */
66
117
  const BORDER_TARGET = 3;
67
118
  /** `--border-strong` in tokens.css: 55 % border, 45 % text. */
@@ -13,6 +13,7 @@ import {
13
13
  solveLightness,
14
14
  } from "./color";
15
15
  import type { CvdType, Oklch } from "./color";
16
+ import { brandSoft, hoverFill } from "./contrast-tokens";
16
17
 
17
18
  /**
18
19
  * Build a whole palette from one brand colour, plus any number of colours pinned by
@@ -161,7 +162,14 @@ function placeHue(home: number, slack: number, taken: number[]): number {
161
162
  const SURFACE_L = {
162
163
  // Light: the PAGE is slightly darker than the cards on it, so a card reads as raised
163
164
  // without needing a shadow to say so. Matches the shipped presets' relationship.
164
- light: { page: 0.912, surface: 0.945, surface2: 0.929 },
165
+ //
166
+ // The light WELL is the page itself (0.33, docs/colour-roles-harmonization.md §5.3,
167
+ // k26). Between the card and the page it sat 1.02–1.05:1 from the card it is inset
168
+ // in, which is no well at all; at the page's lightness it stands out from the card
169
+ // exactly as far as the card does from the page, and nothing re-solves, because the
170
+ // page is already the surface every text role is solved against. Dark keeps its
171
+ // well a step lighter than the card (1.11–1.13:1).
172
+ light: { page: 0.912, surface: 0.945, surface2: 0.912 },
165
173
  dark: { page: 0.158, surface: 0.206, surface2: 0.249 },
166
174
  } as const;
167
175
 
@@ -272,6 +280,19 @@ export function derivePalette({
272
280
  const textSecondary = solveAgainst(brand.h, textChroma, targets.secondary, worstSurface, mode);
273
281
  const textMuted = solveAgainst(brand.h, textChroma, targets.muted, worstSurface, mode);
274
282
 
283
+ // The darkest fill text sits on (the lightest, in dark): the worst surface, or a
284
+ // hovered row on the card (`--bg-hover`), which in light is a shade darker than the
285
+ // page. The colours that are solved exactly to 4.5:1 — money, the semantic text — are
286
+ // solved against this, so they hold on a hovered row too (0.33, §12.10). The text
287
+ // roles above keep their margin (4.6:1 and up) and need no second solve.
288
+ const hover = hoverFill({ textPrimary, bgSurface: surfaces.bgSurface });
289
+ const worstText = [worstSurface, hover].reduce((a, b) =>
290
+ contrast(a, mode === "light" ? "#000000" : "#ffffff") <
291
+ contrast(b, mode === "light" ? "#000000" : "#ffffff")
292
+ ? a
293
+ : b,
294
+ );
295
+
275
296
  // ── Brand. Keep the hue the caller asked for; move only the lightness, and only as
276
297
  // far as 3:1 against the worst surface — WCAG 1.4.11, since a brand-filled control is
277
298
  // a graphical object whose boundary has to be perceivable.
@@ -280,7 +301,7 @@ export function derivePalette({
280
301
  // NORMALISED, never the caller's raw string. `parseHex` accepts "4f46e5" and "#abc",
281
302
  // and passing either through unchanged put a value in the TokenSet that is not a CSS
282
303
  // colour — it then fails silently at paint time, three layers from here.
283
- const brandHex =
304
+ let brandHex =
284
305
  brandRatio >= 3
285
306
  ? toHex(parseHex(anchors.brand)!)
286
307
  : solveAgainst(brand.h, brandChroma, 3, worstSurface, mode);
@@ -291,13 +312,39 @@ export function derivePalette({
291
312
  `${brandHex}; hue and chroma are unchanged.`,
292
313
  );
293
314
  }
315
+ // `--brand-muted` is the brand AS TEXT — a soft brand chip's label, an outline chip, a
316
+ // current link — and tokens.css derives it: 62 % brand into the ink. A brand solved
317
+ // to 3:1 left it at 4.43–4.51:1 in three dark presets (0.33, §5.5), so it gets the
318
+ // text floor here: the brand moves away from the surfaces, a step at a time, until its
319
+ // muted form clears 4.5:1 on the worst fill and on its own soft chip, at rest
320
+ // (`--brand-bg`) and hovered (`--brand-bg-hover`, the harder of the two: §12.11).
321
+ const brandMutedWorst = (hex: string) => {
322
+ const soft = brandSoft({ brand: hex, textPrimary, bgSurface: surfaces.bgSurface });
323
+ return Math.min(
324
+ contrast(soft.brandMuted, worstText),
325
+ contrast(soft.brandMuted, soft.brandBg),
326
+ contrast(soft.brandMuted, soft.brandBgHover),
327
+ );
328
+ };
329
+ for (let i = 0; i < 40 && brandMutedWorst(brandHex) < 4.5; i++) {
330
+ const lch = hexToOklch(brandHex)!;
331
+ brandHex = oklchToHex({ ...lch, l: mode === "light" ? lch.l - 0.005 : lch.l + 0.005 });
332
+ }
333
+ if (brandMutedWorst(brandHex) < 4.5) {
334
+ warnings.push(
335
+ `the brand as text (--brand-muted) reaches only ${brandMutedWorst(brandHex).toFixed(2)}:1 ` +
336
+ `against the ${mode} surfaces (needs 4.5:1).`,
337
+ );
338
+ }
294
339
  const brandLch = hexToOklch(brandHex)!;
340
+ const brandContrast = contrastOn(brandHex);
295
341
  const brandHover = oklchToHex({
296
342
  ...brandLch,
297
- // Hover moves AWAY from the page in both themes: darker on light, lighter on dark.
298
- l: mode === "light" ? brandLch.l - 0.06 : brandLch.l + 0.06,
343
+ // Hover moves AWAY from the colour of the text on it (0.33, §5.5): darker under
344
+ // white, lighter under a dark ink. It used to move away from the page, which in dark
345
+ // lightened a fill that carries WHITE text, toward its own label: 4.07:1.
346
+ l: brandContrast === "#ffffff" ? brandLch.l - 0.06 : brandLch.l + 0.06,
299
347
  });
300
- const brandContrast = contrastOn(brandHex);
301
348
  const onBrand = contrast(brandHex, brandContrast);
302
349
  if (onBrand < 4.5) {
303
350
  warnings.push(
@@ -352,7 +399,7 @@ export function derivePalette({
352
399
  const bg = tintOn(hue, chroma, surfaces.bgSurface, mode);
353
400
  const onSurface = solveLightness(
354
401
  4.5,
355
- parseHex(worstSurface)!,
402
+ parseHex(worstText)!,
356
403
  hue,
357
404
  chroma,
358
405
  mode === "light" ? "darker" : "lighter",
@@ -425,11 +472,19 @@ export function derivePalette({
425
472
  // red/green pair — which is why the shipped presets use teal/amber/violet. Derived
426
473
  // here at fixed hues rather than from the brand, for the same reason `--danger` is:
427
474
  // a consumer who rebrands does not thereby change what "money out" looks like.
428
- const money = (hue: number, target: number) => solveAgainst(hue, 0.12, target, worstSurface, mode);
475
+ const money = (hue: number, target: number) => solveAgainst(hue, 0.12, target, worstText, mode);
429
476
  const moneyIncome = money(175, 4.5);
430
477
  const moneyExpense = money(70, 4.5);
431
478
  const moneyNet = money(300, 4.5);
432
- const moneyNeutral = oklchToHex({ l: mode === "light" ? 0.52 : 0.72, c: 0.012, h: brand.h });
479
+ // "No money has moved": a quiet grey at a fixed lightness, but printed as TEXT (the
480
+ // numpad's sign, a future row's figure), so it gets the text floor too (0.33). The
481
+ // fixed lightness left it at 4.24:1 on four light pages.
482
+ const neutralFloor = solveLightness(4.5, parseHex(worstText)!, brand.h, 0.012, mode === "light" ? "darker" : "lighter");
483
+ const moneyNeutral = oklchToHex({
484
+ l: mode === "light" ? Math.min(0.52, neutralFloor) : Math.max(0.72, neutralFloor),
485
+ c: 0.012,
486
+ h: brand.h,
487
+ });
433
488
 
434
489
  const tokens: TokenSet = {
435
490
  ...surfaces,
@@ -498,6 +553,13 @@ export function auditPalette(t: TokenSet, semantic?: SemanticTokens): ContrastRe
498
553
  ["bgSurface", t.bgSurface],
499
554
  ["bgSurface2", t.bgSurface2],
500
555
  ];
556
+ // A hovered row on the card (`--bg-hover`) carries the same text as the card, and in
557
+ // light it is the darkest fill that text sits on (0.33, §12.10). Text is measured on
558
+ // it; the brand fill and the hairline, which are not text, are not.
559
+ const hover = hoverFill(t);
560
+ const textGrounds: Array<[string, string]> = [...surfaces, ["bgHover", hover]];
561
+ // `--brand-muted`, `--brand-bg` and `--brand-bg-hover`, mixed as tokens.css mixes them.
562
+ const { brandMuted, brandBg, brandBgHover } = brandSoft(t);
501
563
 
502
564
  const add = (pair: string, fg: string, bg: string, required: number, rule: string) => {
503
565
  const ratio = contrast(fg, bg);
@@ -505,21 +567,35 @@ export function auditPalette(t: TokenSet, semantic?: SemanticTokens): ContrastRe
505
567
  };
506
568
 
507
569
  for (const [name, bg] of surfaces) {
570
+ add(`brand on ${name}`, t.brand, bg, 3, "WCAG 1.4.11 graphical object");
571
+ add(`border on ${name}`, t.border, bg, 1.1, "house rule: a hairline must be findable");
572
+ }
573
+ for (const [name, bg] of textGrounds) {
508
574
  add(`textPrimary on ${name}`, t.textPrimary, bg, 7, "WCAG 1.4.6 AAA body text");
509
575
  add(`textSecondary on ${name}`, t.textSecondary, bg, 4.5, "WCAG 1.4.3 AA body text");
510
576
  add(`textMuted on ${name}`, t.textMuted, bg, 4.5, "WCAG 1.4.3 AA body text");
511
- add(`brand on ${name}`, t.brand, bg, 3, "WCAG 1.4.11 graphical object");
512
- add(`border on ${name}`, t.border, bg, 1.1, "house rule: a hairline must be findable");
577
+ add(`brandMuted on ${name}`, brandMuted, bg, 4.5, "WCAG 1.4.3 — the brand as text");
513
578
  for (const [key, hex] of [
514
579
  ["moneyIncome", t.moneyIncome],
515
580
  ["moneyExpense", t.moneyExpense],
516
581
  ["moneyNet", t.moneyNet],
582
+ // Not a figure's colour but still printed as one (the numpad's sign, a future
583
+ // row's amount) — measured since 0.33, when it was found at 4.24:1.
584
+ ["moneyNeutral", t.moneyNeutral],
517
585
  ] as const) {
518
586
  add(`${key} on ${name}`, hex, bg, 4.5, "WCAG 1.4.3 — these are rendered as TEXT");
519
587
  }
520
588
  }
521
589
 
522
590
  add("brandContrast on brand", t.brandContrast, t.brand, 4.5, "WCAG 1.4.3 AA body text");
591
+ // A hovered brand button carries the same label (0.33): the derived dark presets
592
+ // lightened the hover toward their white label and left it at 4.07:1.
593
+ add("brandContrast on brandHover", t.brandContrast, t.brandHover, 4.5, "WCAG 1.4.3 AA body text");
594
+ add("brandMuted on brandBg", brandMuted, brandBg, 4.5, "WCAG 1.4.3 — a soft brand chip's label");
595
+ // The same label on the chip hovered (§12.11): a current link, a selected day. At 70 %
596
+ // brand the muted text read 4.08–4.42:1 here (Default and Moss light; Ink, Moss, Plum
597
+ // and Contrast dark).
598
+ add("brandMuted on brandBgHover", brandMuted, brandBgHover, 4.5, "WCAG 1.4.3 — a hovered soft brand chip's label");
523
599
 
524
600
  if (semantic) {
525
601
  for (const [name, fg] of [
@@ -529,6 +605,7 @@ export function auditPalette(t: TokenSet, semantic?: SemanticTokens): ContrastRe
529
605
  ["success", semantic.success],
530
606
  ] as const) {
531
607
  add(`${name} on bgSurface`, fg, t.bgSurface, 4.5, "WCAG 1.4.3 — rendered as text");
608
+ add(`${name} on bgHover`, fg, hover, 4.5, "WCAG 1.4.3 — rendered as text, on a hovered row");
532
609
  }
533
610
  add("danger on dangerBg", semantic.danger, semantic.dangerBg, 4.5, "WCAG 1.4.3 — badge text");
534
611
  add("warning on warningBg", semantic.warning, semantic.warningBg, 4.5, "WCAG 1.4.3 — badge text");