zabi-components 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/CHANGELOG.md +2935 -0
  2. package/README.md +243 -10
  3. package/THEME.md +162 -332
  4. package/THEMING.md +1419 -259
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +120 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +46 -0
  8. package/dist/atoms/Badge.svelte +71 -13
  9. package/dist/atoms/Badge.svelte.d.ts +8 -2
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +217 -30
  13. package/dist/atoms/Button.svelte.d.ts +33 -4
  14. package/dist/atoms/Card.svelte +182 -26
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/CardHeader.svelte +16 -4
  17. package/dist/atoms/Checkbox.svelte +81 -21
  18. package/dist/atoms/Checkbox.svelte.d.ts +31 -2
  19. package/dist/atoms/Chip.svelte +351 -0
  20. package/dist/atoms/Chip.svelte.d.ts +57 -0
  21. package/dist/atoms/CodeBlock.svelte +20 -2
  22. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  23. package/dist/atoms/CodeField.svelte +385 -0
  24. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  25. package/dist/atoms/ColorPicker.svelte +260 -49
  26. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  27. package/dist/atoms/Container.svelte +16 -8
  28. package/dist/atoms/DateField.svelte +145 -0
  29. package/dist/atoms/DateField.svelte.d.ts +81 -0
  30. package/dist/atoms/FieldMessages.svelte +69 -0
  31. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  32. package/dist/atoms/FloatingActionButton.svelte +176 -0
  33. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  34. package/dist/atoms/Heading.svelte +63 -5
  35. package/dist/atoms/Heading.svelte.d.ts +14 -0
  36. package/dist/atoms/IconButton.svelte +294 -26
  37. package/dist/atoms/IconButton.svelte.d.ts +62 -4
  38. package/dist/atoms/Input.svelte +259 -53
  39. package/dist/atoms/Input.svelte.d.ts +33 -2
  40. package/dist/atoms/List.svelte +5 -1
  41. package/dist/atoms/ListItem.svelte +70 -13
  42. package/dist/atoms/ListItem.svelte.d.ts +11 -0
  43. package/dist/atoms/PickerField.svelte +239 -0
  44. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  45. package/dist/atoms/Progress.svelte +99 -19
  46. package/dist/atoms/Progress.svelte.d.ts +15 -1
  47. package/dist/atoms/Radio.svelte +51 -3
  48. package/dist/atoms/Radio.svelte.d.ts +30 -2
  49. package/dist/atoms/Rating.svelte +620 -0
  50. package/dist/atoms/Rating.svelte.d.ts +77 -0
  51. package/dist/atoms/Select.svelte +438 -98
  52. package/dist/atoms/Select.svelte.d.ts +61 -3
  53. package/dist/atoms/SelectionControl.svelte +193 -23
  54. package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
  55. package/dist/atoms/Skeleton.svelte +114 -18
  56. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  57. package/dist/atoms/SkeletonGroup.svelte +43 -0
  58. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  59. package/dist/atoms/Slider.svelte +344 -0
  60. package/dist/atoms/Slider.svelte.d.ts +40 -0
  61. package/dist/atoms/Sparkline.svelte +109 -0
  62. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  63. package/dist/atoms/SpeechBubble.svelte +187 -0
  64. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  65. package/dist/atoms/Spinner.svelte +57 -0
  66. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  67. package/dist/atoms/Stat.svelte +72 -0
  68. package/dist/atoms/Stat.svelte.d.ts +19 -0
  69. package/dist/atoms/Table.svelte +151 -6
  70. package/dist/atoms/Table.svelte.d.ts +15 -3
  71. package/dist/atoms/TemporalField.svelte +472 -0
  72. package/dist/atoms/TemporalField.svelte.d.ts +48 -0
  73. package/dist/atoms/Text.svelte +23 -11
  74. package/dist/atoms/Text.svelte.d.ts +6 -3
  75. package/dist/atoms/Textarea.svelte +150 -44
  76. package/dist/atoms/Textarea.svelte.d.ts +35 -2
  77. package/dist/atoms/ThemeToggle.svelte +223 -44
  78. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  79. package/dist/atoms/TimeColumn.svelte +150 -0
  80. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  81. package/dist/atoms/TimeField.svelte +151 -0
  82. package/dist/atoms/TimeField.svelte.d.ts +85 -0
  83. package/dist/atoms/Toast.svelte +22 -8
  84. package/dist/atoms/Toast.svelte.d.ts +2 -0
  85. package/dist/atoms/Toggle.svelte +98 -14
  86. package/dist/atoms/Toggle.svelte.d.ts +26 -2
  87. package/dist/atoms/Tooltip.svelte +528 -24
  88. package/dist/atoms/Tooltip.svelte.d.ts +18 -0
  89. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  90. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  91. package/dist/atoms/index.d.ts +38 -0
  92. package/dist/atoms/index.js +29 -0
  93. package/dist/atoms/selection-control.styles.d.ts +47 -5
  94. package/dist/atoms/selection-control.styles.js +52 -10
  95. package/dist/components/atoms/index.d.ts +38 -0
  96. package/dist/components/atoms/index.d.ts.map +1 -1
  97. package/dist/components/atoms/selection-control.styles.d.ts +47 -5
  98. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  99. package/dist/components/index.d.ts +104 -2
  100. package/dist/components/index.d.ts.map +1 -1
  101. package/dist/components/map.d.ts +11 -0
  102. package/dist/components/map.d.ts.map +1 -0
  103. package/dist/components/molecules/index.d.ts +51 -0
  104. package/dist/components/molecules/index.d.ts.map +1 -1
  105. package/dist/components/molecules/toast-store.d.ts +83 -7
  106. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  107. package/dist/components/organisms/index.d.ts +10 -0
  108. package/dist/components/organisms/index.d.ts.map +1 -1
  109. package/dist/components/types/variants.d.ts +24 -1
  110. package/dist/components/types/variants.d.ts.map +1 -1
  111. package/dist/components/util/app-shell.d.ts +79 -0
  112. package/dist/components/util/app-shell.d.ts.map +1 -0
  113. package/dist/components/util/avatar.d.ts +39 -0
  114. package/dist/components/util/avatar.d.ts.map +1 -0
  115. package/dist/components/util/bottom-sheet.d.ts +30 -0
  116. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  117. package/dist/components/util/bottom-tab-bar.d.ts +41 -0
  118. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  119. package/dist/components/util/calendar.d.ts +56 -0
  120. package/dist/components/util/calendar.d.ts.map +1 -0
  121. package/dist/components/util/chart.d.ts +140 -0
  122. package/dist/components/util/chart.d.ts.map +1 -0
  123. package/dist/components/util/chip.d.ts +66 -0
  124. package/dist/components/util/chip.d.ts.map +1 -0
  125. package/dist/components/util/cn.d.ts.map +1 -1
  126. package/dist/components/util/code-field.d.ts +63 -0
  127. package/dist/components/util/code-field.d.ts.map +1 -0
  128. package/dist/components/util/collapsible.d.ts +64 -0
  129. package/dist/components/util/collapsible.d.ts.map +1 -0
  130. package/dist/components/util/confirm-dialog.d.ts +12 -0
  131. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  132. package/dist/components/util/date.d.ts +96 -0
  133. package/dist/components/util/date.d.ts.map +1 -0
  134. package/dist/components/util/display-voice.d.ts +10 -0
  135. package/dist/components/util/display-voice.d.ts.map +1 -0
  136. package/dist/components/util/drawer.d.ts +13 -0
  137. package/dist/components/util/drawer.d.ts.map +1 -0
  138. package/dist/components/util/dropdown.d.ts +47 -0
  139. package/dist/components/util/dropdown.d.ts.map +1 -0
  140. package/dist/components/util/field-control.d.ts +30 -0
  141. package/dist/components/util/field-control.d.ts.map +1 -0
  142. package/dist/components/util/field.d.ts +45 -0
  143. package/dist/components/util/field.d.ts.map +1 -0
  144. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  145. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  146. package/dist/components/util/focus-utils.d.ts +77 -2
  147. package/dist/components/util/focus-utils.d.ts.map +1 -1
  148. package/dist/components/util/hydration.d.ts +45 -0
  149. package/dist/components/util/hydration.d.ts.map +1 -0
  150. package/dist/components/util/keyboard-inset.d.ts +18 -0
  151. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  152. package/dist/components/util/map-view.d.ts +144 -0
  153. package/dist/components/util/map-view.d.ts.map +1 -0
  154. package/dist/components/util/media-grid.d.ts +51 -0
  155. package/dist/components/util/media-grid.d.ts.map +1 -0
  156. package/dist/components/util/media.d.ts +6 -0
  157. package/dist/components/util/media.d.ts.map +1 -0
  158. package/dist/components/util/overlay-chrome.d.ts +53 -0
  159. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  160. package/dist/components/util/overlay.d.ts +72 -0
  161. package/dist/components/util/overlay.d.ts.map +1 -0
  162. package/dist/components/util/photo.d.ts +234 -0
  163. package/dist/components/util/photo.d.ts.map +1 -0
  164. package/dist/components/util/portal.d.ts +23 -0
  165. package/dist/components/util/portal.d.ts.map +1 -0
  166. package/dist/components/util/progress.d.ts +19 -0
  167. package/dist/components/util/progress.d.ts.map +1 -0
  168. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  169. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  170. package/dist/components/util/radio-keys.d.ts +25 -0
  171. package/dist/components/util/radio-keys.d.ts.map +1 -0
  172. package/dist/components/util/rating.d.ts +23 -0
  173. package/dist/components/util/rating.d.ts.map +1 -0
  174. package/dist/components/util/ready-made-strings.d.ts +77 -0
  175. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  176. package/dist/components/util/scroll-edge.d.ts +33 -0
  177. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  178. package/dist/components/util/scroll-padding.d.ts +20 -0
  179. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  180. package/dist/components/util/segmented-control.d.ts +16 -0
  181. package/dist/components/util/segmented-control.d.ts.map +1 -0
  182. package/dist/components/util/select.d.ts +41 -0
  183. package/dist/components/util/select.d.ts.map +1 -0
  184. package/dist/components/util/sheet-drag.d.ts +84 -0
  185. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  186. package/dist/components/util/sidebar.d.ts +63 -0
  187. package/dist/components/util/sidebar.d.ts.map +1 -0
  188. package/dist/components/util/skeleton.d.ts +15 -0
  189. package/dist/components/util/skeleton.d.ts.map +1 -0
  190. package/dist/components/util/sliding-indicator.d.ts +25 -0
  191. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  192. package/dist/components/util/sortable-list.d.ts +65 -0
  193. package/dist/components/util/sortable-list.d.ts.map +1 -0
  194. package/dist/components/util/stepper.d.ts +40 -0
  195. package/dist/components/util/stepper.d.ts.map +1 -0
  196. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  197. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  198. package/dist/components/util/temporal-field.d.ts +89 -0
  199. package/dist/components/util/temporal-field.d.ts.map +1 -0
  200. package/dist/components/util/theme-mode.d.ts +80 -0
  201. package/dist/components/util/theme-mode.d.ts.map +1 -0
  202. package/dist/components/util/toaster.d.ts +133 -0
  203. package/dist/components/util/toaster.d.ts.map +1 -0
  204. package/dist/components/util/tone-surface.d.ts +34 -0
  205. package/dist/components/util/tone-surface.d.ts.map +1 -0
  206. package/dist/components/util/tooltip.d.ts +10 -0
  207. package/dist/components/util/tooltip.d.ts.map +1 -0
  208. package/dist/components/util/top-navbar.d.ts +35 -0
  209. package/dist/components/util/top-navbar.d.ts.map +1 -0
  210. package/dist/components/util/touch-target.d.ts +31 -0
  211. package/dist/components/util/touch-target.d.ts.map +1 -0
  212. package/dist/components/util/zabi-strings.d.ts +142 -0
  213. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  214. package/dist/create-theme/cli.js +173 -0
  215. package/dist/create-theme/index.d.ts +190 -0
  216. package/dist/create-theme/index.js +1034 -0
  217. package/dist/create-theme/lib/ramp-math.js +128 -0
  218. package/dist/create-theme/lib/resolve.js +238 -0
  219. package/dist/create-theme/theme-data.js +2644 -0
  220. package/dist/index.d.ts +104 -2
  221. package/dist/index.js +75 -2
  222. package/dist/map.d.ts +10 -0
  223. package/dist/map.js +9 -0
  224. package/dist/molecules/Alert.svelte +63 -16
  225. package/dist/molecules/Alert.svelte.d.ts +14 -1
  226. package/dist/molecules/AppBar.svelte +674 -0
  227. package/dist/molecules/AppBar.svelte.d.ts +131 -0
  228. package/dist/molecules/AvatarGroup.svelte +116 -0
  229. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  230. package/dist/molecules/BarChart.svelte +381 -0
  231. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  232. package/dist/molecules/BottomSheet.svelte +608 -0
  233. package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
  234. package/dist/molecules/BottomTabBar.svelte +403 -0
  235. package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
  236. package/dist/molecules/Calendar.svelte +597 -0
  237. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  238. package/dist/molecules/ChipGroup.svelte +325 -0
  239. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  240. package/dist/molecules/Collapsible.svelte +347 -0
  241. package/dist/molecules/Collapsible.svelte.d.ts +61 -0
  242. package/dist/molecules/CollapsibleGroup.svelte +136 -0
  243. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  244. package/dist/molecules/ComponentDemo.svelte +16 -4
  245. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  246. package/dist/molecules/ConfirmDialog.svelte +257 -0
  247. package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
  248. package/dist/molecules/ContactForm.svelte +34 -17
  249. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  250. package/dist/molecules/Drawer.svelte +415 -0
  251. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  252. package/dist/molecules/Dropdown.svelte +517 -48
  253. package/dist/molecules/Dropdown.svelte.d.ts +47 -6
  254. package/dist/molecules/DropdownItem.svelte +122 -0
  255. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  256. package/dist/molecules/EmptyState.svelte +55 -10
  257. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  258. package/dist/molecules/FormField.svelte +9 -1
  259. package/dist/molecules/FormField.svelte.d.ts +2 -0
  260. package/dist/molecules/Header.svelte +1 -1
  261. package/dist/molecules/ImageUpload.svelte +492 -69
  262. package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
  263. package/dist/molecules/LineChart.svelte +459 -0
  264. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  265. package/dist/molecules/MapView.svelte +565 -0
  266. package/dist/molecules/MapView.svelte.d.ts +88 -0
  267. package/dist/molecules/MediaGrid.svelte +527 -0
  268. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  269. package/dist/molecules/Modal.svelte +293 -72
  270. package/dist/molecules/Modal.svelte.d.ts +51 -2
  271. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  272. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  273. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  274. package/dist/molecules/NavigationMenuList.svelte +10 -3
  275. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  276. package/dist/molecules/Page.svelte +32 -2
  277. package/dist/molecules/Page.svelte.d.ts +11 -0
  278. package/dist/molecules/PhotoGrid.svelte +523 -0
  279. package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
  280. package/dist/molecules/PhotoViewer.svelte +1213 -0
  281. package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
  282. package/dist/molecules/PieChart.svelte +421 -0
  283. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  284. package/dist/molecules/PropsTable.svelte +22 -10
  285. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  286. package/dist/molecules/PullToRefresh.svelte +263 -0
  287. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  288. package/dist/molecules/RadioGroup.svelte +72 -18
  289. package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
  290. package/dist/molecules/Section.svelte +1 -1
  291. package/dist/molecules/SegmentedControl.svelte +456 -0
  292. package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
  293. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  294. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  295. package/dist/molecules/SidebarFooter.svelte +30 -7
  296. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  297. package/dist/molecules/SidebarNavSection.svelte +1 -1
  298. package/dist/molecules/SlideUp.svelte +220 -61
  299. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  300. package/dist/molecules/SortableList.svelte +633 -0
  301. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  302. package/dist/molecules/Stepper.svelte +562 -0
  303. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  304. package/dist/molecules/StickyActionBar.svelte +250 -0
  305. package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
  306. package/dist/molecules/SwipeableListItem.svelte +334 -0
  307. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  308. package/dist/molecules/Tabs.svelte +310 -9
  309. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  310. package/dist/molecules/Toaster.svelte +514 -5
  311. package/dist/molecules/Toaster.svelte.d.ts +153 -2
  312. package/dist/molecules/ToasterToast.svelte +495 -77
  313. package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
  314. package/dist/molecules/UnsavedChangesBar.svelte +223 -0
  315. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  316. package/dist/molecules/index.d.ts +51 -0
  317. package/dist/molecules/index.js +31 -0
  318. package/dist/molecules/toast-store.d.ts +83 -7
  319. package/dist/molecules/toast-store.js +25 -1
  320. package/dist/organisms/AppNavigation.svelte +440 -0
  321. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  322. package/dist/organisms/AppShell.svelte +696 -0
  323. package/dist/organisms/AppShell.svelte.d.ts +157 -0
  324. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  325. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  326. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  327. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  328. package/dist/organisms/SidebarNavigation.svelte +111 -43
  329. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  330. package/dist/organisms/SidebarPanel.svelte +34 -17
  331. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  332. package/dist/organisms/SidebarShell.svelte +164 -14
  333. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  334. package/dist/organisms/TopNavbar.svelte +353 -39
  335. package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
  336. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  337. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  338. package/dist/organisms/index.d.ts +10 -0
  339. package/dist/organisms/index.js +7 -0
  340. package/dist/types/components.d.ts +21 -1
  341. package/dist/types/components.d.ts.map +1 -1
  342. package/dist/types/index.d.ts +1093 -26
  343. package/dist/types/index.d.ts.map +1 -1
  344. package/dist/types/variants.ts +28 -1
  345. package/dist/util/app-shell.d.ts +78 -0
  346. package/dist/util/app-shell.js +97 -0
  347. package/dist/util/avatar.d.ts +38 -0
  348. package/dist/util/avatar.js +52 -0
  349. package/dist/util/bottom-sheet.d.ts +29 -0
  350. package/dist/util/bottom-sheet.js +43 -0
  351. package/dist/util/bottom-tab-bar.d.ts +40 -0
  352. package/dist/util/bottom-tab-bar.js +54 -0
  353. package/dist/util/calendar.d.ts +55 -0
  354. package/dist/util/calendar.js +74 -0
  355. package/dist/util/chart.d.ts +139 -0
  356. package/dist/util/chart.js +305 -0
  357. package/dist/util/chip.d.ts +65 -0
  358. package/dist/util/chip.js +58 -0
  359. package/dist/util/cn.js +28 -2
  360. package/dist/util/code-field.d.ts +62 -0
  361. package/dist/util/code-field.js +112 -0
  362. package/dist/util/collapsible.d.ts +63 -0
  363. package/dist/util/collapsible.js +23 -0
  364. package/dist/util/confirm-dialog.d.ts +11 -0
  365. package/dist/util/confirm-dialog.js +2 -0
  366. package/dist/util/date.d.ts +95 -0
  367. package/dist/util/date.js +237 -0
  368. package/dist/util/display-voice.d.ts +9 -0
  369. package/dist/util/display-voice.js +9 -0
  370. package/dist/util/drawer.d.ts +12 -0
  371. package/dist/util/drawer.js +7 -0
  372. package/dist/util/dropdown.d.ts +46 -0
  373. package/dist/util/dropdown.js +21 -0
  374. package/dist/util/field-control.d.ts +29 -0
  375. package/dist/util/field-control.js +32 -0
  376. package/dist/util/field.d.ts +44 -0
  377. package/dist/util/field.js +38 -0
  378. package/dist/util/fit-in-viewport.d.ts +200 -0
  379. package/dist/util/fit-in-viewport.js +399 -0
  380. package/dist/util/focus-utils.d.ts +77 -2
  381. package/dist/util/focus-utils.js +186 -9
  382. package/dist/util/hydration.d.ts +44 -0
  383. package/dist/util/hydration.js +58 -0
  384. package/dist/util/keyboard-inset.d.ts +17 -0
  385. package/dist/util/keyboard-inset.js +53 -0
  386. package/dist/util/map-view.d.ts +143 -0
  387. package/dist/util/map-view.js +121 -0
  388. package/dist/util/media-grid.d.ts +50 -0
  389. package/dist/util/media-grid.js +81 -0
  390. package/dist/util/media.d.ts +5 -0
  391. package/dist/util/media.js +10 -0
  392. package/dist/util/overlay-chrome.d.ts +52 -0
  393. package/dist/util/overlay-chrome.js +56 -0
  394. package/dist/util/overlay.d.ts +71 -0
  395. package/dist/util/overlay.js +240 -0
  396. package/dist/util/photo.d.ts +233 -0
  397. package/dist/util/photo.js +209 -0
  398. package/dist/util/portal.d.ts +22 -0
  399. package/dist/util/portal.js +50 -0
  400. package/dist/util/progress.d.ts +18 -0
  401. package/dist/util/progress.js +17 -0
  402. package/dist/util/pull-to-refresh.d.ts +26 -0
  403. package/dist/util/pull-to-refresh.js +30 -0
  404. package/dist/util/radio-keys.d.ts +24 -0
  405. package/dist/util/radio-keys.js +49 -0
  406. package/dist/util/rating.d.ts +22 -0
  407. package/dist/util/rating.js +26 -0
  408. package/dist/util/ready-made-strings.d.ts +76 -0
  409. package/dist/util/ready-made-strings.js +67 -0
  410. package/dist/util/scroll-edge.d.ts +32 -0
  411. package/dist/util/scroll-edge.js +87 -0
  412. package/dist/util/scroll-padding.d.ts +19 -0
  413. package/dist/util/scroll-padding.js +44 -0
  414. package/dist/util/segmented-control.d.ts +15 -0
  415. package/dist/util/segmented-control.js +1 -0
  416. package/dist/util/select.d.ts +40 -0
  417. package/dist/util/select.js +63 -0
  418. package/dist/util/sheet-drag.d.ts +83 -0
  419. package/dist/util/sheet-drag.js +311 -0
  420. package/dist/util/sidebar.d.ts +62 -0
  421. package/dist/util/sidebar.js +38 -0
  422. package/dist/util/skeleton.d.ts +14 -0
  423. package/dist/util/skeleton.js +22 -0
  424. package/dist/util/sliding-indicator.d.ts +24 -0
  425. package/dist/util/sliding-indicator.js +44 -0
  426. package/dist/util/sortable-list.d.ts +64 -0
  427. package/dist/util/sortable-list.js +53 -0
  428. package/dist/util/stepper.d.ts +39 -0
  429. package/dist/util/stepper.js +38 -0
  430. package/dist/util/swipeable-list-item.d.ts +35 -0
  431. package/dist/util/swipeable-list-item.js +32 -0
  432. package/dist/util/temporal-field.d.ts +88 -0
  433. package/dist/util/temporal-field.js +136 -0
  434. package/dist/util/theme-mode.d.ts +79 -0
  435. package/dist/util/theme-mode.js +121 -0
  436. package/dist/util/toaster.d.ts +132 -0
  437. package/dist/util/toaster.js +130 -0
  438. package/dist/util/tone-surface.d.ts +33 -0
  439. package/dist/util/tone-surface.js +35 -0
  440. package/dist/util/tooltip.d.ts +9 -0
  441. package/dist/util/tooltip.js +18 -0
  442. package/dist/util/top-navbar.d.ts +34 -0
  443. package/dist/util/top-navbar.js +28 -0
  444. package/dist/util/touch-target.d.ts +30 -0
  445. package/dist/util/touch-target.js +30 -0
  446. package/dist/util/zabi-strings.d.ts +141 -0
  447. package/dist/util/zabi-strings.js +105 -0
  448. package/dist/zabi-components-colors.css +11 -2
  449. package/dist/zabi-components-theme-dark-only.css +870 -258
  450. package/dist/zabi-components-theme-dark.css +870 -258
  451. package/dist/zabi-components-theme-only.css +2419 -368
  452. package/dist/zabi-components-theme.css +2419 -368
  453. package/dist/zabi-components.css +6032 -711
  454. package/docs/lucide-icons.md +40 -17
  455. package/docs/theme-imports.md +81 -11
  456. package/package.json +45 -8
