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.
Files changed (273) hide show
  1. package/CHANGELOG.md +1011 -0
  2. package/README.md +1 -1
  3. package/THEME.md +18 -5
  4. package/THEMING.md +308 -74
  5. package/dist/atoms/ActionPanel.svelte +1 -1
  6. package/dist/atoms/Avatar.svelte +19 -3
  7. package/dist/atoms/Avatar.svelte.d.ts +12 -0
  8. package/dist/atoms/Badge.svelte +37 -8
  9. package/dist/atoms/Badge.svelte.d.ts +6 -0
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +52 -15
  13. package/dist/atoms/Button.svelte.d.ts +3 -2
  14. package/dist/atoms/Card.svelte +181 -25
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/Checkbox.svelte +33 -0
  17. package/dist/atoms/Checkbox.svelte.d.ts +27 -0
  18. package/dist/atoms/Chip.svelte +351 -0
  19. package/dist/atoms/Chip.svelte.d.ts +57 -0
  20. package/dist/atoms/CodeField.svelte +385 -0
  21. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  22. package/dist/atoms/ColorPicker.svelte +1 -0
  23. package/dist/atoms/DateField.svelte +41 -3
  24. package/dist/atoms/DateField.svelte.d.ts +31 -3
  25. package/dist/atoms/FloatingActionButton.svelte +5 -3
  26. package/dist/atoms/FloatingActionButton.svelte.d.ts +1 -1
  27. package/dist/atoms/Heading.svelte +27 -1
  28. package/dist/atoms/Heading.svelte.d.ts +7 -0
  29. package/dist/atoms/IconButton.svelte +81 -9
  30. package/dist/atoms/IconButton.svelte.d.ts +13 -0
  31. package/dist/atoms/Input.svelte +10 -5
  32. package/dist/atoms/Input.svelte.d.ts +3 -2
  33. package/dist/atoms/List.svelte +3 -2
  34. package/dist/atoms/ListItem.svelte +31 -7
  35. package/dist/atoms/ListItem.svelte.d.ts +6 -0
  36. package/dist/atoms/PickerField.svelte +239 -0
  37. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  38. package/dist/atoms/Progress.svelte +78 -17
  39. package/dist/atoms/Progress.svelte.d.ts +11 -1
  40. package/dist/atoms/Radio.svelte +32 -0
  41. package/dist/atoms/Radio.svelte.d.ts +26 -0
  42. package/dist/atoms/Rating.svelte +132 -6
  43. package/dist/atoms/Rating.svelte.d.ts +24 -3
  44. package/dist/atoms/Select.svelte +24 -26
  45. package/dist/atoms/Select.svelte.d.ts +12 -0
  46. package/dist/atoms/SelectionControl.svelte +99 -6
  47. package/dist/atoms/SelectionControl.svelte.d.ts +25 -0
  48. package/dist/atoms/Skeleton.svelte +114 -18
  49. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  50. package/dist/atoms/SkeletonGroup.svelte +43 -0
  51. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  52. package/dist/atoms/Slider.svelte +35 -0
  53. package/dist/atoms/Sparkline.svelte +109 -0
  54. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  55. package/dist/atoms/SpeechBubble.svelte +187 -0
  56. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  57. package/dist/atoms/Stat.svelte +72 -0
  58. package/dist/atoms/Stat.svelte.d.ts +19 -0
  59. package/dist/atoms/TemporalField.svelte +342 -10
  60. package/dist/atoms/TemporalField.svelte.d.ts +20 -4
  61. package/dist/atoms/Textarea.svelte +100 -5
  62. package/dist/atoms/Textarea.svelte.d.ts +21 -0
  63. package/dist/atoms/TimeColumn.svelte +150 -0
  64. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  65. package/dist/atoms/TimeField.svelte +46 -3
  66. package/dist/atoms/TimeField.svelte.d.ts +34 -3
  67. package/dist/atoms/Toast.svelte +6 -5
  68. package/dist/atoms/Toggle.svelte +61 -10
  69. package/dist/atoms/Toggle.svelte.d.ts +15 -0
  70. package/dist/atoms/Tooltip.svelte +20 -1
  71. package/dist/atoms/Tooltip.svelte.d.ts +7 -0
  72. package/dist/atoms/index.d.ts +21 -0
  73. package/dist/atoms/index.js +17 -0
  74. package/dist/atoms/selection-control.styles.d.ts +15 -4
  75. package/dist/atoms/selection-control.styles.js +16 -5
  76. package/dist/components/atoms/index.d.ts +21 -0
  77. package/dist/components/atoms/index.d.ts.map +1 -1
  78. package/dist/components/atoms/selection-control.styles.d.ts +15 -4
  79. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  80. package/dist/components/index.d.ts +27 -3
  81. package/dist/components/index.d.ts.map +1 -1
  82. package/dist/components/map.d.ts +11 -0
  83. package/dist/components/map.d.ts.map +1 -0
  84. package/dist/components/molecules/index.d.ts +7 -2
  85. package/dist/components/molecules/index.d.ts.map +1 -1
  86. package/dist/components/molecules/toast-store.d.ts +30 -0
  87. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  88. package/dist/components/organisms/index.d.ts +6 -1
  89. package/dist/components/organisms/index.d.ts.map +1 -1
  90. package/dist/components/types/variants.d.ts +12 -3
  91. package/dist/components/types/variants.d.ts.map +1 -1
  92. package/dist/components/util/app-shell.d.ts +55 -4
  93. package/dist/components/util/app-shell.d.ts.map +1 -1
  94. package/dist/components/util/avatar.d.ts +3 -0
  95. package/dist/components/util/avatar.d.ts.map +1 -1
  96. package/dist/components/util/bottom-tab-bar.d.ts +6 -0
  97. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -1
  98. package/dist/components/util/chart.d.ts +140 -0
  99. package/dist/components/util/chart.d.ts.map +1 -0
  100. package/dist/components/util/chip.d.ts +66 -0
  101. package/dist/components/util/chip.d.ts.map +1 -0
  102. package/dist/components/util/cn.d.ts.map +1 -1
  103. package/dist/components/util/code-field.d.ts +63 -0
  104. package/dist/components/util/code-field.d.ts.map +1 -0
  105. package/dist/components/util/collapsible.d.ts +6 -0
  106. package/dist/components/util/collapsible.d.ts.map +1 -1
  107. package/dist/components/util/display-voice.d.ts +10 -0
  108. package/dist/components/util/display-voice.d.ts.map +1 -0
  109. package/dist/components/util/field-control.d.ts +30 -0
  110. package/dist/components/util/field-control.d.ts.map +1 -0
  111. package/dist/components/util/field.d.ts +6 -0
  112. package/dist/components/util/field.d.ts.map +1 -1
  113. package/dist/components/util/focus-utils.d.ts +2 -0
  114. package/dist/components/util/focus-utils.d.ts.map +1 -1
  115. package/dist/components/util/map-view.d.ts +144 -0
  116. package/dist/components/util/map-view.d.ts.map +1 -0
  117. package/dist/components/util/photo.d.ts +19 -0
  118. package/dist/components/util/photo.d.ts.map +1 -1
  119. package/dist/components/util/progress.d.ts +19 -0
  120. package/dist/components/util/progress.d.ts.map +1 -0
  121. package/dist/components/util/scroll-edge.d.ts +33 -0
  122. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  123. package/dist/components/util/sheet-drag.d.ts +25 -1
  124. package/dist/components/util/sheet-drag.d.ts.map +1 -1
  125. package/dist/components/util/skeleton.d.ts +15 -0
  126. package/dist/components/util/skeleton.d.ts.map +1 -0
  127. package/dist/components/util/sliding-indicator.d.ts +25 -0
  128. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  129. package/dist/components/util/temporal-field.d.ts +89 -0
  130. package/dist/components/util/temporal-field.d.ts.map +1 -0
  131. package/dist/components/util/toaster.d.ts +40 -0
  132. package/dist/components/util/toaster.d.ts.map +1 -1
  133. package/dist/components/util/tone-surface.d.ts +34 -0
  134. package/dist/components/util/tone-surface.d.ts.map +1 -0
  135. package/dist/components/util/top-navbar.d.ts +26 -2
  136. package/dist/components/util/top-navbar.d.ts.map +1 -1
  137. package/dist/components/util/zabi-strings.d.ts +9 -0
  138. package/dist/components/util/zabi-strings.d.ts.map +1 -1
  139. package/dist/create-theme/cli.js +6 -2
  140. package/dist/create-theme/index.d.ts +17 -3
  141. package/dist/create-theme/index.js +252 -35
  142. package/dist/create-theme/lib/resolve.js +97 -0
  143. package/dist/create-theme/theme-data.js +1618 -90
  144. package/dist/index.d.ts +27 -3
  145. package/dist/index.js +18 -0
  146. package/dist/map.d.ts +10 -0
  147. package/dist/map.js +9 -0
  148. package/dist/molecules/Alert.svelte +31 -12
  149. package/dist/molecules/Alert.svelte.d.ts +12 -1
  150. package/dist/molecules/AppBar.svelte +287 -28
  151. package/dist/molecules/AppBar.svelte.d.ts +62 -0
  152. package/dist/molecules/AvatarGroup.svelte +1 -1
  153. package/dist/molecules/BarChart.svelte +381 -0
  154. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  155. package/dist/molecules/BottomSheet.svelte +88 -20
  156. package/dist/molecules/BottomSheet.svelte.d.ts +17 -1
  157. package/dist/molecules/BottomTabBar.svelte +134 -28
  158. package/dist/molecules/BottomTabBar.svelte.d.ts +36 -2
  159. package/dist/molecules/Calendar.svelte.d.ts +1 -1
  160. package/dist/molecules/ChipGroup.svelte +325 -0
  161. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  162. package/dist/molecules/Collapsible.svelte +158 -54
  163. package/dist/molecules/Collapsible.svelte.d.ts +23 -6
  164. package/dist/molecules/CollapsibleGroup.svelte +16 -6
  165. package/dist/molecules/ConfirmDialog.svelte +5 -1
  166. package/dist/molecules/ConfirmDialog.svelte.d.ts +3 -0
  167. package/dist/molecules/Drawer.svelte +33 -12
  168. package/dist/molecules/Dropdown.svelte +9 -1
  169. package/dist/molecules/Dropdown.svelte.d.ts +2 -0
  170. package/dist/molecules/DropdownItem.svelte +1 -1
  171. package/dist/molecules/ImageUpload.svelte +230 -120
  172. package/dist/molecules/ImageUpload.svelte.d.ts +38 -3
  173. package/dist/molecules/LineChart.svelte +459 -0
  174. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  175. package/dist/molecules/MapView.svelte +565 -0
  176. package/dist/molecules/MapView.svelte.d.ts +88 -0
  177. package/dist/molecules/Modal.svelte +11 -6
  178. package/dist/molecules/PhotoGrid.svelte +100 -10
  179. package/dist/molecules/PhotoGrid.svelte.d.ts +11 -0
  180. package/dist/molecules/PhotoViewer.svelte +121 -24
  181. package/dist/molecules/PhotoViewer.svelte.d.ts +18 -0
  182. package/dist/molecules/PieChart.svelte +421 -0
  183. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  184. package/dist/molecules/RadioGroup.svelte +20 -4
  185. package/dist/molecules/RadioGroup.svelte.d.ts +5 -0
  186. package/dist/molecules/SegmentedControl.svelte +136 -3
  187. package/dist/molecules/SegmentedControl.svelte.d.ts +6 -0
  188. package/dist/molecules/SidebarFooter.svelte +1 -1
  189. package/dist/molecules/SidebarNavSection.svelte +1 -1
  190. package/dist/molecules/SortableList.svelte +1 -0
  191. package/dist/molecules/Stepper.svelte +21 -7
  192. package/dist/molecules/StickyActionBar.svelte +67 -8
  193. package/dist/molecules/StickyActionBar.svelte.d.ts +14 -1
  194. package/dist/molecules/Tabs.svelte +79 -2
  195. package/dist/molecules/Toaster.svelte +171 -7
  196. package/dist/molecules/Toaster.svelte.d.ts +60 -2
  197. package/dist/molecules/ToasterToast.svelte +292 -60
  198. package/dist/molecules/ToasterToast.svelte.d.ts +6 -0
  199. package/dist/molecules/UnsavedChangesBar.svelte +13 -2
  200. package/dist/molecules/index.d.ts +7 -2
  201. package/dist/molecules/index.js +4 -0
  202. package/dist/molecules/toast-store.d.ts +30 -0
  203. package/dist/molecules/toast-store.js +4 -1
  204. package/dist/organisms/AppNavigation.svelte +440 -0
  205. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  206. package/dist/organisms/AppShell.svelte +539 -39
  207. package/dist/organisms/AppShell.svelte.d.ts +102 -6
  208. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  209. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  210. package/dist/organisms/SidebarNavigation.svelte +23 -36
  211. package/dist/organisms/SidebarPanel.svelte +10 -14
  212. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  213. package/dist/organisms/SidebarShell.svelte +2 -3
  214. package/dist/organisms/TopNavbar.svelte +117 -28
  215. package/dist/organisms/TopNavbar.svelte.d.ts +22 -1
  216. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  217. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  218. package/dist/organisms/index.d.ts +6 -1
  219. package/dist/organisms/index.js +4 -0
  220. package/dist/types/components.d.ts +17 -1
  221. package/dist/types/components.d.ts.map +1 -1
  222. package/dist/types/index.d.ts +497 -10
  223. package/dist/types/index.d.ts.map +1 -1
  224. package/dist/types/variants.ts +14 -3
  225. package/dist/util/app-shell.d.ts +55 -4
  226. package/dist/util/app-shell.js +32 -7
  227. package/dist/util/avatar.d.ts +3 -0
  228. package/dist/util/bottom-tab-bar.d.ts +6 -0
  229. package/dist/util/chart.d.ts +139 -0
  230. package/dist/util/chart.js +305 -0
  231. package/dist/util/chip.d.ts +65 -0
  232. package/dist/util/chip.js +58 -0
  233. package/dist/util/cn.js +28 -2
  234. package/dist/util/code-field.d.ts +62 -0
  235. package/dist/util/code-field.js +112 -0
  236. package/dist/util/collapsible.d.ts +6 -0
  237. package/dist/util/display-voice.d.ts +9 -0
  238. package/dist/util/display-voice.js +9 -0
  239. package/dist/util/field-control.d.ts +29 -0
  240. package/dist/util/field-control.js +32 -0
  241. package/dist/util/field.d.ts +6 -0
  242. package/dist/util/focus-utils.d.ts +2 -0
  243. package/dist/util/focus-utils.js +4 -0
  244. package/dist/util/map-view.d.ts +143 -0
  245. package/dist/util/map-view.js +121 -0
  246. package/dist/util/photo.d.ts +19 -0
  247. package/dist/util/photo.js +3 -0
  248. package/dist/util/progress.d.ts +18 -0
  249. package/dist/util/progress.js +17 -0
  250. package/dist/util/scroll-edge.d.ts +32 -0
  251. package/dist/util/scroll-edge.js +87 -0
  252. package/dist/util/sheet-drag.d.ts +25 -1
  253. package/dist/util/sheet-drag.js +71 -1
  254. package/dist/util/skeleton.d.ts +14 -0
  255. package/dist/util/skeleton.js +22 -0
  256. package/dist/util/sliding-indicator.d.ts +24 -0
  257. package/dist/util/sliding-indicator.js +44 -0
  258. package/dist/util/temporal-field.d.ts +88 -0
  259. package/dist/util/temporal-field.js +136 -0
  260. package/dist/util/toaster.d.ts +40 -0
  261. package/dist/util/toaster.js +55 -0
  262. package/dist/util/tone-surface.d.ts +33 -0
  263. package/dist/util/tone-surface.js +35 -0
  264. package/dist/util/top-navbar.d.ts +26 -2
  265. package/dist/util/top-navbar.js +23 -0
  266. package/dist/util/zabi-strings.d.ts +9 -0
  267. package/dist/zabi-components-colors.css +9 -3
  268. package/dist/zabi-components-theme-dark-only.css +474 -164
  269. package/dist/zabi-components-theme-dark.css +474 -164
  270. package/dist/zabi-components-theme-only.css +933 -99
  271. package/dist/zabi-components-theme.css +933 -99
  272. package/dist/zabi-components.css +2275 -321
  273. package/package.json +11 -1
