zabi-components 5.0.22 → 7.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 (207) hide show
  1. package/README.md +139 -1478
  2. package/THEME.md +473 -0
  3. package/THEMING.md +248 -0
  4. package/dist/atoms/ActionPanel.svelte +116 -0
  5. package/dist/atoms/ActionPanel.svelte.d.ts +19 -0
  6. package/dist/atoms/Badge.svelte +1 -1
  7. package/dist/atoms/Badge.svelte.d.ts +1 -1
  8. package/dist/atoms/Button.svelte +35 -16
  9. package/dist/atoms/Button.svelte.d.ts +3 -1
  10. package/dist/atoms/Card.svelte +49 -67
  11. package/dist/atoms/Card.svelte.d.ts +4 -4
  12. package/dist/atoms/CardHeader.svelte +15 -2
  13. package/dist/atoms/CardHeader.svelte.d.ts +5 -0
  14. package/dist/atoms/Checkbox.svelte +50 -76
  15. package/dist/atoms/Checkbox.svelte.d.ts +7 -2
  16. package/dist/atoms/CodeBlock.svelte +20 -34
  17. package/dist/atoms/CodeBlock.svelte.d.ts +1 -0
  18. package/dist/atoms/ColorPicker.svelte +3 -7
  19. package/dist/atoms/Container.svelte +41 -0
  20. package/dist/atoms/Container.svelte.d.ts +12 -0
  21. package/dist/atoms/Divider.svelte +68 -0
  22. package/dist/atoms/Divider.svelte.d.ts +13 -0
  23. package/dist/atoms/FeatureCard.svelte +35 -50
  24. package/dist/atoms/FeatureCard.svelte.d.ts +9 -4
  25. package/dist/atoms/IconButton.svelte +33 -24
  26. package/dist/atoms/IconButton.svelte.d.ts +4 -8
  27. package/dist/atoms/Input.svelte +58 -26
  28. package/dist/atoms/Input.svelte.d.ts +6 -1
  29. package/dist/atoms/List.svelte +2 -7
  30. package/dist/atoms/List.svelte.d.ts +1 -6
  31. package/dist/atoms/ListItem.svelte +54 -65
  32. package/dist/atoms/ListItem.svelte.d.ts +7 -13
  33. package/dist/atoms/ListItemLeading.svelte +19 -0
  34. package/dist/atoms/ListItemLeading.svelte.d.ts +9 -0
  35. package/dist/atoms/Progress.svelte +2 -2
  36. package/dist/atoms/Radio.svelte +51 -0
  37. package/dist/atoms/Radio.svelte.d.ts +14 -0
  38. package/dist/atoms/Select.svelte +28 -33
  39. package/dist/atoms/Select.svelte.d.ts +0 -10
  40. package/dist/atoms/SelectionControl.svelte +98 -0
  41. package/dist/atoms/SelectionControl.svelte.d.ts +26 -0
  42. package/dist/atoms/Skeleton.svelte +39 -15
  43. package/dist/atoms/Skeleton.svelte.d.ts +7 -3
  44. package/dist/atoms/Table.svelte +27 -0
  45. package/dist/atoms/Table.svelte.d.ts +10 -0
  46. package/dist/atoms/Text.svelte +46 -0
  47. package/dist/atoms/Text.svelte.d.ts +13 -0
  48. package/dist/atoms/Textarea.svelte +74 -53
  49. package/dist/atoms/Textarea.svelte.d.ts +6 -2
  50. package/dist/atoms/ThemeToggle.svelte +35 -24
  51. package/dist/atoms/ThemeToggle.svelte.d.ts +1 -0
  52. package/dist/atoms/Toast.svelte +55 -31
  53. package/dist/atoms/Toast.svelte.d.ts +4 -1
  54. package/dist/atoms/Toggle.svelte +37 -22
  55. package/dist/atoms/Toggle.svelte.d.ts +2 -1
  56. package/dist/atoms/Tooltip.svelte +90 -11
  57. package/dist/atoms/index.d.ts +6 -0
  58. package/dist/atoms/index.js +6 -0
  59. package/dist/atoms/selection-control.styles.d.ts +25 -0
  60. package/dist/atoms/selection-control.styles.js +30 -0
  61. package/dist/components/atoms/index.d.ts +6 -0
  62. package/dist/components/atoms/index.d.ts.map +1 -1
  63. package/dist/components/atoms/selection-control.styles.d.ts +26 -0
  64. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -0
  65. package/dist/components/index.d.ts +42 -7
  66. package/dist/components/index.d.ts.map +1 -1
  67. package/dist/components/molecules/index.d.ts +10 -0
  68. package/dist/components/molecules/index.d.ts.map +1 -1
  69. package/dist/components/molecules/navigation-menu-context.d.ts +11 -0
  70. package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -1
  71. package/dist/components/molecules/toast-store.d.ts +39 -0
  72. package/dist/components/molecules/toast-store.d.ts.map +1 -0
  73. package/dist/components/organisms/index.d.ts +3 -3
  74. package/dist/components/organisms/index.d.ts.map +1 -1
  75. package/dist/components/types/page.types.d.ts +69 -0
  76. package/dist/components/types/page.types.d.ts.map +1 -0
  77. package/dist/components/types/page.types.js +1 -0
  78. package/dist/components/types/variants.d.ts +64 -0
  79. package/dist/components/types/variants.d.ts.map +1 -0
  80. package/dist/components/types/variants.js +44 -0
  81. package/dist/components/util/fixed-sidebar-flyout.d.ts +23 -0
  82. package/dist/components/util/fixed-sidebar-flyout.d.ts.map +1 -0
  83. package/dist/components/util/focus-utils.d.ts +12 -0
  84. package/dist/components/util/focus-utils.d.ts.map +1 -0
  85. package/dist/components/util/ssr-safe.d.ts +17 -0
  86. package/dist/components/util/ssr-safe.d.ts.map +1 -0
  87. package/dist/index.d.ts +42 -7
  88. package/dist/index.js +45 -10
  89. package/dist/lib/layout-width-tokens.d.ts +17 -0
  90. package/dist/lib/layout-width-tokens.d.ts.map +1 -0
  91. package/dist/lib/showcase/component-docs/Button.d.ts +3 -0
  92. package/dist/lib/showcase/component-docs/Button.d.ts.map +1 -0
  93. package/dist/lib/showcase/component-docs/Input.d.ts +3 -0
  94. package/dist/lib/showcase/component-docs/Input.d.ts.map +1 -0
  95. package/dist/lib/showcase/component-docs/List.d.ts +3 -0
  96. package/dist/lib/showcase/component-docs/List.d.ts.map +1 -0
  97. package/dist/lib/showcase/component-docs/Modal.d.ts +3 -0
  98. package/dist/lib/showcase/component-docs/Modal.d.ts.map +1 -0
  99. package/dist/lib/showcase/component-docs/Radio.d.ts +3 -0
  100. package/dist/lib/showcase/component-docs/Radio.d.ts.map +1 -0
  101. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +3 -0
  102. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +1 -0
  103. package/dist/lib/showcase/component-docs/Skeleton.d.ts +3 -0
  104. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +1 -0
  105. package/dist/lib/showcase/component-docs/_shared.d.ts +12 -0
  106. package/dist/lib/showcase/component-docs/_shared.d.ts.map +1 -0
  107. package/dist/lib/showcase/component-docs/index.d.ts +4 -0
  108. package/dist/lib/showcase/component-docs/index.d.ts.map +1 -0
  109. package/dist/lib/showcase/components-catalog.d.ts +3 -0
  110. package/dist/lib/showcase/components-catalog.d.ts.map +1 -0
  111. package/dist/lib/showcase/components-showcase-constants.d.ts +89 -0
  112. package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -0
  113. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +15 -0
  114. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +1 -0
  115. package/dist/molecules/Alert.svelte +50 -23
  116. package/dist/molecules/Alert.svelte.d.ts +1 -1
  117. package/dist/molecules/ComponentDemo.svelte +54 -51
  118. package/dist/molecules/ContactForm.svelte +70 -65
  119. package/dist/molecules/Dropdown.svelte +99 -108
  120. package/dist/molecules/Dropdown.svelte.d.ts +12 -6
  121. package/dist/molecules/EmptyState.svelte +44 -0
  122. package/dist/molecules/EmptyState.svelte.d.ts +11 -0
  123. package/dist/molecules/FormField.svelte +89 -0
  124. package/dist/molecules/FormField.svelte.d.ts +23 -0
  125. package/dist/molecules/Header.svelte +41 -0
  126. package/dist/molecules/Header.svelte.d.ts +10 -0
  127. package/dist/molecules/ImageUpload.svelte +1 -1
  128. package/dist/molecules/Modal.svelte +39 -26
  129. package/dist/molecules/Modal.svelte.d.ts +4 -2
  130. package/dist/molecules/NavigationMenu.svelte +16 -0
  131. package/dist/molecules/NavigationMenu.svelte.d.ts +2 -0
  132. package/dist/molecules/NavigationMenuContent.svelte +9 -1
  133. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  134. package/dist/molecules/NavigationMenuTrigger.svelte +9 -4
  135. package/dist/molecules/Page.svelte +16 -0
  136. package/dist/molecules/Page.svelte.d.ts +8 -0
  137. package/dist/molecules/PropsTable.svelte +61 -0
  138. package/dist/molecules/PropsTable.svelte.d.ts +8 -0
  139. package/dist/molecules/RadioGroup.svelte +197 -0
  140. package/dist/molecules/RadioGroup.svelte.d.ts +20 -0
  141. package/dist/molecules/Section.svelte +14 -23
  142. package/dist/molecules/Section.svelte.d.ts +2 -11
  143. package/dist/molecules/SidebarBrandHeader.svelte +68 -0
  144. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +10 -0
  145. package/dist/molecules/SidebarFooter.svelte +119 -0
  146. package/dist/molecules/SidebarFooter.svelte.d.ts +23 -0
  147. package/dist/molecules/SidebarNavSection.svelte +49 -0
  148. package/dist/molecules/SidebarNavSection.svelte.d.ts +14 -0
  149. package/dist/molecules/SlideUp.svelte +21 -16
  150. package/dist/molecules/SlideUp.svelte.d.ts +1 -1
  151. package/dist/molecules/Tabs.svelte +6 -11
  152. package/dist/molecules/Toaster.svelte +21 -0
  153. package/dist/molecules/Toaster.svelte.d.ts +6 -0
  154. package/dist/molecules/ToasterToast.svelte +217 -0
  155. package/dist/molecules/ToasterToast.svelte.d.ts +7 -0
  156. package/dist/molecules/index.d.ts +10 -0
  157. package/dist/molecules/index.js +10 -0
  158. package/dist/molecules/navigation-menu-context.d.ts +11 -0
  159. package/dist/molecules/navigation-menu-context.js +18 -0
  160. package/dist/molecules/toast-store.d.ts +38 -0
  161. package/dist/molecules/toast-store.js +34 -0
  162. package/dist/organisms/SidebarAccountPanel.svelte +118 -0
  163. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +20 -0
  164. package/dist/organisms/SidebarNavigation.svelte +286 -212
  165. package/dist/organisms/SidebarNavigation.svelte.d.ts +22 -1
  166. package/dist/organisms/{SidebarProjectPanel.svelte → SidebarPanel.svelte} +67 -34
  167. package/dist/organisms/{SidebarProjectPanel.svelte.d.ts → SidebarPanel.svelte.d.ts} +9 -6
  168. package/dist/organisms/TopNavbar.svelte +214 -0
  169. package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
  170. package/dist/organisms/index.d.ts +3 -3
  171. package/dist/organisms/index.js +3 -3
  172. package/dist/routes/lib/focus-utils.d.ts +7 -18
  173. package/dist/routes/lib/focus-utils.ts +32 -34
  174. package/dist/routes/lib/ssr-safe.ts +5 -4
  175. package/dist/routes/lib/variant-utils.ts +48 -39
  176. package/dist/types/components.d.ts +187 -0
  177. package/dist/types/components.d.ts.map +1 -0
  178. package/dist/types/events.d.ts +200 -0
  179. package/dist/types/events.d.ts.map +1 -0
  180. package/dist/types/index.d.ts +171 -0
  181. package/dist/types/index.d.ts.map +1 -0
  182. package/dist/types/page.types.d.ts +3 -0
  183. package/dist/types/page.types.d.ts.map +1 -0
  184. package/dist/types/page.types.js +1 -0
  185. package/dist/types/page.types.ts +79 -0
  186. package/dist/types/variants.d.ts +3 -0
  187. package/dist/types/variants.d.ts.map +1 -0
  188. package/dist/types/variants.js +1 -0
  189. package/dist/types/variants.ts +75 -0
  190. package/dist/util/fixed-sidebar-flyout.d.ts +22 -0
  191. package/dist/util/fixed-sidebar-flyout.js +148 -0
  192. package/dist/util/focus-utils.d.ts +11 -0
  193. package/dist/util/focus-utils.js +94 -0
  194. package/dist/util/ssr-safe.d.ts +16 -0
  195. package/dist/util/ssr-safe.js +48 -0
  196. package/dist/zabi-components-colors.css +360 -324
  197. package/dist/zabi-components-theme-dark-only.css +164 -151
  198. package/dist/zabi-components-theme-dark.css +164 -151
  199. package/dist/zabi-components-theme-only.css +253 -230
  200. package/dist/zabi-components-theme.css +253 -230
  201. package/dist/zabi-components.css +1262 -241
  202. package/docs/theme-imports.md +112 -0
  203. package/package.json +32 -10
  204. package/dist/organisms/Navbar.svelte +0 -92
  205. package/dist/organisms/Navbar.svelte.d.ts +0 -14
  206. package/dist/organisms/Navigation.svelte +0 -119
  207. package/dist/organisms/Navigation.svelte.d.ts +0 -21
