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
@@ -0,0 +1,116 @@
1
+ <script lang="ts">
2
+ import Badge from "./Badge.svelte";
3
+ import type { ExtendedSemanticVariant } from "../types/variants.js";
4
+
5
+ interface Props {
6
+ title: string;
7
+ description: string;
8
+ href?: string;
9
+ onclick?: (event: MouseEvent) => void | Promise<void>;
10
+ disabled?: boolean;
11
+ loading?: boolean;
12
+ badgeText?: string;
13
+ badgeVariant?: ExtendedSemanticVariant;
14
+ /** Overrides the default accessible name for the clickable region. */
15
+ ariaLabel?: string;
16
+ className?: string;
17
+ target?: HTMLAnchorElement["target"];
18
+ rel?: string;
19
+ }
20
+
21
+ let {
22
+ title,
23
+ description,
24
+ href,
25
+ onclick,
26
+ disabled = false,
27
+ loading = false,
28
+ badgeText,
29
+ badgeVariant = "neutral",
30
+ ariaLabel,
31
+ className = "",
32
+ target,
33
+ rel,
34
+ ...restProps
35
+ }: Props = $props();
36
+
37
+ const isDisabled = $derived(disabled || loading);
38
+
39
+ const computedAriaLabel = $derived(
40
+ ariaLabel ?? (href ? `Open ${title}` : title),
41
+ );
42
+
43
+ const baseClasses =
44
+ "group focus-ring focus-ring--muted block rounded-2xl border border-base-200 bg-base-0 p-6 text-left transition-all duration-200 hover:border-headline/30 hover:bg-base-50 active:scale-[0.99] active:bg-base-100";
45
+
46
+ const disabledClasses =
47
+ "opacity-50 cursor-not-allowed pointer-events-none hover:border-base-200 hover:bg-base-0 active:scale-100";
48
+
49
+ const panelClasses = $derived(
50
+ [baseClasses, isDisabled ? disabledClasses : "", className]
51
+ .filter(Boolean)
52
+ .join(" ")
53
+ .trim(),
54
+ );
55
+
56
+ function handleKeydown(event: KeyboardEvent) {
57
+ if (isDisabled) return;
58
+ if (!href && onclick && (event.key === "Enter" || event.key === " ")) {
59
+ event.preventDefault();
60
+ onclick(event as unknown as MouseEvent);
61
+ }
62
+ }
63
+ </script>
64
+
65
+ {#snippet body()}
66
+ <div class="flex items-start justify-between gap-4">
67
+ <div class="min-w-0">
68
+ <h3
69
+ class="truncate text-lg font-semibold text-headline transition-colors group-hover:text-headline/90"
70
+ >
71
+ {title}
72
+ </h3>
73
+ <p class="mt-3 line-clamp-2 text-description">{description}</p>
74
+ </div>
75
+ <div class="flex shrink-0 items-center gap-3">
76
+ {#if badgeText}
77
+ <Badge variant={badgeVariant} text={badgeText} />
78
+ {/if}
79
+ {#if loading}
80
+ <span
81
+ class="inline-block size-5 shrink-0 animate-spin rounded-full border-2 border-description border-t-transparent opacity-70"
82
+ aria-hidden="true"
83
+ ></span>
84
+ {/if}
85
+ </div>
86
+ </div>
87
+ {/snippet}
88
+
89
+ {#if href}
90
+ <a
91
+ class={panelClasses}
92
+ href={isDisabled ? undefined : href}
93
+ aria-label={computedAriaLabel}
94
+ aria-disabled={isDisabled ? "true" : undefined}
95
+ aria-busy={loading ? "true" : undefined}
96
+ tabindex={isDisabled ? -1 : undefined}
97
+ target={target}
98
+ rel={rel}
99
+ {...restProps}
100
+ >
101
+ {@render body()}
102
+ </a>
103
+ {:else}
104
+ <button
105
+ type="button"
106
+ class={panelClasses}
107
+ aria-label={computedAriaLabel}
108
+ aria-busy={loading ? "true" : undefined}
109
+ disabled={isDisabled}
110
+ onclick={onclick}
111
+ onkeydown={handleKeydown}
112
+ {...restProps}
113
+ >
114
+ {@render body()}
115
+ </button>
116
+ {/if}
@@ -0,0 +1,19 @@
1
+ import type { ExtendedSemanticVariant } from "../types/variants.js";
2
+ interface Props {
3
+ title: string;
4
+ description: string;
5
+ href?: string;
6
+ onclick?: (event: MouseEvent) => void | Promise<void>;
7
+ disabled?: boolean;
8
+ loading?: boolean;
9
+ badgeText?: string;
10
+ badgeVariant?: ExtendedSemanticVariant;
11
+ /** Overrides the default accessible name for the clickable region. */
12
+ ariaLabel?: string;
13
+ className?: string;
14
+ target?: HTMLAnchorElement["target"];
15
+ rel?: string;
16
+ }
17
+ declare const ActionPanel: import("svelte").Component<Props, {}, "">;
18
+ type ActionPanel = ReturnType<typeof ActionPanel>;
19
+ export default ActionPanel;
@@ -3,7 +3,7 @@
3
3
  import type {
4
4
  ExtendedSemanticVariant,
5
5
  SizeVariant,
6
- } from "../../types/variants.js";
6
+ } from "../types/variants.js";
7
7
 
8
8
  interface Props {
9
9
  variant?: ExtendedSemanticVariant;
@@ -1,4 +1,4 @@
1
- import type { ExtendedSemanticVariant, SizeVariant } from "../../types/variants.js";
1
+ import type { ExtendedSemanticVariant, SizeVariant } from "../types/variants.js";
2
2
  interface Props {
3
3
  variant?: ExtendedSemanticVariant;
4
4
  size?: SizeVariant;
@@ -1,14 +1,16 @@
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
6
  variant?: ButtonVariant;
7
7
  size?: SizeVariant;
8
8
  disabled?: boolean;
9
+ loading?: boolean;
9
10
  type?: "button" | "submit" | "reset";
10
11
  text?: string;
11
12
  isFullWidth?: boolean;
13
+ class?: string;
12
14
  onclick?: (event: MouseEvent) => void;
13
15
  children?: Snippet;
14
16
  }
@@ -17,15 +19,19 @@
17
19
  variant = "primary",
18
20
  size = "md",
19
21
  disabled = false,
22
+ loading = false,
20
23
  type = "button",
21
24
  text = "",
22
25
  isFullWidth = false,
26
+ class: className = "",
23
27
  onclick,
24
28
  children,
25
29
  ...restProps
26
30
  }: Props = $props();
27
31
 
28
- const sizeClass = $derived(() => {
32
+ const isDisabled = $derived(disabled || loading);
33
+
34
+ const sizeClass = $derived.by(() => {
29
35
  if (size === "sm") {
30
36
  return {
31
37
  padding: "px-4 py-2.5",
@@ -59,33 +65,46 @@
59
65
  }
60
66
  });
61
67
 
62
- const variantClass = $derived(() => {
68
+ const variantClass = $derived.by(() => {
63
69
  return variant === "primary"
64
- ? "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"
70
+ ? "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98]"
65
71
  : variant === "secondary"
66
- ? "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"
72
+ ? "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active active:scale-[0.98]"
67
73
  : variant === "danger"
68
- ? "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"
74
+ ? "bg-action-danger text-inverse hover:bg-action-danger-hover active:bg-action-danger-active active:scale-[0.98] focus-ring--danger"
69
75
  : variant === "ghost"
70
- ? "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"
71
- : variant === "outline"
72
- ? "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"
76
+ ? "bg-transparent text-headline hover:bg-base-100 active:bg-base-200 active:scale-[0.98] focus-ring--muted disabled:text-disabled"
77
+ : variant === "outline"
78
+ ? "bg-transparent border border-border text-headline hover:bg-action-secondary hover:border-border-medium hover:text-action-primary active:bg-action-primary-active active:text-action-primary active:scale-[0.98] disabled:border-disabled disabled:text-disabled"
73
79
  : variant === "link"
74
- ? "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"
75
- : "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";
80
+ ? "text-brand-700 active:text-brand-700 underline-offset-4 hover:underline disabled:text-disabled disabled:no-underline"
81
+ : "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98]";
76
82
  });
77
83
 
78
- const buttonClasses = $derived(() => {
79
- const sizeStyles = sizeClass();
84
+ const buttonClasses = $derived.by(() => {
85
+ const sizeStyles = sizeClass;
80
86
  const flexClass = isFullWidth ? "flex" : "inline-flex";
81
87
  const widthClass = isFullWidth ? "w-full" : "";
82
- const baseClasses = `${flexClass} items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none`;
88
+ const baseClasses = `${flexClass} focus-ring items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100`;
83
89
 
84
- return `${baseClasses} ${widthClass} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.font} ${sizeStyles.leading} ${sizeStyles.tracking} ${sizeStyles.radius} ${sizeStyles.gap} ${variantClass()}`.trim();
90
+ return `${baseClasses} ${widthClass} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.font} ${sizeStyles.leading} ${sizeStyles.tracking} ${sizeStyles.radius} ${sizeStyles.gap} ${variantClass} ${className}`.trim();
85
91
  });
86
92
  </script>
87
93
 
88
- <button {type} class={buttonClasses()} {disabled} {onclick} {...restProps}>
94
+ <button
95
+ {type}
96
+ class={buttonClasses}
97
+ disabled={isDisabled}
98
+ aria-busy={loading ? "true" : undefined}
99
+ {onclick}
100
+ {...restProps}
101
+ >
102
+ {#if loading}
103
+ <span
104
+ class="inline-block size-4 shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-80"
105
+ aria-hidden="true"
106
+ ></span>
107
+ {/if}
89
108
  {#if text}
90
109
  {text}
91
110
  {:else if children}
@@ -1,12 +1,14 @@
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
4
  variant?: ButtonVariant;
5
5
  size?: SizeVariant;
6
6
  disabled?: boolean;
7
+ loading?: boolean;
7
8
  type?: "button" | "submit" | "reset";
8
9
  text?: string;
9
10
  isFullWidth?: boolean;
11
+ class?: string;
10
12
  onclick?: (event: MouseEvent) => void;
11
13
  children?: Snippet;
12
14
  }
@@ -1,70 +1,73 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
- import type { CardVariant, SizeVariant } from "../../types/variants.js";
4
- import CardHeader from "./CardHeader.svelte";
5
- import CardContent from "./CardContent.svelte";
3
+ import type { CardVariant, SizeVariant } from "../types/variants.js";
6
4
 
7
5
  interface Props {
8
- title?: string;
9
- description?: string;
10
- image?: string;
11
6
  onclick?: (event: MouseEvent) => void | Promise<void>;
12
7
  size?: SizeVariant;
13
8
  variant?: CardVariant;
14
9
  fullWidth?: boolean;
10
+ className?: string;
11
+ /** Required when `onclick` is set (no visible label). */
12
+ ariaLabel?: string;
15
13
  children?: Snippet;
16
14
  }
17
15
 
18
16
  let {
19
- title = "",
20
- description = "",
21
- image = "",
22
17
  onclick,
23
18
  size = "md",
24
19
  variant = "default",
25
20
  fullWidth = true,
21
+ className = "",
22
+ ariaLabel,
26
23
  children,
27
24
  ...restProps
28
25
  }: Props = $props();
29
26
 
30
- const sizeClass = $derived(() => {
31
- return size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6";
32
- });
33
-
34
- const variantClasses = $derived(() => {
35
- switch (variant) {
36
- case "elevated":
37
- return "bg-card shadow-lg";
38
- case "outlined":
39
- return "bg-card border-2 border-base-200 shadow-none";
40
- case "flat":
41
- return "bg-card shadow-none border-none";
42
- default:
43
- return "bg-card shadow-sm";
44
- }
45
- });
27
+ const sizeClass = $derived(
28
+ size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6",
29
+ );
46
30
 
47
- const useOldAPI = $derived(
48
- title !== "" || description !== "" || image !== "",
31
+ const radiusClass = $derived(
32
+ size === "sm"
33
+ ? "rounded-xl"
34
+ : size === "lg"
35
+ ? "rounded-3xl"
36
+ : "rounded-2xl",
49
37
  );
50
38
 
51
- const cardClasses = $derived(() => {
52
- const baseClasses = `rounded-lg transition-all duration-200 ${variantClasses()}`;
53
- const interactiveClasses = onclick
54
- ? variant === "elevated"
55
- ? "cursor-pointer hover:shadow-xl hover:bg-card-hover"
56
- : variant === "outlined"
57
- ? "cursor-pointer hover:border-brand-500 hover:bg-card-hover"
58
- : variant === "flat"
59
- ? "cursor-pointer hover:bg-card-hover"
60
- : "cursor-pointer hover:shadow-lg hover:bg-card-hover"
61
- : "";
62
- const widthClasses = fullWidth ? "w-full" : "";
63
- const minWidthClass = useOldAPI ? "min-w-64" : "";
64
- const paddingClass = useOldAPI ? sizeClass() : "";
39
+ const variantClasses = $derived(
40
+ variant === "elevated"
41
+ ? "bg-card shadow-lg"
42
+ : variant === "outlined"
43
+ ? "bg-card border-2 border-base-200 shadow-none"
44
+ : variant === "flat"
45
+ ? "bg-card shadow-none border-none"
46
+ : "bg-card shadow-sm",
47
+ );
65
48
 
66
- return `${baseClasses} ${interactiveClasses} ${widthClasses} ${minWidthClass} ${paddingClass}`.trim();
67
- });
49
+ const cardClasses = $derived(
50
+ [
51
+ radiusClass,
52
+ "transition-all duration-200",
53
+ variantClasses,
54
+ onclick
55
+ ? variant === "elevated"
56
+ ? "cursor-pointer hover:shadow-xl hover:bg-card-hover"
57
+ : variant === "outlined"
58
+ ? "cursor-pointer hover:border-brand-500 hover:bg-card-hover"
59
+ : variant === "flat"
60
+ ? "cursor-pointer hover:bg-card-hover"
61
+ : "cursor-pointer hover:shadow-lg hover:bg-card-hover"
62
+ : "",
63
+ fullWidth ? "w-full" : "",
64
+ sizeClass,
65
+ className,
66
+ ]
67
+ .filter(Boolean)
68
+ .join(" ")
69
+ .trim(),
70
+ );
68
71
 
69
72
  function handleKeydown(event: KeyboardEvent) {
70
73
  if (onclick && (event.key === "Enter" || event.key === " ")) {
@@ -74,37 +77,16 @@
74
77
  }
75
78
 
76
79
  const cardRole = $derived(onclick ? "button" : undefined);
77
- const cardAriaLabel = $derived(onclick && title ? title : undefined);
78
80
  </script>
79
81
 
80
82
  <div
81
- class={cardClasses()}
83
+ class={cardClasses}
82
84
  {onclick}
83
85
  role={cardRole}
84
86
  {...onclick ? { tabindex: 0 } : {}}
85
- aria-label={cardAriaLabel}
87
+ aria-label={ariaLabel}
86
88
  onkeydown={onclick ? handleKeydown : undefined}
87
89
  {...restProps}
88
90
  >
89
- {#if useOldAPI}
90
- <!-- Old API: Using title/description/image props -->
91
- {#if title || description}
92
- <CardHeader {title} {description} />
93
- {/if}
94
-
95
- {#if image || children}
96
- <CardContent {image} imageAlt={title}>
97
- {#if children}
98
- {@render children()}
99
- {/if}
100
- </CardContent>
101
- {/if}
102
- {:else}
103
- <!-- New API: Using compound components (CardHeader, CardContent, CardFooter) -->
104
- {#if children}
105
- <CardContent className="pt-6">
106
- {@render children()}
107
- </CardContent>
108
- {/if}
109
- {/if}
91
+ {@render children?.()}
110
92
  </div>
@@ -1,13 +1,13 @@
1
1
  import type { Snippet } from "svelte";
2
- import type { CardVariant, SizeVariant } from "../../types/variants.js";
2
+ import type { CardVariant, SizeVariant } from "../types/variants.js";
3
3
  interface Props {
4
- title?: string;
5
- description?: string;
6
- image?: string;
7
4
  onclick?: (event: MouseEvent) => void | Promise<void>;
8
5
  size?: SizeVariant;
9
6
  variant?: CardVariant;
10
7
  fullWidth?: boolean;
8
+ className?: string;
9
+ /** Required when `onclick` is set (no visible label). */
10
+ ariaLabel?: string;
11
11
  children?: Snippet;
12
12
  }
13
13
  declare const Card: import("svelte").Component<Props, {}, "">;
@@ -3,7 +3,12 @@
3
3
 
4
4
  interface Props {
5
5
  title?: string;
6
+ subtitle?: string;
6
7
  description?: string;
8
+ /** `id` on the subtitle `<p>` (e.g. `aria-describedby` on the title). */
9
+ subtitleId?: string;
10
+ /** `id` on the description `<p>`. */
11
+ descriptionId?: string;
7
12
  level?: 1 | 2 | 3 | 4 | 5 | 6;
8
13
  className?: string;
9
14
  children?: Snippet;
@@ -11,7 +16,10 @@
11
16
 
12
17
  let {
13
18
  title = "",
19
+ subtitle = "",
14
20
  description = "",
21
+ subtitleId,
22
+ descriptionId,
15
23
  level = 3,
16
24
  className = "",
17
25
  children,
@@ -24,7 +32,7 @@
24
32
  });
25
33
  </script>
26
34
 
27
- <header class="flex flex-col space-y-1.5 pb-4{className}" {...restProps}>
35
+ <header class={`flex flex-col space-y-1.5 pb-4 ${className}`.trim()} {...restProps}>
28
36
  {#if title}
29
37
  <svelte:element this={headingTag} class={headingClasses()}>
30
38
  {title}
@@ -33,8 +41,13 @@
33
41
  {#if children}
34
42
  {@render children()}
35
43
  {/if}
44
+ {#if subtitle}
45
+ <p id={subtitleId} class="text-sm text-description">
46
+ {subtitle}
47
+ </p>
48
+ {/if}
36
49
  {#if description}
37
- <p class="text-sm text-description">
50
+ <p id={descriptionId} class="text-sm text-description">
38
51
  {description}
39
52
  </p>
40
53
  {/if}
@@ -1,7 +1,12 @@
1
1
  import type { Snippet } from "svelte";
2
2
  interface Props {
3
3
  title?: string;
4
+ subtitle?: string;
4
5
  description?: string;
6
+ /** `id` on the subtitle `<p>` (e.g. `aria-describedby` on the title). */
7
+ subtitleId?: string;
8
+ /** `id` on the description `<p>`. */
9
+ descriptionId?: string;
5
10
  level?: 1 | 2 | 3 | 4 | 5 | 6;
6
11
  className?: string;
7
12
  children?: Snippet;
@@ -1,95 +1,69 @@
1
1
  <script lang="ts">
2
- import { generateId } from "../../routes/lib/ssr-safe.js";
2
+ import SelectionControl from "./SelectionControl.svelte";
3
+ import type { SelectionControlMarkProps } from "./SelectionControl.svelte";
3
4
 
4
5
  interface Props {
5
- checked?: boolean;
6
+ id?: string;
6
7
  name?: string;
8
+ value?: string;
7
9
  disabled?: boolean;
10
+ loading?: boolean;
8
11
  label?: string;
12
+ defaultChecked?: boolean;
13
+ checked?: boolean;
14
+ onChange?: (event: Event) => void;
9
15
  onchange?: (event: Event) => void;
10
16
  }
11
17
 
12
18
  let {
13
- checked = false,
19
+ id,
20
+ value = "",
14
21
  name = "",
15
22
  disabled = false,
23
+ loading = false,
16
24
  label = "",
25
+ defaultChecked = false,
26
+ checked = $bindable(defaultChecked),
27
+ onChange,
17
28
  onchange,
18
29
  ...restProps
19
30
  }: Props = $props();
20
-
21
- const checkboxId = generateId("checkbox");
22
-
23
- const checkboxContainerClasses = $derived(() => {
24
- const baseClasses =
25
- "relative inline-flex items-center justify-center w-5 h-5 rounded transition-all duration-200";
26
-
27
- if (disabled) {
28
- return `${baseClasses} opacity-50 cursor-not-allowed`;
29
- }
30
- return `${baseClasses} cursor-pointer`;
31
- });
32
-
33
- const checkboxBoxClasses = $derived(() => {
34
- if (checked) {
35
- return disabled
36
- ? "border-0 opacity-50 bg-brand-500"
37
- : "border-0 bg-brand-500";
38
- }
39
- return disabled
40
- ? "border-2 border-base-400 bg-transparent"
41
- : "border-2 border-base-400 bg-transparent";
42
- });
43
-
44
- function handleChange(event: Event) {
45
- if (disabled) return;
46
- const target = event.target as HTMLInputElement;
47
- checked = target.checked;
48
- if (onchange) {
49
- onchange(event);
50
- }
51
- }
52
31
  </script>
53
32
 
54
- <div class="flex items-center gap-2">
55
- <label
56
- for={checkboxId}
57
- class="flex items-center gap-2 cursor-pointer {disabled
58
- ? 'opacity-50 cursor-not-allowed'
59
- : ''}"
33
+ {#snippet mark(props: SelectionControlMarkProps)}
34
+ <span
35
+ class="pointer-events-none absolute inline-block size-3 shrink-0 animate-spin rounded-full border-2 border-brand-500 border-t-transparent z-10 opacity-0 group-has-[aria-busy=true]/control:opacity-100 group-has-checked/control:border-base-50"
36
+ aria-hidden="true"
37
+ ></span>
38
+ <svg
39
+ class="absolute w-3 h-3 text-base-50 pointer-events-none z-10 opacity-0 group-has-checked/control:opacity-100"
40
+ fill="none"
41
+ stroke="currentColor"
42
+ viewBox="0 0 24 24"
43
+ aria-hidden="true"
60
44
  >
61
- <div class={checkboxContainerClasses()}>
62
- <input
63
- type="checkbox"
64
- id={checkboxId}
65
- {name}
66
- {checked}
67
- {disabled}
68
- class="sr-only"
69
- onchange={handleChange}
70
- {...restProps}
71
- />
72
- <div class="absolute inset-0 {checkboxBoxClasses()} rounded"></div>
73
- {#if checked}
74
- <svg
75
- class="absolute w-3 h-3 text-base-50 pointer-events-none z-10"
76
- fill="none"
77
- stroke="currentColor"
78
- viewBox="0 0 24 24"
79
- >
80
- <path
81
- stroke-linecap="round"
82
- stroke-linejoin="round"
83
- stroke-width="2.5"
84
- d="M5 13l4 4L19 7"
85
- />
86
- </svg>
87
- {/if}
88
- </div>
89
- {#if label}
90
- <span class="text-sm font-medium text-label">
91
- {label}
92
- </span>
93
- {/if}
94
- </label>
95
- </div>
45
+ <path
46
+ stroke-linecap="round"
47
+ stroke-linejoin="round"
48
+ stroke-width="2.5"
49
+ d="M5 13l4 4L19 7"
50
+ />
51
+ </svg>
52
+ {/snippet}
53
+
54
+ <SelectionControl
55
+ type="checkbox"
56
+ shape="square"
57
+ {id}
58
+ {name}
59
+ {value}
60
+ {label}
61
+ {disabled}
62
+ {loading}
63
+ bind:checked
64
+ {defaultChecked}
65
+ {onChange}
66
+ {onchange}
67
+ {mark}
68
+ {...restProps}
69
+ />
@@ -1,10 +1,15 @@
1
1
  interface Props {
2
- checked?: boolean;
2
+ id?: string;
3
3
  name?: string;
4
+ value?: string;
4
5
  disabled?: boolean;
6
+ loading?: boolean;
5
7
  label?: string;
8
+ defaultChecked?: boolean;
9
+ checked?: boolean;
10
+ onChange?: (event: Event) => void;
6
11
  onchange?: (event: Event) => void;
7
12
  }
8
- declare const Checkbox: import("svelte").Component<Props, {}, "">;
13
+ declare const Checkbox: import("svelte").Component<Props, {}, "checked">;
9
14
  type Checkbox = ReturnType<typeof Checkbox>;
10
15
  export default Checkbox;