@@ -1,18 +1,71 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
- import type { HTMLAttributes } from "svelte/elements";
4
- import type { CardVariant, SizeVariant } from "../types/variants.js";
3
+ import type { HTMLAnchorAttributes, HTMLAttributes } from "svelte/elements";
4
+ import type { CardVariant, SizeVariant, SurfaceTone } from "../types/variants.js";
5
5
 
6
6
  import { cn } from "../util/cn.js";
7
+ import { toneSurface } from "../util/tone-surface.js";
7
8
  type Props = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
8
9
  onclick?: (event: MouseEvent) => void | Promise<void>;
9
10
  size?: SizeVariant;
11
+ /** The look of a `neutral` card. With a `tone` or a `fill` the fill is the edge: no border, and a shadow only for `elevated`. */
10
12
  variant?: CardVariant;
13
+ /**
14
+ * What the card is filled with: `neutral` (the default, the card surface),
15
+ * `tint` (the brand tint), `brand` or `accent`. On `brand` and `accent`
16
+ * the text, links, controls and focus rings inside take the fill's label
17
+ * colour; a Card, field or Alert inside is a surface again.
18
+ */
19
+ tone?: SurfaceTone;
20
+ /**
21
+ * A fill the app chooses, any CSS colour (`var(--group-colour)`). Wins
22
+ * over `tone`. Set `onFill`, the label colour on it. The library cannot
23
+ * check that pair's contrast: the app must hold it to 4.5:1.
24
+ */
25
+ fill?: string;
26
+ /** The label colour on `fill`, any CSS colour. */
27
+ onFill?: string;
28
+ /**
29
+ * With `href` the card itself is an `<a>`: the whole card is the link,
30
+ * and it works without scripts. A link card must not contain other
31
+ * interactive elements (a link or a button in a link is invalid and
32
+ * unreachable); a card with actions inside takes `onclick` or none.
33
+ * With `onclick` too, it is a link and `onclick` runs on activation.
34
+ */
35
+ href?: string;
36
+ /** With `href`: where the link opens. */
37
+ target?: HTMLAnchorAttributes["target"];
38
+ /** With `href`: the link's relationship, such as `noopener`. */
39
+ rel?: string;
40
+ /**
41
+ * The card itself is a `<button>`: the whole card is the control, and
42
+ * `submit` posts the form around it without scripts. With `href`
43
+ * the card is a link and this is ignored. Like a link card it must not
44
+ * contain other interactive elements. `onclick`
45
+ * runs on a press, as on any button.
46
+ */
47
+ type?: "submit" | "button" | "reset";
48
+ /** With `type`: the name the button submits under. */
49
+ name?: string;
50
+ /** With `type`: what the button submits. */
51
+ value?: string;
52
+ /** With `type`: the id of the form it belongs to, when it is not inside one. */
53
+ form?: string;
54
+ /** With `type`: the card cannot be pressed, is dimmed, and is skipped by the keyboard. */
55
+ disabled?: boolean;
56
+ /**
57
+ * With `type`: the card's action is on its way, as `Button`'s and
58
+ * `Chip`'s `loading`. `aria-busy` and `aria-disabled` (not `disabled`),
59
+ * the press swallowed (it submits nothing and does not reach
60
+ * `onclick`), the pressed look held, the size and the Tab stop kept.
61
+ * No spinner. Ignored by a link card and a plain card.
62
+ */
63
+ loading?: boolean;
11
64
  fullWidth?: boolean;
