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
@@ -1,13 +1,45 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from "svelte";
3
- import type { HTMLButtonAttributes } from "svelte/elements";
2
+ import { tick, untrack, type Snippet } from "svelte";
3
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
4
4
  import type { ButtonVariant, SizeVariant } from "../types/variants.js";
5
+ import { cn } from "../util/cn.js";
5
6
 
7
+ /**
8
+ * One type for the button and the link, not a union of the two elements'
9
+ * attributes: that union is too large for TypeScript to carry through a
10
+ * wrapper such as a Storybook `Meta`. Event handlers are typed for the
11
+ * button; on a link they are called with the same event.
12
+ */
6
13
  type Props = Omit<HTMLButtonAttributes, "class"> & {
14
+ /**
15
+ * Where the link goes. Makes this an `<a>` that looks the same;
16
+ * without it, it is a `<button>`. A link cannot be disabled: while
17
+ * `disabled` or `loading` it has no `href`, is `aria-disabled` and
18
+ * does nothing when pressed. While `loading` it stays a Tab stop.
19
+ */
20
+ href?: string;
21
+ /** With `href`: where the link opens. */
22
+ target?: HTMLAnchorAttributes["target"];
23
+ /** With `href`: the link's relationship, such as `noopener`. */
24
+ rel?: HTMLAnchorAttributes["rel"];
25
+ /** With `href`: download the address instead of opening it. */
26
+ download?: HTMLAnchorAttributes["download"];
27
+ hreflang?: HTMLAnchorAttributes["hreflang"];
28
+ referrerpolicy?: HTMLAnchorAttributes["referrerpolicy"];
29
+ ping?: HTMLAnchorAttributes["ping"];
7
30
  variant?: ButtonVariant;
8
31
  size?: SizeVariant;
32
+ /**
33
+ * Shows a spinner and makes the button unavailable without taking
34
+ * focus from it: `aria-disabled` and `aria-busy`, still a Tab stop,
35
+ * and a press does nothing. It is not `disabled`: match it in CSS
36
+ * with `[aria-busy="true"]`.
37
+ */
9
38
  loading?: boolean;
10
39
  text?: string;
40
+ /** Stretch to the width of the container. */
41
+ fullWidth?: boolean;
42
+ /** @deprecated use `fullWidth` — kept so 7.x call sites keep working. */
11
43
  isFullWidth?: boolean;
12
44
  class?: string;
13
45
  children?: Snippet;
@@ -18,8 +50,10 @@
18
50
  size = "md",
19
51
  disabled = false,
20
52
  loading = false,
21
- type = "button",
53
+ type,
54
+ href,
22
55
  text = "",
56
+ fullWidth = false,
23
57
  isFullWidth = false,
24
58
  class: className = "",
25
59
  onclick,
@@ -27,79 +61,163 @@
27
61
  ...restProps
28
62
  }: Props = $props();
29
63
 
30
- const isDisabled = $derived(disabled || loading);
64
+ const isDisabled = $derived(!!disabled || loading);
65
+ /**
66
+ * Loading without `disabled`: unavailable, and still where focus is.
67
+ *
68
+ * `disabled` on a button, or no `href` on a link, makes the element
69
+ * unfocusable, and the browser then drops focus on `<body>`: whoever
70
+ * pressed Save with the keyboard was at the top of the page when the
71
+ * save came back. So a loading control says it the ARIA way
72
+ * (`aria-disabled`, `aria-busy`), stays a Tab stop, and swallows the
73
+ * press. `disabled` is the platform's: out of the Tab order.
74
+ */
75
+ const isBusy = $derived(loading && !disabled);
76
+ const isLink = $derived(href !== undefined && href !== null);
31
77
 
