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,9 +1,37 @@
1
1
  <script lang="ts">
2
- import { CheckCircle, AlertTriangle, AlertCircle } from "@lucide/svelte";
2
+ import { zabiCommonStrings } from "../util/zabi-strings.js";
3
+ import Eye from "@lucide/svelte/icons/eye";
4
+ import EyeOff from "@lucide/svelte/icons/eye-off";
5
+ import { tick, type Snippet } from "svelte";
6
+ import type { HTMLInputAttributes } from "svelte/elements";
3
7
  import type { SemanticVariant, SizeVariant } from "../types/variants.js";
8
+ import { cn } from "../util/cn.js";
9
+ import { fieldDescribedBy, fieldMessageState } from "../util/field.js";
4
10
  import { generateId } from "../util/ssr-safe.js";
11
+ import { TOUCH_HIT_AREA } from "../util/touch-target.js";
12
+ import FieldMessages from "./FieldMessages.svelte";
13
+ import IconButton from "./IconButton.svelte";
5
14
 
6
- interface Props {
15
+ /**
16
+ * Other attributes (`autocomplete`, `inputmode`, `maxlength`,
17
+ * `enterkeyhint`, `data-*`, `onchange`, ...) land on the `<input>`.
18
+ */
19
+ type Props = Omit<
20
+ HTMLInputAttributes,
21
+ | "class"
22
+ | "size"
23
+ | "value"
24
+ | "type"
25
+ | "id"
26
+ | "name"
27
+ | "placeholder"
28
+ | "required"
29
+ | "disabled"
30
+ | "oninput"
31
+ | "onblur"
32
+ | "aria-label"
33
+ | "aria-describedby"
34
+ > & {
7
35
  /** Omit to auto-generate; pair with FormField `id` when used inside FormField. */
8
36
  id?: string;
9
37
  value?: string;
@@ -19,15 +47,39 @@
19
47
  loading?: boolean;
20
48
  size?: SizeVariant;
21
49
  variant?: SemanticVariant;
50
+ /** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
22
51
  message?: string;
52
+ /** Help text under the field, read out with it. */
53
+ hint?: string;
54
+ /** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
55
+ error?: string;
56
+ /**
57
+ * Drawn inside the field, before the text: an icon, a unit. Decoration
58
+ * lets a press through to the field; a button or link in it is pressable.
59
+ */
60
+ leading?: Snippet;
61
+ /** Drawn inside the field, after the text and before the loading spinner's place. */
62
+ trailing?: Snippet;
63
+ /**
64
+ * With `type="password"`: adds a button at the end of the field that
65
+ * shows the password as text, and hides it again. Ignored for other types.
66
+ */
67
+ revealable?: boolean;
68
+ /**
69
+ * Accessible name of that button. It stays the same in both states;
70
+ * the button's pressed state says whether the password is showing.
71
+ */
72
+ revealLabel?: string;
23
73
  oninput?: (event: Event) => void;
24
74
  onblur?: (event: Event) => void;
25
75
  "aria-label"?: string;
26
- }
76
+ /** Ids of other elements that describe the field. The hint and the message are added after them. */
77
+ "aria-describedby"?: string | null;
78
+ };
27
79
 
28
80
  let {
29
81
  id: idProp,
30
- value = $bindable(""),
82
+ value = $bindable<Exclude<Props["value"], undefined>>(),
31
83
  type = "text",
32
84
  name = "",
33
85
  class: className = "",
@@ -40,84 +92,186 @@
40
92
  size = "md",
41
93
  variant = "default",
42
94
  message = "",
95
+ hint = "",
96
+ error = "",
97
+ leading,
98
+ trailing,
99
+ revealable = false,
100
+ revealLabel: revealLabelGiven,
43
101
  oninput,
44
102
  onblur,
103
+ "aria-describedby": describedBy,
45
104
  ...restProps
46
105
  }: Props = $props();
47
106
 
