@eifi1/ui-kit 0.4.0 → 0.5.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 (410) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +498 -57
  3. package/dist/chart.d.ts +10 -0
  4. package/dist/chart.js +10 -0
  5. package/dist/chart.js.map +1 -0
  6. package/dist/components/account-settings.d.ts +11 -0
  7. package/dist/components/account-settings.js +19 -12
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/alert-banner.d.ts +10 -6
  10. package/dist/components/alert-banner.js +20 -16
  11. package/dist/components/alert-banner.js.map +1 -1
  12. package/dist/components/amount-input.d.ts +30 -4
  13. package/dist/components/amount-input.js +36 -14
  14. package/dist/components/amount-input.js.map +1 -1
  15. package/dist/components/calculator.d.ts +24 -7
  16. package/dist/components/calculator.js +37 -20
  17. package/dist/components/calculator.js.map +1 -1
  18. package/dist/components/chart-zoom.d.ts +156 -0
  19. package/dist/components/chart-zoom.js +262 -0
  20. package/dist/components/chart-zoom.js.map +1 -0
  21. package/dist/components/chart.d.ts +28 -3
  22. package/dist/components/chart.js +71 -32
  23. package/dist/components/chart.js.map +1 -1
  24. package/dist/components/checkbox.d.ts +51 -0
  25. package/dist/components/checkbox.js +123 -0
  26. package/dist/components/checkbox.js.map +1 -0
  27. package/dist/components/chip.d.ts +128 -0
  28. package/dist/components/chip.js +357 -0
  29. package/dist/components/chip.js.map +1 -0
  30. package/dist/components/choice-parts.d.ts +24 -0
  31. package/dist/components/choice-parts.js +18 -0
  32. package/dist/components/choice-parts.js.map +1 -0
  33. package/dist/components/combobox-core.d.ts +52 -9
  34. package/dist/components/combobox-core.js +91 -20
  35. package/dist/components/combobox-core.js.map +1 -1
  36. package/dist/components/combobox.d.ts +43 -33
  37. package/dist/components/combobox.js +409 -340
  38. package/dist/components/combobox.js.map +1 -1
  39. package/dist/components/currency-select.d.ts +48 -8
  40. package/dist/components/currency-select.js +107 -65
  41. package/dist/components/currency-select.js.map +1 -1
  42. package/dist/components/data-table-filter-popover.d.ts +15 -3
  43. package/dist/components/data-table-filter-popover.js +23 -17
  44. package/dist/components/data-table-filter-popover.js.map +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +3 -2
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +53 -2
  49. package/dist/components/data-table-labels.js +31 -2
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table-pagination.d.ts +3 -1
  52. package/dist/components/data-table-pagination.js +16 -10
  53. package/dist/components/data-table-pagination.js.map +1 -1
  54. package/dist/components/data-table-sort.js +1 -0
  55. package/dist/components/data-table-sort.js.map +1 -1
  56. package/dist/components/data-table.d.ts +1 -1
  57. package/dist/components/data-table.js +201 -85
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/date-picker.d.ts +25 -9
  60. package/dist/components/date-picker.js +232 -94
  61. package/dist/components/date-picker.js.map +1 -1
  62. package/dist/components/dropdown.d.ts +96 -20
  63. package/dist/components/dropdown.js +77 -33
  64. package/dist/components/dropdown.js.map +1 -1
  65. package/dist/components/entity-combobox.d.ts +5 -4
  66. package/dist/components/entity-combobox.js +107 -76
  67. package/dist/components/entity-combobox.js.map +1 -1
  68. package/dist/components/facing-pair.d.ts +36 -0
  69. package/dist/components/facing-pair.js +40 -0
  70. package/dist/components/facing-pair.js.map +1 -0
  71. package/dist/components/field-sync.d.ts +164 -0
  72. package/dist/components/field-sync.js +264 -0
  73. package/dist/components/field-sync.js.map +1 -0
  74. package/dist/components/file-dropzone.d.ts +38 -6
  75. package/dist/components/file-dropzone.js +34 -9
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/full-bleed-dialog.d.ts +37 -26
  78. package/dist/components/full-bleed-dialog.js +20 -5
  79. package/dist/components/full-bleed-dialog.js.map +1 -1
  80. package/dist/components/grouped-picker.d.ts +21 -10
  81. package/dist/components/grouped-picker.js +66 -52
  82. package/dist/components/grouped-picker.js.map +1 -1
  83. package/dist/components/hover-menu.d.ts +20 -4
  84. package/dist/components/hover-menu.js +15 -4
  85. package/dist/components/hover-menu.js.map +1 -1
  86. package/dist/components/mini-calendar.d.ts +102 -14
  87. package/dist/components/mini-calendar.js +256 -70
  88. package/dist/components/mini-calendar.js.map +1 -1
  89. package/dist/components/modal.d.ts +9 -3
  90. package/dist/components/modal.js +34 -45
  91. package/dist/components/modal.js.map +1 -1
  92. package/dist/components/month-picker.d.ts +79 -0
  93. package/dist/components/month-picker.js +342 -0
  94. package/dist/components/month-picker.js.map +1 -0
  95. package/dist/components/multi-entity-combobox.d.ts +7 -6
  96. package/dist/components/multi-entity-combobox.js +112 -72
  97. package/dist/components/multi-entity-combobox.js.map +1 -1
  98. package/dist/components/multi-select.d.ts +13 -11
  99. package/dist/components/multi-select.js +161 -84
  100. package/dist/components/multi-select.js.map +1 -1
  101. package/dist/components/number-field.d.ts +92 -0
  102. package/dist/components/number-field.js +111 -0
  103. package/dist/components/number-field.js.map +1 -0
  104. package/dist/components/number-input.d.ts +28 -2
  105. package/dist/components/number-input.js +12 -2
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +32 -4
  108. package/dist/components/numpad-sheet.js +30 -13
  109. package/dist/components/numpad-sheet.js.map +1 -1
  110. package/dist/components/page-contents.d.ts +90 -0
  111. package/dist/components/page-contents.js +170 -0
  112. package/dist/components/page-contents.js.map +1 -0
  113. package/dist/components/password-strength.d.ts +106 -0
  114. package/dist/components/password-strength.js +132 -0
  115. package/dist/components/password-strength.js.map +1 -0
  116. package/dist/components/picker-sheet.d.ts +23 -15
  117. package/dist/components/picker-sheet.js +34 -14
  118. package/dist/components/picker-sheet.js.map +1 -1
  119. package/dist/components/popover.d.ts +62 -4
  120. package/dist/components/popover.js +52 -2
  121. package/dist/components/popover.js.map +1 -1
  122. package/dist/components/search-field.d.ts +25 -7
  123. package/dist/components/search-field.js +7 -5
  124. package/dist/components/search-field.js.map +1 -1
  125. package/dist/components/series-chart-labels.d.ts +14 -0
  126. package/dist/components/series-chart-labels.js +11 -0
  127. package/dist/components/series-chart-labels.js.map +1 -0
  128. package/dist/components/series-chart.d.ts +207 -0
  129. package/dist/components/series-chart.js +223 -0
  130. package/dist/components/series-chart.js.map +1 -0
  131. package/dist/components/settings-fields.d.ts +14 -4
  132. package/dist/components/settings-fields.js +19 -8
  133. package/dist/components/settings-fields.js.map +1 -1
  134. package/dist/components/signature-pad.d.ts +119 -0
  135. package/dist/components/signature-pad.js +405 -0
  136. package/dist/components/signature-pad.js.map +1 -0
  137. package/dist/components/slider.d.ts +80 -0
  138. package/dist/components/slider.js +155 -0
  139. package/dist/components/slider.js.map +1 -0
  140. package/dist/components/sparkline.d.ts +93 -0
  141. package/dist/components/sparkline.js +205 -0
  142. package/dist/components/sparkline.js.map +1 -0
  143. package/dist/components/stat-tile.d.ts +151 -0
  144. package/dist/components/stat-tile.js +236 -0
  145. package/dist/components/stat-tile.js.map +1 -0
  146. package/dist/components/swipeable-row.d.ts +25 -2
  147. package/dist/components/swipeable-row.js +31 -0
  148. package/dist/components/swipeable-row.js.map +1 -1
  149. package/dist/components/switch.d.ts +60 -0
  150. package/dist/components/switch.js +81 -0
  151. package/dist/components/switch.js.map +1 -0
  152. package/dist/components/time-input.d.ts +58 -0
  153. package/dist/components/time-input.js +50 -0
  154. package/dist/components/time-input.js.map +1 -0
  155. package/dist/components/toggle-group.d.ts +18 -3
  156. package/dist/components/toggle-group.js +10 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +115 -0
  159. package/dist/components/toggle-legend.js +114 -0
  160. package/dist/components/toggle-legend.js.map +1 -0
  161. package/dist/components/tooltip.d.ts +40 -11
  162. package/dist/components/tooltip.js +77 -20
  163. package/dist/components/tooltip.js.map +1 -1
  164. package/dist/components/treemap.d.ts +132 -0
  165. package/dist/components/treemap.js +179 -0
  166. package/dist/components/treemap.js.map +1 -0
  167. package/dist/components/ui.d.ts +159 -46
  168. package/dist/components/ui.js +220 -115
  169. package/dist/components/ui.js.map +1 -1
  170. package/dist/components/use-mobile-reveal.js +1 -0
  171. package/dist/components/use-mobile-reveal.js.map +1 -1
  172. package/dist/components/use-table-state.d.ts +1 -1
  173. package/dist/components/use-table-state.js +38 -17
  174. package/dist/components/use-table-state.js.map +1 -1
  175. package/dist/components/user-avatar.d.ts +15 -4
  176. package/dist/components/user-avatar.js +5 -4
  177. package/dist/components/user-avatar.js.map +1 -1
  178. package/dist/components/wizard-stepper.js +3 -2
  179. package/dist/components/wizard-stepper.js.map +1 -1
  180. package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
  181. package/dist/data-table.d.ts +7 -0
  182. package/dist/data-table.js +12 -0
  183. package/dist/data-table.js.map +1 -0
  184. package/dist/feedback/feedback-attachment.js +12 -11
  185. package/dist/feedback/feedback-attachment.js.map +1 -1
  186. package/dist/feedback/feedback-dialog.js +2 -1
  187. package/dist/feedback/feedback-dialog.js.map +1 -1
  188. package/dist/feedback/feedback-inbox.d.ts +33 -7
  189. package/dist/feedback/feedback-inbox.js +49 -37
  190. package/dist/feedback/feedback-inbox.js.map +1 -1
  191. package/dist/feedback.d.ts +5 -0
  192. package/dist/feedback.js +5 -0
  193. package/dist/feedback.js.map +1 -0
  194. package/dist/hooks/use-anchored-panel.js +1 -0
  195. package/dist/hooks/use-anchored-panel.js.map +1 -1
  196. package/dist/hooks/use-anchored-rect.js +1 -0
  197. package/dist/hooks/use-anchored-rect.js.map +1 -1
  198. package/dist/hooks/use-announce.d.ts +46 -0
  199. package/dist/hooks/use-announce.js +37 -0
  200. package/dist/hooks/use-announce.js.map +1 -0
  201. package/dist/hooks/use-body-scroll-lock.js +1 -0
  202. package/dist/hooks/use-body-scroll-lock.js.map +1 -1
  203. package/dist/hooks/use-close-transition.js +1 -0
  204. package/dist/hooks/use-close-transition.js.map +1 -1
  205. package/dist/hooks/use-dismiss.js +1 -0
  206. package/dist/hooks/use-dismiss.js.map +1 -1
  207. package/dist/hooks/use-focus-trap.d.ts +16 -0
  208. package/dist/hooks/use-focus-trap.js +67 -0
  209. package/dist/hooks/use-focus-trap.js.map +1 -0
  210. package/dist/hooks/use-media-query.js +1 -0
  211. package/dist/hooks/use-media-query.js.map +1 -1
  212. package/dist/hooks/use-overlay-history.js +27 -1
  213. package/dist/hooks/use-overlay-history.js.map +1 -1
  214. package/dist/hooks/use-row-swipe.js +1 -0
  215. package/dist/hooks/use-row-swipe.js.map +1 -1
  216. package/dist/i18n/defaults.d.ts +31 -0
  217. package/dist/i18n/defaults.js +63 -0
  218. package/dist/i18n/defaults.js.map +1 -0
  219. package/dist/i18n/kit-labels.d.ts +250 -0
  220. package/dist/i18n/kit-labels.js +184 -0
  221. package/dist/i18n/kit-labels.js.map +1 -0
  222. package/dist/index.d.ts +47 -22
  223. package/dist/index.js +28 -0
  224. package/dist/index.js.map +1 -1
  225. package/dist/lib/calc.js +1 -0
  226. package/dist/lib/calc.js.map +1 -1
  227. package/dist/lib/cn.js +1 -0
  228. package/dist/lib/cn.js.map +1 -1
  229. package/dist/lib/dates.js +1 -0
  230. package/dist/lib/dates.js.map +1 -1
  231. package/dist/lib/logger.js +1 -0
  232. package/dist/lib/logger.js.map +1 -1
  233. package/dist/lib/safe-storage.js +1 -0
  234. package/dist/lib/safe-storage.js.map +1 -1
  235. package/dist/search/command-palette.d.ts +11 -1
  236. package/dist/search/command-palette.js +21 -15
  237. package/dist/search/command-palette.js.map +1 -1
  238. package/dist/search.d.ts +2 -0
  239. package/dist/search.js +3 -0
  240. package/dist/search.js.map +1 -0
  241. package/dist/shell/app-shell.d.ts +33 -4
  242. package/dist/shell/app-shell.js +275 -78
  243. package/dist/shell/app-shell.js.map +1 -1
  244. package/dist/shell/option-switcher-menu.js +3 -2
  245. package/dist/shell/option-switcher-menu.js.map +1 -1
  246. package/dist/shell/role-switcher.d.ts +6 -0
  247. package/dist/shell/role-switcher.js +7 -4
  248. package/dist/shell/role-switcher.js.map +1 -1
  249. package/dist/shell/top-bar.d.ts +15 -7
  250. package/dist/shell/top-bar.js +3 -5
  251. package/dist/shell/top-bar.js.map +1 -1
  252. package/dist/shell/topbar-action-menu.js +3 -2
  253. package/dist/shell/topbar-action-menu.js.map +1 -1
  254. package/dist/shell/topbar-controls.d.ts +5 -2
  255. package/dist/shell/topbar-controls.js +20 -15
  256. package/dist/shell/topbar-controls.js.map +1 -1
  257. package/dist/shell.d.ts +13 -0
  258. package/dist/shell.js +8 -0
  259. package/dist/shell.js.map +1 -0
  260. package/dist/theme/chart-palette.d.ts +19 -12
  261. package/dist/theme/chart-palette.js +24 -30
  262. package/dist/theme/chart-palette.js.map +1 -1
  263. package/dist/theme/color.d.ts +78 -0
  264. package/dist/theme/color.js +148 -0
  265. package/dist/theme/color.js.map +1 -0
  266. package/dist/theme/palette-derive.d.ts +140 -0
  267. package/dist/theme/palette-derive.js +376 -0
  268. package/dist/theme/palette-derive.js.map +1 -0
  269. package/dist/theme/palette-presets.d.ts +27 -5
  270. package/dist/theme/palette-presets.js +231 -33
  271. package/dist/theme/palette-presets.js.map +1 -1
  272. package/dist/theme/palette-store.js +1 -0
  273. package/dist/theme/palette-store.js.map +1 -1
  274. package/dist/theme/theme-store.js +1 -0
  275. package/dist/theme/theme-store.js.map +1 -1
  276. package/dist/tour/tour.d.ts +2 -1
  277. package/dist/tour/tour.js +15 -9
  278. package/dist/tour/tour.js.map +1 -1
  279. package/dist/tour.d.ts +2 -0
  280. package/dist/tour.js +3 -0
  281. package/dist/tour.js.map +1 -0
  282. package/dist/wizard/stepper-nav.js +10 -8
  283. package/dist/wizard/stepper-nav.js.map +1 -1
  284. package/dist/wizard/types.d.ts +18 -2
  285. package/dist/wizard/types.js +4 -1
  286. package/dist/wizard/types.js.map +1 -1
  287. package/dist/wizard/use-wizard.js +12 -4
  288. package/dist/wizard/use-wizard.js.map +1 -1
  289. package/dist/wizard/validation.js +1 -0
  290. package/dist/wizard/validation.js.map +1 -1
  291. package/dist/wizard/wizard-context.js +1 -0
  292. package/dist/wizard/wizard-context.js.map +1 -1
  293. package/dist/wizard/wizard-step.js +1 -0
  294. package/dist/wizard/wizard-step.js.map +1 -1
  295. package/dist/wizard/wizard-summary.js +5 -3
  296. package/dist/wizard/wizard-summary.js.map +1 -1
  297. package/dist/wizard.d.ts +8 -0
  298. package/dist/wizard.js +9 -0
  299. package/dist/wizard.js.map +1 -0
  300. package/package.json +67 -14
  301. package/src/chart.ts +22 -0
  302. package/src/components/account-settings.tsx +275 -0
  303. package/src/components/alert-banner.tsx +75 -0
  304. package/src/components/amount-input.tsx +471 -0
  305. package/src/components/calculator.tsx +252 -0
  306. package/src/components/chart-zoom.tsx +499 -0
  307. package/src/components/chart.tsx +363 -0
  308. package/src/components/checkbox.tsx +222 -0
  309. package/src/components/chip.tsx +570 -0
  310. package/src/components/choice-parts.ts +33 -0
  311. package/src/components/combobox-core.tsx +540 -0
  312. package/src/components/combobox.tsx +1026 -0
  313. package/src/components/currency-select.tsx +269 -0
  314. package/src/components/data-table-filter-popover.tsx +252 -0
  315. package/src/components/data-table-filters.ts +176 -0
  316. package/src/components/data-table-labels.ts +190 -0
  317. package/src/components/data-table-pagination.tsx +126 -0
  318. package/src/components/data-table-sort.ts +70 -0
  319. package/src/components/data-table.tsx +1740 -0
  320. package/src/components/date-picker.tsx +631 -0
  321. package/src/components/dropdown.tsx +341 -0
  322. package/src/components/entity-combobox.tsx +215 -0
  323. package/src/components/facing-pair.ts +86 -0
  324. package/src/components/field-sync.tsx +472 -0
  325. package/src/components/file-dropzone.tsx +181 -0
  326. package/src/components/full-bleed-dialog.tsx +179 -0
  327. package/src/components/grouped-picker.tsx +157 -0
  328. package/src/components/hover-menu.tsx +183 -0
  329. package/src/components/mini-calendar.tsx +554 -0
  330. package/src/components/modal.tsx +287 -0
  331. package/src/components/month-picker.tsx +549 -0
  332. package/src/components/multi-entity-combobox.tsx +226 -0
  333. package/src/components/multi-select.tsx +303 -0
  334. package/src/components/number-field.tsx +225 -0
  335. package/src/components/number-input.tsx +211 -0
  336. package/src/components/numpad-sheet.tsx +264 -0
  337. package/src/components/page-contents.tsx +289 -0
  338. package/src/components/password-strength.tsx +258 -0
  339. package/src/components/picker-sheet.tsx +251 -0
  340. package/src/components/popover.tsx +195 -0
  341. package/src/components/search-field.tsx +146 -0
  342. package/src/components/series-chart-labels.ts +26 -0
  343. package/src/components/series-chart.tsx +481 -0
  344. package/src/components/settings-fields.tsx +68 -0
  345. package/src/components/signature-pad.tsx +630 -0
  346. package/src/components/slider.tsx +313 -0
  347. package/src/components/sparkline.tsx +329 -0
  348. package/src/components/stat-tile.tsx +448 -0
  349. package/src/components/swipeable-row.tsx +271 -0
  350. package/src/components/switch.tsx +170 -0
  351. package/src/components/time-input.tsx +107 -0
  352. package/src/components/toggle-group.tsx +153 -0
  353. package/src/components/toggle-legend.tsx +229 -0
  354. package/src/components/tooltip.tsx +477 -0
  355. package/src/components/treemap.tsx +390 -0
  356. package/src/components/ui.tsx +1284 -0
  357. package/src/components/use-mobile-reveal.ts +54 -0
  358. package/src/components/use-table-state.ts +295 -0
  359. package/src/components/user-avatar.tsx +61 -0
  360. package/src/components/wizard-stepper.tsx +37 -0
  361. package/src/data-table.ts +19 -0
  362. package/src/feedback/feedback-attachment.tsx +246 -0
  363. package/src/feedback/feedback-dialog.tsx +209 -0
  364. package/src/feedback/feedback-inbox.tsx +615 -0
  365. package/src/feedback.ts +8 -0
  366. package/src/hooks/use-anchored-panel.ts +169 -0
  367. package/src/hooks/use-anchored-rect.ts +78 -0
  368. package/src/hooks/use-announce.ts +85 -0
  369. package/src/hooks/use-body-scroll-lock.ts +94 -0
  370. package/src/hooks/use-close-transition.ts +99 -0
  371. package/src/hooks/use-dismiss.ts +77 -0
  372. package/src/hooks/use-focus-trap.ts +160 -0
  373. package/src/hooks/use-media-query.ts +23 -0
  374. package/src/hooks/use-overlay-history.ts +345 -0
  375. package/src/hooks/use-row-swipe.ts +212 -0
  376. package/src/i18n/defaults.ts +68 -0
  377. package/src/i18n/kit-labels.tsx +447 -0
  378. package/src/index.ts +169 -0
  379. package/src/lib/calc.ts +309 -0
  380. package/src/lib/cn.ts +5 -0
  381. package/src/lib/dates.ts +187 -0
  382. package/src/lib/logger.ts +73 -0
  383. package/src/lib/safe-storage.ts +37 -0
  384. package/src/search/command-palette.tsx +342 -0
  385. package/src/search.ts +3 -0
  386. package/src/shell/app-shell.tsx +772 -0
  387. package/src/shell/option-switcher-menu.tsx +72 -0
  388. package/src/shell/role-switcher.tsx +46 -0
  389. package/src/shell/top-bar.tsx +39 -0
  390. package/src/shell/topbar-action-menu.tsx +91 -0
  391. package/src/shell/topbar-controls.tsx +229 -0
  392. package/src/shell.ts +10 -0
  393. package/src/theme/chart-palette.ts +145 -0
  394. package/src/theme/color.ts +266 -0
  395. package/src/theme/palette-derive.ts +681 -0
  396. package/src/theme/palette-presets.ts +760 -0
  397. package/src/theme/palette-store.ts +94 -0
  398. package/src/theme/theme-store.ts +145 -0
  399. package/src/tour/tour.tsx +586 -0
  400. package/src/tour.ts +3 -0
  401. package/src/vite-env.d.ts +26 -0
  402. package/src/wizard/stepper-nav.tsx +221 -0
  403. package/src/wizard/types.ts +178 -0
  404. package/src/wizard/use-wizard.ts +345 -0
  405. package/src/wizard/validation.ts +37 -0
  406. package/src/wizard/wizard-context.tsx +29 -0
  407. package/src/wizard/wizard-step.tsx +14 -0
  408. package/src/wizard/wizard-summary.tsx +69 -0
  409. package/src/wizard.ts +14 -0
  410. package/tokens.css +222 -20