12
65
  class?: string;
13
66
  /** @deprecated use `class` — kept so 7.x call sites keep working. */
14
67
  className?: string;
15
- /** Required when `onclick` is set (no visible label). */
68
+ /** Names the card when it is interactive and its content does not (the content names a link well enough in most cases). */
16
69
  ariaLabel?: string;
17
70
  children?: Snippet;
18
71
  };
@@ -21,10 +74,23 @@
21
74
  onclick,
22
75
  size = "md",
23
76
  variant = "default",
77
+ tone = "neutral",
78
+ fill,
79
+ onFill,
80
+ href,
81
+ target,
82
+ rel,
83
+ type: buttonType,
84
+ name,
85
+ value,
86
+ form,
87
+ disabled = false,
88
+ loading = false,
24
89
  fullWidth = true,
25
90
  class: classProp = "",
26
91
  className = "",
27
92
  ariaLabel,
93
+ style: styleProp,
28
94
  children,
29
95
  ...restProps
30
96
  }: Props = $props();
@@ -35,36 +101,79 @@
35
101
  size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6",
36
102
  );
37
103
 
104
+ const surface = $derived(toneSurface({ tone, fill, onFill }));
105
+
106
+ const isLink = $derived(href !== undefined && href !== null);
107
+ const isButton = $derived(!isLink && buttonType !== undefined);
108
+
38
109
  const variantClasses = $derived(
39
- variant === "elevated"
40
- ? "bg-card-elevated shadow-lg"
41
- : variant === "outlined"
42
- ? "bg-card border border-border shadow-none"
110
+ surface.filled
111
+ ? // The fill is the edge: no border, flat unless the variant lifts it.
112
+ `${surface.classes} ${variant === "elevated" ? "shadow-lg" : "shadow-none"}`
113
+ : variant === "elevated"
114
+ ? "bg-card-elevated shadow-lg"
43
115
  : variant === "flat"
44
- ? "bg-card shadow-none border-none"
45
- : "bg-card shadow-sm",
116
+ ? "bg-card-flat shadow-none border-none"
117
+ : "bg-card border border-border shadow-none",
46
118
  );