@@ -0,0 +1,456 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { SizeVariant } from "../types/variants.js";
4
+ import { cn } from "../util/cn.js";
5
+ import { resetValueOf, groupBinding } from "../util/hydration.js";
6
+ import { isRtl, radioKeyIndex } from "../util/radio-keys.js";
7
+ import type { SegmentedControlOption } from "../util/segmented-control.js";
8
+ import { generateId } from "../util/ssr-safe.js";
9
+ import {
10
+ afterFrame,
11
+ measureIndicator,
12
+ observeSizes,
13
+ type IndicatorBox,
14
+ } from "../util/sliding-indicator.js";
15
+
16
+ /** Other attributes (`data-*`, `id`, ...) land on the host element. */
17
+ type Props = Omit<
18
+ HTMLAttributes<HTMLDivElement>,
19
+ "class" | "onchange" | "aria-label" | "aria-labelledby"
20
+ > & {
21
+ /** Extra classes for the host element. */
22
+ class?: string;
23
+ /** Two to four choices, shown in one row. */
24
+ options: SegmentedControlOption[];
25
+ /** Selected value; supports `bind:value`. `undefined` for no selection. */
26
+ value?: string | undefined;
27
+ /** Accessible name of the group. Not shown. */
28
+ label?: string;
29
+ /** Accessible name of the group, the same as `label`. */
30
+ "aria-label"?: string;
31
+ /** Id of a visible element that names the group, in place of `label`. */
32
+ "aria-labelledby"?: string;
33
+ /** Height, on the scale Input and Button use. At least 44px on a touch screen. */
34
+ size?: SizeVariant;
35
+ /** Equal-width segments that fill the row. Off: each segment is as wide as its label. */
36
+ fullWidth?: boolean;
37
+ /**
38
+ * Name the value is submitted under in a form. Without a name the
39
+ * control is not part of the form around it: it submits nothing and a
40
+ * form reset leaves it alone.
41
+ */
42
+ name?: string;
43
+ /**
44
+ * The look of the selected segment. `neutral`: a card-coloured thumb with
45
+ * the headline colour on a tinted track, which does not compete with the
46
+ * screen's main button. `primary`: a solid primary fill, as in 8.1.
47
+ */
48
+ tone?: "neutral" | "primary";
49
+ disabled?: boolean;
50
+ /** Called with the new value when the user picks another segment. */
51
+ onchange?: (value: string) => void;
52
+ };
53
+
54
+ let {
55
+ class: className = "",
56
+ options,
57
+ value = $bindable(),
58
+ label = "",
59
+ "aria-label": ariaLabel,
60
+ "aria-labelledby": ariaLabelledby,
61
+ size = "md",
62
+ fullWidth = true,
63
+ name = "",
64
+ tone = "neutral",
65
+ disabled = false,
66
+ onchange,
67
+ ...restProps
68
+ }: Props = $props();
69
+
70
+ $effect(() => {
71
+ if (import.meta.env?.DEV && (options.length < 2 || options.length > 4)) {
72
+ console.warn(
73
+ `[zabi-components] SegmentedControl is made for 2 to 4 options and got ${options.length}. Use Tabs, RadioGroup or Select for more.`,
74
+ );
75
+ }
76
+ });
77
+
78
+ /**
79
+ * The radios always share a name, the caller's or one made here: without
80
+ * one they are not a group to the browser, and before the page hydrates a
81
+ * second choice would leave the first one checked beside it.
82
+ *
83
+ * With a name made here they are also kept out of any form around them
84
+ * (`form` names a form that does not exist), so nothing is submitted
85
+ * that the caller did not name, as before.
86
+ */
87
+ const fallbackName = generateId("segmented");
88
+ const groupName = $derived(name || fallbackName);
89
+
90
+ const enabled = $derived(
91
+ options.filter((option) => !disabled && !option.disabled),
92
+ );
93
+
94
+ /**
95
+ * The sliding indicator. Until a box has been measured (on the server,
96
+ * before hydration, with no layout) it is null and the checked segment draws
97
+ * its own selected look; with a box, the host is marked ready and that look
98
+ * moves to the indicator, which slides between segments.
99
+ */
100
+ let host = $state<HTMLDivElement | null>(null);
101
+ let indicator = $state<IndicatorBox | null>(null);
102
+ /** Off for the first placement, so the indicator appears in place and does not fly in. */
103
+ let indicatorMotion = $state(false);
104
+
105
+ const faceOf = (input: HTMLInputElement | undefined) =>
106
+ input?.nextElementSibling as HTMLElement | null | undefined;
107
+
108
+ function measure() {
109
+ const face = value === undefined ? undefined : faceOf(inputs[value]);
110
+ indicator = host && face ? measureIndicator(host, face) : null;
111
+ }
112
+
113
+ // After the DOM has followed a change of selection, options, size or look.
114
+ $effect(() => {
115
+ void [options, size, fullWidth, tone, host, value];
116
+ measure();
117
+ });
118
+
119
+ // The segments wrap and resize with zoomed text, a loaded font or a narrower container.
120
+ $effect(() => {
121
+ void options;
122
+ if (!host) return;
123
+ return observeSizes([host, ...Object.values(inputs).map(faceOf)], measure);
124
+ });
125
+
126
+ $effect(() => {
127
+ if (!indicator || indicatorMotion) return;
128
+ return afterFrame(() => (indicatorMotion = true));
129
+ });
130
+
131
+ /** The segment that takes Tab: the selected one, or the first that can be chosen. */
132
+ const tabStop = $derived(
133
+ enabled.find((option) => option.value === value)?.value ?? enabled[0]?.value,
134
+ );
135
+
136
+ const inputs: Record<string, HTMLInputElement | undefined> = $state({});
137
+
138
+ /** A press on the selected segment is not a change: a single selection never clears. */
139
+ function select(next: string) {
140
+ if (disabled || next === value) return;
141
+ value = next;
142
+ onchange?.(next);
143
+ }
144
+
145
+ /**
146
+ * The radios are bound, so a segment picked before the page hydrated is
147
+ * kept: Svelte's binding hands it over here instead of overwriting it
148
+ * (see util/hydration.ts), and it goes through `select` like any other
149
+ * choice, `onchange` included. A form reset empties the selection, as it
150
+ * empties a bound native input.
151
+ */
152
+ const chosen = groupBinding<string>(
153
+ () => value,
154
+ (next) => select(next),
155
+ () => {
156
+ if (!disabled) value = resetValueOf<string>(Object.values(inputs));
157
+ },
158
+ );
159
+
160
+ /** `focused` is the value of the segment the key was pressed on, which need not be the selected one. */
161
+ function handleKeydown(event: KeyboardEvent, focused: string) {
162
+ const next = radioKeyIndex(
163
+ event.key,
164
+ enabled.findIndex((option) => option.value === focused),
165
+ enabled.length,
166
+ isRtl(event.currentTarget as Element),
167
+ enabled.some((option) => option.value === value),
168
+ );
169
+ if (next === -1) return;
170
+ event.preventDefault();
171
+ select(enabled[next].value);
172
+ inputs[enabled[next].value]?.focus();
173
+ }
174
+ </script>
175
+
176
+ <!-- Native radio inputs: one Tab stop, Space selects, a press on the selected
177
+ one changes nothing, and the value goes with a form. -->
178
+ <div
179
+ class={cn("segmented", fullWidth ? "w-full" : "", className)}
180
+ data-size={size}
181
+ data-tone={tone}
182
+ data-indicator={indicator ? "ready" : undefined}
183
+ data-indicator-motion={indicator && indicatorMotion ? "" : undefined}
184
+ bind:this={host}
185
+ data-full-width={fullWidth ? "" : undefined}
186
+ role="radiogroup"
187
+ aria-label={ariaLabelledby ? undefined : label || ariaLabel}
188
+ aria-labelledby={ariaLabelledby}
189
+ aria-disabled={disabled ? "true" : undefined}
190
+ {...restProps}
191
+ >
192
+ {#if indicator}
193
+ <!-- Under the segments (it comes first and they are positioned), and never in their way. -->
194
+ <span
195
+ class="segment-indicator"
196
+ aria-hidden="true"
197
+ data-disabled={disabled || options.find((option) => option.value === value)?.disabled
198
+ ? ""
199
+ : undefined}
200
+ style="transform: translate({indicator.x}px, {indicator.y}px); width: {indicator.width}px; height: {indicator.height}px;"
201
+ ></span>
202
+ {/if}
203
+ {#each options as option (option.value)}
204
+ {@const optionDisabled = disabled || !!option.disabled}
205
+ {@const Icon = option.icon}
206
+ <label class="segment">
207
+ <input
208
+ bind:this={inputs[option.value]}
209
+ type="radio"
210
+ class="sr-only"
211
+ name={groupName}
212
+ form={name ? undefined : groupName}
213
+ value={option.value}
214
+ bind:group={chosen.value}
215
+ disabled={optionDisabled}
216
+ tabindex={option.value === tabStop ? 0 : -1}
217
+ onkeydown={(event) => handleKeydown(event, option.value)}
218
+ />
219
+ <span class="segment-face text-sm font-medium">
220
+ {#if Icon}
221
+ <span class="flex shrink-0" aria-hidden="true"><Icon size={16} /></span>
222
+ {/if}
223
+ <span class="segment-label">{option.label}</span>
224
+ </span>
225
+ </label>
226
+ {/each}
227
+ </div>
228
+
229
+ <style>
230
+ /*
231
+ * The states hang off the radio input (`:checked`, `:disabled`,
232
+ * `:focus-visible`), which utilities on the face could only reach through
233
+ * a `peer-*` variant per declaration.
234
+ *
235
+ * Columns are as many as fit at 3.5rem each, so two to four segments share
236
+ * one row down to a 320px screen, and fold into rows instead of squeezing
237
+ * the labels when the text is zoomed or the container is narrower still.
238
+ */
239
+ .segmented {
240
+ --zabi-segment-height: 2.125rem;
241
+ /* In px: side padding that grew with zoomed text would take the room the text needs. */
242
+ --zabi-segment-pad: 12px;
243
+ display: grid;
244
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 3.5rem), 1fr));
245
+ /* One value for the padding and the gap, and for the segments' corner radius below. */
246
+ --zabi-segment-inset: 0.125rem;
247
+ gap: var(--zabi-segment-inset);
248
+ padding: var(--zabi-segment-inset);
249
+ border: 1px solid var(--color-border);
250
+ border-radius: var(--radius-control);
251
+ background: var(--color-action-secondary);
252
+ /* The sliding indicator is placed against the padding box. */
253
+ position: relative;
254
+ }
255
+
256
+ .segmented:not([data-full-width]) {
257
+ display: inline-flex;
258
+ flex-wrap: wrap;
259
+ max-width: 100%;
260
+ }
261
+
262
+ .segmented[data-size="sm"] {
263
+ --zabi-segment-height: 1.625rem;
264
+ --zabi-segment-pad: 8px;
265
+ }
266
+
267
+ .segmented[data-size="lg"] {
268
+ --zabi-segment-height: 2.625rem;
269
+ --zabi-segment-pad: 16px;
270
+ }
271
+
272
+ /* A finger needs 44px whatever the size says. */
273
+ @media (pointer: coarse) {
274
+ .segmented,
275
+ .segmented[data-size] {
276
+ --zabi-segment-height: 2.75rem;
277
+ }
278
+ }
279
+
280
+ .segment {
281
+ position: relative;
282
+ display: flex;
283
+ min-width: 0;
284
+ cursor: pointer;
285
+ -webkit-tap-highlight-color: transparent;
286
+ }
287
+
288
+ .segmented:not([data-full-width]) .segment {
289
+ flex: 0 1 auto;
290
+ min-width: 3.5rem;
291
+ }
292
+
293
+ .segment-face {
294
+ display: flex;
295
+ flex: 1;
296
+ align-items: center;
297
+ justify-content: center;
298
+ /* An icon moves above its label before the label has to break. */
299
+ flex-wrap: wrap;
300
+ gap: 0.125rem 0.5rem;
301
+ min-width: 0;
302
+ min-height: var(--zabi-segment-height);
303
+ padding: 0.25rem var(--zabi-segment-pad);
304
+ /* Nested corners: the track's radius less what lies between its edge and
305
+ the segment, which is the track's padding and its 1px border. */
306
+ border-radius: calc(var(--radius-control) - var(--zabi-segment-inset) - 1px);
307
+ color: var(--color-body);
308
+ line-height: 1.25;
309
+ text-align: center;
310
+ transition:
311
+ background-color var(--duration-base),
312
+ color var(--duration-base);
313
+ }
314
+
315
+ /* A long label wraps onto a second line, and breaks inside a word only when a word alone does not fit. */
316
+ .segment-label {
317
+ min-width: 0;
318
+ overflow-wrap: anywhere;
319
+ hyphens: auto;
320
+ }
321
+
322
+ /* Only where a pointer can hover: after a tap, `:hover` stays on the segment until the next tap elsewhere. */
323
+ @media (hover: hover) {
324
+ .segment:hover input:enabled:not(:checked) + .segment-face {
325
+ background: var(--color-surface-hover);
326
+ }
327
+ }
328
+
329
+ .segment:active input:enabled:not(:checked) + .segment-face {
330
+ background: var(--color-surface-active);
331
+ }
332
+
333
+ /*
334
+ * The selected look, drawn by the segment itself: before the page hydrates,
335
+ * and wherever the indicator cannot be measured. Neutral is a thumb: the
336
+ * card colour with a soft shadow and an edge (the edge, held to 3:1 against
337
+ * the track, is what tells it from the others; the fill is only a step).
338
+ */
339
+ input:checked + .segment-face {
340
+ background: var(--color-segment-thumb);
341
+ color: var(--color-segment-thumb-text);
342
+ box-shadow:
343
+ var(--shadow-sm),
344
+ 0 0 0 1px var(--color-segment-thumb-border);
345
+ }
346
+
347
+ :where(.segmented[data-tone="primary"]) input:checked + .segment-face {
348
+ background: var(--color-action-primary);
349
+ color: var(--color-action-primary-text);
350
+ box-shadow: none;
351
+ }
352
+
353
+ /* With an indicator the segment keeps only its label colour. */
354
+ @media (forced-colors: none) {
355
+ :where(.segmented[data-indicator="ready"]) input:checked + .segment-face {
356
+ background: transparent;
357
+ box-shadow: none;
358
+ }
359
+ }
360
+
361
+ .segment-indicator {
362
+ position: absolute;
363
+ top: 0;
364
+ left: 0;
365
+ pointer-events: none;
366
+ border-radius: calc(var(--radius-control) - var(--zabi-segment-inset) - 1px);
367
+ background: var(--color-segment-thumb);
368
+ box-shadow:
369
+ var(--shadow-sm),
370
+ 0 0 0 1px var(--color-segment-thumb-border);
371
+ }
372
+
373
+ .segmented[data-tone="primary"] .segment-indicator {
374
+ background: var(--color-action-primary);
375
+ box-shadow: none;
376
+ }
377
+
378
+ /* Only once the first placement is on screen: the thumb then slides and resizes. */
379
+ .segmented[data-indicator-motion] .segment-indicator {
380
+ transition:
381
+ transform var(--duration-moderate) var(--ease-spring),
382
+ width var(--duration-moderate) var(--ease-spring),
383
+ height var(--duration-moderate) var(--ease-spring);
384
+ }
385
+
386
+ /* Forced colours keep the segment's own Highlight look below. */
387
+ @media (forced-colors: active) {
388
+ .segment-indicator {
389
+ display: none;
390
+ }
391
+ }
392
+
393
+ /* Same geometry as `.focus-ring`: a 2px gap, then a 2px ring. */
394
+ input:focus-visible + .segment-face {
395
+ box-shadow:
396
+ 0 0 0 2px var(--color-focus-ring-offset),
397
+ 0 0 0 4px var(--color-focus-ring);
398
+ }
399
+
400
+ .segment:has(input:disabled) {
401
+ cursor: not-allowed;
402
+ }
403
+
404
+ input:disabled + .segment-face {
405
+ opacity: 0.5;
406
+ }
407
+
408
+ /* A disabled neutral thumb keeps its edge at full strength, so it is still seen; only its label dims. */
409
+ :where(.segmented[data-tone="neutral"]) input:checked:disabled + .segment-face {
410
+ opacity: 1;
411
+ color: color-mix(in srgb, var(--color-segment-thumb-text) 50%, transparent);
412
+ }
413
+
414
+ .segmented[data-tone="primary"] .segment-indicator[data-disabled] {
415
+ opacity: 0.5;
416
+ }
417
+
418
+ /*
419
+ * Forced colours paint every background with the canvas colour and drop
420
+ * every box-shadow, which would leave the selected segment looking like
421
+ * the others and keyboard focus with no mark. The selection takes the
422
+ * system's own selection colours, and focus an outline, as `.focus-ring`
423
+ * gets in app.css.
424
+ */
425
+ @media (forced-colors: active) {
426
+ input:checked + .segment-face {
427
+ forced-color-adjust: none;
428
+ background: Highlight;
429
+ color: HighlightText;
430
+ }
431
+
432
+ input:disabled + .segment-face {
433
+ opacity: 1;
434
+ color: GrayText;
435
+ }
436
+
437
+ input:checked:disabled + .segment-face {
438
+ background: GrayText;
439
+ color: Canvas;
440
+ }
441
+
442
+ /* The selected face opted out of forced colours above, so its shadow ring would come back beside the outline. */
443
+ input:focus-visible + .segment-face {
444
+ box-shadow: none;
445
+ outline: 2px solid Highlight;
446
+ outline-offset: 2px;
447
+ }
448
+ }
449
+
450
+ @media (prefers-reduced-motion: reduce) {
451
+ .segment-face,
452
+ .segmented[data-indicator-motion] .segment-indicator {
453
+ transition: none;
454
+ }
455
+ }
456
+ </style>
@@ -0,0 +1,40 @@
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ import type { SizeVariant } from "../types/variants.js";
3
+ import type { SegmentedControlOption } from "../util/segmented-control.js";
4
+ /** Other attributes (`data-*`, `id`, ...) land on the host element. */
5
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "onchange" | "aria-label" | "aria-labelledby"> & {
6
+ /** Extra classes for the host element. */
7
+ class?: string;
8
+ /** Two to four choices, shown in one row. */
9
+ options: SegmentedControlOption[];
10
+ /** Selected value; supports `bind:value`. `undefined` for no selection. */
11
+ value?: string | undefined;
12
+ /** Accessible name of the group. Not shown. */
13
+ label?: string;
14
+ /** Accessible name of the group, the same as `label`. */
15
+ "aria-label"?: string;
16
+ /** Id of a visible element that names the group, in place of `label`. */
17
+ "aria-labelledby"?: string;
18
+ /** Height, on the scale Input and Button use. At least 44px on a touch screen. */
19
+ size?: SizeVariant;
20
+ /** Equal-width segments that fill the row. Off: each segment is as wide as its label. */
21
+ fullWidth?: boolean;
22
+ /**
23
+ * Name the value is submitted under in a form. Without a name the
24
+ * control is not part of the form around it: it submits nothing and a
25
+ * form reset leaves it alone.
26
+ */
27
+ name?: string;
28
+ /**
29
+ * The look of the selected segment. `neutral`: a card-coloured thumb with
30
+ * the headline colour on a tinted track, which does not compete with the
31
+ * screen's main button. `primary`: a solid primary fill, as in 8.1.
32
+ */
33
+ tone?: "neutral" | "primary";
34
+ disabled?: boolean;
35
+ /** Called with the new value when the user picks another segment. */
36
+ onchange?: (value: string) => void;
37
+ };
38
+ declare const SegmentedControl: import("svelte").Component<Props, {}, "value">;
39
+ type SegmentedControl = ReturnType<typeof SegmentedControl>;
40
+ export default SegmentedControl;
@@ -1,11 +1,18 @@
1
1
  <script lang="ts">