78
+ /**
79
+ * A loading link has no `href`, so it is a Tab stop only by `tabindex`.
80
+ * The browser takes focus from an element the moment it stops being
81
+ * focusable, so the two attributes must never both be missing: the
82
+ * `tabindex` goes on before the `href` comes off (their order in the
83
+ * markup), and when loading ends it stays until the `href` is back, one
84
+ * update longer.
85
+ */
86
+ let keepsTabStop = $state(false);
87
+ $effect.pre(() => {
88
+ if (isBusy) {
89
+ keepsTabStop = true;
90
+ } else if (untrack(() => keepsTabStop)) {
91
+ void tick().then(() => (keepsTabStop = false));
92
+ }
93
+ });
94
+
95
+ /** A text link in a sentence: `variant="link"` on a real link. */
96
+ const isInlineLink = $derived(isLink && variant === "link");
97
+
98
+ /** A disabled link has no `href`, but a pointer can still press it. */
99
+ function handleLinkClick(event: MouseEvent) {
100
+ if (isDisabled) {
101
+ event.preventDefault();
102
+ event.stopPropagation();
103
+ return;
104
+ }
105
+ (onclick as ((event: MouseEvent) => void) | null | undefined)?.(event);
106
+ }
107
+
108
+ /** A loading button is not `disabled`, so the press reaches it: it ends here, and submits nothing. */
109
+ function handleButtonClick(event: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement }) {
110
+ if (isBusy) {
111
+ event.preventDefault();
112
+ event.stopPropagation();
113
+ return;
114
+ }
115
+ onclick?.(event);
116
+ }
117
+
118
+ /**
119
+ * One height scale, shared with Input, Select and IconButton, so controls
120
+ * of the same size line up in a row: 32 / 40 / 48px. It is a minimum
121
+ * height, not a fixed one, so a label that does not fit on one line wraps
122
+ * and the button grows; it used to paint outside the button (a long label
123
+ * at 200% text on a phone). One line of text and the vertical padding are
124
+ * never taller than the minimum, the outline variant's border included,
125
+ * so a label that fits is exactly on the scale:
126
+ * scripts/check-control-geometry.js holds the padding to that.
127
+ *
128
+ * Weight and tracking do NOT change with size: a large button used to drop
129
+ * to font-normal, so the biggest button had the lightest label.
130
+ */
32
131
  const sizeClass = $derived.by(() => {
132
+ // sm and md are 44px tall on a coarse pointer, like Input, Select and
133
+ // IconButton, so a row still lines up and the box is the touch target.
134
+ // No minimum width: text and padding make a button wide enough, and one
135
+ // would stop the actions of an ImageUpload shrinking in a narrow space.
33
136
  if (size === "sm") {
34
- return {
35
- padding: "px-4 py-2.5",
36
- text: "text-sm",
37
- font: "font-medium",
38
- leading: "leading-5",
39
- tracking: "tracking-[0.1px]",
40
- radius: "rounded-lg",
41
- gap: "gap-2",
42
- };
43
- } else if (size === "lg") {
44
- return {
45
- padding: "px-8 py-4",
46
- text: "text-lg",
47
- font: "font-normal",
48
- leading: "leading-8",
49
- tracking: "tracking-normal",
50
- radius: "rounded-xl",
51
- gap: "gap-3",
52
- };
53
- } else {
54
- return {
55
- padding: "px-5 py-3",
56
- text: "text-base",
57
- font: "font-medium",
58
- leading: "leading-6",
59
- tracking: "tracking-[0.15px]",
60
- radius: "rounded-lg",
61
- gap: "gap-2",
62
- };
137
+ return { box: "min-h-8 px-3 py-1 pointer-coarse:min-h-11", text: "text-sm", gap: "gap-2", spinner: "size-3.5" };
63
138
  }
139
+ if (size === "lg") {
140
+ return { box: "min-h-12 px-5 py-2", text: "text-base", gap: "gap-2", spinner: "size-4" };
141
+ }
142
+ return { box: "min-h-10 px-4 py-2 pointer-coarse:min-h-11", text: "text-sm", gap: "gap-2", spinner: "size-4" };
64
143
  });
65
144
 