47
119
 
120
+ /**
121
+ * Hover and pressed on a fill the card does not know: a veil of the card's
122
+ * own text colour (`bg-current`), so it reads on a tint, the brand, the
123
+ * accent and any colour an app passes. The card is `relative` for it.
124
+ */
125
+ const STATE_LAYER =
126
+ "relative after:absolute after:inset-0 after:rounded-[inherit] after:bg-current after:opacity-0 after:pointer-events-none after:transition-opacity after:duration-(--duration-base) motion-reduce:after:transition-none hover:after:opacity-8 active:after:opacity-12";
127
+
128
+ /** Loading without `disabled`: unavailable, and still where focus is (see Button). */
129
+ const isBusy = $derived(isButton && loading && !disabled);
130
+
131
+ const interactive = $derived(!!onclick || isLink || isButton);
132
+
48
133
  const interactiveClasses = $derived.by(() => {
49
- if (!onclick) return "";
134
+ if (!interactive) return "";
135
+ if (isBusy) {
136
+ // The pressed look, held: no hover or pressed rule is left to change it.
137
+ // Forced colours drop the fills the held look is made of: GrayText text and a dashed inset edge say it there.
138
+ const held =
139
+ "cursor-progress focus-ring forced-colors:text-[color:GrayText] forced-colors:outline-1 forced-colors:outline-dashed forced-colors:-outline-offset-1 forced-colors:outline-[color:GrayText]";
140
+ if (surface.filled) return `${held} relative after:absolute after:inset-0 after:rounded-[inherit] after:bg-current after:opacity-12 after:pointer-events-none`;
141
+ switch (variant) {
142
+ case "elevated":
143
+ return `${held} bg-card-active`;
144
+ case "flat":
145
+ return `${held} bg-card-flat-active`;
146
+ default:
147
+ return `${held} border-border-medium bg-card-active`;
148
+ }
149
+ }
50
150
  const shared = "cursor-pointer focus-ring";
151
+ if (surface.filled) return `${shared} ${STATE_LAYER}`;
51
152
  switch (variant) {
52
153
  case "elevated":
53
154
  return `${shared} hover:bg-card-hover active:bg-card-active`;
54
- case "outlined":
55
- return `${shared} hover:border-border-medium hover:bg-card-hover active:bg-card-active`;
56
155
  case "flat":
57
- return `${shared} hover:bg-card-hover active:bg-card-active`;
156
+ return `${shared} hover:bg-card-flat-hover active:bg-card-flat-active`;
58
157
  default:
59
- return `${shared} hover:shadow-lg hover:bg-card-hover active:bg-card-active`;
158
+ return `${shared} hover:border-border-medium hover:bg-card-hover active:bg-card-active`;
60
159
  }
61
160
  });
