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
package/dist/atoms/Avatar.svelte
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import User from "@lucide/svelte/icons/user";
|
|
3
|
-
import { untrack } from "svelte";
|
|
3
|
+
import { untrack, type Snippet } from "svelte";
|
|
4
4
|
import type { HTMLAttributes } from "svelte/elements";
|
|
5
5
|
import { initialsOf, type AvatarSize } from "../util/avatar.js";
|
|
6
6
|
import { cn } from "../util/cn.js";
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* and the icon inside are presentational. Not interactive: put it in a
|
|
13
13
|
* link or a button of your own.
|
|
14
14
|
*
|
|
15
|
+
* A picture that is not an address (an inline drawing that reads the
|
|
16
|
+
* page's tokens) goes in as children and fills the circle in place of the
|
|
17
|
+
* initials and `src`.
|
|
18
|
+
*
|
|
15
19
|
* Other attributes (`id`, `data-*`, ...) land on the root element.
|
|
16
20
|
*/
|
|
17
21
|
type Props = Omit<HTMLAttributes<HTMLSpanElement>, "class" | "role" | "aria-label"> & {
|
|
@@ -32,6 +36,13 @@
|
|
|
32
36
|
* read: pass it where that matters.
|
|
33
37
|
*/
|
|
34
38
|
locale?: string;
|
|
39
|
+
/**
|
|
40
|
+
* The picture, drawn inside the circle at the avatar's size: an inline
|
|
41
|
+
* SVG, say. It takes the place of `src` and the initials, is
|
|
42
|
+
* presentational (the name is still `name`), and is clipped to the
|
|
43
|
+
* circle.
|
|
44
|
+
*/
|
|
45
|
+
children?: Snippet;
|
|
35
46
|
/** Extra classes for the root element. */
|
|
36
47
|
class?: string;
|
|
37
48
|
};
|
|
@@ -42,6 +53,7 @@
|
|
|
42
53
|
size = "md",
|
|
43
54
|
alt,
|
|
44
55
|
locale,
|
|
56
|
+
children,
|
|
45
57
|
class: className = "",
|
|
46
58
|
...restProps
|
|
47
59
|
}: Props = $props();
|
|
@@ -86,12 +98,16 @@ the fill is gone and a transparent border is drawn. -->
|
|
|
86
98
|
>
|
|
87
99
|
<!-- Under the picture from the start, in the server's markup too: nothing
|
|
88
100
|
moves when the picture arrives, or when it does not. -->
|
|
89
|
-
{#if
|
|
101
|
+
{#if children}
|
|
102
|
+
<span class="absolute inset-0 flex items-center justify-center" aria-hidden="true" data-avatar-picture>
|
|
103
|
+
{@render children()}
|
|
104
|
+
</span>
|
|
105
|
+
{:else if initials}
|
|
90
106
|
<span aria-hidden="true" data-avatar-initials>{initials}</span>
|
|
91
107
|
{:else}
|
|
92
108
|
<User class="size-[60%]" aria-hidden="true" />
|
|
93
109
|
{/if}
|
|
94
|
-
{#if src && !failed}
|
|
110
|
+
{#if !children && src && !failed}
|
|
95
111
|
<img
|
|
96
112
|
{src}
|
|
97
113
|
alt=""
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Snippet } from "svelte";
|
|
1
2
|
import type { HTMLAttributes } from "svelte/elements";
|
|
2
3
|
/**
|
|
3
4
|
* A round picture of a person, which falls back to their initials.
|
|
@@ -6,6 +7,10 @@ import type { HTMLAttributes } from "svelte/elements";
|
|
|
6
7
|
* and the icon inside are presentational. Not interactive: put it in a
|
|
7
8
|
* link or a button of your own.
|
|
8
9
|
*
|
|
10
|
+
* A picture that is not an address (an inline drawing that reads the
|
|
11
|
+
* page's tokens) goes in as children and fills the circle in place of the
|
|
12
|
+
* initials and `src`.
|
|
13
|
+
*
|
|
9
14
|
* Other attributes (`id`, `data-*`, ...) land on the root element.
|
|
10
15
|
*/
|
|
11
16
|
type Props = Omit<HTMLAttributes<HTMLSpanElement>, "class" | "role" | "aria-label"> & {
|
|
@@ -26,6 +31,13 @@ type Props = Omit<HTMLAttributes<HTMLSpanElement>, "class" | "role" | "aria-labe
|
|
|
26
31
|
* read: pass it where that matters.
|
|
27
32
|
*/
|
|
28
33
|
locale?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The picture, drawn inside the circle at the avatar's size: an inline
|
|
36
|
+
* SVG, say. It takes the place of `src` and the initials, is
|
|
37
|
+
* presentational (the name is still `name`), and is clipped to the
|
|
38
|
+
* circle.
|
|
39
|
+
*/
|
|
40
|
+
children?: Snippet;
|
|
29
41
|
/** Extra classes for the root element. */
|
|
30
42
|
class?: string;
|
|
31
43
|
};
|
package/dist/atoms/Badge.svelte
CHANGED
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
variant?: BadgeVariant;
|
|
24
24
|
size?: SizeVariant;
|
|
25
25
|
emphasis?: BadgeEmphasis;
|
|
26
|
+
/**
|
|
27
|
+
* Draws the family's edge on a subtle badge. Off by default: the fill
|
|
28
|
+
* and the text already carry it, and a same-hue outline on every badge
|
|
29
|
+
* is one more line per row. Solid badges never have one.
|
|
30
|
+
*/
|
|
31
|
+
bordered?: boolean;
|
|
26
32
|
/** Label text. Ignored when `children` is provided. */
|
|
27
33
|
text?: string;
|
|
28
34
|
showIcon?: boolean;
|
|
@@ -34,6 +40,7 @@
|
|
|
34
40
|
variant = "default",
|
|
35
41
|
size = "md",
|
|
36
42
|
emphasis = "subtle",
|
|
43
|
+
bordered = false,
|
|
37
44
|
text = "",
|
|
38
45
|
showIcon = false,
|
|
39
46
|
class: className = "",
|
|
@@ -49,7 +56,9 @@
|
|
|
49
56
|
*
|
|
50
57
|
* The vertical padding is what is left of the height after one line of
|
|
51
58
|
* text and the 1px border on each side (16 + 2 + 2, 16 + 6 + 2 and
|
|
52
|
-
* 20 + 6 + 2), so a one-line badge is exactly as tall as it was.
|
|
59
|
+
* 20 + 6 + 2), so a one-line badge is exactly as tall as it was. The
|
|
60
|
+
* border box stays when the border is not drawn (transparent), so
|
|
61
|
+
* `bordered` does not move the badge by a pixel.
|
|
53
62
|
*
|
|
54
63
|
* The corner is half of that one-line height, not the pill radius: for
|
|
55
64
|
* one line that is the same full round end, and a badge of two lines
|
|
@@ -67,43 +76,63 @@
|
|
|
67
76
|
* every variant reads at the same weight. Subtle is 100/200/800 (8.2:1),
|
|
68
77
|
* solid is 600 with its `-text` foreground (4.9:1). Before the ramps were
|
|
69
78
|
* calibrated, a solid warning badge sat at 2.15:1 and energetic at 1.97:1.
|
|
79
|
+
*
|
|
80
|
+
* A subtle badge has a transparent edge unless `bordered` is set, which
|
|
81
|
+
* puts the family's `-border` step back (the 8.1 look). The class is
|
|
82
|
+
* written out for each case so Tailwind can see it.
|
|
83
|
+
*
|
|
84
|
+
* The neutral subtle badge reads `--zabi-badge-neutral-fill` and
|
|
85
|
+
* `--zabi-badge-neutral-text` (src/app.css) in front of the neutral
|
|
86
|
+
* tokens: set on any ancestor or in the theme, they move this badge and
|
|
87
|
+
* no other reader of `--color-neutral-subtle` or `--color-neutral-text`.
|
|
70
88
|
*/
|
|
71
89
|
const variantClass = $derived.by(() => {
|
|
72
90
|
const solid = emphasis === "solid";
|
|
91
|
+
const edge = (drawn: string) => (bordered ? drawn : "border-transparent");
|
|
73
92
|
switch (variant) {
|
|
74
93
|
case "success":
|
|
75
94
|
return solid
|
|
76
95
|
? "bg-success text-card border-transparent"
|
|
77
|
-
:
|
|
96
|
+
: `bg-success-subtle text-success-text ${edge("border-success-border")}`;
|
|
78
97
|
case "warning":
|
|
79
98
|
return solid
|
|
80
99
|
? "bg-warning text-card border-transparent"
|
|
81
|
-
:
|
|
100
|
+
: `bg-warning-subtle text-warning-text ${edge("border-warning-border")}`;
|
|
82
101
|
case "error":
|
|
83
102
|
return solid
|
|
84
103
|
? "bg-error text-card border-transparent"
|
|
85
|
-
:
|
|
104
|
+
: `bg-error-subtle text-error-text ${edge("border-error-border")}`;
|
|
86
105
|
case "info":
|
|
87
106
|
return solid
|
|
88
107
|
? "bg-info text-card border-transparent"
|
|
89
|
-
:
|
|
108
|
+
: `bg-info-subtle text-info-text ${edge("border-info-border")}`;
|
|
90
109
|
case "energetic":
|
|
91
110
|
return solid
|
|
92
111
|
? "bg-energetic text-card border-transparent"
|
|
93
|
-
:
|
|
112
|
+
: `bg-energetic-subtle text-energetic-text ${edge("border-energetic-border")}`;
|
|
94
113
|
// The app's second brand colour. Its solid label is the "on accent"
|
|
95
114
|
// role, not the card surface: an app with a light accent (a
|
|
96
115
|
// yellow) sets a dark label there, and white would be unreadable.
|
|
97
116
|
case "accent":
|
|
98
117
|
return solid
|
|
99
118
|
? "bg-accent text-on-accent border-transparent"
|
|
100
|
-
:
|
|
119
|
+
: `bg-accent-subtle text-accent-text ${edge("border-accent-border")}`;
|
|
120
|
+
// The primary brand colour, for the neutral-but-branded label
|
|
121
|
+
// ("Member"). Subtle text is the link role, guarded on this fill
|
|
122
|
+
// as the label of a selected pill tab; solid carries the label
|
|
123
|
+
// of the primary fill. The colour is read from the token, not
|
|
124
|
+
// through `text-link`: that class darkens on hover and while
|
|
125
|
+
// pressed, and a badge is not a link.
|
|
126
|
+
case "brand":
|
|
127
|
+
return solid
|
|
128
|
+
? "bg-action-primary text-action-primary border-transparent"
|
|
129
|
+
: `bg-action-primary-subtle text-(color:--color-link) ${edge("border-action-primary-border")}`;
|
|
101
130
|
case "neutral":
|
|
102
131
|
case "default":
|
|
103
132
|
default:
|
|
104
133
|
return solid
|
|
105
134
|
? "bg-neutral text-card border-transparent"
|
|
106
|
-
:
|
|
135
|
+
: `bg-badge-neutral text-badge-neutral ${edge("border-neutral-border")}`;
|
|
107
136
|
}
|
|
108
137
|
});
|
|
109
138
|
|
|
@@ -11,6 +11,12 @@ type Props = Omit<HTMLAttributes<HTMLSpanElement>, "class"> & {
|
|
|
11
11
|
variant?: BadgeVariant;
|
|
12
12
|
size?: SizeVariant;
|
|
13
13
|
emphasis?: BadgeEmphasis;
|
|
14
|
+
/**
|
|
15
|
+
* Draws the family's edge on a subtle badge. Off by default: the fill
|
|
16
|
+
* and the text already carry it, and a same-hue outline on every badge
|
|
17
|
+
* is one more line per row. Solid badges never have one.
|
|
18
|
+
*/
|
|
19
|
+
bordered?: boolean;
|
|
14
20
|
/** Label text. Ignored when `children` is provided. */
|
|
15
21
|
text?: string;
|
|
16
22
|
showIcon?: boolean;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import type { SurfaceTone } from "../types/variants.js";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../util/cn.js";
|
|
7
|
+
import { toneSurface } from "../util/tone-surface.js";
|
|
8
|
+
|
|
9
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
10
|
+
/**
|
|
11
|
+
* The block's fill: `neutral` (the default, the card surface), `tint`,
|
|
12
|
+
* `brand` or `accent`. On `brand` and `accent` the text, links, controls
|
|
13
|
+
* and focus rings inside take the fill's label colour.
|
|
14
|
+
*/
|
|
15
|
+
tone?: SurfaceTone;
|
|
16
|
+
/** A fill the app chooses, any CSS colour. Wins over `tone`. The app must check the contrast of `fill` and `onFill`. */
|
|
17
|
+
fill?: string;
|
|
18
|
+
/** The label colour on `fill`, any CSS colour. */
|
|
19
|
+
onFill?: string;
|
|
20
|
+
/** Inner space: `none`, `sm` (16 / 16), `md` (16 inline, 24 block; the default) or `lg` (24 inline, 32 block). */
|
|
21
|
+
padding?: "none" | "sm" | "md" | "lg";
|
|
22
|
+
/** The element: `div` by default; a landmark (`section` needs a name, `aria-label` or `aria-labelledby`). */
|
|
23
|
+
as?: "div" | "section" | "header" | "footer" | "article" | "aside";
|
|
24
|
+
/**
|
|
25
|
+
* A Block is full width and square-cornered, and does not break out of
|
|
26
|
+
* a padded parent by itself: put it outside the padded container, or
|
|
27
|
+
* give it a negative inline margin here (for example `-mx-4`).
|
|
28
|
+
*/
|
|
29
|
+
class?: string;
|
|
30
|
+
children?: Snippet;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
tone = "neutral",
|
|
35
|
+
fill,
|
|
36
|
+
onFill,
|
|
37
|
+
padding = "md",
|
|
38
|
+
as = "div",
|
|
39
|
+
class: classProp = "",
|
|
40
|
+
style: styleProp,
|
|
41
|
+
children,
|
|
42
|
+
...restProps
|
|
43
|
+
}: Props = $props();
|
|
44
|
+
|
|
45
|
+
const surface = $derived(toneSurface({ tone, fill, onFill }));
|
|
46
|
+
|
|
47
|
+
const paddingClass = $derived(
|
|
48
|
+
padding === "none" ? "" : padding === "sm" ? "px-4 py-4" : padding === "lg" ? "px-6 py-8" : "px-4 py-6",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const classes = $derived(
|
|
52
|
+
cn("block w-full rounded-none border-none shadow-none", surface.filled ? surface.classes : "bg-card", paddingClass, classProp),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const style = $derived(
|
|
56
|
+
[surface.style, typeof styleProp === "string" ? styleProp : undefined].filter(Boolean).join("; ") || undefined,
|
|
57
|
+
);
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<!-- One branch per element, not a dynamic element: hydration takes a dynamic
|
|
61
|
+
element out and puts it back (see Heading). -->
|
|
62
|
+
{#if as === "section"}
|
|
63
|
+
<section class={classes} {style} {...restProps}>{@render children?.()}</section>
|
|
64
|
+
{:else if as === "header"}
|
|
65
|
+
<header class={classes} {style} {...restProps}>{@render children?.()}</header>
|
|
66
|
+
{:else if as === "footer"}
|
|
67
|
+
<footer class={classes} {style} {...restProps}>{@render children?.()}</footer>
|
|
68
|
+
{:else if as === "article"}
|
|
69
|
+
<article class={classes} {style} {...restProps}>{@render children?.()}</article>
|
|
70
|
+
{:else if as === "aside"}
|
|
71
|
+
<aside class={classes} {style} {...restProps}>{@render children?.()}</aside>
|
|
72
|
+
{:else}
|
|
73
|
+
<div class={classes} {style} {...restProps}>{@render children?.()}</div>
|
|
74
|
+
{/if}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { SurfaceTone } from "../types/variants.js";
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
5
|
+
/**
|
|
6
|
+
* The block's fill: `neutral` (the default, the card surface), `tint`,
|
|
7
|
+
* `brand` or `accent`. On `brand` and `accent` the text, links, controls
|
|
8
|
+
* and focus rings inside take the fill's label colour.
|
|
9
|
+
*/
|
|
10
|
+
tone?: SurfaceTone;
|
|
11
|
+
/** A fill the app chooses, any CSS colour. Wins over `tone`. The app must check the contrast of `fill` and `onFill`. */
|
|
12
|
+
fill?: string;
|
|
13
|
+
/** The label colour on `fill`, any CSS colour. */
|
|
14
|
+
onFill?: string;
|
|
15
|
+
/** Inner space: `none`, `sm` (16 / 16), `md` (16 inline, 24 block; the default) or `lg` (24 inline, 32 block). */
|
|
16
|
+
padding?: "none" | "sm" | "md" | "lg";
|
|
17
|
+
/** The element: `div` by default; a landmark (`section` needs a name, `aria-label` or `aria-labelledby`). */
|
|
18
|
+
as?: "div" | "section" | "header" | "footer" | "article" | "aside";
|
|
19
|
+
/**
|
|
20
|
+
* A Block is full width and square-cornered, and does not break out of
|
|
21
|
+
* a padded parent by itself: put it outside the padded container, or
|
|
22
|
+
* give it a negative inline margin here (for example `-mx-4`).
|
|
23
|
+
*/
|
|
24
|
+
class?: string;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
};
|
|
27
|
+
declare const Block: import("svelte").Component<Props, {}, "">;
|
|
28
|
+
type Block = ReturnType<typeof Block>;
|
|
29
|
+
export default Block;
|
package/dist/atoms/Button.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { tick, untrack, type Snippet } from "svelte";
|
|
3
3
|
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
|
4
|
-
import type {
|
|
4
|
+
import type { ButtonSize, ButtonVariant } from "../types/variants.js";
|
|
5
5
|
import { cn } from "../util/cn.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
referrerpolicy?: HTMLAnchorAttributes["referrerpolicy"];
|
|
29
29
|
ping?: HTMLAnchorAttributes["ping"];
|
|
30
30
|
variant?: ButtonVariant;
|
|
31
|
-
size
|
|
31
|
+
/** `xl` is 56px, for a primary action on a phone. It is a Button size only. */
|
|
32
|
+
size?: ButtonSize;
|
|
32
33
|
/**
|
|
33
34
|
* Shows a spinner and makes the button unavailable without taking
|
|
34
35
|
* focus from it: `aria-disabled` and `aria-busy`, still a Tab stop,
|
|
@@ -117,7 +118,8 @@
|
|
|
117
118
|
|
|
118
119
|
/**
|
|
119
120
|
* One height scale, shared with Input, Select and IconButton, so controls
|
|
120
|
-
* of the same size line up in a row: 32 / 40 / 48px
|
|
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
|
|
121
123
|
* height, not a fixed one, so a label that does not fit on one line wraps
|
|
122
124
|
* and the button grows; it used to paint outside the button (a long label
|
|
123
125
|
* at 200% text on a phone). One line of text and the vertical padding are
|
|
@@ -136,6 +138,10 @@
|
|
|
136
138
|
if (size === "sm") {
|
|
137
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" };
|
|
138
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" };
|
|
144
|
+
}
|
|
139
145
|
if (size === "lg") {
|
|
140
146
|
return { box: "min-h-12 px-5 py-2", text: "text-base", gap: "gap-2", spinner: "size-4" };
|
|
141
147
|
}
|
|
@@ -160,28 +166,56 @@
|
|
|
160
166
|
*/
|
|
161
167
|
const BUSY_BUTTON = "bg-(--color-action-disabled) text-action-disabled-text cursor-not-allowed";
|
|
162
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
|
+
|
|
163
183
|
const disabledClass =
|
|
164
|
-
"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";
|
|
165
185
|
|
|
166
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 ";
|
|
167
193
|
switch (variant) {
|
|
168
194
|
case "secondary":
|
|
169
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";
|
|
170
198
|
case "danger":
|
|
171
|
-
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`;
|
|
172
200
|
case "ghost":
|
|
173
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";
|
|
174
202
|
case "outline":
|
|
175
|
-
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";
|
|
176
204
|
case "link":
|
|
177
205
|
// A link variant has to look like a link at rest, not only on
|
|
178
206
|
// hover — otherwise it is indistinguishable from `ghost`.
|
|
179
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";
|
|
180
214
|
case "accent":
|
|
181
|
-
return
|
|
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`;
|
|
182
216
|
case "primary":
|
|
183
217
|
default:
|
|
184
|
-
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`;
|
|
185
219
|
}
|
|
186
220
|
});
|
|
187
221
|
|
|
@@ -195,21 +229,24 @@
|
|
|
195
229
|
? "text-action-disabled-text no-underline cursor-not-allowed"
|
|
196
230
|
: "text-link hover:text-link-hover underline underline-offset-4 decoration-1 hover:decoration-2 cursor-pointer";
|
|
197
231
|
return cn(
|
|
198
|
-
`focus-ring focus-ring--muted inline rounded-
|
|
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}`,
|
|
199
233
|
);
|
|
200
234
|
}
|
|
201
235
|
// The label wraps when it has to, balanced over its lines; the icon
|
|
202
|
-
// and the spinner stay centred beside it.
|
|
203
|
-
|
|
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`;
|
|
204
240
|
// `disabled:` does not match an `<a>`: a disabled link wears the
|
|
205
241
|
// disabled pair itself, in place of its variant.
|
|
206
|
-
const busyBox = variant === "outline" ? "border border-transparent" : variant === "link" ? "px-0" : "";
|
|
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";
|
|
207
244
|
const state =
|
|
208
245
|
isLink && isDisabled
|
|
209
|
-
? DISABLED_LINK
|
|
246
|
+
? `${DISABLED_LINK} ${FORCED_EDGE_DISABLED}`
|
|
210
247
|
: isBusy
|
|
211
|
-
? `${BUSY_BUTTON} ${busyBox}`
|
|
212
|
-
: `${variantClass} ${disabledClass}`;
|
|
248
|
+
? `${BUSY_BUTTON} ${busyBox} ${FORCED_EDGE_DISABLED}`
|
|
249
|
+
: `${variantClass} ${disabledClass} ${drawnByFill ? FORCED_EDGE : ""} ${FORCED_EDGE_DISABLED_ONLY}`;
|
|
213
250
|
return cn(`${base} ${s.box} ${s.text} ${s.gap} ${state} ${className}`);
|
|
214
251
|
});
|
|
215
252
|
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Snippet } from "svelte";
|
|
2
2
|
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ButtonSize, ButtonVariant } from "../types/variants.js";
|
|
4
4
|
/**
|
|
5
5
|
* One type for the button and the link, not a union of the two elements'
|
|
6
6
|
* attributes: that union is too large for TypeScript to carry through a
|
|
@@ -25,7 +25,8 @@ type Props = Omit<HTMLButtonAttributes, "class"> & {
|
|
|
25
25
|
referrerpolicy?: HTMLAnchorAttributes["referrerpolicy"];
|
|
26
26
|
ping?: HTMLAnchorAttributes["ping"];
|
|
27
27
|
variant?: ButtonVariant;
|
|
28
|
-
size
|
|
28
|
+
/** `xl` is 56px, for a primary action on a phone. It is a Button size only. */
|
|
29
|
+
size?: ButtonSize;
|
|
29
30
|
/**
|
|
30
31
|
* Shows a spinner and makes the button unavailable without taking
|
|
31
32
|
* focus from it: `aria-disabled` and `aria-busy`, still a Tab stop,
|