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,11 +1,19 @@
1
1
  <script lang="ts">
2
- import Button from "../atoms/Button.svelte";
3
- import { generateId } from "../../routes/lib/ssr-safe.js";
2
+ import { generateId } from "../util/ssr-safe.js";
3
+ import type { Snippet } from 'svelte';
4
+
5
+ export type DropdownTriggerProps = {
6
+ 'aria-expanded': boolean;
7
+ 'aria-haspopup': 'menu' | 'listbox';
8
+ 'aria-controls': string;
9
+ };
4
10
 
5
11
  interface Props {
6
12
  isOpen?: boolean;
7
- placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
13
+ placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
8
14
  ariaLabel?: string;
15
+ /** `listbox` for Select-style; `menu` for actions. */
16
+ menuRole?: 'menu' | 'listbox';
9
17
  selectedValue?: string | number | null;
10
18
  options?: Array<{
11
19
  value: string | number;
@@ -13,31 +21,40 @@
13
21
  disabled?: boolean;
14
22
  }>;
15
23
  onOptionClick?: (value: string | number) => void;
24
+ trigger: Snippet<[DropdownTriggerProps]>;
25
+ children?: Snippet;
16
26
  }
17
27
 
18
28
  let {
19
- isOpen = false,
20
- placement = "bottom-start",
21
- ariaLabel = "Menu",
29
+ isOpen = $bindable(false),
30
+ placement = 'bottom-start',
31
+ ariaLabel = 'Menu',
32
+ menuRole = 'menu',
22
33
  selectedValue = null,
23
34
  options = [],
24
35
  onOptionClick,
25
- children,
26
36
  trigger,
37
+ children,
27
38
  ...restProps
28
- }: Props & { children?: any; trigger?: any } = $props();
39
+ }: Props = $props();
40
+
41
+ const menuId = generateId('dropdown-menu');
42
+ let rootEl = $state<HTMLDivElement | null>(null);
29
43
 
30
- const dropdownId = generateId("dropdown");
31
- let triggerElement = $state<HTMLElement | null>(null);
32
- let menuElement = $state<HTMLElement | null>(null);
33
44
  let openedViaKeyboard = $state(false);
34
45
 
46
+ const triggerAria = $derived({
47
+ 'aria-expanded': isOpen,
48
+ 'aria-haspopup': menuRole === 'listbox' ? 'listbox' as const : 'menu' as const,
49
+ 'aria-controls': menuId,
50
+ } satisfies DropdownTriggerProps);
51
+
35
52
  function handleKeydown(event: KeyboardEvent) {
36
53
  if (!isOpen) {
37
54
  if (
38
- event.key === "Enter" ||
39
- event.key === " " ||
40
- event.key === "ArrowDown"
55
+ event.key === 'Enter' ||
56
+ event.key === ' ' ||
57
+ event.key === 'ArrowDown'
41
58
  ) {
42
59
  event.preventDefault();
43
60
  openedViaKeyboard = true;
@@ -47,38 +64,39 @@
47
64
  }
48
65
 
49
66
  switch (event.key) {
50
- case "Escape":
67
+ case 'Escape':
51
68
  event.preventDefault();
52
69
  isOpen = false;
53
- triggerElement?.focus();
54
70
  break;
55
- case "ArrowDown":
71
+ case 'ArrowDown':
56
72
  event.preventDefault();
57
73
  focusNextItem();
58
74
  break;
59
- case "ArrowUp":
75
+ case 'ArrowUp':
60
76
  event.preventDefault();
61
77
  focusPreviousItem();
62
78
  break;
63
- case "Home":
79
+ case 'Home':
64
80
  event.preventDefault();
65
81
  focusFirstItem();
66
82
  break;
67
- case "End":
83
+ case 'End':
68
84
  event.preventDefault();
69
85
  focusLastItem();
70
86
  break;
71
- case "Tab":
87
+ case 'Tab':
72
88
  isOpen = false;
73
89
  break;
74
90
  }
75
91
  }
76
92
 
93
+ let menuElement = $state<HTMLElement | null>(null);
94
+
77
95
  function getMenuItems(): HTMLElement[] {
78
96
  if (!menuElement) return [];
79
97
  return Array.from(
80
98
  menuElement.querySelectorAll<HTMLElement>(
81
- '[role="menuitem"], button, a',
99
+ '[role="menuitem"], [role="option"]',
82
100
  ),
83
101
  );
84
102
  }
@@ -104,8 +122,7 @@
104
122
  }
