zabi-components 6.0.0 → 7.0.1

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 (200) hide show
  1. package/README.md +137 -1473
  2. package/dist/atoms/ActionPanel.svelte +116 -0
  3. package/dist/atoms/ActionPanel.svelte.d.ts +19 -0
  4. package/dist/atoms/Badge.svelte +1 -1
  5. package/dist/atoms/Badge.svelte.d.ts +1 -1
  6. package/dist/atoms/Button.svelte +38 -21
  7. package/dist/atoms/Button.svelte.d.ts +6 -6
  8. package/dist/atoms/Card.svelte +12 -3
  9. package/dist/atoms/Card.svelte.d.ts +2 -2
  10. package/dist/atoms/CardHeader.svelte +8 -4
  11. package/dist/atoms/CardHeader.svelte.d.ts +4 -2
  12. package/dist/atoms/Checkbox.svelte +50 -76
  13. package/dist/atoms/Checkbox.svelte.d.ts +7 -2
  14. package/dist/atoms/CodeBlock.svelte +20 -34
  15. package/dist/atoms/CodeBlock.svelte.d.ts +1 -0
  16. package/dist/atoms/ColorPicker.svelte +2 -6
  17. package/dist/atoms/Container.svelte +41 -0
  18. package/dist/atoms/Container.svelte.d.ts +12 -0
  19. package/dist/atoms/Divider.svelte +68 -0
  20. package/dist/atoms/Divider.svelte.d.ts +13 -0
  21. package/dist/atoms/FeatureCard.svelte +35 -50
  22. package/dist/atoms/FeatureCard.svelte.d.ts +9 -4
  23. package/dist/atoms/IconButton.svelte +36 -29
  24. package/dist/atoms/IconButton.svelte.d.ts +7 -13
  25. package/dist/atoms/Input.svelte +58 -26
  26. package/dist/atoms/Input.svelte.d.ts +6 -1
  27. package/dist/atoms/List.svelte +2 -7
  28. package/dist/atoms/List.svelte.d.ts +1 -6
  29. package/dist/atoms/ListItem.svelte +54 -65
  30. package/dist/atoms/ListItem.svelte.d.ts +7 -13
  31. package/dist/atoms/ListItemLeading.svelte +19 -0
  32. package/dist/atoms/ListItemLeading.svelte.d.ts +9 -0
  33. package/dist/atoms/Progress.svelte +1 -1
  34. package/dist/atoms/Radio.svelte +51 -0
  35. package/dist/atoms/Radio.svelte.d.ts +14 -0
  36. package/dist/atoms/Select.svelte +23 -30
  37. package/dist/atoms/Select.svelte.d.ts +0 -10
  38. package/dist/atoms/SelectionControl.svelte +98 -0
  39. package/dist/atoms/SelectionControl.svelte.d.ts +26 -0
  40. package/dist/atoms/Skeleton.svelte +39 -15
  41. package/dist/atoms/Skeleton.svelte.d.ts +7 -3
  42. package/dist/atoms/Table.svelte +27 -0
  43. package/dist/atoms/Table.svelte.d.ts +10 -0
  44. package/dist/atoms/Text.svelte +46 -0
  45. package/dist/atoms/Text.svelte.d.ts +13 -0
  46. package/dist/atoms/Textarea.svelte +74 -53
  47. package/dist/atoms/Textarea.svelte.d.ts +6 -2
  48. package/dist/atoms/ThemeToggle.svelte +35 -24
  49. package/dist/atoms/ThemeToggle.svelte.d.ts +1 -0
  50. package/dist/atoms/Toast.svelte +55 -31
  51. package/dist/atoms/Toast.svelte.d.ts +4 -1
  52. package/dist/atoms/Toggle.svelte +37 -22
  53. package/dist/atoms/Toggle.svelte.d.ts +2 -1
  54. package/dist/atoms/Tooltip.svelte +97 -14
  55. package/dist/atoms/Tooltip.svelte.d.ts +6 -5
  56. package/dist/atoms/index.d.ts +6 -0
  57. package/dist/atoms/index.js +6 -0
  58. package/dist/atoms/selection-control.styles.d.ts +25 -0
  59. package/dist/atoms/selection-control.styles.js +30 -0
  60. package/dist/components/atoms/index.d.ts +6 -0
  61. package/dist/components/atoms/index.d.ts.map +1 -1
  62. package/dist/components/atoms/selection-control.styles.d.ts +26 -0
  63. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -0
  64. package/dist/components/index.d.ts +41 -5
  65. package/dist/components/index.d.ts.map +1 -1
  66. package/dist/components/molecules/index.d.ts +10 -0
  67. package/dist/components/molecules/index.d.ts.map +1 -1
  68. package/dist/components/molecules/navigation-menu-context.d.ts +11 -0
  69. package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -1
  70. package/dist/components/molecules/toast-store.d.ts +39 -0
  71. package/dist/components/molecules/toast-store.d.ts.map +1 -0
  72. package/dist/components/organisms/index.d.ts +2 -1
  73. package/dist/components/organisms/index.d.ts.map +1 -1
  74. package/dist/components/types/page.types.d.ts +69 -0
  75. package/dist/components/types/page.types.d.ts.map +1 -0
  76. package/dist/components/types/page.types.js +1 -0
  77. package/dist/components/types/variants.d.ts +64 -0
  78. package/dist/components/types/variants.d.ts.map +1 -0
  79. package/dist/components/types/variants.js +44 -0
  80. package/dist/components/util/fixed-sidebar-flyout.d.ts +23 -0
  81. package/dist/components/util/fixed-sidebar-flyout.d.ts.map +1 -0
  82. package/dist/components/util/focus-utils.d.ts +12 -0
  83. package/dist/components/util/focus-utils.d.ts.map +1 -0
  84. package/dist/components/util/ssr-safe.d.ts +17 -0
  85. package/dist/components/util/ssr-safe.d.ts.map +1 -0
  86. package/dist/index.d.ts +41 -5
  87. package/dist/index.js +44 -8
  88. package/dist/lib/layout-width-tokens.d.ts +17 -0
  89. package/dist/lib/layout-width-tokens.d.ts.map +1 -0
  90. package/dist/lib/showcase/component-docs/Button.d.ts +3 -0
  91. package/dist/lib/showcase/component-docs/Button.d.ts.map +1 -0
  92. package/dist/lib/showcase/component-docs/Input.d.ts +3 -0
  93. package/dist/lib/showcase/component-docs/Input.d.ts.map +1 -0
  94. package/dist/lib/showcase/component-docs/List.d.ts +3 -0
  95. package/dist/lib/showcase/component-docs/List.d.ts.map +1 -0
  96. package/dist/lib/showcase/component-docs/Modal.d.ts +3 -0
  97. package/dist/lib/showcase/component-docs/Modal.d.ts.map +1 -0
  98. package/dist/lib/showcase/component-docs/Radio.d.ts +3 -0
  99. package/dist/lib/showcase/component-docs/Radio.d.ts.map +1 -0
  100. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +3 -0
  101. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +1 -0
  102. package/dist/lib/showcase/component-docs/Skeleton.d.ts +3 -0
  103. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +1 -0
  104. package/dist/lib/showcase/component-docs/_shared.d.ts +12 -0
  105. package/dist/lib/showcase/component-docs/_shared.d.ts.map +1 -0
  106. package/dist/lib/showcase/component-docs/index.d.ts +4 -0
  107. package/dist/lib/showcase/component-docs/index.d.ts.map +1 -0
  108. package/dist/lib/showcase/components-catalog.d.ts.map +1 -1
  109. package/dist/lib/showcase/components-showcase-constants.d.ts +26 -0
  110. package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -1
  111. package/dist/molecules/Alert.svelte +50 -23
  112. package/dist/molecules/Alert.svelte.d.ts +1 -1
  113. package/dist/molecules/ComponentDemo.svelte +2 -2
  114. package/dist/molecules/ContactForm.svelte +1 -1
  115. package/dist/molecules/Dropdown.svelte +99 -109
  116. package/dist/molecules/Dropdown.svelte.d.ts +12 -6
  117. package/dist/molecules/EmptyState.svelte +44 -0
  118. package/dist/molecules/EmptyState.svelte.d.ts +11 -0
  119. package/dist/molecules/FormField.svelte +89 -0
  120. package/dist/molecules/FormField.svelte.d.ts +23 -0
  121. package/dist/molecules/Header.svelte +41 -0
  122. package/dist/molecules/Header.svelte.d.ts +10 -0
  123. package/dist/molecules/Modal.svelte +37 -24
  124. package/dist/molecules/Modal.svelte.d.ts +4 -2
  125. package/dist/molecules/NavigationMenu.svelte +16 -0
  126. package/dist/molecules/NavigationMenu.svelte.d.ts +2 -0
  127. package/dist/molecules/NavigationMenuContent.svelte +9 -1
  128. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  129. package/dist/molecules/NavigationMenuTrigger.svelte +9 -1
  130. package/dist/molecules/Page.svelte +16 -0
  131. package/dist/molecules/Page.svelte.d.ts +8 -0
  132. package/dist/molecules/PropsTable.svelte +61 -0
  133. package/dist/molecules/PropsTable.svelte.d.ts +8 -0
  134. package/dist/molecules/RadioGroup.svelte +197 -0
  135. package/dist/molecules/RadioGroup.svelte.d.ts +20 -0
  136. package/dist/molecules/Section.svelte +2 -11
  137. package/dist/molecules/Section.svelte.d.ts +2 -11
  138. package/dist/molecules/SidebarBrandHeader.svelte +68 -0
  139. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +10 -0
  140. package/dist/molecules/SidebarFooter.svelte +119 -0
  141. package/dist/molecules/SidebarFooter.svelte.d.ts +23 -0
  142. package/dist/molecules/SidebarNavSection.svelte +49 -0
  143. package/dist/molecules/SidebarNavSection.svelte.d.ts +14 -0
  144. package/dist/molecules/SlideUp.svelte +19 -14
  145. package/dist/molecules/SlideUp.svelte.d.ts +1 -1
  146. package/dist/molecules/Tabs.svelte +4 -9
  147. package/dist/molecules/Toaster.svelte +21 -0
  148. package/dist/molecules/Toaster.svelte.d.ts +6 -0
  149. package/dist/molecules/ToasterToast.svelte +217 -0
  150. package/dist/molecules/ToasterToast.svelte.d.ts +7 -0
  151. package/dist/molecules/index.d.ts +10 -0
  152. package/dist/molecules/index.js +10 -0
  153. package/dist/molecules/navigation-menu-context.d.ts +11 -0
  154. package/dist/molecules/navigation-menu-context.js +18 -0
  155. package/dist/molecules/toast-store.d.ts +38 -0
  156. package/dist/molecules/toast-store.js +34 -0
  157. package/dist/organisms/SidebarAccountPanel.svelte +118 -0
  158. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +20 -0
  159. package/dist/organisms/SidebarNavigation.svelte +267 -237
  160. package/dist/organisms/SidebarNavigation.svelte.d.ts +21 -5
  161. package/dist/organisms/SidebarPanel.svelte +54 -22
  162. package/dist/organisms/SidebarPanel.svelte.d.ts +3 -0
  163. package/dist/organisms/{Navbar.svelte → TopNavbar.svelte} +22 -17
  164. package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
  165. package/dist/organisms/index.d.ts +2 -1
  166. package/dist/organisms/index.js +2 -1
  167. package/dist/routes/lib/focus-utils.d.ts +7 -18
  168. package/dist/routes/lib/focus-utils.ts +32 -34
  169. package/dist/routes/lib/ssr-safe.ts +5 -4
  170. package/dist/routes/lib/variant-utils.ts +48 -39
  171. package/dist/types/components.d.ts +187 -0
  172. package/dist/types/components.d.ts.map +1 -0
  173. package/dist/types/events.d.ts +200 -0
  174. package/dist/types/events.d.ts.map +1 -0
  175. package/dist/types/index.d.ts +171 -0
  176. package/dist/types/index.d.ts.map +1 -0
  177. package/dist/types/page.types.d.ts +2 -31
  178. package/dist/types/page.types.d.ts.map +1 -1
  179. package/dist/types/page.types.js +1 -0
  180. package/dist/types/page.types.ts +79 -0
  181. package/dist/types/variants.d.ts +3 -0
  182. package/dist/types/variants.d.ts.map +1 -0
  183. package/dist/types/variants.js +1 -0
  184. package/dist/types/variants.ts +5 -6
  185. package/dist/util/fixed-sidebar-flyout.d.ts +22 -0
  186. package/dist/util/fixed-sidebar-flyout.js +148 -0
  187. package/dist/util/focus-utils.d.ts +11 -0
  188. package/dist/util/focus-utils.js +94 -0
  189. package/dist/util/ssr-safe.d.ts +16 -0
  190. package/dist/util/ssr-safe.js +48 -0
  191. package/dist/zabi-components-colors.css +128 -115
  192. package/dist/zabi-components-theme-dark-only.css +77 -72
  193. package/dist/zabi-components-theme-dark.css +77 -72
  194. package/dist/zabi-components-theme-only.css +51 -43
  195. package/dist/zabi-components-theme.css +51 -43
  196. package/dist/zabi-components.css +952 -249
  197. package/docs/lucide-icons.md +24 -0
  198. package/package.json +22 -9
  199. package/scripts/fix-lucide-svelte-icon-dts.js +35 -0
  200. package/dist/organisms/Navbar.svelte.d.ts +0 -41