@@ -1,17 +1,22 @@
1
1
  <script lang="ts">
2
2
  import { CheckCircle, AlertTriangle, AlertCircle } from "@lucide/svelte";
3
- import type { SemanticVariant, SizeVariant } from "../../types/variants.js";
4
- import { generateId } from "../../routes/lib/ssr-safe.js";
3
+ import type { SemanticVariant, SizeVariant } from "../types/variants.js";
4
+ import { generateId } from "../util/ssr-safe.js";
5
5
 
6
6
  interface Props {
7
+ /** Omit to auto-generate; pair with FormField `id` when used inside FormField. */
8
+ id?: string;
7
9
  value?: string;
8
10
  type?: string;
9
11
  name?: string;
10
12
  class?: string;
11
13
  label?: string;
14
+ /** True when FormField supplies the visible `<label>`. */
15
+ hideLabel?: boolean;
12
16
  placeholder?: string;
13
17
  required?: boolean;
14
18
  disabled?: boolean;
19
+ loading?: boolean;
15
20
  size?: SizeVariant;
16
21
  variant?: SemanticVariant;
17
22
  message?: string;
@@ -21,14 +26,17 @@
21
26
  }
22
27
 
23
28
  let {
29
+ id: idProp,
24
30
  value = $bindable(""),
25
31
  type = "text",
26
32
  name = "",
27
33
  class: className = "",
28
34
  label = "",
35
+ hideLabel = false,
29
36
  placeholder = "",
30
37
  required = false,
31
38
  disabled = false,
39
+ loading = false,
32
40
  size = "md",
33
41
  variant = "default",
34
42
  message = "",
@@ -37,7 +45,9 @@
37
45
  ...restProps
38
46
  }: Props = $props();
