zabi-components 7.0.2 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1924 -0
- package/README.md +287 -11
- package/THEME.md +149 -332
- package/THEMING.md +1329 -163
- package/dist/atoms/ActionPanel.svelte +24 -9
- package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
- package/dist/atoms/Avatar.svelte +104 -0
- package/dist/atoms/Avatar.svelte.d.ts +34 -0
- package/dist/atoms/Badge.svelte +124 -58
- package/dist/atoms/Badge.svelte.d.ts +17 -5
- package/dist/atoms/Button.svelte +212 -62
- package/dist/atoms/Button.svelte.d.ts +33 -2
- package/dist/atoms/Card.svelte +31 -28
- package/dist/atoms/Card.svelte.d.ts +5 -2
- package/dist/atoms/CardContent.svelte +10 -2
- package/dist/atoms/CardContent.svelte.d.ts +2 -0
- package/dist/atoms/CardFooter.svelte +10 -2
- package/dist/atoms/CardFooter.svelte.d.ts +2 -0
- package/dist/atoms/CardHeader.svelte +26 -6
- package/dist/atoms/CardHeader.svelte.d.ts +2 -0
- package/dist/atoms/Checkbox.svelte +52 -21
- package/dist/atoms/Checkbox.svelte.d.ts +6 -2
- package/dist/atoms/CodeBlock.svelte +35 -9
- package/dist/atoms/CodeBlock.svelte.d.ts +13 -3
- package/dist/atoms/ColorPicker.svelte +272 -52
- package/dist/atoms/ColorPicker.svelte.d.ts +6 -1
- package/dist/atoms/Container.svelte +17 -10
- package/dist/atoms/DateField.svelte +107 -0
- package/dist/atoms/DateField.svelte.d.ts +53 -0
- package/dist/atoms/Divider.svelte +4 -4
- package/dist/atoms/FeatureCard.svelte +4 -3
- package/dist/atoms/FieldMessages.svelte +69 -0
- package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
- package/dist/atoms/FloatingActionButton.svelte +174 -0
- package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
- package/dist/atoms/Heading.svelte +93 -32
- package/dist/atoms/Heading.svelte.d.ts +24 -4
- package/dist/atoms/IconButton.svelte +242 -53
- package/dist/atoms/IconButton.svelte.d.ts +49 -4
- package/dist/atoms/Input.svelte +267 -74
- package/dist/atoms/Input.svelte.d.ts +32 -2
- package/dist/atoms/List.svelte +13 -2
- package/dist/atoms/List.svelte.d.ts +2 -0
- package/dist/atoms/ListItem.svelte +44 -11
- package/dist/atoms/ListItem.svelte.d.ts +5 -0
- package/dist/atoms/ListItemLeading.svelte +4 -2
- package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
- package/dist/atoms/OptimizedImage.svelte +11 -2
- package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
- package/dist/atoms/Progress.svelte +31 -9
- package/dist/atoms/Progress.svelte.d.ts +6 -0
- package/dist/atoms/Radio.svelte +23 -3
- package/dist/atoms/Radio.svelte.d.ts +6 -2
- package/dist/atoms/Rating.svelte +494 -0
- package/dist/atoms/Rating.svelte.d.ts +56 -0
- package/dist/atoms/Select.svelte +475 -135
- package/dist/atoms/Select.svelte.d.ts +52 -2
- package/dist/atoms/SelectionControl.svelte +99 -18
- package/dist/atoms/SelectionControl.svelte.d.ts +6 -3
- package/dist/atoms/Skeleton.svelte +5 -3
- package/dist/atoms/Slider.svelte +309 -0
- package/dist/atoms/Slider.svelte.d.ts +40 -0
- package/dist/atoms/Spinner.svelte +57 -0
- package/dist/atoms/Spinner.svelte.d.ts +19 -0
- package/dist/atoms/Table.svelte +152 -6
- package/dist/atoms/Table.svelte.d.ts +15 -3
- package/dist/atoms/TemporalField.svelte +140 -0
- package/dist/atoms/TemporalField.svelte.d.ts +32 -0
- package/dist/atoms/Text.svelte +56 -15
- package/dist/atoms/Text.svelte.d.ts +13 -4
- package/dist/atoms/Textarea.svelte +57 -45
- package/dist/atoms/Textarea.svelte.d.ts +14 -2
- package/dist/atoms/ThemeToggle.svelte +238 -56
- package/dist/atoms/ThemeToggle.svelte.d.ts +28 -3
- package/dist/atoms/TimeField.svelte +108 -0
- package/dist/atoms/TimeField.svelte.d.ts +54 -0
- package/dist/atoms/Toast.svelte +25 -10
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +60 -19
- package/dist/atoms/Toggle.svelte.d.ts +18 -2
- package/dist/atoms/Tooltip.svelte +583 -40
- package/dist/atoms/Tooltip.svelte.d.ts +26 -0
- package/dist/atoms/ZabiStringsProvider.svelte +45 -0
- package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
- package/dist/atoms/index.d.ts +17 -0
- package/dist/atoms/index.js +12 -0
- package/dist/atoms/selection-control.styles.d.ts +36 -5
- package/dist/atoms/selection-control.styles.js +40 -9
- package/dist/components/atoms/index.d.ts +17 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +36 -5
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +83 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +46 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +53 -7
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +6 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +15 -1
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +28 -0
- package/dist/components/util/app-shell.d.ts.map +1 -0
- package/dist/components/util/avatar.d.ts +36 -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 +35 -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/cn.d.ts +8 -0
- package/dist/components/util/cn.d.ts.map +1 -0
- package/dist/components/util/collapsible.d.ts +58 -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/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.d.ts +39 -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 +75 -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/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 +215 -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/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-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 +60 -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/sortable-list.d.ts +65 -0
- package/dist/components/util/sortable-list.d.ts.map +1 -0
- package/dist/components/util/ssr-safe.d.ts +4 -4
- package/dist/components/util/ssr-safe.d.ts.map +1 -1
- 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/theme-mode.d.ts +80 -0
- package/dist/components/util/theme-mode.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +93 -0
- package/dist/components/util/toaster.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 +11 -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 +133 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -0
- package/dist/create-theme/cli.js +169 -0
- package/dist/create-theme/index.d.ts +176 -0
- package/dist/create-theme/index.js +817 -0
- package/dist/create-theme/lib/ramp-math.js +128 -0
- package/dist/create-theme/lib/resolve.js +141 -0
- package/dist/create-theme/theme-data.js +1116 -0
- package/dist/index.d.ts +83 -3
- package/dist/index.js +60 -8
- package/dist/molecules/Alert.svelte +73 -66
- package/dist/molecules/Alert.svelte.d.ts +9 -1
- package/dist/molecules/AppBar.svelte +415 -0
- package/dist/molecules/AppBar.svelte.d.ts +69 -0
- package/dist/molecules/AvatarGroup.svelte +116 -0
- package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
- package/dist/molecules/BottomSheet.svelte +540 -0
- package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
- package/dist/molecules/BottomTabBar.svelte +297 -0
- package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
- package/dist/molecules/Calendar.svelte +597 -0
- package/dist/molecules/Calendar.svelte.d.ts +72 -0
- package/dist/molecules/Collapsible.svelte +243 -0
- package/dist/molecules/Collapsible.svelte.d.ts +44 -0
- package/dist/molecules/CollapsibleGroup.svelte +126 -0
- package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
- package/dist/molecules/ComponentDemo.svelte +25 -5
- package/dist/molecules/ComponentDemo.svelte.d.ts +5 -0
- package/dist/molecules/ConfirmDialog.svelte +253 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
- package/dist/molecules/ContactForm.svelte +46 -19
- package/dist/molecules/ContactForm.svelte.d.ts +10 -0
- package/dist/molecules/Drawer.svelte +394 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +540 -50
- package/dist/molecules/Dropdown.svelte.d.ts +49 -6
- package/dist/molecules/DropdownItem.svelte +122 -0
- package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
- package/dist/molecules/EmptyState.svelte +56 -10
- package/dist/molecules/EmptyState.svelte.d.ts +8 -0
- package/dist/molecules/Form.svelte +25 -14
- package/dist/molecules/Form.svelte.d.ts +13 -5
- package/dist/molecules/FormField.svelte +11 -2
- package/dist/molecules/FormField.svelte.d.ts +2 -0
- package/dist/molecules/Header.svelte +13 -3
- package/dist/molecules/Header.svelte.d.ts +2 -0
- package/dist/molecules/ImageUpload.svelte +422 -70
- package/dist/molecules/ImageUpload.svelte.d.ts +61 -5
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +340 -45
- package/dist/molecules/Modal.svelte.d.ts +59 -7
- package/dist/molecules/NavigationMenu.svelte +15 -3
- package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
- package/dist/molecules/NavigationMenuContent.svelte +91 -36
- package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuItem.svelte +10 -3
- package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuLink.svelte +10 -2
- package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuList.svelte +19 -4
- package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
- package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
- package/dist/molecules/Page.svelte +38 -2
- package/dist/molecules/Page.svelte.d.ts +13 -0
- package/dist/molecules/PhotoGrid.svelte +433 -0
- package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
- package/dist/molecules/PhotoViewer.svelte +1116 -0
- package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
- package/dist/molecules/PropsTable.svelte +26 -11
- package/dist/molecules/PropsTable.svelte.d.ts +5 -0
- package/dist/molecules/PullToRefresh.svelte +263 -0
- package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
- package/dist/molecules/RadioGroup.svelte +57 -15
- package/dist/molecules/RadioGroup.svelte.d.ts +16 -3
- package/dist/molecules/Section.svelte +13 -5
- package/dist/molecules/Section.svelte.d.ts +3 -1
- package/dist/molecules/SegmentedControl.svelte +323 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
- package/dist/molecules/SidebarBrandHeader.svelte +24 -5
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +5 -0
- package/dist/molecules/SidebarFooter.svelte +39 -8
- package/dist/molecules/SidebarFooter.svelte.d.ts +5 -0
- package/dist/molecules/SidebarNavSection.svelte +12 -4
- package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
- package/dist/molecules/SlideUp.svelte +253 -30
- package/dist/molecules/SlideUp.svelte.d.ts +31 -4
- package/dist/molecules/SortableList.svelte +632 -0
- package/dist/molecules/SortableList.svelte.d.ts +69 -0
- package/dist/molecules/Stepper.svelte +548 -0
- package/dist/molecules/Stepper.svelte.d.ts +39 -0
- package/dist/molecules/StickyActionBar.svelte +191 -0
- package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
- package/dist/molecules/SwipeableListItem.svelte +334 -0
- package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
- package/dist/molecules/Tabs.svelte +254 -24
- package/dist/molecules/Tabs.svelte.d.ts +15 -4
- package/dist/molecules/Toaster.svelte +352 -6
- package/dist/molecules/Toaster.svelte.d.ts +95 -2
- package/dist/molecules/ToasterToast.svelte +279 -62
- package/dist/molecules/ToasterToast.svelte.d.ts +11 -0
- package/dist/molecules/UnsavedChangesBar.svelte +212 -0
- package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
- package/dist/molecules/index.d.ts +46 -0
- package/dist/molecules/index.js +27 -0
- package/dist/molecules/toast-store.d.ts +53 -7
- package/dist/molecules/toast-store.js +21 -0
- package/dist/organisms/AppShell.svelte +196 -0
- package/dist/organisms/AppShell.svelte.d.ts +61 -0
- package/dist/organisms/SidebarAccountPanel.svelte +111 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +21 -0
- package/dist/organisms/SidebarNavigation.svelte +193 -69
- package/dist/organisms/SidebarNavigation.svelte.d.ts +31 -1
- package/dist/organisms/SidebarPanel.svelte +45 -16
- package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarShell.svelte +263 -0
- package/dist/organisms/SidebarShell.svelte.d.ts +106 -0
- package/dist/organisms/TopNavbar.svelte +329 -33
- package/dist/organisms/TopNavbar.svelte.d.ts +28 -0
- package/dist/organisms/index.d.ts +6 -0
- package/dist/organisms/index.js +4 -0
- package/dist/routes/lib/variant-utils.d.ts +26 -0
- package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
- package/dist/types/components.d.ts +4 -0
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/events.js +1 -0
- package/dist/types/index.d.ts +605 -25
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/variants.ts +17 -1
- package/dist/util/app-shell.d.ts +27 -0
- package/dist/util/app-shell.js +72 -0
- package/dist/util/avatar.d.ts +35 -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 +34 -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/cn.d.ts +7 -0
- package/dist/util/cn.js +48 -0
- package/dist/util/collapsible.d.ts +57 -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/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.d.ts +38 -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 +75 -2
- package/dist/util/focus-utils.js +182 -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/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 +214 -0
- package/dist/util/photo.js +206 -0
- package/dist/util/portal.d.ts +22 -0
- package/dist/util/portal.js +50 -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-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 +59 -0
- package/dist/util/sheet-drag.js +241 -0
- package/dist/util/sidebar.d.ts +62 -0
- package/dist/util/sidebar.js +38 -0
- package/dist/util/sortable-list.d.ts +64 -0
- package/dist/util/sortable-list.js +53 -0
- package/dist/util/ssr-safe.d.ts +4 -4
- 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/theme-mode.d.ts +79 -0
- package/dist/util/theme-mode.js +121 -0
- package/dist/util/toaster.d.ts +92 -0
- package/dist/util/toaster.js +75 -0
- package/dist/util/tooltip.d.ts +9 -0
- package/dist/util/tooltip.js +18 -0
- package/dist/util/top-navbar.d.ts +10 -0
- package/dist/util/top-navbar.js +5 -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 +132 -0
- package/dist/util/zabi-strings.js +105 -0
- package/dist/zabi-components-colors.css +5 -2
- package/dist/zabi-components-theme-dark-only.css +574 -225
- package/dist/zabi-components-theme-dark.css +574 -225
- package/dist/zabi-components-theme-only.css +1680 -317
- package/dist/zabi-components-theme.css +1680 -317
- package/dist/zabi-components.css +4473 -1158
- package/docs/lucide-icons.md +41 -16
- package/docs/theme-imports.md +81 -11
- package/package.json +51 -29
- package/dist/lib/layout-width-tokens.d.ts +0 -17
- package/dist/lib/layout-width-tokens.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/List.d.ts +0 -3
- package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
- package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/index.d.ts +0 -4
- package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
- package/dist/lib/showcase/components-catalog.d.ts +0 -3
- package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
- package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
- package/dist/routes/lib/focus-utils.d.ts +0 -12
- package/dist/routes/lib/focus-utils.ts +0 -110
- package/dist/routes/lib/ssr-safe.ts +0 -58
- package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { zabiCommonStrings } from "../util/zabi-strings.js";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
5
|
+
import X from "@lucide/svelte/icons/x";
|
|
6
|
+
import {
|
|
7
|
+
nextSnap,
|
|
8
|
+
normaliseSnapPoints,
|
|
9
|
+
settleSnap,
|
|
10
|
+
type BottomSheetCloseReason,
|
|
11
|
+
type BottomSheetSnap,
|
|
12
|
+
} from "../util/bottom-sheet.js";
|
|
13
|
+
import { cn } from "../util/cn.js";
|
|
14
|
+
import {
|
|
15
|
+
OVERLAY_CHROME,
|
|
16
|
+
OVERLAY_TITLE_HYPHENS,
|
|
17
|
+
OVERLAY_TITLE_WRAP,
|
|
18
|
+
OVERLAY_TITLE_XL,
|
|
19
|
+
textIsEnlarged,
|
|
20
|
+
} from "../util/overlay-chrome.js";
|
|
21
|
+
import {
|
|
22
|
+
focusFirstElement,
|
|
23
|
+
joinOverlayStack,
|
|
24
|
+
recoverStrayFocus,
|
|
25
|
+
returnFocus,
|
|
26
|
+
saveFocus,
|
|
27
|
+
} from "../util/focus-utils.js";
|
|
28
|
+
import {
|
|
29
|
+
followKeyboard,
|
|
30
|
+
lockBodyScroll,
|
|
31
|
+
registerOverlayFooter,
|
|
32
|
+
registerOverlayHeader,
|
|
33
|
+
trapTabKey,
|
|
34
|
+
watchScrollerNeedsFocus,
|
|
35
|
+
} from "../util/overlay.js";
|
|
36
|
+
import { portal as portalTo } from "../util/portal.js";
|
|
37
|
+
import { attachSheetDrag, FLICK_VELOCITY, prefersReducedMotion } from "../util/sheet-drag.js";
|
|
38
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A panel that slides up from the bottom of the screen for a picker, a
|
|
42
|
+
* set of filters or a short form, and rests at half or full height.
|
|
43
|
+
*
|
|
44
|
+
* It is a modal dialog, on the same footing as Modal, SlideUp and Drawer:
|
|
45
|
+
* focus moves in and stays in, the page behind does not scroll, and
|
|
46
|
+
* Escape, the backdrop, the close button or a swipe down close it and
|
|
47
|
+
* return focus to what opened it.
|
|
48
|
+
*
|
|
49
|
+
* ```svelte
|
|
50
|
+
* <BottomSheet bind:isOpen title="Filters" bind:snap>
|
|
51
|
+
* …
|
|
52
|
+
* {#snippet footer()}
|
|
53
|
+
* <Button onclick={apply}>Show results</Button>
|
|
54
|
+
* {/snippet}
|
|
55
|
+
* </BottomSheet>
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* The grip at the top drags the sheet between its snap points and, past
|
|
59
|
+
* the lowest, closed. It is also a button that moves the sheet a step, so
|
|
60
|
+
* the same is there for a keyboard and a screen reader. A swipe on the
|
|
61
|
+
* content scrolls the content; it moves the sheet only when the content
|
|
62
|
+
* is at its top and the swipe goes down.
|
|
63
|
+
*
|
|
64
|
+
* From the `md` breakpoint up the sheet is still attached to the bottom
|
|
65
|
+
* edge, centred and no wider than 40rem.
|
|
66
|
+
*
|
|
67
|
+
* Other attributes (`id`, `data-*`, ...) land on the `role="dialog"` panel.
|
|
68
|
+
*/
|
|
69
|
+
type Props = Omit<
|
|
70
|
+
HTMLAttributes<HTMLDivElement>,
|
|
71
|
+
// `onclose` is also a DOM event handler; ours carries a reason instead.
|
|
72
|
+
"class" | "title" | "onclose" | "onkeydown"
|
|
73
|
+
> & {
|
|
74
|
+
isOpen?: boolean;
|
|
75
|
+
/** Heading of the sheet, and its accessible name. */
|
|
76
|
+
title: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
/**
|
|
79
|
+
* The heights the sheet can rest at: `half` of the screen, `full`
|
|
80
|
+
* down from the status bar. With one, the grip only drags.
|
|
81
|
+
*/
|
|
82
|
+
snapPoints?: BottomSheetSnap[];
|
|
83
|
+
/**
|
|
84
|
+
* The snap point the sheet is at. Bindable. It opens at the one you
|
|
85
|
+
* pass, or at the lowest of `snapPoints`. It is kept while the sheet
|
|
86
|
+
* is closed: one left at `full` opens at `full` again, bound or not.
|
|
87
|
+
* Set it when you open the sheet to start somewhere else.
|
|
88
|
+
*/
|
|
89
|
+
snap?: BottomSheetSnap;
|
|
90
|
+
/**
|
|
91
|
+
* Render the overlay in `document.body`, so an ancestor with a
|
|
92
|
+
* transform, filter or clipped overflow cannot trap it; pass `false`
|
|
93
|
+
* to render in place. The theme class belongs on `<html>` or `<body>`;
|
|
94
|
+
* set lower, it does not reach a portalled sheet.
|
|
95
|
+
*/
|
|
96
|
+
portal?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* When false, Escape, a backdrop click, the close button and a swipe
|
|
99
|
+
* down do not close the sheet. Focus stays trapped, the grip still
|
|
100
|
+
* moves it between snap points, and setting `isOpen` yourself still
|
|
101
|
+
* closes it.
|
|
102
|
+
*/
|
|
103
|
+
dismissible?: boolean;
|
|
104
|
+
/** Fired when the sheet closes itself, with what the user did. */
|
|
105
|
+
onclose?: (detail: { reason: BottomSheetCloseReason }) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Hears every keydown in the sheet, after the sheet has handled
|
|
108
|
+
* Escape. The Tab cycle is kept either way.
|
|
109
|
+
*/
|
|
110
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
111
|
+
/** Accessible name of the close button. */
|
|
112
|
+
closeLabel?: string;
|
|
113
|
+
/** Accessible name of the grip while its button takes the sheet up a step. */
|
|
114
|
+
expandLabel?: string;
|
|
115
|
+
/** Accessible name of the grip while its button takes the sheet down a step. */
|
|
116
|
+
collapseLabel?: string;
|
|
117
|
+
/**
|
|
118
|
+
* CSS selector, looked up inside the panel, of the control that takes
|
|
119
|
+
* focus when the sheet opens (a search field, say). Without it, or
|
|
120
|
+
* with no match, the first control does: the grip, or the close button.
|
|
121
|
+
*/
|
|
122
|
+
initialFocus?: string;
|
|
123
|
+
/** Extra classes for the dialog panel. */
|
|
124
|
+
class?: string;
|
|
125
|
+
children?: Snippet;
|
|
126
|
+
/** Pinned to the bottom of the panel, below the scrolling content. */
|
|
127
|
+
footer?: Snippet;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
let {
|
|
131
|
+
isOpen = $bindable<Exclude<Props["isOpen"], undefined>>(),
|
|
132
|
+
title,
|
|
133
|
+
description = "",
|
|
134
|
+
snapPoints,
|
|
135
|
+
snap = $bindable(),
|
|
136
|
+
portal = true,
|
|
137
|
+
dismissible = true,
|
|
138
|
+
onclose,
|
|
139
|
+
onkeydown,
|
|
140
|
+
closeLabel: closeLabelGiven,
|
|
141
|
+
expandLabel: expandLabelGiven,
|
|
142
|
+
collapseLabel: collapseLabelGiven,
|
|
143
|
+
initialFocus,
|
|
144
|
+
class: className = "",
|
|
145
|
+
children,
|
|
146
|
+
footer,
|
|
147
|
+
...restProps
|
|
148
|
+
}: Props = $props();
|
|
149
|
+
|
|
150
|
+
/** Words many components share: a `ZabiStringsProvider` above this one may give them; else English. */
|
|
151
|
+
const common = zabiCommonStrings();
|
|
152
|
+
const closeLabel = $derived(closeLabelGiven ?? common().close);
|
|
153
|
+
const expandLabel = $derived(expandLabelGiven ?? common().expand);
|
|
154
|
+
const collapseLabel = $derived(collapseLabelGiven ?? common().collapse);
|
|
155
|
+
|
|
156
|
+
// No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
|
|
157
|
+
// one that has a fallback (`props_invalid_value`), and a page that throws
|
|
158
|
+
// while it hydrates never becomes interactive. The default is applied
|
|
159
|
+
// here instead: at once, for the server and the first render, and again
|
|
160
|
+
// whenever a parent hands back `undefined`.
|
|
161
|
+
const applyDefaults = () => {
|
|
162
|
+
if (isOpen === undefined) isOpen = false;
|
|
163
|
+
};
|
|
164
|
+
applyDefaults();
|
|
165
|
+
$effect.pre(applyDefaults);
|
|
166
|
+
|
|
167
|
+
const SLIDE_MS = 200;
|
|
168
|
+
|
|
169
|
+
const titleId = generateId("bottom-sheet-title");
|
|
170
|
+
const descriptionId = generateId("bottom-sheet-description");
|
|
171
|
+
|
|
172
|
+
let root = $state<HTMLDivElement>();
|
|
173
|
+
let panel = $state<HTMLDivElement>();
|
|
174
|
+
let header = $state<HTMLDivElement>();
|
|
175
|
+
let scroller = $state<HTMLDivElement>();
|
|
176
|
+
let footerElement = $state<HTMLDivElement>();
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Read as the overlay opens: with the text enlarged, a long word in the
|
|
180
|
+
* title is hyphenated before it is cut (util/overlay-chrome.ts).
|
|
181
|
+
*/
|
|
182
|
+
let enlargedText = $state(false);
|
|
183
|
+
$effect(() => {
|
|
184
|
+
if (isOpen) enlargedText = textIsEnlarged();
|
|
185
|
+
});
|
|
186
|
+
let focusActive = false;
|
|
187
|
+
let scrollerNeedsFocus = $state(false);
|
|
188
|
+
/** Position among the open overlays; lifts a sheet opened later above the earlier ones. */
|
|
189
|
+
let depth = $state(0);
|
|
190
|
+
|
|
191
|
+
const points = $derived(normaliseSnapPoints(snapPoints));
|
|
192
|
+
/** The snap point in effect: the one asked for if it is in use, else the lowest. */
|
|
193
|
+
const currentSnap = $derived(snap && points.includes(snap) ? snap : points[0]);
|
|
194
|
+
const stepTo = $derived(nextSnap(points, currentSnap));
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* `100%` is the overlay, which is the viewport. Full height stops at the
|
|
198
|
+
* status bar, so the close button stays in reach; half is never so low
|
|
199
|
+
* that a short screen leaves no room for content, and never above full.
|
|
200
|
+
*/
|
|
201
|
+
const FULL = "calc(100% - env(safe-area-inset-top, 0px))";
|
|
202
|
+
const snapHeight = $derived(
|
|
203
|
+
currentSnap === "full" ? FULL : `min(max(50%, 18rem), ${FULL})`,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
/** While a drag is on: the height and offset it has moved the panel to, in px. */
|
|
207
|
+
let dragging = $state(false);
|
|
208
|
+
let dragHeight = $state<number | null>(null);
|
|
209
|
+
let dragOffset = $state(0);
|
|
210
|
+
|
|
211
|
+
function close(reason: BottomSheetCloseReason) {
|
|
212
|
+
if (!dismissible) return;
|
|
213
|
+
isOpen = false;
|
|
214
|
+
if (focusActive) {
|
|
215
|
+
focusActive = false;
|
|
216
|
+
returnFocus();
|
|
217
|
+
}
|
|
218
|
+
onclose?.({ reason });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Slides the panel up from the bottom edge. Skipped where motion is unwanted or unsupported. */
|
|
222
|
+
function slideIn(container: HTMLElement) {
|
|
223
|
+
if (typeof container.animate !== "function" || prefersReducedMotion()) return;
|
|
224
|
+
container.animate(
|
|
225
|
+
[{ transform: "translateY(100%)" }, { transform: "translateY(0)" }],
|
|
226
|
+
{ duration: SLIDE_MS, easing: "ease-out" },
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** The snap points in px, lowest first, for the screen as it is now. */
|
|
231
|
+
function measureSnapHeights(): number[] {
|
|
232
|
+
if (!root) return [];
|
|
233
|
+
const available = root.clientHeight - parseFloat(getComputedStyle(root).paddingTop || "0");
|
|
234
|
+
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
235
|
+
const half = Math.min(Math.max(root.clientHeight * 0.5, 18 * rem), available);
|
|
236
|
+
return points.map((point) => (point === "full" ? available : half));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function stepSnap() {
|
|
240
|
+
if (stepTo) snap = stepTo;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
$effect(() => {
|
|
244
|
+
const box = scroller;
|
|
245
|
+
if (!isOpen || !box) {
|
|
246
|
+
scrollerNeedsFocus = false;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
return watchScrollerNeedsFocus(box, (needsFocus) => (scrollerNeedsFocus = needsFocus));
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Where the on-screen keyboard covers the page, the overlay keeps to what
|
|
253
|
+
// is left above it. The snap heights are shares of the overlay, so half
|
|
254
|
+
// is half of what can be seen, and the footer stays above the keyboard.
|
|
255
|
+
$effect(() => {
|
|
256
|
+
const overlay = root;
|
|
257
|
+
if (!isOpen || !overlay) return;
|
|
258
|
+
return followKeyboard(overlay);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// For the toast stack, which stays off a pinned footer.
|
|
262
|
+
$effect(() => {
|
|
263
|
+
const pinned = footerElement;
|
|
264
|
+
if (!isOpen || !pinned) return;
|
|
265
|
+
return registerOverlayFooter(pinned);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// And the header, with the close button, which the stack starts below.
|
|
269
|
+
$effect(() => {
|
|
270
|
+
const top = header;
|
|
271
|
+
if (!isOpen || !top) return;
|
|
272
|
+
return registerOverlayHeader(top);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
$effect(() => {
|
|
276
|
+
const container = panel;
|
|
277
|
+
if (isOpen && container) {
|
|
278
|
+
saveFocus();
|
|
279
|
+
focusActive = true;
|
|
280
|
+
const unlockScroll = lockBodyScroll();
|
|
281
|
+
const overlay = joinOverlayStack(container);
|
|
282
|
+
depth = overlay.depth;
|
|
283
|
+
slideIn(container);
|
|
284
|
+
const t = setTimeout(() => {
|
|
285
|
+
// Something inside has taken focus already (a field that
|
|
286
|
+
// focuses itself as it mounts): leave it there. `initialFocus`
|
|
287
|
+
// is the consumer saying where focus goes, so it still decides.
|
|
288
|
+
const active = document.activeElement;
|
|
289
|
+
const inside = !!active && active !== container && container.contains(active);
|
|
290
|
+
if (inside && !(initialFocus && container.querySelector(initialFocus))) return;
|
|
291
|
+
focusFirstElement(container, initialFocus);
|
|
292
|
+
}, 0);
|
|
293
|
+
// If the focused control is disabled or removed, focus lands on
|
|
294
|
+
// `<body>`; take Tab and Escape back from there.
|
|
295
|
+
const stopRecovery = recoverStrayFocus(container, {
|
|
296
|
+
onEscape: () => close("escape"),
|
|
297
|
+
});
|
|
298
|
+
return () => {
|
|
299
|
+
clearTimeout(t);
|
|
300
|
+
stopRecovery();
|
|
301
|
+
overlay.leave();
|
|
302
|
+
unlockScroll();
|
|
303
|
+
dragging = false;
|
|
304
|
+
dragHeight = null;
|
|
305
|
+
dragOffset = 0;
|
|
306
|
+
if (focusActive) {
|
|
307
|
+
focusActive = false;
|
|
308
|
+
returnFocus();
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
$effect(() => {
|
|
315
|
+
const zone = header;
|
|
316
|
+
const container = panel;
|
|
317
|
+
if (!isOpen || !zone || !container) return;
|
|
318
|
+
|
|
319
|
+
let heights: number[] = [];
|
|
320
|
+
let startHeight = 0;
|
|
321
|
+
let startIndex = 0;
|
|
322
|
+
|
|
323
|
+
/** How much of the sheet shows after the pointer has moved `distance` down. */
|
|
324
|
+
const shown = (distance: number) =>
|
|
325
|
+
Math.min(Math.max(startHeight - distance, 0), heights[heights.length - 1]);
|
|
326
|
+
|
|
327
|
+
return attachSheetDrag(
|
|
328
|
+
{ handleZone: zone, scroller },
|
|
329
|
+
{
|
|
330
|
+
onStart() {
|
|
331
|
+
heights = measureSnapHeights();
|
|
332
|
+
startHeight = container.getBoundingClientRect().height;
|
|
333
|
+
startIndex = points.indexOf(currentSnap);
|
|
334
|
+
dragging = true;
|
|
335
|
+
},
|
|
336
|
+
onMove(distance) {
|
|
337
|
+
const height = shown(distance);
|
|
338
|
+
const lowest = heights[0];
|
|
339
|
+
if (height >= lowest) {
|
|
340
|
+
dragHeight = height;
|
|
341
|
+
dragOffset = 0;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// Below the lowest snap point the sheet keeps its size and
|
|
345
|
+
// slides off; one that cannot be closed only gives a little.
|
|
346
|
+
dragHeight = lowest;
|
|
347
|
+
dragOffset = (lowest - height) * (dismissible ? 1 : 0.2);
|
|
348
|
+
},
|
|
349
|
+
onEnd(distance, velocity) {
|
|
350
|
+
const index = settleSnap({
|
|
351
|
+
heights,
|
|
352
|
+
startIndex,
|
|
353
|
+
height: shown(distance),
|
|
354
|
+
velocity,
|
|
355
|
+
flick: FLICK_VELOCITY,
|
|
356
|
+
canClose: dismissible,
|
|
357
|
+
});
|
|
358
|
+
dragging = false;
|
|
359
|
+
dragHeight = null;
|
|
360
|
+
dragOffset = 0;
|
|
361
|
+
if (index === -1) close("swipe");
|
|
362
|
+
else snap = points[index];
|
|
363
|
+
},
|
|
364
|
+
onCancel() {
|
|
365
|
+
dragging = false;
|
|
366
|
+
dragHeight = null;
|
|
367
|
+
dragOffset = 0;
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
function handleBackdropClick(event: Event) {
|
|
374
|
+
if (event.target === event.currentTarget) close("backdrop");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
378
|
+
// `defaultPrevented` means a nested dialog already handled the key.
|
|
379
|
+
// Prevented here even when not dismissible, so a dialog underneath
|
|
380
|
+
// does not take the Escape as its own.
|
|
381
|
+
if (event.key === "Escape" && !event.defaultPrevented) {
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
close("escape");
|
|
384
|
+
}
|
|
385
|
+
onkeydown?.(event);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 44px wide and tall in px: the target must not shrink, and need not grow
|
|
390
|
+
* with the text. It is flush with the top edge of the panel, so it has
|
|
391
|
+
* the panel's radius; that also clears the mark inside it, a 4px pill
|
|
392
|
+
* 10px in (2px + 10px).
|
|
393
|
+
*/
|
|
394
|
+
const gripClasses =
|
|
395
|
+
"absolute top-0 left-1/2 flex h-[44px] w-[80px] -translate-x-1/2 cursor-grab items-start justify-center rounded-overlay pt-[10px] active:cursor-grabbing";
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The focus ring of the grip button, drawn inside its box. The usual ring
|
|
399
|
+
* reaches 4px outside it, and the title row starts 28px down, inside the
|
|
400
|
+
* 44px box: outside, the ring cut across the title. Same gap and ring,
|
|
401
|
+
* handed to the same property `.focus-ring` uses, only inset.
|
|
402
|
+
*/
|
|
403
|
+
const gripFocusClasses =
|
|
404
|
+
"focus-ring focus-visible:[--tw-ring-offset-shadow:inset_0_0_0_2px_var(--zabi-focus-ring-offset-color),inset_0_0_0_4px_var(--zabi-focus-ring-color)]";
|
|
405
|
+
</script>
|
|
406
|
+
|
|
407
|
+
<!-- The bar of the grip. Drawn in the text colour where colours are forced:
|
|
408
|
+
a background is dropped there, and the grip would be gone. -->
|
|
409
|
+
{#snippet gripMark()}
|
|
410
|
+
<span
|
|
411
|
+
class="block h-1 w-9 rounded-pill bg-current text-description forced-colors:bg-[CanvasText]"
|
|
412
|
+
aria-hidden="true"
|
|
413
|
+
></span>
|
|
414
|
+
{/snippet}
|
|
415
|
+
|
|
416
|
+
{#if isOpen}
|
|
417
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
418
|
+
<div
|
|
419
|
+
bind:this={root}
|
|
420
|
+
class="group/overlay fixed inset-0 z-modal overflow-hidden pt-[env(safe-area-inset-top)] {dismissible
|
|
421
|
+
? 'cursor-pointer'
|
|
422
|
+
: 'cursor-default'} bg-overlay"
|
|
423
|
+
style:z-index={depth > 0 ? `calc(var(--z-modal) + ${depth})` : undefined}
|
|
424
|
+
data-overlay-depth={depth}
|
|
425
|
+
use:portalTo={portal}
|
|
426
|
+
onclick={handleBackdropClick}
|
|
427
|
+
onkeydown={handleKeydown}
|
|
428
|
+
role="presentation"
|
|
429
|
+
>
|
|
430
|
+
<div
|
|
431
|
+
bind:this={panel}
|
|
432
|
+
class={cn(
|
|
433
|
+
"absolute inset-x-0 bottom-0 mx-auto flex w-full cursor-default flex-col rounded-t-overlay border-t border-border-overlay bg-surface-overlay shadow-lg",
|
|
434
|
+
"max-h-[calc(100dvh-env(safe-area-inset-top,0px))] pb-[env(safe-area-inset-bottom)]",
|
|
435
|
+
// Over the keyboard: no taller than what is left, and the home
|
|
436
|
+
// indicator it would clear is under the keyboard.
|
|
437
|
+
"group-data-keyboard-open/overlay:max-h-[calc(100%_-_env(safe-area-inset-top,0px))] group-data-keyboard-open/overlay:pb-0",
|
|
438
|
+
// Clear of the notch and the rounded corners in landscape.
|
|
439
|
+
"pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
|
|
440
|
+
"transition-[height,transform] duration-200 ease-out motion-reduce:transition-none",
|
|
441
|
+
"md:w-[min(100%,40rem)] md:border-x",
|
|
442
|
+
className,
|
|
443
|
+
)}
|
|
444
|
+
style:height={dragHeight !== null ? `${dragHeight}px` : snapHeight}
|
|
445
|
+
style:transform={dragOffset > 0 ? `translateY(${dragOffset}px)` : undefined}
|
|
446
|
+
style:transition={dragging ? "none" : undefined}
|
|
447
|
+
role="dialog"
|
|
448
|
+
aria-modal="true"
|
|
449
|
+
aria-labelledby={titleId}
|
|
450
|
+
aria-describedby={description ? descriptionId : undefined}
|
|
451
|
+
tabindex="-1"
|
|
452
|
+
data-snap={currentSnap}
|
|
453
|
+
data-dragging={dragging ? "true" : undefined}
|
|
454
|
+
{...restProps}
|
|
455
|
+
onkeydown={(event) => trapTabKey(panel, event)}
|
|
456
|
+
>
|
|
457
|
+
<!-- The whole header drags the sheet, in both directions.
|
|
458
|
+
`touch-none`: otherwise the browser takes a touch here for itself.
|
|
459
|
+
Chrome: laid out in px, with a title that stops at 1.3 times its
|
|
460
|
+
size (util/overlay-chrome.ts). The gutters of the content and
|
|
461
|
+
the footer are px with it, so the three stay in line. -->
|
|
462
|
+
<div bind:this={header} class="relative shrink-0 touch-none px-4 pt-7 pb-3 {OVERLAY_CHROME}">
|
|
463
|
+
{#if stepTo}
|
|
464
|
+
<button
|
|
465
|
+
type="button"
|
|
466
|
+
data-sheet-grip
|
|
467
|
+
class={cn(gripFocusClasses, gripClasses)}
|
|
468
|
+
aria-label={stepTo === "full" ? expandLabel : collapseLabel}
|
|
469
|
+
onclick={stepSnap}
|
|
470
|
+
>
|
|
471
|
+
{@render gripMark()}
|
|
472
|
+
</button>
|
|
473
|
+
{:else}
|
|
474
|
+
<div data-sheet-grip class={gripClasses}>
|
|
475
|
+
{@render gripMark()}
|
|
476
|
+
</div>
|
|
477
|
+
{/if}
|
|
478
|
+
<div class="flex items-start justify-between gap-[8px]">
|
|
479
|
+
<div class="min-w-0 flex-1">
|
|
480
|
+
<h2
|
|
481
|
+
id={titleId}
|
|
482
|
+
class="{OVERLAY_TITLE_XL} font-semibold text-headline {OVERLAY_TITLE_WRAP} {enlargedText ? OVERLAY_TITLE_HYPHENS : ''}"
|
|
483
|
+
>
|
|
484
|
+
{title}
|
|
485
|
+
</h2>
|
|
486
|
+
</div>
|
|
487
|
+
<!-- `aria-disabled`, not `disabled` or removed: the button may hold focus when `dismissible` turns false, and a disabled or missing element would drop focus out of the trap. -->
|
|
488
|
+
<!-- 8px in from the end edge of the panel: its radius
|
|
489
|
+
(control) plus that is the panel's (overlay). -->
|
|
490
|
+
<button
|
|
491
|
+
type="button"
|
|
492
|
+
onclick={() => close("close-button")}
|
|
493
|
+
class="focus-ring -mt-2 -me-2 flex size-11 shrink-0 items-center justify-center rounded-control text-description transition-colors motion-reduce:transition-none {dismissible
|
|
494
|
+
? 'cursor-pointer hover:bg-surface-overlay-hover hover:text-headline'
|
|
495
|
+
: 'cursor-not-allowed opacity-50'}"
|
|
496
|
+
aria-label={closeLabel}
|
|
497
|
+
aria-disabled={dismissible ? undefined : "true"}
|
|
498
|
+
>
|
|
499
|
+
<X size={20} aria-hidden="true" />
|
|
500
|
+
</button>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<!-- `pt-1`: a scroll container clips, and the focus ring of a
|
|
505
|
+
first control needs the room. `overscroll-contain`: the page
|
|
506
|
+
behind must not take over when the content reaches an end. -->
|
|
507
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
508
|
+
<div
|
|
509
|
+
bind:this={scroller}
|
|
510
|
+
data-bottom-sheet-content
|
|
511
|
+
class={cn(
|
|
512
|
+
"min-h-0 flex-1 overflow-y-auto overscroll-contain px-[16px] pt-[4px] pb-[16px]",
|
|
513
|
+
scrollerNeedsFocus &&
|
|
514
|
+
"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring",
|
|
515
|
+
)}
|
|
516
|
+
tabindex={scrollerNeedsFocus ? 0 : undefined}
|
|
517
|
+
role={scrollerNeedsFocus ? "group" : undefined}
|
|
518
|
+
aria-labelledby={scrollerNeedsFocus ? titleId : undefined}
|
|
519
|
+
>
|
|
520
|
+
<!-- With the content, not in the header: at half height on a
|
|
521
|
+
small screen every fixed line is taken from the content. -->
|
|
522
|
+
{#if description}
|
|
523
|
+
<p id={descriptionId} class="mb-3 text-sm text-description">
|
|
524
|
+
{description}
|
|
525
|
+
</p>
|
|
526
|
+
{/if}
|
|
527
|
+
{@render children?.()}
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
{#if footer}
|
|
531
|
+
<div
|
|
532
|
+
bind:this={footerElement}
|
|
533
|
+
class="flex shrink-0 flex-wrap justify-end gap-[8px] border-t border-border-overlay px-[16px] py-[12px]"
|
|
534
|
+
>
|
|
535
|
+
{@render footer()}
|
|
536
|
+
</div>
|
|
537
|
+
{/if}
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
{/if}
|
|
@@ -0,0 +1,93 @@
|
|
|
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.
|
|
27
|
+
*
|
|
28
|
+
* From the `md` breakpoint up the sheet is still attached to the bottom
|
|
29
|
+
* edge, centred and no wider than 40rem.
|
|
30
|
+
*
|
|
31
|
+
* Other attributes (`id`, `data-*`, ...) land on the `role="dialog"` panel.
|
|
32
|
+
*/
|
|
33
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "title" | "onclose" | "onkeydown"> & {
|
|
34
|
+
isOpen?: boolean;
|
|
35
|
+
/** Heading of the sheet, and its accessible name. */
|
|
36
|
+
title: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The heights the sheet can rest at: `half` of the screen, `full`
|
|
40
|
+
* down from the status bar. With one, the grip only drags.
|
|
41
|
+
*/
|
|
42
|
+
snapPoints?: BottomSheetSnap[];
|
|
43
|
+
/**
|
|
44
|
+
* The snap point the sheet is at. Bindable. It opens at the one you
|
|
45
|
+
* pass, or at the lowest of `snapPoints`. It is kept while the sheet
|
|
46
|
+
* is closed: one left at `full` opens at `full` again, bound or not.
|
|
47
|
+
* Set it when you open the sheet to start somewhere else.
|
|
48
|
+
*/
|
|
49
|
+
snap?: BottomSheetSnap;
|
|
50
|
+
/**
|
|
51
|
+
* Render the overlay in `document.body`, so an ancestor with a
|
|
52
|
+
* transform, filter or clipped overflow cannot trap it; pass `false`
|
|
53
|
+
* to render in place. The theme class belongs on `<html>` or `<body>`;
|
|
54
|
+
* set lower, it does not reach a portalled sheet.
|
|
55
|
+
*/
|
|
56
|
+
portal?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* When false, Escape, a backdrop click, the close button and a swipe
|
|
59
|
+
* down do not close the sheet. Focus stays trapped, the grip still
|
|
60
|
+
* moves it between snap points, and setting `isOpen` yourself still
|
|
61
|
+
* closes it.
|
|
62
|
+
*/
|
|
63
|
+
dismissible?: boolean;
|
|
64
|
+
/** Fired when the sheet closes itself, with what the user did. */
|
|
65
|
+
onclose?: (detail: {
|
|
66
|
+
reason: BottomSheetCloseReason;
|
|
67
|
+
}) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Hears every keydown in the sheet, after the sheet has handled
|
|
70
|
+
* Escape. The Tab cycle is kept either way.
|
|
71
|
+
*/
|
|
72
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
73
|
+
/** Accessible name of the close button. */
|
|
74
|
+
closeLabel?: string;
|
|
75
|
+
/** Accessible name of the grip while its button takes the sheet up a step. */
|
|
76
|
+
expandLabel?: string;
|
|
77
|
+
/** Accessible name of the grip while its button takes the sheet down a step. */
|
|
78
|
+
collapseLabel?: string;
|
|
79
|
+
/**
|
|
80
|
+
* CSS selector, looked up inside the panel, of the control that takes
|
|
81
|
+
* focus when the sheet opens (a search field, say). Without it, or
|
|
82
|
+
* with no match, the first control does: the grip, or the close button.
|
|
83
|
+
*/
|
|
84
|
+
initialFocus?: string;
|
|
85
|
+
/** Extra classes for the dialog panel. */
|
|
86
|
+
class?: string;
|
|
87
|
+
children?: Snippet;
|
|
88
|
+
/** Pinned to the bottom of the panel, below the scrolling content. */
|
|
89
|
+
footer?: Snippet;
|
|
90
|
+
};
|
|
91
|
+
declare const BottomSheet: import("svelte").Component<Props, {}, "isOpen" | "snap">;
|
|
92
|
+
type BottomSheet = ReturnType<typeof BottomSheet>;
|
|
93
|
+
export default BottomSheet;
|