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,109 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import { type BottomSheetCloseReason, type BottomSheetSnap } from "../util/bottom-sheet.js";
4
+ /**
5
+ * A panel that slides up from the bottom of the screen for a picker, a
6
+ * set of filters or a short form, and rests at half or full height.
7
+ *
8
+ * It is a modal dialog, on the same footing as Modal, SlideUp and Drawer:
9
+ * focus moves in and stays in, the page behind does not scroll, and
10
+ * Escape, the backdrop, the close button or a swipe down close it and
11
+ * return focus to what opened it.
12
+ *
13
+ * ```svelte
14
+ * <BottomSheet bind:isOpen title="Filters" bind:snap>
15
+ * …
16
+ * {#snippet footer()}
17
+ * <Button onclick={apply}>Show results</Button>
18
+ * {/snippet}
19
+ * </BottomSheet>
20
+ * ```
21
+ *
22
+ * The grip at the top drags the sheet between its snap points and, past
23
+ * the lowest, closed. It is also a button that moves the sheet a step, so
24
+ * the same is there for a keyboard and a screen reader. A swipe on the
25
+ * content scrolls the content; it moves the sheet only when the content
26
+ * is at its top and the swipe goes down. A box inside the content that
27
+ * scrolls by itself (a long list, a text area) keeps every swipe that
28
+ * starts on it, at its top too, and so does anything carrying
29
+ * `data-sheet-no-drag` (a map, a wheel, a swipeable row) or a range input.
30
+ *
31
+ * From the `md` breakpoint up the sheet is still attached to the bottom
32
+ * edge, centred and no wider than 40rem.
33
+ *
34
+ * Other attributes (`id`, `data-*`, ...) land on the `role="dialog"` panel.
35
+ */
36
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "title" | "onclose" | "onkeydown"> & {
37
+ isOpen?: boolean;
38
+ /** Heading of the sheet, and its accessible name. */
39
+ title: string;
40
+ description?: string;
41
+ /**
42
+ * The heights the sheet can rest at: `half` of the screen, `full`
43
+ * down from the status bar. With one, the grip only drags.
44
+ */
45
+ snapPoints?: BottomSheetSnap[];
46
+ /**
47
+ * The snap point the sheet is at. Bindable. It opens at the one you
48
+ * pass, or at the lowest of `snapPoints`. It is kept while the sheet
49
+ * is closed: one left at `full` opens at `full` again, bound or not.
50
+ * Set it when you open the sheet to start somewhere else.
51
+ */
52
+ snap?: BottomSheetSnap;
53
+ /**
54
+ * The sheet is as tall as its content, up to half the screen, instead
55
+ * of half the screen whatever it holds. It has one resting height, so
56
+ * `snapPoints` and `snap` are ignored and the grip only drags. Off by
57
+ * default.
58
+ */
59
+ fit?: boolean;
60
+ /**
61
+ * With `fit`: the tallest the sheet grows before its content scrolls.
62
+ * `half` (the default) is half the screen; `full` is down from the
63
+ * status bar, like the `full` snap point. Ignored without `fit`.
64
+ */
65
+ fitMax?: "half" | "full";
66
+ /**
67
+ * Render the overlay in `document.body`, so an ancestor with a
68
+ * transform, filter or clipped overflow cannot trap it; pass `false`
69
+ * to render in place. The theme class belongs on `<html>` or `<body>`;
70
+ * set lower, it does not reach a portalled sheet.
71
+ */
72
+ portal?: boolean;
73
+ /**
74
+ * When false, Escape, a backdrop click, the close button and a swipe
75
+ * down do not close the sheet. Focus stays trapped, the grip still
76
+ * moves it between snap points, and setting `isOpen` yourself still
77
+ * closes it.
78
+ */
79
+ dismissible?: boolean;
80
+ /** Fired when the sheet closes itself, with what the user did. */
81
+ onclose?: (detail: {
82
+ reason: BottomSheetCloseReason;
83
+ }) => void;
84
+ /**
85
+ * Hears every keydown in the sheet, after the sheet has handled
86
+ * Escape. The Tab cycle is kept either way.
87
+ */
88
+ onkeydown?: (event: KeyboardEvent) => void;
89
+ /** Accessible name of the close button. */
90
+ closeLabel?: string;
91
+ /** Accessible name of the grip while its button takes the sheet up a step. */
92
+ expandLabel?: string;
93
+ /** Accessible name of the grip while its button takes the sheet down a step. */
94
+ collapseLabel?: string;
95
+ /**
96
+ * CSS selector, looked up inside the panel, of the control that takes
97
+ * focus when the sheet opens (a search field, say). Without it, or
98
+ * with no match, the first control does: the grip, or the close button.
99
+ */
100
+ initialFocus?: string;
101
+ /** Extra classes for the dialog panel. */
102
+ class?: string;
103
+ children?: Snippet;
104
+ /** Pinned to the bottom of the panel, below the scrolling content. */
105
+ footer?: Snippet;
106
+ };
107
+ declare const BottomSheet: import("svelte").Component<Props, {}, "isOpen" | "snap">;
108
+ type BottomSheet = ReturnType<typeof BottomSheet>;
109
+ export default BottomSheet;
@@ -0,0 +1,403 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import Badge from "../atoms/Badge.svelte";
4
+ import { getAppShell, isDevBuild } from "../util/app-shell.js";
5
+ import {
6
+ activeTabHref,
7
+ badgeText,
8
+ defaultBadgeLabel,
9
+ type BottomTabBarItem,
10
+ } from "../util/bottom-tab-bar.js";
11
+ import { cn } from "../util/cn.js";
12
+ import { resolveScrolledUnder, watchScrollEdge, type ScrollEdgeMode } from "../util/scroll-edge.js";
13
+
14
+ /**
15
+ * The navigation bar at the bottom of a phone screen: three to five
16
+ * destinations, each an icon over a short label, within reach of a thumb.
17
+ *
18
+ * The tabs are links. Pressing the active one is an ordinary navigation to
19
+ * the page you are on; nothing is ever deselected.
20
+ *
21
+ * On its own the bar is fixed over the page and reserves no room. Give the
22
+ * page `padding-bottom` and `scroll-padding-bottom` of the bar's height
23
+ * (`calc(64px + env(safe-area-inset-bottom))`), or the end of the
24
+ * content, and a focused control there, sits under it. `AppShell` does
25
+ * this for you.
26
+ *
27
+ * The bar is one row, 64px tall, at every width and text size. It is
28
+ * chrome: when the reader enlarges text, the room belongs to the page.
29
+ * So its sizes are in px; a label is one line, grows with the text only
30
+ * up to 1.3 times its size and no further than its tab has room for, and
31
+ * is cut with an ellipsis, never inside a word, when it still does not
32
+ * fit. Where a tab is narrower than 52px (five tabs below 260px) the bar
33
+ * shows icons only. The link is always named by its full label.
34
+ *
35
+ * The bar is flush with the page and page-coloured at rest; once content
36
+ * is under it (not at the end of what scrolls) it turns to frosted glass
37
+ * with a hairline above it. Inside an `AppShell` the shell says when; on
38
+ * its own the bar watches the window.
39
+ *
40
+ * With `floating` the bar is a glass capsule inset from the screen edges
41
+ * instead: always glass, since content is always under it.
42
+ *
43
+ * ```svelte
44
+ * <BottomTabBar
45
+ * items={[
46
+ * { href: "/", label: "Home", icon: House },
47
+ * { href: "/quiz", label: "Quiz", icon: Trophy },
48
+ * { href: "/inbox", label: "Inbox", icon: Bell, badge: 3 },
49
+ * ]}
50
+ * active={page.url.pathname}
51
+ * />
52
+ * ```
53
+ */
54
+ type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
55
+ /** Three to five destinations. */
56
+ items: BottomTabBarItem[];
57
+ /**
58
+ * The href of the active tab, or the path of the current page: the tab
59
+ * for that page or the closest one above it is marked. Left out, the
60
+ * bar reads the address of the page once it runs in the browser. In
61
+ * SvelteKit pass `page.url.pathname`, so the server marks it too.
62
+ */
63
+ active?: string;
64
+ /**
65
+ * The href or path of the tab a person has just pressed, while its
66
+ * page is still on its way. It takes the mark (the pill, the heavier
67
+ * icon, the semibold label) at once, but `aria-current="page"` stays
68
+ * on the tab for `active` until `active` changes, and the pending link
69
+ * has `aria-busy`. Left out, or when it matches no tab, the mark and
70
+ * `aria-current` are on the same tab, as before.
71
+ */
72
+ pending?: string;
73
+ /** Accessible name of the navigation landmark. */
74
+ label?: string;
75
+ /**
76
+ * What a badge adds to the tab's accessible name, after the label and
77
+ * a comma. Replace it to translate or to say what is being counted.
78
+ */
79
+ badgeLabel?: (count: number, item: BottomTabBarItem) => string;
80
+ /** Counts above this show as `99+`. The accessible name keeps the real count. */
81
+ badgeMax?: number;
82
+ /**
83
+ * `fixed` pins the bar to the bottom of the screen; `static` leaves it
84
+ * where it is in the page. Defaults to `fixed`, or to `static` inside
85
+ * an `AppShell`, which places the bar itself.
86
+ */
87
+ position?: "fixed" | "static";
88
+ /**
89
+ * When the bar shows the glass and the hairline. `auto` follows the
90
+ * scroll position: flush at the end of the content, glass while
91
+ * content is under it. `always` is glass all the time, `never` flush
92
+ * all the time.
93
+ */
94
+ scrollEdge?: ScrollEdgeMode;
95
+ /**
96
+ * Draws the bar as a rounded capsule of glass, inset 12px from the
97
+ * screen edges (and from the home indicator), instead of edge to edge.
98
+ * Always the regular material, because content is always under it, so
99
+ * `scrollEdge` has no effect. The height a shell measures includes
100
+ * the 12px. The landmark, the safe-area insets and the position stay
101
+ * on the `<nav>`.
102
+ */
103
+ floating?: boolean;
104
+ class?: string;
105
+ };
106
+
107
+ let {
108
+ items,
109
+ active,
110
+ pending,
111
+ label = "Main",
112
+ badgeLabel = defaultBadgeLabel,
113
+ badgeMax = 99,
114
+ position,
115
+ scrollEdge = "auto",
116
+ floating = false,
117
+ class: className = "",
118
+ ...restProps
119
+ }: Props = $props();
120
+
121
+ const shell = getAppShell();
122
+ const inShell = shell !== undefined;
123
+ const resolvedPosition = $derived(position ?? (inShell ? "static" : "fixed"));
124
+
125
+ let host: HTMLElement | undefined = $state();
126
+ /** Content is under the bar: from the shell if there is one, else from the window. */
127
+ let ownScrolled = $state(false);
128
+ $effect(() => {
129
+ if (shell || scrollEdge !== "auto" || !host) return;
130
+ return watchScrollEdge(null, (edge) => (ownScrolled = edge.bottom));
131
+ });
132
+ const scrolledUnder = $derived(
133
+ resolveScrolledUnder(scrollEdge, shell ? shell.scrolledBottom : ownScrolled),
134
+ );
135
+
136
+ /**
137
+ * The page's own path, used while `active` is not given. Empty on the
138
+ * server and until the bar has mounted: nothing here may read `location`
139
+ * while rendering.
140
+ */
141
+ let locationPath = $state("");
142
+
143
+ $effect(() => {
144
+ if (active !== undefined) return;
145
+ const read = () => {
146
+ locationPath = window.location.pathname;
147
+ };
148
+ read();
149
+ // Back and forward, and every other same-document navigation where the
150
+ // browser reports them. A router that only calls `history.pushState`
151
+ // is not seen by a browser without the Navigation API: pass `active`.
152
+ window.addEventListener("popstate", read);
153
+ const navigation = (window as { navigation?: EventTarget }).navigation;
154
+ navigation?.addEventListener("currententrychange", read);
155
+ return () => {
156
+ window.removeEventListener("popstate", read);
157
+ navigation?.removeEventListener("currententrychange", read);
158
+ };
159
+ });
160
+
161
+ const activeHref = $derived(activeTabHref(items, active ?? locationPath));
162
+ const pendingHref = $derived(
163
+ pending !== undefined && pending !== "" ? activeTabHref(items, pending) : undefined,
164
+ );
165
+ /** The tab that wears the mark: the pending one, else the current one. */
166
+ const markHref = $derived(pendingHref ?? activeHref);
167
+
168
+ $effect(() => {
169
+ if (!isDevBuild()) return;
170
+ if (items.length < 3 || items.length > 5) {
171
+ console.warn(
172
+ `[zabi-components] BottomTabBar is made for 3 to 5 items and got ${items.length}. ` +
173
+ "With fewer, use links in the page; with more, move the rest into a menu.",
174
+ );
175
+ }
176
+ });
177
+
178
+ /**
179
+ * The colours are chosen here, not with an `aria-[current=page]:` variant:
180
+ * the hand-written colour classes sit outside Tailwind's cascade layer and
181
+ * would beat it (see TopNavbar).
182
+ */
183
+ function linkClasses(isActive: boolean): string {
184
+ // Every size is in px (the nav sets `--spacing: 4px`): in rem they
185
+ // doubled with the text size, and the bar was 177px tall at 200%.
186
+ //
187
+ // Radius: the active pill (32px high, so 16px at its ends) sits 4px
188
+ // inside this box on every side, so the box takes that radius plus
189
+ // the 4px. No gap between pill and label: 32px and a 16px line in a
190
+ // 56px box leave the same 4px above and below as at the sides.
191
+ const base =
192
+ "focus-ring focus-ring--nav flex min-h-14 w-full min-w-0 flex-col items-center justify-center gap-0 rounded-[20px] px-[2px] text-center no-underline transition-colors duration-(--duration-base) motion-reduce:transition-none";
193
+ return isActive
194
+ ? cn(base, "font-semibold text-nav-menu-item-active")
195
+ : cn(
196
+ base,
197
+ "font-medium text-nav-menu-item hover:bg-nav-menu-hover hover:text-nav-menu-item-hover active:bg-surface-active",
198
+ );
199
+ }
200
+ </script>
201
+
202
+ <nav
203
+ bind:this={host}
204
+ aria-label={label}
205
+ data-position={resolvedPosition}
206
+ data-bar-edge="bottom"
207
+ data-scrolled-under={String(scrolledUnder)}
208
+ data-floating={floating ? "true" : undefined}
209
+ class={cn(
210
+ "tabbar [--spacing:4px]",
211
+ floating
212
+ ? // Transparent: the capsule inside carries the glass. The gaps around
213
+ // it let clicks through to the page.
214
+ "pointer-events-none pb-[calc(env(safe-area-inset-bottom)+12px)] pl-[calc(env(safe-area-inset-left)+12px)] pr-[calc(env(safe-area-inset-right)+12px)]"
215
+ : // Clear of the home indicator, and of the rounded corners in landscape.
216
+ "material-bar pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
217
+ // The layer of the material is placed against the bar, so it is always positioned.
218
+ resolvedPosition === "fixed" ? "fixed inset-x-0 bottom-0 z-sticky" : "relative",
219
+ className,
220
+ )}
221
+ style:--tabbar-count={items.length}
222
+ {...restProps}
223
+ >
224
+ <!-- 8px between the tabs and at the sides while every tab can be 52px
225
+ wide; less as the bar narrows, down to none, so the tabs keep the width a
226
+ label needs for as long as there is any. See the style block. -->
227
+ {#if floating}
228
+ <!-- Radius: the tab's box is 20px and sits the list's gap (8px, less
229
+ in a narrow bar) inside the capsule on all four sides, so the capsule
230
+ is 20px plus that gap: outer radius = inner radius + padding. See
231
+ `.tabbar-capsule` in the style block. -->
232
+ <div class="tabbar-capsule material-layer-regular pointer-events-auto relative">
233
+ {@render tabs()}
234
+ </div>
235
+ {:else}
236
+ {@render tabs()}
237
+ {/if}
238
+ </nav>
239
+
240
+ {#snippet tabs()}
241
+ <ul class="tabbar-list m-0 flex list-none items-stretch py-1">
242
+ {#each items as item (item.href)}
243
+ {@const isActive = item.href === markHref}
244
+ {@const isCurrent = item.href === activeHref}
245
+ {@const isPending = pendingHref !== undefined && item.href === pendingHref && !isCurrent}
246
+ {@const Icon = isActive && item.activeIcon ? item.activeIcon : item.icon}
247
+ {@const count = item.badge ?? 0}
248
+ <li class="tabbar-tab flex">
249
+ <!-- The name is spelled out when there is a count: built from
250
+ the content, a browser reads "Inbox , 3 new" or leaves the
251
+ count out, depending on how it treats the hidden text. It
252
+ starts with the visible label, so voice control still finds it. -->
253
+ <a
254
+ href={item.href}
255
+ class={linkClasses(isActive)}
256
+ aria-current={isCurrent ? "page" : undefined}
257
+ aria-busy={isPending ? "true" : undefined}
258
+ aria-label={count > 0 ? `${item.label}, ${badgeLabel(count, item)}` : undefined}
259
+ >
260
+ <!-- The pill is the active mark, with no outline: a pill with a
261
+ ring is what keyboard focus looks like, and the bar would
262
+ seem to hold focus. Selection is not by colour alone. The
263
+ set of marks tells the active tab apart: the pill, the
264
+ icon (the filled `activeIcon`, or the same icon drawn
265
+ heavier), the semibold label and the active colour (4.5:1
266
+ for the label on the pill). WCAG 1.4.11 is met by the set,
267
+ not by the fill, which is within a shade of the bar. In
268
+ forced-colors mode the fill is dropped, so the pill gets
269
+ an outline in a system colour there only and the active
270
+ tab keeps a shape. Decorative with all it holds: the label
271
+ names the tab. -->
272
+ <span
273
+ aria-hidden="true"
274
+ class={cn(
275
+ "flex h-8 w-[min(56px,100%)] shrink-0 items-center justify-center rounded-pill transition-colors duration-(--duration-base) motion-reduce:transition-none",
276
+ isActive &&
277
+ "bg-tabbar-active text-nav-menu-item-active forced-colors:outline-2 forced-colors:-outline-offset-2 forced-colors:outline-[color:Highlight]",
278
+ )}
279
+ >
280
+ <!-- The badge is placed against the icon's own 24px
281
+ box, in px: from its upper end corner outwards. It
282
+ covers that corner by 8px and no more, whatever the
283
+ text size and however narrow the pill is. -->
284
+ <span class="relative flex size-6 shrink-0 items-center justify-center">
285
+ <Icon
286
+ size={24}
287
+ class="shrink-0"
288
+ strokeWidth={isActive && !item.activeIcon ? 2.5 : undefined}
289
+ />
290
+ {#if count > 0}
291
+ <Badge
292
+ size="sm"
293
+ emphasis="solid"
294
+ variant="error"
295
+ class="tabbar-badge ring-2 ring-(color:--color-bar) absolute start-4 bottom-4 h-[18px] min-h-[18px] min-w-[18px] px-[5px] py-0 text-[11px] leading-[18px] whitespace-nowrap"
296
+ >
297
+ {badgeText(count, badgeMax)}
298
+ </Badge>
299
+ {/if}
300
+ </span>
301
+ </span>
302
+ <span class="tabbar-label">
303
+ {item.label}
304
+ </span>
305
+ </a>
306
+ </li>
307
+ {/each}
308
+ </ul>
309
+ {/snippet}
310
+
311
+ <style>
312
+ /*
313
+ * The bar measures itself: the tabs and the gaps are sized from its own
314
+ * width and the number of tabs, not from the screen, so a bar in a narrow
315
+ * column behaves like one on a narrow phone.
316
+ */
317
+ .tabbar {
318
+ container: tabbar / inline-size;
319
+ }
320
+
321
+ /*
322
+ * The room between the tabs and at the two sides is whatever is left
323
+ * when every tab is 52px wide, shared out, and never more than 8px. So
324
+ * the bar gives up its gaps before it gives up label room: five tabs are
325
+ * 8px apart from 308px up (a 320px phone included), closer below that,
326
+ * and touch at 260px.
327
+ */
328
+ .tabbar-list,
329
+ .tabbar-capsule {
330
+ --tabbar-gap: clamp(
331
+ 0px,
332
+ calc((100cqi - var(--tabbar-count) * 52px) / (var(--tabbar-count) + 1)),
333
+ 8px
334
+ );
335
+ }
336
+
337
+ .tabbar-list {
338
+ gap: var(--tabbar-gap);
339
+ padding-inline: var(--tabbar-gap);
340
+ }
341
+
342
+ /*
343
+ * The floating capsule and the tabs inside it are nested rounded shapes,
344
+ * so they are concentric: the tabs sit the same distance inside the
345
+ * capsule above and below as at its two ends (the gap, in place of the
346
+ * edge-to-edge bar's 4px), and the capsule's radius is the tab's 20px
347
+ * plus that distance. At 8px the capsule is 72px tall with a 28px corner.
348
+ */
349
+ .tabbar-capsule {
350
+ border-radius: calc(20px + var(--tabbar-gap));
351
+ }
352
+
353
+ .tabbar-capsule .tabbar-list {
354
+ padding-block: var(--tabbar-gap);
355
+ }
356
+
357
+ /*
358
+ * A tab is at least 44px wide for as long as that many fit the bar. When
359
+ * they do not (five tabs below 220px), they share the bar edge to edge:
360
+ * narrower than a full target, but never on top of each other, and the
361
+ * bar never scrolls sideways. Each tab is a container too, so its label
362
+ * can follow its width.
363
+ */
364
+ .tabbar-tab {
365
+ flex: 1 1 0;
366
+ min-width: min(44px, calc(100cqi / var(--tabbar-count)));
367
+ container: tabbar-tab / inline-size;
368
+ }
369
+
370
+ /*
371
+ * One line. 0.75rem, so it follows the reader's text size, but never
372
+ * above 1.3 times its 12px, nor above what a nine-letter word needs to
373
+ * fit the tab (18.5% of its width), and never below 11px. A label longer
374
+ * than that is cut with an ellipsis, between letters but with the mark
375
+ * that says so; it is not broken over lines.
376
+ */
377
+ .tabbar-label {
378
+ display: block;
379
+ width: 100%;
380
+ min-width: 0;
381
+ overflow: hidden;
382
+ text-overflow: ellipsis;
383
+ white-space: nowrap;
384
+ font-size: clamp(11px, 0.75rem, min(15.6px, 18.5cqi));
385
+ line-height: 16px;
386
+ }
387
+
388
+ /*
389
+ * Narrower than 52px, a tab has no room for a word: icons only, for every
390
+ * tab, the active one too, so the row reads as one thing. The label stays
391
+ * in the page for the link's name; it is only not drawn.
392
+ */
393
+ @container tabbar-tab (max-width: 51.9px) {
394
+ .tabbar-label {
395
+ position: absolute;
396
+ width: 1px;
397
+ height: 1px;
398
+ overflow: hidden;
399
+ clip-path: inset(50%);
400
+ white-space: nowrap;
401
+ }
402
+ }
403
+ </style>
@@ -0,0 +1,98 @@
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ import { type BottomTabBarItem } from "../util/bottom-tab-bar.js";
3
+ import { type ScrollEdgeMode } from "../util/scroll-edge.js";
4
+ /**
5
+ * The navigation bar at the bottom of a phone screen: three to five
6
+ * destinations, each an icon over a short label, within reach of a thumb.
7
+ *
8
+ * The tabs are links. Pressing the active one is an ordinary navigation to
9
+ * the page you are on; nothing is ever deselected.
10
+ *
11
+ * On its own the bar is fixed over the page and reserves no room. Give the
12
+ * page `padding-bottom` and `scroll-padding-bottom` of the bar's height
13
+ * (`calc(64px + env(safe-area-inset-bottom))`), or the end of the
14
+ * content, and a focused control there, sits under it. `AppShell` does
15
+ * this for you.
16
+ *
17
+ * The bar is one row, 64px tall, at every width and text size. It is
18
+ * chrome: when the reader enlarges text, the room belongs to the page.
19
+ * So its sizes are in px; a label is one line, grows with the text only
20
+ * up to 1.3 times its size and no further than its tab has room for, and
21
+ * is cut with an ellipsis, never inside a word, when it still does not
22
+ * fit. Where a tab is narrower than 52px (five tabs below 260px) the bar
23
+ * shows icons only. The link is always named by its full label.
24
+ *
25
+ * The bar is flush with the page and page-coloured at rest; once content
26
+ * is under it (not at the end of what scrolls) it turns to frosted glass
27
+ * with a hairline above it. Inside an `AppShell` the shell says when; on
28
+ * its own the bar watches the window.
29
+ *
30
+ * With `floating` the bar is a glass capsule inset from the screen edges
31
+ * instead: always glass, since content is always under it.
32
+ *
33
+ * ```svelte
34
+ * <BottomTabBar
35
+ * items={[
36
+ * { href: "/", label: "Home", icon: House },
37
+ * { href: "/quiz", label: "Quiz", icon: Trophy },
38
+ * { href: "/inbox", label: "Inbox", icon: Bell, badge: 3 },
39
+ * ]}
40
+ * active={page.url.pathname}
41
+ * />
42
+ * ```
43
+ */
44
+ type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
45
+ /** Three to five destinations. */
46
+ items: BottomTabBarItem[];
47
+ /**
48
+ * The href of the active tab, or the path of the current page: the tab
49
+ * for that page or the closest one above it is marked. Left out, the
50
+ * bar reads the address of the page once it runs in the browser. In
51
+ * SvelteKit pass `page.url.pathname`, so the server marks it too.
52
+ */
53
+ active?: string;
54
+ /**
55
+ * The href or path of the tab a person has just pressed, while its
56
+ * page is still on its way. It takes the mark (the pill, the heavier
57
+ * icon, the semibold label) at once, but `aria-current="page"` stays
58
+ * on the tab for `active` until `active` changes, and the pending link
59
+ * has `aria-busy`. Left out, or when it matches no tab, the mark and
60
+ * `aria-current` are on the same tab, as before.
61
+ */
62
+ pending?: string;
63
+ /** Accessible name of the navigation landmark. */
64
+ label?: string;
65
+ /**
66
+ * What a badge adds to the tab's accessible name, after the label and
67
+ * a comma. Replace it to translate or to say what is being counted.
68
+ */
69
+ badgeLabel?: (count: number, item: BottomTabBarItem) => string;
70
+ /** Counts above this show as `99+`. The accessible name keeps the real count. */
71
+ badgeMax?: number;
72
+ /**
73
+ * `fixed` pins the bar to the bottom of the screen; `static` leaves it
74
+ * where it is in the page. Defaults to `fixed`, or to `static` inside
75
+ * an `AppShell`, which places the bar itself.
76
+ */
77
+ position?: "fixed" | "static";
78
+ /**
79
+ * When the bar shows the glass and the hairline. `auto` follows the
80
+ * scroll position: flush at the end of the content, glass while
81
+ * content is under it. `always` is glass all the time, `never` flush
82
+ * all the time.
83
+ */
84
+ scrollEdge?: ScrollEdgeMode;
85
+ /**
86
+ * Draws the bar as a rounded capsule of glass, inset 12px from the
87
+ * screen edges (and from the home indicator), instead of edge to edge.
88
+ * Always the regular material, because content is always under it, so
89
+ * `scrollEdge` has no effect. The height a shell measures includes
90
+ * the 12px. The landmark, the safe-area insets and the position stay
91
+ * on the `<nav>`.
92
+ */
93
+ floating?: boolean;
94
+ class?: string;
95
+ };
96
+ declare const BottomTabBar: import("svelte").Component<Props, {}, "">;
97
+ type BottomTabBar = ReturnType<typeof BottomTabBar>;
98
+ export default BottomTabBar;