zabi-components 6.0.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +137 -1473
  2. package/dist/atoms/ActionPanel.svelte +116 -0
  3. package/dist/atoms/ActionPanel.svelte.d.ts +19 -0
  4. package/dist/atoms/Badge.svelte +1 -1
  5. package/dist/atoms/Badge.svelte.d.ts +1 -1
  6. package/dist/atoms/Button.svelte +38 -21
  7. package/dist/atoms/Button.svelte.d.ts +6 -6
  8. package/dist/atoms/Card.svelte +12 -3
  9. package/dist/atoms/Card.svelte.d.ts +2 -2
  10. package/dist/atoms/CardHeader.svelte +8 -4
  11. package/dist/atoms/CardHeader.svelte.d.ts +4 -2
  12. package/dist/atoms/Checkbox.svelte +50 -76
  13. package/dist/atoms/Checkbox.svelte.d.ts +7 -2
  14. package/dist/atoms/CodeBlock.svelte +20 -34
  15. package/dist/atoms/CodeBlock.svelte.d.ts +1 -0
  16. package/dist/atoms/ColorPicker.svelte +2 -6
  17. package/dist/atoms/Container.svelte +41 -0
  18. package/dist/atoms/Container.svelte.d.ts +12 -0
  19. package/dist/atoms/Divider.svelte +68 -0
  20. package/dist/atoms/Divider.svelte.d.ts +13 -0
  21. package/dist/atoms/FeatureCard.svelte +35 -50
  22. package/dist/atoms/FeatureCard.svelte.d.ts +9 -4
  23. package/dist/atoms/IconButton.svelte +36 -29
  24. package/dist/atoms/IconButton.svelte.d.ts +7 -13
  25. package/dist/atoms/Input.svelte +58 -26
  26. package/dist/atoms/Input.svelte.d.ts +6 -1
  27. package/dist/atoms/List.svelte +2 -7
  28. package/dist/atoms/List.svelte.d.ts +1 -6
  29. package/dist/atoms/ListItem.svelte +54 -65
  30. package/dist/atoms/ListItem.svelte.d.ts +7 -13
  31. package/dist/atoms/ListItemLeading.svelte +19 -0
  32. package/dist/atoms/ListItemLeading.svelte.d.ts +9 -0
  33. package/dist/atoms/Progress.svelte +1 -1
  34. package/dist/atoms/Radio.svelte +51 -0
  35. package/dist/atoms/Radio.svelte.d.ts +14 -0
  36. package/dist/atoms/Select.svelte +23 -30
  37. package/dist/atoms/Select.svelte.d.ts +0 -10
  38. package/dist/atoms/SelectionControl.svelte +98 -0
  39. package/dist/atoms/SelectionControl.svelte.d.ts +26 -0
  40. package/dist/atoms/Skeleton.svelte +39 -15
  41. package/dist/atoms/Skeleton.svelte.d.ts +7 -3
  42. package/dist/atoms/Table.svelte +27 -0
  43. package/dist/atoms/Table.svelte.d.ts +10 -0
  44. package/dist/atoms/Text.svelte +46 -0
  45. package/dist/atoms/Text.svelte.d.ts +13 -0
  46. package/dist/atoms/Textarea.svelte +74 -53
  47. package/dist/atoms/Textarea.svelte.d.ts +6 -2
  48. package/dist/atoms/ThemeToggle.svelte +35 -24
  49. package/dist/atoms/ThemeToggle.svelte.d.ts +1 -0
  50. package/dist/atoms/Toast.svelte +55 -31
  51. package/dist/atoms/Toast.svelte.d.ts +4 -1
  52. package/dist/atoms/Toggle.svelte +37 -22
  53. package/dist/atoms/Toggle.svelte.d.ts +2 -1
  54. package/dist/atoms/Tooltip.svelte +97 -14
  55. package/dist/atoms/Tooltip.svelte.d.ts +6 -5
  56. package/dist/atoms/index.d.ts +6 -0
  57. package/dist/atoms/index.js +6 -0
  58. package/dist/atoms/selection-control.styles.d.ts +25 -0
  59. package/dist/atoms/selection-control.styles.js +30 -0
  60. package/dist/components/atoms/index.d.ts +6 -0
  61. package/dist/components/atoms/index.d.ts.map +1 -1
  62. package/dist/components/atoms/selection-control.styles.d.ts +26 -0
  63. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -0
  64. package/dist/components/index.d.ts +41 -5
  65. package/dist/components/index.d.ts.map +1 -1
  66. package/dist/components/molecules/index.d.ts +10 -0
  67. package/dist/components/molecules/index.d.ts.map +1 -1
  68. package/dist/components/molecules/navigation-menu-context.d.ts +11 -0
  69. package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -1
  70. package/dist/components/molecules/toast-store.d.ts +39 -0
  71. package/dist/components/molecules/toast-store.d.ts.map +1 -0
  72. package/dist/components/organisms/index.d.ts +2 -1
  73. package/dist/components/organisms/index.d.ts.map +1 -1
  74. package/dist/components/types/page.types.d.ts +69 -0
  75. package/dist/components/types/page.types.d.ts.map +1 -0
  76. package/dist/components/types/page.types.js +1 -0
  77. package/dist/components/types/variants.d.ts +64 -0
  78. package/dist/components/types/variants.d.ts.map +1 -0
  79. package/dist/components/types/variants.js +44 -0
  80. package/dist/components/util/fixed-sidebar-flyout.d.ts +23 -0
  81. package/dist/components/util/fixed-sidebar-flyout.d.ts.map +1 -0
  82. package/dist/components/util/focus-utils.d.ts +12 -0
  83. package/dist/components/util/focus-utils.d.ts.map +1 -0
  84. package/dist/components/util/ssr-safe.d.ts +17 -0
  85. package/dist/components/util/ssr-safe.d.ts.map +1 -0
  86. package/dist/index.d.ts +41 -5
  87. package/dist/index.js +44 -8
  88. package/dist/lib/layout-width-tokens.d.ts +17 -0
  89. package/dist/lib/layout-width-tokens.d.ts.map +1 -0
  90. package/dist/lib/showcase/component-docs/Button.d.ts +3 -0
  91. package/dist/lib/showcase/component-docs/Button.d.ts.map +1 -0
  92. package/dist/lib/showcase/component-docs/Input.d.ts +3 -0
  93. package/dist/lib/showcase/component-docs/Input.d.ts.map +1 -0
  94. package/dist/lib/showcase/component-docs/List.d.ts +3 -0
  95. package/dist/lib/showcase/component-docs/List.d.ts.map +1 -0
  96. package/dist/lib/showcase/component-docs/Modal.d.ts +3 -0
  97. package/dist/lib/showcase/component-docs/Modal.d.ts.map +1 -0
  98. package/dist/lib/showcase/component-docs/Radio.d.ts +3 -0
  99. package/dist/lib/showcase/component-docs/Radio.d.ts.map +1 -0
  100. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +3 -0
  101. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +1 -0
  102. package/dist/lib/showcase/component-docs/Skeleton.d.ts +3 -0
  103. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +1 -0
  104. package/dist/lib/showcase/component-docs/_shared.d.ts +12 -0
  105. package/dist/lib/showcase/component-docs/_shared.d.ts.map +1 -0
  106. package/dist/lib/showcase/component-docs/index.d.ts +4 -0
  107. package/dist/lib/showcase/component-docs/index.d.ts.map +1 -0
  108. package/dist/lib/showcase/components-catalog.d.ts.map +1 -1
  109. package/dist/lib/showcase/components-showcase-constants.d.ts +26 -0
  110. package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -1
  111. package/dist/molecules/Alert.svelte +50 -23
  112. package/dist/molecules/Alert.svelte.d.ts +1 -1
  113. package/dist/molecules/ComponentDemo.svelte +2 -2
  114. package/dist/molecules/ContactForm.svelte +1 -1
  115. package/dist/molecules/Dropdown.svelte +99 -109
  116. package/dist/molecules/Dropdown.svelte.d.ts +12 -6
  117. package/dist/molecules/EmptyState.svelte +44 -0
  118. package/dist/molecules/EmptyState.svelte.d.ts +11 -0
  119. package/dist/molecules/FormField.svelte +89 -0
  120. package/dist/molecules/FormField.svelte.d.ts +23 -0
  121. package/dist/molecules/Header.svelte +41 -0
  122. package/dist/molecules/Header.svelte.d.ts +10 -0
  123. package/dist/molecules/Modal.svelte +37 -24
  124. package/dist/molecules/Modal.svelte.d.ts +4 -2
  125. package/dist/molecules/NavigationMenu.svelte +16 -0
  126. package/dist/molecules/NavigationMenu.svelte.d.ts +2 -0
  127. package/dist/molecules/NavigationMenuContent.svelte +9 -1
  128. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  129. package/dist/molecules/NavigationMenuTrigger.svelte +9 -1
  130. package/dist/molecules/Page.svelte +16 -0
  131. package/dist/molecules/Page.svelte.d.ts +8 -0
  132. package/dist/molecules/PropsTable.svelte +61 -0
  133. package/dist/molecules/PropsTable.svelte.d.ts +8 -0
  134. package/dist/molecules/RadioGroup.svelte +197 -0
  135. package/dist/molecules/RadioGroup.svelte.d.ts +20 -0
  136. package/dist/molecules/Section.svelte +2 -11
  137. package/dist/molecules/Section.svelte.d.ts +2 -11
  138. package/dist/molecules/SidebarBrandHeader.svelte +68 -0
  139. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +10 -0
  140. package/dist/molecules/SidebarFooter.svelte +119 -0
  141. package/dist/molecules/SidebarFooter.svelte.d.ts +23 -0
  142. package/dist/molecules/SidebarNavSection.svelte +49 -0
  143. package/dist/molecules/SidebarNavSection.svelte.d.ts +14 -0
  144. package/dist/molecules/SlideUp.svelte +19 -14
  145. package/dist/molecules/SlideUp.svelte.d.ts +1 -1
  146. package/dist/molecules/Tabs.svelte +4 -9
  147. package/dist/molecules/Toaster.svelte +21 -0
  148. package/dist/molecules/Toaster.svelte.d.ts +6 -0
  149. package/dist/molecules/ToasterToast.svelte +217 -0
  150. package/dist/molecules/ToasterToast.svelte.d.ts +7 -0
  151. package/dist/molecules/index.d.ts +10 -0
  152. package/dist/molecules/index.js +10 -0
  153. package/dist/molecules/navigation-menu-context.d.ts +11 -0
  154. package/dist/molecules/navigation-menu-context.js +18 -0
  155. package/dist/molecules/toast-store.d.ts +38 -0
  156. package/dist/molecules/toast-store.js +34 -0
  157. package/dist/organisms/SidebarAccountPanel.svelte +118 -0
  158. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +20 -0
  159. package/dist/organisms/SidebarNavigation.svelte +267 -237
  160. package/dist/organisms/SidebarNavigation.svelte.d.ts +21 -5
  161. package/dist/organisms/SidebarPanel.svelte +54 -22
  162. package/dist/organisms/SidebarPanel.svelte.d.ts +3 -0
  163. package/dist/organisms/{Navbar.svelte → TopNavbar.svelte} +22 -17
  164. package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
  165. package/dist/organisms/index.d.ts +2 -1
  166. package/dist/organisms/index.js +2 -1
  167. package/dist/routes/lib/focus-utils.d.ts +7 -18
  168. package/dist/routes/lib/focus-utils.ts +32 -34
  169. package/dist/routes/lib/ssr-safe.ts +5 -4
  170. package/dist/routes/lib/variant-utils.ts +48 -39
  171. package/dist/types/components.d.ts +187 -0
  172. package/dist/types/components.d.ts.map +1 -0
  173. package/dist/types/events.d.ts +200 -0
  174. package/dist/types/events.d.ts.map +1 -0
  175. package/dist/types/index.d.ts +171 -0
  176. package/dist/types/index.d.ts.map +1 -0
  177. package/dist/types/page.types.d.ts +2 -31
  178. package/dist/types/page.types.d.ts.map +1 -1
  179. package/dist/types/page.types.js +1 -0
  180. package/dist/types/page.types.ts +79 -0
  181. package/dist/types/variants.d.ts +3 -0
  182. package/dist/types/variants.d.ts.map +1 -0
  183. package/dist/types/variants.js +1 -0
  184. package/dist/types/variants.ts +5 -6
  185. package/dist/util/fixed-sidebar-flyout.d.ts +22 -0
  186. package/dist/util/fixed-sidebar-flyout.js +148 -0
  187. package/dist/util/focus-utils.d.ts +11 -0
  188. package/dist/util/focus-utils.js +94 -0
  189. package/dist/util/ssr-safe.d.ts +16 -0
  190. package/dist/util/ssr-safe.js +48 -0
  191. package/dist/zabi-components-colors.css +128 -115
  192. package/dist/zabi-components-theme-dark-only.css +77 -72
  193. package/dist/zabi-components-theme-dark.css +77 -72
  194. package/dist/zabi-components-theme-only.css +51 -43
  195. package/dist/zabi-components-theme.css +51 -43
  196. package/dist/zabi-components.css +952 -249
  197. package/docs/lucide-icons.md +24 -0
  198. package/package.json +22 -9
  199. package/scripts/fix-lucide-svelte-icon-dts.js +35 -0
  200. package/dist/organisms/Navbar.svelte.d.ts +0 -41