107
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
108
+ // one that has a fallback (`props_invalid_value`), and a page that throws
109
+ // while it hydrates never becomes interactive. The default is applied
110
+ // here instead: at once, for the server and the first render, and again
111
+ // whenever a parent hands back `undefined`.
112
+ const applyDefaults = () => {
113
+ if (value === undefined) value = "";
114
+ };
115
+ applyDefaults();
116
+ $effect.pre(applyDefaults);
117
+
118
+ /** Words many components share: a `ZabiStringsProvider` above this one may give them; else English. */
119
+ const common = zabiCommonStrings();
120
+ const revealLabel = $derived(revealLabelGiven ?? common().showPassword);
121
+
48
122
  const fallbackId = generateId("input");
49
123
  const inputId = $derived(idProp ?? fallbackId);
50
124
  const isDisabled = $derived(disabled || loading);
51
125
 
126
+ const status = $derived(fieldMessageState({ variant, message, hint, error }));
127
+
128
+ /** Only a password can be revealed: on any other type the button would have nothing to do. */
129
+ const canReveal = $derived(revealable && type === "password");
130
+ let revealed = $state(false);
131
+ const shownType = $derived(canReveal && revealed ? "text" : type);
132
+
133
+ let input: HTMLInputElement | undefined = $state();
134
+
135
+ /**
136
+ * Changing `type` can drop the caret in some browsers. When the field has
137
+ * focus (a mouse or a finger pressed the button: see `keepFocus`), the
138
+ * selection is put back where it was.
139
+ */
140
+ async function restoreCaret() {
141
+ const field = input;
142
+ if (!field || document.activeElement !== field) return;
143
+ const { selectionStart, selectionEnd, selectionDirection } = field;
144
+ if (selectionStart === null || selectionEnd === null) return;
145
+ const restore = () => {
146
+ if (document.activeElement !== field) return;
147
+ try {
148
+ field.setSelectionRange(selectionStart, selectionEnd, selectionDirection ?? undefined);
149
+ } catch {
150
+ // A type without a text selection: nothing to put back.
151
+ }
152
+ };
153
+ await tick();
154
+ restore();
155
+ // Chromium moves the caret to the start once more after the type has
156
+ // changed, so it is put back again when the frame is done.
157
+ requestAnimationFrame(restore);
158
+ }
159
+
160
+ /**
161
+ * A press with a pointer does not take focus from the field: on a phone
162
+ * that would close the keyboard in the middle of typing a password. From
163
+ * the keyboard the button has focus and keeps it.
164
+ */
165
+ function keepFocus(event: Event) {
166
+ event.preventDefault();
167
+ }
168
+
169
+ /**
170
+ * Same fixed height scale as Button, IconButton and Select (32 / 40 / 48),
171
+ * so a field and its submit button are the same height in a row. These
172
+ * used to be 38 / 46 / 50 while Button was 40 / 48 / 64.
173
+ *
174
+ * Below the `sm` breakpoint the text is 16px at every size: iOS Safari
175
+ * zooms the page when a focused field is smaller. Only the font grows;
176
+ * the box keeps its height.
177
+ */
52
178
  const sizeClass = $derived(() => {
53
179
  if (size === "sm") {
54
- return {
55
- padding: "px-4 py-2",
56
- text: "text-sm",
57
- leading: "leading-5",
58
- spinner: "size-4",
59
- };
180
+ return { box: "h-8 px-3 pointer-coarse:min-h-11", text: "text-sm max-sm:text-base", spinner: "size-3.5" };
60
181
  } else if (size === "lg") {
61
- return {
62
- padding: "px-4 py-3",
63
- text: "text-base",
64
- leading: "leading-6",
65
- spinner: "size-5",
66
- };
182
+ return { box: "h-12 px-4", text: "text-base", spinner: "size-5" };
67
183
  } else {
68
- return {
69
- padding: "px-4 py-2.5",
70
- text: "text-base",
71
- leading: "leading-6",
72
- spinner: "size-5",
73
- };
184
+ return { box: "h-10 px-3 pointer-coarse:min-h-11", text: "text-sm max-sm:text-base", spinner: "size-4" };
74
185
  }
75
186
  });
76
187
 
