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.
- package/CHANGELOG.md +2935 -0
- package/README.md +243 -10
- package/THEME.md +162 -332
- package/THEMING.md +1419 -259
- package/dist/atoms/ActionPanel.svelte +15 -5
- package/dist/atoms/Avatar.svelte +120 -0
- package/dist/atoms/Avatar.svelte.d.ts +46 -0
- package/dist/atoms/Badge.svelte +71 -13
- package/dist/atoms/Badge.svelte.d.ts +8 -2
- package/dist/atoms/Block.svelte +74 -0
- package/dist/atoms/Block.svelte.d.ts +29 -0
- package/dist/atoms/Button.svelte +217 -30
- package/dist/atoms/Button.svelte.d.ts +33 -4
- package/dist/atoms/Card.svelte +182 -26
- package/dist/atoms/Card.svelte.d.ts +55 -3
- package/dist/atoms/CardHeader.svelte +16 -4
- package/dist/atoms/Checkbox.svelte +81 -21
- package/dist/atoms/Checkbox.svelte.d.ts +31 -2
- package/dist/atoms/Chip.svelte +351 -0
- package/dist/atoms/Chip.svelte.d.ts +57 -0
- package/dist/atoms/CodeBlock.svelte +20 -2
- package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
- package/dist/atoms/CodeField.svelte +385 -0
- package/dist/atoms/CodeField.svelte.d.ts +61 -0
- package/dist/atoms/ColorPicker.svelte +260 -49
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
- package/dist/atoms/Container.svelte +16 -8
- package/dist/atoms/DateField.svelte +145 -0
- package/dist/atoms/DateField.svelte.d.ts +81 -0
- package/dist/atoms/FieldMessages.svelte +69 -0
- package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
- package/dist/atoms/FloatingActionButton.svelte +176 -0
- package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
- package/dist/atoms/Heading.svelte +63 -5
- package/dist/atoms/Heading.svelte.d.ts +14 -0
- package/dist/atoms/IconButton.svelte +294 -26
- package/dist/atoms/IconButton.svelte.d.ts +62 -4
- package/dist/atoms/Input.svelte +259 -53
- package/dist/atoms/Input.svelte.d.ts +33 -2
- package/dist/atoms/List.svelte +5 -1
- package/dist/atoms/ListItem.svelte +70 -13
- package/dist/atoms/ListItem.svelte.d.ts +11 -0
- package/dist/atoms/PickerField.svelte +239 -0
- package/dist/atoms/PickerField.svelte.d.ts +67 -0
- package/dist/atoms/Progress.svelte +99 -19
- package/dist/atoms/Progress.svelte.d.ts +15 -1
- package/dist/atoms/Radio.svelte +51 -3
- package/dist/atoms/Radio.svelte.d.ts +30 -2
- package/dist/atoms/Rating.svelte +620 -0
- package/dist/atoms/Rating.svelte.d.ts +77 -0
- package/dist/atoms/Select.svelte +438 -98
- package/dist/atoms/Select.svelte.d.ts +61 -3
- package/dist/atoms/SelectionControl.svelte +193 -23
- package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
- package/dist/atoms/Skeleton.svelte +114 -18
- package/dist/atoms/Skeleton.svelte.d.ts +30 -3
- package/dist/atoms/SkeletonGroup.svelte +43 -0
- package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
- package/dist/atoms/Slider.svelte +344 -0
- package/dist/atoms/Slider.svelte.d.ts +40 -0
- package/dist/atoms/Sparkline.svelte +109 -0
- package/dist/atoms/Sparkline.svelte.d.ts +27 -0
- package/dist/atoms/SpeechBubble.svelte +187 -0
- package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
- package/dist/atoms/Spinner.svelte +57 -0
- package/dist/atoms/Spinner.svelte.d.ts +19 -0
- package/dist/atoms/Stat.svelte +72 -0
- package/dist/atoms/Stat.svelte.d.ts +19 -0
- package/dist/atoms/Table.svelte +151 -6
- package/dist/atoms/Table.svelte.d.ts +15 -3
- package/dist/atoms/TemporalField.svelte +472 -0
- package/dist/atoms/TemporalField.svelte.d.ts +48 -0
- package/dist/atoms/Text.svelte +23 -11
- package/dist/atoms/Text.svelte.d.ts +6 -3
- package/dist/atoms/Textarea.svelte +150 -44
- package/dist/atoms/Textarea.svelte.d.ts +35 -2
- package/dist/atoms/ThemeToggle.svelte +223 -44
- package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
- package/dist/atoms/TimeColumn.svelte +150 -0
- package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
- package/dist/atoms/TimeField.svelte +151 -0
- package/dist/atoms/TimeField.svelte.d.ts +85 -0
- package/dist/atoms/Toast.svelte +22 -8
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +98 -14
- package/dist/atoms/Toggle.svelte.d.ts +26 -2
- package/dist/atoms/Tooltip.svelte +528 -24
- package/dist/atoms/Tooltip.svelte.d.ts +18 -0
- package/dist/atoms/ZabiStringsProvider.svelte +45 -0
- package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
- package/dist/atoms/index.d.ts +38 -0
- package/dist/atoms/index.js +29 -0
- package/dist/atoms/selection-control.styles.d.ts +47 -5
- package/dist/atoms/selection-control.styles.js +52 -10
- package/dist/components/atoms/index.d.ts +38 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +47 -5
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +104 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/map.d.ts +11 -0
- package/dist/components/map.d.ts.map +1 -0
- package/dist/components/molecules/index.d.ts +51 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +83 -7
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +10 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +24 -1
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +79 -0
- package/dist/components/util/app-shell.d.ts.map +1 -0
- package/dist/components/util/avatar.d.ts +39 -0
- package/dist/components/util/avatar.d.ts.map +1 -0
- package/dist/components/util/bottom-sheet.d.ts +30 -0
- package/dist/components/util/bottom-sheet.d.ts.map +1 -0
- package/dist/components/util/bottom-tab-bar.d.ts +41 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
- package/dist/components/util/calendar.d.ts +56 -0
- package/dist/components/util/calendar.d.ts.map +1 -0
- package/dist/components/util/chart.d.ts +140 -0
- package/dist/components/util/chart.d.ts.map +1 -0
- package/dist/components/util/chip.d.ts +66 -0
- package/dist/components/util/chip.d.ts.map +1 -0
- package/dist/components/util/cn.d.ts.map +1 -1
- package/dist/components/util/code-field.d.ts +63 -0
- package/dist/components/util/code-field.d.ts.map +1 -0
- package/dist/components/util/collapsible.d.ts +64 -0
- package/dist/components/util/collapsible.d.ts.map +1 -0
- package/dist/components/util/confirm-dialog.d.ts +12 -0
- package/dist/components/util/confirm-dialog.d.ts.map +1 -0
- package/dist/components/util/date.d.ts +96 -0
- package/dist/components/util/date.d.ts.map +1 -0
- package/dist/components/util/display-voice.d.ts +10 -0
- package/dist/components/util/display-voice.d.ts.map +1 -0
- package/dist/components/util/drawer.d.ts +13 -0
- package/dist/components/util/drawer.d.ts.map +1 -0
- package/dist/components/util/dropdown.d.ts +47 -0
- package/dist/components/util/dropdown.d.ts.map +1 -0
- package/dist/components/util/field-control.d.ts +30 -0
- package/dist/components/util/field-control.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +45 -0
- package/dist/components/util/field.d.ts.map +1 -0
- package/dist/components/util/fit-in-viewport.d.ts +201 -0
- package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
- package/dist/components/util/focus-utils.d.ts +77 -2
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- package/dist/components/util/hydration.d.ts +45 -0
- package/dist/components/util/hydration.d.ts.map +1 -0
- package/dist/components/util/keyboard-inset.d.ts +18 -0
- package/dist/components/util/keyboard-inset.d.ts.map +1 -0
- package/dist/components/util/map-view.d.ts +144 -0
- package/dist/components/util/map-view.d.ts.map +1 -0
- package/dist/components/util/media-grid.d.ts +51 -0
- package/dist/components/util/media-grid.d.ts.map +1 -0
- package/dist/components/util/media.d.ts +6 -0
- package/dist/components/util/media.d.ts.map +1 -0
- package/dist/components/util/overlay-chrome.d.ts +53 -0
- package/dist/components/util/overlay-chrome.d.ts.map +1 -0
- package/dist/components/util/overlay.d.ts +72 -0
- package/dist/components/util/overlay.d.ts.map +1 -0
- package/dist/components/util/photo.d.ts +234 -0
- package/dist/components/util/photo.d.ts.map +1 -0
- package/dist/components/util/portal.d.ts +23 -0
- package/dist/components/util/portal.d.ts.map +1 -0
- package/dist/components/util/progress.d.ts +19 -0
- package/dist/components/util/progress.d.ts.map +1 -0
- package/dist/components/util/pull-to-refresh.d.ts +27 -0
- package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
- package/dist/components/util/radio-keys.d.ts +25 -0
- package/dist/components/util/radio-keys.d.ts.map +1 -0
- package/dist/components/util/rating.d.ts +23 -0
- package/dist/components/util/rating.d.ts.map +1 -0
- package/dist/components/util/ready-made-strings.d.ts +77 -0
- package/dist/components/util/ready-made-strings.d.ts.map +1 -0
- package/dist/components/util/scroll-edge.d.ts +33 -0
- package/dist/components/util/scroll-edge.d.ts.map +1 -0
- package/dist/components/util/scroll-padding.d.ts +20 -0
- package/dist/components/util/scroll-padding.d.ts.map +1 -0
- package/dist/components/util/segmented-control.d.ts +16 -0
- package/dist/components/util/segmented-control.d.ts.map +1 -0
- package/dist/components/util/select.d.ts +41 -0
- package/dist/components/util/select.d.ts.map +1 -0
- package/dist/components/util/sheet-drag.d.ts +84 -0
- package/dist/components/util/sheet-drag.d.ts.map +1 -0
- package/dist/components/util/sidebar.d.ts +63 -0
- package/dist/components/util/sidebar.d.ts.map +1 -0
- package/dist/components/util/skeleton.d.ts +15 -0
- package/dist/components/util/skeleton.d.ts.map +1 -0
- package/dist/components/util/sliding-indicator.d.ts +25 -0
- package/dist/components/util/sliding-indicator.d.ts.map +1 -0
- package/dist/components/util/sortable-list.d.ts +65 -0
- package/dist/components/util/sortable-list.d.ts.map +1 -0
- package/dist/components/util/stepper.d.ts +40 -0
- package/dist/components/util/stepper.d.ts.map +1 -0
- package/dist/components/util/swipeable-list-item.d.ts +36 -0
- package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
- package/dist/components/util/temporal-field.d.ts +89 -0
- package/dist/components/util/temporal-field.d.ts.map +1 -0
- package/dist/components/util/theme-mode.d.ts +80 -0
- package/dist/components/util/theme-mode.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +133 -0
- package/dist/components/util/toaster.d.ts.map +1 -0
- package/dist/components/util/tone-surface.d.ts +34 -0
- package/dist/components/util/tone-surface.d.ts.map +1 -0
- package/dist/components/util/tooltip.d.ts +10 -0
- package/dist/components/util/tooltip.d.ts.map +1 -0
- package/dist/components/util/top-navbar.d.ts +35 -0
- package/dist/components/util/top-navbar.d.ts.map +1 -0
- package/dist/components/util/touch-target.d.ts +31 -0
- package/dist/components/util/touch-target.d.ts.map +1 -0
- package/dist/components/util/zabi-strings.d.ts +142 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -0
- package/dist/create-theme/cli.js +173 -0
- package/dist/create-theme/index.d.ts +190 -0
- package/dist/create-theme/index.js +1034 -0
- package/dist/create-theme/lib/ramp-math.js +128 -0
- package/dist/create-theme/lib/resolve.js +238 -0
- package/dist/create-theme/theme-data.js +2644 -0
- package/dist/index.d.ts +104 -2
- package/dist/index.js +75 -2
- package/dist/map.d.ts +10 -0
- package/dist/map.js +9 -0
- package/dist/molecules/Alert.svelte +63 -16
- package/dist/molecules/Alert.svelte.d.ts +14 -1
- package/dist/molecules/AppBar.svelte +674 -0
- package/dist/molecules/AppBar.svelte.d.ts +131 -0
- package/dist/molecules/AvatarGroup.svelte +116 -0
- package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
- package/dist/molecules/BarChart.svelte +381 -0
- package/dist/molecules/BarChart.svelte.d.ts +45 -0
- package/dist/molecules/BottomSheet.svelte +608 -0
- package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
- package/dist/molecules/BottomTabBar.svelte +403 -0
- package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
- package/dist/molecules/Calendar.svelte +597 -0
- package/dist/molecules/Calendar.svelte.d.ts +72 -0
- package/dist/molecules/ChipGroup.svelte +325 -0
- package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
- package/dist/molecules/Collapsible.svelte +347 -0
- package/dist/molecules/Collapsible.svelte.d.ts +61 -0
- package/dist/molecules/CollapsibleGroup.svelte +136 -0
- package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
- package/dist/molecules/ComponentDemo.svelte +16 -4
- package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
- package/dist/molecules/ConfirmDialog.svelte +257 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
- package/dist/molecules/ContactForm.svelte +34 -17
- package/dist/molecules/ContactForm.svelte.d.ts +8 -0
- package/dist/molecules/Drawer.svelte +415 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +517 -48
- package/dist/molecules/Dropdown.svelte.d.ts +47 -6
- package/dist/molecules/DropdownItem.svelte +122 -0
- package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
- package/dist/molecules/EmptyState.svelte +55 -10
- package/dist/molecules/EmptyState.svelte.d.ts +8 -0
- package/dist/molecules/FormField.svelte +9 -1
- package/dist/molecules/FormField.svelte.d.ts +2 -0
- package/dist/molecules/Header.svelte +1 -1
- package/dist/molecules/ImageUpload.svelte +492 -69
- package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
- package/dist/molecules/LineChart.svelte +459 -0
- package/dist/molecules/LineChart.svelte.d.ts +53 -0
- package/dist/molecules/MapView.svelte +565 -0
- package/dist/molecules/MapView.svelte.d.ts +88 -0
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +293 -72
- package/dist/molecules/Modal.svelte.d.ts +51 -2
- package/dist/molecules/NavigationMenuContent.svelte +58 -1
- package/dist/molecules/NavigationMenuItem.svelte +0 -1
- package/dist/molecules/NavigationMenuLink.svelte +1 -1
- package/dist/molecules/NavigationMenuList.svelte +10 -3
- package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
- package/dist/molecules/Page.svelte +32 -2
- package/dist/molecules/Page.svelte.d.ts +11 -0
- package/dist/molecules/PhotoGrid.svelte +523 -0
- package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
- package/dist/molecules/PhotoViewer.svelte +1213 -0
- package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
- package/dist/molecules/PieChart.svelte +421 -0
- package/dist/molecules/PieChart.svelte.d.ts +51 -0
- package/dist/molecules/PropsTable.svelte +22 -10
- package/dist/molecules/PropsTable.svelte.d.ts +3 -0
- package/dist/molecules/PullToRefresh.svelte +263 -0
- package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
- package/dist/molecules/RadioGroup.svelte +72 -18
- package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
- package/dist/molecules/Section.svelte +1 -1
- package/dist/molecules/SegmentedControl.svelte +456 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
- package/dist/molecules/SidebarBrandHeader.svelte +11 -1
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
- package/dist/molecules/SidebarFooter.svelte +30 -7
- package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
- package/dist/molecules/SidebarNavSection.svelte +1 -1
- package/dist/molecules/SlideUp.svelte +220 -61
- package/dist/molecules/SlideUp.svelte.d.ts +26 -0
- package/dist/molecules/SortableList.svelte +633 -0
- package/dist/molecules/SortableList.svelte.d.ts +69 -0
- package/dist/molecules/Stepper.svelte +562 -0
- package/dist/molecules/Stepper.svelte.d.ts +39 -0
- package/dist/molecules/StickyActionBar.svelte +250 -0
- package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
- package/dist/molecules/SwipeableListItem.svelte +334 -0
- package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
- package/dist/molecules/Tabs.svelte +310 -9
- package/dist/molecules/Tabs.svelte.d.ts +7 -0
- package/dist/molecules/Toaster.svelte +514 -5
- package/dist/molecules/Toaster.svelte.d.ts +153 -2
- package/dist/molecules/ToasterToast.svelte +495 -77
- package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
- package/dist/molecules/UnsavedChangesBar.svelte +223 -0
- package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
- package/dist/molecules/index.d.ts +51 -0
- package/dist/molecules/index.js +31 -0
- package/dist/molecules/toast-store.d.ts +83 -7
- package/dist/molecules/toast-store.js +25 -1
- package/dist/organisms/AppNavigation.svelte +440 -0
- package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
- package/dist/organisms/AppShell.svelte +696 -0
- package/dist/organisms/AppShell.svelte.d.ts +157 -0
- package/dist/organisms/AppShellHeaderPart.svelte +163 -0
- package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
- package/dist/organisms/SidebarAccountPanel.svelte +107 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
- package/dist/organisms/SidebarNavigation.svelte +111 -43
- package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
- package/dist/organisms/SidebarPanel.svelte +34 -17
- package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
- package/dist/organisms/SidebarShell.svelte +164 -14
- package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
- package/dist/organisms/TopNavbar.svelte +353 -39
- package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
- package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
- package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
- package/dist/organisms/index.d.ts +10 -0
- package/dist/organisms/index.js +7 -0
- package/dist/types/components.d.ts +21 -1
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +1093 -26
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +28 -1
- package/dist/util/app-shell.d.ts +78 -0
- package/dist/util/app-shell.js +97 -0
- package/dist/util/avatar.d.ts +38 -0
- package/dist/util/avatar.js +52 -0
- package/dist/util/bottom-sheet.d.ts +29 -0
- package/dist/util/bottom-sheet.js +43 -0
- package/dist/util/bottom-tab-bar.d.ts +40 -0
- package/dist/util/bottom-tab-bar.js +54 -0
- package/dist/util/calendar.d.ts +55 -0
- package/dist/util/calendar.js +74 -0
- package/dist/util/chart.d.ts +139 -0
- package/dist/util/chart.js +305 -0
- package/dist/util/chip.d.ts +65 -0
- package/dist/util/chip.js +58 -0
- package/dist/util/cn.js +28 -2
- package/dist/util/code-field.d.ts +62 -0
- package/dist/util/code-field.js +112 -0
- package/dist/util/collapsible.d.ts +63 -0
- package/dist/util/collapsible.js +23 -0
- package/dist/util/confirm-dialog.d.ts +11 -0
- package/dist/util/confirm-dialog.js +2 -0
- package/dist/util/date.d.ts +95 -0
- package/dist/util/date.js +237 -0
- package/dist/util/display-voice.d.ts +9 -0
- package/dist/util/display-voice.js +9 -0
- package/dist/util/drawer.d.ts +12 -0
- package/dist/util/drawer.js +7 -0
- package/dist/util/dropdown.d.ts +46 -0
- package/dist/util/dropdown.js +21 -0
- package/dist/util/field-control.d.ts +29 -0
- package/dist/util/field-control.js +32 -0
- package/dist/util/field.d.ts +44 -0
- package/dist/util/field.js +38 -0
- package/dist/util/fit-in-viewport.d.ts +200 -0
- package/dist/util/fit-in-viewport.js +399 -0
- package/dist/util/focus-utils.d.ts +77 -2
- package/dist/util/focus-utils.js +186 -9
- package/dist/util/hydration.d.ts +44 -0
- package/dist/util/hydration.js +58 -0
- package/dist/util/keyboard-inset.d.ts +17 -0
- package/dist/util/keyboard-inset.js +53 -0
- package/dist/util/map-view.d.ts +143 -0
- package/dist/util/map-view.js +121 -0
- package/dist/util/media-grid.d.ts +50 -0
- package/dist/util/media-grid.js +81 -0
- package/dist/util/media.d.ts +5 -0
- package/dist/util/media.js +10 -0
- package/dist/util/overlay-chrome.d.ts +52 -0
- package/dist/util/overlay-chrome.js +56 -0
- package/dist/util/overlay.d.ts +71 -0
- package/dist/util/overlay.js +240 -0
- package/dist/util/photo.d.ts +233 -0
- package/dist/util/photo.js +209 -0
- package/dist/util/portal.d.ts +22 -0
- package/dist/util/portal.js +50 -0
- package/dist/util/progress.d.ts +18 -0
- package/dist/util/progress.js +17 -0
- package/dist/util/pull-to-refresh.d.ts +26 -0
- package/dist/util/pull-to-refresh.js +30 -0
- package/dist/util/radio-keys.d.ts +24 -0
- package/dist/util/radio-keys.js +49 -0
- package/dist/util/rating.d.ts +22 -0
- package/dist/util/rating.js +26 -0
- package/dist/util/ready-made-strings.d.ts +76 -0
- package/dist/util/ready-made-strings.js +67 -0
- package/dist/util/scroll-edge.d.ts +32 -0
- package/dist/util/scroll-edge.js +87 -0
- package/dist/util/scroll-padding.d.ts +19 -0
- package/dist/util/scroll-padding.js +44 -0
- package/dist/util/segmented-control.d.ts +15 -0
- package/dist/util/segmented-control.js +1 -0
- package/dist/util/select.d.ts +40 -0
- package/dist/util/select.js +63 -0
- package/dist/util/sheet-drag.d.ts +83 -0
- package/dist/util/sheet-drag.js +311 -0
- package/dist/util/sidebar.d.ts +62 -0
- package/dist/util/sidebar.js +38 -0
- package/dist/util/skeleton.d.ts +14 -0
- package/dist/util/skeleton.js +22 -0
- package/dist/util/sliding-indicator.d.ts +24 -0
- package/dist/util/sliding-indicator.js +44 -0
- package/dist/util/sortable-list.d.ts +64 -0
- package/dist/util/sortable-list.js +53 -0
- package/dist/util/stepper.d.ts +39 -0
- package/dist/util/stepper.js +38 -0
- package/dist/util/swipeable-list-item.d.ts +35 -0
- package/dist/util/swipeable-list-item.js +32 -0
- package/dist/util/temporal-field.d.ts +88 -0
- package/dist/util/temporal-field.js +136 -0
- package/dist/util/theme-mode.d.ts +79 -0
- package/dist/util/theme-mode.js +121 -0
- package/dist/util/toaster.d.ts +132 -0
- package/dist/util/toaster.js +130 -0
- package/dist/util/tone-surface.d.ts +33 -0
- package/dist/util/tone-surface.js +35 -0
- package/dist/util/tooltip.d.ts +9 -0
- package/dist/util/tooltip.js +18 -0
- package/dist/util/top-navbar.d.ts +34 -0
- package/dist/util/top-navbar.js +28 -0
- package/dist/util/touch-target.d.ts +30 -0
- package/dist/util/touch-target.js +30 -0
- package/dist/util/zabi-strings.d.ts +141 -0
- package/dist/util/zabi-strings.js +105 -0
- package/dist/zabi-components-colors.css +11 -2
- package/dist/zabi-components-theme-dark-only.css +870 -258
- package/dist/zabi-components-theme-dark.css +870 -258
- package/dist/zabi-components-theme-only.css +2419 -368
- package/dist/zabi-components-theme.css +2419 -368
- package/dist/zabi-components.css +6032 -711
- package/docs/lucide-icons.md +40 -17
- package/docs/theme-imports.md +81 -11
- 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;
|