2
+ import { zabiStringsFor } from "../util/zabi-strings.js";
2
3
  import { cn } from "../util/cn.js";
4
+ import {
5
+ DEFAULT_SIDEBAR_BRAND_HEADER_STRINGS,
6
+ type SidebarBrandHeaderStrings,
7
+ } from "../util/sidebar.js";
3
8
 
4
9
  interface Props {
5
10
  collapsed?: boolean;
6
11
  brandName?: string;
7
12
  logoSrc?: string;
8
13
  logoAlt?: string;
14
+ /** `brandAlt`: the logo's alt text when neither `logoAlt` nor `brandName` is given. */
15
+ strings?: Partial<SidebarBrandHeaderStrings>;
9
16
  class?: string;
10
17
  /** @deprecated use `class`. */
11
18
  className?: string;
@@ -16,10 +23,13 @@
16
23
  brandName = "",
17
24
  logoSrc = "",
18
25
  logoAlt = "",
26
+ strings,
19
27
  class: classAttr = "",
20
28
  className: legacyClass = "",
21
29
  }: Props = $props();
22
30
 
31
+ const provided = zabiStringsFor("sidebarBrandHeader");
32
+
23
33
  /** `class` is the public prop; `className` is a deprecated alias.
24
34
  * Both are merged here so existing call sites keep working. */
