zabi-components 8.0.0 → 9.0.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 (456) hide show
  1. package/CHANGELOG.md +2935 -0
  2. package/README.md +243 -10
  3. package/THEME.md +162 -332
  4. package/THEMING.md +1419 -259
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +120 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +46 -0
  8. package/dist/atoms/Badge.svelte +71 -13
  9. package/dist/atoms/Badge.svelte.d.ts +8 -2
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +217 -30
  13. package/dist/atoms/Button.svelte.d.ts +33 -4
  14. package/dist/atoms/Card.svelte +182 -26
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/CardHeader.svelte +16 -4
  17. package/dist/atoms/Checkbox.svelte +81 -21
  18. package/dist/atoms/Checkbox.svelte.d.ts +31 -2
  19. package/dist/atoms/Chip.svelte +351 -0
  20. package/dist/atoms/Chip.svelte.d.ts +57 -0
  21. package/dist/atoms/CodeBlock.svelte +20 -2
  22. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  23. package/dist/atoms/CodeField.svelte +385 -0
  24. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  25. package/dist/atoms/ColorPicker.svelte +260 -49
  26. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  27. package/dist/atoms/Container.svelte +16 -8
  28. package/dist/atoms/DateField.svelte +145 -0
  29. package/dist/atoms/DateField.svelte.d.ts +81 -0
  30. package/dist/atoms/FieldMessages.svelte +69 -0
  31. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  32. package/dist/atoms/FloatingActionButton.svelte +176 -0
  33. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  34. package/dist/atoms/Heading.svelte +63 -5
  35. package/dist/atoms/Heading.svelte.d.ts +14 -0
  36. package/dist/atoms/IconButton.svelte +294 -26
  37. package/dist/atoms/IconButton.svelte.d.ts +62 -4
  38. package/dist/atoms/Input.svelte +259 -53
  39. package/dist/atoms/Input.svelte.d.ts +33 -2
  40. package/dist/atoms/List.svelte +5 -1
  41. package/dist/atoms/ListItem.svelte +70 -13
  42. package/dist/atoms/ListItem.svelte.d.ts +11 -0
  43. package/dist/atoms/PickerField.svelte +239 -0
  44. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  45. package/dist/atoms/Progress.svelte +99 -19
  46. package/dist/atoms/Progress.svelte.d.ts +15 -1
  47. package/dist/atoms/Radio.svelte +51 -3
  48. package/dist/atoms/Radio.svelte.d.ts +30 -2
  49. package/dist/atoms/Rating.svelte +620 -0
  50. package/dist/atoms/Rating.svelte.d.ts +77 -0
  51. package/dist/atoms/Select.svelte +438 -98
  52. package/dist/atoms/Select.svelte.d.ts +61 -3
  53. package/dist/atoms/SelectionControl.svelte +193 -23
  54. package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
  55. package/dist/atoms/Skeleton.svelte +114 -18
  56. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  57. package/dist/atoms/SkeletonGroup.svelte +43 -0
  58. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  59. package/dist/atoms/Slider.svelte +344 -0
  60. package/dist/atoms/Slider.svelte.d.ts +40 -0
  61. package/dist/atoms/Sparkline.svelte +109 -0
  62. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  63. package/dist/atoms/SpeechBubble.svelte +187 -0
  64. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  65. package/dist/atoms/Spinner.svelte +57 -0
  66. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  67. package/dist/atoms/Stat.svelte +72 -0
  68. package/dist/atoms/Stat.svelte.d.ts +19 -0
  69. package/dist/atoms/Table.svelte +151 -6
  70. package/dist/atoms/Table.svelte.d.ts +15 -3
  71. package/dist/atoms/TemporalField.svelte +472 -0
  72. package/dist/atoms/TemporalField.svelte.d.ts +48 -0
  73. package/dist/atoms/Text.svelte +23 -11
  74. package/dist/atoms/Text.svelte.d.ts +6 -3
  75. package/dist/atoms/Textarea.svelte +150 -44
  76. package/dist/atoms/Textarea.svelte.d.ts +35 -2
  77. package/dist/atoms/ThemeToggle.svelte +223 -44
  78. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  79. package/dist/atoms/TimeColumn.svelte +150 -0
  80. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  81. package/dist/atoms/TimeField.svelte +151 -0
  82. package/dist/atoms/TimeField.svelte.d.ts +85 -0
  83. package/dist/atoms/Toast.svelte +22 -8
  84. package/dist/atoms/Toast.svelte.d.ts +2 -0
  85. package/dist/atoms/Toggle.svelte +98 -14
  86. package/dist/atoms/Toggle.svelte.d.ts +26 -2
  87. package/dist/atoms/Tooltip.svelte +528 -24
  88. package/dist/atoms/Tooltip.svelte.d.ts +18 -0
  89. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  90. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  91. package/dist/atoms/index.d.ts +38 -0
  92. package/dist/atoms/index.js +29 -0
  93. package/dist/atoms/selection-control.styles.d.ts +47 -5
  94. package/dist/atoms/selection-control.styles.js +52 -10
  95. package/dist/components/atoms/index.d.ts +38 -0
  96. package/dist/components/atoms/index.d.ts.map +1 -1
  97. package/dist/components/atoms/selection-control.styles.d.ts +47 -5
  98. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  99. package/dist/components/index.d.ts +104 -2
  100. package/dist/components/index.d.ts.map +1 -1
  101. package/dist/components/map.d.ts +11 -0
  102. package/dist/components/map.d.ts.map +1 -0
  103. package/dist/components/molecules/index.d.ts +51 -0
  104. package/dist/components/molecules/index.d.ts.map +1 -1
  105. package/dist/components/molecules/toast-store.d.ts +83 -7
  106. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  107. package/dist/components/organisms/index.d.ts +10 -0
  108. package/dist/components/organisms/index.d.ts.map +1 -1
  109. package/dist/components/types/variants.d.ts +24 -1
  110. package/dist/components/types/variants.d.ts.map +1 -1
  111. package/dist/components/util/app-shell.d.ts +79 -0
  112. package/dist/components/util/app-shell.d.ts.map +1 -0
  113. package/dist/components/util/avatar.d.ts +39 -0
  114. package/dist/components/util/avatar.d.ts.map +1 -0
  115. package/dist/components/util/bottom-sheet.d.ts +30 -0
  116. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  117. package/dist/components/util/bottom-tab-bar.d.ts +41 -0
  118. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  119. package/dist/components/util/calendar.d.ts +56 -0
  120. package/dist/components/util/calendar.d.ts.map +1 -0
  121. package/dist/components/util/chart.d.ts +140 -0
  122. package/dist/components/util/chart.d.ts.map +1 -0
  123. package/dist/components/util/chip.d.ts +66 -0
  124. package/dist/components/util/chip.d.ts.map +1 -0
  125. package/dist/components/util/cn.d.ts.map +1 -1
  126. package/dist/components/util/code-field.d.ts +63 -0
  127. package/dist/components/util/code-field.d.ts.map +1 -0
  128. package/dist/components/util/collapsible.d.ts +64 -0
  129. package/dist/components/util/collapsible.d.ts.map +1 -0
  130. package/dist/components/util/confirm-dialog.d.ts +12 -0
  131. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  132. package/dist/components/util/date.d.ts +96 -0
  133. package/dist/components/util/date.d.ts.map +1 -0
  134. package/dist/components/util/display-voice.d.ts +10 -0
  135. package/dist/components/util/display-voice.d.ts.map +1 -0
  136. package/dist/components/util/drawer.d.ts +13 -0
  137. package/dist/components/util/drawer.d.ts.map +1 -0
  138. package/dist/components/util/dropdown.d.ts +47 -0
  139. package/dist/components/util/dropdown.d.ts.map +1 -0
  140. package/dist/components/util/field-control.d.ts +30 -0
  141. package/dist/components/util/field-control.d.ts.map +1 -0
  142. package/dist/components/util/field.d.ts +45 -0
  143. package/dist/components/util/field.d.ts.map +1 -0
  144. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  145. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  146. package/dist/components/util/focus-utils.d.ts +77 -2
  147. package/dist/components/util/focus-utils.d.ts.map +1 -1
  148. package/dist/components/util/hydration.d.ts +45 -0
  149. package/dist/components/util/hydration.d.ts.map +1 -0
  150. package/dist/components/util/keyboard-inset.d.ts +18 -0
  151. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  152. package/dist/components/util/map-view.d.ts +144 -0
  153. package/dist/components/util/map-view.d.ts.map +1 -0
  154. package/dist/components/util/media-grid.d.ts +51 -0
  155. package/dist/components/util/media-grid.d.ts.map +1 -0
  156. package/dist/components/util/media.d.ts +6 -0
  157. package/dist/components/util/media.d.ts.map +1 -0
  158. package/dist/components/util/overlay-chrome.d.ts +53 -0
  159. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  160. package/dist/components/util/overlay.d.ts +72 -0
  161. package/dist/components/util/overlay.d.ts.map +1 -0
  162. package/dist/components/util/photo.d.ts +234 -0
  163. package/dist/components/util/photo.d.ts.map +1 -0
  164. package/dist/components/util/portal.d.ts +23 -0
  165. package/dist/components/util/portal.d.ts.map +1 -0
  166. package/dist/components/util/progress.d.ts +19 -0
  167. package/dist/components/util/progress.d.ts.map +1 -0
  168. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  169. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  170. package/dist/components/util/radio-keys.d.ts +25 -0
  171. package/dist/components/util/radio-keys.d.ts.map +1 -0
  172. package/dist/components/util/rating.d.ts +23 -0
  173. package/dist/components/util/rating.d.ts.map +1 -0
  174. package/dist/components/util/ready-made-strings.d.ts +77 -0
  175. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  176. package/dist/components/util/scroll-edge.d.ts +33 -0
  177. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  178. package/dist/components/util/scroll-padding.d.ts +20 -0
  179. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  180. package/dist/components/util/segmented-control.d.ts +16 -0
  181. package/dist/components/util/segmented-control.d.ts.map +1 -0
  182. package/dist/components/util/select.d.ts +41 -0
  183. package/dist/components/util/select.d.ts.map +1 -0
  184. package/dist/components/util/sheet-drag.d.ts +84 -0
  185. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  186. package/dist/components/util/sidebar.d.ts +63 -0
  187. package/dist/components/util/sidebar.d.ts.map +1 -0
  188. package/dist/components/util/skeleton.d.ts +15 -0
  189. package/dist/components/util/skeleton.d.ts.map +1 -0
  190. package/dist/components/util/sliding-indicator.d.ts +25 -0
  191. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  192. package/dist/components/util/sortable-list.d.ts +65 -0
  193. package/dist/components/util/sortable-list.d.ts.map +1 -0
  194. package/dist/components/util/stepper.d.ts +40 -0
  195. package/dist/components/util/stepper.d.ts.map +1 -0
  196. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  197. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  198. package/dist/components/util/temporal-field.d.ts +89 -0
  199. package/dist/components/util/temporal-field.d.ts.map +1 -0
  200. package/dist/components/util/theme-mode.d.ts +80 -0
  201. package/dist/components/util/theme-mode.d.ts.map +1 -0
  202. package/dist/components/util/toaster.d.ts +133 -0
  203. package/dist/components/util/toaster.d.ts.map +1 -0
  204. package/dist/components/util/tone-surface.d.ts +34 -0
  205. package/dist/components/util/tone-surface.d.ts.map +1 -0
  206. package/dist/components/util/tooltip.d.ts +10 -0
  207. package/dist/components/util/tooltip.d.ts.map +1 -0
  208. package/dist/components/util/top-navbar.d.ts +35 -0
  209. package/dist/components/util/top-navbar.d.ts.map +1 -0
  210. package/dist/components/util/touch-target.d.ts +31 -0
  211. package/dist/components/util/touch-target.d.ts.map +1 -0
  212. package/dist/components/util/zabi-strings.d.ts +142 -0
  213. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  214. package/dist/create-theme/cli.js +173 -0
  215. package/dist/create-theme/index.d.ts +190 -0
  216. package/dist/create-theme/index.js +1034 -0
  217. package/dist/create-theme/lib/ramp-math.js +128 -0
  218. package/dist/create-theme/lib/resolve.js +238 -0
  219. package/dist/create-theme/theme-data.js +2644 -0
  220. package/dist/index.d.ts +104 -2
  221. package/dist/index.js +75 -2
  222. package/dist/map.d.ts +10 -0
  223. package/dist/map.js +9 -0
  224. package/dist/molecules/Alert.svelte +63 -16
  225. package/dist/molecules/Alert.svelte.d.ts +14 -1
  226. package/dist/molecules/AppBar.svelte +674 -0
  227. package/dist/molecules/AppBar.svelte.d.ts +131 -0
  228. package/dist/molecules/AvatarGroup.svelte +116 -0
  229. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  230. package/dist/molecules/BarChart.svelte +381 -0
  231. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  232. package/dist/molecules/BottomSheet.svelte +608 -0
  233. package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
  234. package/dist/molecules/BottomTabBar.svelte +403 -0
  235. package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
  236. package/dist/molecules/Calendar.svelte +597 -0
  237. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  238. package/dist/molecules/ChipGroup.svelte +325 -0
  239. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  240. package/dist/molecules/Collapsible.svelte +347 -0
  241. package/dist/molecules/Collapsible.svelte.d.ts +61 -0
  242. package/dist/molecules/CollapsibleGroup.svelte +136 -0
  243. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  244. package/dist/molecules/ComponentDemo.svelte +16 -4
  245. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  246. package/dist/molecules/ConfirmDialog.svelte +257 -0
  247. package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
  248. package/dist/molecules/ContactForm.svelte +34 -17
  249. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  250. package/dist/molecules/Drawer.svelte +415 -0
  251. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  252. package/dist/molecules/Dropdown.svelte +517 -48
  253. package/dist/molecules/Dropdown.svelte.d.ts +47 -6
  254. package/dist/molecules/DropdownItem.svelte +122 -0
  255. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  256. package/dist/molecules/EmptyState.svelte +55 -10
  257. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  258. package/dist/molecules/FormField.svelte +9 -1
  259. package/dist/molecules/FormField.svelte.d.ts +2 -0
  260. package/dist/molecules/Header.svelte +1 -1
  261. package/dist/molecules/ImageUpload.svelte +492 -69
  262. package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
  263. package/dist/molecules/LineChart.svelte +459 -0
  264. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  265. package/dist/molecules/MapView.svelte +565 -0
  266. package/dist/molecules/MapView.svelte.d.ts +88 -0
  267. package/dist/molecules/MediaGrid.svelte +527 -0
  268. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  269. package/dist/molecules/Modal.svelte +293 -72
  270. package/dist/molecules/Modal.svelte.d.ts +51 -2
  271. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  272. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  273. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  274. package/dist/molecules/NavigationMenuList.svelte +10 -3
  275. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  276. package/dist/molecules/Page.svelte +32 -2
  277. package/dist/molecules/Page.svelte.d.ts +11 -0
  278. package/dist/molecules/PhotoGrid.svelte +523 -0
  279. package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
  280. package/dist/molecules/PhotoViewer.svelte +1213 -0
  281. package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
  282. package/dist/molecules/PieChart.svelte +421 -0
  283. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  284. package/dist/molecules/PropsTable.svelte +22 -10
  285. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  286. package/dist/molecules/PullToRefresh.svelte +263 -0
  287. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  288. package/dist/molecules/RadioGroup.svelte +72 -18
  289. package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
  290. package/dist/molecules/Section.svelte +1 -1
  291. package/dist/molecules/SegmentedControl.svelte +456 -0
  292. package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
  293. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  294. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  295. package/dist/molecules/SidebarFooter.svelte +30 -7
  296. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  297. package/dist/molecules/SidebarNavSection.svelte +1 -1
  298. package/dist/molecules/SlideUp.svelte +220 -61
  299. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  300. package/dist/molecules/SortableList.svelte +633 -0
  301. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  302. package/dist/molecules/Stepper.svelte +562 -0
  303. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  304. package/dist/molecules/StickyActionBar.svelte +250 -0
  305. package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
  306. package/dist/molecules/SwipeableListItem.svelte +334 -0
  307. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  308. package/dist/molecules/Tabs.svelte +310 -9
  309. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  310. package/dist/molecules/Toaster.svelte +514 -5
  311. package/dist/molecules/Toaster.svelte.d.ts +153 -2
  312. package/dist/molecules/ToasterToast.svelte +495 -77
  313. package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
  314. package/dist/molecules/UnsavedChangesBar.svelte +223 -0
  315. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  316. package/dist/molecules/index.d.ts +51 -0
  317. package/dist/molecules/index.js +31 -0
  318. package/dist/molecules/toast-store.d.ts +83 -7
  319. package/dist/molecules/toast-store.js +25 -1
  320. package/dist/organisms/AppNavigation.svelte +440 -0
  321. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  322. package/dist/organisms/AppShell.svelte +696 -0
  323. package/dist/organisms/AppShell.svelte.d.ts +157 -0
  324. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  325. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  326. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  327. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  328. package/dist/organisms/SidebarNavigation.svelte +111 -43
  329. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  330. package/dist/organisms/SidebarPanel.svelte +34 -17
  331. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  332. package/dist/organisms/SidebarShell.svelte +164 -14
  333. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  334. package/dist/organisms/TopNavbar.svelte +353 -39
  335. package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
  336. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  337. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  338. package/dist/organisms/index.d.ts +10 -0
  339. package/dist/organisms/index.js +7 -0
  340. package/dist/types/components.d.ts +21 -1
  341. package/dist/types/components.d.ts.map +1 -1
  342. package/dist/types/index.d.ts +1093 -26
  343. package/dist/types/index.d.ts.map +1 -1
  344. package/dist/types/variants.ts +28 -1
  345. package/dist/util/app-shell.d.ts +78 -0
  346. package/dist/util/app-shell.js +97 -0
  347. package/dist/util/avatar.d.ts +38 -0
  348. package/dist/util/avatar.js +52 -0
  349. package/dist/util/bottom-sheet.d.ts +29 -0
  350. package/dist/util/bottom-sheet.js +43 -0
  351. package/dist/util/bottom-tab-bar.d.ts +40 -0
  352. package/dist/util/bottom-tab-bar.js +54 -0
  353. package/dist/util/calendar.d.ts +55 -0
  354. package/dist/util/calendar.js +74 -0
  355. package/dist/util/chart.d.ts +139 -0
  356. package/dist/util/chart.js +305 -0
  357. package/dist/util/chip.d.ts +65 -0
  358. package/dist/util/chip.js +58 -0
  359. package/dist/util/cn.js +28 -2
  360. package/dist/util/code-field.d.ts +62 -0
  361. package/dist/util/code-field.js +112 -0
  362. package/dist/util/collapsible.d.ts +63 -0
  363. package/dist/util/collapsible.js +23 -0
  364. package/dist/util/confirm-dialog.d.ts +11 -0
  365. package/dist/util/confirm-dialog.js +2 -0
  366. package/dist/util/date.d.ts +95 -0
  367. package/dist/util/date.js +237 -0
  368. package/dist/util/display-voice.d.ts +9 -0
  369. package/dist/util/display-voice.js +9 -0
  370. package/dist/util/drawer.d.ts +12 -0
  371. package/dist/util/drawer.js +7 -0
  372. package/dist/util/dropdown.d.ts +46 -0
  373. package/dist/util/dropdown.js +21 -0
  374. package/dist/util/field-control.d.ts +29 -0
  375. package/dist/util/field-control.js +32 -0
  376. package/dist/util/field.d.ts +44 -0
  377. package/dist/util/field.js +38 -0
  378. package/dist/util/fit-in-viewport.d.ts +200 -0
  379. package/dist/util/fit-in-viewport.js +399 -0
  380. package/dist/util/focus-utils.d.ts +77 -2
  381. package/dist/util/focus-utils.js +186 -9
  382. package/dist/util/hydration.d.ts +44 -0
  383. package/dist/util/hydration.js +58 -0
  384. package/dist/util/keyboard-inset.d.ts +17 -0
  385. package/dist/util/keyboard-inset.js +53 -0
  386. package/dist/util/map-view.d.ts +143 -0
  387. package/dist/util/map-view.js +121 -0
  388. package/dist/util/media-grid.d.ts +50 -0
  389. package/dist/util/media-grid.js +81 -0
  390. package/dist/util/media.d.ts +5 -0
  391. package/dist/util/media.js +10 -0
  392. package/dist/util/overlay-chrome.d.ts +52 -0
  393. package/dist/util/overlay-chrome.js +56 -0
  394. package/dist/util/overlay.d.ts +71 -0
  395. package/dist/util/overlay.js +240 -0
  396. package/dist/util/photo.d.ts +233 -0
  397. package/dist/util/photo.js +209 -0
  398. package/dist/util/portal.d.ts +22 -0
  399. package/dist/util/portal.js +50 -0
  400. package/dist/util/progress.d.ts +18 -0
  401. package/dist/util/progress.js +17 -0
  402. package/dist/util/pull-to-refresh.d.ts +26 -0
  403. package/dist/util/pull-to-refresh.js +30 -0
  404. package/dist/util/radio-keys.d.ts +24 -0
  405. package/dist/util/radio-keys.js +49 -0
  406. package/dist/util/rating.d.ts +22 -0
  407. package/dist/util/rating.js +26 -0
  408. package/dist/util/ready-made-strings.d.ts +76 -0
  409. package/dist/util/ready-made-strings.js +67 -0
  410. package/dist/util/scroll-edge.d.ts +32 -0
  411. package/dist/util/scroll-edge.js +87 -0
  412. package/dist/util/scroll-padding.d.ts +19 -0
  413. package/dist/util/scroll-padding.js +44 -0
  414. package/dist/util/segmented-control.d.ts +15 -0
  415. package/dist/util/segmented-control.js +1 -0
  416. package/dist/util/select.d.ts +40 -0
  417. package/dist/util/select.js +63 -0
  418. package/dist/util/sheet-drag.d.ts +83 -0
  419. package/dist/util/sheet-drag.js +311 -0
  420. package/dist/util/sidebar.d.ts +62 -0
  421. package/dist/util/sidebar.js +38 -0
  422. package/dist/util/skeleton.d.ts +14 -0
  423. package/dist/util/skeleton.js +22 -0
  424. package/dist/util/sliding-indicator.d.ts +24 -0
  425. package/dist/util/sliding-indicator.js +44 -0
  426. package/dist/util/sortable-list.d.ts +64 -0
  427. package/dist/util/sortable-list.js +53 -0
  428. package/dist/util/stepper.d.ts +39 -0
  429. package/dist/util/stepper.js +38 -0
  430. package/dist/util/swipeable-list-item.d.ts +35 -0
  431. package/dist/util/swipeable-list-item.js +32 -0
  432. package/dist/util/temporal-field.d.ts +88 -0
  433. package/dist/util/temporal-field.js +136 -0
  434. package/dist/util/theme-mode.d.ts +79 -0
  435. package/dist/util/theme-mode.js +121 -0
  436. package/dist/util/toaster.d.ts +132 -0
  437. package/dist/util/toaster.js +130 -0
  438. package/dist/util/tone-surface.d.ts +33 -0
  439. package/dist/util/tone-surface.js +35 -0
  440. package/dist/util/tooltip.d.ts +9 -0
  441. package/dist/util/tooltip.js +18 -0
  442. package/dist/util/top-navbar.d.ts +34 -0
  443. package/dist/util/top-navbar.js +28 -0
  444. package/dist/util/touch-target.d.ts +30 -0
  445. package/dist/util/touch-target.js +30 -0
  446. package/dist/util/zabi-strings.d.ts +141 -0
  447. package/dist/util/zabi-strings.js +105 -0
  448. package/dist/zabi-components-colors.css +11 -2
  449. package/dist/zabi-components-theme-dark-only.css +870 -258
  450. package/dist/zabi-components-theme-dark.css +870 -258
  451. package/dist/zabi-components-theme-only.css +2419 -368
  452. package/dist/zabi-components-theme.css +2419 -368
  453. package/dist/zabi-components.css +6032 -711
  454. package/docs/lucide-icons.md +40 -17
  455. package/docs/theme-imports.md +81 -11
  456. package/package.json +45 -8