188
+ /**
189
+ * What stands inside the field at either end sits in a slot 4px in from
190
+ * the field's edge and 8px smaller than the field: 24, 32 and 40px. An
191
+ * icon is centred in it, which puts it where the text's own padding would;
192
+ * a button fills it.
193
+ */
194
+ const SLOT_INSET = 4;
195
+ const SLOTS = {
196
+ sm: { px: 24, min: "min-w-[24px]", reveal: "xs", icon: 14 },
197
+ md: { px: 32, min: "min-w-[32px]", reveal: "sm", icon: 16 },
198
+ lg: { px: 40, min: "min-w-[40px]", reveal: "md", icon: 16 },
199
+ } as const;
200
+ const slot = $derived(SLOTS[size].px);
201
+ const slotClass = $derived(SLOTS[size].min);
202
+ /** The reveal button is one size down from the field, so it fits the slot: 24, 32 and 40px. */
203
+ const revealSize = $derived(SLOTS[size].reveal);
204
+
205
+ const hasLeading = $derived(!!leading);
206
+ /** Whether the end of the field holds more than the spinner. */
207
+ const hasTrailing = $derived(!!trailing || canReveal);
208
+
209
+ /** Measured once mounted; until then, and on the server, the slots are assumed to be their least size. */
210
+ let leadingWidth = $state(0);
211
+ let trailingWidth = $state(0);
212
+
213
+ /**
214
+ * Reports the width of what stands at one end of the field, and again
215
+ * when it changes. Where there is no `ResizeObserver` (an old browser, a
216
+ * test without layout) the assumed size stays.
217
+ */
218
+ function measured(report: (width: number) => void) {
219
+ return (element: HTMLElement) => {
220
+ if (typeof ResizeObserver === "undefined") return;
221
+ const observer = new ResizeObserver(() => report(element.offsetWidth));
222
+ observer.observe(element);
223
+ return () => observer.disconnect();
224
+ };
225
+ }
226
+ const trailingCount = $derived((trailing ? 1 : 0) + (canReveal ? 1 : 0) + (loading ? 1 : 0));
227
+ const leadingPad = $derived(
228
+ hasLeading ? `${(leadingWidth || SLOT_INSET + slot) + SLOT_INSET}px` : undefined,
229
+ );
230
+ const trailingPad = $derived(
231
+ hasTrailing
232
+ ? `${(trailingWidth || SLOT_INSET + trailingCount * slot + (trailingCount - 1) * SLOT_INSET) + SLOT_INSET}px`
233
+ : undefined,
234
+ );
235
+
77
236
  const variantClass = $derived(() => {
78
- return variant === "success"
237
+ return status.variant === "success"
79
238
  ? "border-success focus-visible:border-success"
80
- : variant === "warning"
239
+ : status.variant === "warning"
81
240
  ? "border-warning focus-visible:border-warning"
82
- : variant === "error"
241
+ : status.variant === "error"
83
242
  ? "border-error focus-visible:border-error"
84
- : "border-input-border focus-visible:border-input-border";
243
+ : "border-input-border enabled:hover:border-input-border-hover focus-visible:border-input-border";
85
244
  });
86
245
 
87
246
  const inputClasses = $derived(() => {
88
247
  const sizeStyles = sizeClass();
89
- const trailingPad = loading
90
- ? size === "sm"
91
- ? "pr-9"
92
- : "pr-10"
93
- : "";
248
+ // Room for the spinner alone. With more at the end, the measured width is used instead.
249
+ const spinnerPad = loading && !hasTrailing ? SPINNER_PAD[size] : "";
250
+ /**
251
+ * The field is a raised surface (`bg-input` = white in light, an inset
252
+ * step in dark). It used to be a grey fill on a grey page, which read
253
+ * as disabled. `min-w-48` is gone: it forced a 192px floor on every
254
+ * field regardless of the layout around it.
255
+ */
94
256
  const baseClasses =
95
- "focus-ring w-full min-w-48 border bg-input hover:bg-input-hover focus-visible:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus-visible:outline-none disabled:opacity-50 disabled:cursor-not-allowed";
257
+ "focus-ring w-full border bg-input hover:bg-input-hover focus-visible:bg-input-focus disabled:bg-input-disabled rounded-control transition-colors duration-150 placeholder:text-input-placeholder text-body focus:outline-none focus-visible:outline-none disabled:text-action-disabled-text disabled:cursor-not-allowed";
96
258
 
97
- return `${baseClasses} ${sizeStyles.padding} ${trailingPad} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()} ${className}`.trim();
259
+ return cn(`${baseClasses} ${sizeStyles.box} ${spinnerPad} ${sizeStyles.text} ${variantClass()} ${className}`);
98
260
  });
