zabi-components 8.1.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 +1011 -0
- package/README.md +1 -1
- package/THEME.md +18 -5
- package/THEMING.md +308 -74
- package/dist/atoms/ActionPanel.svelte +1 -1
- package/dist/atoms/Avatar.svelte +19 -3
- package/dist/atoms/Avatar.svelte.d.ts +12 -0
- package/dist/atoms/Badge.svelte +37 -8
- package/dist/atoms/Badge.svelte.d.ts +6 -0
- package/dist/atoms/Block.svelte +74 -0
- package/dist/atoms/Block.svelte.d.ts +29 -0
- package/dist/atoms/Button.svelte +52 -15
- package/dist/atoms/Button.svelte.d.ts +3 -2
- package/dist/atoms/Card.svelte +181 -25
- package/dist/atoms/Card.svelte.d.ts +55 -3
- package/dist/atoms/Checkbox.svelte +33 -0
- package/dist/atoms/Checkbox.svelte.d.ts +27 -0
- package/dist/atoms/Chip.svelte +351 -0
- package/dist/atoms/Chip.svelte.d.ts +57 -0
- package/dist/atoms/CodeField.svelte +385 -0
- package/dist/atoms/CodeField.svelte.d.ts +61 -0
- package/dist/atoms/ColorPicker.svelte +1 -0
- package/dist/atoms/DateField.svelte +41 -3
- package/dist/atoms/DateField.svelte.d.ts +31 -3
- package/dist/atoms/FloatingActionButton.svelte +5 -3
- package/dist/atoms/FloatingActionButton.svelte.d.ts +1 -1
- package/dist/atoms/Heading.svelte +27 -1
- package/dist/atoms/Heading.svelte.d.ts +7 -0
- package/dist/atoms/IconButton.svelte +81 -9
- package/dist/atoms/IconButton.svelte.d.ts +13 -0
- package/dist/atoms/Input.svelte +10 -5
- package/dist/atoms/Input.svelte.d.ts +3 -2
- package/dist/atoms/List.svelte +3 -2
- package/dist/atoms/ListItem.svelte +31 -7
- package/dist/atoms/ListItem.svelte.d.ts +6 -0
- package/dist/atoms/PickerField.svelte +239 -0
- package/dist/atoms/PickerField.svelte.d.ts +67 -0
- package/dist/atoms/Progress.svelte +78 -17
- package/dist/atoms/Progress.svelte.d.ts +11 -1
- package/dist/atoms/Radio.svelte +32 -0
- package/dist/atoms/Radio.svelte.d.ts +26 -0
- package/dist/atoms/Rating.svelte +132 -6
- package/dist/atoms/Rating.svelte.d.ts +24 -3
- package/dist/atoms/Select.svelte +24 -26
- package/dist/atoms/Select.svelte.d.ts +12 -0
- package/dist/atoms/SelectionControl.svelte +99 -6
- package/dist/atoms/SelectionControl.svelte.d.ts +25 -0
- 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 +35 -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/Stat.svelte +72 -0
- package/dist/atoms/Stat.svelte.d.ts +19 -0
- package/dist/atoms/TemporalField.svelte +342 -10
- package/dist/atoms/TemporalField.svelte.d.ts +20 -4
- package/dist/atoms/Textarea.svelte +100 -5
- package/dist/atoms/Textarea.svelte.d.ts +21 -0
- package/dist/atoms/TimeColumn.svelte +150 -0
- package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
- package/dist/atoms/TimeField.svelte +46 -3
- package/dist/atoms/TimeField.svelte.d.ts +34 -3
- package/dist/atoms/Toast.svelte +6 -5
- package/dist/atoms/Toggle.svelte +61 -10
- package/dist/atoms/Toggle.svelte.d.ts +15 -0
- package/dist/atoms/Tooltip.svelte +20 -1
- package/dist/atoms/Tooltip.svelte.d.ts +7 -0
- package/dist/atoms/index.d.ts +21 -0
- package/dist/atoms/index.js +17 -0
- package/dist/atoms/selection-control.styles.d.ts +15 -4
- package/dist/atoms/selection-control.styles.js +16 -5
- package/dist/components/atoms/index.d.ts +21 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +15 -4
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +27 -3
- 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 +7 -2
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +30 -0
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +6 -1
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +12 -3
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +55 -4
- package/dist/components/util/app-shell.d.ts.map +1 -1
- package/dist/components/util/avatar.d.ts +3 -0
- package/dist/components/util/avatar.d.ts.map +1 -1
- package/dist/components/util/bottom-tab-bar.d.ts +6 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -1
- 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 +6 -0
- package/dist/components/util/collapsible.d.ts.map +1 -1
- 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/field-control.d.ts +30 -0
- package/dist/components/util/field-control.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +6 -0
- package/dist/components/util/field.d.ts.map +1 -1
- package/dist/components/util/focus-utils.d.ts +2 -0
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- 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/photo.d.ts +19 -0
- package/dist/components/util/photo.d.ts.map +1 -1
- package/dist/components/util/progress.d.ts +19 -0
- package/dist/components/util/progress.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/sheet-drag.d.ts +25 -1
- package/dist/components/util/sheet-drag.d.ts.map +1 -1
- 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/temporal-field.d.ts +89 -0
- package/dist/components/util/temporal-field.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +40 -0
- package/dist/components/util/toaster.d.ts.map +1 -1
- 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/top-navbar.d.ts +26 -2
- package/dist/components/util/top-navbar.d.ts.map +1 -1
- package/dist/components/util/zabi-strings.d.ts +9 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -1
- package/dist/create-theme/cli.js +6 -2
- package/dist/create-theme/index.d.ts +17 -3
- package/dist/create-theme/index.js +252 -35
- package/dist/create-theme/lib/resolve.js +97 -0
- package/dist/create-theme/theme-data.js +1618 -90
- package/dist/index.d.ts +27 -3
- package/dist/index.js +18 -0
- package/dist/map.d.ts +10 -0
- package/dist/map.js +9 -0
- package/dist/molecules/Alert.svelte +31 -12
- package/dist/molecules/Alert.svelte.d.ts +12 -1
- package/dist/molecules/AppBar.svelte +287 -28
- package/dist/molecules/AppBar.svelte.d.ts +62 -0
- package/dist/molecules/AvatarGroup.svelte +1 -1
- package/dist/molecules/BarChart.svelte +381 -0
- package/dist/molecules/BarChart.svelte.d.ts +45 -0
- package/dist/molecules/BottomSheet.svelte +88 -20
- package/dist/molecules/BottomSheet.svelte.d.ts +17 -1
- package/dist/molecules/BottomTabBar.svelte +134 -28
- package/dist/molecules/BottomTabBar.svelte.d.ts +36 -2
- package/dist/molecules/Calendar.svelte.d.ts +1 -1
- package/dist/molecules/ChipGroup.svelte +325 -0
- package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
- package/dist/molecules/Collapsible.svelte +158 -54
- package/dist/molecules/Collapsible.svelte.d.ts +23 -6
- package/dist/molecules/CollapsibleGroup.svelte +16 -6
- package/dist/molecules/ConfirmDialog.svelte +5 -1
- package/dist/molecules/ConfirmDialog.svelte.d.ts +3 -0
- package/dist/molecules/Drawer.svelte +33 -12
- package/dist/molecules/Dropdown.svelte +9 -1
- package/dist/molecules/Dropdown.svelte.d.ts +2 -0
- package/dist/molecules/DropdownItem.svelte +1 -1
- package/dist/molecules/ImageUpload.svelte +230 -120
- package/dist/molecules/ImageUpload.svelte.d.ts +38 -3
- 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/Modal.svelte +11 -6
- package/dist/molecules/PhotoGrid.svelte +100 -10
- package/dist/molecules/PhotoGrid.svelte.d.ts +11 -0
- package/dist/molecules/PhotoViewer.svelte +121 -24
- package/dist/molecules/PhotoViewer.svelte.d.ts +18 -0
- package/dist/molecules/PieChart.svelte +421 -0
- package/dist/molecules/PieChart.svelte.d.ts +51 -0
- package/dist/molecules/RadioGroup.svelte +20 -4
- package/dist/molecules/RadioGroup.svelte.d.ts +5 -0
- package/dist/molecules/SegmentedControl.svelte +136 -3
- package/dist/molecules/SegmentedControl.svelte.d.ts +6 -0
- package/dist/molecules/SidebarFooter.svelte +1 -1
- package/dist/molecules/SidebarNavSection.svelte +1 -1
- package/dist/molecules/SortableList.svelte +1 -0
- package/dist/molecules/Stepper.svelte +21 -7
- package/dist/molecules/StickyActionBar.svelte +67 -8
- package/dist/molecules/StickyActionBar.svelte.d.ts +14 -1
- package/dist/molecules/Tabs.svelte +79 -2
- package/dist/molecules/Toaster.svelte +171 -7
- package/dist/molecules/Toaster.svelte.d.ts +60 -2
- package/dist/molecules/ToasterToast.svelte +292 -60
- package/dist/molecules/ToasterToast.svelte.d.ts +6 -0
- package/dist/molecules/UnsavedChangesBar.svelte +13 -2
- package/dist/molecules/index.d.ts +7 -2
- package/dist/molecules/index.js +4 -0
- package/dist/molecules/toast-store.d.ts +30 -0
- package/dist/molecules/toast-store.js +4 -1
- package/dist/organisms/AppNavigation.svelte +440 -0
- package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
- package/dist/organisms/AppShell.svelte +539 -39
- package/dist/organisms/AppShell.svelte.d.ts +102 -6
- package/dist/organisms/AppShellHeaderPart.svelte +163 -0
- package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
- package/dist/organisms/SidebarNavigation.svelte +23 -36
- package/dist/organisms/SidebarPanel.svelte +10 -14
- package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
- package/dist/organisms/SidebarShell.svelte +2 -3
- package/dist/organisms/TopNavbar.svelte +117 -28
- package/dist/organisms/TopNavbar.svelte.d.ts +22 -1
- package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
- package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
- package/dist/organisms/index.d.ts +6 -1
- package/dist/organisms/index.js +4 -0
- package/dist/types/components.d.ts +17 -1
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +497 -10
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +14 -3
- package/dist/util/app-shell.d.ts +55 -4
- package/dist/util/app-shell.js +32 -7
- package/dist/util/avatar.d.ts +3 -0
- package/dist/util/bottom-tab-bar.d.ts +6 -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 +6 -0
- package/dist/util/display-voice.d.ts +9 -0
- package/dist/util/display-voice.js +9 -0
- package/dist/util/field-control.d.ts +29 -0
- package/dist/util/field-control.js +32 -0
- package/dist/util/field.d.ts +6 -0
- package/dist/util/focus-utils.d.ts +2 -0
- package/dist/util/focus-utils.js +4 -0
- package/dist/util/map-view.d.ts +143 -0
- package/dist/util/map-view.js +121 -0
- package/dist/util/photo.d.ts +19 -0
- package/dist/util/photo.js +3 -0
- package/dist/util/progress.d.ts +18 -0
- package/dist/util/progress.js +17 -0
- package/dist/util/scroll-edge.d.ts +32 -0
- package/dist/util/scroll-edge.js +87 -0
- package/dist/util/sheet-drag.d.ts +25 -1
- package/dist/util/sheet-drag.js +71 -1
- 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/temporal-field.d.ts +88 -0
- package/dist/util/temporal-field.js +136 -0
- package/dist/util/toaster.d.ts +40 -0
- package/dist/util/toaster.js +55 -0
- package/dist/util/tone-surface.d.ts +33 -0
- package/dist/util/tone-surface.js +35 -0
- package/dist/util/top-navbar.d.ts +26 -2
- package/dist/util/top-navbar.js +23 -0
- package/dist/util/zabi-strings.d.ts +9 -0
- package/dist/zabi-components-colors.css +9 -3
- package/dist/zabi-components-theme-dark-only.css +474 -164
- package/dist/zabi-components-theme-dark.css +474 -164
- package/dist/zabi-components-theme-only.css +933 -99
- package/dist/zabi-components-theme.css +933 -99
- package/dist/zabi-components.css +2275 -321
- package/package.json +11 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from "svelte/elements";
|
|
3
3
|
import Badge from "../atoms/Badge.svelte";
|
|
4
|
-
import {
|
|
4
|
+
import { getAppShell, isDevBuild } from "../util/app-shell.js";
|
|
5
5
|
import {
|
|
6
6
|
activeTabHref,
|
|
7
7
|
badgeText,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
type BottomTabBarItem,
|
|
10
10
|
} from "../util/bottom-tab-bar.js";
|
|
11
11
|
import { cn } from "../util/cn.js";
|
|
12
|
+
import { resolveScrolledUnder, watchScrollEdge, type ScrollEdgeMode } from "../util/scroll-edge.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The navigation bar at the bottom of a phone screen: three to five
|
|
@@ -19,11 +20,11 @@
|
|
|
19
20
|
*
|
|
20
21
|
* On its own the bar is fixed over the page and reserves no room. Give the
|
|
21
22
|
* page `padding-bottom` and `scroll-padding-bottom` of the bar's height
|
|
22
|
-
* (`calc(
|
|
23
|
+
* (`calc(64px + env(safe-area-inset-bottom))`), or the end of the
|
|
23
24
|
* content, and a focused control there, sits under it. `AppShell` does
|
|
24
25
|
* this for you.
|
|
25
26
|
*
|
|
26
|
-
* The bar is one row,
|
|
27
|
+
* The bar is one row, 64px tall, at every width and text size. It is
|
|
27
28
|
* chrome: when the reader enlarges text, the room belongs to the page.
|
|
28
29
|
* So its sizes are in px; a label is one line, grows with the text only
|
|
29
30
|
* up to 1.3 times its size and no further than its tab has room for, and
|
|
@@ -31,6 +32,14 @@
|
|
|
31
32
|
* fit. Where a tab is narrower than 52px (five tabs below 260px) the bar
|
|
32
33
|
* shows icons only. The link is always named by its full label.
|
|
33
34
|
*
|
|
35
|
+
* The bar is flush with the page and page-coloured at rest; once content
|
|
36
|
+
* is under it (not at the end of what scrolls) it turns to frosted glass
|
|
37
|
+
* with a hairline above it. Inside an `AppShell` the shell says when; on
|
|
38
|
+
* its own the bar watches the window.
|
|
39
|
+
*
|
|
40
|
+
* With `floating` the bar is a glass capsule inset from the screen edges
|
|
41
|
+
* instead: always glass, since content is always under it.
|
|
42
|
+
*
|
|
34
43
|
* ```svelte
|
|
35
44
|
* <BottomTabBar
|
|
36
45
|
* items={[
|
|
@@ -52,6 +61,15 @@
|
|
|
52
61
|
* SvelteKit pass `page.url.pathname`, so the server marks it too.
|
|
53
62
|
*/
|
|
54
63
|
active?: string;
|
|
64
|
+
/**
|
|
65
|
+
* The href or path of the tab a person has just pressed, while its
|
|
66
|
+
* page is still on its way. It takes the mark (the pill, the heavier
|
|
67
|
+
* icon, the semibold label) at once, but `aria-current="page"` stays
|
|
68
|
+
* on the tab for `active` until `active` changes, and the pending link
|
|
69
|
+
* has `aria-busy`. Left out, or when it matches no tab, the mark and
|
|
70
|
+
* `aria-current` are on the same tab, as before.
|
|
71
|
+
*/
|
|
72
|
+
pending?: string;
|
|
55
73
|
/** Accessible name of the navigation landmark. */
|
|
56
74
|
label?: string;
|
|
57
75
|
/**
|
|
@@ -67,23 +85,54 @@
|
|
|
67
85
|
* an `AppShell`, which places the bar itself.
|
|
68
86
|
*/
|
|
69
87
|
position?: "fixed" | "static";
|
|
88
|
+
/**
|
|
89
|
+
* When the bar shows the glass and the hairline. `auto` follows the
|
|
90
|
+
* scroll position: flush at the end of the content, glass while
|
|
91
|
+
* content is under it. `always` is glass all the time, `never` flush
|
|
92
|
+
* all the time.
|
|
93
|
+
*/
|
|
94
|
+
scrollEdge?: ScrollEdgeMode;
|
|
95
|
+
/**
|
|
96
|
+
* Draws the bar as a rounded capsule of glass, inset 12px from the
|
|
97
|
+
* screen edges (and from the home indicator), instead of edge to edge.
|
|
98
|
+
* Always the regular material, because content is always under it, so
|
|
99
|
+
* `scrollEdge` has no effect. The height a shell measures includes
|
|
100
|
+
* the 12px. The landmark, the safe-area insets and the position stay
|
|
101
|
+
* on the `<nav>`.
|
|
102
|
+
*/
|
|
103
|
+
floating?: boolean;
|
|
70
104
|
class?: string;
|
|
71
105
|
};
|
|
72
106
|
|
|
73
107
|
let {
|
|
74
108
|
items,
|
|
75
109
|
active,
|
|
110
|
+
pending,
|
|
76
111
|
label = "Main",
|
|
77
112
|
badgeLabel = defaultBadgeLabel,
|
|
78
113
|
badgeMax = 99,
|
|
79
114
|
position,
|
|
115
|
+
scrollEdge = "auto",
|
|
116
|
+
floating = false,
|
|
80
117
|
class: className = "",
|
|
81
118
|
...restProps
|
|
82
119
|
}: Props = $props();
|
|
83
120
|
|
|
84
|
-
const
|
|
121
|
+
const shell = getAppShell();
|
|
122
|
+
const inShell = shell !== undefined;
|
|
85
123
|
const resolvedPosition = $derived(position ?? (inShell ? "static" : "fixed"));
|
|
86
124
|
|
|
125
|
+
let host: HTMLElement | undefined = $state();
|
|
126
|
+
/** Content is under the bar: from the shell if there is one, else from the window. */
|
|
127
|
+
let ownScrolled = $state(false);
|
|
128
|
+
$effect(() => {
|
|
129
|
+
if (shell || scrollEdge !== "auto" || !host) return;
|
|
130
|
+
return watchScrollEdge(null, (edge) => (ownScrolled = edge.bottom));
|
|
131
|
+
});
|
|
132
|
+
const scrolledUnder = $derived(
|
|
133
|
+
resolveScrolledUnder(scrollEdge, shell ? shell.scrolledBottom : ownScrolled),
|
|
134
|
+
);
|
|
135
|
+
|
|
87
136
|
/**
|
|
88
137
|
* The page's own path, used while `active` is not given. Empty on the
|
|
89
138
|
* server and until the bar has mounted: nothing here may read `location`
|
|
@@ -110,6 +159,11 @@
|
|
|
110
159
|
});
|
|
111
160
|
|
|
112
161
|
const activeHref = $derived(activeTabHref(items, active ?? locationPath));
|
|
162
|
+
const pendingHref = $derived(
|
|
163
|
+
pending !== undefined && pending !== "" ? activeTabHref(items, pending) : undefined,
|
|
164
|
+
);
|
|
165
|
+
/** The tab that wears the mark: the pending one, else the current one. */
|
|
166
|
+
const markHref = $derived(pendingHref ?? activeHref);
|
|
113
167
|
|
|
114
168
|
$effect(() => {
|
|
115
169
|
if (!isDevBuild()) return;
|
|
@@ -135,7 +189,7 @@
|
|
|
135
189
|
// the 4px. No gap between pill and label: 32px and a 16px line in a
|
|
136
190
|
// 56px box leave the same 4px above and below as at the sides.
|
|
137
191
|
const base =
|
|
138
|
-
"focus-ring focus-ring--nav flex min-h-14 w-full min-w-0 flex-col items-center justify-center gap-0 rounded-[20px] px-[
|
|
192
|
+
"focus-ring focus-ring--nav flex min-h-14 w-full min-w-0 flex-col items-center justify-center gap-0 rounded-[20px] px-[2px] text-center no-underline transition-colors duration-(--duration-base) motion-reduce:transition-none";
|
|
139
193
|
return isActive
|
|
140
194
|
? cn(base, "font-semibold text-nav-menu-item-active")
|
|
141
195
|
: cn(
|
|
@@ -146,13 +200,22 @@
|
|
|
146
200
|
</script>
|
|
147
201
|
|
|
148
202
|
<nav
|
|
203
|
+
bind:this={host}
|
|
149
204
|
aria-label={label}
|
|
150
205
|
data-position={resolvedPosition}
|
|
206
|
+
data-bar-edge="bottom"
|
|
207
|
+
data-scrolled-under={String(scrolledUnder)}
|
|
208
|
+
data-floating={floating ? "true" : undefined}
|
|
151
209
|
class={cn(
|
|
152
|
-
"tabbar
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
210
|
+
"tabbar [--spacing:4px]",
|
|
211
|
+
floating
|
|
212
|
+
? // Transparent: the capsule inside carries the glass. The gaps around
|
|
213
|
+
// it let clicks through to the page.
|
|
214
|
+
"pointer-events-none pb-[calc(env(safe-area-inset-bottom)+12px)] pl-[calc(env(safe-area-inset-left)+12px)] pr-[calc(env(safe-area-inset-right)+12px)]"
|
|
215
|
+
: // Clear of the home indicator, and of the rounded corners in landscape.
|
|
216
|
+
"material-bar pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
|
|
217
|
+
// The layer of the material is placed against the bar, so it is always positioned.
|
|
218
|
+
resolvedPosition === "fixed" ? "fixed inset-x-0 bottom-0 z-sticky" : "relative",
|
|
156
219
|
className,
|
|
157
220
|
)}
|
|
158
221
|
style:--tabbar-count={items.length}
|
|
@@ -161,10 +224,26 @@
|
|
|
161
224
|
<!-- 8px between the tabs and at the sides while every tab can be 52px
|
|
162
225
|
wide; less as the bar narrows, down to none, so the tabs keep the width a
|
|
163
226
|
label needs for as long as there is any. See the style block. -->
|
|
227
|
+
{#if floating}
|
|
228
|
+
<!-- Radius: the tab's box is 20px and sits the list's gap (8px, less
|
|
229
|
+
in a narrow bar) inside the capsule on all four sides, so the capsule
|
|
230
|
+
is 20px plus that gap: outer radius = inner radius + padding. See
|
|
231
|
+
`.tabbar-capsule` in the style block. -->
|
|
232
|
+
<div class="tabbar-capsule material-layer-regular pointer-events-auto relative">
|
|
233
|
+
{@render tabs()}
|
|
234
|
+
</div>
|
|
235
|
+
{:else}
|
|
236
|
+
{@render tabs()}
|
|
237
|
+
{/if}
|
|
238
|
+
</nav>
|
|
239
|
+
|
|
240
|
+
{#snippet tabs()}
|
|
164
241
|
<ul class="tabbar-list m-0 flex list-none items-stretch py-1">
|
|
165
242
|
{#each items as item (item.href)}
|
|
166
|
-
{@const isActive = item.href ===
|
|
167
|
-
{@const
|
|
243
|
+
{@const isActive = item.href === markHref}
|
|
244
|
+
{@const isCurrent = item.href === activeHref}
|
|
245
|
+
{@const isPending = pendingHref !== undefined && item.href === pendingHref && !isCurrent}
|
|
246
|
+
{@const Icon = isActive && item.activeIcon ? item.activeIcon : item.icon}
|
|
168
247
|
{@const count = item.badge ?? 0}
|
|
169
248
|
<li class="tabbar-tab flex">
|
|
170
249
|
<!-- The name is spelled out when there is a count: built from
|
|
@@ -174,25 +253,28 @@
|
|
|
174
253
|
<a
|
|
175
254
|
href={item.href}
|
|
176
255
|
class={linkClasses(isActive)}
|
|
177
|
-
aria-current={
|
|
256
|
+
aria-current={isCurrent ? "page" : undefined}
|
|
257
|
+
aria-busy={isPending ? "true" : undefined}
|
|
178
258
|
aria-label={count > 0 ? `${item.label}, ${badgeLabel(count, item)}` : undefined}
|
|
179
259
|
>
|
|
180
|
-
<!-- The pill is the active mark
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
260
|
+
<!-- The pill is the active mark, with no outline: a pill with a
|
|
261
|
+
ring is what keyboard focus looks like, and the bar would
|
|
262
|
+
seem to hold focus. Selection is not by colour alone. The
|
|
263
|
+
set of marks tells the active tab apart: the pill, the
|
|
264
|
+
icon (the filled `activeIcon`, or the same icon drawn
|
|
265
|
+
heavier), the semibold label and the active colour (4.5:1
|
|
266
|
+
for the label on the pill). WCAG 1.4.11 is met by the set,
|
|
267
|
+
not by the fill, which is within a shade of the bar. In
|
|
268
|
+
forced-colors mode the fill is dropped, so the pill gets
|
|
269
|
+
an outline in a system colour there only and the active
|
|
270
|
+
tab keeps a shape. Decorative with all it holds: the label
|
|
271
|
+
names the tab. -->
|
|
190
272
|
<span
|
|
191
273
|
aria-hidden="true"
|
|
192
274
|
class={cn(
|
|
193
|
-
"flex h-8 w-[min(56px,100%)] shrink-0 items-center justify-center rounded-pill transition-colors duration-
|
|
275
|
+
"flex h-8 w-[min(56px,100%)] shrink-0 items-center justify-center rounded-pill transition-colors duration-(--duration-base) motion-reduce:transition-none",
|
|
194
276
|
isActive &&
|
|
195
|
-
"bg-
|
|
277
|
+
"bg-tabbar-active text-nav-menu-item-active forced-colors:outline-2 forced-colors:-outline-offset-2 forced-colors:outline-[color:Highlight]",
|
|
196
278
|
)}
|
|
197
279
|
>
|
|
198
280
|
<!-- The badge is placed against the icon's own 24px
|
|
@@ -200,12 +282,17 @@
|
|
|
200
282
|
covers that corner by 8px and no more, whatever the
|
|
201
283
|
text size and however narrow the pill is. -->
|
|
202
284
|
<span class="relative flex size-6 shrink-0 items-center justify-center">
|
|
203
|
-
<Icon
|
|
285
|
+
<Icon
|
|
286
|
+
size={24}
|
|
287
|
+
class="shrink-0"
|
|
288
|
+
strokeWidth={isActive && !item.activeIcon ? 2.5 : undefined}
|
|
289
|
+
/>
|
|
204
290
|
{#if count > 0}
|
|
205
291
|
<Badge
|
|
206
292
|
size="sm"
|
|
207
293
|
emphasis="solid"
|
|
208
|
-
|
|
294
|
+
variant="error"
|
|
295
|
+
class="tabbar-badge ring-2 ring-(color:--color-bar) absolute start-4 bottom-4 h-[18px] min-h-[18px] min-w-[18px] px-[5px] py-0 text-[11px] leading-[18px] whitespace-nowrap"
|
|
209
296
|
>
|
|
210
297
|
{badgeText(count, badgeMax)}
|
|
211
298
|
</Badge>
|
|
@@ -219,7 +306,7 @@
|
|
|
219
306
|
</li>
|
|
220
307
|
{/each}
|
|
221
308
|
</ul>
|
|
222
|
-
|
|
309
|
+
{/snippet}
|
|
223
310
|
|
|
224
311
|
<style>
|
|
225
312
|
/*
|
|
@@ -238,16 +325,35 @@
|
|
|
238
325
|
* 8px apart from 308px up (a 320px phone included), closer below that,
|
|
239
326
|
* and touch at 260px.
|
|
240
327
|
*/
|
|
241
|
-
.tabbar-list
|
|
328
|
+
.tabbar-list,
|
|
329
|
+
.tabbar-capsule {
|
|
242
330
|
--tabbar-gap: clamp(
|
|
243
331
|
0px,
|
|
244
332
|
calc((100cqi - var(--tabbar-count) * 52px) / (var(--tabbar-count) + 1)),
|
|
245
333
|
8px
|
|
246
334
|
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.tabbar-list {
|
|
247
338
|
gap: var(--tabbar-gap);
|
|
248
339
|
padding-inline: var(--tabbar-gap);
|
|
249
340
|
}
|
|
250
341
|
|
|
342
|
+
/*
|
|
343
|
+
* The floating capsule and the tabs inside it are nested rounded shapes,
|
|
344
|
+
* so they are concentric: the tabs sit the same distance inside the
|
|
345
|
+
* capsule above and below as at its two ends (the gap, in place of the
|
|
346
|
+
* edge-to-edge bar's 4px), and the capsule's radius is the tab's 20px
|
|
347
|
+
* plus that distance. At 8px the capsule is 72px tall with a 28px corner.
|
|
348
|
+
*/
|
|
349
|
+
.tabbar-capsule {
|
|
350
|
+
border-radius: calc(20px + var(--tabbar-gap));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.tabbar-capsule .tabbar-list {
|
|
354
|
+
padding-block: var(--tabbar-gap);
|
|
355
|
+
}
|
|
356
|
+
|
|
251
357
|
/*
|
|
252
358
|
* A tab is at least 44px wide for as long as that many fit the bar. When
|
|
253
359
|
* they do not (five tabs below 220px), they share the bar edge to edge:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { HTMLAttributes } from "svelte/elements";
|
|
2
2
|
import { type BottomTabBarItem } from "../util/bottom-tab-bar.js";
|
|
3
|
+
import { type ScrollEdgeMode } from "../util/scroll-edge.js";
|
|
3
4
|
/**
|
|
4
5
|
* The navigation bar at the bottom of a phone screen: three to five
|
|
5
6
|
* destinations, each an icon over a short label, within reach of a thumb.
|
|
@@ -9,11 +10,11 @@ import { type BottomTabBarItem } from "../util/bottom-tab-bar.js";
|
|
|
9
10
|
*
|
|
10
11
|
* On its own the bar is fixed over the page and reserves no room. Give the
|
|
11
12
|
* page `padding-bottom` and `scroll-padding-bottom` of the bar's height
|
|
12
|
-
* (`calc(
|
|
13
|
+
* (`calc(64px + env(safe-area-inset-bottom))`), or the end of the
|
|
13
14
|
* content, and a focused control there, sits under it. `AppShell` does
|
|
14
15
|
* this for you.
|
|
15
16
|
*
|
|
16
|
-
* The bar is one row,
|
|
17
|
+
* The bar is one row, 64px tall, at every width and text size. It is
|
|
17
18
|
* chrome: when the reader enlarges text, the room belongs to the page.
|
|
18
19
|
* So its sizes are in px; a label is one line, grows with the text only
|
|
19
20
|
* up to 1.3 times its size and no further than its tab has room for, and
|
|
@@ -21,6 +22,14 @@ import { type BottomTabBarItem } from "../util/bottom-tab-bar.js";
|
|
|
21
22
|
* fit. Where a tab is narrower than 52px (five tabs below 260px) the bar
|
|
22
23
|
* shows icons only. The link is always named by its full label.
|
|
23
24
|
*
|
|
25
|
+
* The bar is flush with the page and page-coloured at rest; once content
|
|
26
|
+
* is under it (not at the end of what scrolls) it turns to frosted glass
|
|
27
|
+
* with a hairline above it. Inside an `AppShell` the shell says when; on
|
|
28
|
+
* its own the bar watches the window.
|
|
29
|
+
*
|
|
30
|
+
* With `floating` the bar is a glass capsule inset from the screen edges
|
|
31
|
+
* instead: always glass, since content is always under it.
|
|
32
|
+
*
|
|
24
33
|
* ```svelte
|
|
25
34
|
* <BottomTabBar
|
|
26
35
|
* items={[
|
|
@@ -42,6 +51,15 @@ type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
|
42
51
|
* SvelteKit pass `page.url.pathname`, so the server marks it too.
|
|
43
52
|
*/
|
|
44
53
|
active?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The href or path of the tab a person has just pressed, while its
|
|
56
|
+
* page is still on its way. It takes the mark (the pill, the heavier
|
|
57
|
+
* icon, the semibold label) at once, but `aria-current="page"` stays
|
|
58
|
+
* on the tab for `active` until `active` changes, and the pending link
|
|
59
|
+
* has `aria-busy`. Left out, or when it matches no tab, the mark and
|
|
60
|
+
* `aria-current` are on the same tab, as before.
|
|
61
|
+
*/
|
|
62
|
+
pending?: string;
|
|
45
63
|
/** Accessible name of the navigation landmark. */
|
|
46
64
|
label?: string;
|
|
47
65
|
/**
|
|
@@ -57,6 +75,22 @@ type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
|
57
75
|
* an `AppShell`, which places the bar itself.
|
|
58
76
|
*/
|
|
59
77
|
position?: "fixed" | "static";
|
|
78
|
+
/**
|
|
79
|
+
* When the bar shows the glass and the hairline. `auto` follows the
|
|
80
|
+
* scroll position: flush at the end of the content, glass while
|
|
81
|
+
* content is under it. `always` is glass all the time, `never` flush
|
|
82
|
+
* all the time.
|
|
83
|
+
*/
|
|
84
|
+
scrollEdge?: ScrollEdgeMode;
|
|
85
|
+
/**
|
|
86
|
+
* Draws the bar as a rounded capsule of glass, inset 12px from the
|
|
87
|
+
* screen edges (and from the home indicator), instead of edge to edge.
|
|
88
|
+
* Always the regular material, because content is always under it, so
|
|
89
|
+
* `scrollEdge` has no effect. The height a shell measures includes
|
|
90
|
+
* the 12px. The landmark, the safe-area insets and the position stay
|
|
91
|
+
* on the `<nav>`.
|
|
92
|
+
*/
|
|
93
|
+
floating?: boolean;
|
|
60
94
|
class?: string;
|
|
61
95
|
};
|
|
62
96
|
declare const BottomTabBar: import("svelte").Component<Props, {}, "">;
|
|
@@ -67,6 +67,6 @@ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "onselect"> & {
|
|
|
67
67
|
/** Extra classes for the host element. */
|
|
68
68
|
class?: string;
|
|
69
69
|
};
|
|
70
|
-
declare const Calendar: import("svelte").Component<Props, {}, "
|
|
70
|
+
declare const Calendar: import("svelte").Component<Props, {}, "selected" | "month">;
|
|
71
71
|
type Calendar = ReturnType<typeof Calendar>;
|
|
72
72
|
export default Calendar;
|