zabi-components 8.0.0 → 8.1.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 (364) hide show
  1. package/CHANGELOG.md +1924 -0
  2. package/README.md +243 -10
  3. package/THEME.md +149 -332
  4. package/THEMING.md +1192 -266
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +104 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +34 -0
  8. package/dist/atoms/Badge.svelte +36 -7
  9. package/dist/atoms/Badge.svelte.d.ts +2 -2
  10. package/dist/atoms/Button.svelte +177 -27
  11. package/dist/atoms/Button.svelte.d.ts +30 -2
  12. package/dist/atoms/Card.svelte +4 -4
  13. package/dist/atoms/CardHeader.svelte +16 -4
  14. package/dist/atoms/Checkbox.svelte +48 -21
  15. package/dist/atoms/Checkbox.svelte.d.ts +4 -2
  16. package/dist/atoms/CodeBlock.svelte +20 -2
  17. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  18. package/dist/atoms/ColorPicker.svelte +259 -49
  19. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  20. package/dist/atoms/Container.svelte +16 -8
  21. package/dist/atoms/DateField.svelte +107 -0
  22. package/dist/atoms/DateField.svelte.d.ts +53 -0
  23. package/dist/atoms/FieldMessages.svelte +69 -0
  24. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  25. package/dist/atoms/FloatingActionButton.svelte +174 -0
  26. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  27. package/dist/atoms/Heading.svelte +37 -5
  28. package/dist/atoms/Heading.svelte.d.ts +7 -0
  29. package/dist/atoms/IconButton.svelte +220 -24
  30. package/dist/atoms/IconButton.svelte.d.ts +49 -4
  31. package/dist/atoms/Input.svelte +253 -52
  32. package/dist/atoms/Input.svelte.d.ts +32 -2
  33. package/dist/atoms/List.svelte +4 -1
  34. package/dist/atoms/ListItem.svelte +44 -11
  35. package/dist/atoms/ListItem.svelte.d.ts +5 -0
  36. package/dist/atoms/Progress.svelte +27 -8
  37. package/dist/atoms/Progress.svelte.d.ts +4 -0
  38. package/dist/atoms/Radio.svelte +19 -3
  39. package/dist/atoms/Radio.svelte.d.ts +4 -2
  40. package/dist/atoms/Rating.svelte +494 -0
  41. package/dist/atoms/Rating.svelte.d.ts +56 -0
  42. package/dist/atoms/Select.svelte +433 -91
  43. package/dist/atoms/Select.svelte.d.ts +49 -3
  44. package/dist/atoms/SelectionControl.svelte +94 -17
  45. package/dist/atoms/SelectionControl.svelte.d.ts +4 -3
  46. package/dist/atoms/Skeleton.svelte +1 -1
  47. package/dist/atoms/Slider.svelte +309 -0
  48. package/dist/atoms/Slider.svelte.d.ts +40 -0
  49. package/dist/atoms/Spinner.svelte +57 -0
  50. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  51. package/dist/atoms/Table.svelte +151 -6
  52. package/dist/atoms/Table.svelte.d.ts +15 -3
  53. package/dist/atoms/TemporalField.svelte +140 -0
  54. package/dist/atoms/TemporalField.svelte.d.ts +32 -0
  55. package/dist/atoms/Text.svelte +23 -11
  56. package/dist/atoms/Text.svelte.d.ts +6 -3
  57. package/dist/atoms/Textarea.svelte +55 -44
  58. package/dist/atoms/Textarea.svelte.d.ts +14 -2
  59. package/dist/atoms/ThemeToggle.svelte +223 -44
  60. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  61. package/dist/atoms/TimeField.svelte +108 -0
  62. package/dist/atoms/TimeField.svelte.d.ts +54 -0
  63. package/dist/atoms/Toast.svelte +17 -4
  64. package/dist/atoms/Toast.svelte.d.ts +2 -0
  65. package/dist/atoms/Toggle.svelte +41 -8
  66. package/dist/atoms/Toggle.svelte.d.ts +11 -2
  67. package/dist/atoms/Tooltip.svelte +508 -23
  68. package/dist/atoms/Tooltip.svelte.d.ts +11 -0
  69. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  70. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  71. package/dist/atoms/index.d.ts +17 -0
  72. package/dist/atoms/index.js +12 -0
  73. package/dist/atoms/selection-control.styles.d.ts +36 -5
  74. package/dist/atoms/selection-control.styles.js +40 -9
  75. package/dist/components/atoms/index.d.ts +17 -0
  76. package/dist/components/atoms/index.d.ts.map +1 -1
  77. package/dist/components/atoms/selection-control.styles.d.ts +36 -5
  78. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  79. package/dist/components/index.d.ts +80 -2
  80. package/dist/components/index.d.ts.map +1 -1
  81. package/dist/components/molecules/index.d.ts +46 -0
  82. package/dist/components/molecules/index.d.ts.map +1 -1
  83. package/dist/components/molecules/toast-store.d.ts +53 -7
  84. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  85. package/dist/components/organisms/index.d.ts +5 -0
  86. package/dist/components/organisms/index.d.ts.map +1 -1
  87. package/dist/components/types/variants.d.ts +15 -1
  88. package/dist/components/types/variants.d.ts.map +1 -1
  89. package/dist/components/util/app-shell.d.ts +28 -0
  90. package/dist/components/util/app-shell.d.ts.map +1 -0
  91. package/dist/components/util/avatar.d.ts +36 -0
  92. package/dist/components/util/avatar.d.ts.map +1 -0
  93. package/dist/components/util/bottom-sheet.d.ts +30 -0
  94. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  95. package/dist/components/util/bottom-tab-bar.d.ts +35 -0
  96. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  97. package/dist/components/util/calendar.d.ts +56 -0
  98. package/dist/components/util/calendar.d.ts.map +1 -0
  99. package/dist/components/util/collapsible.d.ts +58 -0
  100. package/dist/components/util/collapsible.d.ts.map +1 -0
  101. package/dist/components/util/confirm-dialog.d.ts +12 -0
  102. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  103. package/dist/components/util/date.d.ts +96 -0
  104. package/dist/components/util/date.d.ts.map +1 -0
  105. package/dist/components/util/drawer.d.ts +13 -0
  106. package/dist/components/util/drawer.d.ts.map +1 -0
  107. package/dist/components/util/dropdown.d.ts +47 -0
  108. package/dist/components/util/dropdown.d.ts.map +1 -0
  109. package/dist/components/util/field.d.ts +39 -0
  110. package/dist/components/util/field.d.ts.map +1 -0
  111. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  112. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  113. package/dist/components/util/focus-utils.d.ts +75 -2
  114. package/dist/components/util/focus-utils.d.ts.map +1 -1
  115. package/dist/components/util/hydration.d.ts +45 -0
  116. package/dist/components/util/hydration.d.ts.map +1 -0
  117. package/dist/components/util/keyboard-inset.d.ts +18 -0
  118. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  119. package/dist/components/util/media-grid.d.ts +51 -0
  120. package/dist/components/util/media-grid.d.ts.map +1 -0
  121. package/dist/components/util/media.d.ts +6 -0
  122. package/dist/components/util/media.d.ts.map +1 -0
  123. package/dist/components/util/overlay-chrome.d.ts +53 -0
  124. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  125. package/dist/components/util/overlay.d.ts +72 -0
  126. package/dist/components/util/overlay.d.ts.map +1 -0
  127. package/dist/components/util/photo.d.ts +215 -0
  128. package/dist/components/util/photo.d.ts.map +1 -0
  129. package/dist/components/util/portal.d.ts +23 -0
  130. package/dist/components/util/portal.d.ts.map +1 -0
  131. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  132. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  133. package/dist/components/util/radio-keys.d.ts +25 -0
  134. package/dist/components/util/radio-keys.d.ts.map +1 -0
  135. package/dist/components/util/rating.d.ts +23 -0
  136. package/dist/components/util/rating.d.ts.map +1 -0
  137. package/dist/components/util/ready-made-strings.d.ts +77 -0
  138. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  139. package/dist/components/util/scroll-padding.d.ts +20 -0
  140. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  141. package/dist/components/util/segmented-control.d.ts +16 -0
  142. package/dist/components/util/segmented-control.d.ts.map +1 -0
  143. package/dist/components/util/select.d.ts +41 -0
  144. package/dist/components/util/select.d.ts.map +1 -0
  145. package/dist/components/util/sheet-drag.d.ts +60 -0
  146. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  147. package/dist/components/util/sidebar.d.ts +63 -0
  148. package/dist/components/util/sidebar.d.ts.map +1 -0
  149. package/dist/components/util/sortable-list.d.ts +65 -0
  150. package/dist/components/util/sortable-list.d.ts.map +1 -0
  151. package/dist/components/util/stepper.d.ts +40 -0
  152. package/dist/components/util/stepper.d.ts.map +1 -0
  153. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  154. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  155. package/dist/components/util/theme-mode.d.ts +80 -0
  156. package/dist/components/util/theme-mode.d.ts.map +1 -0
  157. package/dist/components/util/toaster.d.ts +93 -0
  158. package/dist/components/util/toaster.d.ts.map +1 -0
  159. package/dist/components/util/tooltip.d.ts +10 -0
  160. package/dist/components/util/tooltip.d.ts.map +1 -0
  161. package/dist/components/util/top-navbar.d.ts +11 -0
  162. package/dist/components/util/top-navbar.d.ts.map +1 -0
  163. package/dist/components/util/touch-target.d.ts +31 -0
  164. package/dist/components/util/touch-target.d.ts.map +1 -0
  165. package/dist/components/util/zabi-strings.d.ts +133 -0
  166. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  167. package/dist/create-theme/cli.js +169 -0
  168. package/dist/create-theme/index.d.ts +176 -0
  169. package/dist/create-theme/index.js +817 -0
  170. package/dist/create-theme/lib/ramp-math.js +128 -0
  171. package/dist/create-theme/lib/resolve.js +141 -0
  172. package/dist/create-theme/theme-data.js +1116 -0
  173. package/dist/index.d.ts +80 -2
  174. package/dist/index.js +57 -2
  175. package/dist/molecules/Alert.svelte +33 -5
  176. package/dist/molecules/Alert.svelte.d.ts +2 -0
  177. package/dist/molecules/AppBar.svelte +415 -0
  178. package/dist/molecules/AppBar.svelte.d.ts +69 -0
  179. package/dist/molecules/AvatarGroup.svelte +116 -0
  180. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  181. package/dist/molecules/BottomSheet.svelte +540 -0
  182. package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
  183. package/dist/molecules/BottomTabBar.svelte +297 -0
  184. package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
  185. package/dist/molecules/Calendar.svelte +597 -0
  186. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  187. package/dist/molecules/Collapsible.svelte +243 -0
  188. package/dist/molecules/Collapsible.svelte.d.ts +44 -0
  189. package/dist/molecules/CollapsibleGroup.svelte +126 -0
  190. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  191. package/dist/molecules/ComponentDemo.svelte +16 -4
  192. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  193. package/dist/molecules/ConfirmDialog.svelte +253 -0
  194. package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
  195. package/dist/molecules/ContactForm.svelte +34 -17
  196. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  197. package/dist/molecules/Drawer.svelte +394 -0
  198. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  199. package/dist/molecules/Dropdown.svelte +509 -48
  200. package/dist/molecules/Dropdown.svelte.d.ts +45 -6
  201. package/dist/molecules/DropdownItem.svelte +122 -0
  202. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  203. package/dist/molecules/EmptyState.svelte +55 -10
  204. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  205. package/dist/molecules/FormField.svelte +9 -1
  206. package/dist/molecules/FormField.svelte.d.ts +2 -0
  207. package/dist/molecules/Header.svelte +1 -1
  208. package/dist/molecules/ImageUpload.svelte +383 -70
  209. package/dist/molecules/ImageUpload.svelte.d.ts +47 -2
  210. package/dist/molecules/MediaGrid.svelte +527 -0
  211. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  212. package/dist/molecules/Modal.svelte +288 -72
  213. package/dist/molecules/Modal.svelte.d.ts +51 -2
  214. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  215. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  216. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  217. package/dist/molecules/NavigationMenuList.svelte +10 -3
  218. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  219. package/dist/molecules/Page.svelte +32 -2
  220. package/dist/molecules/Page.svelte.d.ts +11 -0
  221. package/dist/molecules/PhotoGrid.svelte +433 -0
  222. package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
  223. package/dist/molecules/PhotoViewer.svelte +1116 -0
  224. package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
  225. package/dist/molecules/PropsTable.svelte +22 -10
  226. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  227. package/dist/molecules/PullToRefresh.svelte +263 -0
  228. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  229. package/dist/molecules/RadioGroup.svelte +52 -14
  230. package/dist/molecules/RadioGroup.svelte.d.ts +14 -3
  231. package/dist/molecules/Section.svelte +1 -1
  232. package/dist/molecules/SegmentedControl.svelte +323 -0
  233. package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
  234. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  235. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  236. package/dist/molecules/SidebarFooter.svelte +29 -6
  237. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  238. package/dist/molecules/SlideUp.svelte +220 -61
  239. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  240. package/dist/molecules/SortableList.svelte +632 -0
  241. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  242. package/dist/molecules/Stepper.svelte +548 -0
  243. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  244. package/dist/molecules/StickyActionBar.svelte +191 -0
  245. package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
  246. package/dist/molecules/SwipeableListItem.svelte +334 -0
  247. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  248. package/dist/molecules/Tabs.svelte +233 -9
  249. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  250. package/dist/molecules/Toaster.svelte +350 -5
  251. package/dist/molecules/Toaster.svelte.d.ts +95 -2
  252. package/dist/molecules/ToasterToast.svelte +253 -67
  253. package/dist/molecules/ToasterToast.svelte.d.ts +9 -0
  254. package/dist/molecules/UnsavedChangesBar.svelte +212 -0
  255. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  256. package/dist/molecules/index.d.ts +46 -0
  257. package/dist/molecules/index.js +27 -0
  258. package/dist/molecules/toast-store.d.ts +53 -7
  259. package/dist/molecules/toast-store.js +21 -0
  260. package/dist/organisms/AppShell.svelte +196 -0
  261. package/dist/organisms/AppShell.svelte.d.ts +61 -0
  262. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  263. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  264. package/dist/organisms/SidebarNavigation.svelte +96 -15
  265. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  266. package/dist/organisms/SidebarPanel.svelte +31 -10
  267. package/dist/organisms/SidebarShell.svelte +162 -11
  268. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  269. package/dist/organisms/TopNavbar.svelte +256 -31
  270. package/dist/organisms/TopNavbar.svelte.d.ts +26 -0
  271. package/dist/organisms/index.d.ts +5 -0
  272. package/dist/organisms/index.js +3 -0
  273. package/dist/types/components.d.ts +4 -0
  274. package/dist/types/components.d.ts.map +1 -1
  275. package/dist/types/index.d.ts +605 -25
  276. package/dist/types/index.d.ts.map +1 -1
  277. package/dist/types/variants.ts +17 -1
  278. package/dist/util/app-shell.d.ts +27 -0
  279. package/dist/util/app-shell.js +72 -0
  280. package/dist/util/avatar.d.ts +35 -0
  281. package/dist/util/avatar.js +52 -0
  282. package/dist/util/bottom-sheet.d.ts +29 -0
  283. package/dist/util/bottom-sheet.js +43 -0
  284. package/dist/util/bottom-tab-bar.d.ts +34 -0
  285. package/dist/util/bottom-tab-bar.js +54 -0
  286. package/dist/util/calendar.d.ts +55 -0
  287. package/dist/util/calendar.js +74 -0
  288. package/dist/util/collapsible.d.ts +57 -0
  289. package/dist/util/collapsible.js +23 -0
  290. package/dist/util/confirm-dialog.d.ts +11 -0
  291. package/dist/util/confirm-dialog.js +2 -0
  292. package/dist/util/date.d.ts +95 -0
  293. package/dist/util/date.js +237 -0
  294. package/dist/util/drawer.d.ts +12 -0
  295. package/dist/util/drawer.js +7 -0
  296. package/dist/util/dropdown.d.ts +46 -0
  297. package/dist/util/dropdown.js +21 -0
  298. package/dist/util/field.d.ts +38 -0
  299. package/dist/util/field.js +38 -0
  300. package/dist/util/fit-in-viewport.d.ts +200 -0
  301. package/dist/util/fit-in-viewport.js +399 -0
  302. package/dist/util/focus-utils.d.ts +75 -2
  303. package/dist/util/focus-utils.js +182 -9
  304. package/dist/util/hydration.d.ts +44 -0
  305. package/dist/util/hydration.js +58 -0
  306. package/dist/util/keyboard-inset.d.ts +17 -0
  307. package/dist/util/keyboard-inset.js +53 -0
  308. package/dist/util/media-grid.d.ts +50 -0
  309. package/dist/util/media-grid.js +81 -0
  310. package/dist/util/media.d.ts +5 -0
  311. package/dist/util/media.js +10 -0
  312. package/dist/util/overlay-chrome.d.ts +52 -0
  313. package/dist/util/overlay-chrome.js +56 -0
  314. package/dist/util/overlay.d.ts +71 -0
  315. package/dist/util/overlay.js +240 -0
  316. package/dist/util/photo.d.ts +214 -0
  317. package/dist/util/photo.js +206 -0
  318. package/dist/util/portal.d.ts +22 -0
  319. package/dist/util/portal.js +50 -0
  320. package/dist/util/pull-to-refresh.d.ts +26 -0
  321. package/dist/util/pull-to-refresh.js +30 -0
  322. package/dist/util/radio-keys.d.ts +24 -0
  323. package/dist/util/radio-keys.js +49 -0
  324. package/dist/util/rating.d.ts +22 -0
  325. package/dist/util/rating.js +26 -0
  326. package/dist/util/ready-made-strings.d.ts +76 -0
  327. package/dist/util/ready-made-strings.js +67 -0
  328. package/dist/util/scroll-padding.d.ts +19 -0
  329. package/dist/util/scroll-padding.js +44 -0
  330. package/dist/util/segmented-control.d.ts +15 -0
  331. package/dist/util/segmented-control.js +1 -0
  332. package/dist/util/select.d.ts +40 -0
  333. package/dist/util/select.js +63 -0
  334. package/dist/util/sheet-drag.d.ts +59 -0
  335. package/dist/util/sheet-drag.js +241 -0
  336. package/dist/util/sidebar.d.ts +62 -0
  337. package/dist/util/sidebar.js +38 -0
  338. package/dist/util/sortable-list.d.ts +64 -0
  339. package/dist/util/sortable-list.js +53 -0
  340. package/dist/util/stepper.d.ts +39 -0
  341. package/dist/util/stepper.js +38 -0
  342. package/dist/util/swipeable-list-item.d.ts +35 -0
  343. package/dist/util/swipeable-list-item.js +32 -0
  344. package/dist/util/theme-mode.d.ts +79 -0
  345. package/dist/util/theme-mode.js +121 -0
  346. package/dist/util/toaster.d.ts +92 -0
  347. package/dist/util/toaster.js +75 -0
  348. package/dist/util/tooltip.d.ts +9 -0
  349. package/dist/util/tooltip.js +18 -0
  350. package/dist/util/top-navbar.d.ts +10 -0
  351. package/dist/util/top-navbar.js +5 -0
  352. package/dist/util/touch-target.d.ts +30 -0
  353. package/dist/util/touch-target.js +30 -0
  354. package/dist/util/zabi-strings.d.ts +132 -0
  355. package/dist/util/zabi-strings.js +105 -0
  356. package/dist/zabi-components-colors.css +5 -2
  357. package/dist/zabi-components-theme-dark-only.css +543 -241
  358. package/dist/zabi-components-theme-dark.css +543 -241
  359. package/dist/zabi-components-theme-only.css +1584 -367
  360. package/dist/zabi-components-theme.css +1584 -367
  361. package/dist/zabi-components.css +3885 -518
  362. package/docs/lucide-icons.md +40 -17
  363. package/docs/theme-imports.md +81 -11
  364. package/package.json +35 -8
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * zabi-theme — the command line over createTheme.
4
+ *
5
+ * npx zabi-theme --brand "#0026EA" --out src/lib/brand.generated.css
6
+ *
7
+ * The stylesheet goes to --out, or to stdout. Everything else (warnings, the
8
+ * one-line summary) goes to stderr, so `zabi-theme … > file.css` stays clean.
9
+ *
10
+ * Exit codes: 0 done · 1 --strict and a role pair is below WCAG AA · 2 bad usage.
11
+ */
12
+
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { createTheme } from './index.js';
16
+
17
+ const HELP = `zabi-theme: generate brand tokens for zabi-components
18
+
19
+ Usage
20
+ zabi-theme --brand <hex> [--pin] [--accent <hex>] [--pin-accent] [--neutral <hex>]
21
+ [--neutral-chroma <n>]
22
+ [--out <file>] [--strict] [--set <token>=<value> ...]
23
+ [--set-light <token>=<value> ...] [--set-dark <token>=<value> ...]
24
+
25
+ Options
26
+ --brand <hex> Brand colour (required). Builds --zabi-brand-50 … 950.
27
+ --pin Put the exact brand colour on primary buttons (see below).
28
+ --accent <hex> Second brand colour. Builds --zabi-accent-50 … 950.
29
+ --pin-accent The same for the solid accent fill. Needs --accent.
30
+ --neutral <hex> Tints the 21-step neutral ramp, --zabi-base-50 … 950.
31
+ --neutral-chroma <n>
32
+ Chroma at the neutral ramp's peak, 0 to 0.1 (OKLCH). Default: the
33
+ hue of --neutral at most 0.03. Use 0.04 to 0.06 for a clearly
34
+ tinted neutral. Needs --neutral. With it, the ink roles (secondary
35
+ button, hover and pressed tints, overlay edge, shadow colour)
36
+ follow the neutral ramp instead of staying grey.
37
+ --out <file> Write the CSS here. Without it the CSS goes to stdout.
38
+ --strict Exit 1 when any role pair is below WCAG AA.
39
+ --set <t>=<v> Also write this declaration, e.g. --set "--color-link=var(--color-brand-800)".
40
+ Repeatable. It applies in light and dark and is contrast-checked.
41
+ --set-light <t>=<v>
42
+ The same, for light only. Dark keeps the library's own value for that
43
+ token: the file restates it under the dark selectors. Repeatable.
44
+ --set-dark <t>=<v>
45
+ The same, for dark only. Repeatable. A token given to --set and to
46
+ --set-light or --set-dark: the mode-specific value wins in its mode.
47
+ --help Show this text.
48
+
49
+ Each ramp is built on the library's lightness curve. Your colour supplies hue
50
+ and chroma and is not pinned to a step, so the exact hex may not appear in the
51
+ file; the header says which step is closest.
52
+
53
+ With --pin the ramp is the same, and the primary action is the exact colour in
54
+ light: the fill, its hover and pressed states, and where they still pass, the
55
+ focus ring and links. The label is white or the ramp's dark end, whichever
56
+ reaches 4.5:1. Dark takes the colour only if it passes there; otherwise dark
57
+ keeps the ramp, and the header says so. A pair that fails is reported, and
58
+ the colour is never moved to make it pass.
59
+
60
+ Import the file after the theme:
61
+ @import "zabi-components/theme-only";
62
+ @import "zabi-components/theme-dark-only";
63
+ @import "./brand.generated.css";
64
+ `;
65
+
66
+ const VALUE_OPTIONS = ['brand', 'accent', 'neutral', 'neutral-chroma', 'out'];
67
+ const FLAG_OPTIONS = ['strict', 'help', 'pin', 'pin-accent'];
68
+
69
+ function usageError(message) {
70
+ process.stderr.write(`zabi-theme: ${message}\nRun "zabi-theme --help" for usage.\n`);
71
+ process.exit(2);
72
+ }
73
+
74
+ function parseArgs(argv) {
75
+ const options = {};
76
+ for (let i = 0; i < argv.length; i += 1) {
77
+ const arg = argv[i];
78
+ if (arg === '-h') { options.help = true; continue; }
79
+ if (!arg.startsWith('--')) usageError(`unexpected argument "${arg}"`);
80
+ const [name, inline] = arg.slice(2).split(/=(.*)/s, 2);
81
+ if (name === 'set' || name === 'set-light' || name === 'set-dark') {
82
+ // The value is itself a custom property name, so it starts with "--".
83
+ const pair = inline !== undefined ? inline : argv[(i += 1)];
84
+ const match = /^(--[\w-]+)\s*[=:]\s*(.+)$/s.exec(pair ?? '');
85
+ if (!match) usageError(`--${name} needs <token>=<value>, for example --${name} "--color-link=var(--color-brand-800)"`);
86
+ // `--set` alone stays the flat map; a mode flag adds its own map.
87
+ const key = name === 'set' ? 'both' : name === 'set-light' ? 'light' : 'dark';
88
+ options.modes = { ...options.modes, [key]: { ...options.modes?.[key], [match[1]]: match[2].trim() } };
89
+ } else if (FLAG_OPTIONS.includes(name)) {
90
+ if (inline !== undefined) usageError(`--${name} takes no value`);
91
+ options[name] = true;
92
+ } else if (VALUE_OPTIONS.includes(name)) {
93
+ const value = inline !== undefined ? inline : argv[(i += 1)];
94
+ if (value === undefined || value === '' || (inline === undefined && value.startsWith('--'))) {
95
+ usageError(`--${name} needs a value`);
96
+ }
97
+ options[name] = value;
98
+ } else {
99
+ usageError(`unknown option "--${name}"`);
100
+ }
101
+ }
102
+ return options;
103
+ }
104
+
105
+ function parseChroma(text) {
106
+ const value = Number(text);
107
+ if (text.trim() === '' || !Number.isFinite(value)) usageError(`--neutral-chroma must be a number such as 0.05 (got "${text}")`);
108
+ return value;
109
+ }
110
+
111
+ const options = parseArgs(process.argv.slice(2));
112
+
113
+ if (options.help) {
114
+ process.stdout.write(HELP);
115
+ process.exit(0);
116
+ }
117
+ if (!options.brand) usageError('--brand is required, for example --brand "#0026EA"');
118
+
119
+ let result;
120
+ try {
121
+ result = createTheme({
122
+ brand: options.brand,
123
+ accent: options.accent,
124
+ neutral: options.neutral,
125
+ // Left out when not asked for, so the options are what they always were.
126
+ ...(options['neutral-chroma'] !== undefined ? { neutralChroma: parseChroma(options['neutral-chroma']) } : {}),
127
+ // `--set` alone is the flat map, as it always was; a mode flag makes it { light, dark, both }.
128
+ overrides: options.modes && (options.modes.light || options.modes.dark) ? options.modes : options.modes?.both,
129
+ // Left out when not asked for, so the options are what they always were.
130
+ ...(options.pin || options['pin-accent'] ? { pin: { brand: !!options.pin, accent: !!options['pin-accent'] } } : {}),
131
+ });
132
+ } catch (error) {
133
+ if (error instanceof TypeError) usageError(error.message.replace(/^zabi-theme: /, ''));
134
+ throw error;
135
+ }
136
+
137
+ if (options.out) {
138
+ const target = path.resolve(options.out);
139
+ try {
140
+ fs.mkdirSync(path.dirname(target), { recursive: true });
141
+ fs.writeFileSync(target, result.css, 'utf8');
142
+ } catch (error) {
143
+ usageError(`could not write ${options.out}: ${error.message}`);
144
+ }
145
+ } else {
146
+ process.stdout.write(result.css);
147
+ }
148
+
149
+ const contrast = result.warnings.filter((warning) => warning.type === 'contrast');
150
+ for (const warning of result.warnings) {
151
+ process.stderr.write(`zabi-theme: warning: ${warning.message}\n`);
152
+ }
153
+ const closest = Object.entries(result.closest)
154
+ .map(([name, c]) => `${name} closest to step ${c.step}`)
155
+ .join(', ');
156
+ for (const [name, report] of Object.entries(result.pinned ?? {})) {
157
+ process.stderr.write(
158
+ `zabi-theme: ${name} ${report.hex} is pinned in light` +
159
+ (report.dark.pinned ? ' and in dark.\n' : '; dark keeps the ramp, because the colour fails there.\n'),
160
+ );
161
+ }
162
+ process.stderr.write(
163
+ `zabi-theme: ${options.out ? `wrote ${options.out}` : 'done'} (${closest}); ` +
164
+ (contrast.length === 0
165
+ ? 'every role pair passes WCAG AA in light and dark.\n'
166
+ : `${contrast.length} role pair${contrast.length === 1 ? '' : 's'} below WCAG AA.\n`),
167
+ );
168
+
169
+ if (options.strict && contrast.length > 0) process.exit(1);
@@ -0,0 +1,176 @@
1
+ /** A hex colour: `#rgb` or `#rrggbb`, with or without the `#`. */
2
+ export type HexColor = string;
3
+
4
+ /** A map of custom property names to values. */
5
+ export type TokenMap = Record<string, string>;
6
+
7
+ /**
8
+ * Either one map for both modes, or `light`, `dark` and `both` maps. The two
9
+ * forms may be mixed: a `--token` key beside them counts as `both`.
10
+ */
11
+ export type Overrides = TokenMap | ({ light?: TokenMap; dark?: TokenMap; both?: TokenMap } & { [token: `--${string}`]: string });
12
+
13
+ export interface CreateThemeOptions {
14
+ /**
15
+ * The brand colour. Supplies hue and chroma for `--zabi-brand-50 … 950`:
16
+ * primary actions, focus rings, links and brand tints.
17
+ */
18
+ brand: HexColor;
19
+ /** Second brand colour, for `--zabi-accent-50 … 950`. Omitted: the library's citron. */
20
+ accent?: HexColor;
21
+ /**
22
+ * Tints the neutral ramp, `--zabi-base-50 … 950` (21 steps), toward this
23
+ * hue: text, borders, the page, cards and the dark surface levels. Only the
24
+ * hue and a little chroma are used. Omitted: the library's greys.
25
+ */
26
+ neutral?: HexColor;
27
+ /**
28
+ * The OKLCH chroma at the neutral ramp's peak, `0` to `0.1`. Needs
29
+ * `neutral`, which still supplies the hue. Omitted: the ramp takes the hue
30
+ * of `neutral` at no more than 0.03 chroma, so it stays a tinted grey. For a
31
+ * clearly tinted neutral, such as a blue-slate UI, give 0.04 to 0.06. The
32
+ * profile across the 21 steps is the same; only its peak moves.
33
+ *
34
+ * With it, the translucent ink roles follow the neutral ramp instead of
35
+ * staying grey: `--color-action-secondary` (and `-hover`, `-active`),
36
+ * `--color-surface-hover`, `--color-surface-active` and
37
+ * `--color-border-overlay` become `color-mix()` of `--zabi-base-900` (light)
38
+ * or `--zabi-base-50` (dark) at their usual alphas, and `--shadow-color` is
39
+ * the ramp's step 900 in light. `--color-overlay` (the modal scrim) stays
40
+ * black. Dark values are written under the dark selectors, as with `pin`.
41
+ * Omitted: the output is exactly what it is without the option.
42
+ *
43
+ * The contrast check does not evaluate these translucent roles, with or
44
+ * without the option.
45
+ */
46
+ neutralChroma?: number;
47
+ /**
48
+ * Extra declarations written after the ramps, for example
49
+ * `{ "--color-link": "var(--color-brand-800)" }`. They apply in light and in
50
+ * dark, and take part in the contrast check and in the choice of the "on"
51
+ * colours.
52
+ *
53
+ * Or per mode: `{ light, dark, both }`, each a map of the same shape.
54
+ * `both` is the flat form (flat keys beside the three also count as `both`).
55
+ * A token in `light` goes on `:root`, and the file restates the library's
56
+ * own dark value for it under the dark selectors, so dark does not move; a
57
+ * token in `dark` is written only under the dark selectors. A token set in
58
+ * `both` and in a mode takes the mode's value in that mode. The contrast
59
+ * check uses each mode's own values. A token the library defines no dark
60
+ * value for (or one you invented) cannot be put back in dark: it stays what
61
+ * `light` says there unless you also set it in `dark`.
62
+ *
63
+ * @example
64
+ * overrides: { light: { "--color-surface-raised": "#f8faff" } }
65
+ */
66
+ overrides?: Overrides;
67
+ /**
68
+ * Put the exact colour on the solid fill, where the ramp would use its
69
+ * step 600. `true` pins the brand: in light, `--color-action-primary` is
70
+ * the hex you gave, with hover and pressed states derived from it and a
71
+ * label (white, or the ramp's dark end) that reaches 4.5:1 on all three.
72
+ * The focus ring and links take the colour too where every guarded pair
73
+ * still passes, and stay on the ramp where it would not. The ramp itself,
74
+ * and so every tint and border, is unchanged.
75
+ *
76
+ * Dark takes the colour only if nothing guarded fails with it; otherwise
77
+ * dark keeps the mirrored ramp step, as it does without `pin`.
78
+ *
79
+ * A pair that fails with the pinned colour is reported in `warnings`. The
80
+ * colour is never moved to make it pass.
81
+ *
82
+ * `{ accent: true }` does the same for `--color-accent` and needs `accent`.
83
+ * Omitted or `false`: the output is exactly what it is without the option.
84
+ */
85
+ pin?: boolean | { brand?: boolean; accent?: boolean };
86
+ }
87
+
88
+ /** A role pair below WCAG AA with the generated ramps in place. */
89
+ export interface ContrastWarning {
90
+ type: 'contrast';
91
+ /** The pair, as the library's contrast guard names it, e.g. `"link on page"`. */
92
+ pair: string;
93
+ mode: 'light' | 'dark';
94
+ /** Measured ratio, to two decimals. */
95
+ ratio: number;
96
+ /** 4.5 for text, 3 for UI parts and focus rings. */
97
+ required: number;
98
+ foreground: { token: string; value: string };
99
+ background: { token: string; value: string };
100
+ message: string;
101
+ }
102
+
103
+ /** An input that will not give the ramp its author probably expects. */
104
+ /** What `pin` did with one colour. */
105
+ export interface PinReport {
106
+ /** The pinned colour, as `#rrggbb`. */
107
+ hex: string;
108
+ light: {
109
+ /** Role groups that took the colour besides the fill: `"focus ring"`, `"link"`, `"accent text"`. */
110
+ followed: string[];
111
+ /** Role groups left on the ramp, because the colour fails a guarded pair there. */
112
+ onRamp: string[];
113
+ /** Which way hover and pressed go from the fill. */
114
+ states: 'darker' | 'lighter';
115
+ };
116
+ dark: {
117
+ /** False when the colour fails in dark and dark keeps the ramp. */
118
+ pinned: boolean;
119
+ followed: string[];
120
+ onRamp: string[];
121
+ states?: 'darker' | 'lighter';
122
+ /** When not pinned: the pairs that would fail with the colour. */
123
+ failed?: string[];
124
+ };
125
+ }
126
+
127
+ export interface InputWarning {
128
+ type: 'input';
129
+ option: 'brand' | 'accent' | 'neutral';
130
+ message: string;
131
+ }
132
+
133
+ export type ThemeWarning = ContrastWarning | InputWarning;
134
+
135
+ /** Where an input colour landed in the ramp built from it. */
136
+ export interface ClosestStep {
137
+ step: number;
138
+ hex: string;
139
+ /** CIELAB ΔE*ab between the input and that step, to one decimal. */
140
+ deltaE: number;
141
+ exact: boolean;
142
+ }
143
+
144
+ export interface CreateThemeResult {
145
+ /**
146
+ * The stylesheet: a header comment and one `:root { … }` rule. With `pin`,
147
+ * `neutralChroma` or `light` / `dark` overrides, two more rules follow, with the dark values of those roles:
148
+ * `.dark, [data-theme="dark"] { … }` and the same declarations for
149
+ * `[data-theme="auto"]` inside `@media (prefers-color-scheme: dark)`.
150
+ */
151
+ css: string;
152
+ /** Every declaration of the `:root` rule, name to value, in output order. */
153
+ tokens: Record<string, string>;
154
+ /** Only with `pin`, `neutralChroma` or `light` / `dark` overrides: every declaration of the dark rules. */
155
+ darkTokens?: Record<string, string>;
156
+ /** Only with `pin`: what was pinned and which roles took the colour. */
157
+ pinned?: { brand?: PinReport; accent?: PinReport };
158
+ /** Contrast failures and input notes. Empty when everything passes. */
159
+ warnings: ThemeWarning[];
160
+ closest: { brand: ClosestStep; accent?: ClosestStep; neutral?: ClosestStep };
161
+ }
162
+
163
+ /**
164
+ * Builds the ramps for a brand on the library's lightness curve and checks
165
+ * every guarded role pair in light and dark. Deterministic: the same options
166
+ * give the same bytes.
167
+ *
168
+ * @throws {TypeError} when `brand` is missing, a colour is not a hex value, or `neutralChroma` is not a number from 0 to 0.1 or is given without `neutral`.
169
+ */
170
+ export function createTheme(options: CreateThemeOptions): CreateThemeResult;
171
+ export default createTheme;
172
+
173
+ /** The steps of a chromatic ramp: 50, 100 … 900, 950. */
174
+ export const RAMP_STEPS: number[];
175
+ /** The 21 steps of the neutral ramp, half steps included. */
176
+ export const BASE_STEPS: number[];