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,597 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { mergeStrings } from "../util/ready-made-strings.js";
|
|
3
|
+
import { zabiStringsFor } from "../util/zabi-strings.js";
|
|
4
|
+
import { tick } from "svelte";
|
|
5
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
6
|
+
import ChevronLeft from "@lucide/svelte/icons/chevron-left";
|
|
7
|
+
import ChevronRight from "@lucide/svelte/icons/chevron-right";
|
|
8
|
+
import IconButton from "../atoms/IconButton.svelte";
|
|
9
|
+
import {
|
|
10
|
+
calendarDayName,
|
|
11
|
+
calendarKeyTarget,
|
|
12
|
+
DEFAULT_CALENDAR_STRINGS,
|
|
13
|
+
groupEventsByDate,
|
|
14
|
+
MAX_EVENT_DOTS,
|
|
15
|
+
type CalendarEvent,
|
|
16
|
+
type CalendarStrings,
|
|
17
|
+
} from "../util/calendar.js";
|
|
18
|
+
import { cn } from "../util/cn.js";
|
|
19
|
+
import {
|
|
20
|
+
addMonthsToMonth,
|
|
21
|
+
clampDate,
|
|
22
|
+
daysInMonth,
|
|
23
|
+
formatDate,
|
|
24
|
+
isIsoDate,
|
|
25
|
+
monthGrid,
|
|
26
|
+
monthOf,
|
|
27
|
+
normaliseWeekStart,
|
|
28
|
+
parseIsoDate,
|
|
29
|
+
parseIsoMonth,
|
|
30
|
+
todayIso,
|
|
31
|
+
todayIsoUtc,
|
|
32
|
+
toIsoDate,
|
|
33
|
+
weekdayOrder,
|
|
34
|
+
} from "../util/date.js";
|
|
35
|
+
import { isRtl } from "../util/radio-keys.js";
|
|
36
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One month as a grid of days, with a dot on the days that have events.
|
|
40
|
+
* Pick a day and list what happens on it below:
|
|
41
|
+
*
|
|
42
|
+
* ```svelte
|
|
43
|
+
* <Calendar bind:selected {events} locale="sv" />
|
|
44
|
+
* {#each events.filter((event) => event.date === selected) as event}
|
|
45
|
+
* <p>{event.label}</p>
|
|
46
|
+
* {/each}
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Dates are strings: `YYYY-MM-DD` for a day, `YYYY-MM` for a month. A
|
|
50
|
+
* press on the selected day changes nothing: the selection is never
|
|
51
|
+
* cleared by pressing it again.
|
|
52
|
+
*
|
|
53
|
+
* It is as wide as its container, seven equal columns: give it a width
|
|
54
|
+
* where the container is wide. Days are at least 44px tall, and at least
|
|
55
|
+
* 40px wide down to a 320px screen with 16px margins.
|
|
56
|
+
* The weekday names are three letters ("Mon"), or one where the text is
|
|
57
|
+
* enlarged so far that three would not fit their column.
|
|
58
|
+
*
|
|
59
|
+
* On the server, and until it runs in the browser, it does not know what
|
|
60
|
+
* day it is: nothing is marked as today, and a calendar given neither
|
|
61
|
+
* `month` nor `selected` shows the month it is in UTC. Pass `month` to
|
|
62
|
+
* decide the first month yourself, and `locale` so the names are the same
|
|
63
|
+
* on the server and in the browser.
|
|
64
|
+
*
|
|
65
|
+
* Other attributes (`id`, `data-*`, ...) land on the host element.
|
|
66
|
+
*/
|
|
67
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "onselect"> & {
|
|
68
|
+
/**
|
|
69
|
+
* The month shown, as `YYYY-MM`. Supports `bind:month`. Left out, it
|
|
70
|
+
* starts at the month of `selected`, or of today.
|
|
71
|
+
*/
|
|
72
|
+
month?: string;
|
|
73
|
+
/** The selected day, as `YYYY-MM-DD`, or null. Supports `bind:selected`. */
|
|
74
|
+
selected?: string | null;
|
|
75
|
+
/** What happens on which day. A day shows a dot per event, three at most. */
|
|
76
|
+
events?: CalendarEvent[];
|
|
77
|
+
/** First day of the week: 0 for Sunday to 6 for Saturday. */
|
|
78
|
+
weekStartsOn?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Language of the month and day names, as a BCP 47 tag (`sv`, `en-GB`).
|
|
81
|
+
* Left out, it is the page's language (`<html lang>`), then the
|
|
82
|
+
* browser's; on the server, English.
|
|
83
|
+
*
|
|
84
|
+
* It translates only what comes from the calendar of that language:
|
|
85
|
+
* the month, the weekdays and the dates. The words the component
|
|
86
|
+
* adds ("Previous month", "today", "selected", "2 events: ...") stay
|
|
87
|
+
* English until you pass them in `strings`. Translate both.
|
|
88
|
+
*/
|
|
89
|
+
locale?: string;
|
|
90
|
+
/** Earliest day that can be selected, as `YYYY-MM-DD`. Months before it cannot be reached. */
|
|
91
|
+
min?: string;
|
|
92
|
+
/** Latest day that can be selected, as `YYYY-MM-DD`. Months after it cannot be reached. */
|
|
93
|
+
max?: string;
|
|
94
|
+
/** Return true for a day that cannot be selected. It can still be focused and read. */
|
|
95
|
+
isDateDisabled?: (date: string) => boolean;
|
|
96
|
+
/** The words the calendar says; replace any of them to translate. */
|
|
97
|
+
strings?: Partial<CalendarStrings>;
|
|
98
|
+
/** Called with the day when the user selects another one. */
|
|
99
|
+
onselect?: (date: string) => void;
|
|
100
|
+
/** Called with the month when the user moves to another one. */
|
|
101
|
+
onmonthchange?: (month: string) => void;
|
|
102
|
+
/** Extra classes for the host element. */
|
|
103
|
+
class?: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
let {
|
|
107
|
+
month = $bindable(),
|
|
108
|
+
selected = $bindable<Exclude<Props["selected"], undefined>>(),
|
|
109
|
+
events = [],
|
|
110
|
+
weekStartsOn = 1,
|
|
111
|
+
locale,
|
|
112
|
+
min,
|
|
113
|
+
max,
|
|
114
|
+
isDateDisabled,
|
|
115
|
+
strings,
|
|
116
|
+
onselect,
|
|
117
|
+
onmonthchange,
|
|
118
|
+
class: className = "",
|
|
119
|
+
...restProps
|
|
120
|
+
}: Props = $props();
|
|
121
|
+
|
|
122
|
+
// No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
|
|
123
|
+
// one that has a fallback (`props_invalid_value`), and a page that throws
|
|
124
|
+
// while it hydrates never becomes interactive. The default is applied
|
|
125
|
+
// here instead: at once, for the server and the first render, and again
|
|
126
|
+
// whenever a parent hands back `undefined`.
|
|
127
|
+
const applyDefaults = () => {
|
|
128
|
+
if (selected === undefined) selected = null;
|
|
129
|
+
};
|
|
130
|
+
applyDefaults();
|
|
131
|
+
$effect.pre(applyDefaults);
|
|
132
|
+
|
|
133
|
+
const titleId = generateId("calendar-title");
|
|
134
|
+
/** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
|
|
135
|
+
const provided = zabiStringsFor("calendar");
|
|
136
|
+
const text = $derived(mergeStrings(DEFAULT_CALENDAR_STRINGS, provided(), strings));
|
|
137
|
+
|
|
138
|
+
let grid: HTMLTableElement | undefined = $state();
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Null on the server and in the first render in the browser, so the two
|
|
142
|
+
* agree whatever the time zones; the real day arrives once mounted.
|
|
143
|
+
*/
|
|
144
|
+
let today = $state<string | null>(null);
|
|
145
|
+
/** The page's or the browser's language, known once mounted. */
|
|
146
|
+
let detectedLocale = $state<string | undefined>(undefined);
|
|
147
|
+
/**
|
|
148
|
+
* The month to show when neither `month` nor `selected` says. UTC first:
|
|
149
|
+
* the server and the browser compute the same one. The user's own month
|
|
150
|
+
* replaces it once mounted, which differs only around the turn of a month.
|
|
151
|
+
*/
|
|
152
|
+
let fallbackMonth = $state(monthOf(todayIsoUtc())!);
|
|
153
|
+
|
|
154
|
+
$effect(() => {
|
|
155
|
+
const now = todayIso();
|
|
156
|
+
today = now;
|
|
157
|
+
fallbackMonth = monthOf(now)!;
|
|
158
|
+
detectedLocale = document.documentElement.lang || navigator.language || undefined;
|
|
159
|
+
// A calendar left open over midnight: look again when it is looked at again.
|
|
160
|
+
const refresh = () => {
|
|
161
|
+
if (document.visibilityState === "visible") today = todayIso();
|
|
162
|
+
};
|
|
163
|
+
document.addEventListener("visibilitychange", refresh);
|
|
164
|
+
return () => document.removeEventListener("visibilitychange", refresh);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const language = $derived(locale ?? detectedLocale ?? "en");
|
|
168
|
+
const weekStart = $derived(normaliseWeekStart(weekStartsOn));
|
|
169
|
+
const selectedDate = $derived(isIsoDate(selected) ? selected : null);
|
|
170
|
+
const viewMonth = $derived(
|
|
171
|
+
parseIsoMonth(month) ? (month as string) : (monthOf(selectedDate) ?? fallbackMonth),
|
|
172
|
+
);
|
|
173
|
+
const weeks = $derived(monthGrid(viewMonth, weekStart));
|
|
174
|
+
const eventsByDate = $derived(groupEventsByDate(events));
|
|
175
|
+
|
|
176
|
+
const title = $derived(
|
|
177
|
+
formatDate(`${viewMonth}-01`, language, { month: "long", year: "numeric" }),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
/** 1 January 2023 was a Sunday: that week gives the names of the seven days. */
|
|
181
|
+
const weekdays = $derived(
|
|
182
|
+
weekdayOrder(weekStart).map((weekday) => {
|
|
183
|
+
const sample = toIsoDate(2023, 1, 1 + weekday);
|
|
184
|
+
return {
|
|
185
|
+
short: formatDate(sample, language, { weekday: "short" }),
|
|
186
|
+
narrow: formatDate(sample, language, { weekday: "narrow" }),
|
|
187
|
+
long: formatDate(sample, language, { weekday: "long" }),
|
|
188
|
+
};
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
function isDisabled(date: string): boolean {
|
|
193
|
+
if (isIsoDate(min) && date < min) return true;
|
|
194
|
+
if (isIsoDate(max) && date > max) return true;
|
|
195
|
+
return isDateDisabled?.(date) ?? false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const previous = $derived(addMonthsToMonth(viewMonth, -1));
|
|
199
|
+
const next = $derived(addMonthsToMonth(viewMonth, 1));
|
|
200
|
+
/** A month can be reached when at least one of its days is within `min` and `max`. */
|
|
201
|
+
const canGoPrevious = $derived.by(() => {
|
|
202
|
+
const parts = parseIsoMonth(previous);
|
|
203
|
+
if (!parts) return false;
|
|
204
|
+
const last = toIsoDate(parts.year, parts.month, daysInMonth(parts.year, parts.month));
|
|
205
|
+
return !(isIsoDate(min) && last < min);
|
|
206
|
+
});
|
|
207
|
+
const canGoNext = $derived(!!next && !(isIsoDate(max) && `${next}-01` > max));
|
|
208
|
+
|
|
209
|
+
/** The day the keyboard was last on. It keeps the one Tab stop where the user left it. */
|
|
210
|
+
let focused = $state<string | null>(null);
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The one day in the grid that takes Tab: where the keyboard was, else
|
|
214
|
+
* the selected day, else today, else the first day that can be selected;
|
|
215
|
+
* always a day of the month shown.
|
|
216
|
+
*/
|
|
217
|
+
const tabStop = $derived.by(() => {
|
|
218
|
+
const inView = (date: string | null) => !!date && monthOf(date) === viewMonth;
|
|
219
|
+
if (inView(focused)) return focused;
|
|
220
|
+
if (inView(selectedDate)) return selectedDate;
|
|
221
|
+
if (inView(today)) return today;
|
|
222
|
+
const days = weeks.flat().filter((date): date is string => date !== null);
|
|
223
|
+
return days.find((date) => !isDisabled(date)) ?? days[0] ?? null;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
function showMonth(nextMonth: string | null) {
|
|
227
|
+
if (!nextMonth || nextMonth === viewMonth) return;
|
|
228
|
+
month = nextMonth;
|
|
229
|
+
onmonthchange?.(nextMonth);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** A press on the selected day is not a change: a single selection never clears. */
|
|
233
|
+
function select(date: string) {
|
|
234
|
+
if (isDisabled(date) || date === selectedDate) return;
|
|
235
|
+
selected = date;
|
|
236
|
+
onselect?.(date);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function focusDay(date: string) {
|
|
240
|
+
focused = date;
|
|
241
|
+
showMonth(monthOf(date));
|
|
242
|
+
await tick();
|
|
243
|
+
grid?.querySelector<HTMLElement>(`[data-date="${date}"]`)?.focus();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
247
|
+
const origin = (event.target as Element | null)?.closest<HTMLElement>("[data-date]");
|
|
248
|
+
const from = origin?.dataset.date;
|
|
249
|
+
if (!from || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
250
|
+
const target = calendarKeyTarget(event.key, from, {
|
|
251
|
+
shift: event.shiftKey,
|
|
252
|
+
rtl: isRtl(event.currentTarget as Element),
|
|
253
|
+
weekStartsOn: weekStart,
|
|
254
|
+
});
|
|
255
|
+
if (!target) return;
|
|
256
|
+
// The key is the grid's either way: the page must not scroll under it.
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
// Not past the first or last day that may be chosen.
|
|
259
|
+
const allowed = clampDate(target, min, max);
|
|
260
|
+
if (allowed !== from) void focusDay(allowed);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const fullDate = (date: string) =>
|
|
264
|
+
formatDate(date, language, {
|
|
265
|
+
weekday: "long",
|
|
266
|
+
day: "numeric",
|
|
267
|
+
month: "long",
|
|
268
|
+
year: "numeric",
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
/** `aria-disabled`, not `disabled`: a button that turns disabled under the finger or the key would drop focus. */
|
|
272
|
+
const stepClasses = (available: boolean) =>
|
|
273
|
+
available ? "" : "cursor-not-allowed opacity-50 hover:bg-transparent active:bg-transparent active:scale-100";
|
|
274
|
+
</script>
|
|
275
|
+
|
|
276
|
+
<div class={cn("zabi-calendar w-full", className)} data-month={viewMonth} {...restProps}>
|
|
277
|
+
<div class="flex items-center justify-between gap-[8px] pb-2">
|
|
278
|
+
<IconButton
|
|
279
|
+
variant="ghost"
|
|
280
|
+
label={text.previousMonth}
|
|
281
|
+
aria-disabled={canGoPrevious ? undefined : "true"}
|
|
282
|
+
class={stepClasses(canGoPrevious)}
|
|
283
|
+
onclick={() => canGoPrevious && showMonth(previous)}
|
|
284
|
+
>
|
|
285
|
+
<ChevronLeft size={20} class="rtl:rotate-180" aria-hidden="true" />
|
|
286
|
+
</IconButton>
|
|
287
|
+
<!-- A live region: a change of month is read out, also when it comes
|
|
288
|
+
from the arrow keys in the grid. -->
|
|
289
|
+
<div
|
|
290
|
+
id={titleId}
|
|
291
|
+
class="min-w-0 text-center text-base font-semibold text-headline first-letter:uppercase"
|
|
292
|
+
aria-live="polite"
|
|
293
|
+
aria-atomic="true"
|
|
294
|
+
>
|
|
295
|
+
{title}
|
|
296
|
+
</div>
|
|
297
|
+
<IconButton
|
|
298
|
+
variant="ghost"
|
|
299
|
+
label={text.nextMonth}
|
|
300
|
+
aria-disabled={canGoNext ? undefined : "true"}
|
|
301
|
+
class={stepClasses(canGoNext)}
|
|
302
|
+
onclick={() => canGoNext && showMonth(next)}
|
|
303
|
+
>
|
|
304
|
+
<ChevronRight size={20} class="rtl:rotate-180" aria-hidden="true" />
|
|
305
|
+
</IconButton>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_to_interactive_role -->
|
|
309
|
+
<table
|
|
310
|
+
bind:this={grid}
|
|
311
|
+
role="grid"
|
|
312
|
+
aria-labelledby={titleId}
|
|
313
|
+
class="w-full table-fixed border-collapse"
|
|
314
|
+
onkeydown={handleKeydown}
|
|
315
|
+
>
|
|
316
|
+
<thead>
|
|
317
|
+
<tr>
|
|
318
|
+
{#each weekdays as weekday (weekday.long)}
|
|
319
|
+
<th scope="col" abbr={weekday.long} class="h-8 p-0 text-xs font-medium text-description">
|
|
320
|
+
<span class="weekday" aria-hidden="true">
|
|
321
|
+
<span class="weekday-short">{weekday.short}</span>
|
|
322
|
+
<span class="weekday-narrow">{weekday.narrow}</span>
|
|
323
|
+
</span>
|
|
324
|
+
<span class="sr-only">{weekday.long}</span>
|
|
325
|
+
</th>
|
|
326
|
+
{/each}
|
|
327
|
+
</tr>
|
|
328
|
+
</thead>
|
|
329
|
+
<tbody>
|
|
330
|
+
{#each weeks as week, index (index)}
|
|
331
|
+
<tr>
|
|
332
|
+
{#each week as date, column (column)}
|
|
333
|
+
{#if date}
|
|
334
|
+
{@const dayEvents = eventsByDate.get(date) ?? []}
|
|
335
|
+
{@const isToday = date === today}
|
|
336
|
+
{@const isSelected = date === selectedDate}
|
|
337
|
+
{@const disabled = isDisabled(date)}
|
|
338
|
+
<td role="gridcell" class="p-0" aria-selected={isSelected ? "true" : undefined}>
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
class="day"
|
|
342
|
+
data-date={date}
|
|
343
|
+
data-today={isToday ? "" : undefined}
|
|
344
|
+
data-selected={isSelected ? "" : undefined}
|
|
345
|
+
tabindex={date === tabStop ? 0 : -1}
|
|
346
|
+
aria-label={calendarDayName(
|
|
347
|
+
fullDate(date),
|
|
348
|
+
{
|
|
349
|
+
today: isToday,
|
|
350
|
+
selected: isSelected,
|
|
351
|
+
disabled,
|
|
352
|
+
events: dayEvents,
|
|
353
|
+
},
|
|
354
|
+
text,
|
|
355
|
+
)}
|
|
356
|
+
aria-current={isToday ? "date" : undefined}
|
|
357
|
+
aria-disabled={disabled ? "true" : undefined}
|
|
358
|
+
onclick={() => select(date)}
|
|
359
|
+
onfocus={() => (focused = date)}
|
|
360
|
+
>
|
|
361
|
+
<span class="face">
|
|
362
|
+
<span>{parseIsoDate(date)?.day}</span>
|
|
363
|
+
{#if dayEvents.length > 0}
|
|
364
|
+
<span class="dots" aria-hidden="true">
|
|
365
|
+
{#each dayEvents.slice(0, MAX_EVENT_DOTS) as event, dot (dot)}
|
|
366
|
+
<span class="dot" data-tone={event.tone ?? "default"}></span>
|
|
367
|
+
{/each}
|
|
368
|
+
</span>
|
|
369
|
+
{/if}
|
|
370
|
+
</span>
|
|
371
|
+
</button>
|
|
372
|
+
</td>
|
|
373
|
+
{:else}
|
|
374
|
+
<!-- A place before the 1st or after the last day: days of
|
|
375
|
+
other months are not shown, so each day is in the grid once. -->
|
|
376
|
+
<td role="gridcell" class="p-0"></td>
|
|
377
|
+
{/if}
|
|
378
|
+
{/each}
|
|
379
|
+
</tr>
|
|
380
|
+
{/each}
|
|
381
|
+
</tbody>
|
|
382
|
+
</table>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<style>
|
|
386
|
+
/*
|
|
387
|
+
* The states of a day hang off the button (`data-selected`, `data-today`,
|
|
388
|
+
* `aria-disabled`) and are drawn on the face inside it, which utilities
|
|
389
|
+
* could only reach through a group variant per declaration.
|
|
390
|
+
*
|
|
391
|
+
* The button fills its cell and has square corners, so the cells take
|
|
392
|
+
* touches edge to edge, corners included, with no gaps between them. The
|
|
393
|
+
* face is drawn 2px inside it, so two marked days side by side do not run
|
|
394
|
+
* into each other. The focus ring is drawn around the face as well: on
|
|
395
|
+
* the square button it would not follow the shape that is seen.
|
|
396
|
+
*/
|
|
397
|
+
/*
|
|
398
|
+
* The weekday names follow the text size, the columns do not: with the
|
|
399
|
+
* text enlarged to 200% on a 320px screen "Wed" is wider than its column
|
|
400
|
+
* and runs into "Thu". In a column under 1.9rem the name is one letter
|
|
401
|
+
* instead. The full name is read out either way.
|
|
402
|
+
*
|
|
403
|
+
* The query is on the name's own box, as wide as the column: the table
|
|
404
|
+
* sets that width, not the name, so containing it changes no layout.
|
|
405
|
+
*/
|
|
406
|
+
.weekday {
|
|
407
|
+
display: block;
|
|
408
|
+
container-type: inline-size;
|
|
409
|
+
}
|
|
410
|
+
.weekday-narrow {
|
|
411
|
+
display: none;
|
|
412
|
+
}
|
|
413
|
+
@container (max-width: 1.9rem) {
|
|
414
|
+
.weekday-short {
|
|
415
|
+
display: none;
|
|
416
|
+
}
|
|
417
|
+
.weekday-narrow {
|
|
418
|
+
display: inline;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.day {
|
|
423
|
+
position: relative;
|
|
424
|
+
display: block;
|
|
425
|
+
width: 100%;
|
|
426
|
+
/* 44px for the finger, in px: it must not shrink. The rem part lets
|
|
427
|
+
the number and the dots under it both fit when the text is enlarged. */
|
|
428
|
+
min-height: max(44px, calc(1.25rem + 24px));
|
|
429
|
+
padding: 0;
|
|
430
|
+
border-radius: 0;
|
|
431
|
+
outline: none;
|
|
432
|
+
background: transparent;
|
|
433
|
+
color: var(--color-body);
|
|
434
|
+
font-size: 0.875rem;
|
|
435
|
+
line-height: 1.25rem;
|
|
436
|
+
cursor: pointer;
|
|
437
|
+
-webkit-tap-highlight-color: transparent;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* Above its neighbours while focused: the ring reaches over their edges. */
|
|
441
|
+
.day:focus-visible {
|
|
442
|
+
z-index: 1;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* The library's focus ring (see `.focus-ring` in app.css), on the face. */
|
|
446
|
+
.day:focus-visible .face {
|
|
447
|
+
box-shadow:
|
|
448
|
+
0 0 0 2px var(--color-focus-ring-offset),
|
|
449
|
+
0 0 0 4px var(--color-focus-ring);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.face {
|
|
453
|
+
position: absolute;
|
|
454
|
+
inset: 2px;
|
|
455
|
+
display: flex;
|
|
456
|
+
align-items: center;
|
|
457
|
+
justify-content: center;
|
|
458
|
+
border-radius: var(--radius-control);
|
|
459
|
+
transition: background-color 150ms;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
@media (hover: hover) {
|
|
463
|
+
.day:not([aria-disabled="true"]):hover .face {
|
|
464
|
+
background-color: var(--color-surface-hover);
|
|
465
|
+
}
|
|
466
|
+
.day[data-selected]:not([aria-disabled="true"]):hover .face {
|
|
467
|
+
background-color: var(--color-action-primary-hover);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.day:not([aria-disabled="true"]):active .face {
|
|
472
|
+
background-color: var(--color-surface-active);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.day[data-selected] .face,
|
|
476
|
+
.day[data-selected]:active .face {
|
|
477
|
+
background-color: var(--color-action-primary);
|
|
478
|
+
color: var(--color-action-primary-text);
|
|
479
|
+
font-weight: 600;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Today: a ring and a heavier number, so it does not rest on colour. On
|
|
483
|
+
the selected day the ring moves inside the fill and takes its text colour. */
|
|
484
|
+
.day[data-today] .face {
|
|
485
|
+
outline: 2px solid var(--color-action-primary);
|
|
486
|
+
outline-offset: -2px;
|
|
487
|
+
font-weight: 700;
|
|
488
|
+
}
|
|
489
|
+
.day[data-today][data-selected] .face {
|
|
490
|
+
outline-color: currentColor;
|
|
491
|
+
outline-offset: -4px;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Struck through as well as grey: the grey alone is a thin difference
|
|
495
|
+
(3:1 against the page), and colour must not be the only sign. */
|
|
496
|
+
.day[aria-disabled="true"] {
|
|
497
|
+
color: var(--color-action-disabled-text);
|
|
498
|
+
cursor: not-allowed;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* On the number itself: the face is positioned out of the flow, and a
|
|
502
|
+
line set on the button is not drawn through what is out of the flow. */
|
|
503
|
+
.day[aria-disabled="true"] .face > span:first-child {
|
|
504
|
+
text-decoration: line-through;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* 5px above the bottom of the face: 4px dots, so 2px at their ends, plus
|
|
508
|
+
those 5px stay within the face's 8px radius. */
|
|
509
|
+
.dots {
|
|
510
|
+
position: absolute;
|
|
511
|
+
bottom: 5px;
|
|
512
|
+
left: 0;
|
|
513
|
+
right: 0;
|
|
514
|
+
display: flex;
|
|
515
|
+
justify-content: center;
|
|
516
|
+
gap: 2px;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.dot {
|
|
520
|
+
width: 4px;
|
|
521
|
+
height: 4px;
|
|
522
|
+
border-radius: var(--radius-pill);
|
|
523
|
+
background-color: var(--color-action-primary);
|
|
524
|
+
}
|
|
525
|
+
.dot[data-tone="success"] {
|
|
526
|
+
background-color: var(--color-success);
|
|
527
|
+
}
|
|
528
|
+
.dot[data-tone="warning"] {
|
|
529
|
+
background-color: var(--color-warning);
|
|
530
|
+
}
|
|
531
|
+
.dot[data-tone="danger"] {
|
|
532
|
+
background-color: var(--color-error);
|
|
533
|
+
}
|
|
534
|
+
.dot[data-tone="accent"] {
|
|
535
|
+
background-color: var(--color-accent);
|
|
536
|
+
}
|
|
537
|
+
/* On the selected day's fill every dot takes the text colour: a tone
|
|
538
|
+
would not show against it. The events are named with the day anyway. */
|
|
539
|
+
.day[data-selected] .dot {
|
|
540
|
+
background-color: currentColor;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
@media (prefers-reduced-motion: reduce) {
|
|
544
|
+
.face {
|
|
545
|
+
transition: none;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* Fills are dropped where colours are forced: the selected day and the
|
|
550
|
+
dots are drawn in system colours instead. */
|
|
551
|
+
@media (forced-colors: active) {
|
|
552
|
+
.day[data-selected] .face,
|
|
553
|
+
.day[data-selected]:active .face {
|
|
554
|
+
forced-color-adjust: none;
|
|
555
|
+
background-color: Highlight;
|
|
556
|
+
color: HighlightText;
|
|
557
|
+
}
|
|
558
|
+
/* A button's text is forced to ButtonText: without this a day that
|
|
559
|
+
cannot be selected looks like one that can. */
|
|
560
|
+
.day[aria-disabled="true"] {
|
|
561
|
+
color: GrayText;
|
|
562
|
+
}
|
|
563
|
+
.day[data-today] .face {
|
|
564
|
+
outline-color: CanvasText;
|
|
565
|
+
}
|
|
566
|
+
.day[data-today][data-selected] .face {
|
|
567
|
+
outline-color: HighlightText;
|
|
568
|
+
}
|
|
569
|
+
/* The face's outline is today's mark, so focus is drawn on the
|
|
570
|
+
button here, inside its edge. */
|
|
571
|
+
.day:focus-visible {
|
|
572
|
+
outline: 2px solid Highlight;
|
|
573
|
+
outline-offset: -2px;
|
|
574
|
+
}
|
|
575
|
+
.day:focus-visible .face {
|
|
576
|
+
box-shadow: none;
|
|
577
|
+
}
|
|
578
|
+
/* `[data-tone]` as well: a tone's own rule would otherwise win. */
|
|
579
|
+
.dot,
|
|
580
|
+
.dot[data-tone] {
|
|
581
|
+
forced-color-adjust: none;
|
|
582
|
+
background-color: CanvasText;
|
|
583
|
+
}
|
|
584
|
+
.day[data-selected] .dot,
|
|
585
|
+
.day[data-selected] .dot[data-tone] {
|
|
586
|
+
background-color: HighlightText;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* The selected day's hover rule is more specific than the one above, and
|
|
591
|
+
with the adjustment off its own blue would show under the pointer. */
|
|
592
|
+
@media (forced-colors: active) and (hover: hover) {
|
|
593
|
+
.day[data-selected]:not([aria-disabled="true"]):hover .face {
|
|
594
|
+
background-color: Highlight;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
</style>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import { type CalendarEvent, type CalendarStrings } from "../util/calendar.js";
|
|
3
|
+
/**
|
|
4
|
+
* One month as a grid of days, with a dot on the days that have events.
|
|
5
|
+
* Pick a day and list what happens on it below:
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <Calendar bind:selected {events} locale="sv" />
|
|
9
|
+
* {#each events.filter((event) => event.date === selected) as event}
|
|
10
|
+
* <p>{event.label}</p>
|
|
11
|
+
* {/each}
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Dates are strings: `YYYY-MM-DD` for a day, `YYYY-MM` for a month. A
|
|
15
|
+
* press on the selected day changes nothing: the selection is never
|
|
16
|
+
* cleared by pressing it again.
|
|
17
|
+
*
|
|
18
|
+
* It is as wide as its container, seven equal columns: give it a width
|
|
19
|
+
* where the container is wide. Days are at least 44px tall, and at least
|
|
20
|
+
* 40px wide down to a 320px screen with 16px margins.
|
|
21
|
+
* The weekday names are three letters ("Mon"), or one where the text is
|
|
22
|
+
* enlarged so far that three would not fit their column.
|
|
23
|
+
*
|
|
24
|
+
* On the server, and until it runs in the browser, it does not know what
|
|
25
|
+
* day it is: nothing is marked as today, and a calendar given neither
|
|
26
|
+
* `month` nor `selected` shows the month it is in UTC. Pass `month` to
|
|
27
|
+
* decide the first month yourself, and `locale` so the names are the same
|
|
28
|
+
* on the server and in the browser.
|
|
29
|
+
*
|
|
30
|
+
* Other attributes (`id`, `data-*`, ...) land on the host element.
|
|
31
|
+
*/
|
|
32
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "onselect"> & {
|
|
33
|
+
/**
|
|
34
|
+
* The month shown, as `YYYY-MM`. Supports `bind:month`. Left out, it
|
|
35
|
+
* starts at the month of `selected`, or of today.
|
|
36
|
+
*/
|
|
37
|
+
month?: string;
|
|
38
|
+
/** The selected day, as `YYYY-MM-DD`, or null. Supports `bind:selected`. */
|
|
39
|
+
selected?: string | null;
|
|
40
|
+
/** What happens on which day. A day shows a dot per event, three at most. */
|
|
41
|
+
events?: CalendarEvent[];
|
|
42
|
+
/** First day of the week: 0 for Sunday to 6 for Saturday. */
|
|
43
|
+
weekStartsOn?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Language of the month and day names, as a BCP 47 tag (`sv`, `en-GB`).
|
|
46
|
+
* Left out, it is the page's language (`<html lang>`), then the
|
|
47
|
+
* browser's; on the server, English.
|
|
48
|
+
*
|
|
49
|
+
* It translates only what comes from the calendar of that language:
|
|
50
|
+
* the month, the weekdays and the dates. The words the component
|
|
51
|
+
* adds ("Previous month", "today", "selected", "2 events: ...") stay
|
|
52
|
+
* English until you pass them in `strings`. Translate both.
|
|
53
|
+
*/
|
|
54
|
+
locale?: string;
|
|
55
|
+
/** Earliest day that can be selected, as `YYYY-MM-DD`. Months before it cannot be reached. */
|
|
56
|
+
min?: string;
|
|
57
|
+
/** Latest day that can be selected, as `YYYY-MM-DD`. Months after it cannot be reached. */
|
|
58
|
+
max?: string;
|
|
59
|
+
/** Return true for a day that cannot be selected. It can still be focused and read. */
|
|
60
|
+
isDateDisabled?: (date: string) => boolean;
|
|
61
|
+
/** The words the calendar says; replace any of them to translate. */
|
|
62
|
+
strings?: Partial<CalendarStrings>;
|
|
63
|
+
/** Called with the day when the user selects another one. */
|
|
64
|
+
onselect?: (date: string) => void;
|
|
65
|
+
/** Called with the month when the user moves to another one. */
|
|
66
|
+
onmonthchange?: (month: string) => void;
|
|
67
|
+
/** Extra classes for the host element. */
|
|
68
|
+
class?: string;
|
|
69
|
+
};
|
|
70
|
+
declare const Calendar: import("svelte").Component<Props, {}, "month" | "selected">;
|
|
71
|
+
type Calendar = ReturnType<typeof Calendar>;
|
|
72
|
+
export default Calendar;
|