62
161
 
162
+ /** A link card keeps the body colour (its whole content is not link-coloured); a scoped fill sets its own. */
163
+ const linkClasses = $derived(
164
+ isLink
165
+ ? `block no-underline min-h-11 ${surface.scoped ? "" : "text-body"}`
166
+ : isButton
167
+ ? `block text-start min-h-11 ${surface.scoped ? "" : "text-body"} ${disabled ? "cursor-not-allowed opacity-50 pointer-events-none" : ""}`
168
+ : "",
169
+ );
170
+
63
171
  const cardClasses = $derived(
64
172
  cn(
65
173
  "rounded-container transition-all duration-150",
66
174
  variantClasses,
67
175
  interactiveClasses,
176
+ linkClasses,
68
177
  fullWidth ? "w-full" : "",
69
178
  sizeClass,
70
179
  classProp,
@@ -72,6 +181,20 @@
72
181
  ),
73
182
  );
74
183
 
184
+ const cardStyle = $derived(
185
+ [surface.style, typeof styleProp === "string" ? styleProp : undefined].filter(Boolean).join("; ") || undefined,
186
+ );
187
+
188
+ /** A busy card is not `disabled`, so the press reaches it: it ends here, and submits nothing. */
189
+ function handleButtonClick(event: MouseEvent) {
190
+ if (isBusy) {
191
+ event.preventDefault();
192
+ event.stopPropagation();
193
+ return;
194
+ }
195
+ onclick?.(event);
196
+ }
197
+
75
198
  function handleKeydown(event: KeyboardEvent) {
76
199
  if (onclick && (event.key === "Enter" || event.key === " ")) {
77
200
  event.preventDefault();
@@ -82,14 +205,47 @@
82
205
  const cardRole = $derived(onclick ? "button" : undefined);
83
206
  </script>
84
207
 
85
- <div
86
- class={cardClasses}
87
- {onclick}
88
- role={cardRole}
89
- {...onclick ? { tabindex: 0 } : {}}
90
- aria-label={ariaLabel}
91
- onkeydown={onclick ? handleKeydown : undefined}
92
- {...restProps}
93
- >
94
- {@render children?.()}
95
- </div>
208
+ {#if isLink}
209
+ <a
210
+ class={cardClasses}
211
+ style={cardStyle}
212
+ {href}
213
+ {target}
214
+ {rel}
215
+ {onclick}
216
+ aria-label={ariaLabel}
217
+ {...restProps as HTMLAnchorAttributes}
218
+ >
219
+ {@render children?.()}
220
+ </a>
221
+ {:else if isButton}
222
+ <button
223
+ type={buttonType}
224
+ {name}
225
+ {value}
226
+ {form}
227
+ {disabled}
228
+ class={cardClasses}
229
+ style={cardStyle}
230
+ aria-label={ariaLabel}
231
+ aria-busy={isBusy ? "true" : undefined}
232
+ aria-disabled={isBusy ? "true" : undefined}
233
+ {...restProps as HTMLAttributes<HTMLButtonElement>}
234
+ onclick={handleButtonClick}
235
+ >
236
+ {@render children?.()}
237
+ </button>
238
+ {:else}
239
+ <div
240
+ class={cardClasses}
241
+ style={cardStyle}
242
+ {onclick}
243
+ role={cardRole}
244
+ {...onclick ? { tabindex: 0 } : {}}
245
+ aria-label={ariaLabel}
246
+ onkeydown={onclick ? handleKeydown : undefined}
247
+ {...restProps}
248
+ >
249
+ {@render children?.()}
250
+ </div>
251
+ {/if}
@@ -1,15 +1,67 @@
1
1
  import type { Snippet } from "svelte";
2
- import type { HTMLAttributes } from "svelte/elements";
3
- import type { CardVariant, SizeVariant } from "../types/variants.js";
2
+ import type { HTMLAnchorAttributes, HTMLAttributes } from "svelte/elements";
3
+ import type { CardVariant, SizeVariant, SurfaceTone } from "../types/variants.js";
4
4
  type Props = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
5
5
  onclick?: (event: MouseEvent) => void | Promise<void>;
6
6
  size?: SizeVariant;
7
+ /** The look of a `neutral` card. With a `tone` or a `fill` the fill is the edge: no border, and a shadow only for `elevated`. */
7
8
  variant?: CardVariant;
9
+ /**
10
+ * What the card is filled with: `neutral` (the default, the card surface),
11
+ * `tint` (the brand tint), `brand` or `accent`. On `brand` and `accent`
12
+ * the text, links, controls and focus rings inside take the fill's label
13
+ * colour; a Card, field or Alert inside is a surface again.
14
+ */
15
+ tone?: SurfaceTone;
16
+ /**
17
+ * A fill the app chooses, any CSS colour (`var(--group-colour)`). Wins
18
+ * over `tone`. Set `onFill`, the label colour on it. The library cannot
19
+ * check that pair's contrast: the app must hold it to 4.5:1.
20
+ */
21
+ fill?: string;
22
+ /** The label colour on `fill`, any CSS colour. */
23
+ onFill?: string;
24
+ /**
25
+ * With `href` the card itself is an `<a>`: the whole card is the link,
26
+ * and it works without scripts. A link card must not contain other
27
+ * interactive elements (a link or a button in a link is invalid and
28
+ * unreachable); a card with actions inside takes `onclick` or none.
29
+ * With `onclick` too, it is a link and `onclick` runs on activation.
30
+ */
31
+ href?: string;
32
+ /** With `href`: where the link opens. */
33
+ target?: HTMLAnchorAttributes["target"];
34
+ /** With `href`: the link's relationship, such as `noopener`. */
35
+ rel?: string;
36
+ /**
37
+ * The card itself is a `<button>`: the whole card is the control, and
38
+ * `submit` posts the form around it without scripts. With `href`
39
+ * the card is a link and this is ignored. Like a link card it must not
40
+ * contain other interactive elements. `onclick`
41
+ * runs on a press, as on any button.
42
+ */
43
+ type?: "submit" | "button" | "reset";
44
+ /** With `type`: the name the button submits under. */
45
+ name?: string;
46
+ /** With `type`: what the button submits. */
47
+ value?: string;
48
+ /** With `type`: the id of the form it belongs to, when it is not inside one. */
49
+ form?: string;
50
+ /** With `type`: the card cannot be pressed, is dimmed, and is skipped by the keyboard. */
51
+ disabled?: boolean;
52
+ /**
53
+ * With `type`: the card's action is on its way, as `Button`'s and
54
+ * `Chip`'s `loading`. `aria-busy` and `aria-disabled` (not `disabled`),
55
+ * the press swallowed (it submits nothing and does not reach
56
+ * `onclick`), the pressed look held, the size and the Tab stop kept.
57
+ * No spinner. Ignored by a link card and a plain card.
58
+ */
59
+ loading?: boolean;
8
60
  fullWidth?: boolean;
9
61
  class?: string;
10
62
  /** @deprecated use `class` — kept so 7.x call sites keep working. */
11
63
  className?: string;
12
- /** Required when `onclick` is set (no visible label). */
64
+ /** Names the card when it is interactive and its content does not (the content names a link well enough in most cases). */
13
65
  ariaLabel?: string;
14
66
  children?: Snippet;
15
67
  };
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from "svelte";
2
3
  import type { HTMLInputAttributes } from "svelte/elements";
4
+ import type { SemanticVariant } from "../types/variants.js";
3
5
  import SelectionControl from "./SelectionControl.svelte";
4
6
  import type { SelectionControlMarkProps } from "./SelectionControl.svelte";
5
7
 
@@ -20,6 +22,31 @@
20
22
  checked?: boolean;
21
23
  onChange?: (event: Event) => void;
22
24
  onchange?: (event: Event) => void;
25
+ /**
26
+ * Drawn inside the one label, between the box and the label text, as
27
+ * decoration for the name `label` already gives. A leading image
28
+ * should have an empty alt: the label names the row.
29
+ */
30
+ leading?: Snippet;
31
+ /**
32
+ * A line under the label, tied to the box with `aria-describedby`.
33
+ * It is inside the label, so a tap on it ticks the box; it does not
34
+ * join the name.
35
+ */
36
+ description?: string;
37
+ /**
38
+ * Where the box sits in the row. `end` puts it after the leading, the
39
+ * label and the description, at the far edge; the row is still one
40
+ * label and fills the width it is given.
41
+ */
42
+ controlPosition?: "start" | "end";
43
+ variant?: SemanticVariant;
44
+ /** Status text under the control. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
45
+ message?: string;
46
+ /** Help text under the control, read out with it. */
47
+ hint?: string;
48
+ /** An error under the control. It marks the input invalid and is announced; it wins over `variant` and `message`. */
49
+ error?: string;
23
50
  };
24
51
 
25
52
  let {
@@ -34,6 +61,9 @@
34
61
  checked = $bindable<Exclude<Props["checked"], undefined>>(),
35
62
  onChange,
36
63
  onchange,
64
+ leading,
65
+ description,
66
+ controlPosition = "start",
37
67
  ...restProps
38
68
  }: Props = $props();
39
69
 
@@ -95,6 +125,9 @@
95
125
  {onChange}
96
126
  {onchange}
97
127
  {mark}
128
+ {leading}
129
+ {description}
130
+ {controlPosition}
98
131
  class={className}
99
132
  {...restProps}
100
133
  />
@@ -1,4 +1,6 @@
1
+ import type { Snippet } from "svelte";
1
2
  import type { HTMLInputAttributes } from "svelte/elements";
3
+ import type { SemanticVariant } from "../types/variants.js";
2
4
  /** Other attributes (`required`, `data-*`, `aria-*`, ...) land on the `<input>`. */
3
5
  type Props = Omit<HTMLInputAttributes, "class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"> & {
4
6
  /** Extra classes for the host element. */
@@ -13,6 +15,31 @@ type Props = Omit<HTMLInputAttributes, "class" | "id" | "name" | "value" | "disa
13
15
  checked?: boolean;
14
16
  onChange?: (event: Event) => void;
15
17
  onchange?: (event: Event) => void;
18
+ /**
19
+ * Drawn inside the one label, between the box and the label text, as
20
+ * decoration for the name `label` already gives. A leading image
21
+ * should have an empty alt: the label names the row.
22
+ */
23
+ leading?: Snippet;
24
+ /**
25
+ * A line under the label, tied to the box with `aria-describedby`.
26
+ * It is inside the label, so a tap on it ticks the box; it does not
27
+ * join the name.
28
+ */
29
+ description?: string;
30
+ /**
31
+ * Where the box sits in the row. `end` puts it after the leading, the
32
+ * label and the description, at the far edge; the row is still one
33
+ * label and fills the width it is given.
34
+ */
35
+ controlPosition?: "start" | "end";
36
+ variant?: SemanticVariant;
37
+ /** Status text under the control. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
38
+ message?: string;
39
+ /** Help text under the control, read out with it. */
40
+ hint?: string;
41
+ /** An error under the control. It marks the input invalid and is announced; it wins over `variant` and `message`. */
42
+ error?: string;
16
43
  };
17
44
  declare const Checkbox: import("svelte").Component<Props, {}, "checked">;
18
45
  type Checkbox = ReturnType<typeof Checkbox>;