145
+ /**
146
+ * Disabled is deliberately NOT `opacity-50` over the variant colour — that
147
+ * made a disabled primary lighter and more colourful than an enabled
148
+ * secondary. Every variant falls back to the same neutral disabled pair.
149
+ */
150
+ const DISABLED_LINK =
151
+ "bg-(--color-action-disabled) text-action-disabled-text border border-transparent cursor-not-allowed";
152
+
153
+ /**
154
+ * A loading button wears the same pair. It is not `:disabled`, so the
155
+ * variant's classes are left out instead of overridden: none of its hover
156
+ * or pressed fills can show. What the variant gives the box is kept (the
157
+ * outline's border, the link's missing side padding), so the button does
158
+ * not change size when it starts loading. It keeps the focus ring: it
159
+ * can be focused.
160
+ */
161
+ const BUSY_BUTTON = "bg-(--color-action-disabled) text-action-disabled-text cursor-not-allowed";
162
+
163
+ const disabledClass =
164
+ "disabled:bg-action-disabled disabled:text-action-disabled-text disabled:border-transparent disabled:no-underline disabled:shadow-none disabled:cursor-not-allowed disabled:active:scale-100";
165
+
66
166
  const variantClass = $derived.by(() => {
67
- return variant === "primary"
68
- ? "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98]"
69
- : variant === "secondary"
70
- ? "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active active:scale-[0.98]"
71
- : variant === "danger"
72
- ? "bg-action-danger text-inverse hover:bg-action-danger-hover active:bg-action-danger-active active:scale-[0.98] focus-ring--danger"
73
- : variant === "ghost"
74
- ? "bg-transparent text-headline hover:bg-base-100 active:bg-base-200 active:scale-[0.98] focus-ring--muted disabled:text-disabled"
75
- : variant === "outline"
76
- ? "bg-transparent border border-border text-headline hover:bg-action-secondary hover:border-border-medium hover:text-action-primary active:bg-action-primary-active active:text-action-primary active:scale-[0.98] disabled:border-disabled disabled:text-disabled"
77
- : variant === "link"
78
- ? "text-brand-700 active:text-brand-700 underline-offset-4 hover:underline disabled:text-disabled disabled:no-underline"
79
- : "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98]";
167
+ switch (variant) {
168
+ case "secondary":
169
+ return "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active active:scale-[0.98] motion-reduce:active:scale-100";
170
+ case "danger":
171
+ return "bg-action-danger text-action-danger-text hover:bg-action-danger-hover active:bg-action-danger-active active:scale-[0.98] motion-reduce:active:scale-100 focus-ring--danger";
172
+ case "ghost":
173
+ return "bg-transparent text-headline hover:bg-surface-hover active:bg-surface-active active:scale-[0.98] motion-reduce:active:scale-100 focus-ring--muted";
174
+ case "outline":
175
+ return "bg-transparent border border-border text-headline hover:bg-surface-hover hover:border-border-medium active:bg-surface-active active:scale-[0.98] motion-reduce:active:scale-100";
176
+ case "link":
177
+ // A link variant has to look like a link at rest, not only on
178
+ // hover — otherwise it is indistinguishable from `ghost`.
179
+ return "bg-transparent text-link hover:text-link-hover underline underline-offset-4 decoration-1 hover:decoration-2 px-0 focus-ring--muted";
180
+ case "accent":
181
+ return "bg-accent text-on-accent hover:bg-accent-hover active:bg-accent-active active:scale-[0.98] motion-reduce:active:scale-100";
182
+ case "primary":
183
+ default:
184
+ return "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98] motion-reduce:active:scale-100";
185
+ }
80
186
  });
81
187
 