99
261
 
100
262
  const labelClasses = $derived(
101
- () => "block text-sm font-medium text-label mb-1",
263
+ () => "block text-sm font-medium text-label mb-2",
102
264
  );
103
265
 
104
- const messageClasses = $derived(() => {
105
- if (variant === "error") {
106
- return "text-error text-sm mt-1 flex items-center gap-1.5";
107
- } else if (variant === "success") {
108
- return "text-success text-sm mt-1 flex items-center gap-1.5";
109
- } else if (variant === "warning") {
110
- return "text-warning text-sm mt-1 flex items-center gap-1.5";
111
- }
112
- return "text-description text-sm mt-1 flex items-center gap-1.5";
113
- });
266
+ /**
267
+ * Both ends are laid over the field, which stays one box with one focus
268
+ * ring. They let a press through to it, except on something in them that
269
+ * can itself be pressed.
270
+ */
271
+ const SPINNER_PAD = { sm: "pe-9", md: "pe-10", lg: "pe-10" } as const;
114
272
 
115
- const getIcon = $derived(() => {
116
- if (variant === "error") return AlertCircle;
117
- if (variant === "success") return CheckCircle;
118
- if (variant === "warning") return AlertTriangle;
119
- return null;
120
- });
273
+ const END_BASE =
274
+ "pointer-events-none absolute inset-y-0 flex items-center gap-[4px] text-description [&_:is(a,button,input,select,textarea,[tabindex])]:pointer-events-auto";
121
275
 