@@ -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;
@@ -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;
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import Dropdown from "../molecules/Dropdown.svelte";
3
- import Button from "./Button.svelte";
4
3
  import Input from "./Input.svelte";
5
4
  import {
6
5
  ChevronDown,
@@ -8,7 +7,7 @@
8
7
  AlertTriangle,
9
8
  AlertCircle,
10
9
  } from "@lucide/svelte";
11
- import { generateId } from "../../routes/lib/ssr-safe.js";
10
+ import { generateId } from "../util/ssr-safe.js";
12
11
 
13
12
  interface Props {
14
13
  value?: string | number | undefined;
@@ -17,25 +16,15 @@
17
16
  label: string;
18
17
  disabled?: boolean;
19
18
  }>;
20
- /** Enables the search input inside the dropdown. */
21
19
  searchable?: boolean;
22
- /** Placeholder for the search input. */
23
20
  searchPlaceholder?: string;
24
- /** Max height for the options list (supports any valid CSS length). */
25
21
  maxMenuHeight?: string;
26
- /** Width for the dropdown menu (supports any valid CSS length). */
27
22
  menuWidth?: string;
28
- /** Message shown when no options match the search. */
29
23
  noResultsText?: string;
30
- /** Shows a loading state while options are being fetched. */
31
24
  isLoading?: boolean;
