zabi-components 7.0.2 → 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 (433) hide show
  1. package/CHANGELOG.md +1924 -0
  2. package/README.md +287 -11
  3. package/THEME.md +149 -332
  4. package/THEMING.md +1329 -163
  5. package/dist/atoms/ActionPanel.svelte +24 -9
  6. package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
  7. package/dist/atoms/Avatar.svelte +104 -0
  8. package/dist/atoms/Avatar.svelte.d.ts +34 -0
  9. package/dist/atoms/Badge.svelte +124 -58
  10. package/dist/atoms/Badge.svelte.d.ts +17 -5
  11. package/dist/atoms/Button.svelte +212 -62
  12. package/dist/atoms/Button.svelte.d.ts +33 -2
  13. package/dist/atoms/Card.svelte +31 -28
  14. package/dist/atoms/Card.svelte.d.ts +5 -2
  15. package/dist/atoms/CardContent.svelte +10 -2
  16. package/dist/atoms/CardContent.svelte.d.ts +2 -0
  17. package/dist/atoms/CardFooter.svelte +10 -2
  18. package/dist/atoms/CardFooter.svelte.d.ts +2 -0
  19. package/dist/atoms/CardHeader.svelte +26 -6
  20. package/dist/atoms/CardHeader.svelte.d.ts +2 -0
  21. package/dist/atoms/Checkbox.svelte +52 -21
  22. package/dist/atoms/Checkbox.svelte.d.ts +6 -2
  23. package/dist/atoms/CodeBlock.svelte +35 -9
  24. package/dist/atoms/CodeBlock.svelte.d.ts +13 -3
  25. package/dist/atoms/ColorPicker.svelte +272 -52
  26. package/dist/atoms/ColorPicker.svelte.d.ts +6 -1
  27. package/dist/atoms/Container.svelte +17 -10
  28. package/dist/atoms/DateField.svelte +107 -0
  29. package/dist/atoms/DateField.svelte.d.ts +53 -0
  30. package/dist/atoms/Divider.svelte +4 -4
  31. package/dist/atoms/FeatureCard.svelte +4 -3
  32. package/dist/atoms/FieldMessages.svelte +69 -0
  33. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  34. package/dist/atoms/FloatingActionButton.svelte +174 -0
  35. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  36. package/dist/atoms/Heading.svelte +93 -32
  37. package/dist/atoms/Heading.svelte.d.ts +24 -4
  38. package/dist/atoms/IconButton.svelte +242 -53
  39. package/dist/atoms/IconButton.svelte.d.ts +49 -4
  40. package/dist/atoms/Input.svelte +267 -74
  41. package/dist/atoms/Input.svelte.d.ts +32 -2
  42. package/dist/atoms/List.svelte +13 -2
  43. package/dist/atoms/List.svelte.d.ts +2 -0
  44. package/dist/atoms/ListItem.svelte +44 -11
  45. package/dist/atoms/ListItem.svelte.d.ts +5 -0
  46. package/dist/atoms/ListItemLeading.svelte +4 -2
  47. package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
  48. package/dist/atoms/OptimizedImage.svelte +11 -2
  49. package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
  50. package/dist/atoms/Progress.svelte +31 -9
  51. package/dist/atoms/Progress.svelte.d.ts +6 -0
  52. package/dist/atoms/Radio.svelte +23 -3
  53. package/dist/atoms/Radio.svelte.d.ts +6 -2
  54. package/dist/atoms/Rating.svelte +494 -0
  55. package/dist/atoms/Rating.svelte.d.ts +56 -0
  56. package/dist/atoms/Select.svelte +475 -135
  57. package/dist/atoms/Select.svelte.d.ts +52 -2
  58. package/dist/atoms/SelectionControl.svelte +99 -18
  59. package/dist/atoms/SelectionControl.svelte.d.ts +6 -3
  60. package/dist/atoms/Skeleton.svelte +5 -3
  61. package/dist/atoms/Slider.svelte +309 -0
  62. package/dist/atoms/Slider.svelte.d.ts +40 -0
  63. package/dist/atoms/Spinner.svelte +57 -0
  64. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  65. package/dist/atoms/Table.svelte +152 -6
  66. package/dist/atoms/Table.svelte.d.ts +15 -3
  67. package/dist/atoms/TemporalField.svelte +140 -0
  68. package/dist/atoms/TemporalField.svelte.d.ts +32 -0
  69. package/dist/atoms/Text.svelte +56 -15
  70. package/dist/atoms/Text.svelte.d.ts +13 -4
  71. package/dist/atoms/Textarea.svelte +57 -45
  72. package/dist/atoms/Textarea.svelte.d.ts +14 -2
  73. package/dist/atoms/ThemeToggle.svelte +238 -56
  74. package/dist/atoms/ThemeToggle.svelte.d.ts +28 -3
  75. package/dist/atoms/TimeField.svelte +108 -0
  76. package/dist/atoms/TimeField.svelte.d.ts +54 -0
  77. package/dist/atoms/Toast.svelte +25 -10
  78. package/dist/atoms/Toast.svelte.d.ts +2 -0
  79. package/dist/atoms/Toggle.svelte +60 -19
  80. package/dist/atoms/Toggle.svelte.d.ts +18 -2
  81. package/dist/atoms/Tooltip.svelte +583 -40
  82. package/dist/atoms/Tooltip.svelte.d.ts +26 -0
  83. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  84. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  85. package/dist/atoms/index.d.ts +17 -0
  86. package/dist/atoms/index.js +12 -0
  87. package/dist/atoms/selection-control.styles.d.ts +36 -5
  88. package/dist/atoms/selection-control.styles.js +40 -9
  89. package/dist/components/atoms/index.d.ts +17 -0
  90. package/dist/components/atoms/index.d.ts.map +1 -1
  91. package/dist/components/atoms/selection-control.styles.d.ts +36 -5
  92. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  93. package/dist/components/index.d.ts +83 -3
  94. package/dist/components/index.d.ts.map +1 -1
  95. package/dist/components/molecules/index.d.ts +46 -0
  96. package/dist/components/molecules/index.d.ts.map +1 -1
  97. package/dist/components/molecules/toast-store.d.ts +53 -7
  98. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  99. package/dist/components/organisms/index.d.ts +6 -0
  100. package/dist/components/organisms/index.d.ts.map +1 -1
  101. package/dist/components/types/variants.d.ts +15 -1
  102. package/dist/components/types/variants.d.ts.map +1 -1
  103. package/dist/components/util/app-shell.d.ts +28 -0
  104. package/dist/components/util/app-shell.d.ts.map +1 -0
  105. package/dist/components/util/avatar.d.ts +36 -0
  106. package/dist/components/util/avatar.d.ts.map +1 -0
  107. package/dist/components/util/bottom-sheet.d.ts +30 -0
  108. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  109. package/dist/components/util/bottom-tab-bar.d.ts +35 -0
  110. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  111. package/dist/components/util/calendar.d.ts +56 -0
  112. package/dist/components/util/calendar.d.ts.map +1 -0
  113. package/dist/components/util/cn.d.ts +8 -0
  114. package/dist/components/util/cn.d.ts.map +1 -0
  115. package/dist/components/util/collapsible.d.ts +58 -0
  116. package/dist/components/util/collapsible.d.ts.map +1 -0
  117. package/dist/components/util/confirm-dialog.d.ts +12 -0
  118. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  119. package/dist/components/util/date.d.ts +96 -0
  120. package/dist/components/util/date.d.ts.map +1 -0
  121. package/dist/components/util/drawer.d.ts +13 -0
  122. package/dist/components/util/drawer.d.ts.map +1 -0
  123. package/dist/components/util/dropdown.d.ts +47 -0
  124. package/dist/components/util/dropdown.d.ts.map +1 -0
  125. package/dist/components/util/field.d.ts +39 -0
  126. package/dist/components/util/field.d.ts.map +1 -0
  127. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  128. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  129. package/dist/components/util/focus-utils.d.ts +75 -2
  130. package/dist/components/util/focus-utils.d.ts.map +1 -1
  131. package/dist/components/util/hydration.d.ts +45 -0
  132. package/dist/components/util/hydration.d.ts.map +1 -0
  133. package/dist/components/util/keyboard-inset.d.ts +18 -0
  134. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  135. package/dist/components/util/media-grid.d.ts +51 -0
  136. package/dist/components/util/media-grid.d.ts.map +1 -0
  137. package/dist/components/util/media.d.ts +6 -0
  138. package/dist/components/util/media.d.ts.map +1 -0
  139. package/dist/components/util/overlay-chrome.d.ts +53 -0
  140. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  141. package/dist/components/util/overlay.d.ts +72 -0
  142. package/dist/components/util/overlay.d.ts.map +1 -0
  143. package/dist/components/util/photo.d.ts +215 -0
  144. package/dist/components/util/photo.d.ts.map +1 -0
  145. package/dist/components/util/portal.d.ts +23 -0
  146. package/dist/components/util/portal.d.ts.map +1 -0
  147. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  148. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  149. package/dist/components/util/radio-keys.d.ts +25 -0
  150. package/dist/components/util/radio-keys.d.ts.map +1 -0
  151. package/dist/components/util/rating.d.ts +23 -0
  152. package/dist/components/util/rating.d.ts.map +1 -0
  153. package/dist/components/util/ready-made-strings.d.ts +77 -0
  154. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  155. package/dist/components/util/scroll-padding.d.ts +20 -0
  156. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  157. package/dist/components/util/segmented-control.d.ts +16 -0
  158. package/dist/components/util/segmented-control.d.ts.map +1 -0
  159. package/dist/components/util/select.d.ts +41 -0
  160. package/dist/components/util/select.d.ts.map +1 -0
  161. package/dist/components/util/sheet-drag.d.ts +60 -0
  162. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  163. package/dist/components/util/sidebar.d.ts +63 -0
  164. package/dist/components/util/sidebar.d.ts.map +1 -0
  165. package/dist/components/util/sortable-list.d.ts +65 -0
  166. package/dist/components/util/sortable-list.d.ts.map +1 -0
  167. package/dist/components/util/ssr-safe.d.ts +4 -4
  168. package/dist/components/util/ssr-safe.d.ts.map +1 -1
  169. package/dist/components/util/stepper.d.ts +40 -0
  170. package/dist/components/util/stepper.d.ts.map +1 -0
  171. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  172. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  173. package/dist/components/util/theme-mode.d.ts +80 -0
  174. package/dist/components/util/theme-mode.d.ts.map +1 -0
  175. package/dist/components/util/toaster.d.ts +93 -0
  176. package/dist/components/util/toaster.d.ts.map +1 -0
  177. package/dist/components/util/tooltip.d.ts +10 -0
  178. package/dist/components/util/tooltip.d.ts.map +1 -0
  179. package/dist/components/util/top-navbar.d.ts +11 -0
  180. package/dist/components/util/top-navbar.d.ts.map +1 -0
  181. package/dist/components/util/touch-target.d.ts +31 -0
  182. package/dist/components/util/touch-target.d.ts.map +1 -0
  183. package/dist/components/util/zabi-strings.d.ts +133 -0
  184. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  185. package/dist/create-theme/cli.js +169 -0
  186. package/dist/create-theme/index.d.ts +176 -0
  187. package/dist/create-theme/index.js +817 -0
  188. package/dist/create-theme/lib/ramp-math.js +128 -0
  189. package/dist/create-theme/lib/resolve.js +141 -0
  190. package/dist/create-theme/theme-data.js +1116 -0
  191. package/dist/index.d.ts +83 -3
  192. package/dist/index.js +60 -8
  193. package/dist/molecules/Alert.svelte +73 -66
  194. package/dist/molecules/Alert.svelte.d.ts +9 -1
  195. package/dist/molecules/AppBar.svelte +415 -0
  196. package/dist/molecules/AppBar.svelte.d.ts +69 -0
  197. package/dist/molecules/AvatarGroup.svelte +116 -0
  198. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  199. package/dist/molecules/BottomSheet.svelte +540 -0
  200. package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
  201. package/dist/molecules/BottomTabBar.svelte +297 -0
  202. package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
  203. package/dist/molecules/Calendar.svelte +597 -0
  204. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  205. package/dist/molecules/Collapsible.svelte +243 -0
  206. package/dist/molecules/Collapsible.svelte.d.ts +44 -0
  207. package/dist/molecules/CollapsibleGroup.svelte +126 -0
  208. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  209. package/dist/molecules/ComponentDemo.svelte +25 -5
  210. package/dist/molecules/ComponentDemo.svelte.d.ts +5 -0
  211. package/dist/molecules/ConfirmDialog.svelte +253 -0
  212. package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
  213. package/dist/molecules/ContactForm.svelte +46 -19
  214. package/dist/molecules/ContactForm.svelte.d.ts +10 -0
  215. package/dist/molecules/Drawer.svelte +394 -0
  216. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  217. package/dist/molecules/Dropdown.svelte +540 -50
  218. package/dist/molecules/Dropdown.svelte.d.ts +49 -6
  219. package/dist/molecules/DropdownItem.svelte +122 -0
  220. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  221. package/dist/molecules/EmptyState.svelte +56 -10
  222. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  223. package/dist/molecules/Form.svelte +25 -14
  224. package/dist/molecules/Form.svelte.d.ts +13 -5
  225. package/dist/molecules/FormField.svelte +11 -2
  226. package/dist/molecules/FormField.svelte.d.ts +2 -0
  227. package/dist/molecules/Header.svelte +13 -3
  228. package/dist/molecules/Header.svelte.d.ts +2 -0
  229. package/dist/molecules/ImageUpload.svelte +422 -70
  230. package/dist/molecules/ImageUpload.svelte.d.ts +61 -5
  231. package/dist/molecules/MediaGrid.svelte +527 -0
  232. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  233. package/dist/molecules/Modal.svelte +340 -45
  234. package/dist/molecules/Modal.svelte.d.ts +59 -7
  235. package/dist/molecules/NavigationMenu.svelte +15 -3
  236. package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
  237. package/dist/molecules/NavigationMenuContent.svelte +91 -36
  238. package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
  239. package/dist/molecules/NavigationMenuItem.svelte +10 -3
  240. package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
  241. package/dist/molecules/NavigationMenuLink.svelte +10 -2
  242. package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
  243. package/dist/molecules/NavigationMenuList.svelte +19 -4
  244. package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
  245. package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
  246. package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
  247. package/dist/molecules/Page.svelte +38 -2
  248. package/dist/molecules/Page.svelte.d.ts +13 -0
  249. package/dist/molecules/PhotoGrid.svelte +433 -0
  250. package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
  251. package/dist/molecules/PhotoViewer.svelte +1116 -0
  252. package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
  253. package/dist/molecules/PropsTable.svelte +26 -11
  254. package/dist/molecules/PropsTable.svelte.d.ts +5 -0
  255. package/dist/molecules/PullToRefresh.svelte +263 -0
  256. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  257. package/dist/molecules/RadioGroup.svelte +57 -15
  258. package/dist/molecules/RadioGroup.svelte.d.ts +16 -3
  259. package/dist/molecules/Section.svelte +13 -5
  260. package/dist/molecules/Section.svelte.d.ts +3 -1
  261. package/dist/molecules/SegmentedControl.svelte +323 -0
  262. package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
  263. package/dist/molecules/SidebarBrandHeader.svelte +24 -5
  264. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +5 -0
  265. package/dist/molecules/SidebarFooter.svelte +39 -8
  266. package/dist/molecules/SidebarFooter.svelte.d.ts +5 -0
  267. package/dist/molecules/SidebarNavSection.svelte +12 -4
  268. package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
  269. package/dist/molecules/SlideUp.svelte +253 -30
  270. package/dist/molecules/SlideUp.svelte.d.ts +31 -4
  271. package/dist/molecules/SortableList.svelte +632 -0
  272. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  273. package/dist/molecules/Stepper.svelte +548 -0
  274. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  275. package/dist/molecules/StickyActionBar.svelte +191 -0
  276. package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
  277. package/dist/molecules/SwipeableListItem.svelte +334 -0
  278. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  279. package/dist/molecules/Tabs.svelte +254 -24
  280. package/dist/molecules/Tabs.svelte.d.ts +15 -4
  281. package/dist/molecules/Toaster.svelte +352 -6
  282. package/dist/molecules/Toaster.svelte.d.ts +95 -2
  283. package/dist/molecules/ToasterToast.svelte +279 -62
  284. package/dist/molecules/ToasterToast.svelte.d.ts +11 -0
  285. package/dist/molecules/UnsavedChangesBar.svelte +212 -0
  286. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  287. package/dist/molecules/index.d.ts +46 -0
  288. package/dist/molecules/index.js +27 -0
  289. package/dist/molecules/toast-store.d.ts +53 -7
  290. package/dist/molecules/toast-store.js +21 -0
  291. package/dist/organisms/AppShell.svelte +196 -0
  292. package/dist/organisms/AppShell.svelte.d.ts +61 -0
  293. package/dist/organisms/SidebarAccountPanel.svelte +111 -12
  294. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +21 -0
  295. package/dist/organisms/SidebarNavigation.svelte +193 -69
  296. package/dist/organisms/SidebarNavigation.svelte.d.ts +31 -1
  297. package/dist/organisms/SidebarPanel.svelte +45 -16
  298. package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
  299. package/dist/organisms/SidebarShell.svelte +263 -0
  300. package/dist/organisms/SidebarShell.svelte.d.ts +106 -0
  301. package/dist/organisms/TopNavbar.svelte +329 -33
  302. package/dist/organisms/TopNavbar.svelte.d.ts +28 -0
  303. package/dist/organisms/index.d.ts +6 -0
  304. package/dist/organisms/index.js +4 -0
  305. package/dist/routes/lib/variant-utils.d.ts +26 -0
  306. package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
  307. package/dist/types/components.d.ts +4 -0
  308. package/dist/types/components.d.ts.map +1 -1
  309. package/dist/types/events.js +1 -0
  310. package/dist/types/index.d.ts +605 -25
  311. package/dist/types/index.d.ts.map +1 -1
  312. package/dist/types/index.js +3 -0
  313. package/dist/types/variants.ts +17 -1
  314. package/dist/util/app-shell.d.ts +27 -0
  315. package/dist/util/app-shell.js +72 -0
  316. package/dist/util/avatar.d.ts +35 -0
  317. package/dist/util/avatar.js +52 -0
  318. package/dist/util/bottom-sheet.d.ts +29 -0
  319. package/dist/util/bottom-sheet.js +43 -0
  320. package/dist/util/bottom-tab-bar.d.ts +34 -0
  321. package/dist/util/bottom-tab-bar.js +54 -0
  322. package/dist/util/calendar.d.ts +55 -0
  323. package/dist/util/calendar.js +74 -0
  324. package/dist/util/cn.d.ts +7 -0
  325. package/dist/util/cn.js +48 -0
  326. package/dist/util/collapsible.d.ts +57 -0
  327. package/dist/util/collapsible.js +23 -0
  328. package/dist/util/confirm-dialog.d.ts +11 -0
  329. package/dist/util/confirm-dialog.js +2 -0
  330. package/dist/util/date.d.ts +95 -0
  331. package/dist/util/date.js +237 -0
  332. package/dist/util/drawer.d.ts +12 -0
  333. package/dist/util/drawer.js +7 -0
  334. package/dist/util/dropdown.d.ts +46 -0
  335. package/dist/util/dropdown.js +21 -0
  336. package/dist/util/field.d.ts +38 -0
  337. package/dist/util/field.js +38 -0
  338. package/dist/util/fit-in-viewport.d.ts +200 -0
  339. package/dist/util/fit-in-viewport.js +399 -0
  340. package/dist/util/focus-utils.d.ts +75 -2
  341. package/dist/util/focus-utils.js +182 -9
  342. package/dist/util/hydration.d.ts +44 -0
  343. package/dist/util/hydration.js +58 -0
  344. package/dist/util/keyboard-inset.d.ts +17 -0
  345. package/dist/util/keyboard-inset.js +53 -0
  346. package/dist/util/media-grid.d.ts +50 -0
  347. package/dist/util/media-grid.js +81 -0
  348. package/dist/util/media.d.ts +5 -0
  349. package/dist/util/media.js +10 -0
  350. package/dist/util/overlay-chrome.d.ts +52 -0
  351. package/dist/util/overlay-chrome.js +56 -0
  352. package/dist/util/overlay.d.ts +71 -0
  353. package/dist/util/overlay.js +240 -0
  354. package/dist/util/photo.d.ts +214 -0
  355. package/dist/util/photo.js +206 -0
  356. package/dist/util/portal.d.ts +22 -0
  357. package/dist/util/portal.js +50 -0
  358. package/dist/util/pull-to-refresh.d.ts +26 -0
  359. package/dist/util/pull-to-refresh.js +30 -0
  360. package/dist/util/radio-keys.d.ts +24 -0
  361. package/dist/util/radio-keys.js +49 -0
  362. package/dist/util/rating.d.ts +22 -0
  363. package/dist/util/rating.js +26 -0
  364. package/dist/util/ready-made-strings.d.ts +76 -0
  365. package/dist/util/ready-made-strings.js +67 -0
  366. package/dist/util/scroll-padding.d.ts +19 -0
  367. package/dist/util/scroll-padding.js +44 -0
  368. package/dist/util/segmented-control.d.ts +15 -0
  369. package/dist/util/segmented-control.js +1 -0
  370. package/dist/util/select.d.ts +40 -0
  371. package/dist/util/select.js +63 -0
  372. package/dist/util/sheet-drag.d.ts +59 -0
  373. package/dist/util/sheet-drag.js +241 -0
  374. package/dist/util/sidebar.d.ts +62 -0
  375. package/dist/util/sidebar.js +38 -0
  376. package/dist/util/sortable-list.d.ts +64 -0
  377. package/dist/util/sortable-list.js +53 -0
  378. package/dist/util/ssr-safe.d.ts +4 -4
  379. package/dist/util/stepper.d.ts +39 -0
  380. package/dist/util/stepper.js +38 -0
  381. package/dist/util/swipeable-list-item.d.ts +35 -0
  382. package/dist/util/swipeable-list-item.js +32 -0
  383. package/dist/util/theme-mode.d.ts +79 -0
  384. package/dist/util/theme-mode.js +121 -0
  385. package/dist/util/toaster.d.ts +92 -0
  386. package/dist/util/toaster.js +75 -0
  387. package/dist/util/tooltip.d.ts +9 -0
  388. package/dist/util/tooltip.js +18 -0
  389. package/dist/util/top-navbar.d.ts +10 -0
  390. package/dist/util/top-navbar.js +5 -0
  391. package/dist/util/touch-target.d.ts +30 -0
  392. package/dist/util/touch-target.js +30 -0
  393. package/dist/util/zabi-strings.d.ts +132 -0
  394. package/dist/util/zabi-strings.js +105 -0
  395. package/dist/zabi-components-colors.css +5 -2
  396. package/dist/zabi-components-theme-dark-only.css +574 -225
  397. package/dist/zabi-components-theme-dark.css +574 -225
  398. package/dist/zabi-components-theme-only.css +1680 -317
  399. package/dist/zabi-components-theme.css +1680 -317
  400. package/dist/zabi-components.css +4473 -1158
  401. package/docs/lucide-icons.md +41 -16
  402. package/docs/theme-imports.md +81 -11
  403. package/package.json +51 -29
  404. package/dist/lib/layout-width-tokens.d.ts +0 -17
  405. package/dist/lib/layout-width-tokens.d.ts.map +0 -1
  406. package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
  407. package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
  408. package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
  409. package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
  410. package/dist/lib/showcase/component-docs/List.d.ts +0 -3
  411. package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
  412. package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
  413. package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
  414. package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
  415. package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
  416. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
  417. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
  418. package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
  419. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
  420. package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
  421. package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
  422. package/dist/lib/showcase/component-docs/index.d.ts +0 -4
  423. package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
  424. package/dist/lib/showcase/components-catalog.d.ts +0 -3
  425. package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
  426. package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
  427. package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
  428. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
  429. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
  430. package/dist/routes/lib/focus-utils.d.ts +0 -12
  431. package/dist/routes/lib/focus-utils.ts +0 -110
  432. package/dist/routes/lib/ssr-safe.ts +0 -58
  433. package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