39
47
 
40
- const inputId = generateId("input");
48
+ const fallbackId = generateId("input");
49
+ const inputId = $derived(idProp ?? fallbackId);
50
+ const isDisabled = $derived(disabled || loading);
41
51
 
42
52
  const sizeClass = $derived(() => {
43
53
  if (size === "sm") {
@@ -45,38 +55,46 @@
45
55
  padding: "px-4 py-2",
46
56
  text: "text-sm",
47
57
  leading: "leading-5",
58
+ spinner: "size-4",
48
59
  };
49
60
  } else if (size === "lg") {
50
61
  return {
51
62
  padding: "px-4 py-3",
52
63
  text: "text-base",
53
64
  leading: "leading-6",
65
+ spinner: "size-5",
54
66
  };
55
67
  } else {
56
68
  return {
57
69
  padding: "px-4 py-2.5",
58
70
  text: "text-base",
59
71
  leading: "leading-6",
72
+ spinner: "size-5",
60
73
  };
61
74
  }
62
75
  });
63
76
 
64
77
  const variantClass = $derived(() => {
65
78
  return variant === "success"
66
- ? "border-success focus:border-success focus:ring-success"
79
+ ? "border-success focus-visible:border-success"
67
80
  : variant === "warning"
68
- ? "border-warning focus:border-warning focus:ring-warning"
81
+ ? "border-warning focus-visible:border-warning"
69
82
  : variant === "error"
70
- ? "border-error focus:border-error focus:ring-error"
71
- : "border-0 focus:ring-2 focus:ring-brand-500"; // default - no border
83
+ ? "border-error focus-visible:border-error"
84
+ : "border-input-border focus-visible:border-input-border";
72
85
  });