25
35
  const className = $derived(`${classAttr} ${legacyClass}`.trim());
@@ -52,7 +62,7 @@
52
62
  >
53
63
  <img
54
64
  src={logoSrc}
55
- alt={logoAlt || trimmedName || "Brand"}
65
+ alt={logoAlt || trimmedName || (strings?.brandAlt ?? provided()?.brandAlt ?? DEFAULT_SIDEBAR_BRAND_HEADER_STRINGS.brandAlt)}
56
66
  class="size-full object-cover"
57
67
  width="36"
58
68
  height="36"
@@ -1,8 +1,11 @@
1
+ import { type SidebarBrandHeaderStrings } from "../util/sidebar.js";
1
2
  interface Props {
2
3
  collapsed?: boolean;
3
4
  brandName?: string;
4
5
  logoSrc?: string;
5
6
  logoAlt?: string;
7
+ /** `brandAlt`: the logo's alt text when neither `logoAlt` nor `brandName` is given. */
8
+ strings?: Partial<SidebarBrandHeaderStrings>;
6
9
  class?: string;
7
10
  /** @deprecated use `class`. */
8
11
  className?: string;
@@ -1,10 +1,15 @@
1
1
  <script lang="ts">
2
+ import { mergeStrings } from "../util/ready-made-strings.js";
3
+ import { zabiStringsFor } from "../util/zabi-strings.js";
2
4
  import type { Snippet } from "svelte";
3
5
  import { fixedSidebarFlyout } from "../util/fixed-sidebar-flyout.js";
4
6
  import { cn } from "../util/cn.js";
7
+ import { DEFAULT_SIDEBAR_FOOTER_STRINGS, type SidebarFooterStrings } from "../util/sidebar.js";
5
8
 
6
9
  interface Props {
7
10
  collapsed?: boolean;
11
+ /** The footer's name and the profile button's, for another language. */
12
+ strings?: Partial<SidebarFooterStrings>;
8
13
  showProfile?: boolean;
9
14
  profileName?: string;
10
15
  profileEmail?: string;
@@ -27,6 +32,7 @@
27
32
 
28
33
  let {
29
34
  collapsed = false,
35
+ strings,
30
36
  showProfile = true,
31
37
  profileName = "Zabi",
32
38
  profileEmail = "hello@zabi.dev",
@@ -35,7 +41,7 @@
35
41
  logoutLabel = "Logout",
36
42
  showThemeToggle = true,
37
43
  lightModeLabel = "Light mode",
38
- isLightMode = $bindable(false),
44
+ isLightMode = $bindable<Exclude<Props["isLightMode"], undefined>>(),
39
45
  onLogout,
40
46
  onThemeToggle,
41
47
  class: classAttr = "",
@@ -46,15 +52,32 @@
46
52
  profilePanel,
47
53
  }: Props = $props();
48
54
 
55
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
56
+ // one that has a fallback (`props_invalid_value`), and a page that throws
57
+ // while it hydrates never becomes interactive. The default is applied
58
+ // here instead: at once, for the server and the first render, and again
59
+ // whenever a parent hands back `undefined`.
60
+ const applyDefaults = () => {
61
+ if (isLightMode === undefined) isLightMode = false;
62
+ };
63
+ applyDefaults();
64
+ $effect.pre(applyDefaults);
65
+
49
66
  /** `class` is the public prop; `className` is a deprecated alias.
50
67
  * Both are merged here so existing call sites keep working. */
51
68
  const className = $derived(cn(`${classAttr} ${legacyClass}`));
52
69
 
70
+ /** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
71
+ const provided = zabiStringsFor("sidebarFooter");
72
+ const text = $derived(mergeStrings(DEFAULT_SIDEBAR_FOOTER_STRINGS, provided(), strings));
73
+
53
74
  const showFooter = $derived(showProfile || showLogout || showThemeToggle);
54
75
  const showPanelLauncher = $derived(showProfile);
55
76
 
77
+ // Nested corners: the avatar sits 0.5rem (`px-2 py-2`) inside the profile
78
+ // button, so its radius is the button's less that gap.
56
79
  const avatarClasses =
57
- "size-10 rounded-container bg-action-primary text-action-primary flex items-center justify-center text-sm font-semibold shrink-0 ring-1 ring-border-focus";
80
+ "size-10 rounded-[calc(var(--radius-container)-0.5rem)] bg-action-primary text-action-primary flex items-center justify-center text-sm font-semibold shrink-0 ring-1 ring-border";
58
81
 
59
82
  function getTextToneClass(isMuted = false): string {
60
83
  return isMuted ? "text-description" : "text-headline";
@@ -68,22 +91,22 @@
68
91
  {#if showFooter}
69
92
  <div class="w-full shrink-0" data-sidebar-flyout-root>
70
93
  <footer
71
- class={cn(`flex w-full shrink-0 flex-col gap-3 border-t border-border pt-3 pb-1 ${className}`)}
72
- aria-label="Account and settings"
94
+ class={cn(`flex w-full shrink-0 flex-col gap-3 border-t border-border-weak pt-3 pb-1 ${className}`)}
95
+ aria-label={text.accountAndSettings}
73
96
  >
74
97
  {#if showPanelLauncher}
75
98
  <button
76
99
  type="button"
77
100
  data-sidebar-flyout-anchor="profile"
78
- class="w-full cursor-pointer rounded-container px-2 py-2 outline-none transition-colors hover:bg-nav-menu-hover hover:text-nav-menu-item-hover hover:ring-1 hover:ring-border/60 active:bg-surface-active focus-ring focus-ring--nav"
101
+ class="w-full cursor-pointer rounded-container px-2 py-2 outline-none transition-colors hover:bg-nav-menu-hover hover:text-nav-menu-item-hover hover:ring-1 hover:ring-border active:bg-surface-active focus-ring focus-ring--nav"
79
102
  aria-haspopup="dialog"
80
103
  aria-expanded={profilePanelOpen}
81
104
  aria-controls={profilePanelControlsId.trim()
82
105
  ? profilePanelControlsId.trim()
83
106
  : undefined}
84
107
  aria-label={collapsed
85
- ? `Open account panel`
86
- : `Open account panel for ${profileName}`}
108
+ ? text.openAccountPanel
109
+ : text.openAccountPanelFor(profileName)}
87
110
  onclick={handleProfileClick}
88
111
  >
89
112
  <span class="flex w-full items-center gap-3 text-left">
@@ -1,6 +1,9 @@
1
1
  import type { Snippet } from "svelte";
2
+ import { type SidebarFooterStrings } from "../util/sidebar.js";
2
3
  interface Props {
3
4
  collapsed?: boolean;
5
+ /** The footer's name and the profile button's, for another language. */
6
+ strings?: Partial<SidebarFooterStrings>;
4
7
  showProfile?: boolean;
5
8
  profileName?: string;
6
9
  profileEmail?: string;
@@ -41,7 +41,7 @@
41
41
  <div class={cn(`flex w-full flex-col gap-2 ${className}`)}>
42
42
  {#if showHeading && headingId}
43
43
  <h2
44
- class="px-0 text-xs font-semibold uppercase tracking-wider text-description"
44
+ class="px-0 text-xs font-medium text-description"
45
45
  id={headingId}
46
46
  >
47
47
  {trimmedTitle}