@@ -0,0 +1,53 @@
1
+ import type { HTMLInputAttributes } from "svelte/elements";
2
+ import type { SizeVariant } from "../types/variants.js";
3
+ /**
4
+ * A date, picked with the device's own date picker and styled like Input.
5
+ *
6
+ * It is a native `<input type="date">`: an iPhone shows its wheels or
7
+ * calendar, Android its calendar, a desktop browser its own. `value` is
8
+ * always `YYYY-MM-DD`, or `""` while empty, whatever the field shows.
9
+ *
10
+ * What the field shows is the browser's decision: the date in the format
11
+ * of the browser or device, not of the page, and no attribute changes
12
+ * that. To show a date as text in the page's language, use `formatDate`:
13
+ *
14
+ * ```svelte
15
+ * <DateField label="Date" bind:value={date} min="2026-01-01" />
16
+ * <p>{formatDate(date, "sv")}</p> <!-- 6 okt. 2026 -->
17
+ * ```
18
+ *
19
+ * Other attributes (`autocomplete`, `data-*`, `onchange`, ...) land on the
20
+ * `<input>`.
21
+ */
22
+ type Props = Omit<HTMLInputAttributes, "class" | "type" | "value" | "size" | "min" | "max" | "step" | "required" | "disabled" | "readonly" | "id" | "name"> & {
23
+ /** Omit to auto-generate; pair with FormField's `id` when used inside FormField. */
24
+ id?: string;
25
+ /** The date as `YYYY-MM-DD`, or `""` while empty. Supports `bind:value`. */
26
+ value?: string;
27
+ /** Name the value is submitted under in a form. */
28
+ name?: string;
29
+ label?: string;
30
+ /** True when FormField supplies the visible `<label>`. */
31
+ hideLabel?: boolean;
32
+ /** Help text under the field, read out with it. */
33
+ hint?: string;
34
+ /** An error under the field. It marks the field invalid and is announced. */
35
+ error?: string;
36
+ /** Earliest date that can be picked, as `YYYY-MM-DD`. */
37
+ min?: string;
38
+ /** Latest date that can be picked, as `YYYY-MM-DD`. */
39
+ max?: string;
40
+ /** Steps between dates that can be picked, in days. */
41
+ step?: number | "any";
42
+ required?: boolean;
43
+ disabled?: boolean;
44
+ /** Shows the value without letting it be changed; it is still submitted. */
45
+ readonly?: boolean;
46
+ /** Height, on the scale Input and Button use. At least 44px on a touch screen. */
47
+ size?: SizeVariant;
48
+ /** Extra classes for the `<input>`. */
49
+ class?: string;
50
+ };
51
+ declare const DateField: import("svelte").Component<Props, {}, "value">;
52
+ type DateField = ReturnType<typeof DateField>;
53
+ export default DateField;
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { cn } from "../util/cn.js";
3
+
2
4
  type Orientation = 'horizontal' | 'vertical';
