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,415 @@
|
|
|
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 ArrowLeft from "@lucide/svelte/icons/arrow-left";
|
|
6
|
+
import { findScrollParent } from "../util/app-shell.js";
|
|
7
|
+
import { cn } from "../util/cn.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The bar at the top of a phone screen: where you are, the way back, and
|
|
11
|
+
* at most two things to do here.
|
|
12
|
+
*
|
|
13
|
+
* It is one row, 56px tall, whenever the title has 72px or more beside
|
|
14
|
+
* the controls, at every text size. The bar is chrome: when the reader
|
|
15
|
+
* enlarges text, the room belongs to the page under it. So the controls
|
|
16
|
+
* are sized in px (48px targets), the title grows with the text only up
|
|
17
|
+
* to 1.3 times its size, and a title too long for the room it has is cut
|
|
18
|
+
* with an ellipsis (after two lines with `titleLines={2}`). The heading
|
|
19
|
+
* keeps its full text for a screen reader; keep the visible start of it
|
|
20
|
+
* enough to name the screen.
|
|
21
|
+
*
|
|
22
|
+
* The title never has less than 72px. Where the back control, `leading`
|
|
23
|
+
* and the actions leave it less (a wide chip in `leading`, a very narrow
|
|
24
|
+
* bar), it takes a second row of its own, as wide as the bar, and they
|
|
25
|
+
* stay together on the first.
|
|
26
|
+
*/
|
|
27
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, "class" | "title"> & {
|
|
28
|
+
/** The name of the screen, as a heading. */
|
|
29
|
+
title?: string;
|
|
30
|
+
/** Heading level of the title. 1 when the bar names the page. */
|
|
31
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
32
|
+
/** Shows the back control as a link to this address. */
|
|
33
|
+
backHref?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Called when the back control is activated. On its own it makes the
|
|
36
|
+
* control a button; with `backHref` it runs before the link is
|
|
37
|
+
* followed, and `event.preventDefault()` stops that.
|
|
38
|
+
*/
|
|
39
|
+
onback?: (event: MouseEvent) => void;
|
|
40
|
+
/** Accessible name of the back control. */
|
|
41
|
+
backLabel?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Hides the bar while the page scrolls down and brings it back as
|
|
44
|
+
* soon as it scrolls up. It follows the nearest scrolling ancestor
|
|
45
|
+
* (the one in `AppShell`), or the window. The bar never hides while
|
|
46
|
+
* keyboard focus is inside it, and focus arriving from the keyboard
|
|
47
|
+
* brings it back. Focus left behind by a tap or a click does not hold
|
|
48
|
+
* it: it hides as usual, and the next key press brings it back.
|
|
49
|
+
*/
|
|
50
|
+
collapseOnScroll?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* How many lines the title may take before it is cut with an
|
|
53
|
+
* ellipsis. The bar is taller only while the second line is in use.
|
|
54
|
+
*/
|
|
55
|
+
titleLines?: 1 | 2;
|
|
56
|
+
/**
|
|
57
|
+
* Before the title, after the back control: a logo, an avatar, a
|
|
58
|
+
* chip. Rendered as it is, a child of the bar's row. It keeps its
|
|
59
|
+
* width; when that leaves the title under 72px the title moves to a
|
|
60
|
+
* row of its own, and a `leading` wider than the row is held to the
|
|
61
|
+
* row's width.
|
|
62
|
+
*/
|
|
63
|
+
leading?: Snippet;
|
|
64
|
+
/**
|
|
65
|
+
* After the title. At most two icon buttons, at `size="lg"` so each is
|
|
66
|
+
* a 48px target; put anything more in a menu. Their size does not
|
|
67
|
+
* grow with the text size here: inside the bar the spacing scale is
|
|
68
|
+
* in px.
|
|
69
|
+
*/
|
|
70
|
+
actions?: Snippet;
|
|
71
|
+
class?: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let {
|
|
75
|
+
title = "",
|
|
76
|
+
headingLevel = 1,
|
|
77
|
+
backHref,
|
|
78
|
+
onback,
|
|
79
|
+
backLabel: backLabelGiven,
|
|
80
|
+
collapseOnScroll = false,
|
|
81
|
+
titleLines = 1,
|
|
82
|
+
leading,
|
|
83
|
+
actions,
|
|
84
|
+
class: className = "",
|
|
85
|
+
...restProps
|
|
86
|
+
}: Props = $props();
|
|
87
|
+
|
|
88
|
+
/** Words many components share: a `ZabiStringsProvider` above this one may give them; else English. */
|
|
89
|
+
const common = zabiCommonStrings();
|
|
90
|
+
const backLabel = $derived(backLabelGiven ?? common().back);
|
|
91
|
+
|
|
92
|
+
let host: HTMLElement | undefined = $state();
|
|
93
|
+
/** Scrolled out of view. Only ever true with `collapseOnScroll`. */
|
|
94
|
+
let scrolledAway = $state(false);
|
|
95
|
+
const collapsed = $derived(collapseOnScroll && scrolledAway);
|
|
96
|
+
|
|
97
|
+
/** Scrolling less than this in one direction does not move the bar. */
|
|
98
|
+
const SCROLL_TOLERANCE = 8;
|
|
99
|
+
|
|
100
|
+
$effect(() => {
|
|
101
|
+
if (!collapseOnScroll || !host) return;
|
|
102
|
+
const bar = host;
|
|
103
|
+
const parent = findScrollParent(bar);
|
|
104
|
+
const target: HTMLElement | Window = parent ?? window;
|
|
105
|
+
const position = () => (parent ? parent.scrollTop : window.scrollY);
|
|
106
|
+
const end = () =>
|
|
107
|
+
parent
|
|
108
|
+
? parent.scrollHeight - parent.clientHeight
|
|
109
|
+
: document.documentElement.scrollHeight - window.innerHeight;
|
|
110
|
+
// Rubber-banding past either end reports positions outside the range.
|
|
111
|
+
const clamped = () => Math.min(Math.max(0, position()), Math.max(0, end()));
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether the focus inside the bar is there for the keyboard. Only
|
|
115
|
+
* that holds the bar: after a tap on an action the button stays
|
|
116
|
+
* focused for as long as the user reads on, and a bar held by it
|
|
117
|
+
* would never hide again. `:focus-visible` is not asked, because it
|
|
118
|
+
* answers for the browser's own ring, not for how focus arrived.
|
|
119
|
+
*/
|
|
120
|
+
let heldByKeyboard = false;
|
|
121
|
+
/** A press began in the bar and has not yet turned into a click. */
|
|
122
|
+
let pressing = false;
|
|
123
|
+
|
|
124
|
+
const onPointerDown = () => {
|
|
125
|
+
pressing = true;
|
|
126
|
+
};
|
|
127
|
+
const onClick = () => {
|
|
128
|
+
pressing = false;
|
|
129
|
+
};
|
|
130
|
+
const onFocusIn = () => {
|
|
131
|
+
// A press cannot reach a bar that is away, so focus that arrives
|
|
132
|
+
// then is never the pointer's. The focus of a press is not held.
|
|
133
|
+
if (pressing && !scrolledAway) {
|
|
134
|
+
heldByKeyboard = false;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
heldByKeyboard = true;
|
|
138
|
+
scrolledAway = false;
|
|
139
|
+
};
|
|
140
|
+
const onFocusOut = (event: FocusEvent) => {
|
|
141
|
+
const next = event.relatedTarget;
|
|
142
|
+
if (!(next instanceof Node) || !bar.contains(next)) heldByKeyboard = false;
|
|
143
|
+
};
|
|
144
|
+
/** A key pressed in the bar: the keyboard is in use, wherever focus came from. */
|
|
145
|
+
const onKeyDown = () => {
|
|
146
|
+
heldByKeyboard = true;
|
|
147
|
+
scrolledAway = false;
|
|
148
|
+
};
|
|
149
|
+
/** Any key ends a press that never became a click (a drag off the bar). */
|
|
150
|
+
const onAnyKeyDown = () => {
|
|
151
|
+
pressing = false;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
let last = clamped();
|
|
155
|
+
const onScroll = () => {
|
|
156
|
+
const now = clamped();
|
|
157
|
+
// At the top the bar is where it was laid out: always showing.
|
|
158
|
+
if (now <= bar.offsetHeight) {
|
|
159
|
+
last = now;
|
|
160
|
+
scrolledAway = false;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const delta = now - last;
|
|
164
|
+
if (Math.abs(delta) < SCROLL_TOLERANCE) return;
|
|
165
|
+
last = now;
|
|
166
|
+
scrolledAway =
|
|
167
|
+
delta > 0 && !(heldByKeyboard && bar.contains(document.activeElement));
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
target.addEventListener("scroll", onScroll, { passive: true });
|
|
171
|
+
bar.addEventListener("pointerdown", onPointerDown);
|
|
172
|
+
bar.addEventListener("click", onClick);
|
|
173
|
+
bar.addEventListener("focusin", onFocusIn);
|
|
174
|
+
bar.addEventListener("focusout", onFocusOut);
|
|
175
|
+
bar.addEventListener("keydown", onKeyDown);
|
|
176
|
+
window.addEventListener("keydown", onAnyKeyDown, true);
|
|
177
|
+
return () => {
|
|
178
|
+
target.removeEventListener("scroll", onScroll);
|
|
179
|
+
bar.removeEventListener("pointerdown", onPointerDown);
|
|
180
|
+
bar.removeEventListener("click", onClick);
|
|
181
|
+
bar.removeEventListener("focusin", onFocusIn);
|
|
182
|
+
bar.removeEventListener("focusout", onFocusOut);
|
|
183
|
+
bar.removeEventListener("keydown", onKeyDown);
|
|
184
|
+
window.removeEventListener("keydown", onAnyKeyDown, true);
|
|
185
|
+
scrolledAway = false;
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/** A 48px ghost target, the same as `IconButton` at `variant="ghost" size="lg"`. */
|
|
190
|
+
const backClasses =
|
|
191
|
+
"focus-ring focus-ring--muted inline-flex size-12 shrink-0 cursor-pointer items-center justify-center rounded-control bg-transparent text-headline transition-colors duration-150 hover:bg-surface-hover active:bg-surface-active";
|
|
192
|
+
|
|
193
|
+
const hasBack = $derived(backHref !== undefined || onback !== undefined);
|
|
194
|
+
|
|
195
|
+
/** The least room a title is left with beside the controls, in px. */
|
|
196
|
+
const TITLE_MIN = 72;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Where the title is: `inline`, between `leading` and the actions, or on
|
|
200
|
+
* a row of its `own` under them. Null until the bar has been measured
|
|
201
|
+
* (the server, a test DOM without layout): the stylesheet's own fallback
|
|
202
|
+
* then keeps the title at 72px by wrapping.
|
|
203
|
+
*
|
|
204
|
+
* It has to be measured. What the title is left with depends on how wide
|
|
205
|
+
* `leading` is, which is the app's markup: no media or container query
|
|
206
|
+
* knows that. And wrapping alone cannot do it: the actions come after
|
|
207
|
+
* the title, so they would follow it down to the second row.
|
|
208
|
+
*/
|
|
209
|
+
let titleRow = $state<"inline" | "own" | null>(null);
|
|
210
|
+
/** The widest `leading` can be on the first row, in px. */
|
|
211
|
+
let leadingMax = $state<number | null>(null);
|
|
212
|
+
let row: HTMLElement | undefined = $state();
|
|
213
|
+
|
|
214
|
+
$effect(() => {
|
|
215
|
+
const bar = row;
|
|
216
|
+
if (!bar || typeof ResizeObserver === "undefined") return;
|
|
217
|
+
// Read here so that a new title or line count measures again.
|
|
218
|
+
void title;
|
|
219
|
+
void titleLines;
|
|
220
|
+
|
|
221
|
+
const measure = () => {
|
|
222
|
+
const style = getComputedStyle(bar);
|
|
223
|
+
const inner =
|
|
224
|
+
bar.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
|
|
225
|
+
// Nothing is laid out (a hidden bar): leave it as it is.
|
|
226
|
+
if (!(inner > 0)) return;
|
|
227
|
+
const gap = parseFloat(style.columnGap) || 0;
|
|
228
|
+
|
|
229
|
+
// Every part at the width it asks for, on one line, with the
|
|
230
|
+
// title out of the count: what is left is the title's.
|
|
231
|
+
bar.dataset.measuring = "";
|
|
232
|
+
let fixed = 0;
|
|
233
|
+
let beside = 0;
|
|
234
|
+
let parts = 0;
|
|
235
|
+
for (const child of bar.children) {
|
|
236
|
+
if (!(child instanceof HTMLElement)) continue;
|
|
237
|
+
const part = child.dataset.appbarPart;
|
|
238
|
+
if (part === "title" || child.offsetWidth === 0) continue;
|
|
239
|
+
parts += 1;
|
|
240
|
+
beside += child.offsetWidth;
|
|
241
|
+
// The back control and the actions keep their width whatever happens.
|
|
242
|
+
if (part === "back" || part === "actions") fixed += child.offsetWidth;
|
|
243
|
+
}
|
|
244
|
+
delete bar.dataset.measuring;
|
|
245
|
+
|
|
246
|
+
const left = inner - beside - gap * parts;
|
|
247
|
+
const next = left < TITLE_MIN ? "own" : "inline";
|
|
248
|
+
if (next !== titleRow) titleRow = next;
|
|
249
|
+
const fixedParts = bar.querySelectorAll(
|
|
250
|
+
":scope > [data-appbar-part='back'], :scope > [data-appbar-part='actions']",
|
|
251
|
+
).length;
|
|
252
|
+
const room = Math.max(0, Math.floor(inner - fixed - gap * fixedParts));
|
|
253
|
+
if (room !== leadingMax) leadingMax = room;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
measure();
|
|
257
|
+
const sizes = new ResizeObserver(measure);
|
|
258
|
+
const watch = () => {
|
|
259
|
+
sizes.disconnect();
|
|
260
|
+
sizes.observe(bar);
|
|
261
|
+
for (const child of bar.children) sizes.observe(child);
|
|
262
|
+
};
|
|
263
|
+
watch();
|
|
264
|
+
// `leading` and the actions are the app's: they come and go.
|
|
265
|
+
const nodes = new MutationObserver(() => {
|
|
266
|
+
watch();
|
|
267
|
+
measure();
|
|
268
|
+
});
|
|
269
|
+
nodes.observe(bar, { childList: true });
|
|
270
|
+
return () => {
|
|
271
|
+
sizes.disconnect();
|
|
272
|
+
nodes.disconnect();
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
</script>
|
|
276
|
+
|
|
277
|
+
<header
|
|
278
|
+
bind:this={host}
|
|
279
|
+
data-collapsed={collapseOnScroll ? String(collapsed) : undefined}
|
|
280
|
+
class={cn(
|
|
281
|
+
"sticky top-0 z-sticky border-b border-border-weak bg-surface-elevated",
|
|
282
|
+
// Clear of the status bar and the notch, and of the corners in landscape.
|
|
283
|
+
"pt-[env(safe-area-inset-top)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
|
|
284
|
+
"transition-transform duration-200 ease-out motion-reduce:transition-none",
|
|
285
|
+
// The strip under the status bar stays, so content never shows through it.
|
|
286
|
+
collapsed && "-translate-y-[calc(100%-env(safe-area-inset-top,0px))]",
|
|
287
|
+
className,
|
|
288
|
+
)}
|
|
289
|
+
{...restProps}
|
|
290
|
+
>
|
|
291
|
+
<!-- `--spacing: 4px`: Tailwind sizes everything from `--spacing`, which
|
|
292
|
+
is 0.25rem, so a 48px button was 96px with the text at 200% and the bar
|
|
293
|
+
wrapped to 225px. In px here, the back control, the actions and whatever
|
|
294
|
+
`leading` holds keep their size, and the title has the rest of the row,
|
|
295
|
+
or a row of its own when the rest is under 72px (see the style block).
|
|
296
|
+
The same at 100%: 0.25rem is 4px.
|
|
297
|
+
Fades out while the bar is away: under a status bar the strip that stays
|
|
298
|
+
is the bottom of the bar, and the controls would show in it. They keep
|
|
299
|
+
their place in the tab order; keyboard focus brings the bar back. -->
|
|
300
|
+
<div
|
|
301
|
+
bind:this={row}
|
|
302
|
+
data-title-row={titleRow ?? undefined}
|
|
303
|
+
style:--appbar-leading-max={leadingMax !== null ? `${leadingMax}px` : undefined}
|
|
304
|
+
class={cn(
|
|
305
|
+
"appbar-row flex min-h-14 items-center gap-2 px-2 py-1 [--spacing:4px]",
|
|
306
|
+
"transition-opacity duration-200 ease-out motion-reduce:transition-none",
|
|
307
|
+
collapsed && "pointer-events-none opacity-0",
|
|
308
|
+
)}
|
|
309
|
+
>
|
|
310
|
+
{#if backHref !== undefined}
|
|
311
|
+
<a href={backHref} class={backClasses} aria-label={backLabel} onclick={onback} data-appbar-part="back">
|
|
312
|
+
<ArrowLeft size={24} class="rtl:rotate-180" aria-hidden="true" />
|
|
313
|
+
</a>
|
|
314
|
+
{:else if onback}
|
|
315
|
+
<button type="button" class={backClasses} aria-label={backLabel} onclick={onback} data-appbar-part="back">
|
|
316
|
+
<ArrowLeft size={24} class="rtl:rotate-180" aria-hidden="true" />
|
|
317
|
+
</button>
|
|
318
|
+
{/if}
|
|
319
|
+
{@render leading?.()}
|
|
320
|
+
{#if title}
|
|
321
|
+
<!-- One line, cut with an ellipsis; two before it is cut with
|
|
322
|
+
`titleLines={2}`. 1.125rem, and no more than 1.3 times that
|
|
323
|
+
(23.4px) however large the text is set.
|
|
324
|
+
One branch per level, not a dynamic element: hydration takes a dynamic
|
|
325
|
+
element out and puts it back, which blurs a control inside it that the user
|
|
326
|
+
had already tabbed to. -->
|
|
327
|
+
{@const titleClasses = cn(
|
|
328
|
+
"appbar-title m-0 text-[length:min(1.125rem,23.4px)] leading-[1.34] font-semibold text-headline",
|
|
329
|
+
titleLines === 2 ? "line-clamp-2 [overflow-wrap:anywhere]" : "truncate",
|
|
330
|
+
!hasBack && !leading && "ps-2",
|
|
331
|
+
)}
|
|
332
|
+
{#if headingLevel === 1}
|
|
333
|
+
<h1 class={titleClasses} data-appbar-part="title">{title}</h1>
|
|
334
|
+
{:else if headingLevel === 2}
|
|
335
|
+
<h2 class={titleClasses} data-appbar-part="title">{title}</h2>
|
|
336
|
+
{:else if headingLevel === 3}
|
|
337
|
+
<h3 class={titleClasses} data-appbar-part="title">{title}</h3>
|
|
338
|
+
{:else if headingLevel === 4}
|
|
339
|
+
<h4 class={titleClasses} data-appbar-part="title">{title}</h4>
|
|
340
|
+
{:else if headingLevel === 5}
|
|
341
|
+
<h5 class={titleClasses} data-appbar-part="title">{title}</h5>
|
|
342
|
+
{:else}
|
|
343
|
+
<h6 class={titleClasses} data-appbar-part="title">{title}</h6>
|
|
344
|
+
{/if}
|
|
345
|
+
{:else}
|
|
346
|
+
<div class="min-w-0 flex-1" data-appbar-part="title"></div>
|
|
347
|
+
{/if}
|
|
348
|
+
{#if actions}
|
|
349
|
+
<div class="ms-auto flex shrink-0 items-center gap-2" data-appbar-part="actions">
|
|
350
|
+
{@render actions()}
|
|
351
|
+
</div>
|
|
352
|
+
{/if}
|
|
353
|
+
</div>
|
|
354
|
+
</header>
|
|
355
|
+
|
|
356
|
+
<style>
|
|
357
|
+
/*
|
|
358
|
+
* Until the bar has measured itself (the server's markup, no scripts):
|
|
359
|
+
* the row may wrap and the title asks for 72px, so it is never narrower.
|
|
360
|
+
* The actions follow it to the second row there, which is the one thing
|
|
361
|
+
* wrapping cannot avoid: they come after the title.
|
|
362
|
+
*/
|
|
363
|
+
.appbar-row {
|
|
364
|
+
flex-wrap: wrap;
|
|
365
|
+
/* A `leading` that is wider than the row must not widen the page.
|
|
366
|
+
`clip`, not `hidden`: nothing scrolls, and nothing is cut above or
|
|
367
|
+
below, where a focus ring is. */
|
|
368
|
+
overflow-x: clip;
|
|
369
|
+
}
|
|
370
|
+
.appbar-row :global(> [data-appbar-part="title"]) {
|
|
371
|
+
flex: 1 1 72px;
|
|
372
|
+
min-width: 72px;
|
|
373
|
+
}
|
|
374
|
+
/* `leading`: whatever in the row is not the bar's own. */
|
|
375
|
+
.appbar-row :global(> :not([data-appbar-part])) {
|
|
376
|
+
min-width: 0;
|
|
377
|
+
max-width: var(--appbar-leading-max, 100%);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/* Measured, and the title has 72px or more: one row, as it was. */
|
|
381
|
+
.appbar-row[data-title-row="inline"] {
|
|
382
|
+
flex-wrap: nowrap;
|
|
383
|
+
}
|
|
384
|
+
.appbar-row[data-title-row="inline"] :global(> [data-appbar-part="title"]) {
|
|
385
|
+
flex: 1 1 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/*
|
|
389
|
+
* Measured, and it has less: the title is last and takes a whole row;
|
|
390
|
+
* the back control, `leading` and the actions are the first. Only the
|
|
391
|
+
* drawing order changes: in the document, and so for a screen reader and
|
|
392
|
+
* the Tab key, it is still back, leading, title, actions.
|
|
393
|
+
*/
|
|
394
|
+
.appbar-row[data-title-row="own"] :global(> [data-appbar-part="actions"]) {
|
|
395
|
+
order: 1;
|
|
396
|
+
}
|
|
397
|
+
.appbar-row[data-title-row="own"] :global(> [data-appbar-part="title"]) {
|
|
398
|
+
order: 2;
|
|
399
|
+
flex: 1 1 100%;
|
|
400
|
+
/* The same inset as on a row with nothing before it, and the row's
|
|
401
|
+
4px above and below; 8px more at the sides, in line with the
|
|
402
|
+
icons' edges. */
|
|
403
|
+
padding-inline: 8px;
|
|
404
|
+
padding-block-end: 4px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* While it measures: every part at the width it asks for, on one line. */
|
|
408
|
+
.appbar-row[data-measuring] {
|
|
409
|
+
flex-wrap: nowrap;
|
|
410
|
+
}
|
|
411
|
+
.appbar-row[data-measuring] :global(> *) {
|
|
412
|
+
flex: none;
|
|
413
|
+
max-width: none;
|
|
414
|
+
}
|
|
415
|
+
</style>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* The bar at the top of a phone screen: where you are, the way back, and
|
|
5
|
+
* at most two things to do here.
|
|
6
|
+
*
|
|
7
|
+
* It is one row, 56px tall, whenever the title has 72px or more beside
|
|
8
|
+
* the controls, at every text size. The bar is chrome: when the reader
|
|
9
|
+
* enlarges text, the room belongs to the page under it. So the controls
|
|
10
|
+
* are sized in px (48px targets), the title grows with the text only up
|
|
11
|
+
* to 1.3 times its size, and a title too long for the room it has is cut
|
|
12
|
+
* with an ellipsis (after two lines with `titleLines={2}`). The heading
|
|
13
|
+
* keeps its full text for a screen reader; keep the visible start of it
|
|
14
|
+
* enough to name the screen.
|
|
15
|
+
*
|
|
16
|
+
* The title never has less than 72px. Where the back control, `leading`
|
|
17
|
+
* and the actions leave it less (a wide chip in `leading`, a very narrow
|
|
18
|
+
* bar), it takes a second row of its own, as wide as the bar, and they
|
|
19
|
+
* stay together on the first.
|
|
20
|
+
*/
|
|
21
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, "class" | "title"> & {
|
|
22
|
+
/** The name of the screen, as a heading. */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** Heading level of the title. 1 when the bar names the page. */
|
|
25
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
26
|
+
/** Shows the back control as a link to this address. */
|
|
27
|
+
backHref?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Called when the back control is activated. On its own it makes the
|
|
30
|
+
* control a button; with `backHref` it runs before the link is
|
|
31
|
+
* followed, and `event.preventDefault()` stops that.
|
|
32
|
+
*/
|
|
33
|
+
onback?: (event: MouseEvent) => void;
|
|
34
|
+
/** Accessible name of the back control. */
|
|
35
|
+
backLabel?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Hides the bar while the page scrolls down and brings it back as
|
|
38
|
+
* soon as it scrolls up. It follows the nearest scrolling ancestor
|
|
39
|
+
* (the one in `AppShell`), or the window. The bar never hides while
|
|
40
|
+
* keyboard focus is inside it, and focus arriving from the keyboard
|
|
41
|
+
* brings it back. Focus left behind by a tap or a click does not hold
|
|
42
|
+
* it: it hides as usual, and the next key press brings it back.
|
|
43
|
+
*/
|
|
44
|
+
collapseOnScroll?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* How many lines the title may take before it is cut with an
|
|
47
|
+
* ellipsis. The bar is taller only while the second line is in use.
|
|
48
|
+
*/
|
|
49
|
+
titleLines?: 1 | 2;
|
|
50
|
+
/**
|
|
51
|
+
* Before the title, after the back control: a logo, an avatar, a
|
|
52
|
+
* chip. Rendered as it is, a child of the bar's row. It keeps its
|
|
53
|
+
* width; when that leaves the title under 72px the title moves to a
|
|
54
|
+
* row of its own, and a `leading` wider than the row is held to the
|
|
55
|
+
* row's width.
|
|
56
|
+
*/
|
|
57
|
+
leading?: Snippet;
|
|
58
|
+
/**
|
|
59
|
+
* After the title. At most two icon buttons, at `size="lg"` so each is
|
|
60
|
+
* a 48px target; put anything more in a menu. Their size does not
|
|
61
|
+
* grow with the text size here: inside the bar the spacing scale is
|
|
62
|
+
* in px.
|
|
63
|
+
*/
|
|
64
|
+
actions?: Snippet;
|
|
65
|
+
class?: string;
|
|
66
|
+
};
|
|
67
|
+
declare const AppBar: import("svelte").Component<Props, {}, "">;
|
|
68
|
+
type AppBar = ReturnType<typeof AppBar>;
|
|
69
|
+
export default AppBar;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { mergeStrings } from "../util/ready-made-strings.js";
|
|
3
|
+
import { zabiStringsFor } from "../util/zabi-strings.js";
|
|
4
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
5
|
+
import Avatar from "../atoms/Avatar.svelte";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_AVATAR_GROUP_STRINGS,
|
|
8
|
+
splitGroup,
|
|
9
|
+
type AvatarGroupStrings,
|
|
10
|
+
type AvatarPerson,
|
|
11
|
+
type AvatarSize,
|
|
12
|
+
} from "../util/avatar.js";
|
|
13
|
+
import { cn } from "../util/cn.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A row of overlapping avatars: who is going, the members, who rated.
|
|
17
|
+
*
|
|
18
|
+
* It is a list, each avatar named by its person. Past `max` the rest
|
|
19
|
+
* stand behind a "+3" of the same size and shape. Not interactive.
|
|
20
|
+
*
|
|
21
|
+
* Between two avatars is a ring in the colour of the surface under the
|
|
22
|
+
* group. It cannot read that colour, so it is the raised surface (a
|
|
23
|
+
* card) unless the group is told otherwise:
|
|
24
|
+
*
|
|
25
|
+
* ```svelte
|
|
26
|
+
* <AvatarGroup {people} style="--zabi-avatar-ring: var(--color-surface-base)" />
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Other attributes (`id`, `data-*`, `style`, ...) land on the list.
|
|
30
|
+
*/
|
|
31
|
+
type Props = Omit<HTMLAttributes<HTMLUListElement>, "class" | "aria-label"> & {
|
|
32
|
+
/** The people, in the order they are shown. */
|
|
33
|
+
people: AvatarPerson[];
|
|
34
|
+
/**
|
|
35
|
+
* How many are shown before the rest become "+N". Below 1 is 1. One
|
|
36
|
+
* person too many is shown and not counted: there is no "+1".
|
|
37
|
+
*/
|
|
38
|
+
max?: number;
|
|
39
|
+
/** Diameter of each avatar: 24, 32 and 48px. */
|
|
40
|
+
size?: "sm" | "md" | "lg";
|
|
41
|
+
/** Accessible name of the list ("Who's going"). */
|
|
42
|
+
label?: string;
|
|
43
|
+
/** Overrides for the built-in strings. */
|
|
44
|
+
strings?: Partial<AvatarGroupStrings>;
|
|
45
|
+
/** The language the initials are worked out in; passed to each Avatar. Without it, the page's. */
|
|
46
|
+
locale?: string;
|
|
47
|
+
/** Extra classes for the list. */
|
|
48
|
+
class?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let {
|
|
52
|
+
people,
|
|
53
|
+
max = 4,
|
|
54
|
+
size = "md",
|
|
55
|
+
label,
|
|
56
|
+
strings,
|
|
57
|
+
locale,
|
|
58
|
+
class: className = "",
|
|
59
|
+
...restProps
|
|
60
|
+
}: Props = $props();
|
|
61
|
+
|
|
62
|
+
/** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
|
|
63
|
+
const provided = zabiStringsFor("avatarGroup");
|
|
64
|
+
const text = $derived(mergeStrings(DEFAULT_AVATAR_GROUP_STRINGS, provided(), strings));
|
|
65
|
+
const split = $derived(splitGroup(people.length, max));
|
|
66
|
+
const shown = $derived(people.slice(0, split.shown));
|
|
67
|
+
const hiddenNames = $derived(people.slice(split.shown).map((person) => person.name));
|
|
68
|
+
|
|
69
|
+
// A quarter of the circle lies under the next one.
|
|
70
|
+
const OVERLAP: Record<AvatarSize, string> = {
|
|
71
|
+
sm: "-ms-[6px]",
|
|
72
|
+
md: "-ms-[8px]",
|
|
73
|
+
lg: "-ms-[12px]",
|
|
74
|
+
};
|
|
75
|
+
const CHIP: Record<AvatarSize, string> = {
|
|
76
|
+
sm: "size-[24px] text-[10px]",
|
|
77
|
+
md: "size-[32px] text-[12px]",
|
|
78
|
+
lg: "size-[48px] text-[16px]",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** The ring that parts one circle from the next. A shadow, so it takes no room; forced colours draw the avatars' own borders instead. */
|
|
82
|
+
const ring = "rounded-pill shadow-[0_0_0_2px_var(--zabi-avatar-ring,var(--color-surface-raised))]";
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<!-- `role`, because a list without bullets loses its role in Safari. -->
|
|
86
|
+
<ul
|
|
87
|
+
class={cn("m-0 flex list-none items-center p-0", className)}
|
|
88
|
+
role="list"
|
|
89
|
+
aria-label={label || undefined}
|
|
90
|
+
data-avatar-group
|
|
91
|
+
data-size={size}
|
|
92
|
+
{...restProps}
|
|
93
|
+
>
|
|
94
|
+
{#each shown as person, index (index)}
|
|
95
|
+
<li class={cn("flex", index > 0 && (OVERLAP[size] ?? OVERLAP.md))}>
|
|
96
|
+
<Avatar name={person.name} src={person.src} {size} {locale} class={ring} />
|
|
97
|
+
</li>
|
|
98
|
+
{/each}
|
|
99
|
+
{#if split.hidden > 0}
|
|
100
|
+
<li class={cn("flex", OVERLAP[size] ?? OVERLAP.md)}>
|
|
101
|
+
<!-- The same circle as an avatar, in the neutral pair: it is a count, not a person. -->
|
|
102
|
+
<span
|
|
103
|
+
class={cn(
|
|
104
|
+
"inline-flex shrink-0 items-center justify-center border border-transparent bg-neutral-subtle leading-none font-semibold text-neutral-text tabular-nums select-none",
|
|
105
|
+
CHIP[size] ?? CHIP.md,
|
|
106
|
+
ring,
|
|
107
|
+
)}
|
|
108
|
+
role="img"
|
|
109
|
+
aria-label={text.more(split.hidden, hiddenNames)}
|
|
110
|
+
data-avatar-more
|
|
111
|
+
>
|
|
112
|
+
<span aria-hidden="true">+{split.hidden}</span>
|
|
113
|
+
</span>
|
|
114
|
+
</li>
|
|
115
|
+
{/if}
|
|
116
|
+
</ul>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import { type AvatarGroupStrings, type AvatarPerson } from "../util/avatar.js";
|
|
3
|
+
/**
|
|
4
|
+
* A row of overlapping avatars: who is going, the members, who rated.
|
|
5
|
+
*
|
|
6
|
+
* It is a list, each avatar named by its person. Past `max` the rest
|
|
7
|
+
* stand behind a "+3" of the same size and shape. Not interactive.
|
|
8
|
+
*
|
|
9
|
+
* Between two avatars is a ring in the colour of the surface under the
|
|
10
|
+
* group. It cannot read that colour, so it is the raised surface (a
|
|
11
|
+
* card) unless the group is told otherwise:
|
|
12
|
+
*
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <AvatarGroup {people} style="--zabi-avatar-ring: var(--color-surface-base)" />
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Other attributes (`id`, `data-*`, `style`, ...) land on the list.
|
|
18
|
+
*/
|
|
19
|
+
type Props = Omit<HTMLAttributes<HTMLUListElement>, "class" | "aria-label"> & {
|
|
20
|
+
/** The people, in the order they are shown. */
|
|
21
|
+
people: AvatarPerson[];
|
|
22
|
+
/**
|
|
23
|
+
* How many are shown before the rest become "+N". Below 1 is 1. One
|
|
24
|
+
* person too many is shown and not counted: there is no "+1".
|
|
25
|
+
*/
|
|
26
|
+
max?: number;
|
|
27
|
+
/** Diameter of each avatar: 24, 32 and 48px. */
|
|
28
|
+
size?: "sm" | "md" | "lg";
|
|
29
|
+
/** Accessible name of the list ("Who's going"). */
|
|
30
|
+
label?: string;
|
|
31
|
+
/** Overrides for the built-in strings. */
|
|
32
|
+
strings?: Partial<AvatarGroupStrings>;
|
|
33
|
+
/** The language the initials are worked out in; passed to each Avatar. Without it, the page's. */
|
|
34
|
+
locale?: string;
|
|
35
|
+
/** Extra classes for the list. */
|
|
36
|
+
class?: string;
|
|
37
|
+
};
|
|
38
|
+
declare const AvatarGroup: import("svelte").Component<Props, {}, "">;
|
|
39
|
+
type AvatarGroup = ReturnType<typeof AvatarGroup>;
|
|
40
|
+
export default AvatarGroup;
|