82
188
  const buttonClasses = $derived.by(() => {
83
- const sizeStyles = sizeClass;
84
- const flexClass = isFullWidth ? "flex" : "inline-flex";
85
- const widthClass = isFullWidth ? "w-full" : "";
86
- const baseClasses = `${flexClass} focus-ring items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100`;
87
-
88
- return `${baseClasses} ${widthClass} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.font} ${sizeStyles.leading} ${sizeStyles.tracking} ${sizeStyles.radius} ${sizeStyles.gap} ${variantClass} ${className}`.trim();
189
+ const s = sizeClass;
190
+ const layout = fullWidth || isFullWidth ? "flex w-full" : "inline-flex";
191
+ if (isInlineLink) {
192
+ // Flows with the sentence it is in: no box, no minimum height, and
193
+ // it breaks across lines as a link does.
194
+ const state = isDisabled
195
+ ? "text-action-disabled-text no-underline cursor-not-allowed"
196
+ : "text-link hover:text-link-hover underline underline-offset-4 decoration-1 hover:decoration-2 cursor-pointer";
197
+ return cn(
198
+ `focus-ring focus-ring--muted inline rounded-control font-medium transition-colors duration-150 ${s.text} ${state} ${fullWidth || isFullWidth ? "block w-full" : ""} ${className}`,
199
+ );
200
+ }
201
+ // The label wraps when it has to, balanced over its lines; the icon
202
+ // and the spinner stay centred beside it.
203
+ const base = `${layout} focus-ring items-center justify-center rounded-control text-center font-medium text-balance transition-colors duration-150 cursor-pointer select-none`;
204
+ // `disabled:` does not match an `<a>`: a disabled link wears the
205
+ // disabled pair itself, in place of its variant.
206
+ const busyBox = variant === "outline" ? "border border-transparent" : variant === "link" ? "px-0" : "";
207
+ const state =
208
+ isLink && isDisabled
209
+ ? DISABLED_LINK
210
+ : isBusy
211
+ ? `${BUSY_BUTTON} ${busyBox}`
212
+ : `${variantClass} ${disabledClass}`;
213
+ return cn(`${base} ${s.box} ${s.text} ${s.gap} ${state} ${className}`);
89
214
  });
90
215
  </script>
91
216
 
