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,56 +1,152 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
|
|
2
4
|
import { cn } from "../util/cn.js";
|
|
5
|
+
import { mergeStrings } from "../util/ready-made-strings.js";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_SKELETON_STRINGS,
|
|
8
|
+
inSkeletonGroup,
|
|
9
|
+
skeletonLineCount,
|
|
10
|
+
type SkeletonStrings,
|
|
11
|
+
} from "../util/skeleton.js";
|
|
12
|
+
import { zabiStringsFor } from "../util/zabi-strings.js";
|
|
3
13
|
|
|
4
14
|
type Variant = "text" | "circle" | "block";
|
|
15
|
+
type Tone = "neutral" | "on-color";
|
|
5
16
|
|
|
6
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Anything else a `div` takes (`id`, `data-testid`, `style`, `aria-*`) goes to the piece, or to the
|
|
19
|
+
* paragraph's wrapper with `lines`. `role`, `aria-busy`, `aria-hidden` and `data-skeleton` are set by
|
|
20
|
+
* the component and are not overridden.
|
|
21
|
+
*/
|
|
22
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-busy" | "aria-hidden"> & {
|
|
7
23
|
variant?: Variant;
|
|
8
24
|
/** CSS length or px number; omit to use variant default (circle stays square). */
|
|
9
25
|
width?: string | number;
|
|
10
|
-
/** CSS length or px number; omit for variant default height. */
|
|
26
|
+
/** CSS length or px number; omit for variant default height. With `lines`, the height of each line. */
|
|
11
27
|
height?: string | number;
|
|
12
28
|
class?: string;
|
|
29
|
+
/** The status label. Defaults to the provider's `skeleton.label`, then "Loading…". */
|
|
13
30
|
"aria-label"?: string;
|
|
14
|
-
|
|
31
|
+
/** Overrides for the built-in strings. */
|
|
32
|
+
strings?: Partial<SkeletonStrings>;
|
|
33
|
+
/**
|
|
34
|
+
* Draws the piece only: no role, no label, hidden from assistive tech.
|
|
35
|
+
* For a piece beside a status of your own. A piece under a `SkeletonGroup` is decorative already.
|
|
36
|
+
*/
|
|
37
|
+
decorative?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* "neutral" is the quiet fill for a page or a card. "on-color" is the label colour of
|
|
40
|
+
* the surface it sits on at a low strength, for a brand, accent or fill block (`on-brand`,
|
|
41
|
+
* `on-accent`, `on-fill`, a `Card` with `tone`), in light and dark.
|
|
42
|
+
*/
|
|
43
|
+
tone?: Tone;
|
|
44
|
+
/**
|
|
45
|
+
* Text variant only: draws this many lines (1 to 20) as one placeholder, the last one shorter.
|
|
46
|
+
* `class` and `width` then apply to the whole paragraph, `height` to each line.
|
|
47
|
+
*/
|
|
48
|
+
lines?: number;
|
|
49
|
+
};
|
|
15
50
|
|
|
16
51
|
let {
|
|
17
52
|
variant = "text",
|
|
18
53
|
width,
|
|
19
54
|
height,
|
|
20
55
|
class: className = "",
|
|
21
|
-
"aria-label": ariaLabel
|
|
56
|
+
"aria-label": ariaLabel,
|
|
57
|
+
strings,
|
|
58
|
+
decorative = false,
|
|
59
|
+
tone = "neutral",
|
|
60
|
+
lines,
|
|
61
|
+
style,
|
|
62
|
+
...restProps
|
|
22
63
|
}: Props = $props();
|
|
23
64
|
|
|
65
|
+
/** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
|
|
66
|
+
const provided = zabiStringsFor("skeleton");
|
|
67
|
+
const text = $derived(mergeStrings(DEFAULT_SKELETON_STRINGS, provided(), strings));
|
|
68
|
+
|
|
69
|
+
const grouped = inSkeletonGroup();
|
|
70
|
+
const hidden = $derived(decorative || grouped);
|
|
71
|
+
|
|
24
72
|
const radius: Record<Variant, string> = {
|
|
25
73
|
text: "rounded-control",
|
|
26
74
|
circle: "rounded-full",
|
|
27
75
|
block: "rounded-control",
|
|
28
76
|
};
|
|
29
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The variant's own box, at zero specificity (`:where()`): any size class at
|
|
80
|
+
* the call site wins over it in the stylesheet, whether `cn()` knows its name
|
|
81
|
+
* (`size-14`) or not (`size-sticker`, an app's own step).
|
|
82
|
+
*/
|
|
30
83
|
const defaultShape: Record<Variant, string> = {
|
|
31
|
-
text: "w-full h-3",
|
|
32
|
-
circle: "w-10 aspect-square",
|
|
33
|
-
block: "w-full h-32",
|
|
84
|
+
text: "[:where(&)]:w-full [:where(&)]:h-3",
|
|
85
|
+
circle: "[:where(&)]:w-10 aspect-square",
|
|
86
|
+
block: "[:where(&)]:w-full [:where(&)]:h-32",
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const fill: Record<Tone, string> = {
|
|
90
|
+
neutral: "bg-neutral-subtle",
|
|
91
|
+
"on-color": "bg-skeleton-on-color",
|
|
34
92
|
};
|
|
35
93
|
|
|
36
94
|
function toSize(value: string | number): string {
|
|
37
95
|
return typeof value === "number" ? `${value}px` : value;
|
|
38
96
|
}
|
|
39
97
|
|
|
98
|
+
/** The component's own inline styles first, then the caller's `style`, which wins where both set a property. */
|
|
99
|
+
function joinStyles(...parts: Array<string | null | undefined>): string | undefined {
|
|
100
|
+
const joined = parts
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
.map((part) => String(part).replace(/;\s*$/, ""))
|
|
103
|
+
.join("; ");
|
|
104
|
+
return joined || undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
40
107
|
const inlineStyle = $derived(
|
|
41
|
-
|
|
108
|
+
joinStyles(
|
|
42
109
|
width !== undefined ? `width: ${toSize(width)}` : null,
|
|
43
110
|
height !== undefined ? `height: ${toSize(height)}` : null,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.join("; "),
|
|
111
|
+
style,
|
|
112
|
+
),
|
|
47
113
|
);
|
|
114
|
+
|
|
115
|
+
const lineCount = $derived(variant === "text" ? skeletonLineCount(lines) : 0);
|
|
116
|
+
const lineStyle = $derived(height !== undefined ? `height: ${toSize(height)}` : undefined);
|
|
117
|
+
const wrapperStyle = $derived(joinStyles(width !== undefined ? `width: ${toSize(width)}` : null, style));
|
|
118
|
+
const pieceClass = $derived(cn("animate-pulse motion-reduce:animate-none", fill[tone], radius.text));
|
|
48
119
|
</script>
|
|
49
120
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
121
|
+
{#if lineCount > 0}
|
|
122
|
+
<div
|
|
123
|
+
{...restProps}
|
|
124
|
+
class={cn("flex w-full flex-col gap-2", className)}
|
|
125
|
+
style={wrapperStyle}
|
|
126
|
+
data-skeleton-paragraph
|
|
127
|
+
role={hidden ? undefined : "status"}
|
|
128
|
+
aria-busy={hidden ? undefined : "true"}
|
|
129
|
+
aria-label={hidden ? undefined : (ariaLabel ?? text.label)}
|
|
130
|
+
aria-hidden={hidden ? "true" : undefined}
|
|
131
|
+
>
|
|
132
|
+
{#each { length: lineCount } as _, index (index)}
|
|
133
|
+
<div
|
|
134
|
+
class={cn(pieceClass, "h-3", index === lineCount - 1 && lineCount > 1 ? "w-3/5" : "w-full")}
|
|
135
|
+
style={lineStyle}
|
|
136
|
+
data-skeleton="text"
|
|
137
|
+
data-skeleton-line
|
|
138
|
+
></div>
|
|
139
|
+
{/each}
|
|
140
|
+
</div>
|
|
141
|
+
{:else}
|
|
142
|
+
<div
|
|
143
|
+
{...restProps}
|
|
144
|
+
class={cn("animate-pulse motion-reduce:animate-none", fill[tone], defaultShape[variant], radius[variant], className)}
|
|
145
|
+
style={inlineStyle}
|
|
146
|
+
data-skeleton={variant}
|
|
147
|
+
role={hidden ? undefined : "status"}
|
|
148
|
+
aria-busy={hidden ? undefined : "true"}
|
|
149
|
+
aria-label={hidden ? undefined : (ariaLabel ?? text.label)}
|
|
150
|
+
aria-hidden={hidden ? "true" : undefined}
|
|
151
|
+
></div>
|
|
152
|
+
{/if}
|
|
@@ -1,13 +1,40 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import { type SkeletonStrings } from "../util/skeleton.js";
|
|
1
3
|
type Variant = "text" | "circle" | "block";
|
|
2
|
-
|
|
4
|
+
type Tone = "neutral" | "on-color";
|
|
5
|
+
/**
|
|
6
|
+
* Anything else a `div` takes (`id`, `data-testid`, `style`, `aria-*`) goes to the piece, or to the
|
|
7
|
+
* paragraph's wrapper with `lines`. `role`, `aria-busy`, `aria-hidden` and `data-skeleton` are set by
|
|
8
|
+
* the component and are not overridden.
|
|
9
|
+
*/
|
|
10
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-busy" | "aria-hidden"> & {
|
|
3
11
|
variant?: Variant;
|
|
4
12
|
/** CSS length or px number; omit to use variant default (circle stays square). */
|
|
5
13
|
width?: string | number;
|
|
6
|
-
/** CSS length or px number; omit for variant default height. */
|
|
14
|
+
/** CSS length or px number; omit for variant default height. With `lines`, the height of each line. */
|
|
7
15
|
height?: string | number;
|
|
8
16
|
class?: string;
|
|
17
|
+
/** The status label. Defaults to the provider's `skeleton.label`, then "Loading…". */
|
|
9
18
|
"aria-label"?: string;
|
|
10
|
-
|
|
19
|
+
/** Overrides for the built-in strings. */
|
|
20
|
+
strings?: Partial<SkeletonStrings>;
|
|
21
|
+
/**
|
|
22
|
+
* Draws the piece only: no role, no label, hidden from assistive tech.
|
|
23
|
+
* For a piece beside a status of your own. A piece under a `SkeletonGroup` is decorative already.
|
|
24
|
+
*/
|
|
25
|
+
decorative?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* "neutral" is the quiet fill for a page or a card. "on-color" is the label colour of
|
|
28
|
+
* the surface it sits on at a low strength, for a brand, accent or fill block (`on-brand`,
|
|
29
|
+
* `on-accent`, `on-fill`, a `Card` with `tone`), in light and dark.
|
|
30
|
+
*/
|
|
31
|
+
tone?: Tone;
|
|
32
|
+
/**
|
|
33
|
+
* Text variant only: draws this many lines (1 to 20) as one placeholder, the last one shorter.
|
|
34
|
+
* `class` and `width` then apply to the whole paragraph, `height` to each line.
|
|
35
|
+
*/
|
|
36
|
+
lines?: number;
|
|
37
|
+
};
|
|
11
38
|
declare const Skeleton: import("svelte").Component<Props, {}, "">;
|
|
12
39
|
type Skeleton = ReturnType<typeof Skeleton>;
|
|
13
40
|
export default Skeleton;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
6
|
+
import { mergeStrings } from "../util/ready-made-strings.js";
|
|
7
|
+
import { DEFAULT_SKELETON_STRINGS, provideSkeletonGroup, type SkeletonStrings } from "../util/skeleton.js";
|
|
8
|
+
import { zabiStringsFor } from "../util/zabi-strings.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One loading status for a set of `Skeleton` pieces. The group is the
|
|
12
|
+
* `role="status"` region with the label; every `Skeleton` inside it is
|
|
13
|
+
* hidden from assistive tech, so a screen's eight pieces are announced
|
|
14
|
+
* once, not eight times.
|
|
15
|
+
*/
|
|
16
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-busy"> & {
|
|
17
|
+
/** The status label. Defaults to the provider's `skeleton.label`, then "Loading…". */
|
|
18
|
+
"aria-label"?: string;
|
|
19
|
+
/** Overrides for the built-in strings. */
|
|
20
|
+
strings?: Partial<SkeletonStrings>;
|
|
21
|
+
class?: string;
|
|
22
|
+
children?: Snippet;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let { "aria-label": ariaLabel, strings, class: className = "", children, ...restProps }: Props = $props();
|
|
26
|
+
|
|
27
|
+
provideSkeletonGroup();
|
|
28
|
+
|
|
29
|
+
/** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
|
|
30
|
+
const provided = zabiStringsFor("skeleton");
|
|
31
|
+
const text = $derived(mergeStrings(DEFAULT_SKELETON_STRINGS, provided(), strings));
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<div
|
|
35
|
+
{...restProps}
|
|
36
|
+
class={cn(className)}
|
|
37
|
+
role="status"
|
|
38
|
+
aria-busy="true"
|
|
39
|
+
aria-label={ariaLabel ?? text.label}
|
|
40
|
+
data-skeleton-group
|
|
41
|
+
>
|
|
42
|
+
{@render children?.()}
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import { type SkeletonStrings } from "../util/skeleton.js";
|
|
4
|
+
/**
|
|
5
|
+
* One loading status for a set of `Skeleton` pieces. The group is the
|
|
6
|
+
* `role="status"` region with the label; every `Skeleton` inside it is
|
|
7
|
+
* hidden from assistive tech, so a screen's eight pieces are announced
|
|
8
|
+
* once, not eight times.
|
|
9
|
+
*/
|
|
10
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-busy"> & {
|
|
11
|
+
/** The status label. Defaults to the provider's `skeleton.label`, then "Loading…". */
|
|
12
|
+
"aria-label"?: string;
|
|
13
|
+
/** Overrides for the built-in strings. */
|
|
14
|
+
strings?: Partial<SkeletonStrings>;
|
|
15
|
+
class?: string;
|
|
16
|
+
children?: Snippet;
|
|
17
|
+
};
|
|
18
|
+
declare const SkeletonGroup: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type SkeletonGroup = ReturnType<typeof SkeletonGroup>;
|
|
20
|
+
export default SkeletonGroup;
|
package/dist/atoms/Slider.svelte
CHANGED
|
@@ -295,6 +295,41 @@
|
|
|
295
295
|
transition: box-shadow 150ms;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
/* Forced colours: the fills are gone and a gradient is not trusted to stay,
|
|
299
|
+
so the filled part is `Highlight` on a `Canvas` track with a `ButtonText`
|
|
300
|
+
edge, and a disabled slider is `GrayText`. The thumb keeps its ring. */
|
|
301
|
+
@media (forced-colors: active) {
|
|
302
|
+
.slider {
|
|
303
|
+
--zabi-slider-fill: Highlight;
|
|
304
|
+
--zabi-slider-rest: Canvas;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.slider:disabled {
|
|
308
|
+
--zabi-slider-fill: GrayText;
|
|
309
|
+
--zabi-slider-rest: Canvas;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.slider::-webkit-slider-runnable-track {
|
|
313
|
+
box-sizing: border-box;
|
|
314
|
+
border: 1px solid ButtonText;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.slider::-moz-range-track {
|
|
318
|
+
box-sizing: border-box;
|
|
319
|
+
border: 1px solid ButtonText;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.slider:disabled::-webkit-slider-runnable-track,
|
|
323
|
+
.slider:disabled::-moz-range-track {
|
|
324
|
+
border-color: GrayText;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.slider::-webkit-slider-thumb,
|
|
328
|
+
.slider::-moz-range-thumb {
|
|
329
|
+
background: Canvas;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
298
333
|
@media (prefers-reduced-motion: reduce) {
|
|
299
334
|
.slider::-webkit-slider-thumb {
|
|
300
335
|
-webkit-transition: none;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { SVGAttributes } from "svelte/elements";
|
|
3
|
+
import { cn } from "../util/cn.js";
|
|
4
|
+
import { chartColor, linePath, type ChartColor } from "../util/chart.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A small line with no axes, for a trend beside a figure or in a table
|
|
8
|
+
* cell. Plain SVG, an image named by `label`; it has no table of its
|
|
9
|
+
* own, so say what the number is in the text next to it.
|
|
10
|
+
*
|
|
11
|
+
* It is 5rem by 1.5rem; size it with `class` (`w-full h-8`). The line
|
|
12
|
+
* stretches to the box and keeps its thickness. A dot marks the last
|
|
13
|
+
* value: the one the figure beside it is.
|
|
14
|
+
*/
|
|
15
|
+
type Props = Omit<SVGAttributes<SVGSVGElement>, "class" | "points" | "min" | "max" | "color"> & {
|
|
16
|
+
/** The values, in order. `null` leaves a gap. */
|
|
17
|
+
points: Array<number | null>;
|
|
18
|
+
/** What the image is called ("Results, last eight weeks: rising"). Without it the line is decoration, hidden from assistive technology. */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** A theme colour by name or any CSS colour. Without it, the first series colour. */
|
|
21
|
+
color?: ChartColor;
|
|
22
|
+
/** The bottom of the scale. Without it, the lowest value. */
|
|
23
|
+
min?: number;
|
|
24
|
+
/** The top of the scale. Without it, the highest value. */
|
|
25
|
+
max?: number;
|
|
26
|
+
class?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let { points, label, color, min, max, class: className = "", ...restProps }: Props = $props();
|
|
30
|
+
|
|
31
|
+
const isValue = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
|
|
32
|
+
|
|
33
|
+
const placed = $derived.by(() => {
|
|
34
|
+
const values = points.filter(isValue);
|
|
35
|
+
if (!values.length) return [];
|
|
36
|
+
const low = isValue(min) ? min : Math.min(...values);
|
|
37
|
+
const high = isValue(max) ? max : Math.max(...values);
|
|
38
|
+
const span = high - low;
|
|
39
|
+
const last = Math.max(points.length - 1, 1);
|
|
40
|
+
// 100 by 100, with room above and below for the stroke; the box stretches it.
|
|
41
|
+
return points.map((value, index) =>
|
|
42
|
+
isValue(value)
|
|
43
|
+
? {
|
|
44
|
+
x: points.length === 1 ? 50 : (index / last) * 100,
|
|
45
|
+
y: span > 0 ? 92 - (Math.min(Math.max(value, low), high) - low) / span * 84 : 50,
|
|
46
|
+
}
|
|
47
|
+
: null,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
const path = $derived(linePath(placed));
|
|
51
|
+
const single = $derived(points.filter(isValue).length === 1);
|
|
52
|
+
/** One value alone is a dash, not a dot that stretches. */
|
|
53
|
+
const d = $derived(single && path ? "M0 50L100 50" : path);
|
|
54
|
+
/**
|
|
55
|
+
* The last value, as a line of no length with a round end: the box may
|
|
56
|
+
* stretch the drawing, and a circle would stretch with it.
|
|
57
|
+
*/
|
|
58
|
+
const end = $derived.by(() => {
|
|
59
|
+
const at = single ? { x: 100, y: 50 } : [...placed].reverse().find((point) => point !== null);
|
|
60
|
+
return at ? `M${Math.round(at.x * 100) / 100} ${Math.round(at.y * 100) / 100}h0` : "";
|
|
61
|
+
});
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<svg
|
|
65
|
+
class={cn("zabi-sparkline inline-block h-6 w-20 align-middle", className)}
|
|
66
|
+
viewBox="0 0 100 100"
|
|
67
|
+
preserveAspectRatio="none"
|
|
68
|
+
role={label ? "img" : undefined}
|
|
69
|
+
aria-label={label || undefined}
|
|
70
|
+
aria-hidden={label ? undefined : "true"}
|
|
71
|
+
focusable="false"
|
|
72
|
+
style:--_c={chartColor(color, 0)}
|
|
73
|
+
{...restProps}
|
|
74
|
+
>
|
|
75
|
+
<path class="line" {d} />
|
|
76
|
+
{#if end}<path class="end" d={end} />{/if}
|
|
77
|
+
</svg>
|
|
78
|
+
|
|
79
|
+
<style>
|
|
80
|
+
/* The colour an app may set, on the line or on anything around it; read
|
|
81
|
+
with a fallback (in util/chart.ts) and never declared here: --zabi-chart-1 */
|
|
82
|
+
.zabi-sparkline {
|
|
83
|
+
overflow: visible;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.line {
|
|
87
|
+
fill: none;
|
|
88
|
+
stroke: var(--_c);
|
|
89
|
+
stroke-width: 2;
|
|
90
|
+
stroke-linecap: round;
|
|
91
|
+
stroke-linejoin: round;
|
|
92
|
+
vector-effect: non-scaling-stroke;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.end {
|
|
96
|
+
fill: none;
|
|
97
|
+
stroke: var(--_c);
|
|
98
|
+
stroke-width: 6;
|
|
99
|
+
stroke-linecap: round;
|
|
100
|
+
vector-effect: non-scaling-stroke;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media (forced-colors: active) {
|
|
104
|
+
.line,
|
|
105
|
+
.end {
|
|
106
|
+
stroke: CanvasText;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SVGAttributes } from "svelte/elements";
|
|
2
|
+
import { type ChartColor } from "../util/chart.js";
|
|
3
|
+
/**
|
|
4
|
+
* A small line with no axes, for a trend beside a figure or in a table
|
|
5
|
+
* cell. Plain SVG, an image named by `label`; it has no table of its
|
|
6
|
+
* own, so say what the number is in the text next to it.
|
|
7
|
+
*
|
|
8
|
+
* It is 5rem by 1.5rem; size it with `class` (`w-full h-8`). The line
|
|
9
|
+
* stretches to the box and keeps its thickness. A dot marks the last
|
|
10
|
+
* value: the one the figure beside it is.
|
|
11
|
+
*/
|
|
12
|
+
type Props = Omit<SVGAttributes<SVGSVGElement>, "class" | "points" | "min" | "max" | "color"> & {
|
|
13
|
+
/** The values, in order. `null` leaves a gap. */
|
|
14
|
+
points: Array<number | null>;
|
|
15
|
+
/** What the image is called ("Results, last eight weeks: rising"). Without it the line is decoration, hidden from assistive technology. */
|
|
16
|
+
label?: string;
|
|
17
|
+
/** A theme colour by name or any CSS colour. Without it, the first series colour. */
|
|
18
|
+
color?: ChartColor;
|
|
19
|
+
/** The bottom of the scale. Without it, the lowest value. */
|
|
20
|
+
min?: number;
|
|
21
|
+
/** The top of the scale. Without it, the highest value. */
|
|
22
|
+
max?: number;
|
|
23
|
+
class?: string;
|
|
24
|
+
};
|
|
25
|
+
declare const Sparkline: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
type Sparkline = ReturnType<typeof Sparkline>;
|
|
27
|
+
export default Sparkline;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
7
|
+
/**
|
|
8
|
+
* The caption that names the quotation ("Säg så här i luren"), read
|
|
9
|
+
* before it. A string, or a snippet for a caption in the app's own
|
|
10
|
+
* text style.
|
|
11
|
+
*/
|
|
12
|
+
label?: string | Snippet;
|
|
13
|
+
/** Keeps the caption for assistive technology and takes it off the screen. */
|
|
14
|
+
labelHidden?: boolean;
|
|
15
|
+
/** The side the tail is on: `end` (default) or `start`. Follows the text direction. */
|
|
16
|
+
side?: "end" | "start";
|
|
17
|
+
/**
|
|
18
|
+
* `ink` (default): the headline colour as fill, the page colour as
|
|
19
|
+
* text, so it is dark in light and light in dark. `paper`: the two
|
|
20
|
+
* swapped, for a bubble on a filled block. A pair of your own:
|
|
21
|
+
* `--zabi-bubble-fill` and `--zabi-bubble-text`, which win over both.
|
|
22
|
+
*/
|
|
23
|
+
tone?: "ink" | "paper";
|
|
24
|
+
/**
|
|
25
|
+
* A drawing on the top corner of the tail's side, half over the edge
|
|
26
|
+
* (a sparkle, a quotation mark). Hidden from assistive technology;
|
|
27
|
+
* the app sizes and colours what it passes.
|
|
28
|
+
*/
|
|
29
|
+
decoration?: Snippet;
|
|
30
|
+
class?: string;
|
|
31
|
+
/** The quoted text: phrasing content or paragraphs. */
|
|
32
|
+
children?: Snippet;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
label,
|
|
37
|
+
labelHidden = false,
|
|
38
|
+
side = "end",
|
|
39
|
+
tone = "ink",
|
|
40
|
+
decoration,
|
|
41
|
+
class: className = "",
|
|
42
|
+
children,
|
|
43
|
+
...restProps
|
|
44
|
+
}: Props = $props();
|
|
45
|
+
|
|
46
|
+
/* The type is in utility classes, so a call-site class can replace it;
|
|
47
|
+
the quotation inherits it. */
|
|
48
|
+
const hostClasses = $derived(cn("speech-bubble flex flex-col gap-2 text-lg font-medium", className));
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<figure
|
|
52
|
+
class={hostClasses}
|
|
53
|
+
data-side={side === "start" ? "start" : "end"}
|
|
54
|
+
data-tone={tone === "paper" ? "paper" : "ink"}
|
|
55
|
+
{...restProps}
|
|
56
|
+
>
|
|
57
|
+
{#if label}
|
|
58
|
+
<figcaption class={cn("text-description text-sm font-normal", labelHidden && "sr-only")}>
|
|
59
|
+
{#if typeof label === "string"}{label}{:else}{@render label()}{/if}
|
|
60
|
+
</figcaption>
|
|
61
|
+
{/if}
|
|
62
|
+
<blockquote class="quote">
|
|
63
|
+
<span class="art" aria-hidden="true">
|
|
64
|
+
<span class="fill"></span>
|
|
65
|
+
<svg class="tail" viewBox="0 0 30 24" focusable="false">
|
|
66
|
+
<path d="M1 0C1 9 9 20 29 23 22 18 19 10 21 0Z" />
|
|
67
|
+
</svg>
|
|
68
|
+
</span>
|
|
69
|
+
{#if decoration}
|
|
70
|
+
<span class="decoration" aria-hidden="true">{@render decoration()}</span>
|
|
71
|
+
{/if}
|
|
72
|
+
{@render children?.()}
|
|
73
|
+
</blockquote>
|
|
74
|
+
</figure>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
/*
|
|
78
|
+
* What an app may set, on the component or on anything around it (they
|
|
79
|
+
* inherit); read with a fallback and never declared here:
|
|
80
|
+
* --zabi-bubble-fill the bubble and its tail; default by `tone`
|
|
81
|
+
* --zabi-bubble-text the quoted text; default by `tone`
|
|
82
|
+
* --zabi-bubble-radius any border-radius value, the eight-value form
|
|
83
|
+
* included; default 1.5rem
|
|
84
|
+
* --zabi-bubble-tilt turns the fill and the tail, never the text;
|
|
85
|
+
* default 0deg
|
|
86
|
+
* Inside a filled block (`on-brand`, `on-accent`, `on-fill`) the headline
|
|
87
|
+
* role is the block's label colour; `--zabi-theme-headline` is the
|
|
88
|
+
* theme's own, kept by the block's parent, so the bubble is the same
|
|
89
|
+
* object on a block as on the page.
|
|
90
|
+
*/
|
|
91
|
+
.quote {
|
|
92
|
+
--_bubble-ink: var(--zabi-theme-headline, var(--color-headline));
|
|
93
|
+
--_bubble-paper: var(--color-surface-base);
|
|
94
|
+
--_bubble-fill: var(--zabi-bubble-fill, var(--_bubble-ink));
|
|
95
|
+
position: relative;
|
|
96
|
+
isolation: isolate;
|
|
97
|
+
/* The tail hangs 0.8rem under the bubble; this keeps it off what follows. */
|
|
98
|
+
margin: 0 0 1rem;
|
|
99
|
+
padding: 1.125rem 1.25rem 1.25rem;
|
|
100
|
+
color: var(--zabi-bubble-text, var(--_bubble-paper));
|
|
101
|
+
font-style: normal;
|
|
102
|
+
overflow-wrap: anywhere;
|
|
103
|
+
-webkit-user-select: text;
|
|
104
|
+
-moz-user-select: text;
|
|
105
|
+
user-select: text;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.speech-bubble[data-tone="paper"] .quote {
|
|
109
|
+
--_bubble-fill: var(--zabi-bubble-fill, var(--_bubble-paper));
|
|
110
|
+
color: var(--zabi-bubble-text, var(--_bubble-ink));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.art {
|
|
114
|
+
position: absolute;
|
|
115
|
+
inset: 0;
|
|
116
|
+
z-index: -1;
|
|
117
|
+
pointer-events: none;
|
|
118
|
+
rotate: var(--zabi-bubble-tilt, 0deg);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.fill {
|
|
122
|
+
position: absolute;
|
|
123
|
+
inset: 0;
|
|
124
|
+
border-radius: var(--zabi-bubble-radius, 1.5rem);
|
|
125
|
+
background-color: var(--_bubble-fill);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* A drawn curl that turns outward, towards the side it is on. Its top
|
|
129
|
+
0.4rem is under the fill; all of it is in rem, so it grows with the text. */
|
|
130
|
+
.tail {
|
|
131
|
+
position: absolute;
|
|
132
|
+
inset-block-start: calc(100% - 0.4rem);
|
|
133
|
+
inset-inline-end: 1.5rem;
|
|
134
|
+
inline-size: 1.5rem;
|
|
135
|
+
block-size: 1.2rem;
|
|
136
|
+
fill: var(--_bubble-fill);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.decoration {
|
|
140
|
+
position: absolute;
|
|
141
|
+
inset-block-start: 0;
|
|
142
|
+
inset-inline-end: 0;
|
|
143
|
+
display: flex;
|
|
144
|
+
line-height: 0;
|
|
145
|
+
pointer-events: none;
|
|
146
|
+
translate: 25% -55%;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.speech-bubble[data-side="start"] .tail {
|
|
150
|
+
inset-inline: 1.5rem auto;
|
|
151
|
+
scale: -1 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.speech-bubble[data-side="start"] .decoration {
|
|
155
|
+
inset-inline: 0 auto;
|
|
156
|
+
translate: -25% -55%;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* The path is drawn turning right; the other three cases follow from it. */
|
|
160
|
+
.speech-bubble:dir(rtl) .tail {
|
|
161
|
+
scale: -1 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.speech-bubble:dir(rtl) .decoration {
|
|
165
|
+
translate: -25% -55%;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.speech-bubble[data-side="start"]:dir(rtl) .tail {
|
|
169
|
+
scale: none;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.speech-bubble[data-side="start"]:dir(rtl) .decoration {
|
|
173
|
+
translate: 25% -55%;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Forced colours drop the fill: the shape is an outline in the text
|
|
177
|
+
colour, tilt and radius kept, and the tail is not drawn. */
|
|
178
|
+
@media (forced-colors: active) {
|
|
179
|
+
.fill {
|
|
180
|
+
outline: 0.125rem solid CanvasText;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.tail {
|
|
184
|
+
display: none;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
4
|
+
/**
|
|
5
|
+
* The caption that names the quotation ("Säg så här i luren"), read
|
|
6
|
+
* before it. A string, or a snippet for a caption in the app's own
|
|
7
|
+
* text style.
|
|
8
|
+
*/
|
|
9
|
+
label?: string | Snippet;
|
|
10
|
+
/** Keeps the caption for assistive technology and takes it off the screen. */
|
|
11
|
+
labelHidden?: boolean;
|
|
12
|
+
/** The side the tail is on: `end` (default) or `start`. Follows the text direction. */
|
|
13
|
+
side?: "end" | "start";
|
|
14
|
+
/**
|
|
15
|
+
* `ink` (default): the headline colour as fill, the page colour as
|
|
16
|
+
* text, so it is dark in light and light in dark. `paper`: the two
|
|
17
|
+
* swapped, for a bubble on a filled block. A pair of your own:
|
|
18
|
+
* `--zabi-bubble-fill` and `--zabi-bubble-text`, which win over both.
|
|
19
|
+
*/
|
|
20
|
+
tone?: "ink" | "paper";
|
|
21
|
+
/**
|
|
22
|
+
* A drawing on the top corner of the tail's side, half over the edge
|
|
23
|
+
* (a sparkle, a quotation mark). Hidden from assistive technology;
|
|
24
|
+
* the app sizes and colours what it passes.
|
|
25
|
+
*/
|
|
26
|
+
decoration?: Snippet;
|
|
27
|
+
class?: string;
|
|
28
|
+
/** The quoted text: phrasing content or paragraphs. */
|
|
29
|
+
children?: Snippet;
|
|
30
|
+
};
|
|
31
|
+
declare const SpeechBubble: import("svelte").Component<Props, {}, "">;
|
|
32
|
+
type SpeechBubble = ReturnType<typeof SpeechBubble>;
|
|
33
|
+
export default SpeechBubble;
|