122
276
  function handleInput(event: Event) {
123
277
  if (oninput) {
@@ -132,46 +286,85 @@
132
286
  {/if}
133
287
  <div class="relative">
134
288
  <input
289
+ bind:this={input}
135
290
  id={inputId}
136
- {type}
291
+ type={shownType}
137
292
  {name}
138
293
  bind:value
139
294
  {placeholder}
140
295
  {required}
141
296
  disabled={isDisabled}
142
297
  class={inputClasses()}
298
+ style:padding-inline-start={leadingPad}
299
+ style:padding-inline-end={trailingPad}
143
300
  oninput={handleInput}
144
301
  {onblur}
145
- aria-invalid={variant === "error" ? "true" : undefined}
302
+ aria-invalid={status.variant === "error" ? "true" : undefined}
146
303
  aria-required={required ? "true" : undefined}
147
304
  aria-busy={loading ? "true" : undefined}
148
- aria-describedby={message ? `${inputId}-message` : undefined}
305
+ aria-describedby={fieldDescribedBy(inputId, status, describedBy)}
149
306
  {...restProps}
150
307
  />
151
- {#if loading}
308
+ {#if leading}
309
+ <span
310
+ class={cn(END_BASE, "start-0 ps-[4px]")}
311
+ {@attach measured((width) => (leadingWidth = width))}
312
+ data-input-leading
313
+ >
314
+ <span class={cn("flex items-center justify-center", slotClass)}>
315
+ {@render leading()}
316
+ </span>
317
+ </span>
318
+ {/if}
319
+ {#if loading || hasTrailing}
152
320
  <span
153
- class="pointer-events-none absolute inset-y-0 right-3 flex items-center text-description"
154
- aria-hidden="true"
321
+ class={cn(END_BASE, "end-0 pe-[4px]")}
322
+ {@attach measured((width) => (trailingWidth = width))}
323
+ data-input-trailing
155
324
  >
156
- <span
157
- class="inline-block {sizeClass()
158
- .spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70"
159
- ></span>
325
+ {#if trailing}
326
+ <span class={cn("flex items-center justify-center", slotClass)}>
327
+ {@render trailing()}
328
+ </span>
329
+ {/if}
330
+ {#if canReveal}
331
+ <!-- A toggle with one name: "Show password", pressed or not.
332
+ A name that changed with the state would read as "Hide
333
+ password, pressed". The visible box is 4px inside the field
334
+ at every size, so its corners are the field's less 4px; on a
335
+ touch screen the layer around it is the 44px target. -->
336
+ <IconButton
337
+ variant="ghost"
338
+ size={revealSize}
339
+ bind:pressed={revealed}
340
+ label={revealLabel}
341
+ disabled={isDisabled}
342
+ class={cn(
343
+ "rounded-[calc(var(--radius-control)-4px)] pointer-coarse:relative pointer-coarse:min-h-0 pointer-coarse:min-w-0",
344
+ TOUCH_HIT_AREA,
345
+ )}
346
+ onmousedown={keepFocus}
347
+ onclick={restoreCaret}
348
+ data-input-reveal
349
+ >
350
+ {#if revealed}
351
+ <EyeOff size={SLOTS[size].icon} aria-hidden="true" />
352
+ {:else}
353
+ <Eye size={SLOTS[size].icon} aria-hidden="true" />
354
+ {/if}
355
+ </IconButton>
356
+ {/if}
357
+ {#if loading}
358
+ <!-- 12px from the field's edge, where it has always been. -->
359
+ <span class="flex items-center ps-[4px] pe-[8px]" aria-hidden="true">
360
+ <span
361
+ class="inline-block {sizeClass()
362
+ .spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70 motion-reduce:animate-pulse"
363
+ ></span>
364
+ </span>
365
+ {/if}
160
366
  </span>
161
367
  {/if}
162
368
  </div>
163
- {#if message && variant !== "default"}
164
- <p
165
- id={`${inputId}-message`}
166
- class={messageClasses()}
167
- role={variant === "error" ? "alert" : "status"}
168
- aria-live={variant === "error" ? "assertive" : "polite"}
169
- >
170
- {#if getIcon()}
171
- {@const Icon = getIcon()}
172
- <Icon size={14} class="shrink-0" />
173
- {/if}
174
- <span>{message}</span>
175
- </p>
176
- {/if}
369
+ <FieldMessages fieldId={inputId} {status} {hint} />
177
370
  </div>
@@ -1,5 +1,11 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { HTMLInputAttributes } from "svelte/elements";
1
3
  import type { SemanticVariant, SizeVariant } from "../types/variants.js";
2
- interface Props {
4
+ /**
5
+ * Other attributes (`autocomplete`, `inputmode`, `maxlength`,
6
+ * `enterkeyhint`, `data-*`, `onchange`, ...) land on the `<input>`.
7
+ */
8
+ type Props = Omit<HTMLInputAttributes, "class" | "size" | "value" | "type" | "id" | "name" | "placeholder" | "required" | "disabled" | "oninput" | "onblur" | "aria-label" | "aria-describedby"> & {
3
9
  /** Omit to auto-generate; pair with FormField `id` when used inside FormField. */
4
10
  id?: string;
5
11
  value?: string;
@@ -15,11 +21,35 @@ interface Props {
15
21
  loading?: boolean;
16
22
  size?: SizeVariant;
17
23
  variant?: SemanticVariant;
24
+ /** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
18
25
  message?: string;
26
+ /** Help text under the field, read out with it. */
27
+ hint?: string;
28
+ /** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
29
+ error?: string;
30
+ /**
31
+ * Drawn inside the field, before the text: an icon, a unit. Decoration
32
+ * lets a press through to the field; a button or link in it is pressable.
33
+ */
34
+ leading?: Snippet;
35
+ /** Drawn inside the field, after the text and before the loading spinner's place. */
36
+ trailing?: Snippet;
37
+ /**
38
+ * With `type="password"`: adds a button at the end of the field that
39
+ * shows the password as text, and hides it again. Ignored for other types.
40
+ */
41
+ revealable?: boolean;
42
+ /**
43
+ * Accessible name of that button. It stays the same in both states;
44
+ * the button's pressed state says whether the password is showing.
45
+ */
46
+ revealLabel?: string;
19
47
  oninput?: (event: Event) => void;
20
48
  onblur?: (event: Event) => void;
21
49
  "aria-label"?: string;
22
- }
50
+ /** Ids of other elements that describe the field. The hint and the message are added after them. */
51
+ "aria-describedby"?: string | null;
52
+ };
23
53
  declare const Input: import("svelte").Component<Props, {}, "value">;
24
54
  type Input = ReturnType<typeof Input>;
25
55
  export default Input;
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import ListItemRow, { type ListItemData } from "./ListItem.svelte";
3
+ import { cn } from "../util/cn.js";
3
4
 
4
5
  export type ListItem = ListItemData;
5
6
 
@@ -9,6 +10,8 @@
9
10
  ariaLabel?: string;
10
11
  selectedId?: string;
11
12
  showArrow?: boolean;
13
+ class?: string;
14
+ /** @deprecated use `class`. */
12
15
  className?: string;
13
16
  onclick?: (item: ListItem, event: MouseEvent) => void;
14
17
  }
@@ -18,13 +21,21 @@
18
21
  ariaLabel = "List items",
19
22
  selectedId = "",
20
23
  showArrow = true,
21
- className = "",
24
+ class: classAttr = "",
25
+ className: legacyClass = "",
22
26
  onclick,
23
27
  ...restProps
24
28
  }: Props = $props();
25
29
 
30
+ /** `class` is the public prop; `className` is a deprecated alias.
31
+ * Both are merged here so existing call sites keep working. */
32
+ const className = $derived(cn(`${classAttr} ${legacyClass}`));
33
+
26
34
  const listClasses = $derived(
27
- `space-y-1 overflow-hidden rounded-xl ${className}`.trim(),
35
+ // The same radius as its rows. Not `overflow-hidden`: the rows fill
36
+ // the list edge to edge, so clipping here cut a row's focus ring off
37
+ // on every side but the gap to the next row.
38
+ cn(`space-y-1 rounded-[var(--zabi-list-row-radius,var(--radius-container))] ${className}`),
28
39
  );
29
40
  </script>
30
41
 
@@ -6,6 +6,8 @@ interface Props {
6
6
  ariaLabel?: string;
7
7
  selectedId?: string;
8
8
  showArrow?: boolean;
9
+ class?: string;
10
+ /** @deprecated use `class`. */
9
11
  className?: string;
10
12
  onclick?: (item: ListItem, event: MouseEvent) => void;
11
13
  }
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { ArrowRight } from "@lucide/svelte";
2
+ import ArrowRight from "@lucide/svelte/icons/arrow-right";
3
3
  import type { Component, Snippet } from "svelte";
4
4
  import ListItemLeading from "./ListItemLeading.svelte";
5
5
 
@@ -21,9 +21,14 @@
21
21
  interface Props {
22
22
  item: ListItemData;
23
23
  selected?: boolean;
24
+ /** The arrow at the end of a row that can be pressed. A row with nothing to do never has one. */
24
25
  showArrow?: boolean;
25
26
  /** Shown before the chevron when `showArrow` is true. */
26
27
  trailing?: Snippet;
28
+ /**
29
+ * Called when the row is pressed. A row with neither this nor
30
+ * `item.href` has nothing to do: it is plain content, not a button.
31
+ */
27
32
  onclick?: (item: ListItemData, event: MouseEvent) => void;
28
33
  }
29
34
 
@@ -36,16 +41,37 @@
36
41
  ...restProps
37
42
  }: Props = $props();
38
43
 
44
+ /**
45
+ * A row is a link with `item.href`, a button with `onclick`, and otherwise
46
+ * plain content: it used to be a focusable button with a pointer cursor
47
+ * and an arrow, and nothing happened when it was pressed.
48
+ */
49
+ const interactive = $derived(!!item.href || !!onclick);
50
+
39
51
  const itemClasses = $derived.by(() => {
40
52
  const baseClasses =
41
- "group focus-ring flex w-full items-center gap-3 rounded-xl border border-border px-4 py-3 pr-5 text-left transition-all duration-150";
42
- const interactiveClasses = item.disabled
43
- ? "cursor-not-allowed opacity-50"
44
- : "cursor-pointer focus-visible:bg-base-100";
45
- const selectedClasses = selected
46
- ? "bg-action-primary-subtle border-brand-500"
47
- : "";
48
- return `${baseClasses} ${interactiveClasses} ${selectedClasses}`.trim();
53
+ // The container radius, or the one a `.list-group` shell around the list
54
+ // hands down so the row is concentric with it (see app.css).
55
+ "group flex w-full items-center gap-3 rounded-[var(--zabi-list-row-radius,var(--radius-container))] border px-4 py-3 pr-5 text-left transition-all duration-150";
56
+ if (!interactive) {
57
+ const tone = selected ? "bg-action-primary-subtle border-action-primary" : "border-border";
58
+ return `${baseClasses} ${tone} ${item.disabled ? "opacity-50" : ""}`.trim();
59
+ }
60
+ const cursorClasses = item.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer";
61
+ // A selected row keeps its fill under keyboard focus, which the focus
62
+ // ring already shows; the hover tint is for rows with no fill. The two
63
+ // never share an element, and neither does the selected border with
64
+ // `border-border`: beside each other the second of each pair lost.
65
+ //
66
+ // Hover is for a pointer that can hover (the hand-written rule is
67
+ // gated on it, so the tint does not stay on after a tap); the pressed
68
+ // fill is for every pointer, and is what a finger sees.
69
+ const stateClasses = selected
70
+ ? "bg-action-primary-subtle border-action-primary active:bg-action-primary-subtle-active"
71
+ : item.disabled
72
+ ? "border-border"
73
+ : "border-border hover:bg-surface-hover focus-visible:bg-surface-hover active:bg-surface-active";
74
+ return `focus-ring ${baseClasses} ${cursorClasses} ${stateClasses}`.trim();
49
75
  });
50
76
 
51
77
  const ariaCurrent = $derived(item.href && selected ? "page" : undefined);
@@ -101,7 +127,7 @@
101
127
  {@render trailing()}
102
128
  </span>
103
129
  {/if}
104
- {#if showArrow}
130
+ {#if showArrow && interactive}
105
131
  <ArrowRight
106
132
  size={16}
107
133
  class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-1 group-focus-visible:translate-x-1"
@@ -124,7 +150,7 @@
124
150
  >
125
151
  {@render rowContent()}
126
152
  </a>
127
- {:else}
153
+ {:else if onclick}
128
154
  <button
129
155
  type="button"
130
156
  class={itemClasses}
@@ -134,4 +160,11 @@
134
160
  >
135
161
  {@render rowContent()}
136
162
  </button>
163
+ {:else}
164
+ <!-- A plain row has no role, so `aria-disabled` on it would say nothing
165
+ to anyone: a disabled one is only drawn dimmed. There is nothing in it to
166
+ press, enabled or not. -->
167
+ <div class={itemClasses} {...restProps}>
168
+ {@render rowContent()}
169
+ </div>
137
170
  {/if}
@@ -19,9 +19,14 @@ export interface ListItemData {
19
19
  interface Props {
20
20
  item: ListItemData;
21
21
  selected?: boolean;
22
+ /** The arrow at the end of a row that can be pressed. A row with nothing to do never has one. */
22
23
  showArrow?: boolean;
23
24
  /** Shown before the chevron when `showArrow` is true. */
24
25
  trailing?: Snippet;
26
+ /**
27
+ * Called when the row is pressed. A row with neither this nor
28
+ * `item.href` has nothing to do: it is plain content, not a button.
29
+ */
25
30
  onclick?: (item: ListItemData, event: MouseEvent) => void;
26
31
  }
27
32
  declare const ListItem: Component<Props, {}, "">;
@@ -1,15 +1,17 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
3
 
4
+ import { cn } from "../util/cn.js";
4
5
  interface Props {
5
- /** e.g. `rounded-full` for avatars; default is `rounded-md` for icons. */
6
+ /** e.g. `rounded-full` for avatars; default is `rounded-control` for icons. */
6
7
  class?: string;
7
8
  children?: Snippet;
8
9
  }
9
10
 
10
11
  let { class: className = "", children }: Props = $props();
11
12
 
12
- const roundingClass = $derived(className.trim() ? className.trim() : "rounded-md");
13
+ // cn lets a call-site radius win outright, instead of replacing the default wholesale.
14
+ const roundingClass = $derived(cn("rounded-control", className));
13
15
  </script>
14
16
 
15
17
  <span
@@ -1,6 +1,6 @@
1
1
  import type { Snippet } from "svelte";
2
2
  interface Props {
3
- /** e.g. `rounded-full` for avatars; default is `rounded-md` for icons. */
3
+ /** e.g. `rounded-full` for avatars; default is `rounded-control` for icons. */
4
4
  class?: string;
5
5
  children?: Snippet;
6
6
  }