92
- <button
93
- {type}
94
- class={buttonClasses}
95
- disabled={isDisabled}
96
- aria-busy={loading ? "true" : undefined}
97
- {onclick}
98
- {...restProps}
99
- >
217
+ {#snippet content()}
100
218
  {#if loading}
101
219
  <span
102
- class="inline-block size-4 shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-80"
220
+ class="inline-block {sizeClass.spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-80 motion-reduce:animate-pulse"
103
221
  aria-hidden="true"
104
222
  ></span>
105
223
  {/if}
@@ -108,4 +226,36 @@
108
226
  {:else if children}
109
227
  {@render children()}
110
228
  {/if}
111
- </button>
229
+ {/snippet}
230
+
231
+ {#if isLink}
232
+ <!-- A link that cannot be followed keeps its role and says it is
233
+ unavailable; without an `href` it is not a Tab stop, as a disabled button
234
+ is not. A loading one is given the Tab stop back, so focus stays on it:
235
+ `tabindex` is written before `href` is taken away, in that order (see
236
+ `keepsTabStop`). -->
237
+ <a
238
+ tabindex={isBusy || keepsTabStop ? 0 : undefined}
239
+ href={isDisabled ? undefined : href}
240
+ class={buttonClasses}
241
+ role={isDisabled ? "link" : undefined}
242
+ aria-disabled={isDisabled ? "true" : undefined}
243
+ aria-busy={loading ? "true" : undefined}
244
+ {...(restProps as unknown as HTMLAnchorAttributes)}
245
+ onclick={handleLinkClick}
246
+ >
247
+ {@render content()}
248
+ </a>
249
+ {:else}
250
+ <button
251
+ type={type ?? "button"}
252
+ class={buttonClasses}
253
+ disabled={!!disabled}
254
+ aria-disabled={isBusy ? "true" : undefined}
255
+ aria-busy={loading ? "true" : undefined}
256
+ {...restProps}
257
+ onclick={handleButtonClick}
258
+ >
259
+ {@render content()}
260
+ </button>
261
+ {/if}
@@ -1,11 +1,42 @@
1
- import type { Snippet } from "svelte";
2
- import type { HTMLButtonAttributes } from "svelte/elements";
1
+ import { type Snippet } from "svelte";
2
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
3
3
  import type { ButtonVariant, SizeVariant } from "../types/variants.js";
4
+ /**
5
+ * One type for the button and the link, not a union of the two elements'
6
+ * attributes: that union is too large for TypeScript to carry through a
7
+ * wrapper such as a Storybook `Meta`. Event handlers are typed for the
8
+ * button; on a link they are called with the same event.
9
+ */
4
10
  type Props = Omit<HTMLButtonAttributes, "class"> & {
11
+ /**
12
+ * Where the link goes. Makes this an `<a>` that looks the same;
13
+ * without it, it is a `<button>`. A link cannot be disabled: while
14
+ * `disabled` or `loading` it has no `href`, is `aria-disabled` and
15
+ * does nothing when pressed. While `loading` it stays a Tab stop.
16
+ */
17
+ href?: string;
18
+ /** With `href`: where the link opens. */
19
+ target?: HTMLAnchorAttributes["target"];
20
+ /** With `href`: the link's relationship, such as `noopener`. */
21
+ rel?: HTMLAnchorAttributes["rel"];
22
+ /** With `href`: download the address instead of opening it. */
23
+ download?: HTMLAnchorAttributes["download"];
24
+ hreflang?: HTMLAnchorAttributes["hreflang"];
25
+ referrerpolicy?: HTMLAnchorAttributes["referrerpolicy"];
26
+ ping?: HTMLAnchorAttributes["ping"];
5
27
  variant?: ButtonVariant;
6
28
  size?: SizeVariant;
29
+ /**
30
+ * Shows a spinner and makes the button unavailable without taking
31
+ * focus from it: `aria-disabled` and `aria-busy`, still a Tab stop,
32
+ * and a press does nothing. It is not `disabled`: match it in CSS
33
+ * with `[aria-busy="true"]`.
34
+ */
7
35
  loading?: boolean;
8
36
  text?: string;
37
+ /** Stretch to the width of the container. */
38
+ fullWidth?: boolean;
39
+ /** @deprecated use `fullWidth` — kept so 7.x call sites keep working. */
9
40
  isFullWidth?: boolean;
10
41
  class?: string;
11
42
  children?: Snippet;
@@ -1,72 +1,75 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
3
4
  import type { CardVariant, SizeVariant } from "../types/variants.js";
4
5
 
5
- interface Props {
6
+ import { cn } from "../util/cn.js";
7
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
6
8
  onclick?: (event: MouseEvent) => void | Promise<void>;
7
9
  size?: SizeVariant;
8
10
  variant?: CardVariant;
9
11
  fullWidth?: boolean;
12
+ class?: string;
13
+ /** @deprecated use `class` — kept so 7.x call sites keep working. */
10
14
  className?: string;
11
15
  /** Required when `onclick` is set (no visible label). */
12
16
  ariaLabel?: string;
13
17
  children?: Snippet;
14
- }
18
+ };
15
19
 
16
20
  let {
17
21
  onclick,
18
22
  size = "md",
19
23
  variant = "default",
20
24
  fullWidth = true,
25
+ class: classProp = "",
21
26
  className = "",
22
27
  ariaLabel,
23
28
  children,
24
29
  ...restProps
25
30
  }: Props = $props();
26
31
 
32
+ /** Size changes the padding. It does NOT change the corner — see
33
+ * `--radius-container`. A large card is a bigger box, not a rounder one. */
27
34
  const sizeClass = $derived(
28
35
  size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6",
29
36
  );
30
37
 
31
- const radiusClass = $derived(
32
- size === "sm"
33
- ? "rounded-xl"
34
- : size === "lg"
35
- ? "rounded-3xl"
36
- : "rounded-2xl",
37
- );
38
-
39
38
  const variantClasses = $derived(
40
39
  variant === "elevated"
41
- ? "bg-card shadow-lg"
40
+ ? "bg-card-elevated shadow-lg"
42
41
  : variant === "outlined"
43
- ? "bg-card border-2 border-base-200 shadow-none"
42
+ ? "bg-card border border-border shadow-none"
44
43
  : variant === "flat"
45
44
  ? "bg-card shadow-none border-none"
46
45
  : "bg-card shadow-sm",
47
46
  );
48
47
 
48
+ const interactiveClasses = $derived.by(() => {
49
+ if (!onclick) return "";
50
+ const shared = "cursor-pointer focus-ring";
51
+ switch (variant) {
52
+ case "elevated":
53
+ return `${shared} hover:bg-card-hover active:bg-card-active`;
54
+ case "outlined":
55
+ return `${shared} hover:border-border-medium hover:bg-card-hover active:bg-card-active`;
56
+ case "flat":
57
+ return `${shared} hover:bg-card-hover active:bg-card-active`;
58
+ default:
59
+ return `${shared} hover:shadow-lg hover:bg-card-hover active:bg-card-active`;
60
+ }
61
+ });
62
+
49
63
  const cardClasses = $derived(
50
- [
51
- radiusClass,
52
- "transition-all duration-200",
64
+ cn(
65
+ "rounded-container transition-all duration-150",
53
66
  variantClasses,
54
- onclick
55
- ? variant === "elevated"
56
- ? "cursor-pointer hover:shadow-xl hover:bg-card-hover"
57
- : variant === "outlined"
58
- ? "cursor-pointer hover:border-brand-500 hover:bg-card-hover"
59
- : variant === "flat"
60
- ? "cursor-pointer hover:bg-card-hover"
61
- : "cursor-pointer hover:shadow-lg hover:bg-card-hover"
62
- : "",
67
+ interactiveClasses,
63
68
  fullWidth ? "w-full" : "",
64
69
  sizeClass,
70
+ classProp,
65
71
  className,
66
- ]
67
- .filter(Boolean)
68
- .join(" ")
69
- .trim(),
72
+ ),
70
73
  );
71
74
 
72
75
  function handleKeydown(event: KeyboardEvent) {
@@ -1,15 +1,18 @@
1
1
  import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
2
3
  import type { CardVariant, SizeVariant } from "../types/variants.js";
3
- interface Props {
4
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
4
5
  onclick?: (event: MouseEvent) => void | Promise<void>;
5
6
  size?: SizeVariant;
6
7
  variant?: CardVariant;
7
8
  fullWidth?: boolean;
9
+ class?: string;
10
+ /** @deprecated use `class` — kept so 7.x call sites keep working. */
8
11
  className?: string;
9
12
  /** Required when `onclick` is set (no visible label). */
10
13
  ariaLabel?: string;
11
14
  children?: Snippet;
12
- }
15
+ };
13
16
  declare const Card: import("svelte").Component<Props, {}, "">;
14
17
  type Card = ReturnType<typeof Card>;
15
18
  export default Card;
@@ -1,9 +1,12 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+ import { cn } from "../util/cn.js";
3
4
 
4
5
  interface Props {
5
6
  image?: string;
6
7
  imageAlt?: string;
8
+ class?: string;
9
+ /** @deprecated use `class`. */
7
10
  className?: string;
8
11
  children?: Snippet;
9
12
  }
@@ -11,10 +14,15 @@
11
14
  let {
12
15
  image = "",
13
16
  imageAlt = "",
14
- className = "",
17
+ class: classAttr = "",
18
+ className: legacyClass = "",
15
19
  children,
16
20
  ...restProps
17
21
  }: Props = $props();
22
+
23
+ /** `class` is the public prop; `className` is a deprecated alias.
24
+ * Both are merged here so existing call sites keep working. */
25
+ const className = $derived(cn(`${classAttr} ${legacyClass}`));
18
26
  </script>
19
27
 
20
28
  <div class={className} {...restProps}>
@@ -22,7 +30,7 @@
22
30
  <img
23
31
  src={image}
24
32
  alt={imageAlt}
25
- class="w-full h-48 object-cover rounded-lg mb-4"
33
+ class="w-full h-48 object-cover rounded-control mb-4"
26
34
  />
27
35
  {/if}
28
36
  {#if children}
@@ -2,6 +2,8 @@ import type { Snippet } from "svelte";
2
2
  interface Props {
3
3
  image?: string;
4
4
  imageAlt?: string;
5
+ class?: string;
6
+ /** @deprecated use `class`. */
5
7
  className?: string;
6
8
  children?: Snippet;
7
9
  }
@@ -1,20 +1,28 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+ import { cn } from "../util/cn.js";
3
4
 
4
5
  interface Props {
6
+ class?: string;
7
+ /** @deprecated use `class`. */
5
8
  className?: string;
6
9
  children?: Snippet;
7
10
  }
8
11
 
9
12
  let {
10
- className = "",
13
+ class: classAttr = "",
14
+ className: legacyClass = "",
11
15
  children,
12
16
  ...restProps
13
17
  }: Props = $props();
18
+
19
+ /** `class` is the public prop; `className` is a deprecated alias.
20
+ * Both are merged here so existing call sites keep working. */
21
+ const className = $derived(cn(`${classAttr} ${legacyClass}`));
14
22
  </script>
15
23
 
16
24
  <footer
17
- class="flex items-center p-6 pt-0 {className}"
25
+ class={cn("flex items-center p-6 pt-0", className)}
18
26
  {...restProps}
19
27
  >
20
28
  {#if children}
@@ -1,5 +1,7 @@
1
1
  import type { Snippet } from "svelte";
2
2
  interface Props {
3
+ class?: string;
4
+ /** @deprecated use `class`. */
3
5
  className?: string;
4
6
  children?: Snippet;
5
7
  }
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+ import { cn } from "../util/cn.js";
3
4
 
4
5
  interface Props {
5
6
  title?: string;
@@ -10,6 +11,8 @@
10
11
  /** `id` on the description `<p>`. */
11
12
  descriptionId?: string;
12
13
  level?: 1 | 2 | 3 | 4 | 5 | 6;
14
+ class?: string;
15
+ /** @deprecated use `class`. */
13
16
  className?: string;
14
17
  children?: Snippet;
15
18
  }
@@ -21,22 +24,39 @@
21
24
  subtitleId,
22
25
  descriptionId,
23
26
  level = 3,
24
- className = "",
27
+ class: classAttr = "",
28
+ className: legacyClass = "",
25
29
  children,
26
30
  ...restProps
27
31
  }: Props = $props();
28
32
 
29
- const headingTag = $derived(`h${level}`);
33
+ /** `class` is the public prop; `className` is a deprecated alias.
34
+ * Both are merged here so existing call sites keep working. */
35
+ const className = $derived(cn(`${classAttr} ${legacyClass}`));
36
+
30
37
  const headingClasses = $derived(() => {
31
38
  return "text-2xl font-semibold leading-none tracking-tight text-headline";
32
39
  });
33
40
  </script>
34
41
 
35
- <header class={`flex flex-col space-y-1.5 pb-4 ${className}`.trim()} {...restProps}>
42
+ <header class={cn(`flex flex-col space-y-2 pb-4 ${className}`)} {...restProps}>
36
43
  {#if title}
37
- <svelte:element this={headingTag} class={headingClasses()}>
38
- {title}
39
- </svelte:element>
44
+ <!-- One branch per level, not a dynamic element: hydration takes a dynamic
45
+ element out and puts it back, which blurs a control inside it that the user
46
+ had already tabbed to. -->
47
+ {#if level === 1}
48
+ <h1 class={headingClasses()}>{title}</h1>
49
+ {:else if level === 2}
50
+ <h2 class={headingClasses()}>{title}</h2>
51
+ {:else if level === 3}
52
+ <h3 class={headingClasses()}>{title}</h3>
53
+ {:else if level === 4}
54
+ <h4 class={headingClasses()}>{title}</h4>
55
+ {:else if level === 5}
56
+ <h5 class={headingClasses()}>{title}</h5>
57
+ {:else}
58
+ <h6 class={headingClasses()}>{title}</h6>
59
+ {/if}
40
60
  {/if}
41
61
  {#if children}
42
62
  {@render children()}
@@ -8,6 +8,8 @@ interface Props {
8
8
  /** `id` on the description `<p>`. */
9
9
  descriptionId?: string;
10
10
  level?: 1 | 2 | 3 | 4 | 5 | 6;
11
+ class?: string;
12
+ /** @deprecated use `class`. */
11
13
  className?: string;
12
14
  children?: Snippet;
13
15
  }