32
- /** Loading text shown in trigger and dropdown body. */
33
25
  loadingText?: string;
34
- /** Empty state title shown when there are no available options. */
35
26
  emptyStateTitle?: string;
36
- /** Empty state description shown when there are no available options. */
37
27
  emptyStateDescription?: string;
38
- /** Optional empty state action label. */
39
28
  emptyStateActionLabel?: string;
40
29
  placeholder?: string;
41
30
  label?: string;
@@ -101,18 +90,18 @@
101
90
 
102
91
  const variantClass = $derived(() => {
103
92
  return variant === "success"
104
- ? "border-success focus:border-success focus:ring-success"
93
+ ? "border-success focus-visible:border-success"
105
94
  : variant === "warning"
106
- ? "border-warning focus:border-warning focus:ring-warning"
95
+ ? "border-warning focus-visible:border-warning"
107
96
  : variant === "error"
108
- ? "border-error focus:border-error focus:ring-error"
109
- : "border-input-border focus:ring-2 focus:ring-brand-500";
97
+ ? "border-error focus-visible:border-error"
98
+ : "border-input-border";
110
99
  });
111
100
 
112
101
  const triggerClasses = $derived(() => {
113
102
  const sizeStyles = sizeClass();
114
103
  const baseClasses =
115
- "flex w-full cursor-pointer items-center justify-between rounded-lg border bg-input text-body transition-all duration-200 hover:bg-input-hover focus:bg-input-focus focus:outline-none focus:ring-offset-0 disabled:cursor-not-allowed disabled:bg-input-disabled disabled:opacity-50";
104
+ "focus-ring flex w-full cursor-pointer items-center justify-between rounded-lg border bg-input text-body transition-all duration-200 hover:bg-input-hover active:bg-input-focus focus-visible:bg-input-focus focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-input-disabled disabled:opacity-50";
116
105
 
117
106
  return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()}`.trim();
118
107
  });
@@ -209,7 +198,6 @@
209
198
  const target = event.target as HTMLElement;
210
199
  const clickedSelectContainer = target.closest(".select-container");
211
200
 
212
- // Close if clicking outside any select container, or clicking on a different select
213
201
  if (
214
202
  !clickedSelectContainer ||
215
203
  clickedSelectContainer !== selectContainer
@@ -233,21 +221,21 @@
233
221
  {/if}
234
222
 
235
223
  <Dropdown
236
- {isOpen}
224
+ bind:isOpen={isOpen}
237
225
  placement="bottom-start"
238
226
  selectedValue={value}
239
227
  onOptionClick={handleOptionClick}
240
228
  ariaLabel="Select options"
229
+ menuRole="listbox"
241
230
  >
242
- {#snippet trigger()}
231
+ {#snippet trigger(aria)}
243
232
  <button
244
233
  type="button"
245
234
  id={selectId}
246
235
  class={triggerClasses()}
247
236
  {disabled}
248
237
  onclick={handleTriggerClick}
249
- aria-haspopup="listbox"
250
- aria-expanded={isOpen}
238
+ {...aria}
251
239
  aria-describedby={message ? `${selectId}-message` : undefined}
252
240
  >
253
241
  <span
@@ -305,18 +293,23 @@
305
293
  "data-value": String(option.value),
306
294
  } as Record<string, string>}
307
295
  <div class="w-full my-0.5">
308
- <Button
309
- variant={value === option.value
310
- ? "outline"
311
- : "ghost"}
312
- size="sm"
313
- isFullWidth={true}
296
+ <button
297
+ type="button"
298
+ role="option"
299
+ aria-selected={value === option.value
300
+ ? true
301
+ : undefined}
302
+ class="focus-ring flex w-full items-center justify-start rounded-md border-2 px-3 py-2 text-left text-sm font-medium transition-colors focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 {value ===
303
+ option.value
304
+ ? 'border-action-primary bg-transparent text-headline'
305
+ : 'border-transparent bg-transparent text-body hover:bg-base-100'}"
314
306
  disabled={option.disabled}
315
- onclick={() => handleOptionClick(option.value)}
316
307
  {...buttonRestProps}
308
+ onclick={() =>
309
+ handleOptionClick(option.value)}
317
310
  >
318
311
  {option.label}
319
- </Button>
312
+ </button>
320
313
  </div>
321
314
  {/each}
322
315
  {:else if hasSearchQuery()}
@@ -5,25 +5,15 @@ interface Props {
5
5
  label: string;
6
6
  disabled?: boolean;
7
7
  }>;
8
- /** Enables the search input inside the dropdown. */
9
8
  searchable?: boolean;
10
- /** Placeholder for the search input. */
11
9
  searchPlaceholder?: string;
12
- /** Max height for the options list (supports any valid CSS length). */
13
10
  maxMenuHeight?: string;
14
- /** Width for the dropdown menu (supports any valid CSS length). */
15
11
  menuWidth?: string;
16
- /** Message shown when no options match the search. */
17
12
  noResultsText?: string;
18
- /** Shows a loading state while options are being fetched. */
19
13
  isLoading?: boolean;
20
- /** Loading text shown in trigger and dropdown body. */
21
14
  loadingText?: string;
22
- /** Empty state title shown when there are no available options. */
23
15
  emptyStateTitle?: string;
24
- /** Empty state description shown when there are no available options. */
25
16
  emptyStateDescription?: string;
26
- /** Optional empty state action label. */
27
17
  emptyStateActionLabel?: string;
28
18
  placeholder?: string;
29
19
  label?: string;
@@ -0,0 +1,98 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { generateId } from "../util/ssr-safe.js";
4
+ import {
5
+ SELECTION_CONTROL_LABEL_ROW,
6
+ selectionControlRingOverlayClasses,
7
+ selectionControlShellClasses,
8
+ type SelectionControlShape,
9
+ } from "./selection-control.styles";
10
+
11
+ type SelectionControlType = "checkbox" | "radio";
12
+
13
+ export type SelectionControlMarkProps = {
14
+ checked: boolean;
15
+ loading: boolean;
16
+ disabled: boolean;
17
+ };
18
+
19
+ interface Props {
20
+ id?: string;
21
+ type: SelectionControlType;
22
+ shape: SelectionControlShape;
23
+ name?: string;
24
+ value?: string;
25
+ label?: string;
26
+ disabled?: boolean;
27
+ loading?: boolean;
28
+ defaultChecked?: boolean;
29
+ checked?: boolean;
30
+ onChange?: (event: Event) => void;
31
+ onchange?: (event: Event) => void;
32
+ mark?: Snippet<[SelectionControlMarkProps]>;
33
+ }
34
+
35
+ let {
36
+ id: idProp,
37
+ type,
38
+ shape,
39
+ name = "",
40
+ value = "",
41
+ label = "",
42
+ disabled = false,
43
+ loading = false,
44
+ defaultChecked = false,
45
+ checked = $bindable(defaultChecked),
46
+ onChange,
47
+ onchange,
48
+ mark,
49
+ ...restProps
50
+ }: Props = $props();
51
+
52
+ /** Call once per mount — `generateId()` in `$derived` would churn ids every tick. */
53
+ const fallbackId = generateId("selection-control");
54
+ const controlId = $derived(idProp ?? fallbackId);
55
+ const isDisabled = $derived(disabled || loading);
56
+
57
+ const labelWrapperClasses = SELECTION_CONTROL_LABEL_ROW;
58
+
59
+ const controlContainerClasses = $derived(selectionControlShellClasses(shape));
60
+
61
+ const controlRingClasses = $derived(selectionControlRingOverlayClasses(shape));
62
+
63
+ function handleChange(event: Event) {
64
+ if (isDisabled) return;
65
+ const target = event.target as HTMLInputElement;
66
+ checked = target.checked;
67
+ onChange?.(event);
68
+ onchange?.(event);
69
+ }
70
+ </script>
71
+
72
+ <div class="flex items-center gap-2">
73
+ <label for={controlId} class={labelWrapperClasses}>
74
+ <span class={controlContainerClasses}>
75
+ <input
76
+ {type}
77
+ id={controlId}
78
+ {name}
79
+ {value}
80
+ {checked}
81
+ disabled={isDisabled}
82
+ class="sr-only"
83
+ aria-busy={loading ? "true" : undefined}
84
+ onchange={handleChange}
85
+ {...restProps}
86
+ />
87
+ <span class={controlRingClasses}></span>
88
+ {#if mark}
89
+ {@render mark({ checked, loading, disabled: isDisabled })}
90
+ {/if}
91
+ </span>
92
+ {#if label}
93
+ <span class="text-sm font-medium text-label">
94
+ {label}
95
+ </span>
96
+ {/if}
97
+ </label>
98
+ </div>
@@ -0,0 +1,26 @@
1
+ import type { Snippet } from "svelte";
2
+ import { type SelectionControlShape } from "./selection-control.styles";
3
+ type SelectionControlType = "checkbox" | "radio";
4
+ export type SelectionControlMarkProps = {
5
+ checked: boolean;
6
+ loading: boolean;
7
+ disabled: boolean;
8
+ };
9
+ interface Props {
10
+ id?: string;
11
+ type: SelectionControlType;
12
+ shape: SelectionControlShape;
13
+ name?: string;
14
+ value?: string;
15
+ label?: string;
16
+ disabled?: boolean;
17
+ loading?: boolean;
18
+ defaultChecked?: boolean;
19
+ checked?: boolean;
20
+ onChange?: (event: Event) => void;
21
+ onchange?: (event: Event) => void;
22
+ mark?: Snippet<[SelectionControlMarkProps]>;
23
+ }
24
+ declare const SelectionControl: import("svelte").Component<Props, {}, "checked">;
25
+ type SelectionControl = ReturnType<typeof SelectionControl>;
26
+ export default SelectionControl;