3
5
 
4
6
  interface Props {
@@ -34,13 +36,11 @@
34
36
  </script>
35
37
 
36
38
  <div
37
- class={[
39
+ class={cn(
38
40
  'flex items-center text-description',
39
41
  isVertical ? 'h-full min-h-[1rem] flex-col' : 'w-full flex-row',
40
42
  className,
41
- ]
42
- .filter(Boolean)
43
- .join(' ')}
43
+ )}
44
44
  >
45
45
  <div
46
46
  aria-hidden={decorative ? 'true' : undefined}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Component, Snippet } from "svelte";
3
+ import { cn } from "../util/cn.js";
3
4
 
4
5
  interface Props {
5
6
  title: string;
@@ -19,18 +20,18 @@
19
20
  </script>
20
21
 
21
22
  <article
22
- class="flex flex-col gap-3 p-6 rounded-2xl bg-surface-1 border border-base-200 transition-colors duration-200 hover:border-base-300 {className}"
23
+ class={cn("flex flex-col gap-3 p-6 rounded-container bg-surface-1 border border-border transition-colors duration-200 hover:border-border-medium", className)}
23
24
  >
24
25
  {#if Icon}
25
26
  <span
26
- class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-brand-50 text-brand-600"
27
+ class="inline-flex items-center justify-center w-10 h-10 rounded-control bg-brand-50 text-brand-600"
27
28
  aria-hidden="true"
28
29
  >
29
30
  <Icon size={20} />
30
31
  </span>
31
32
  {/if}
32
33
 
33
- <div class="flex flex-col gap-1.5">
34
+ <div class="flex flex-col gap-2">
34
35
  <h3 class="text-base font-semibold leading-snug text-headline">
35
36
  {title}
36
37
  </h3>
@@ -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,174 @@
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: 65px"
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 text-action-primary",
129
+ "transition-[background-color,scale] duration-150 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
+ {...restProps}
160
+ >
161
+ {@render content()}
162
+ </a>
163
+ {:else}
164
+ <button
165
+ bind:this={host}
166
+ type="button"
167
+ class={classes}
168
+ aria-label={extended ? undefined : label}
169
+ data-position={position}
170
+ {...restProps}
171
+ >
172
+ {@render content()}
173
+ </button>
174
+ {/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: 65px"
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;
@@ -1,38 +1,99 @@
1
1
  <script lang="ts">
2
- interface Props {
3
- level?: number;
4
- text: string;
5
- }
6
-
7
- let { level = 1, text }: Props = $props();
8
-
9
- const fontWeight = $derived(level <= 2 ? "700" : "500");
10
- const Tag = $derived(`h${level}`);
11
-
12
- const sizeClasses = {
13
- 1: "text-4xl",
14
- 2: "text-3xl",
15
- 3: "text-2xl",
16
- 4: "text-xl",
17
- 5: "text-lg",
18
- 6: "text-base",
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import { cn } from "../util/cn.js";
5
+ import type { OnFillTone } from "../types/variants.js";
6
+
7
+ /** Semantic heading level — also the default visual size. */
8
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
9
+
10
+ type Props = Omit<HTMLAttributes<HTMLHeadingElement>, "class"> & {
11
+ level?: HeadingLevel;
12
+ /**
13
+ * Visual size, when it should differ from the semantic level — an `h2`
14
+ * that needs to look like an `h4` keeps the document outline correct
15
+ * without forcing the wrong tag.
16
+ */
17
+ size?: HeadingLevel;
18
+ /** Heading text. Ignored when `children` is provided. */
19
+ text?: string;
20
+ /**
21
+ * Text colour. `headline` (the default) is for a page or a card. On a
22
+ * filled block use `inherit` (the block's own colour), `on-brand` or
23
+ * `on-accent`.
24
+ */
25
+ tone?: "headline" | OnFillTone;
26
+ class?: string;
27
+ children?: Snippet;
28
+ };
29
+
30
+ let {
31
+ level = 1,
32
+ size,
33
+ text = "",
34
+ tone = "headline",
35
+ class: className = "",
36
+ children,
37
+ ...restProps
38
+ }: Props = $props();
39
+
40
+ const toneClasses: Record<"headline" | OnFillTone, string> = {
41
+ headline: "text-headline",
42
+ inherit: "text-inherit",
43
+ "on-brand": "text-on-brand",
44
+ "on-accent": "text-on-accent",
19
45
  };
20
46
 
21
- const sizeClass = $derived(
22
- sizeClasses[level as keyof typeof sizeClasses] || "text-base",
47
+ const visualSize = $derived(size ?? level);
48
+
49
+ /**
50
+ * Display sizes tighten as they grow. Untracked 36px type with default
51
+ * 40px leading is the single clearest "this is stock Tailwind" tell;
52
+ * negative tracking and a tighter leading is most of what separates
53
+ * display type from body type set large.
54
+ */
55
+ const sizeClasses: Record<HeadingLevel, string> = {
56
+ 1: "text-4xl leading-10 tracking-[-0.02em] font-bold",
57
+ 2: "text-3xl leading-9 tracking-[-0.015em] font-bold",
58
+ 3: "text-2xl leading-8 tracking-[-0.01em] font-semibold",
59
+ 4: "text-xl leading-7 tracking-[-0.005em] font-semibold",
60
+ 5: "text-lg leading-7 font-semibold",
61
+ 6: "text-base leading-6 font-semibold",
62
+ };
63
+
64
+ const headingClasses = $derived(
65
+ cn(`${toneClasses[tone] ?? toneClasses.headline} ${sizeClasses[visualSize] ?? sizeClasses[6]} ${className}`),
23
66
  );
24
67
  </script>
25
68
 
26
- <svelte:element
27
- this={Tag}
28
- class="text-headline font-nunito {sizeClass}"
29
- style="font-weight: {fontWeight};"
30
- >
31
- {text}
32
- </svelte:element>
33
-
34
- <style>
35
- .font-nunito {
36
- font-family: "Nunito Sans", ui-sans-serif, system-ui, sans-serif;
37
- }
38
- </style>
69
+ <!--
70
+ The font family comes from `--font-family-heading`, which the theme applies
71
+ to h1–h6 in its base layer and which defaults to `--font-family-sans`. This
72
+ component used to carry its own `font-family: "Nunito Sans"` in a local
73
+ <style> block, which meant a consumer who rebranded the font token got a
74
+ rebranded body and Nunito Sans headings.
75
+ -->
76
+ {#snippet content()}
77
+ {#if children}
78
+ {@render children()}
79
+ {:else}
80
+ {text}
81
+ {/if}
82
+ {/snippet}
83
+
84
+ <!-- One branch per level, not a dynamic element: hydration takes a dynamic
85
+ element out and puts it back, which blurs a control inside it that the user
86
+ had already tabbed to. -->
87
+ {#if level === 1}
88
+ <h1 class={headingClasses} {...restProps}>{@render content()}</h1>
89
+ {:else if level === 2}
90
+ <h2 class={headingClasses} {...restProps}>{@render content()}</h2>
91
+ {:else if level === 3}
92
+ <h3 class={headingClasses} {...restProps}>{@render content()}</h3>
93
+ {:else if level === 4}
94
+ <h4 class={headingClasses} {...restProps}>{@render content()}</h4>
95
+ {:else if level === 5}
96
+ <h5 class={headingClasses} {...restProps}>{@render content()}</h5>
97
+ {:else}
98
+ <h6 class={headingClasses} {...restProps}>{@render content()}</h6>
99
+ {/if}
@@ -1,7 +1,27 @@
1
- interface Props {
2
- level?: number;
3
- text: string;
4
- }
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { OnFillTone } from "../types/variants.js";
4
+ /** Semantic heading level — also the default visual size. */
5
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
6
+ type Props = Omit<HTMLAttributes<HTMLHeadingElement>, "class"> & {
7
+ level?: HeadingLevel;
8
+ /**
9
+ * Visual size, when it should differ from the semantic level — an `h2`
10
+ * that needs to look like an `h4` keeps the document outline correct
11
+ * without forcing the wrong tag.
12
+ */
13
+ size?: HeadingLevel;
14
+ /** Heading text. Ignored when `children` is provided. */
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
+ class?: string;
23
+ children?: Snippet;
24
+ };
5
25
  declare const Heading: import("svelte").Component<Props, {}, "">;
6
26
  type Heading = ReturnType<typeof Heading>;
7
27
  export default Heading;