zabi-components 6.0.0 → 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 (197) 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 +35 -16
  7. package/dist/atoms/Button.svelte.d.ts +3 -1
  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 +33 -24
  24. package/dist/atoms/IconButton.svelte.d.ts +4 -8
  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 +90 -11
  55. package/dist/atoms/index.d.ts +6 -0
  56. package/dist/atoms/index.js +6 -0
  57. package/dist/atoms/selection-control.styles.d.ts +25 -0
  58. package/dist/atoms/selection-control.styles.js +30 -0
  59. package/dist/components/atoms/index.d.ts +6 -0
  60. package/dist/components/atoms/index.d.ts.map +1 -1
  61. package/dist/components/atoms/selection-control.styles.d.ts +26 -0
  62. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -0
  63. package/dist/components/index.d.ts +41 -5
  64. package/dist/components/index.d.ts.map +1 -1
  65. package/dist/components/molecules/index.d.ts +10 -0
  66. package/dist/components/molecules/index.d.ts.map +1 -1
  67. package/dist/components/molecules/navigation-menu-context.d.ts +11 -0
  68. package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -1
  69. package/dist/components/molecules/toast-store.d.ts +39 -0
  70. package/dist/components/molecules/toast-store.d.ts.map +1 -0
  71. package/dist/components/organisms/index.d.ts +2 -1
  72. package/dist/components/organisms/index.d.ts.map +1 -1
  73. package/dist/components/types/page.types.d.ts +69 -0
  74. package/dist/components/types/page.types.d.ts.map +1 -0
  75. package/dist/components/types/page.types.js +1 -0
  76. package/dist/components/types/variants.d.ts +64 -0
  77. package/dist/components/types/variants.d.ts.map +1 -0
  78. package/dist/components/types/variants.js +44 -0
  79. package/dist/components/util/fixed-sidebar-flyout.d.ts +23 -0
  80. package/dist/components/util/fixed-sidebar-flyout.d.ts.map +1 -0
  81. package/dist/components/util/focus-utils.d.ts +12 -0
  82. package/dist/components/util/focus-utils.d.ts.map +1 -0
  83. package/dist/components/util/ssr-safe.d.ts +17 -0
  84. package/dist/components/util/ssr-safe.d.ts.map +1 -0
  85. package/dist/index.d.ts +41 -5
  86. package/dist/index.js +44 -8
  87. package/dist/lib/layout-width-tokens.d.ts +17 -0
  88. package/dist/lib/layout-width-tokens.d.ts.map +1 -0
  89. package/dist/lib/showcase/component-docs/Button.d.ts +3 -0
  90. package/dist/lib/showcase/component-docs/Button.d.ts.map +1 -0
  91. package/dist/lib/showcase/component-docs/Input.d.ts +3 -0
  92. package/dist/lib/showcase/component-docs/Input.d.ts.map +1 -0
  93. package/dist/lib/showcase/component-docs/List.d.ts +3 -0
  94. package/dist/lib/showcase/component-docs/List.d.ts.map +1 -0
  95. package/dist/lib/showcase/component-docs/Modal.d.ts +3 -0
  96. package/dist/lib/showcase/component-docs/Modal.d.ts.map +1 -0
  97. package/dist/lib/showcase/component-docs/Radio.d.ts +3 -0
  98. package/dist/lib/showcase/component-docs/Radio.d.ts.map +1 -0
  99. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +3 -0
  100. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +1 -0
  101. package/dist/lib/showcase/component-docs/Skeleton.d.ts +3 -0
  102. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +1 -0
  103. package/dist/lib/showcase/component-docs/_shared.d.ts +12 -0
  104. package/dist/lib/showcase/component-docs/_shared.d.ts.map +1 -0
  105. package/dist/lib/showcase/component-docs/index.d.ts +4 -0
  106. package/dist/lib/showcase/component-docs/index.d.ts.map +1 -0
  107. package/dist/lib/showcase/components-catalog.d.ts.map +1 -1
  108. package/dist/lib/showcase/components-showcase-constants.d.ts +26 -0
  109. package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -1
  110. package/dist/molecules/Alert.svelte +50 -23
  111. package/dist/molecules/Alert.svelte.d.ts +1 -1
  112. package/dist/molecules/ComponentDemo.svelte +2 -2
  113. package/dist/molecules/ContactForm.svelte +1 -1
  114. package/dist/molecules/Dropdown.svelte +99 -109
  115. package/dist/molecules/Dropdown.svelte.d.ts +12 -6
  116. package/dist/molecules/EmptyState.svelte +44 -0
  117. package/dist/molecules/EmptyState.svelte.d.ts +11 -0
  118. package/dist/molecules/FormField.svelte +89 -0
  119. package/dist/molecules/FormField.svelte.d.ts +23 -0
  120. package/dist/molecules/Header.svelte +41 -0
  121. package/dist/molecules/Header.svelte.d.ts +10 -0
  122. package/dist/molecules/Modal.svelte +37 -24
  123. package/dist/molecules/Modal.svelte.d.ts +4 -2
  124. package/dist/molecules/NavigationMenu.svelte +16 -0
  125. package/dist/molecules/NavigationMenu.svelte.d.ts +2 -0
  126. package/dist/molecules/NavigationMenuContent.svelte +9 -1
  127. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  128. package/dist/molecules/NavigationMenuTrigger.svelte +9 -1
  129. package/dist/molecules/Page.svelte +16 -0
  130. package/dist/molecules/Page.svelte.d.ts +8 -0
  131. package/dist/molecules/PropsTable.svelte +61 -0
  132. package/dist/molecules/PropsTable.svelte.d.ts +8 -0
  133. package/dist/molecules/RadioGroup.svelte +197 -0
  134. package/dist/molecules/RadioGroup.svelte.d.ts +20 -0
  135. package/dist/molecules/Section.svelte +2 -11
  136. package/dist/molecules/Section.svelte.d.ts +2 -11
  137. package/dist/molecules/SidebarBrandHeader.svelte +68 -0
  138. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +10 -0
  139. package/dist/molecules/SidebarFooter.svelte +119 -0
  140. package/dist/molecules/SidebarFooter.svelte.d.ts +23 -0
  141. package/dist/molecules/SidebarNavSection.svelte +49 -0
  142. package/dist/molecules/SidebarNavSection.svelte.d.ts +14 -0
  143. package/dist/molecules/SlideUp.svelte +19 -14
  144. package/dist/molecules/SlideUp.svelte.d.ts +1 -1
  145. package/dist/molecules/Tabs.svelte +4 -9
  146. package/dist/molecules/Toaster.svelte +21 -0
  147. package/dist/molecules/Toaster.svelte.d.ts +6 -0
  148. package/dist/molecules/ToasterToast.svelte +217 -0
  149. package/dist/molecules/ToasterToast.svelte.d.ts +7 -0
  150. package/dist/molecules/index.d.ts +10 -0
  151. package/dist/molecules/index.js +10 -0
  152. package/dist/molecules/navigation-menu-context.d.ts +11 -0
  153. package/dist/molecules/navigation-menu-context.js +18 -0
  154. package/dist/molecules/toast-store.d.ts +38 -0
  155. package/dist/molecules/toast-store.js +34 -0
  156. package/dist/organisms/SidebarAccountPanel.svelte +118 -0
  157. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +20 -0
  158. package/dist/organisms/SidebarNavigation.svelte +267 -237
  159. package/dist/organisms/SidebarNavigation.svelte.d.ts +21 -5
  160. package/dist/organisms/SidebarPanel.svelte +54 -22
  161. package/dist/organisms/SidebarPanel.svelte.d.ts +3 -0
  162. package/dist/organisms/{Navbar.svelte → TopNavbar.svelte} +22 -17
  163. package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
  164. package/dist/organisms/index.d.ts +2 -1
  165. package/dist/organisms/index.js +2 -1
  166. package/dist/routes/lib/focus-utils.d.ts +7 -18
  167. package/dist/routes/lib/focus-utils.ts +32 -34
  168. package/dist/routes/lib/ssr-safe.ts +5 -4
  169. package/dist/routes/lib/variant-utils.ts +48 -39
  170. package/dist/types/components.d.ts +187 -0
  171. package/dist/types/components.d.ts.map +1 -0
  172. package/dist/types/events.d.ts +200 -0
  173. package/dist/types/events.d.ts.map +1 -0
  174. package/dist/types/index.d.ts +171 -0
  175. package/dist/types/index.d.ts.map +1 -0
  176. package/dist/types/page.types.d.ts +2 -31
  177. package/dist/types/page.types.d.ts.map +1 -1
  178. package/dist/types/page.types.js +1 -0
  179. package/dist/types/page.types.ts +79 -0
  180. package/dist/types/variants.d.ts +3 -0
  181. package/dist/types/variants.d.ts.map +1 -0
  182. package/dist/types/variants.js +1 -0
  183. package/dist/types/variants.ts +5 -6
  184. package/dist/util/fixed-sidebar-flyout.d.ts +22 -0
  185. package/dist/util/fixed-sidebar-flyout.js +148 -0
  186. package/dist/util/focus-utils.d.ts +11 -0
  187. package/dist/util/focus-utils.js +94 -0
  188. package/dist/util/ssr-safe.d.ts +16 -0
  189. package/dist/util/ssr-safe.js +48 -0
  190. package/dist/zabi-components-colors.css +14 -1
  191. package/dist/zabi-components-theme-dark-only.css +5 -0
  192. package/dist/zabi-components-theme-dark.css +5 -0
  193. package/dist/zabi-components-theme-only.css +9 -1
  194. package/dist/zabi-components-theme.css +9 -1
  195. package/dist/zabi-components.css +877 -192
  196. package/package.json +19 -7
  197. package/dist/organisms/Navbar.svelte.d.ts +0 -41