73
86
 
74
87
  const inputClasses = $derived(() => {
75
88
  const sizeStyles = sizeClass();
89
+ const trailingPad = loading
90
+ ? size === "sm"
91
+ ? "pr-9"
92
+ : "pr-10"
93
+ : "";
76
94
  const baseClasses =
77
- "w-full min-w-48 bg-input hover:bg-input-hover focus:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed";
95
+ "focus-ring w-full min-w-48 border bg-input hover:bg-input-hover focus-visible:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus-visible:outline-none disabled:opacity-50 disabled:cursor-not-allowed";
78
96
 
79
- return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()} ${className}`.trim();
97
+ return `${baseClasses} ${sizeStyles.padding} ${trailingPad} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()} ${className}`.trim();
80
98
  });
81
99
 
82
100
  const labelClasses = $derived(
@@ -109,25 +127,39 @@
109
127
  </script>
110
128
 
111
129
  <div>
112
- {#if label}
130
+ {#if label && !hideLabel}
113
131
  <label for={inputId} class={labelClasses()}>{label}</label>
114
132
  {/if}
115
- <input
116
- id={inputId}
117
- {type}
118
- {name}
119
- bind:value
120
- {placeholder}
121
- {required}
122
- {disabled}
123
- class={inputClasses()}
124
- oninput={handleInput}
125
- {onblur}
126
- aria-invalid={variant === "error" ? "true" : undefined}
127
- aria-required={required ? "true" : undefined}
128
- aria-describedby={message ? `${inputId}-message` : undefined}
129
- {...restProps}
130
- />
133
+ <div class="relative">
134
+ <input
135
+ id={inputId}
136
+ {type}
137
+ {name}
138
+ bind:value
139
+ {placeholder}
140
+ {required}
141
+ disabled={isDisabled}
142
+ class={inputClasses()}
143
+ oninput={handleInput}
144
+ {onblur}
145
+ aria-invalid={variant === "error" ? "true" : undefined}
146
+ aria-required={required ? "true" : undefined}
147
+ aria-busy={loading ? "true" : undefined}
148
+ aria-describedby={message ? `${inputId}-message` : undefined}
149
+ {...restProps}
150
+ />
151
+ {#if loading}
152
+ <span
153
+ class="pointer-events-none absolute inset-y-0 right-3 flex items-center text-description"
154
+ aria-hidden="true"
155
+ >
156
+ <span
157
+ class="inline-block {sizeClass()
158
+ .spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70"
159
+ ></span>
160
+ </span>
161
+ {/if}
162
+ </div>
131
163
  {#if message && variant !== "default"}
132
164
  <p
133
165
  id={`${inputId}-message`}
@@ -1,13 +1,18 @@
1
- import type { SemanticVariant, SizeVariant } from "../../types/variants.js";
1
+ import type { SemanticVariant, SizeVariant } from "../types/variants.js";
2
2
  interface Props {
3
+ /** Omit to auto-generate; pair with FormField `id` when used inside FormField. */
4
+ id?: string;
3
5
  value?: string;
4
6
  type?: string;
5
7
  name?: string;
6
8
  class?: string;
7
9
  label?: string;
10
+ /** True when FormField supplies the visible `<label>`. */
11
+ hideLabel?: boolean;
8
12
  placeholder?: string;
9
13
  required?: boolean;
10
14
  disabled?: boolean;
15
+ loading?: boolean;
11
16
  size?: SizeVariant;
12
17
  variant?: SemanticVariant;
13
18
  message?: string;
@@ -4,17 +4,12 @@
4
4
  export type ListItem = ListItemData;
5
5
 
6
6
  interface Props {
7
- /** Collection of items rendered in the list. */
8
7
  items: ListItemData[];
9
- /** Accessible label for the list container. */
8
+ /** `aria-label` on the list region. */
10
9
  ariaLabel?: string;
11
- /** Optional active item id for current state styling. */
12
10
  selectedId?: string;
13
- /** Whether to show the arrow icon for each row. */
14
11
  showArrow?: boolean;
15
- /** Optional custom class names for the list container. */
16
12
  className?: string;
17
- /** Item click callback. */
18
13
  onclick?: (item: ListItem, event: MouseEvent) => void;
19
14
  }
20
15
 
@@ -29,7 +24,7 @@
29
24
  }: Props = $props();
30
25
 
31
26
  const listClasses = $derived(
32
- `space-y-1 overflow-hidden rounded-xl border border-border bg-surface p-1 ${className}`.trim(),
27
+ `space-y-1 overflow-hidden rounded-xl ${className}`.trim(),
33
28
  );
34
29
  </script>
35
30
 
@@ -1,17 +1,12 @@
1
1
  import { type ListItemData } from "./ListItem.svelte";
2
2
  export type ListItem = ListItemData;
3
3
  interface Props {
4
- /** Collection of items rendered in the list. */
5
4
  items: ListItemData[];
6
- /** Accessible label for the list container. */
5
+ /** `aria-label` on the list region. */
7
6
  ariaLabel?: string;
8
- /** Optional active item id for current state styling. */
9
7
  selectedId?: string;
10
- /** Whether to show the arrow icon for each row. */
11
8
  showArrow?: boolean;
12
- /** Optional custom class names for the list container. */
13
9
  className?: string;
14
- /** Item click callback. */
15
10
  onclick?: (item: ListItem, event: MouseEvent) => void;
16
11
  }
17
12
  declare const List: import("svelte").Component<Props, {}, "">;
@@ -1,34 +1,29 @@
1
1
  <script lang="ts">
2
2
  import { ArrowRight } from "@lucide/svelte";
3
- import type { Component } from "svelte";
3
+ import type { Component, Snippet } from "svelte";
4
+ import ListItemLeading from "./ListItemLeading.svelte";
4
5
 
5
6
  export interface ListItemData {
6
- /** Unique identifier for stable rendering and active-state tracking. */
7
7
  id: string;
8
- /** Primary item label. */
9
8
  label: string;
10
- /** Optional secondary text shown under the label. */
11
9
  description?: string;
12
- /** Optional URL; when set, the item renders as an anchor. */
13
10
  href?: string;
14
- /** Optional leading icon rendered before text. */
15
11
  icon?: Component<{ size?: number; class?: string }>;
16
- /** Link target, only used when href is provided. */
12
+ /** Leading image; wins over `icon` when both are set. */
13
+ avatar?: string;
14
+ /** `avatar` alt text; `""` if decorative. */
15
+ avatarAlt?: string;
17
16
  target?: "_self" | "_blank" | "_parent" | "_top";
18
- /** Link rel attribute, only used when href is provided. */
19
17
  rel?: string;
20
- /** Disabled state for the item. */
21
18
  disabled?: boolean;
22
19
  }
23
20
 
24
21
  interface Props {
25
- /** Item data to render. */
26
22
  item: ListItemData;
27
- /** Active-state styling flag. */
28
23
  selected?: boolean;
29
- /** Whether to show the right-arrow icon. */
30
24
  showArrow?: boolean;
31
- /** Item click callback. */
25
+ /** Shown before the chevron when `showArrow` is true. */
26
+ trailing?: Snippet;
32
27
  onclick?: (item: ListItemData, event: MouseEvent) => void;
33
28
  }
34
29
 
@@ -36,16 +31,17 @@
36
31
  item,
37
32
  selected = false,
38
33
  showArrow = true,
34
+ trailing,
39
35
  onclick,
40
36
  ...restProps
41
37
  }: Props = $props();
42
38
 
43
39
  const itemClasses = $derived.by(() => {
44
40
  const baseClasses =
45
- "group flex w-full items-center gap-3 rounded-xl border border-border px-4 py-3 text-left transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-inset";
41
+ "group focus-ring flex w-full items-center gap-3 rounded-xl border border-border px-4 py-3 pr-5 text-left transition-all duration-150";
46
42
  const interactiveClasses = item.disabled
47
43
  ? "cursor-not-allowed opacity-50"
48
- : "cursor-pointer hover:ring-1 hover:ring-focus focus-visible:bg-base-100";
44
+ : "cursor-pointer focus-visible:bg-base-100";
49
45
  const selectedClasses = selected
50
46
  ? "bg-action-primary-subtle border-brand-500"
51
47
  : "";
@@ -73,6 +69,47 @@
73
69
  };
74
70
  </script>
75
71
 
72
+ {#snippet rowContent()}
73
+ {#if item.avatar}
74
+ <ListItemLeading class="rounded-full">
75
+ <img
76
+ src={item.avatar}
77
+ alt={item.avatarAlt ?? ""}
78
+ class="block size-full min-h-0 min-w-0 object-cover"
79
+ loading="lazy"
80
+ decoding="async"
81
+ />
82
+ </ListItemLeading>
83
+ {:else if item.icon}
84
+ <ListItemLeading>
85
+ <span aria-hidden="true">
86
+ <item.icon size={16} />
87
+ </span>
88
+ </ListItemLeading>
89
+ {/if}
90
+ <span class="min-w-0 flex-1">
91
+ <span class="block text-sm font-medium text-headline">{item.label}</span
92
+ >
93
+ {#if item.description}
94
+ <span class="mt-0.5 block text-sm text-description"
95
+ >{item.description}</span
96
+ >
97
+ {/if}
98
+ </span>
99
+ {#if trailing}
100
+ <span class="flex shrink-0 items-center gap-2 text-sm text-description">
101
+ {@render trailing()}
102
+ </span>
103
+ {/if}
104
+ {#if showArrow}
105
+ <ArrowRight
106
+ size={16}
107
+ class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-1 group-focus-visible:translate-x-1"
108
+ aria-hidden="true"
109
+ />
110
+ {/if}
111
+ {/snippet}
112
+
76
113
  {#if item.href}
77
114
  <a
78
115
  href={item.href}
@@ -85,31 +122,7 @@
85
122
  onclick={handleItemClick}
86
123
  {...restProps}
87
124
  >
88
- {#if item.icon}
89
- <span
90
- class="flex size-5 shrink-0 items-center justify-center text-description"
91
- aria-hidden="true"
92
- >
93
- <item.icon size={16} />
94
- </span>
95
- {/if}
96
- <span class="min-w-0 flex-1">
97
- <span class="block text-sm font-medium text-headline"
98
- >{item.label}</span
99
- >
100
- {#if item.description}
101
- <span class="mt-0.5 block text-sm text-description"
102
- >{item.description}</span
103
- >
104
- {/if}
105
- </span>
106
- {#if showArrow}
107
- <ArrowRight
108
- size={16}
109
- class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-0.5 group-focus-visible:translate-x-0.5"
110
- aria-hidden="true"
111
- />
112
- {/if}
125
+ {@render rowContent()}
113
126
  </a>
114
127
  {:else}
115
128
  <button
@@ -119,30 +132,6 @@
119
132
  onclick={handleItemClick}
120
133
  {...restProps}
121
134
  >
122
- {#if item.icon}
123
- <span
124
- class="flex size-5 shrink-0 items-center justify-center text-description"
125
- aria-hidden="true"
126
- >
127
- <item.icon size={16} />
128
- </span>
129
- {/if}
130
- <span class="min-w-0 flex-1">
131
- <span class="block text-sm font-medium text-headline"
132
- >{item.label}</span
133
- >
134
- {#if item.description}
135
- <span class="mt-0.5 block text-sm text-description"
136
- >{item.description}</span
137
- >
138
- {/if}
139
- </span>
140
- {#if showArrow}
141
- <ArrowRight
142
- size={16}
143
- class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-0.5 group-focus-visible:translate-x-0.5"
144
- aria-hidden="true"
145
- />
146
- {/if}
135
+ {@render rowContent()}
147
136
  </button>
148
137
  {/if}
@@ -1,33 +1,27 @@
1
- import type { Component } from "svelte";
1
+ import type { Component, Snippet } from "svelte";
2
2
  export interface ListItemData {
3
- /** Unique identifier for stable rendering and active-state tracking. */
4
3
  id: string;
5
- /** Primary item label. */
6
4
  label: string;
7
- /** Optional secondary text shown under the label. */
8
5
  description?: string;
9
- /** Optional URL; when set, the item renders as an anchor. */
10
6
  href?: string;
11
- /** Optional leading icon rendered before text. */
12
7
  icon?: Component<{
13
8
  size?: number;
14
9
  class?: string;
15
10
  }>;
16
- /** Link target, only used when href is provided. */
11
+ /** Leading image; wins over `icon` when both are set. */
12
+ avatar?: string;
13
+ /** `avatar` alt text; `""` if decorative. */
14
+ avatarAlt?: string;
17
15
  target?: "_self" | "_blank" | "_parent" | "_top";
18
- /** Link rel attribute, only used when href is provided. */
19
16
  rel?: string;
20
- /** Disabled state for the item. */
21
17
  disabled?: boolean;
22
18
  }
23
19
  interface Props {
24
- /** Item data to render. */
25
20
  item: ListItemData;
26
- /** Active-state styling flag. */
27
21
  selected?: boolean;
28
- /** Whether to show the right-arrow icon. */
29
22
  showArrow?: boolean;
30
- /** Item click callback. */
23
+ /** Shown before the chevron when `showArrow` is true. */
24
+ trailing?: Snippet;
31
25
  onclick?: (item: ListItemData, event: MouseEvent) => void;
32
26
  }
33
27
  declare const ListItem: Component<Props, {}, "">;
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ interface Props {
5
+ /** e.g. `rounded-full` for avatars; default is `rounded-md` for icons. */
6
+ class?: string;
7
+ children?: Snippet;
8
+ }
9
+
10
+ let { class: className = "", children }: Props = $props();
11
+
12
+ const roundingClass = $derived(className.trim() ? className.trim() : "rounded-md");
13
+ </script>
14
+
15
+ <span
16
+ class="flex size-[length:var(--spacing-xl)] shrink-0 select-none items-center justify-center overflow-hidden text-description {roundingClass}"
17
+ >
18
+ {@render children?.()}
19
+ </span>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ /** e.g. `rounded-full` for avatars; default is `rounded-md` for icons. */
4
+ class?: string;
5
+ children?: Snippet;
6
+ }
7
+ declare const ListItemLeading: import("svelte").Component<Props, {}, "">;
8
+ type ListItemLeading = ReturnType<typeof ListItemLeading>;
9
+ export default ListItemLeading;
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { generateId } from "../../routes/lib/ssr-safe.js";
2
+ import { generateId } from "../util/ssr-safe.js";
3
3
 
4
4
  interface Props {
5
5
  value?: number;
@@ -39,7 +39,7 @@
39
39
 
40
40
  <div
41
41
  id={progressId}
42
- class="w-full bg-input rounded-full overflow-hidden {sizeClasses[size]}"
42
+ class="w-full border border-input-border bg-input rounded-full overflow-hidden {sizeClasses[size]}"
43
43
  role="progressbar"
44
44
  aria-valuenow={value}
45
45
  aria-valuemin="0"
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import SelectionControl from "./SelectionControl.svelte";
3
+ import type { SelectionControlMarkProps } from "./SelectionControl.svelte";
4
+ import { RADIO_CHECKED_DOT_CLASSES } from "./selection-control.styles";
5
+
6
+ interface Props {
7
+ id?: string;
8
+ name?: string;
9
+ value?: string;
10
+ label?: string;
11
+ disabled?: boolean;
12
+ defaultChecked?: boolean;
13
+ checked?: boolean;
14
+ onChange?: (event: Event) => void;
15
+ onchange?: (event: Event) => void;
16
+ }
17
+
18
+ let {
19
+ id,
20
+ name = "",
21
+ value = "",
22
+ label = "",
23
+ disabled = false,
24
+ defaultChecked = false,
25
+ checked = $bindable(defaultChecked),
26
+ onChange,
27
+ onchange,
28
+ ...restProps
29
+ }: Props = $props();
30
+ </script>
31
+
32
+ {#snippet mark(_props: SelectionControlMarkProps)}
33
+ <span class={RADIO_CHECKED_DOT_CLASSES} aria-hidden="true"></span>
34
+ {/snippet}
35
+
36
+ <SelectionControl
37
+ type="radio"
38
+ shape="circle"
39
+ {id}
40
+ {name}
41
+ {value}
42
+ {label}
43
+ {disabled}
44
+ bind:checked
45
+ {defaultChecked}
46
+ {onChange}
47
+ {onchange}
48
+ {mark}
49
+ {...restProps}
50
+ />
51
+
@@ -0,0 +1,14 @@
1
+ interface Props {
2
+ id?: string;
3
+ name?: string;
4
+ value?: string;
5
+ label?: string;
6
+ disabled?: boolean;
7
+ defaultChecked?: boolean;
8
+ checked?: boolean;
9
+ onChange?: (event: Event) => void;
10
+ onchange?: (event: Event) => void;
11
+ }
12
+ declare const Radio: import("svelte").Component<Props, {}, "checked">;
13
+ type Radio = ReturnType<typeof Radio>;
14
+ export default Radio;