zabi-components 8.0.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2935 -0
- package/README.md +243 -10
- package/THEME.md +162 -332
- package/THEMING.md +1419 -259
- package/dist/atoms/ActionPanel.svelte +15 -5
- package/dist/atoms/Avatar.svelte +120 -0
- package/dist/atoms/Avatar.svelte.d.ts +46 -0
- package/dist/atoms/Badge.svelte +71 -13
- package/dist/atoms/Badge.svelte.d.ts +8 -2
- package/dist/atoms/Block.svelte +74 -0
- package/dist/atoms/Block.svelte.d.ts +29 -0
- package/dist/atoms/Button.svelte +217 -30
- package/dist/atoms/Button.svelte.d.ts +33 -4
- package/dist/atoms/Card.svelte +182 -26
- package/dist/atoms/Card.svelte.d.ts +55 -3
- package/dist/atoms/CardHeader.svelte +16 -4
- package/dist/atoms/Checkbox.svelte +81 -21
- package/dist/atoms/Checkbox.svelte.d.ts +31 -2
- package/dist/atoms/Chip.svelte +351 -0
- package/dist/atoms/Chip.svelte.d.ts +57 -0
- package/dist/atoms/CodeBlock.svelte +20 -2
- package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
- package/dist/atoms/CodeField.svelte +385 -0
- package/dist/atoms/CodeField.svelte.d.ts +61 -0
- package/dist/atoms/ColorPicker.svelte +260 -49
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
- package/dist/atoms/Container.svelte +16 -8
- package/dist/atoms/DateField.svelte +145 -0
- package/dist/atoms/DateField.svelte.d.ts +81 -0
- package/dist/atoms/FieldMessages.svelte +69 -0
- package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
- package/dist/atoms/FloatingActionButton.svelte +176 -0
- package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
- package/dist/atoms/Heading.svelte +63 -5
- package/dist/atoms/Heading.svelte.d.ts +14 -0
- package/dist/atoms/IconButton.svelte +294 -26
- package/dist/atoms/IconButton.svelte.d.ts +62 -4
- package/dist/atoms/Input.svelte +259 -53
- package/dist/atoms/Input.svelte.d.ts +33 -2
- package/dist/atoms/List.svelte +5 -1
- package/dist/atoms/ListItem.svelte +70 -13
- package/dist/atoms/ListItem.svelte.d.ts +11 -0
- package/dist/atoms/PickerField.svelte +239 -0
- package/dist/atoms/PickerField.svelte.d.ts +67 -0
- package/dist/atoms/Progress.svelte +99 -19
- package/dist/atoms/Progress.svelte.d.ts +15 -1
- package/dist/atoms/Radio.svelte +51 -3
- package/dist/atoms/Radio.svelte.d.ts +30 -2
- package/dist/atoms/Rating.svelte +620 -0
- package/dist/atoms/Rating.svelte.d.ts +77 -0
- package/dist/atoms/Select.svelte +438 -98
- package/dist/atoms/Select.svelte.d.ts +61 -3
- package/dist/atoms/SelectionControl.svelte +193 -23
- package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
- package/dist/atoms/Skeleton.svelte +114 -18
- package/dist/atoms/Skeleton.svelte.d.ts +30 -3
- package/dist/atoms/SkeletonGroup.svelte +43 -0
- package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
- package/dist/atoms/Slider.svelte +344 -0
- package/dist/atoms/Slider.svelte.d.ts +40 -0
- package/dist/atoms/Sparkline.svelte +109 -0
- package/dist/atoms/Sparkline.svelte.d.ts +27 -0
- package/dist/atoms/SpeechBubble.svelte +187 -0
- package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
- package/dist/atoms/Spinner.svelte +57 -0
- package/dist/atoms/Spinner.svelte.d.ts +19 -0
- package/dist/atoms/Stat.svelte +72 -0
- package/dist/atoms/Stat.svelte.d.ts +19 -0
- package/dist/atoms/Table.svelte +151 -6
- package/dist/atoms/Table.svelte.d.ts +15 -3
- package/dist/atoms/TemporalField.svelte +472 -0
- package/dist/atoms/TemporalField.svelte.d.ts +48 -0
- package/dist/atoms/Text.svelte +23 -11
- package/dist/atoms/Text.svelte.d.ts +6 -3
- package/dist/atoms/Textarea.svelte +150 -44
- package/dist/atoms/Textarea.svelte.d.ts +35 -2
- package/dist/atoms/ThemeToggle.svelte +223 -44
- package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
- package/dist/atoms/TimeColumn.svelte +150 -0
- package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
- package/dist/atoms/TimeField.svelte +151 -0
- package/dist/atoms/TimeField.svelte.d.ts +85 -0
- package/dist/atoms/Toast.svelte +22 -8
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +98 -14
- package/dist/atoms/Toggle.svelte.d.ts +26 -2
- package/dist/atoms/Tooltip.svelte +528 -24
- package/dist/atoms/Tooltip.svelte.d.ts +18 -0
- package/dist/atoms/ZabiStringsProvider.svelte +45 -0
- package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
- package/dist/atoms/index.d.ts +38 -0
- package/dist/atoms/index.js +29 -0
- package/dist/atoms/selection-control.styles.d.ts +47 -5
- package/dist/atoms/selection-control.styles.js +52 -10
- package/dist/components/atoms/index.d.ts +38 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +47 -5
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +104 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/map.d.ts +11 -0
- package/dist/components/map.d.ts.map +1 -0
- package/dist/components/molecules/index.d.ts +51 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +83 -7
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +10 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +24 -1
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +79 -0
- package/dist/components/util/app-shell.d.ts.map +1 -0
- package/dist/components/util/avatar.d.ts +39 -0
- package/dist/components/util/avatar.d.ts.map +1 -0
- package/dist/components/util/bottom-sheet.d.ts +30 -0
- package/dist/components/util/bottom-sheet.d.ts.map +1 -0
- package/dist/components/util/bottom-tab-bar.d.ts +41 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
- package/dist/components/util/calendar.d.ts +56 -0
- package/dist/components/util/calendar.d.ts.map +1 -0
- package/dist/components/util/chart.d.ts +140 -0
- package/dist/components/util/chart.d.ts.map +1 -0
- package/dist/components/util/chip.d.ts +66 -0
- package/dist/components/util/chip.d.ts.map +1 -0
- package/dist/components/util/cn.d.ts.map +1 -1
- package/dist/components/util/code-field.d.ts +63 -0
- package/dist/components/util/code-field.d.ts.map +1 -0
- package/dist/components/util/collapsible.d.ts +64 -0
- package/dist/components/util/collapsible.d.ts.map +1 -0
- package/dist/components/util/confirm-dialog.d.ts +12 -0
- package/dist/components/util/confirm-dialog.d.ts.map +1 -0
- package/dist/components/util/date.d.ts +96 -0
- package/dist/components/util/date.d.ts.map +1 -0
- package/dist/components/util/display-voice.d.ts +10 -0
- package/dist/components/util/display-voice.d.ts.map +1 -0
- package/dist/components/util/drawer.d.ts +13 -0
- package/dist/components/util/drawer.d.ts.map +1 -0
- package/dist/components/util/dropdown.d.ts +47 -0
- package/dist/components/util/dropdown.d.ts.map +1 -0
- package/dist/components/util/field-control.d.ts +30 -0
- package/dist/components/util/field-control.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +45 -0
- package/dist/components/util/field.d.ts.map +1 -0
- package/dist/components/util/fit-in-viewport.d.ts +201 -0
- package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
- package/dist/components/util/focus-utils.d.ts +77 -2
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- package/dist/components/util/hydration.d.ts +45 -0
- package/dist/components/util/hydration.d.ts.map +1 -0
- package/dist/components/util/keyboard-inset.d.ts +18 -0
- package/dist/components/util/keyboard-inset.d.ts.map +1 -0
- package/dist/components/util/map-view.d.ts +144 -0
- package/dist/components/util/map-view.d.ts.map +1 -0
- package/dist/components/util/media-grid.d.ts +51 -0
- package/dist/components/util/media-grid.d.ts.map +1 -0
- package/dist/components/util/media.d.ts +6 -0
- package/dist/components/util/media.d.ts.map +1 -0
- package/dist/components/util/overlay-chrome.d.ts +53 -0
- package/dist/components/util/overlay-chrome.d.ts.map +1 -0
- package/dist/components/util/overlay.d.ts +72 -0
- package/dist/components/util/overlay.d.ts.map +1 -0
- package/dist/components/util/photo.d.ts +234 -0
- package/dist/components/util/photo.d.ts.map +1 -0
- package/dist/components/util/portal.d.ts +23 -0
- package/dist/components/util/portal.d.ts.map +1 -0
- package/dist/components/util/progress.d.ts +19 -0
- package/dist/components/util/progress.d.ts.map +1 -0
- package/dist/components/util/pull-to-refresh.d.ts +27 -0
- package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
- package/dist/components/util/radio-keys.d.ts +25 -0
- package/dist/components/util/radio-keys.d.ts.map +1 -0
- package/dist/components/util/rating.d.ts +23 -0
- package/dist/components/util/rating.d.ts.map +1 -0
- package/dist/components/util/ready-made-strings.d.ts +77 -0
- package/dist/components/util/ready-made-strings.d.ts.map +1 -0
- package/dist/components/util/scroll-edge.d.ts +33 -0
- package/dist/components/util/scroll-edge.d.ts.map +1 -0
- package/dist/components/util/scroll-padding.d.ts +20 -0
- package/dist/components/util/scroll-padding.d.ts.map +1 -0
- package/dist/components/util/segmented-control.d.ts +16 -0
- package/dist/components/util/segmented-control.d.ts.map +1 -0
- package/dist/components/util/select.d.ts +41 -0
- package/dist/components/util/select.d.ts.map +1 -0
- package/dist/components/util/sheet-drag.d.ts +84 -0
- package/dist/components/util/sheet-drag.d.ts.map +1 -0
- package/dist/components/util/sidebar.d.ts +63 -0
- package/dist/components/util/sidebar.d.ts.map +1 -0
- package/dist/components/util/skeleton.d.ts +15 -0
- package/dist/components/util/skeleton.d.ts.map +1 -0
- package/dist/components/util/sliding-indicator.d.ts +25 -0
- package/dist/components/util/sliding-indicator.d.ts.map +1 -0
- package/dist/components/util/sortable-list.d.ts +65 -0
- package/dist/components/util/sortable-list.d.ts.map +1 -0
- package/dist/components/util/stepper.d.ts +40 -0
- package/dist/components/util/stepper.d.ts.map +1 -0
- package/dist/components/util/swipeable-list-item.d.ts +36 -0
- package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
- package/dist/components/util/temporal-field.d.ts +89 -0
- package/dist/components/util/temporal-field.d.ts.map +1 -0
- package/dist/components/util/theme-mode.d.ts +80 -0
- package/dist/components/util/theme-mode.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +133 -0
- package/dist/components/util/toaster.d.ts.map +1 -0
- package/dist/components/util/tone-surface.d.ts +34 -0
- package/dist/components/util/tone-surface.d.ts.map +1 -0
- package/dist/components/util/tooltip.d.ts +10 -0
- package/dist/components/util/tooltip.d.ts.map +1 -0
- package/dist/components/util/top-navbar.d.ts +35 -0
- package/dist/components/util/top-navbar.d.ts.map +1 -0
- package/dist/components/util/touch-target.d.ts +31 -0
- package/dist/components/util/touch-target.d.ts.map +1 -0
- package/dist/components/util/zabi-strings.d.ts +142 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -0
- package/dist/create-theme/cli.js +173 -0
- package/dist/create-theme/index.d.ts +190 -0
- package/dist/create-theme/index.js +1034 -0
- package/dist/create-theme/lib/ramp-math.js +128 -0
- package/dist/create-theme/lib/resolve.js +238 -0
- package/dist/create-theme/theme-data.js +2644 -0
- package/dist/index.d.ts +104 -2
- package/dist/index.js +75 -2
- package/dist/map.d.ts +10 -0
- package/dist/map.js +9 -0
- package/dist/molecules/Alert.svelte +63 -16
- package/dist/molecules/Alert.svelte.d.ts +14 -1
- package/dist/molecules/AppBar.svelte +674 -0
- package/dist/molecules/AppBar.svelte.d.ts +131 -0
- package/dist/molecules/AvatarGroup.svelte +116 -0
- package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
- package/dist/molecules/BarChart.svelte +381 -0
- package/dist/molecules/BarChart.svelte.d.ts +45 -0
- package/dist/molecules/BottomSheet.svelte +608 -0
- package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
- package/dist/molecules/BottomTabBar.svelte +403 -0
- package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
- package/dist/molecules/Calendar.svelte +597 -0
- package/dist/molecules/Calendar.svelte.d.ts +72 -0
- package/dist/molecules/ChipGroup.svelte +325 -0
- package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
- package/dist/molecules/Collapsible.svelte +347 -0
- package/dist/molecules/Collapsible.svelte.d.ts +61 -0
- package/dist/molecules/CollapsibleGroup.svelte +136 -0
- package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
- package/dist/molecules/ComponentDemo.svelte +16 -4
- package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
- package/dist/molecules/ConfirmDialog.svelte +257 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
- package/dist/molecules/ContactForm.svelte +34 -17
- package/dist/molecules/ContactForm.svelte.d.ts +8 -0
- package/dist/molecules/Drawer.svelte +415 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +517 -48
- package/dist/molecules/Dropdown.svelte.d.ts +47 -6
- package/dist/molecules/DropdownItem.svelte +122 -0
- package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
- package/dist/molecules/EmptyState.svelte +55 -10
- package/dist/molecules/EmptyState.svelte.d.ts +8 -0
- package/dist/molecules/FormField.svelte +9 -1
- package/dist/molecules/FormField.svelte.d.ts +2 -0
- package/dist/molecules/Header.svelte +1 -1
- package/dist/molecules/ImageUpload.svelte +492 -69
- package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
- package/dist/molecules/LineChart.svelte +459 -0
- package/dist/molecules/LineChart.svelte.d.ts +53 -0
- package/dist/molecules/MapView.svelte +565 -0
- package/dist/molecules/MapView.svelte.d.ts +88 -0
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +293 -72
- package/dist/molecules/Modal.svelte.d.ts +51 -2
- package/dist/molecules/NavigationMenuContent.svelte +58 -1
- package/dist/molecules/NavigationMenuItem.svelte +0 -1
- package/dist/molecules/NavigationMenuLink.svelte +1 -1
- package/dist/molecules/NavigationMenuList.svelte +10 -3
- package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
- package/dist/molecules/Page.svelte +32 -2
- package/dist/molecules/Page.svelte.d.ts +11 -0
- package/dist/molecules/PhotoGrid.svelte +523 -0
- package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
- package/dist/molecules/PhotoViewer.svelte +1213 -0
- package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
- package/dist/molecules/PieChart.svelte +421 -0
- package/dist/molecules/PieChart.svelte.d.ts +51 -0
- package/dist/molecules/PropsTable.svelte +22 -10
- package/dist/molecules/PropsTable.svelte.d.ts +3 -0
- package/dist/molecules/PullToRefresh.svelte +263 -0
- package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
- package/dist/molecules/RadioGroup.svelte +72 -18
- package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
- package/dist/molecules/Section.svelte +1 -1
- package/dist/molecules/SegmentedControl.svelte +456 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
- package/dist/molecules/SidebarBrandHeader.svelte +11 -1
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
- package/dist/molecules/SidebarFooter.svelte +30 -7
- package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
- package/dist/molecules/SidebarNavSection.svelte +1 -1
- package/dist/molecules/SlideUp.svelte +220 -61
- package/dist/molecules/SlideUp.svelte.d.ts +26 -0
- package/dist/molecules/SortableList.svelte +633 -0
- package/dist/molecules/SortableList.svelte.d.ts +69 -0
- package/dist/molecules/Stepper.svelte +562 -0
- package/dist/molecules/Stepper.svelte.d.ts +39 -0
- package/dist/molecules/StickyActionBar.svelte +250 -0
- package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
- package/dist/molecules/SwipeableListItem.svelte +334 -0
- package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
- package/dist/molecules/Tabs.svelte +310 -9
- package/dist/molecules/Tabs.svelte.d.ts +7 -0
- package/dist/molecules/Toaster.svelte +514 -5
- package/dist/molecules/Toaster.svelte.d.ts +153 -2
- package/dist/molecules/ToasterToast.svelte +495 -77
- package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
- package/dist/molecules/UnsavedChangesBar.svelte +223 -0
- package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
- package/dist/molecules/index.d.ts +51 -0
- package/dist/molecules/index.js +31 -0
- package/dist/molecules/toast-store.d.ts +83 -7
- package/dist/molecules/toast-store.js +25 -1
- package/dist/organisms/AppNavigation.svelte +440 -0
- package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
- package/dist/organisms/AppShell.svelte +696 -0
- package/dist/organisms/AppShell.svelte.d.ts +157 -0
- package/dist/organisms/AppShellHeaderPart.svelte +163 -0
- package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
- package/dist/organisms/SidebarAccountPanel.svelte +107 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
- package/dist/organisms/SidebarNavigation.svelte +111 -43
- package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
- package/dist/organisms/SidebarPanel.svelte +34 -17
- package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
- package/dist/organisms/SidebarShell.svelte +164 -14
- package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
- package/dist/organisms/TopNavbar.svelte +353 -39
- package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
- package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
- package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
- package/dist/organisms/index.d.ts +10 -0
- package/dist/organisms/index.js +7 -0
- package/dist/types/components.d.ts +21 -1
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +1093 -26
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +28 -1
- package/dist/util/app-shell.d.ts +78 -0
- package/dist/util/app-shell.js +97 -0
- package/dist/util/avatar.d.ts +38 -0
- package/dist/util/avatar.js +52 -0
- package/dist/util/bottom-sheet.d.ts +29 -0
- package/dist/util/bottom-sheet.js +43 -0
- package/dist/util/bottom-tab-bar.d.ts +40 -0
- package/dist/util/bottom-tab-bar.js +54 -0
- package/dist/util/calendar.d.ts +55 -0
- package/dist/util/calendar.js +74 -0
- package/dist/util/chart.d.ts +139 -0
- package/dist/util/chart.js +305 -0
- package/dist/util/chip.d.ts +65 -0
- package/dist/util/chip.js +58 -0
- package/dist/util/cn.js +28 -2
- package/dist/util/code-field.d.ts +62 -0
- package/dist/util/code-field.js +112 -0
- package/dist/util/collapsible.d.ts +63 -0
- package/dist/util/collapsible.js +23 -0
- package/dist/util/confirm-dialog.d.ts +11 -0
- package/dist/util/confirm-dialog.js +2 -0
- package/dist/util/date.d.ts +95 -0
- package/dist/util/date.js +237 -0
- package/dist/util/display-voice.d.ts +9 -0
- package/dist/util/display-voice.js +9 -0
- package/dist/util/drawer.d.ts +12 -0
- package/dist/util/drawer.js +7 -0
- package/dist/util/dropdown.d.ts +46 -0
- package/dist/util/dropdown.js +21 -0
- package/dist/util/field-control.d.ts +29 -0
- package/dist/util/field-control.js +32 -0
- package/dist/util/field.d.ts +44 -0
- package/dist/util/field.js +38 -0
- package/dist/util/fit-in-viewport.d.ts +200 -0
- package/dist/util/fit-in-viewport.js +399 -0
- package/dist/util/focus-utils.d.ts +77 -2
- package/dist/util/focus-utils.js +186 -9
- package/dist/util/hydration.d.ts +44 -0
- package/dist/util/hydration.js +58 -0
- package/dist/util/keyboard-inset.d.ts +17 -0
- package/dist/util/keyboard-inset.js +53 -0
- package/dist/util/map-view.d.ts +143 -0
- package/dist/util/map-view.js +121 -0
- package/dist/util/media-grid.d.ts +50 -0
- package/dist/util/media-grid.js +81 -0
- package/dist/util/media.d.ts +5 -0
- package/dist/util/media.js +10 -0
- package/dist/util/overlay-chrome.d.ts +52 -0
- package/dist/util/overlay-chrome.js +56 -0
- package/dist/util/overlay.d.ts +71 -0
- package/dist/util/overlay.js +240 -0
- package/dist/util/photo.d.ts +233 -0
- package/dist/util/photo.js +209 -0
- package/dist/util/portal.d.ts +22 -0
- package/dist/util/portal.js +50 -0
- package/dist/util/progress.d.ts +18 -0
- package/dist/util/progress.js +17 -0
- package/dist/util/pull-to-refresh.d.ts +26 -0
- package/dist/util/pull-to-refresh.js +30 -0
- package/dist/util/radio-keys.d.ts +24 -0
- package/dist/util/radio-keys.js +49 -0
- package/dist/util/rating.d.ts +22 -0
- package/dist/util/rating.js +26 -0
- package/dist/util/ready-made-strings.d.ts +76 -0
- package/dist/util/ready-made-strings.js +67 -0
- package/dist/util/scroll-edge.d.ts +32 -0
- package/dist/util/scroll-edge.js +87 -0
- package/dist/util/scroll-padding.d.ts +19 -0
- package/dist/util/scroll-padding.js +44 -0
- package/dist/util/segmented-control.d.ts +15 -0
- package/dist/util/segmented-control.js +1 -0
- package/dist/util/select.d.ts +40 -0
- package/dist/util/select.js +63 -0
- package/dist/util/sheet-drag.d.ts +83 -0
- package/dist/util/sheet-drag.js +311 -0
- package/dist/util/sidebar.d.ts +62 -0
- package/dist/util/sidebar.js +38 -0
- package/dist/util/skeleton.d.ts +14 -0
- package/dist/util/skeleton.js +22 -0
- package/dist/util/sliding-indicator.d.ts +24 -0
- package/dist/util/sliding-indicator.js +44 -0
- package/dist/util/sortable-list.d.ts +64 -0
- package/dist/util/sortable-list.js +53 -0
- package/dist/util/stepper.d.ts +39 -0
- package/dist/util/stepper.js +38 -0
- package/dist/util/swipeable-list-item.d.ts +35 -0
- package/dist/util/swipeable-list-item.js +32 -0
- package/dist/util/temporal-field.d.ts +88 -0
- package/dist/util/temporal-field.js +136 -0
- package/dist/util/theme-mode.d.ts +79 -0
- package/dist/util/theme-mode.js +121 -0
- package/dist/util/toaster.d.ts +132 -0
- package/dist/util/toaster.js +130 -0
- package/dist/util/tone-surface.d.ts +33 -0
- package/dist/util/tone-surface.js +35 -0
- package/dist/util/tooltip.d.ts +9 -0
- package/dist/util/tooltip.js +18 -0
- package/dist/util/top-navbar.d.ts +34 -0
- package/dist/util/top-navbar.js +28 -0
- package/dist/util/touch-target.d.ts +30 -0
- package/dist/util/touch-target.js +30 -0
- package/dist/util/zabi-strings.d.ts +141 -0
- package/dist/util/zabi-strings.js +105 -0
- package/dist/zabi-components-colors.css +11 -2
- package/dist/zabi-components-theme-dark-only.css +870 -258
- package/dist/zabi-components-theme-dark.css +870 -258
- package/dist/zabi-components-theme-only.css +2419 -368
- package/dist/zabi-components-theme.css +2419 -368
- package/dist/zabi-components.css +6032 -711
- package/docs/lucide-icons.md +40 -17
- package/docs/theme-imports.md +81 -11
- package/package.json +45 -8
package/dist/atoms/Button.svelte
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
3
|
-
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
|
-
import type {
|
|
2
|
+
import { tick, untrack, type Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
|
4
|
+
import type { ButtonSize, ButtonVariant } from "../types/variants.js";
|
|
5
5
|
import { cn } from "../util/cn.js";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* One type for the button and the link, not a union of the two elements'
|
|
9
|
+
* attributes: that union is too large for TypeScript to carry through a
|
|
10
|
+
* wrapper such as a Storybook `Meta`. Event handlers are typed for the
|
|
11
|
+
* button; on a link they are called with the same event.
|
|
12
|
+
*/
|
|
7
13
|
type Props = Omit<HTMLButtonAttributes, "class"> & {
|
|
14
|
+
/**
|
|
15
|
+
* Where the link goes. Makes this an `<a>` that looks the same;
|
|
16
|
+
* without it, it is a `<button>`. A link cannot be disabled: while
|
|
17
|
+
* `disabled` or `loading` it has no `href`, is `aria-disabled` and
|
|
18
|
+
* does nothing when pressed. While `loading` it stays a Tab stop.
|
|
19
|
+
*/
|
|
20
|
+
href?: string;
|
|
21
|
+
/** With `href`: where the link opens. */
|
|
22
|
+
target?: HTMLAnchorAttributes["target"];
|
|
23
|
+
/** With `href`: the link's relationship, such as `noopener`. */
|
|
24
|
+
rel?: HTMLAnchorAttributes["rel"];
|
|
25
|
+
/** With `href`: download the address instead of opening it. */
|
|
26
|
+
download?: HTMLAnchorAttributes["download"];
|
|
27
|
+
hreflang?: HTMLAnchorAttributes["hreflang"];
|
|
28
|
+
referrerpolicy?: HTMLAnchorAttributes["referrerpolicy"];
|
|
29
|
+
ping?: HTMLAnchorAttributes["ping"];
|
|
8
30
|
variant?: ButtonVariant;
|
|
9
|
-
size
|
|
31
|
+
/** `xl` is 56px, for a primary action on a phone. It is a Button size only. */
|
|
32
|
+
size?: ButtonSize;
|
|
33
|
+
/**
|
|
34
|
+
* Shows a spinner and makes the button unavailable without taking
|
|
35
|
+
* focus from it: `aria-disabled` and `aria-busy`, still a Tab stop,
|
|
36
|
+
* and a press does nothing. It is not `disabled`: match it in CSS
|
|
37
|
+
* with `[aria-busy="true"]`.
|
|
38
|
+
*/
|
|
10
39
|
loading?: boolean;
|
|
11
40
|
text?: string;
|
|
12
41
|
/** Stretch to the width of the container. */
|
|
@@ -22,7 +51,8 @@
|
|
|
22
51
|
size = "md",
|
|
23
52
|
disabled = false,
|
|
24
53
|
loading = false,
|
|
25
|
-
type
|
|
54
|
+
type,
|
|
55
|
+
href,
|
|
26
56
|
text = "",
|
|
27
57
|
fullWidth = false,
|
|
28
58
|
isFullWidth = false,
|
|
@@ -32,25 +62,90 @@
|
|
|
32
62
|
...restProps
|
|
33
63
|
}: Props = $props();
|
|
34
64
|
|
|
35
|
-
const isDisabled = $derived(disabled || loading);
|
|
65
|
+
const isDisabled = $derived(!!disabled || loading);
|
|
66
|
+
/**
|
|
67
|
+
* Loading without `disabled`: unavailable, and still where focus is.
|
|
68
|
+
*
|
|
69
|
+
* `disabled` on a button, or no `href` on a link, makes the element
|
|
70
|
+
* unfocusable, and the browser then drops focus on `<body>`: whoever
|
|
71
|
+
* pressed Save with the keyboard was at the top of the page when the
|
|
72
|
+
* save came back. So a loading control says it the ARIA way
|
|
73
|
+
* (`aria-disabled`, `aria-busy`), stays a Tab stop, and swallows the
|
|
74
|
+
* press. `disabled` is the platform's: out of the Tab order.
|
|
75
|
+
*/
|
|
76
|
+
const isBusy = $derived(loading && !disabled);
|
|
77
|
+
const isLink = $derived(href !== undefined && href !== null);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A loading link has no `href`, so it is a Tab stop only by `tabindex`.
|
|
81
|
+
* The browser takes focus from an element the moment it stops being
|
|
82
|
+
* focusable, so the two attributes must never both be missing: the
|
|
83
|
+
* `tabindex` goes on before the `href` comes off (their order in the
|
|
84
|
+
* markup), and when loading ends it stays until the `href` is back, one
|
|
85
|
+
* update longer.
|
|
86
|
+
*/
|
|
87
|
+
let keepsTabStop = $state(false);
|
|
88
|
+
$effect.pre(() => {
|
|
89
|
+
if (isBusy) {
|
|
90
|
+
keepsTabStop = true;
|
|
91
|
+
} else if (untrack(() => keepsTabStop)) {
|
|
92
|
+
void tick().then(() => (keepsTabStop = false));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/** A text link in a sentence: `variant="link"` on a real link. */
|
|
97
|
+
const isInlineLink = $derived(isLink && variant === "link");
|
|
98
|
+
|
|
99
|
+
/** A disabled link has no `href`, but a pointer can still press it. */
|
|
100
|
+
function handleLinkClick(event: MouseEvent) {
|
|
101
|
+
if (isDisabled) {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
event.stopPropagation();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
(onclick as ((event: MouseEvent) => void) | null | undefined)?.(event);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A loading button is not `disabled`, so the press reaches it: it ends here, and submits nothing. */
|
|
110
|
+
function handleButtonClick(event: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement }) {
|
|
111
|
+
if (isBusy) {
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
event.stopPropagation();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
onclick?.(event);
|
|
117
|
+
}
|
|
36
118
|
|
|
37
119
|
/**
|
|
38
120
|
* One height scale, shared with Input, Select and IconButton, so controls
|
|
39
|
-
* of the same size line up in a row
|
|
40
|
-
*
|
|
41
|
-
*
|
|
121
|
+
* of the same size line up in a row: 32 / 40 / 48px (and 56px, `xl`, which
|
|
122
|
+
* only Button has). It is a minimum
|
|
123
|
+
* height, not a fixed one, so a label that does not fit on one line wraps
|
|
124
|
+
* and the button grows; it used to paint outside the button (a long label
|
|
125
|
+
* at 200% text on a phone). One line of text and the vertical padding are
|
|
126
|
+
* never taller than the minimum, the outline variant's border included,
|
|
127
|
+
* so a label that fits is exactly on the scale:
|
|
128
|
+
* scripts/check-control-geometry.js holds the padding to that.
|
|
42
129
|
*
|
|
43
130
|
* Weight and tracking do NOT change with size: a large button used to drop
|
|
44
131
|
* to font-normal, so the biggest button had the lightest label.
|
|
45
132
|
*/
|
|
46
133
|
const sizeClass = $derived.by(() => {
|
|
134
|
+
// sm and md are 44px tall on a coarse pointer, like Input, Select and
|
|
135
|
+
// IconButton, so a row still lines up and the box is the touch target.
|
|
136
|
+
// No minimum width: text and padding make a button wide enough, and one
|
|
137
|
+
// would stop the actions of an ImageUpload shrinking in a narrow space.
|
|
47
138
|
if (size === "sm") {
|
|
48
|
-
return { box: "h-8 px-3", text: "text-sm", gap: "gap-2", spinner: "size-3.5" };
|
|
139
|
+
return { box: "min-h-8 px-3 py-1 pointer-coarse:min-h-11", text: "text-sm", gap: "gap-2", spinner: "size-3.5" };
|
|
140
|
+
}
|
|
141
|
+
// xl is 56px, like the FloatingActionButton; only Button has it.
|
|
142
|
+
if (size === "xl") {
|
|
143
|
+
return { box: "min-h-14 px-6 py-3", text: "text-base", gap: "gap-2", spinner: "size-5" };
|
|
49
144
|
}
|
|
50
145
|
if (size === "lg") {
|
|
51
|
-
return { box: "h-12 px-5", text: "text-base", gap: "gap-2", spinner: "size-4" };
|
|
146
|
+
return { box: "min-h-12 px-5 py-2", text: "text-base", gap: "gap-2", spinner: "size-4" };
|
|
52
147
|
}
|
|
53
|
-
return { box: "h-10 px-4", text: "text-sm", gap: "gap-2", spinner: "size-4" };
|
|
148
|
+
return { box: "min-h-10 px-4 py-2 pointer-coarse:min-h-11", text: "text-sm", gap: "gap-2", spinner: "size-4" };
|
|
54
149
|
});
|
|
55
150
|
|
|
56
151
|
/**
|
|
@@ -58,48 +153,108 @@
|
|
|
58
153
|
* made a disabled primary lighter and more colourful than an enabled
|
|
59
154
|
* secondary. Every variant falls back to the same neutral disabled pair.
|
|
60
155
|
*/
|
|
156
|
+
const DISABLED_LINK =
|
|
157
|
+
"bg-(--color-action-disabled) text-action-disabled-text border border-transparent cursor-not-allowed";
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A loading button wears the same pair. It is not `:disabled`, so the
|
|
161
|
+
* variant's classes are left out instead of overridden: none of its hover
|
|
162
|
+
* or pressed fills can show. What the variant gives the box is kept (the
|
|
163
|
+
* outline's border, the link's missing side padding), so the button does
|
|
164
|
+
* not change size when it starts loading. It keeps the focus ring: it
|
|
165
|
+
* can be focused.
|
|
166
|
+
*/
|
|
167
|
+
const BUSY_BUTTON = "bg-(--color-action-disabled) text-action-disabled-text cursor-not-allowed";
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Forced colours (a Windows contrast theme) remove every fill, so a button
|
|
171
|
+
* told apart by its fill alone is told apart by nothing. The variants that
|
|
172
|
+
* are drawn by a fill get a 1px edge in a system colour there only;
|
|
173
|
+
* `ghost`, `link` and `text` stay bare, and `outline` has its own border.
|
|
174
|
+
* A disabled or loading button is the flat disabled fill, so it gets an
|
|
175
|
+
* edge in the disabled text colour. The `disabled:` copy beats
|
|
176
|
+
* `disabled:border-transparent`, which is the more specific rule.
|
|
177
|
+
*/
|
|
178
|
+
const FORCED_EDGE = "forced-colors:border forced-colors:border-[color:ButtonBorder]";
|
|
179
|
+
const FORCED_EDGE_DISABLED_ONLY = "disabled:forced-colors:border disabled:forced-colors:border-[color:GrayText]";
|
|
180
|
+
const FORCED_EDGE_DISABLED =
|
|
181
|
+
"forced-colors:border forced-colors:border-[color:GrayText] disabled:forced-colors:border-[color:GrayText]";
|
|
182
|
+
|
|
61
183
|
const disabledClass =
|
|
62
|
-
"disabled:bg-action-disabled disabled:text-action-disabled-text disabled:border-transparent disabled:no-underline disabled:shadow-none disabled:cursor-not-allowed disabled:active:scale-100";
|
|
184
|
+
"disabled:bg-action-disabled disabled:text-action-disabled-text disabled:border-transparent disabled:no-underline disabled:bg-none disabled:inset-shadow-none disabled:shadow-none disabled:cursor-not-allowed disabled:active:scale-100";
|
|
63
185
|
|
|
64
186
|
const variantClass = $derived.by(() => {
|
|
187
|
+
// The veil of the solid fills (a top highlight and a darker bottom). A
|
|
188
|
+
// disabled button is the flat neutral pair, so it is left off rather
|
|
189
|
+
// than cleared; `disabledClass` also clears it for a button that is
|
|
190
|
+
// disabled from outside (a `fieldset`).
|
|
191
|
+
const veil = disabled ? "" : "bg-control-gradient ";
|
|
192
|
+
const veilAccent = disabled ? "" : "bg-control-gradient-accent ";
|
|
65
193
|
switch (variant) {
|
|
66
194
|
case "secondary":
|
|
67
|
-
return "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active active:scale-[0.98]";
|
|
195
|
+
return "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active active:scale-[0.98] motion-reduce:active:scale-100";
|
|
196
|
+
case "tonal":
|
|
197
|
+
return "bg-action-tonal text-action-tonal active:scale-[0.98] motion-reduce:active:scale-100";
|
|
68
198
|
case "danger":
|
|
69
|
-
return
|
|
199
|
+
return `${veil}bg-action-danger text-action-danger-text hover:bg-action-danger-hover active:bg-action-danger-active active:scale-[0.98] motion-reduce:active:scale-100 focus-ring--danger`;
|
|
70
200
|
case "ghost":
|
|
71
|
-
return "bg-transparent text-headline hover:bg-surface-hover active:bg-surface-active active:scale-[0.98] focus-ring--muted";
|
|
201
|
+
return "bg-transparent text-headline hover:bg-surface-hover active:bg-surface-active active:scale-[0.98] motion-reduce:active:scale-100 focus-ring--muted";
|
|
72
202
|
case "outline":
|
|
73
|
-
return "bg-transparent border border-
|
|
203
|
+
return "bg-transparent border border-action-outline text-headline hover:bg-surface-hover hover:border-action-outline-hover active:bg-surface-active active:scale-[0.98] motion-reduce:active:scale-100";
|
|
74
204
|
case "link":
|
|
75
205
|
// A link variant has to look like a link at rest, not only on
|
|
76
206
|
// hover — otherwise it is indistinguishable from `ghost`.
|
|
77
207
|
return "bg-transparent text-link hover:text-link-hover underline underline-offset-4 decoration-1 hover:decoration-2 px-0 focus-ring--muted";
|
|
208
|
+
case "text":
|
|
209
|
+
// A standalone action, not a link in a sentence: no underline,
|
|
210
|
+
// a stronger label, and the box of the other variants. The
|
|
211
|
+
// link role gives the primary colour, and the block's own
|
|
212
|
+
// on-colour inside `.on-brand` / `.on-accent`.
|
|
213
|
+
return "bg-transparent text-link px-2 hover:bg-surface-hover active:bg-surface-active active:scale-[0.98] motion-reduce:active:scale-100 focus-ring--muted";
|
|
214
|
+
case "accent":
|
|
215
|
+
return `${veilAccent}bg-accent text-on-accent hover:bg-accent-hover active:bg-accent-active active:scale-[0.98] motion-reduce:active:scale-100`;
|
|
78
216
|
case "primary":
|
|
79
217
|
default:
|
|
80
|
-
return
|
|
218
|
+
return `${veil}bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98] motion-reduce:active:scale-100`;
|
|
81
219
|
}
|
|
82
220
|
});
|
|
83
221
|
|
|
84
222
|
const buttonClasses = $derived.by(() => {
|
|
85
223
|
const s = sizeClass;
|
|
86
224
|
const layout = fullWidth || isFullWidth ? "flex w-full" : "inline-flex";
|
|
87
|
-
|
|
88
|
-
|
|
225
|
+
if (isInlineLink) {
|
|
226
|
+
// Flows with the sentence it is in: no box, no minimum height, and
|
|
227
|
+
// it breaks across lines as a link does.
|
|
228
|
+
const state = isDisabled
|
|
229
|
+
? "text-action-disabled-text no-underline cursor-not-allowed"
|
|
230
|
+
: "text-link hover:text-link-hover underline underline-offset-4 decoration-1 hover:decoration-2 cursor-pointer";
|
|
231
|
+
return cn(
|
|
232
|
+
`focus-ring focus-ring--muted inline rounded-button font-button transition-colors duration-(--duration-base) ${s.text} ${state} ${fullWidth || isFullWidth ? "block w-full" : ""} ${className}`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
// The label wraps when it has to, balanced over its lines; the icon
|
|
236
|
+
// and the spinner stay centred beside it. The button is never wider
|
|
237
|
+
// than what it is in: a word that alone does not fit is hyphenated by
|
|
238
|
+
// the page's language (Z-129).
|
|
239
|
+
const base = `${layout} [max-inline-size:100%] hyphens-auto focus-ring items-center justify-center rounded-button text-center ${variant === "text" ? "font-button-strong" : "font-button"} text-balance transition-colors duration-(--duration-base) cursor-pointer select-none`;
|
|
240
|
+
// `disabled:` does not match an `<a>`: a disabled link wears the
|
|
241
|
+
// disabled pair itself, in place of its variant.
|
|
242
|
+
const busyBox = variant === "outline" ? "border border-transparent" : variant === "link" ? "px-0" : variant === "text" ? "px-2" : "";
|
|
243
|
+
const drawnByFill = variant !== "ghost" && variant !== "link" && variant !== "text" && variant !== "outline";
|
|
244
|
+
const state =
|
|
245
|
+
isLink && isDisabled
|
|
246
|
+
? `${DISABLED_LINK} ${FORCED_EDGE_DISABLED}`
|
|
247
|
+
: isBusy
|
|
248
|
+
? `${BUSY_BUTTON} ${busyBox} ${FORCED_EDGE_DISABLED}`
|
|
249
|
+
: `${variantClass} ${disabledClass} ${drawnByFill ? FORCED_EDGE : ""} ${FORCED_EDGE_DISABLED_ONLY}`;
|
|
250
|
+
return cn(`${base} ${s.box} ${s.text} ${s.gap} ${state} ${className}`);
|
|
89
251
|
});
|
|
90
252
|
</script>
|
|
91
253
|
|
|
92
|
-
|
|
93
|
-
{type}
|
|
94
|
-
class={buttonClasses}
|
|
95
|
-
disabled={isDisabled}
|
|
96
|
-
aria-busy={loading ? "true" : undefined}
|
|
97
|
-
{onclick}
|
|
98
|
-
{...restProps}
|
|
99
|
-
>
|
|
254
|
+
{#snippet content()}
|
|
100
255
|
{#if loading}
|
|
101
256
|
<span
|
|
102
|
-
class="inline-block {sizeClass.spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-80"
|
|
257
|
+
class="inline-block {sizeClass.spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-80 motion-reduce:animate-pulse"
|
|
103
258
|
aria-hidden="true"
|
|
104
259
|
></span>
|
|
105
260
|
{/if}
|
|
@@ -108,4 +263,36 @@
|
|
|
108
263
|
{:else if children}
|
|
109
264
|
{@render children()}
|
|
110
265
|
{/if}
|
|
111
|
-
|
|
266
|
+
{/snippet}
|
|
267
|
+
|
|
268
|
+
{#if isLink}
|
|
269
|
+
<!-- A link that cannot be followed keeps its role and says it is
|
|
270
|
+
unavailable; without an `href` it is not a Tab stop, as a disabled button
|
|
271
|
+
is not. A loading one is given the Tab stop back, so focus stays on it:
|
|
272
|
+
`tabindex` is written before `href` is taken away, in that order (see
|
|
273
|
+
`keepsTabStop`). -->
|
|
274
|
+
<a
|
|
275
|
+
tabindex={isBusy || keepsTabStop ? 0 : undefined}
|
|
276
|
+
href={isDisabled ? undefined : href}
|
|
277
|
+
class={buttonClasses}
|
|
278
|
+
role={isDisabled ? "link" : undefined}
|
|
279
|
+
aria-disabled={isDisabled ? "true" : undefined}
|
|
280
|
+
aria-busy={loading ? "true" : undefined}
|
|
281
|
+
{...(restProps as unknown as HTMLAnchorAttributes)}
|
|
282
|
+
onclick={handleLinkClick}
|
|
283
|
+
>
|
|
284
|
+
{@render content()}
|
|
285
|
+
</a>
|
|
286
|
+
{:else}
|
|
287
|
+
<button
|
|
288
|
+
type={type ?? "button"}
|
|
289
|
+
class={buttonClasses}
|
|
290
|
+
disabled={!!disabled}
|
|
291
|
+
aria-disabled={isBusy ? "true" : undefined}
|
|
292
|
+
aria-busy={loading ? "true" : undefined}
|
|
293
|
+
{...restProps}
|
|
294
|
+
onclick={handleButtonClick}
|
|
295
|
+
>
|
|
296
|
+
{@render content()}
|
|
297
|
+
</button>
|
|
298
|
+
{/if}
|
|
@@ -1,9 +1,38 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
3
|
-
import type {
|
|
1
|
+
import { type Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
|
3
|
+
import type { ButtonSize, ButtonVariant } from "../types/variants.js";
|
|
4
|
+
/**
|
|
5
|
+
* One type for the button and the link, not a union of the two elements'
|
|
6
|
+
* attributes: that union is too large for TypeScript to carry through a
|
|
7
|
+
* wrapper such as a Storybook `Meta`. Event handlers are typed for the
|
|
8
|
+
* button; on a link they are called with the same event.
|
|
9
|
+
*/
|
|
4
10
|
type Props = Omit<HTMLButtonAttributes, "class"> & {
|
|
11
|
+
/**
|
|
12
|
+
* Where the link goes. Makes this an `<a>` that looks the same;
|
|
13
|
+
* without it, it is a `<button>`. A link cannot be disabled: while
|
|
14
|
+
* `disabled` or `loading` it has no `href`, is `aria-disabled` and
|
|
15
|
+
* does nothing when pressed. While `loading` it stays a Tab stop.
|
|
16
|
+
*/
|
|
17
|
+
href?: string;
|
|
18
|
+
/** With `href`: where the link opens. */
|
|
19
|
+
target?: HTMLAnchorAttributes["target"];
|
|
20
|
+
/** With `href`: the link's relationship, such as `noopener`. */
|
|
21
|
+
rel?: HTMLAnchorAttributes["rel"];
|
|
22
|
+
/** With `href`: download the address instead of opening it. */
|
|
23
|
+
download?: HTMLAnchorAttributes["download"];
|
|
24
|
+
hreflang?: HTMLAnchorAttributes["hreflang"];
|
|
25
|
+
referrerpolicy?: HTMLAnchorAttributes["referrerpolicy"];
|
|
26
|
+
ping?: HTMLAnchorAttributes["ping"];
|
|
5
27
|
variant?: ButtonVariant;
|
|
6
|
-
size
|
|
28
|
+
/** `xl` is 56px, for a primary action on a phone. It is a Button size only. */
|
|
29
|
+
size?: ButtonSize;
|
|
30
|
+
/**
|
|
31
|
+
* Shows a spinner and makes the button unavailable without taking
|
|
32
|
+
* focus from it: `aria-disabled` and `aria-busy`, still a Tab stop,
|
|
33
|
+
* and a press does nothing. It is not `disabled`: match it in CSS
|
|
34
|
+
* with `[aria-busy="true"]`.
|
|
35
|
+
*/
|
|
7
36
|
loading?: boolean;
|
|
8
37
|
text?: string;
|
|
9
38
|
/** Stretch to the width of the container. */
|
package/dist/atoms/Card.svelte
CHANGED
|
@@ -1,18 +1,71 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
-
import type { CardVariant, SizeVariant } from "../types/variants.js";
|
|
3
|
+
import type { HTMLAnchorAttributes, HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import type { CardVariant, SizeVariant, SurfaceTone } from "../types/variants.js";
|
|
5
5
|
|
|
6
6
|
import { cn } from "../util/cn.js";
|
|
7
|
+
import { toneSurface } from "../util/tone-surface.js";
|
|
7
8
|
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
|
|
8
9
|
onclick?: (event: MouseEvent) => void | Promise<void>;
|
|
9
10
|
size?: SizeVariant;
|
|
11
|
+
/** The look of a `neutral` card. With a `tone` or a `fill` the fill is the edge: no border, and a shadow only for `elevated`. */
|
|
10
12
|
variant?: CardVariant;
|
|
13
|
+
/**
|
|
14
|
+
* What the card is filled with: `neutral` (the default, the card surface),
|
|
15
|
+
* `tint` (the brand tint), `brand` or `accent`. On `brand` and `accent`
|
|
16
|
+
* the text, links, controls and focus rings inside take the fill's label
|
|
17
|
+
* colour; a Card, field or Alert inside is a surface again.
|
|
18
|
+
*/
|
|
19
|
+
tone?: SurfaceTone;
|
|
20
|
+
/**
|
|
21
|
+
* A fill the app chooses, any CSS colour (`var(--group-colour)`). Wins
|
|
22
|
+
* over `tone`. Set `onFill`, the label colour on it. The library cannot
|
|
23
|
+
* check that pair's contrast: the app must hold it to 4.5:1.
|
|
24
|
+
*/
|
|
25
|
+
fill?: string;
|
|
26
|
+
/** The label colour on `fill`, any CSS colour. */
|
|
27
|
+
onFill?: string;
|
|
28
|
+
/**
|
|
29
|
+
* With `href` the card itself is an `<a>`: the whole card is the link,
|
|
30
|
+
* and it works without scripts. A link card must not contain other
|
|
31
|
+
* interactive elements (a link or a button in a link is invalid and
|
|
32
|
+
* unreachable); a card with actions inside takes `onclick` or none.
|
|
33
|
+
* With `onclick` too, it is a link and `onclick` runs on activation.
|
|
34
|
+
*/
|
|
35
|
+
href?: string;
|
|
36
|
+
/** With `href`: where the link opens. */
|
|
37
|
+
target?: HTMLAnchorAttributes["target"];
|
|
38
|
+
/** With `href`: the link's relationship, such as `noopener`. */
|
|
39
|
+
rel?: string;
|
|
40
|
+
/**
|
|
41
|
+
* The card itself is a `<button>`: the whole card is the control, and
|
|
42
|
+
* `submit` posts the form around it without scripts. With `href`
|
|
43
|
+
* the card is a link and this is ignored. Like a link card it must not
|
|
44
|
+
* contain other interactive elements. `onclick`
|
|
45
|
+
* runs on a press, as on any button.
|
|
46
|
+
*/
|
|
47
|
+
type?: "submit" | "button" | "reset";
|
|
48
|
+
/** With `type`: the name the button submits under. */
|
|
49
|
+
name?: string;
|
|
50
|
+
/** With `type`: what the button submits. */
|
|
51
|
+
value?: string;
|
|
52
|
+
/** With `type`: the id of the form it belongs to, when it is not inside one. */
|
|
53
|
+
form?: string;
|
|
54
|
+
/** With `type`: the card cannot be pressed, is dimmed, and is skipped by the keyboard. */
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* With `type`: the card's action is on its way, as `Button`'s and
|
|
58
|
+
* `Chip`'s `loading`. `aria-busy` and `aria-disabled` (not `disabled`),
|
|
59
|
+
* the press swallowed (it submits nothing and does not reach
|
|
60
|
+
* `onclick`), the pressed look held, the size and the Tab stop kept.
|
|
61
|
+
* No spinner. Ignored by a link card and a plain card.
|
|
62
|
+
*/
|
|
63
|
+
loading?: boolean;
|
|
11
64
|
fullWidth?: boolean;
|
|
12
65
|
class?: string;
|
|
13
66
|
/** @deprecated use `class` — kept so 7.x call sites keep working. */
|
|
14
67
|
className?: string;
|
|
15
|
-
/**
|
|
68
|
+
/** Names the card when it is interactive and its content does not (the content names a link well enough in most cases). */
|
|
16
69
|
ariaLabel?: string;
|
|
17
70
|
children?: Snippet;
|
|
18
71
|
};
|
|
@@ -21,10 +74,23 @@
|
|
|
21
74
|
onclick,
|
|
22
75
|
size = "md",
|
|
23
76
|
variant = "default",
|
|
77
|
+
tone = "neutral",
|
|
78
|
+
fill,
|
|
79
|
+
onFill,
|
|
80
|
+
href,
|
|
81
|
+
target,
|
|
82
|
+
rel,
|
|
83
|
+
type: buttonType,
|
|
84
|
+
name,
|
|
85
|
+
value,
|
|
86
|
+
form,
|
|
87
|
+
disabled = false,
|
|
88
|
+
loading = false,
|
|
24
89
|
fullWidth = true,
|
|
25
90
|
class: classProp = "",
|
|
26
91
|
className = "",
|
|
27
92
|
ariaLabel,
|
|
93
|
+
style: styleProp,
|
|
28
94
|
children,
|
|
29
95
|
...restProps
|
|
30
96
|
}: Props = $props();
|
|
@@ -35,36 +101,79 @@
|
|
|
35
101
|
size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6",
|
|
36
102
|
);
|
|
37
103
|
|
|
104
|
+
const surface = $derived(toneSurface({ tone, fill, onFill }));
|
|
105
|
+
|
|
106
|
+
const isLink = $derived(href !== undefined && href !== null);
|
|
107
|
+
const isButton = $derived(!isLink && buttonType !== undefined);
|
|
108
|
+
|
|
38
109
|
const variantClasses = $derived(
|
|
39
|
-
|
|
40
|
-
?
|
|
41
|
-
|
|
42
|
-
|
|
110
|
+
surface.filled
|
|
111
|
+
? // The fill is the edge: no border, flat unless the variant lifts it.
|
|
112
|
+
`${surface.classes} ${variant === "elevated" ? "shadow-lg" : "shadow-none"}`
|
|
113
|
+
: variant === "elevated"
|
|
114
|
+
? "bg-card-elevated shadow-lg"
|
|
43
115
|
: variant === "flat"
|
|
44
|
-
? "bg-card shadow-none border-none"
|
|
45
|
-
: "bg-card shadow-
|
|
116
|
+
? "bg-card-flat shadow-none border-none"
|
|
117
|
+
: "bg-card border border-border shadow-none",
|
|
46
118
|
);
|
|
47
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Hover and pressed on a fill the card does not know: a veil of the card's
|
|
122
|
+
* own text colour (`bg-current`), so it reads on a tint, the brand, the
|
|
123
|
+
* accent and any colour an app passes. The card is `relative` for it.
|
|
124
|
+
*/
|
|
125
|
+
const STATE_LAYER =
|
|
126
|
+
"relative after:absolute after:inset-0 after:rounded-[inherit] after:bg-current after:opacity-0 after:pointer-events-none after:transition-opacity after:duration-(--duration-base) motion-reduce:after:transition-none hover:after:opacity-8 active:after:opacity-12";
|
|
127
|
+
|
|
128
|
+
/** Loading without `disabled`: unavailable, and still where focus is (see Button). */
|
|
129
|
+
const isBusy = $derived(isButton && loading && !disabled);
|
|
130
|
+
|
|
131
|
+
const interactive = $derived(!!onclick || isLink || isButton);
|
|
132
|
+
|
|
48
133
|
const interactiveClasses = $derived.by(() => {
|
|
49
|
-
if (!
|
|
134
|
+
if (!interactive) return "";
|
|
135
|
+
if (isBusy) {
|
|
136
|
+
// The pressed look, held: no hover or pressed rule is left to change it.
|
|
137
|
+
// Forced colours drop the fills the held look is made of: GrayText text and a dashed inset edge say it there.
|
|
138
|
+
const held =
|
|
139
|
+
"cursor-progress focus-ring forced-colors:text-[color:GrayText] forced-colors:outline-1 forced-colors:outline-dashed forced-colors:-outline-offset-1 forced-colors:outline-[color:GrayText]";
|
|
140
|
+
if (surface.filled) return `${held} relative after:absolute after:inset-0 after:rounded-[inherit] after:bg-current after:opacity-12 after:pointer-events-none`;
|
|
141
|
+
switch (variant) {
|
|
142
|
+
case "elevated":
|
|
143
|
+
return `${held} bg-card-active`;
|
|
144
|
+
case "flat":
|
|
145
|
+
return `${held} bg-card-flat-active`;
|
|
146
|
+
default:
|
|
147
|
+
return `${held} border-border-medium bg-card-active`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
50
150
|
const shared = "cursor-pointer focus-ring";
|
|
151
|
+
if (surface.filled) return `${shared} ${STATE_LAYER}`;
|
|
51
152
|
switch (variant) {
|
|
52
153
|
case "elevated":
|
|
53
|
-
return `${shared} hover:bg-card-hover`;
|
|
54
|
-
case "outlined":
|
|
55
|
-
return `${shared} hover:border-border-medium hover:bg-card-hover`;
|
|
154
|
+
return `${shared} hover:bg-card-hover active:bg-card-active`;
|
|
56
155
|
case "flat":
|
|
57
|
-
return `${shared} hover:bg-card-hover`;
|
|
156
|
+
return `${shared} hover:bg-card-flat-hover active:bg-card-flat-active`;
|
|
58
157
|
default:
|
|
59
|
-
return `${shared} hover:
|
|
158
|
+
return `${shared} hover:border-border-medium hover:bg-card-hover active:bg-card-active`;
|
|
60
159
|
}
|
|
61
160
|
});
|
|
62
161
|
|
|
162
|
+
/** A link card keeps the body colour (its whole content is not link-coloured); a scoped fill sets its own. */
|
|
163
|
+
const linkClasses = $derived(
|
|
164
|
+
isLink
|
|
165
|
+
? `block no-underline min-h-11 ${surface.scoped ? "" : "text-body"}`
|
|
166
|
+
: isButton
|
|
167
|
+
? `block text-start min-h-11 ${surface.scoped ? "" : "text-body"} ${disabled ? "cursor-not-allowed opacity-50 pointer-events-none" : ""}`
|
|
168
|
+
: "",
|
|
169
|
+
);
|
|
170
|
+
|
|
63
171
|
const cardClasses = $derived(
|
|
64
172
|
cn(
|
|
65
173
|
"rounded-container transition-all duration-150",
|
|
66
174
|
variantClasses,
|
|
67
175
|
interactiveClasses,
|
|
176
|
+
linkClasses,
|
|
68
177
|
fullWidth ? "w-full" : "",
|
|
69
178
|
sizeClass,
|
|
70
179
|
classProp,
|
|
@@ -72,6 +181,20 @@
|
|
|
72
181
|
),
|
|
73
182
|
);
|
|
74
183
|
|
|
184
|
+
const cardStyle = $derived(
|
|
185
|
+
[surface.style, typeof styleProp === "string" ? styleProp : undefined].filter(Boolean).join("; ") || undefined,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
/** A busy card is not `disabled`, so the press reaches it: it ends here, and submits nothing. */
|
|
189
|
+
function handleButtonClick(event: MouseEvent) {
|
|
190
|
+
if (isBusy) {
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
event.stopPropagation();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
onclick?.(event);
|
|
196
|
+
}
|
|
197
|
+
|
|
75
198
|
function handleKeydown(event: KeyboardEvent) {
|
|
76
199
|
if (onclick && (event.key === "Enter" || event.key === " ")) {
|
|
77
200
|
event.preventDefault();
|
|
@@ -82,14 +205,47 @@
|
|
|
82
205
|
const cardRole = $derived(onclick ? "button" : undefined);
|
|
83
206
|
</script>
|
|
84
207
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
208
|
+
{#if isLink}
|
|
209
|
+
<a
|
|
210
|
+
class={cardClasses}
|
|
211
|
+
style={cardStyle}
|
|
212
|
+
{href}
|
|
213
|
+
{target}
|
|
214
|
+
{rel}
|
|
215
|
+
{onclick}
|
|
216
|
+
aria-label={ariaLabel}
|
|
217
|
+
{...restProps as HTMLAnchorAttributes}
|
|
218
|
+
>
|
|
219
|
+
{@render children?.()}
|
|
220
|
+
</a>
|
|
221
|
+
{:else if isButton}
|
|
222
|
+
<button
|
|
223
|
+
type={buttonType}
|
|
224
|
+
{name}
|
|
225
|
+
{value}
|
|
226
|
+
{form}
|
|
227
|
+
{disabled}
|
|
228
|
+
class={cardClasses}
|
|
229
|
+
style={cardStyle}
|
|
230
|
+
aria-label={ariaLabel}
|
|
231
|
+
aria-busy={isBusy ? "true" : undefined}
|
|
232
|
+
aria-disabled={isBusy ? "true" : undefined}
|
|
233
|
+
{...restProps as HTMLAttributes<HTMLButtonElement>}
|
|
234
|
+
onclick={handleButtonClick}
|
|
235
|
+
>
|
|
236
|
+
{@render children?.()}
|
|
237
|
+
</button>
|
|
238
|
+
{:else}
|
|
239
|
+
<div
|
|
240
|
+
class={cardClasses}
|
|
241
|
+
style={cardStyle}
|
|
242
|
+
{onclick}
|
|
243
|
+
role={cardRole}
|
|
244
|
+
{...onclick ? { tabindex: 0 } : {}}
|
|
245
|
+
aria-label={ariaLabel}
|
|
246
|
+
onkeydown={onclick ? handleKeydown : undefined}
|
|
247
|
+
{...restProps}
|
|
248
|
+
>
|
|
249
|
+
{@render children?.()}
|
|
250
|
+
</div>
|
|
251
|
+
{/if}
|