@@ -0,0 +1,275 @@
1
+ import { useState } from "react";
2
+ import type { KeyboardEvent, ReactNode } from "react";
3
+ import { Button, Card, FIELD_WRITABLE_LOOK, Input } from "./ui";
4
+ import { UserAvatar } from "./user-avatar";
5
+ import { DEFAULT_COMMON_LABELS, useKitLabels } from "../i18n/kit-labels";
6
+
7
+ /**
8
+ * App-agnostic account-settings SECTIONS (feedback #333). The presentation lives
9
+ * in @hb/ui; each app injects its own auth-API handlers + translated labels and
10
+ * arranges these next to its own app-specific cards (appearance, budgets, …).
11
+ * Every section is a self-contained <Card> owning its transient form state.
12
+ */
13
+
14
+ export interface ProfileSettingLabels {
15
+ title: string;
16
+ email: string;
17
+ role: string;
18
+ memberSince: string;
19
+ displayName: string;
20
+ save: string;
21
+ }
22
+
23
+ export function ProfileSetting({
24
+ name,
25
+ email,
26
+ role,
27
+ memberSince,
28
+ value,
29
+ onChange,
30
+ onSave,
31
+ saving,
32
+ labels,
33
+ }: {
34
+ name?: string | null;
35
+ email?: string | null;
36
+ role?: ReactNode;
37
+ memberSince?: ReactNode;
38
+ value: string;
39
+ onChange: (value: string) => void;
40
+ onSave: () => void;
41
+ saving?: boolean;
42
+ labels: ProfileSettingLabels;
43
+ }) {
44
+ const dirty = value.trim().length > 0 && value.trim() !== (name ?? "");
45
+ return (
46
+ <Card className="p-4 space-y-3">
47
+ <div className="flex items-center gap-3">
48
+ <UserAvatar name={name} email={email} size="lg" />
49
+ <div className="min-w-0">
50
+ <div className="text-sm font-medium">{labels.title}</div>
51
+ <div className="truncate font-mono text-xs text-[var(--text-muted)]">{email ?? "—"}</div>
52
+ </div>
53
+ </div>
54
+ <div className="grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1 text-sm">
55
+ <span className="text-[var(--text-muted)]">{labels.role}</span>
56
+ <span>{role ?? "—"}</span>
57
+ <span className="text-[var(--text-muted)]">{labels.memberSince}</span>
58
+ <span>{memberSince ?? "—"}</span>
59
+ </div>
60
+ <Input
61
+ id="display-name"
62
+ label={labels.displayName}
63
+ value={value}
64
+ onChange={(e) => onChange(e.target.value)}
65
+ maxLength={120}
66
+ />
67
+ <Button onClick={onSave} disabled={!dirty || saving}>
68
+ {labels.save}
69
+ </Button>
70
+ </Card>
71
+ );
72
+ }
73
+
74
+ export interface PasswordSettingLabels {
75
+ title: string;
76
+ current: string;
77
+ next: string;
78
+ confirm: string;
79
+ submit: string;
80
+ tooShort: string;
81
+ mismatch: string;
82
+ }
83
+
84
+ export function PasswordSetting({
85
+ onSubmit,
86
+ pending,
87
+ minLength = 8,
88
+ labels,
89
+ }: {
90
+ /** Called with the validated (current, new) pair; return a promise to auto-clear on success. */
91
+ onSubmit: (currentPassword: string, newPassword: string) => void | Promise<unknown>;
92
+ pending?: boolean;
93
+ minLength?: number;
94
+ labels: PasswordSettingLabels;
95
+ }) {
96
+ const [current, setCurrent] = useState("");
97
+ const [next, setNext] = useState("");
98
+ const [confirm, setConfirm] = useState("");
99
+ const [error, setError] = useState<string | null>(null);
100
+
101
+ const submit = async () => {
102
+ if (next.length < minLength) {
103
+ setError(labels.tooShort);
104
+ return;
105
+ }
106
+ if (next !== confirm) {
107
+ setError(labels.mismatch);
108
+ return;
109
+ }
110
+ setError(null);
111
+ try {
112
+ await onSubmit(current, next);
113
+ setCurrent("");
114
+ setNext("");
115
+ setConfirm("");
116
+ } catch {
117
+ // The app surfaces the failure (e.g. a toast); keep the fields for a retry.
118
+ }
119
+ };
120
+
121
+ return (
122
+ <Card className="p-4 space-y-3">
123
+ <div className="text-sm font-medium">{labels.title}</div>
124
+ <Input type="password" autoComplete="current-password" label={labels.current} value={current} onChange={(e) => setCurrent(e.target.value)} />
125
+ <Input type="password" autoComplete="new-password" label={labels.next} value={next} onChange={(e) => setNext(e.target.value)} />
126
+ <Input type="password" autoComplete="new-password" label={labels.confirm} value={confirm} onChange={(e) => setConfirm(e.target.value)} />
127
+ {error && <p className="text-xs text-[var(--danger)]">{error}</p>}
128
+ <Button onClick={() => void submit()} disabled={!current || !next || !confirm || pending}>
129
+ {labels.submit}
130
+ </Button>
131
+ </Card>
132
+ );
133
+ }
134
+
135
+ export interface TwoFactorSettingLabels {
136
+ status: string;
137
+ enabledText: string;
138
+ disabledText: string;
139
+ enable: string;
140
+ scanHint: string;
141
+ codeLabel: string;
142
+ verify: string;
143
+ disableSection: string;
144
+ password: string;
145
+ disable: string;
146
+ /**
147
+ * Alt text for the setup QR image, which shipped as a hardcoded `alt="QR"`.
148
+ *
149
+ * OPTIONAL, alone among these keys, and not because it matters less: this
150
+ * interface is annotated at consumer call sites (`const LABELS:
151
+ * TwoFactorSettingLabels = {…}`), so a new REQUIRED key is a compile error in
152
+ * every app on the next `npm update` — the additive-API rule in the README. It
153
+ * falls back to English here the way `AmountInput`'s label keys do. Make it
154
+ * required in the next major, when the three apps can be updated with it.
155
+ */
156
+ qrAlt?: string;
157
+ }
158
+
159
+ /** `qrAlt`'s English fallback, kept beside the interface rather than inline so the
160
+ * one English word in this section is findable by the same `DEFAULT_*` grep as
161
+ * every other one. Module-private: the key goes required in the next major. */
162
+ const DEFAULT_QR_ALT = "QR code";
163
+
164
+ export function TwoFactorSetting({
165
+ enabled,
166
+ setup,
167
+ onStartSetup,
168
+ onEnable,
169
+ onDisable,
170
+ busy,
171
+ labels,
172
+ }: {
173
+ enabled: boolean;
174
+ /** QR + secret returned by the app's setup call; null before setup starts. */
175
+ setup: { qrSvg: string; secret: string } | null;
176
+ onStartSetup: () => void;
177
+ onEnable: (code: string) => void;
178
+ onDisable: (password: string, code: string) => void;
179
+ busy?: boolean;
180
+ labels: TwoFactorSettingLabels;
181
+ }) {
182
+ const [code, setCode] = useState("");
183
+ const [password, setPassword] = useState("");
184
+ // Browsers autofill one-time-code fields with the saved username; keep the
185
+ // field readOnly until focus to block that. It therefore has to keep the LOOK of
186
+ // an editable field (FIELD_WRITABLE_LOOK): this is the one place where readOnly
187
+ // does not mean "you may not type here", and without the opt-out the field would
188
+ // sit there greyed until the moment it is focused.
189
+ const [otpReadonly, setOtpReadonly] = useState(true);
190
+ const onOtpFocus = () => setOtpReadonly(false);
191
+ // Only for the "Status: Enabled" composition — the punctuation between a field's
192
+ // name and its value is the language's (see `CommonLabels.fieldValue`).
193
+ const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
194
+
195
+ return (
196
+ <Card className="p-4 space-y-3">
197
+ <div className="text-sm">
198
+ {common.fieldValue(labels.status, enabled ? labels.enabledText : labels.disabledText)}
199
+ </div>
200
+
201
+ {!enabled && !setup && (
202
+ <Button onClick={onStartSetup} disabled={busy}>
203
+ {labels.enable}
204
+ </Button>
205
+ )}
206
+
207
+ {setup && (
208
+ <div className="space-y-2">
209
+ <div className="text-xs text-[var(--text-secondary)]">{labels.scanHint}</div>
210
+ {/* `bg-white`, NOT `--bg-surface`, and it must stay that way: a QR code is
211
+ read by a camera looking for maximum contrast between the modules and
212
+ their quiet zone. On the dark theme a surface-coloured backing makes it
213
+ slow to acquire, and on a warm light preset it lowers the contrast ratio
214
+ the spec is written against. This is the one place in the kit where a
215
+ literal colour is the correct answer. */}
216
+ <div className="inline-block rounded-md bg-white p-2">
217
+ <img
218
+ alt={labels.qrAlt ?? DEFAULT_QR_ALT}
219
+ src={`data:image/svg+xml;base64,${setup.qrSvg}`}
220
+ className="h-48 w-48"
221
+ />
222
+ </div>
223
+ <div className="break-all font-mono text-xs">{setup.secret}</div>
224
+ <Input
225
+ inputMode="numeric"
226
+ autoComplete="one-time-code"
227
+ readOnly={otpReadonly}
228
+ inputClassName={FIELD_WRITABLE_LOOK}
229
+ onFocus={onOtpFocus}
230
+ maxLength={8}
231
+ label={labels.codeLabel}
232
+ value={code}
233
+ onChange={(e) => setCode(e.target.value)}
234
+ onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
235
+ if (e.key === "Enter" && code && !busy) {
236
+ e.preventDefault();
237
+ onEnable(code);
238
+ }
239
+ }}
240
+ />
241
+ <Button onClick={() => onEnable(code)} disabled={!code || busy}>
242
+ {labels.verify}
243
+ </Button>
244
+ </div>
245
+ )}
246
+
247
+ {enabled && (
248
+ <div className="space-y-3 border-t border-[var(--border)] pt-3">
249
+ <div className="text-sm font-medium">{labels.disableSection}</div>
250
+ <Input type="password" autoComplete="current-password" label={labels.password} value={password} onChange={(e) => setPassword(e.target.value)} />
251
+ <Input
252
+ inputMode="numeric"
253
+ autoComplete="one-time-code"
254
+ readOnly={otpReadonly}
255
+ inputClassName={FIELD_WRITABLE_LOOK}
256
+ onFocus={onOtpFocus}
257
+ maxLength={8}
258
+ label={labels.codeLabel}
259
+ value={code}
260
+ onChange={(e) => setCode(e.target.value)}
261
+ onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
262
+ if (e.key === "Enter" && password && code && !busy) {
263
+ e.preventDefault();
264
+ onDisable(password, code);
265
+ }
266
+ }}
267
+ />
268
+ <Button variant="danger" onClick={() => onDisable(password, code)} disabled={!password || !code || busy}>
269
+ {labels.disable}
270
+ </Button>
271
+ </div>
272
+ )}
273
+ </Card>
274
+ );
275
+ }
@@ -0,0 +1,75 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
+ import { AlertTriangle } from "lucide-react";
3
+ import { cn } from "../lib/cn";
4
+
5
+ export type AlertTone = "danger" | "warning" | "neutral";
6
+
7
+ /** Border + surface per tone, WITHOUT a radius or padding, so a caller that owns
8
+ * its own box (a Card, say) can take the tone alone. Single source for both
9
+ * {@link alertFrameClass} and {@link toneFrameClass}.
10
+ *
11
+ * The colored tones are 2px, not 1px, and that is load-bearing rather than
12
+ * decorative. A 1px border is 1.25 device pixels on a 125%-scaled display — the
13
+ * usual Windows setting — so whether it renders as a solid line or as two
14
+ * half-lit pixels depends on where the box happens to land in device-pixel space.
15
+ * The same card then shows a heavy left edge and a right edge that fades out,
16
+ * which is what feedback #264, #277, #491 and #498 all reported. A 2px border is
17
+ * 2.5 device pixels: it always covers at least two pixels fully, on both edges,
18
+ * at any offset. Callers compensate the extra pixel in their own padding so
19
+ * switching tones never shifts the layout. */
20
+ const TONE_FRAME: Record<AlertTone, string> = {
21
+ danger: "border-2 border-[var(--danger-border)] bg-[var(--danger-bg)]",
22
+ warning: "border-2 border-[var(--warning-border)] bg-[var(--warning-bg)]",
23
+ neutral: "border border-[var(--border)]",
24
+ };
25
+
26
+ /** The tone's border + surface on their own — for a caller that already has a box
27
+ * with its own radius and padding (e.g. a Card). Remember to shave 1px off that
28
+ * padding for the colored tones, whose border is 2px wide. */
29
+ export function toneFrameClass(tone: AlertTone): string {
30
+ return TONE_FRAME[tone];
31
+ }
32
+
33
+ /** Single source for the warning-callout frame (feedback #277): the tone's border
34
+ * and surface plus this component's own radius and padding, the latter
35
+ * compensating the 2px colored border so toggling tones never shifts layout. */
36
+ export function alertFrameClass(tone: AlertTone): string {
37
+ const box = tone === "neutral" ? "rounded-md p-3" : "rounded-md p-[11px]";
38
+ return `${box} ${TONE_FRAME[tone]}`;
39
+ }
40
+
41
+ const TONE_TEXT: Record<Exclude<AlertTone, "neutral">, string> = {
42
+ danger: "text-[var(--danger)]",
43
+ warning: "text-[var(--warning)]",
44
+ };
45
+
46
+ const TONE_ICON: Record<Exclude<AlertTone, "neutral">, string> = {
47
+ danger: "text-[var(--danger)]",
48
+ warning: "text-[var(--warning)]",
49
+ };
50
+
51
+ export interface AlertBannerProps extends ComponentPropsWithoutRef<"div"> {
52
+ /** `neutral` is deliberately not offered: a callout with a warning triangle in it
53
+ * and no colour is a warning that looks like a note, which is the one thing this
54
+ * box must never be. The neutral frame is still available on its own through
55
+ * {@link toneFrameClass}. */
56
+ tone?: Exclude<AlertTone, "neutral">;
57
+ children: ReactNode;
58
+ }
59
+
60
+ /** Static warning/danger callout box with the shared frame and an icon. */
61
+ export function AlertBanner({ tone = "danger", className, children, ...rest }: AlertBannerProps) {
62
+ return (
63
+ <div
64
+ // Arbitrary attributes first — a `data-tour` anchor, a test id, an
65
+ // `aria-describedby` — then the layout and tone classes, which are merged
66
+ // through `cn` rather than spread so a caller's `className` refines the box
67
+ // instead of replacing it.
68
+ {...rest}
69
+ className={cn("flex items-start gap-2 text-sm", alertFrameClass(tone), TONE_TEXT[tone], className)}
70
+ >
71
+ <AlertTriangle className={cn("mt-0.5 size-4 shrink-0", TONE_ICON[tone])} />
72
+ <span className="flex-1">{children}</span>
73
+ </div>
74
+ );
75
+ }