@@ -25,7 +25,7 @@
25
25
  let colorMap: HTMLCanvasElement | undefined;
26
26
  let colorMapContainer: HTMLDivElement | undefined;
27
27
 
28
- // HSL values (0-360, 0-100, 0-100)
28
+ // Working color in HSL (h: 0–360; s, l: 0–100).
29
29
  let hue = $state(0);
30
30
  let saturation = $state(100);
31
31
  let lightness = $state(50);
@@ -156,7 +156,6 @@
156
156
  const width = colorMap.width;
157
157
  const height = colorMap.height;
158
158
 
159
- // Draw saturation/lightness gradient
160
159
  for (let x = 0; x < width; x++) {
161
160
  for (let y = 0; y < height; y++) {
162
161
  const s = (x / width) * 100;
@@ -284,7 +283,7 @@
284
283
  type="button"
285
284
  onclick={togglePicker}
286
285
  {disabled}
287
- class="w-11 h-11 rounded-lg border-2 border-card shrink-0 cursor-pointer hover:ring-2 hover:ring-brand-500 transition-colors disabled:cursor-not-allowed disabled:opacity-50"
286
+ class="focus-ring w-11 h-11 rounded-lg border-2 border-card shrink-0 cursor-pointer hover:ring-2 hover:ring-border transition-colors disabled:cursor-not-allowed disabled:opacity-50"
288
287
  style="background-color: {displayColor};"
289
288
  aria-label="Open color picker"
290
289
  aria-expanded={isOpen}
@@ -298,7 +297,6 @@
298
297
  aria-label="Color picker"
299
298
  >
300
299
  <div class="space-y-4">
301
- <!-- Color Map -->
302
300
  <div
303
301
  use:setColorMapContainer
304
302
  class="relative w-full h-48 rounded-xl overflow-hidden cursor-crosshair"
@@ -315,14 +313,12 @@
315
313
  height={192}
316
314
  class="w-full h-full"
317
315
  ></canvas>
318
- <!-- Color indicator -->
319
316
  <div
320
317
  class="absolute w-4 h-4 border-2 border-white rounded-full shadow-lg pointer-events-none transform -translate-x-1/2 -translate-y-1/2"
321
318
  style="left: {pickerIndicatorX}; top: {pickerIndicatorY};"
322
319
  ></div>
323
320
  </div>
324
321
 
325
- <!-- Hue Slider -->
326
322
  <div class="space-y-2">
327
323
  <div
328
324
  class="relative h-6 rounded-lg overflow-hidden"
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ type MaxWidth = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
5
+
6
+ interface Props {
7
+ as?: 'div' | 'section' | 'main' | 'article';
8
+ maxWidth?: MaxWidth;
9
+ padded?: boolean;
10
+ class?: string;
11
+ children?: Snippet;
12
+ }
13
+
14
+ let {
15
+ as: Tag = 'div',
16
+ maxWidth = 'xl',
17
+ padded = true,
18
+ class: className = '',
19
+ children,
20
+ }: Props = $props();
21
+
22
+ const maxClass: Record<MaxWidth, string> = {
23
+ sm: 'max-w-screen-sm',
24
+ md: 'max-w-screen-md',
25
+ lg: 'max-w-screen-lg',
26
+ xl: 'max-w-screen-xl',
27
+ '2xl': 'max-w-screen-2xl',
28
+ full: 'max-w-full',
29
+ };
30
+ </script>
31
+
32
+ <svelte:element
33
+ this={Tag}
34
+ class={['mx-auto w-full', maxClass[maxWidth], padded ? 'px-4 sm:px-6' : '', className]
35
+ .filter(Boolean)
36
+ .join(' ')}
37
+ >
38
+ {#if children}
39
+ {@render children()}
40
+ {/if}
41
+ </svelte:element>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ type MaxWidth = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
3
+ interface Props {
4
+ as?: 'div' | 'section' | 'main' | 'article';
5
+ maxWidth?: MaxWidth;
6
+ padded?: boolean;
7
+ class?: string;
8
+ children?: Snippet;
9
+ }
10
+ declare const Container: import("svelte").Component<Props, {}, "">;
11
+ type Container = ReturnType<typeof Container>;
12
+ export default Container;
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ type Orientation = 'horizontal' | 'vertical';
3
+
4
+ interface Props {
5
+ orientation?: Orientation;
6
+ label?: string;
7
+ inset?: boolean;
8
+ class?: string;
9
+ labelClass?: string;
10
+ /** Decorative: omit `role="separator"` so SR does not announce a split. */
11
+ decorative?: boolean;
12
+ }
13
+
14
+ let {
15
+ orientation = 'horizontal',
16
+ label,
17
+ inset = false,
18
+ class: className = '',
19
+ labelClass = '',
20
+ decorative = true,
21
+ }: Props = $props();
22
+
23
+ const isVertical = $derived(orientation === 'vertical');
24
+
25
+ const lineClasses = $derived(
26
+ [
27
+ 'bg-border',
28
+ isVertical ? 'h-full w-px flex-1' : 'h-px w-full flex-1',
29
+ inset ? (isVertical ? 'my-2' : 'mx-2') : undefined,
30
+ ]
31
+ .filter(Boolean)
32
+ .join(' '),
33
+ );
34
+ </script>
35
+
36
+ <div
37
+ class={[
38
+ 'flex items-center text-description',
39
+ isVertical ? 'h-full min-h-[1rem] flex-col' : 'w-full flex-row',
40
+ className,
41
+ ]
42
+ .filter(Boolean)
43
+ .join(' ')}
44
+ >
45
+ <div
46
+ aria-hidden={decorative ? 'true' : undefined}
47
+ role={decorative ? 'presentation' : 'separator'}
48
+ aria-orientation={decorative ? undefined : orientation}
49
+ class={lineClasses}
50
+ ></div>
51
+
52
+ {#if label}
53
+ <span
54
+ class={['shrink-0 px-3 text-xs font-medium text-description', labelClass]
55
+ .filter(Boolean)
56
+ .join(' ')}
57
+ >
58
+ {label}
59
+ </span>
60
+
61
+ <div
62
+ aria-hidden={decorative ? 'true' : undefined}
63
+ role={decorative ? 'presentation' : 'separator'}
64
+ aria-orientation={decorative ? undefined : orientation}
65
+ class={lineClasses}
66
+ ></div>
67
+ {/if}
68
+ </div>
@@ -0,0 +1,13 @@
1
+ type Orientation = 'horizontal' | 'vertical';
2
+ interface Props {
3
+ orientation?: Orientation;
4
+ label?: string;
5
+ inset?: boolean;
6
+ class?: string;
7
+ labelClass?: string;
8
+ /** Decorative: omit `role="separator"` so SR does not announce a split. */
9
+ decorative?: boolean;
10
+ }
11
+ declare const Divider: import("svelte").Component<Props, {}, "">;
12
+ type Divider = ReturnType<typeof Divider>;
13
+ export default Divider;
@@ -1,64 +1,49 @@
1
1
  <script lang="ts">
2
- import { onMount } from "svelte";
2
+ import type { Component, Snippet } from "svelte";
3
3
 
4
4
  interface Props {
5
- icon?: string;
6
5
  title: string;
7
- description: string;
8
- className?: string;
6
+ description?: string;
7
+ icon?: Component<{ size?: number; class?: string }>;
8
+ children?: Snippet;
9
+ class?: string;
9
10
  }
10
11
 
11
12
  let {
12
- icon = "✨",
13
13
  title,
14
14
  description,
15
- className = "",
16
- ...restProps
15
+ icon: Icon,
16
+ children,
17
+ class: className = "",
17
18
  }: Props = $props();
18
-
19
- let mounted = $state(false);
20
-
21
- onMount(() => {
22
- mounted = true;
23
- });
24
19
  </script>
25
20
 
26
- {#if mounted}
27
- <div
28
- class="p-6 rounded-lg bg-base-50 border border-border hover:border-primary/20 hover:shadow-sm transition-colors duration-200 {className}"
29
- {...restProps}
30
- >
31
- <div class="flex items-start gap-4">
32
- <div class="shrink-0 text-2xl" aria-hidden="true">
33
- {icon}
34
- </div>
35
- <div class="flex-1 min-w-0">
36
- <h3 class="text-lg font-semibold text-headline mb-2">
37
- {title}
38
- </h3>
39
- <p class="text-description leading-relaxed">
40
- {description}
41
- </p>
42
- </div>
43
- </div>
21
+ <article
22
+ class="flex flex-col gap-3 p-6 rounded-2xl bg-surface-1 border border-base-200 transition-colors duration-200 hover:border-base-300 {className}"
23
+ >
24
+ {#if Icon}
25
+ <span
26
+ class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-brand-50 text-brand-600"
27
+ aria-hidden="true"
28
+ >
29
+ <Icon size={20} />
30
+ </span>
31
+ {/if}
32
+
33
+ <div class="flex flex-col gap-1.5">
34
+ <h3 class="text-base font-semibold leading-snug text-headline">
35
+ {title}
36
+ </h3>
37
+ {#if description}
38
+ <p class="text-sm leading-relaxed text-description">
39
+ {description}
40
+ </p>
41
+ {/if}
44
42
  </div>
45
- {:else}
46
- <div
47
- class="p-6 rounded-lg bg-base-100 border border-base-300 hover:border-base-400 hover:shadow-sm transition-colors duration-200 {className}"
48
- {...restProps}
49
- >
50
- <div class="flex items-start gap-4">
51
- <div class="shrink-0 text-2xl" aria-hidden="true">
52
- {icon}
53
- </div>
54
- <div class="flex-1 min-w-0">
55
- <h3 class="text-lg font-semibold text-headline mb-2">
56
- {title}
57
- </h3>
58
- <p class="text-description leading-relaxed">
59
- {description}
60
- </p>
61
- </div>
43
+
44
+ {#if children}
45
+ <div class="mt-2">
46
+ {@render children()}
62
47
  </div>
63
- </div>
64
- {/if}
48
+ {/if}
49
+ </article>
@@ -1,9 +1,14 @@
1
+ import type { Component, Snippet } from "svelte";
1
2
  interface Props {
2
- icon?: string;
3
3
  title: string;
4
- description: string;
5
- className?: string;
4
+ description?: string;
5
+ icon?: Component<{
6
+ size?: number;
7
+ class?: string;
8
+ }>;
9
+ children?: Snippet;
10
+ class?: string;
6
11
  }
7
- declare const FeatureCard: import("svelte").Component<Props, {}, "">;
12
+ declare const FeatureCard: Component<Props, {}, "">;
8
13
  type FeatureCard = ReturnType<typeof FeatureCard>;
9
14
  export default FeatureCard;
@@ -1,21 +1,17 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
- import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
3
+ import type { ButtonVariant, SizeVariant } from "../types/variants.js";
4
4
 
5
5
  interface Props {
6
- /** Visual style variant. */
7
6
  variant?: ButtonVariant;
8
- /** Size variant controlling padding and radius. */
9
7
  size?: SizeVariant;
10
- /** Whether the button is disabled. */
11
8
  disabled?: boolean;
12
- /** Button type attribute. */
9
+ loading?: boolean;
13
10
  type?: "button" | "submit" | "reset";
14
- /** Accessible label for icon-only buttons. */
11
+ /** Required for icon-only usage (no visible text). */
15
12
  label?: string;
16
- /** Click handler for native click events. */
13
+ class?: string;
17
14
  onclick?: (event: MouseEvent) => void;
18
- /** Icon content. */
19
15
  children?: Snippet;
20
16
  }
21
17
 
@@ -23,66 +19,79 @@
23
19
  variant = "primary",
24
20
  size = "md",
25
21
  disabled = false,
22
+ loading = false,
26
23
  type = "button",
27
24
  label = "",
25
+ class: className = "",
28
26
  onclick,
29
27
  children,
30
28
  ...restProps
31
29
  }: Props = $props();
32
30
 
33
- const sizeClass = $derived(() => {
31
+ const isDisabled = $derived(disabled || loading);
32
+
33
+ const sizeClass = $derived.by(() => {
34
34
  if (size === "sm") {
35
35
  return {
36
36
  padding: "p-2",
37
37
  radius: "rounded-md",
38
+ spinner: "size-4",
38
39
  };
39
40
  } else if (size === "lg") {
40
41
  return {
41
42
  padding: "p-3",
42
43
  radius: "rounded-xl",
44
+ spinner: "size-6",
43
45
  };
44
46
  } else {
45
47
  return {
46
48
  padding: "p-2.5",
47
49
  radius: "rounded-lg",
50
+ spinner: "size-5",
48
51
  };
49
52
  }
50
53
  });
51
54
 
52
- const variantClass = $derived(() => {
55
+ const variantClass = $derived.by(() => {
53
56
  return variant === "primary"
54
- ? "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
57
+ ? "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98]"
55
58
  : variant === "secondary"
56
- ? "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
59
+ ? "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active active:scale-[0.98]"
57
60
  : variant === "danger"
58
- ? "bg-action-danger text-inverse hover:bg-action-danger-hover active:bg-action-danger-active focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
61
+ ? "bg-action-danger text-inverse hover:bg-action-danger-hover active:bg-action-danger-active active:scale-[0.98] focus-ring--danger"
59
62
  : variant === "ghost"
60
- ? "bg-transparent text-headline hover:bg-base-100 active:bg-base-200 focus:ring-2 focus:ring-base-500 focus:ring-offset-2 disabled:text-disabled"
63
+ ? "bg-transparent text-headline hover:bg-base-100 active:bg-base-200 active:scale-[0.98] focus-ring--muted disabled:text-disabled"
61
64
  : variant === "outline"
62
- ? "bg-transparent border-2 border-action-primary text-headline hover:bg-action-secondary hover:text-action-primary active:bg-action-primary-active active:text-action-primary focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 disabled:border-disabled disabled:text-disabled"
65
+ ? "bg-transparent border-2 border-action-primary text-headline hover:bg-action-secondary hover:text-action-primary active:bg-action-primary-active active:text-action-primary active:scale-[0.98] disabled:border-disabled disabled:text-disabled"
63
66
  : variant === "link"
64
- ? "text-brand-700 active:text-brand-700 underline-offset-4 hover:underline focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 disabled:text-disabled disabled:no-underline"
65
- : "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2";
67
+ ? "text-brand-700 active:text-brand-700 hover:underline disabled:text-disabled disabled:no-underline"
68
+ : "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98]";
66
69
  });
67
70
 
68
- const buttonClasses = $derived(() => {
69
- const sizeStyles = sizeClass();
71
+ const buttonClasses = $derived.by(() => {
72
+ const sizeStyles = sizeClass;
70
73
  const baseClasses =
71
- "inline-flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none";
74
+ "inline-flex focus-ring items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100";
72
75
 
73
- return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.radius} ${variantClass()}`.trim();
76
+ return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.radius} ${variantClass} ${className}`.trim();
74
77
  });
75
78
  </script>
76
79
 
77
80
  <button
78
81
  {type}
79
- class={buttonClasses()}
80
- {disabled}
82
+ class={buttonClasses}
83
+ disabled={isDisabled}
84
+ aria-busy={loading ? "true" : undefined}
81
85
  {onclick}
82
86
  aria-label={label || undefined}
83
87
  {...restProps}
84
88
  >
85
- {#if children}
89
+ {#if loading}
90
+ <span
91
+ class="inline-block {sizeClass.spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-80"
92
+ aria-hidden="true"
93
+ ></span>
94
+ {:else if children}
86
95
  {@render children()}
87
96
  {/if}
88
97
  </button>
@@ -1,19 +1,15 @@
1
1
  import type { Snippet } from "svelte";
2
- import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
2
+ import type { ButtonVariant, SizeVariant } from "../types/variants.js";
3
3
  interface Props {
4
- /** Visual style variant. */
5
4
  variant?: ButtonVariant;
6
- /** Size variant controlling padding and radius. */
7
5
  size?: SizeVariant;
8
- /** Whether the button is disabled. */
9
6
  disabled?: boolean;
10
- /** Button type attribute. */
7
+ loading?: boolean;
11
8
  type?: "button" | "submit" | "reset";
12
- /** Accessible label for icon-only buttons. */
9
+ /** Required for icon-only usage (no visible text). */
13
10
  label?: string;
14
- /** Click handler for native click events. */
11
+ class?: string;
15
12
  onclick?: (event: MouseEvent) => void;
16
- /** Icon content. */
17
13
  children?: Snippet;
18
14
  }
19
15
  declare const IconButton: import("svelte").Component<Props, {}, "">;
@@ -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-input-border focus:border-input-border focus:ring-2 focus:ring-brand-500";
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 border 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