@@ -0,0 +1,81 @@
1
+ import type { HTMLInputAttributes } from "svelte/elements";
2
+ import type { SizeVariant } from "../types/variants.js";
3
+ import type { DateFieldStrings } from "../util/temporal-field.js";
4
+ /**
5
+ * A date, picked with the device's own date picker and styled like Input.
6
+ *
7
+ * It is a native `<input type="date">`: an iPhone shows its wheels or
8
+ * calendar, Android its calendar, a desktop browser its own. `value` is
9
+ * always `YYYY-MM-DD`, or `""` while empty, whatever the field shows.
10
+ *
11
+ * What the field shows is the browser's decision, unless it has a
12
+ * `locale`: without one, the date is in the format of the browser or
13
+ * device, not of the page, and no attribute changes that. To show a date
14
+ * as text in the page's language, use `formatDate`:
15
+ *
16
+ * ```svelte
17
+ * <DateField label="Date" bind:value={date} min="2026-01-01" />
18
+ * <p>{formatDate(date, "sv")}</p> <!-- 6 okt. 2026 -->
19
+ * ```
20
+ *
21
+ * With `locale`, the field shows the value in that language and its own
22
+ * picker opens: a Calendar in a BottomSheet (at every width), with Clear
23
+ * unless the field is `required`. Until the page has run, and without
24
+ * scripts, it is the native input as above and submits as before; once
25
+ * it has, the native input stays as the form control, out of sight, and a
26
+ * button in its place shows the date. `picker="native"` opens the
27
+ * platform's own picker instead.
28
+ *
29
+ * ```svelte
30
+ * <DateField label="Datum" locale="sv" bind:value format={{ weekday: "short", day: "numeric", month: "short" }} />
31
+ * ```
32
+ *
33
+ * Other attributes (`autocomplete`, `data-*`, `onchange`, ...) land on the
34
+ * `<input>`.
35
+ */
36
+ type Props = Omit<HTMLInputAttributes, "class" | "type" | "value" | "size" | "min" | "max" | "step" | "required" | "disabled" | "readonly" | "id" | "name"> & {
37
+ /** Omit to auto-generate; pair with FormField's `id` when used inside FormField. */
38
+ id?: string;
39
+ /** The date as `YYYY-MM-DD`, or `""` while empty. Supports `bind:value`. */
40
+ value?: string;
41
+ /** Name the value is submitted under in a form. */
42
+ name?: string;
43
+ label?: string;
44
+ /** True when FormField supplies the visible `<label>`. */
45
+ hideLabel?: boolean;
46
+ /** Help text under the field, read out with it. */
47
+ hint?: string;
48
+ /** An error under the field. It marks the field invalid and is announced. */
49
+ error?: string;
50
+ /** Earliest date that can be picked, as `YYYY-MM-DD`. */
51
+ min?: string;
52
+ /** Latest date that can be picked, as `YYYY-MM-DD`. */
53
+ max?: string;
54
+ /** Steps between dates that can be picked, in days. */
55
+ step?: number | "any";
56
+ required?: boolean;
57
+ disabled?: boolean;
58
+ /** Shows the value without letting it be changed; it is still submitted. */
59
+ readonly?: boolean;
60
+ /** Height, on the scale Input and Button use. At least 44px on a touch screen. */
61
+ size?: SizeVariant;
62
+ /** Extra classes for the `<input>`. */
63
+ class?: string;
64
+ /**
65
+ * Language of the field, as a BCP 47 tag (`sv`, `en-GB`) or a list of them. With it the
66
+ * field shows the value in that language, whatever the browser's is, and opens the
67
+ * library's own picker; see above. Left out, the field is the native input.
68
+ */
69
+ locale?: string | string[];
70
+ /** With `locale`: how the value is shown, as options of `Intl.DateTimeFormat`. Default: what formatDate gives for the locale. */
71
+ format?: Intl.DateTimeFormatOptions;
72
+ /** With `locale`: shown in the placeholder colour while the field is empty. */
73
+ placeholder?: string;
74
+ /** With `locale`: `library` (default) opens the library's calendar in a sheet; `native` opens the platform's own picker. */
75
+ picker?: "library" | "native";
76
+ /** With `locale`: the words the field says; replace any of them to translate. */
77
+ strings?: Partial<DateFieldStrings>;
78
+ };
79
+ declare const DateField: import("svelte").Component<Props, {}, "value">;
80
+ type DateField = ReturnType<typeof DateField>;
81
+ export default DateField;
@@ -0,0 +1,69 @@
1
+ <script lang="ts">
2
+ import CheckCircle from "@lucide/svelte/icons/circle-check-big";
3
+ import AlertCircle from "@lucide/svelte/icons/circle-alert";
4
+ import AlertTriangle from "@lucide/svelte/icons/triangle-alert";
5
+ import { cn } from "../util/cn.js";
6
+ import { fieldHintId, fieldMessageId, type FieldMessageState } from "../util/field.js";
7
+
8
+ /**
9
+ * The text under a field: its status message and its hint. Internal: it is
10
+ * not exported from the package. Input, Textarea and Select render it, and
11
+ * DateField and TimeField through Input, so the markup, the ids and the
12
+ * way a message is announced are the same in all of them.
13
+ *
14
+ * The ids are the ones `fieldDescribedBy` lists on the control.
15
+ */
16
+ interface Props {
17
+ /** Id of the control the text belongs to. */
18
+ fieldId: string;
19
+ /** From `fieldMessageState`. */
20
+ status: FieldMessageState;
21
+ hint?: string;
22
+ /** Distance from the control, and between the two: Textarea and Select sit 4px closer. */
23
+ gap?: "mt-1" | "mt-2";
24
+ /** Extra classes for the status message. */
25
+ messageClass?: string;
26
+ }
27
+
28
+ let { fieldId, status, hint = "", gap = "mt-2", messageClass = "" }: Props = $props();
29
+
30
+ // Message text uses the `-text` step (700), which clears 4.5:1 on a page
31
+ // surface; the solid fill step (600) is for fills, not for small text.
32
+ const toneClass = $derived(
33
+ status.variant === "error"
34
+ ? "text-error-text"
35
+ : status.variant === "success"
36
+ ? "text-success-text"
37
+ : status.variant === "warning"
38
+ ? "text-warning-text"
39
+ : "text-description",
40
+ );
41
+
42
+ const Icon = $derived(
43
+ status.variant === "error"
44
+ ? AlertCircle
45
+ : status.variant === "success"
46
+ ? CheckCircle
47
+ : status.variant === "warning"
48
+ ? AlertTriangle
49
+ : null,
50
+ );
51
+ </script>
52
+
53
+ {#if status.showMessage}
54
+ <p
55
+ id={fieldMessageId(fieldId)}
56
+ class={cn("flex items-center gap-2 text-sm", gap, toneClass, messageClass)}
57
+ role={status.variant === "error" ? "alert" : "status"}
58
+ aria-live={status.variant === "error" ? "assertive" : "polite"}
59
+ >
60
+ {#if Icon}
61
+ <Icon size={14} class="shrink-0" />
62
+ {/if}
63
+ <span>{status.message}</span>
64
+ </p>
65
+ {/if}
66
+ {#if status.showHint}
67
+ <!-- Not a live region: a hint is there from the start and is read with the field. -->
68
+ <p id={fieldHintId(fieldId)} class={cn("text-sm text-description", gap)}>{hint}</p>
69
+ {/if}
@@ -0,0 +1,23 @@
1
+ import { type FieldMessageState } from "../util/field.js";
2
+ /**
3
+ * The text under a field: its status message and its hint. Internal: it is
4
+ * not exported from the package. Input, Textarea and Select render it, and
5
+ * DateField and TimeField through Input, so the markup, the ids and the
6
+ * way a message is announced are the same in all of them.
7
+ *
8
+ * The ids are the ones `fieldDescribedBy` lists on the control.
9
+ */
10
+ interface Props {
11
+ /** Id of the control the text belongs to. */
12
+ fieldId: string;
13
+ /** From `fieldMessageState`. */
14
+ status: FieldMessageState;
15
+ hint?: string;
16
+ /** Distance from the control, and between the two: Textarea and Select sit 4px closer. */
17
+ gap?: "mt-1" | "mt-2";
18
+ /** Extra classes for the status message. */
19
+ messageClass?: string;
20
+ }
21
+ declare const FieldMessages: import("svelte").Component<Props, {}, "">;
22
+ type FieldMessages = ReturnType<typeof FieldMessages>;
23
+ export default FieldMessages;
@@ -0,0 +1,176 @@
1
+ <script lang="ts">
2
+ import type { Component } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import Plus from "@lucide/svelte/icons/plus";
5
+ import { findScrollParent, isInsideAppShell } from "../util/app-shell.js";
6
+ import { cn } from "../util/cn.js";
7
+ import { reserveScrollPaddingBottom } from "../util/scroll-padding.js";
8
+
9
+ /**
10
+ * The one main thing to do on a screen, as a round button that floats over
11
+ * the content: new quiz, compose, add.
12
+ *
13
+ * Inside an `AppShell` it is placed against the shell, 16px above the tab
14
+ * bar. On its own it is fixed to the screen, 16px above the home
15
+ * indicator; if something else is fixed to the bottom there (a
16
+ * `BottomTabBar` on its own), say how tall it is with
17
+ * `--fab-bottom-offset` on the button or any ancestor:
18
+ *
19
+ * ```svelte
20
+ * <FloatingActionButton
21
+ * label="New quiz"
22
+ * onclick={create}
23
+ * style="--fab-bottom-offset: 64px"
24
+ * />
25
+ * ```
26
+ *
27
+ * It lies over the content, so the last row would stay under it at the
28
+ * end of the page. Give the content room to scroll clear: `pb-24` (96px)
29
+ * on the list covers the button and its margins.
30
+ *
31
+ * While it is mounted it sets `scroll-padding-bottom` on what scrolls
32
+ * under it (the shell's content, or the page) to the height it takes
33
+ * from the bottom, so a row that takes keyboard focus is scrolled clear
34
+ * of the button and not left half under it.
35
+ */
36
+ type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
37
+ /** What the button does. Its accessible name, and its text when `extended`. */
38
+ label: string;
39
+ /** An icon component, as `@lucide/svelte` icons are. A plus sign by default. */
40
+ icon?: Component<{ size?: number; class?: string }>;
41
+ /** Shows the label as text beside the icon. */
42
+ extended?: boolean;
43
+ /** Makes it a link to this address. Without it, it is a button. */
44
+ href?: string;
45
+ /**
46
+ * The bottom corner it sits in. `bottom-end` is the right in a
47
+ * left-to-right page and the left in a right-to-left one.
48
+ */
49
+ position?: "bottom-end" | "bottom-start" | "bottom-center";
50
+ class?: string;
51
+ };
52
+
53
+ let {
54
+ label,
55
+ icon,
56
+ extended = false,
57
+ href,
58
+ position = "bottom-end",
59
+ class: className = "",
60
+ ...restProps
61
+ }: Props = $props();
62
+
63
+ const inShell = isInsideAppShell();
64
+ const Icon = $derived(icon ?? Plus);
65
+
66
+ let host = $state<HTMLElement>();
67
+
68
+ /**
69
+ * The browser scrolls a focused control into view, to the edge of the
70
+ * scroll container: under this button, which lies over that edge. The
71
+ * room it takes, from its top edge to the bottom of the container, is
72
+ * reserved through the helper the bars use, so the largest request wins.
73
+ */
74
+ $effect(() => {
75
+ const button = host;
76
+ if (!button) return;
77
+ const scroller = findScrollParent(button);
78
+ const reservation = reserveScrollPaddingBottom(scroller ?? document.documentElement);
79
+ const reserve = () => {
80
+ const top = button.getBoundingClientRect().top;
81
+ const bottom = scroller
82
+ ? scroller.getBoundingClientRect().bottom
83
+ : document.documentElement.clientHeight;
84
+ // No layout (a test DOM, a hidden button): nothing to keep clear of.
85
+ reservation.set(button.offsetHeight > 0 ? Math.max(0, Math.ceil(bottom - top)) : 0);
86
+ };
87
+ reserve();
88
+ const observer =
89
+ typeof ResizeObserver === "function" ? new ResizeObserver(reserve) : undefined;
90
+ observer?.observe(button);
91
+ if (scroller) observer?.observe(scroller);
92
+ window.addEventListener("resize", reserve);
93
+ return () => {
94
+ observer?.disconnect();
95
+ window.removeEventListener("resize", reserve);
96
+ reservation.release();
97
+ };
98
+ });
99
+
100
+ /**
101
+ * 16px from the edges, in px: a margin that grew with the text size would
102
+ * push the button into the content it floats over. The side clears a
103
+ * notch in landscape, whichever side that is on.
104
+ */
105
+ const positionClasses = $derived(
106
+ {
107
+ "bottom-end":
108
+ "end-[calc(16px+max(env(safe-area-inset-left,0px),env(safe-area-inset-right,0px)))]",
109
+ "bottom-start":
110
+ "start-[calc(16px+max(env(safe-area-inset-left,0px),env(safe-area-inset-right,0px)))]",
111
+ // Not `left-1/2` with a shift back: that leaves the button half
112
+ // the width to lay its label out in.
113
+ "bottom-center": "inset-x-0 mx-auto w-fit",
114
+ }[position] ??
115
+ "end-[calc(16px+max(env(safe-area-inset-left,0px),env(safe-area-inset-right,0px)))]",
116
+ );
117
+
118
+ const classes = $derived(
119
+ cn(
120
+ "focus-ring z-sticky inline-flex cursor-pointer items-center justify-center gap-2 rounded-pill no-underline shadow-lg select-none",
121
+ // Round: 56px in px, like the 24px icon in it, so it does not
122
+ // double with the text size and cover twice the content.
123
+ // Extended: it holds text, so its height follows the text; it
124
+ // grows sideways, and downwards if the label wraps.
125
+ extended ? "min-h-14 min-w-14" : "size-[56px]",
126
+ // Transparent until forced-colors mode draws it: the fill is gone there.
127
+ "border border-transparent",
128
+ "bg-action-primary bg-control-gradient text-action-primary",
129
+ "transition-[background-color,scale] duration-(--duration-base) active:scale-[0.96] motion-reduce:transition-none motion-reduce:active:scale-100",
130
+ extended ? "px-5 py-2 text-sm font-medium" : "p-0",
131
+ // Never wider than the screen it floats on, margins included.
132
+ "[max-inline-size:calc(100%_-_32px)]",
133
+ inShell
134
+ ? "absolute bottom-[calc(var(--app-shell-bottom-inset,0px)+var(--fab-bottom-offset,0px)+16px)]"
135
+ : "fixed bottom-[calc(env(safe-area-inset-bottom,0px)+var(--fab-bottom-offset,0px)+16px)]",
136
+ positionClasses,
137
+ className,
138
+ ),
139
+ );
140
+ </script>
141
+
142
+ {#snippet content()}
143
+ <!-- Decorative: the label names the button, shown or not. -->
144
+ <span class="flex size-[24px] shrink-0" aria-hidden="true">
145
+ <Icon size={24} />
146
+ </span>
147
+ {#if extended}
148
+ <span class="min-w-0 [overflow-wrap:anywhere]">{label}</span>
149
+ {/if}
150
+ {/snippet}
151
+
152
+ {#if href !== undefined}
153
+ <a
154
+ bind:this={host}
155
+ {href}
156
+ class={classes}
157
+ aria-label={extended ? undefined : label}
158
+ data-position={position}
159
+ data-toast-clear
160
+ {...restProps}
161
+ >
162
+ {@render content()}
163
+ </a>
164
+ {:else}
165
+ <button
166
+ bind:this={host}
167
+ type="button"
168
+ class={classes}
169
+ aria-label={extended ? undefined : label}
170
+ data-position={position}
171
+ data-toast-clear
172
+ {...restProps}
173
+ >
174
+ {@render content()}
175
+ </button>
176
+ {/if}
@@ -0,0 +1,51 @@
1
+ import type { Component } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ /**
4
+ * The one main thing to do on a screen, as a round button that floats over
5
+ * the content: new quiz, compose, add.
6
+ *
7
+ * Inside an `AppShell` it is placed against the shell, 16px above the tab
8
+ * bar. On its own it is fixed to the screen, 16px above the home
9
+ * indicator; if something else is fixed to the bottom there (a
10
+ * `BottomTabBar` on its own), say how tall it is with
11
+ * `--fab-bottom-offset` on the button or any ancestor:
12
+ *
13
+ * ```svelte
14
+ * <FloatingActionButton
15
+ * label="New quiz"
16
+ * onclick={create}
17
+ * style="--fab-bottom-offset: 64px"
18
+ * />
19
+ * ```
20
+ *
21
+ * It lies over the content, so the last row would stay under it at the
22
+ * end of the page. Give the content room to scroll clear: `pb-24` (96px)
23
+ * on the list covers the button and its margins.
24
+ *
25
+ * While it is mounted it sets `scroll-padding-bottom` on what scrolls
26
+ * under it (the shell's content, or the page) to the height it takes
27
+ * from the bottom, so a row that takes keyboard focus is scrolled clear
28
+ * of the button and not left half under it.
29
+ */
30
+ type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
31
+ /** What the button does. Its accessible name, and its text when `extended`. */
32
+ label: string;
33
+ /** An icon component, as `@lucide/svelte` icons are. A plus sign by default. */
34
+ icon?: Component<{
35
+ size?: number;
36
+ class?: string;
37
+ }>;
38
+ /** Shows the label as text beside the icon. */
39
+ extended?: boolean;
40
+ /** Makes it a link to this address. Without it, it is a button. */
41
+ href?: string;
42
+ /**
43
+ * The bottom corner it sits in. `bottom-end` is the right in a
44
+ * left-to-right page and the left in a right-to-left one.
45
+ */
46
+ position?: "bottom-end" | "bottom-start" | "bottom-center";
47
+ class?: string;
48
+ };
49
+ declare const FloatingActionButton: Component<Props, {}, "">;
50
+ type FloatingActionButton = ReturnType<typeof FloatingActionButton>;
51
+ export default FloatingActionButton;
@@ -2,6 +2,8 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import type { HTMLAttributes } from "svelte/elements";
4
4
  import { cn } from "../util/cn.js";
5
+ import type { OnFillTone } from "../types/variants.js";
6
+ import { displayVoice } from "../util/display-voice.js";
5
7
 
6
8
  /** Semantic heading level — also the default visual size. */
7
9
  type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
@@ -16,6 +18,19 @@
16
18
  size?: HeadingLevel;
17
19
  /** Heading text. Ignored when `children` is provided. */
18
20
  text?: string;
21
+ /**
22
+ * Text colour. `headline` (the default) is for a page or a card. On a
23
+ * filled block use `inherit` (the block's own colour), `on-brand` or
24
+ * `on-accent`.
25
+ */
26
+ tone?: "headline" | OnFillTone;
27
+ /**
28
+ * `heading` is the document heading voice. `display` is the second
29
+ * face (`--font-family-display`) with larger steps, for a hero, a
30
+ * score or a big number; its weight and tracking are the custom
31
+ * properties `--zabi-display-weight` and `--zabi-display-tracking`.
32
+ */
33
+ variant?: "heading" | "display";
19
34
  class?: string;
20
35
  children?: Snippet;
21
36
  };
@@ -24,13 +39,21 @@
24
39
  level = 1,
25
40
  size,
26
41
  text = "",
42
+ tone = "headline",
43
+ variant = "heading",
27
44
  class: className = "",
28
45
  children,
29
46
  ...restProps
30
47
  }: Props = $props();
31
48
 
49
+ const toneClasses: Record<"headline" | OnFillTone, string> = {
50
+ headline: "text-headline",
51
+ inherit: "text-inherit",
52
+ "on-brand": "text-on-brand",
53
+ "on-accent": "text-on-accent",
54
+ };
55
+
32
56
  const visualSize = $derived(size ?? level);
33
- const Tag = $derived(`h${level}`);
34
57
 
35
58
  /**
36
59
  * Display sizes tighten as they grow. Untracked 36px type with default
@@ -47,21 +70,56 @@
47
70
  6: "text-base leading-6 font-semibold",
48
71
  };
49
72
 
73
+ /**
74
+ * Display steps: 60px (48 on a phone), 48 (36), 36 (30), 30, 24, 20. Weight and tracking come from
75
+ * `displayVoice`, so no size carries its own.
76
+ */
77
+ const displaySizeClasses: Record<HeadingLevel, string> = {
78
+ 1: "text-5xl sm:text-6xl leading-[1.0]",
79
+ 2: "text-4xl sm:text-5xl leading-[1.05]",
80
+ 3: "text-3xl sm:text-4xl leading-[1.1]",
81
+ 4: "text-3xl leading-[1.2]",
82
+ 5: "text-2xl leading-[1.2]",
83
+ 6: "text-xl leading-[1.2]",
84
+ };
85
+
50
86
  const headingClasses = $derived(
51
- cn(`text-headline ${sizeClasses[visualSize] ?? sizeClasses[6]} ${className}`),
87
+ variant === "display"
88
+ ? cn(
89
+ `${toneClasses[tone] ?? toneClasses.headline} font-display text-balance ${displayVoice} ${displaySizeClasses[visualSize] ?? displaySizeClasses[6]} ${className}`,
90
+ )
91
+ : cn(`${toneClasses[tone] ?? toneClasses.headline} ${sizeClasses[visualSize] ?? sizeClasses[6]} ${className}`),
52
92
  );
53
93
  </script>
54
94
 
55
95
  <!--
56
- The font family comes from `--font-family-sans`, applied on `body`. This
96
+ The font family comes from `--font-family-heading`, which the theme applies
97
+ to h1–h6 in its base layer and which defaults to `--font-family-sans`. This
57
98
  component used to carry its own `font-family: "Nunito Sans"` in a local
58
99
  <style> block, which meant a consumer who rebranded the font token got a
59
100
  rebranded body and Nunito Sans headings.
60
101
  -->
61
- <svelte:element this={Tag} class={headingClasses} {...restProps}>
102
+ {#snippet content()}
62
103
  {#if children}
63
104
  {@render children()}
64
105
  {:else}
65
106
  {text}
66
107
  {/if}
67
- </svelte:element>
108
+ {/snippet}
109
+
110
+ <!-- One branch per level, not a dynamic element: hydration takes a dynamic
111
+ element out and puts it back, which blurs a control inside it that the user
112
+ had already tabbed to. -->
113
+ {#if level === 1}
114
+ <h1 class={headingClasses} {...restProps}>{@render content()}</h1>
115
+ {:else if level === 2}
116
+ <h2 class={headingClasses} {...restProps}>{@render content()}</h2>
117
+ {:else if level === 3}
118
+ <h3 class={headingClasses} {...restProps}>{@render content()}</h3>
119
+ {:else if level === 4}
120
+ <h4 class={headingClasses} {...restProps}>{@render content()}</h4>
121
+ {:else if level === 5}
122
+ <h5 class={headingClasses} {...restProps}>{@render content()}</h5>
123
+ {:else}
124
+ <h6 class={headingClasses} {...restProps}>{@render content()}</h6>
125
+ {/if}
@@ -1,5 +1,6 @@
1
1
  import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
+ import type { OnFillTone } from "../types/variants.js";
3
4
  /** Semantic heading level — also the default visual size. */
4
5
  type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
5
6
  type Props = Omit<HTMLAttributes<HTMLHeadingElement>, "class"> & {
@@ -12,6 +13,19 @@ type Props = Omit<HTMLAttributes<HTMLHeadingElement>, "class"> & {
12
13
  size?: HeadingLevel;
13
14
  /** Heading text. Ignored when `children` is provided. */
14
15
  text?: string;
16
+ /**
17
+ * Text colour. `headline` (the default) is for a page or a card. On a
18
+ * filled block use `inherit` (the block's own colour), `on-brand` or
19
+ * `on-accent`.
20
+ */
21
+ tone?: "headline" | OnFillTone;
22
+ /**
23
+ * `heading` is the document heading voice. `display` is the second
24
+ * face (`--font-family-display`) with larger steps, for a hero, a
25
+ * score or a big number; its weight and tracking are the custom
26
+ * properties `--zabi-display-weight` and `--zabi-display-tracking`.
27
+ */
28
+ variant?: "heading" | "display";
15
29
  class?: string;
16
30
  children?: Snippet;
17
31
  };