105
123
 
106
124
  function focusFirstItem() {
107
- const items = getMenuItems();
108
- items[0]?.focus();
125
+ getMenuItems()[0]?.focus();
109
126
  }
110
127
 
111
128
  function focusLastItem() {
@@ -113,126 +130,107 @@
113
130
  items[items.length - 1]?.focus();
114
131
  }
115
132
 
116
- // Focus first item when dropdown opens via keyboard
117
133
  $effect(() => {
118
134
  if (isOpen && menuElement && openedViaKeyboard) {
119
- setTimeout(() => {
135
+ const t = setTimeout(() => {
120
136
  focusFirstItem();
121
137
  openedViaKeyboard = false;
122
138
  }, 0);
123
- } else if (!isOpen) {
139
+ return () => clearTimeout(t);
140
+ }
141
+ if (!isOpen) {
124
142
  openedViaKeyboard = false;
125
143
  }
126
144
  });
127
145
 
128
- // Mark selected items
129
146
  $effect(() => {
130
- if (isOpen && menuElement) {
131
- // Small delay to ensure DOM is ready
132
- setTimeout(() => {
133
- const items = getMenuItems();
134
- items.forEach((item) => {
135
- if (selectedValue !== null && selectedValue !== undefined) {
136
- const itemValue =
137
- item.getAttribute("data-value") ||
138
- item.textContent?.trim();
139
- if (String(itemValue) === String(selectedValue)) {
140
- item.setAttribute("data-selected", "true");
141
- item.setAttribute("aria-selected", "true");
142
- } else {
143
- item.removeAttribute("data-selected");
144
- item.removeAttribute("aria-selected");
145
- }
146
- } else {
147
- item.removeAttribute("data-selected");
148
- item.removeAttribute("aria-selected");
149
- }
150
- });
151
- }, 0);
147
+ if (!isOpen) return;
148
+ function onDocMouseDown(e: MouseEvent) {
149
+ const t = e.target as Node;
150
+ if (rootEl && !rootEl.contains(t)) {
151
+ isOpen = false;
152
+ }
152
153
  }
154
+ document.addEventListener('mousedown', onDocMouseDown);
155
+ return () => document.removeEventListener('mousedown', onDocMouseDown);
153
156
  });
154
157
 
155
158
  const placementClasses = $derived(() => {
156
- const base = "absolute z-dropdown min-w-[12rem]";
157
- const positioning = {
158
- "bottom-start": "top-full left-0 mt-2",
159
- "bottom-end": "top-full right-0 mt-2",
160
- "top-start": "bottom-full left-0 mb-2",
161
- "top-end": "bottom-full right-0 mb-2",
159
+ const base = 'absolute z-dropdown min-w-[12rem]';
160
+ const positioning: Record<typeof placement, string> = {
161
+ 'bottom-start': 'top-full left-0 mt-2',
162
+ 'bottom-end': 'top-full right-0 mt-2',
163
+ 'top-start': 'bottom-full left-0 mb-2',
164
+ 'top-end': 'bottom-full right-0 mb-2',
162
165
  };
163
166
  return `${base} ${positioning[placement]}`;
164
167
  });
165
168
 
166
169
  const transformClasses = $derived(() => {
167
170
  if (!isOpen) {
168
- const hiddenTransform = {
169
- "bottom-start": "translate-y-1",
170
- "bottom-end": "translate-y-1",
171
- "top-start": "-translate-y-1",
172
- "top-end": "-translate-y-1",
171
+ const hiddenTransform: Record<typeof placement, string> = {
172
+ 'bottom-start': 'translate-y-1',
173
+ 'bottom-end': 'translate-y-1',
174
+ 'top-start': '-translate-y-1',
175
+ 'top-end': '-translate-y-1',
173
176
  };
174
- return `opacity-0 invisible ${hiddenTransform[placement]}`;
177
+ return `invisible opacity-0 ${hiddenTransform[placement]}`;
175
178
  }
176
- return "opacity-100 visible translate-y-0";
179
+ return 'visible translate-y-0 opacity-100';
177
180
  });
178
181
 
179
182
  const dropdownContentClasses = $derived(() => {
180
- return `
181
- ${placementClasses()}
182
- bg-input
183
- rounded-lg
184
- shadow-lg
185
- border-0
186
- py-2
187
- transition-all
188
- duration-200
189
- ease-in-out
190
- ${transformClasses()}
191
- `
192
- .trim()
193
- .replace(/\s+/g, " ");
183
+ return [
184
+ placementClasses(),
185
+ 'rounded-lg border border-border bg-card py-2 shadow-lg transition-all duration-200 ease-in-out',
186
+ transformClasses(),
187
+ ]
188
+ .join(' ')
189
+ .replace(/\s+/g, ' ')
190
+ .trim();
194
191
  });
192
+
193
+ const itemRole = $derived(menuRole === 'listbox' ? 'option' : 'menuitem');
194
+
195
+ const optionClasses =
196
+ 'focus-ring flex w-full items-center justify-start rounded-md px-3 py-2 text-left text-sm text-body transition-colors hover:bg-base-100 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50';
195
197
  </script>
196
198
 
197
199
  <div
200
+ bind:this={rootEl}
198
201
  class="relative inline-block"
199
202
  data-placement={placement}
200
203
  onkeydown={handleKeydown}
201
204
  {...restProps}
202
205
  >
203
- <div bind:this={triggerElement}>
204
- {@render trigger?.()}
205
- </div>
206
+ {@render trigger(triggerAria)}
206
207
 
207
208
  {#if isOpen}
208
209
  <div
209
210
  bind:this={menuElement}
210
- id={dropdownId}
211
+ id={menuId}
211
212
  class={dropdownContentClasses()}
212
- role="menu"
213
+ role={menuRole === 'listbox' ? 'listbox' : 'menu'}
213
214
  aria-label={ariaLabel}
214
- tabindex="-1"
215
215
  >
216
216
  {#if options.length > 0}
217
- <div class="px-2 py-2">
217
+ <div class="px-2 py-1">
218
218
  {#each options as option (option.value)}
219
- {@const buttonRestProps = {
220
- "data-value": String(option.value),
221
- } as any}
222
- <div class="w-full my-0.5">
223
- <Button
224
- variant={selectedValue === option.value
225
- ? "outline"
226
- : "ghost"}
227
- size="sm"
228
- isFullWidth={true}
229
- disabled={option.disabled}
230
- onclick={() => onOptionClick?.(option.value)}
231
- {...buttonRestProps}
232
- >
233
- {option.label}
234
- </Button>
235
- </div>
219
+ <button
220
+ type="button"
221
+ role={itemRole}
222
+ aria-selected={menuRole === 'listbox' &&
223
+ selectedValue !== null &&
224
+ String(selectedValue) === String(option.value)
225
+ ? true
226
+ : undefined}
227
+ data-value={String(option.value)}
228
+ disabled={option.disabled}
229
+ class={optionClasses}
230
+ onclick={() => onOptionClick?.(option.value)}
231
+ >
232
+ {option.label}
233
+ </button>
236
234
  {/each}
237
235
  </div>
238
236
  {:else if children}
@@ -241,10 +239,3 @@
241
239
  </div>
242
240
  {/if}
243
241
  </div>
244
-
245
- <style>
246
- :global([role="menu"] button) {
247
- justify-content: flex-start;
248
- text-align: left;
249
- }
250
- </style>
@@ -1,7 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type DropdownTriggerProps = {
3
+ 'aria-expanded': boolean;
4
+ 'aria-haspopup': 'menu' | 'listbox';
5
+ 'aria-controls': string;
6
+ };
1
7
  interface Props {
2
8
  isOpen?: boolean;
3
- placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
9
+ placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
4
10
  ariaLabel?: string;
11
+ /** `listbox` for Select-style; `menu` for actions. */
12
+ menuRole?: 'menu' | 'listbox';
5
13
  selectedValue?: string | number | null;
6
14
  options?: Array<{
7
15
  value: string | number;
@@ -9,11 +17,9 @@ interface Props {
9
17
  disabled?: boolean;
10
18
  }>;
11
19
  onOptionClick?: (value: string | number) => void;
20
+ trigger: Snippet<[DropdownTriggerProps]>;
21
+ children?: Snippet;
12
22
  }
13
- type $$ComponentProps = Props & {
14
- children?: any;
15
- trigger?: any;
16
- };
17
- declare const Dropdown: import("svelte").Component<$$ComponentProps, {}, "">;
23
+ declare const Dropdown: import("svelte").Component<Props, {}, "isOpen">;
18
24
  type Dropdown = ReturnType<typeof Dropdown>;
19
25
  export default Dropdown;
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { generateId } from "../util/ssr-safe.js";
4
+
5
+ interface Props {
6
+ title: string;
7
+ description: string;
8
+ class?: string;
9
+ action?: Snippet;
10
+ media?: Snippet;
11
+ }
12
+
13
+ let {
14
+ title,
15
+ description,
16
+ class: className = "",
17
+ action,
18
+ media,
19
+ }: Props = $props();
20
+
21
+ const titleId = generateId("empty-state-title");
22
+ </script>
23
+
24
+ <section
25
+ class="mx-auto flex flex-col items-center justify-center gap-4 px-6 py-12 text-center {className}"
26
+ aria-labelledby={titleId}
27
+ >
28
+ {#if media}
29
+ <div class="text-description" aria-hidden="true">
30
+ {@render media()}
31
+ </div>
32
+ {/if}
33
+ <div class="space-y-2">
34
+ <h2 id={titleId} class="text-lg font-semibold text-headline">
35
+ {title}
36
+ </h2>
37
+ <p class="text-sm text-description">{description}</p>
38
+ </div>
39
+ {#if action}
40
+ <div class="mt-2 flex flex-wrap items-center justify-center gap-2">
41
+ {@render action()}
42
+ </div>
43
+ {/if}
44
+ </section>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ title: string;
4
+ description: string;
5
+ class?: string;
6
+ action?: Snippet;
7
+ media?: Snippet;
8
+ }
9
+ declare const EmptyState: import("svelte").Component<Props, {}, "">;
10
+ type EmptyState = ReturnType<typeof EmptyState>;
11
+ export default EmptyState;
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { generateId } from "../util/ssr-safe.js";
4
+
5
+ export type FormFieldControlProps = {
6
+ id: string;
7
+ 'aria-describedby'?: string;
8
+ 'aria-invalid'?: 'true';
9
+ disabled?: boolean;
10
+ required?: boolean;
11
+ };
12
+
13
+ interface Props {
14
+ id?: string;
15
+ label: string;
16
+ description?: string;
17
+ error?: string;
18
+ required?: boolean;
19
+ disabled?: boolean;
20
+ class?: string;
21
+ labelClass?: string;
22
+ control: Snippet<[FormFieldControlProps]>;
23
+ meta?: Snippet;
24
+ }
25
+
26
+ let {
27
+ id: idProp,
28
+ label,
29
+ description,
30
+ error,
31
+ required = false,
32
+ disabled = false,
33
+ class: className = '',
34
+ labelClass = '',
35
+ control,
36
+ meta,
37
+ }: Props = $props();
38
+
39
+ const fallbackId = generateId('field');
40
+ const fieldId = $derived(idProp ?? fallbackId);
41
+ const descriptionId = $derived(
42
+ description ? `${fieldId}-description` : undefined,
43
+ );
44
+ const errorId = $derived(error ? `${fieldId}-error` : undefined);
45
+ const describedBy = $derived(
46
+ [descriptionId, errorId].filter(Boolean).join(' ') || undefined,
47
+ );
48
+ </script>
49
+
50
+ <div class={['space-y-1.5', className].filter(Boolean).join(' ')}>
51
+ <div class="flex items-baseline justify-between gap-3">
52
+ <label
53
+ class={[
54
+ 'text-sm font-medium text-label',
55
+ disabled ? 'opacity-60' : '',
56
+ labelClass,
57
+ ]
58
+ .filter(Boolean)
59
+ .join(' ')}
60
+ for={fieldId}
61
+ >
62
+ {label}
63
+ {#if required}
64
+ <span class="ml-1 text-error" aria-hidden="true">*</span>
65
+ <span class="sr-only">(required)</span>
66
+ {/if}
67
+ </label>
68
+
69
+ {#if meta}
70
+ <div class="text-xs text-description">{@render meta()}</div>
71
+ {/if}
72
+ </div>
73
+
74
+ {@render control({
75
+ id: fieldId,
76
+ 'aria-describedby': describedBy,
77
+ 'aria-invalid': error ? 'true' : undefined,
78
+ disabled,
79
+ required,
80
+ })}
81
+
82
+ {#if description}
83
+ <p id={descriptionId} class="text-xs text-description">{description}</p>
84
+ {/if}
85
+
86
+ {#if error}
87
+ <p id={errorId} class="text-xs text-error" role="alert">{error}</p>
88
+ {/if}
89
+ </div>
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type FormFieldControlProps = {
3
+ id: string;
4
+ 'aria-describedby'?: string;
5
+ 'aria-invalid'?: 'true';
6
+ disabled?: boolean;
7
+ required?: boolean;
8
+ };
9
+ interface Props {
10
+ id?: string;
11
+ label: string;
12
+ description?: string;
13
+ error?: string;
14
+ required?: boolean;
15
+ disabled?: boolean;
16
+ class?: string;
17
+ labelClass?: string;
18
+ control: Snippet<[FormFieldControlProps]>;
19
+ meta?: Snippet;
20
+ }
21
+ declare const FormField: import("svelte").Component<Props, {}, "">;
22
+ type FormField = ReturnType<typeof FormField>;
23
+ export default FormField;
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import Badge from "../atoms/Badge.svelte";
3
+ import Heading from "../atoms/Heading.svelte";
4
+ import type { ComponentCategory } from "../types/page.types";
5
+
6
+ interface Props {
7
+ title: string;
8
+ description: string;
9
+ category: ComponentCategory;
10
+ variantsStates?: string[];
11
+ }
12
+
13
+ let { title, description, category, variantsStates }: Props = $props();
14
+ </script>
15
+
16
+ <header class="space-y-4">
17
+ <div class="space-y-2">
18
+ <Heading level={1} text={title} />
19
+ <p class="text-lg whitespace-pre-line text-description">{description}</p>
20
+ </div>
21
+
22
+ <div class="flex flex-wrap items-center gap-2">
23
+ <Badge variant="info" text={category} />
24
+ {#if variantsStates?.length}
25
+ <Badge variant="info" text="{variantsStates.length} variants" />
26
+ {/if}
27
+ </div>
28
+
29
+ {#if variantsStates?.length}
30
+ <div class="flex flex-wrap items-center gap-2">
31
+ <span class="text-sm font-medium text-headline">
32
+ Variants / states:
33
+ </span>
34
+ {#each variantsStates as v (v)}
35
+ <span class="rounded-md bg-base-100 px-2 py-1 text-xs text-headline">
36
+ {v}
37
+ </span>
38
+ {/each}
39
+ </div>
40
+ {/if}
41
+ </header>
@@ -0,0 +1,10 @@
1
+ import type { ComponentCategory } from "../types/page.types";
2
+ interface Props {
3
+ title: string;
4
+ description: string;
5
+ category: ComponentCategory;
6
+ variantsStates?: string[];
7
+ }
8
+ declare const Header: import("svelte").Component<Props, {}, "">;
9
+ type Header = ReturnType<typeof Header>;
10
+ export default Header;
@@ -75,7 +75,7 @@
75
75
  class="w-full h-32 min-w-64 object-cover rounded-2xl border-0"
76
76
  />
77
77
  <div
78
- class="absolute inset-0 bg-input min-w-64 opacity-0 group-hover:opacity-100 transition-opacity rounded-2xl flex items-center justify-center"
78
+ class="absolute inset-0 border border-input-border bg-input min-w-64 opacity-0 group-hover:opacity-100 transition-opacity rounded-2xl flex items-center justify-center"
79
79
  >
80
80
  <div class="flex gap-2">
81
81
  <Button