@@ -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,31 +1,35 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
- import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
3
+ import type { HTMLButtonAttributes } from "svelte/elements";
4
+ import type { ButtonVariant, SizeVariant } from "../types/variants.js";
4
5
 
5
- interface Props {
6
+ type Props = Omit<HTMLButtonAttributes, "class"> & {
6
7
  variant?: ButtonVariant;
7
8
  size?: SizeVariant;
8
- disabled?: boolean;
9
- type?: "button" | "submit" | "reset";
9
+ loading?: boolean;
10
10
  text?: string;
11
11
  isFullWidth?: boolean;
12
- onclick?: (event: MouseEvent) => void;
12
+ class?: string;
13
13
  children?: Snippet;
14
- }
14
+ };
15
15
 
16
16
  let {
17
17
  variant = "primary",
18
18
  size = "md",
19
19
  disabled = false,
20
+ loading = false,
20
21
  type = "button",
21
22
  text = "",
22
23
  isFullWidth = false,
24
+ class: className = "",
23
25
  onclick,
24
26
  children,
25
27
  ...restProps
26
28
  }: Props = $props();
27
29
 
28
- const sizeClass = $derived(() => {
30
+ const isDisabled = $derived(disabled || loading);
31
+
32
+ const sizeClass = $derived.by(() => {
29
33
  if (size === "sm") {
30
34
  return {
31
35
  padding: "px-4 py-2.5",
@@ -59,33 +63,46 @@
59
63
  }
60
64
  });
61
65
 
62
- const variantClass = $derived(() => {
66
+ const variantClass = $derived.by(() => {
63
67
  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"
68
+ ? "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98]"
65
69
  : 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"
70
+ ? "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active active:scale-[0.98]"
67
71
  : 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"
72
+ ? "bg-action-danger text-inverse hover:bg-action-danger-hover active:bg-action-danger-active active:scale-[0.98] focus-ring--danger"
69
73
  : 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"
74
+ ? "bg-transparent text-headline hover:bg-base-100 active:bg-base-200 active:scale-[0.98] focus-ring--muted disabled:text-disabled"
75
+ : variant === "outline"
76
+ ? "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
77
  : 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";
78
+ ? "text-brand-700 active:text-brand-700 underline-offset-4 hover:underline disabled:text-disabled disabled:no-underline"
79
+ : "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active active:scale-[0.98]";
76
80
  });
77
81
 
78
- const buttonClasses = $derived(() => {
79
- const sizeStyles = sizeClass();
82
+ const buttonClasses = $derived.by(() => {
83
+ const sizeStyles = sizeClass;
80
84
  const flexClass = isFullWidth ? "flex" : "inline-flex";
81
85
  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`;
86
+ 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
87
 
84
- return `${baseClasses} ${widthClass} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.font} ${sizeStyles.leading} ${sizeStyles.tracking} ${sizeStyles.radius} ${sizeStyles.gap} ${variantClass()}`.trim();
88
+ return `${baseClasses} ${widthClass} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.font} ${sizeStyles.leading} ${sizeStyles.tracking} ${sizeStyles.radius} ${sizeStyles.gap} ${variantClass} ${className}`.trim();
85
89
  });
86
90
  </script>
87
91
 
88
- <button {type} class={buttonClasses()} {disabled} {onclick} {...restProps}>
92
+ <button
93
+ {type}
94
+ class={buttonClasses}
95
+ disabled={isDisabled}
96
+ aria-busy={loading ? "true" : undefined}
97
+ {onclick}
98
+ {...restProps}
99
+ >
100
+ {#if loading}
101
+ <span
102
+ class="inline-block size-4 shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-80"
103
+ aria-hidden="true"
104
+ ></span>
105
+ {/if}
89
106
  {#if text}
90
107
  {text}
91
108
  {:else if children}
@@ -1,15 +1,15 @@
1
1
  import type { Snippet } from "svelte";
2
- import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
3
- interface Props {
2
+ import type { HTMLButtonAttributes } from "svelte/elements";
3
+ import type { ButtonVariant, SizeVariant } from "../types/variants.js";
4
+ type Props = Omit<HTMLButtonAttributes, "class"> & {
4
5
  variant?: ButtonVariant;
5
6
  size?: SizeVariant;
6
- disabled?: boolean;
7
- type?: "button" | "submit" | "reset";
7
+ loading?: boolean;
8
8
  text?: string;
9
9
  isFullWidth?: boolean;
10
- onclick?: (event: MouseEvent) => void;
10
+ class?: string;
11
11
  children?: Snippet;
12
- }
12
+ };
13
13
  declare const Button: import("svelte").Component<Props, {}, "">;
14
14
  type Button = ReturnType<typeof Button>;
15
15
  export default Button;
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
- import type { CardVariant, SizeVariant } from "../../types/variants.js";
3
+ import type { CardVariant, SizeVariant } from "../types/variants.js";
4
4
 
5
5
  interface Props {
6
6
  onclick?: (event: MouseEvent) => void | Promise<void>;
@@ -8,7 +8,7 @@
8
8
  variant?: CardVariant;
9
9
  fullWidth?: boolean;
10
10
  className?: string;
11
- /** Required when card is interactive (has onclick) for accessibility */
11
+ /** Required when `onclick` is set (no visible label). */
12
12
  ariaLabel?: string;
13
13
  children?: Snippet;
14
14
  }
@@ -28,6 +28,14 @@
28
28
  size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6",
29
29
  );
30
30
 
31
+ const radiusClass = $derived(
32
+ size === "sm"
33
+ ? "rounded-xl"
34
+ : size === "lg"
35
+ ? "rounded-3xl"
36
+ : "rounded-2xl",
37
+ );
38
+
31
39
  const variantClasses = $derived(
32
40
  variant === "elevated"
33
41
  ? "bg-card shadow-lg"
@@ -40,7 +48,8 @@
40
48
 
41
49
  const cardClasses = $derived(
42
50
  [
43
- "rounded-lg transition-all duration-200",
51
+ radiusClass,
52
+ "transition-all duration-200",
44
53
  variantClasses,
45
54
  onclick
46
55
  ? variant === "elevated"
@@ -1,12 +1,12 @@
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
4
  onclick?: (event: MouseEvent) => void | Promise<void>;
5
5
  size?: SizeVariant;
6
6
  variant?: CardVariant;
7
7
  fullWidth?: boolean;
8
8
  className?: string;
9
- /** Required when card is interactive (has onclick) for accessibility */
9
+ /** Required when `onclick` is set (no visible label). */
10
10
  ariaLabel?: string;
11
11
  children?: Snippet;
12
12
  }
@@ -3,10 +3,12 @@
3
3
 
4
4
  interface Props {
5
5
  title?: string;
6
- /** Short secondary text displayed directly under the title */
7
6
  subtitle?: string;
8
- /** Longer supporting text displayed below the subtitle */
9
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;
10
12
  level?: 1 | 2 | 3 | 4 | 5 | 6;
11
13
  className?: string;
12
14
  children?: Snippet;
@@ -16,6 +18,8 @@
16
18
  title = "",
17
19
  subtitle = "",
18
20
  description = "",
21
+ subtitleId,
22
+ descriptionId,
19
23
  level = 3,
20
24
  className = "",
21
25
  children,
@@ -38,12 +42,12 @@
38
42
  {@render children()}
39
43
  {/if}
40
44
  {#if subtitle}
41
- <p class="text-sm text-description">
45
+ <p id={subtitleId} class="text-sm text-description">
42
46
  {subtitle}
43
47
  </p>
44
48
  {/if}
45
49
  {#if description}
46
- <p class="text-sm text-description">
50
+ <p id={descriptionId} class="text-sm text-description">
47
51
  {description}
48
52
  </p>
49
53
  {/if}
@@ -1,10 +1,12 @@
1
1
  import type { Snippet } from "svelte";
2
2
  interface Props {
3
3
  title?: string;
4
- /** Short secondary text displayed directly under the title */
5
4
  subtitle?: string;
6
- /** Longer supporting text displayed below the subtitle */
7
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;
8
10
  level?: 1 | 2 | 3 | 4 | 5 | 6;
9
11
  className?: string;
10
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;
@@ -1,9 +1,16 @@
1
1
  <script lang="ts">
2
+ import Check from "@lucide/svelte/icons/check";
3
+ import Copy from "@lucide/svelte/icons/copy";
4
+ import IconButton from "./IconButton.svelte";
5
+
2
6
  interface Props {
7
+ /** Raw source; escaped unless `trustHtml` (e.g. highlighter HTML). */
3
8
  code: string;
4
9
  language?: string;
5
10
  className?: string;
6
11
  showCopyButton?: boolean;
12
+ /** If true, `{@html code}` — only trusted, sanitized input. */
13
+ trustHtml?: boolean;
7
14
  }
8
15
 
9
16
  let {
@@ -11,6 +18,7 @@
11
18
  language = "svelte",
12
19
  className = "",
13
20
  showCopyButton = true,
21
+ trustHtml = false,
14
22
  ...restProps
15
23
  } = $props();
16
24
 
@@ -30,11 +38,11 @@
30
38
  </script>
31
39
 
32
40
  <div
33
- class="code-block relative bg-base-900 rounded-lg overflow-hidden {className}"
41
+ class="code-block relative bg-surface-1 border border-border rounded-lg overflow-hidden {className}"
34
42
  {...restProps}
35
43
  >
36
44
  <div
37
- class="flex items-center justify-between px-4 py-2 bg-base-800 border-b border-base-700"
45
+ class="flex items-center justify-between px-4 py-2 bg-surface-2 border-b border-border"
38
46
  >
39
47
  <div class="flex items-center gap-2">
40
48
  <div class="w-3 h-3 rounded-full bg-red-500"></div>
@@ -42,46 +50,24 @@
42
50
  <div class="w-3 h-3 rounded-full bg-green-500"></div>
43
51
  </div>
44
52
  {#if showCopyButton}
45
- <button
53
+ <IconButton
54
+ variant="ghost"
55
+ size="sm"
56
+ label={copied ? "Code copied to clipboard" : "Copy code to clipboard"}
46
57
  onclick={copyToClipboard}
47
- class="flex cursor-pointer items-center gap-2 rounded px-3 py-1 text-xs text-base-300 transition-colors duration-200 hover:bg-base-700 hover:text-white"
48
- aria-label="Copy code to clipboard"
49
58
  >
50
59
  {#if copied}
51
- <svg
52
- class="w-4 h-4"
53
- fill="currentColor"
54
- viewBox="0 0 20 20"
55
- >
56
- <path
57
- fill-rule="evenodd"
58
- d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
59
- clip-rule="evenodd"
60
- />
61
- </svg>
62
- Copied!
60
+ <Check size={16} class="shrink-0" aria-hidden="true" />
63
61
  {:else}
64
- <svg
65
- class="w-4 h-4"
66
- fill="none"
67
- stroke="currentColor"
68
- viewBox="0 0 24 24"
69
- >
70
- <path
71
- stroke-linecap="round"
72
- stroke-linejoin="round"
73
- stroke-width="2"
74
- d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
75
- />
76
- </svg>
77
- Copy
62
+ <Copy size={16} class="shrink-0" aria-hidden="true" />
78
63
  {/if}
79
- </button>
64
+ </IconButton>
80
65
  {/if}
81
66
  </div>
82
67
 
83
- <pre class="p-4 overflow-x-auto text-sm text-base-100 leading-relaxed"><code
84
- class="language-{language}">{@html code}</code
68
+ <pre class="p-4 overflow-x-auto text-sm text-base-900 leading-relaxed"><code
69
+ class="language-{language}"
70
+ >{#if trustHtml}{@html code}{:else}{code}{/if}</code
85
71
  ></pre>
86
72
  </div>
87
73
 
@@ -3,6 +3,7 @@ declare const CodeBlock: import("svelte").Component<{
3
3
  language?: string;
4
4
  className?: string;
5
5
  showCopyButton?: boolean;
6
+ trustHtml?: boolean;
6
7
  } & Record<string, any>, {}, "">;
7
8
  type CodeBlock = ReturnType<typeof CodeBlock>;
8
9
  export default CodeBlock;