zabi-components 5.0.22 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +139 -1478
  2. package/THEME.md +473 -0
  3. package/THEMING.md +248 -0
  4. package/dist/atoms/ActionPanel.svelte +116 -0
  5. package/dist/atoms/ActionPanel.svelte.d.ts +19 -0
  6. package/dist/atoms/Badge.svelte +1 -1
  7. package/dist/atoms/Badge.svelte.d.ts +1 -1
  8. package/dist/atoms/Button.svelte +35 -16
  9. package/dist/atoms/Button.svelte.d.ts +3 -1
  10. package/dist/atoms/Card.svelte +49 -67
  11. package/dist/atoms/Card.svelte.d.ts +4 -4
  12. package/dist/atoms/CardHeader.svelte +15 -2
  13. package/dist/atoms/CardHeader.svelte.d.ts +5 -0
  14. package/dist/atoms/Checkbox.svelte +50 -76
  15. package/dist/atoms/Checkbox.svelte.d.ts +7 -2
  16. package/dist/atoms/CodeBlock.svelte +20 -34
  17. package/dist/atoms/CodeBlock.svelte.d.ts +1 -0
  18. package/dist/atoms/ColorPicker.svelte +3 -7
  19. package/dist/atoms/Container.svelte +41 -0
  20. package/dist/atoms/Container.svelte.d.ts +12 -0
  21. package/dist/atoms/Divider.svelte +68 -0
  22. package/dist/atoms/Divider.svelte.d.ts +13 -0
  23. package/dist/atoms/FeatureCard.svelte +35 -50
  24. package/dist/atoms/FeatureCard.svelte.d.ts +9 -4
  25. package/dist/atoms/IconButton.svelte +33 -24
  26. package/dist/atoms/IconButton.svelte.d.ts +4 -8
  27. package/dist/atoms/Input.svelte +58 -26
  28. package/dist/atoms/Input.svelte.d.ts +6 -1
  29. package/dist/atoms/List.svelte +2 -7
  30. package/dist/atoms/List.svelte.d.ts +1 -6
  31. package/dist/atoms/ListItem.svelte +54 -65
  32. package/dist/atoms/ListItem.svelte.d.ts +7 -13
  33. package/dist/atoms/ListItemLeading.svelte +19 -0
  34. package/dist/atoms/ListItemLeading.svelte.d.ts +9 -0
  35. package/dist/atoms/Progress.svelte +2 -2
  36. package/dist/atoms/Radio.svelte +51 -0
  37. package/dist/atoms/Radio.svelte.d.ts +14 -0
  38. package/dist/atoms/Select.svelte +28 -33
  39. package/dist/atoms/Select.svelte.d.ts +0 -10
  40. package/dist/atoms/SelectionControl.svelte +98 -0
  41. package/dist/atoms/SelectionControl.svelte.d.ts +26 -0
  42. package/dist/atoms/Skeleton.svelte +39 -15
  43. package/dist/atoms/Skeleton.svelte.d.ts +7 -3
  44. package/dist/atoms/Table.svelte +27 -0
  45. package/dist/atoms/Table.svelte.d.ts +10 -0
  46. package/dist/atoms/Text.svelte +46 -0
  47. package/dist/atoms/Text.svelte.d.ts +13 -0
  48. package/dist/atoms/Textarea.svelte +74 -53
  49. package/dist/atoms/Textarea.svelte.d.ts +6 -2
  50. package/dist/atoms/ThemeToggle.svelte +35 -24
  51. package/dist/atoms/ThemeToggle.svelte.d.ts +1 -0
  52. package/dist/atoms/Toast.svelte +55 -31
  53. package/dist/atoms/Toast.svelte.d.ts +4 -1
  54. package/dist/atoms/Toggle.svelte +37 -22
  55. package/dist/atoms/Toggle.svelte.d.ts +2 -1
  56. package/dist/atoms/Tooltip.svelte +90 -11
  57. package/dist/atoms/index.d.ts +6 -0
  58. package/dist/atoms/index.js +6 -0
  59. package/dist/atoms/selection-control.styles.d.ts +25 -0
  60. package/dist/atoms/selection-control.styles.js +30 -0
  61. package/dist/components/atoms/index.d.ts +6 -0
  62. package/dist/components/atoms/index.d.ts.map +1 -1
  63. package/dist/components/atoms/selection-control.styles.d.ts +26 -0
  64. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -0
  65. package/dist/components/index.d.ts +42 -7
  66. package/dist/components/index.d.ts.map +1 -1
  67. package/dist/components/molecules/index.d.ts +10 -0
  68. package/dist/components/molecules/index.d.ts.map +1 -1
  69. package/dist/components/molecules/navigation-menu-context.d.ts +11 -0
  70. package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -1
  71. package/dist/components/molecules/toast-store.d.ts +39 -0
  72. package/dist/components/molecules/toast-store.d.ts.map +1 -0
  73. package/dist/components/organisms/index.d.ts +3 -3
  74. package/dist/components/organisms/index.d.ts.map +1 -1
  75. package/dist/components/types/page.types.d.ts +69 -0
  76. package/dist/components/types/page.types.d.ts.map +1 -0
  77. package/dist/components/types/page.types.js +1 -0
  78. package/dist/components/types/variants.d.ts +64 -0
  79. package/dist/components/types/variants.d.ts.map +1 -0
  80. package/dist/components/types/variants.js +44 -0
  81. package/dist/components/util/fixed-sidebar-flyout.d.ts +23 -0
  82. package/dist/components/util/fixed-sidebar-flyout.d.ts.map +1 -0
  83. package/dist/components/util/focus-utils.d.ts +12 -0
  84. package/dist/components/util/focus-utils.d.ts.map +1 -0
  85. package/dist/components/util/ssr-safe.d.ts +17 -0
  86. package/dist/components/util/ssr-safe.d.ts.map +1 -0
  87. package/dist/index.d.ts +42 -7
  88. package/dist/index.js +45 -10
  89. package/dist/lib/layout-width-tokens.d.ts +17 -0
  90. package/dist/lib/layout-width-tokens.d.ts.map +1 -0
  91. package/dist/lib/showcase/component-docs/Button.d.ts +3 -0
  92. package/dist/lib/showcase/component-docs/Button.d.ts.map +1 -0
  93. package/dist/lib/showcase/component-docs/Input.d.ts +3 -0
  94. package/dist/lib/showcase/component-docs/Input.d.ts.map +1 -0
  95. package/dist/lib/showcase/component-docs/List.d.ts +3 -0
  96. package/dist/lib/showcase/component-docs/List.d.ts.map +1 -0
  97. package/dist/lib/showcase/component-docs/Modal.d.ts +3 -0
  98. package/dist/lib/showcase/component-docs/Modal.d.ts.map +1 -0
  99. package/dist/lib/showcase/component-docs/Radio.d.ts +3 -0
  100. package/dist/lib/showcase/component-docs/Radio.d.ts.map +1 -0
  101. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +3 -0
  102. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +1 -0
  103. package/dist/lib/showcase/component-docs/Skeleton.d.ts +3 -0
  104. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +1 -0
  105. package/dist/lib/showcase/component-docs/_shared.d.ts +12 -0
  106. package/dist/lib/showcase/component-docs/_shared.d.ts.map +1 -0
  107. package/dist/lib/showcase/component-docs/index.d.ts +4 -0
  108. package/dist/lib/showcase/component-docs/index.d.ts.map +1 -0
  109. package/dist/lib/showcase/components-catalog.d.ts +3 -0
  110. package/dist/lib/showcase/components-catalog.d.ts.map +1 -0
  111. package/dist/lib/showcase/components-showcase-constants.d.ts +89 -0
  112. package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -0
  113. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +15 -0
  114. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +1 -0
  115. package/dist/molecules/Alert.svelte +50 -23
  116. package/dist/molecules/Alert.svelte.d.ts +1 -1
  117. package/dist/molecules/ComponentDemo.svelte +54 -51
  118. package/dist/molecules/ContactForm.svelte +70 -65
  119. package/dist/molecules/Dropdown.svelte +99 -108
  120. package/dist/molecules/Dropdown.svelte.d.ts +12 -6
  121. package/dist/molecules/EmptyState.svelte +44 -0
  122. package/dist/molecules/EmptyState.svelte.d.ts +11 -0
  123. package/dist/molecules/FormField.svelte +89 -0
  124. package/dist/molecules/FormField.svelte.d.ts +23 -0
  125. package/dist/molecules/Header.svelte +41 -0
  126. package/dist/molecules/Header.svelte.d.ts +10 -0
  127. package/dist/molecules/ImageUpload.svelte +1 -1
  128. package/dist/molecules/Modal.svelte +39 -26
  129. package/dist/molecules/Modal.svelte.d.ts +4 -2
  130. package/dist/molecules/NavigationMenu.svelte +16 -0
  131. package/dist/molecules/NavigationMenu.svelte.d.ts +2 -0
  132. package/dist/molecules/NavigationMenuContent.svelte +9 -1
  133. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  134. package/dist/molecules/NavigationMenuTrigger.svelte +9 -4
  135. package/dist/molecules/Page.svelte +16 -0
  136. package/dist/molecules/Page.svelte.d.ts +8 -0
  137. package/dist/molecules/PropsTable.svelte +61 -0
  138. package/dist/molecules/PropsTable.svelte.d.ts +8 -0
  139. package/dist/molecules/RadioGroup.svelte +197 -0
  140. package/dist/molecules/RadioGroup.svelte.d.ts +20 -0
  141. package/dist/molecules/Section.svelte +14 -23
  142. package/dist/molecules/Section.svelte.d.ts +2 -11
  143. package/dist/molecules/SidebarBrandHeader.svelte +68 -0
  144. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +10 -0
  145. package/dist/molecules/SidebarFooter.svelte +119 -0
  146. package/dist/molecules/SidebarFooter.svelte.d.ts +23 -0
  147. package/dist/molecules/SidebarNavSection.svelte +49 -0
  148. package/dist/molecules/SidebarNavSection.svelte.d.ts +14 -0
  149. package/dist/molecules/SlideUp.svelte +21 -16
  150. package/dist/molecules/SlideUp.svelte.d.ts +1 -1
  151. package/dist/molecules/Tabs.svelte +6 -11
  152. package/dist/molecules/Toaster.svelte +21 -0
  153. package/dist/molecules/Toaster.svelte.d.ts +6 -0
  154. package/dist/molecules/ToasterToast.svelte +217 -0
  155. package/dist/molecules/ToasterToast.svelte.d.ts +7 -0
  156. package/dist/molecules/index.d.ts +10 -0
  157. package/dist/molecules/index.js +10 -0
  158. package/dist/molecules/navigation-menu-context.d.ts +11 -0
  159. package/dist/molecules/navigation-menu-context.js +18 -0
  160. package/dist/molecules/toast-store.d.ts +38 -0
  161. package/dist/molecules/toast-store.js +34 -0
  162. package/dist/organisms/SidebarAccountPanel.svelte +118 -0
  163. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +20 -0
  164. package/dist/organisms/SidebarNavigation.svelte +286 -212
  165. package/dist/organisms/SidebarNavigation.svelte.d.ts +22 -1
  166. package/dist/organisms/{SidebarProjectPanel.svelte → SidebarPanel.svelte} +67 -34
  167. package/dist/organisms/{SidebarProjectPanel.svelte.d.ts → SidebarPanel.svelte.d.ts} +9 -6
  168. package/dist/organisms/TopNavbar.svelte +214 -0
  169. package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
  170. package/dist/organisms/index.d.ts +3 -3
  171. package/dist/organisms/index.js +3 -3
  172. package/dist/routes/lib/focus-utils.d.ts +7 -18
  173. package/dist/routes/lib/focus-utils.ts +32 -34
  174. package/dist/routes/lib/ssr-safe.ts +5 -4
  175. package/dist/routes/lib/variant-utils.ts +48 -39
  176. package/dist/types/components.d.ts +187 -0
  177. package/dist/types/components.d.ts.map +1 -0
  178. package/dist/types/events.d.ts +200 -0
  179. package/dist/types/events.d.ts.map +1 -0
  180. package/dist/types/index.d.ts +171 -0
  181. package/dist/types/index.d.ts.map +1 -0
  182. package/dist/types/page.types.d.ts +3 -0
  183. package/dist/types/page.types.d.ts.map +1 -0
  184. package/dist/types/page.types.js +1 -0
  185. package/dist/types/page.types.ts +79 -0
  186. package/dist/types/variants.d.ts +3 -0
  187. package/dist/types/variants.d.ts.map +1 -0
  188. package/dist/types/variants.js +1 -0
  189. package/dist/types/variants.ts +75 -0
  190. package/dist/util/fixed-sidebar-flyout.d.ts +22 -0
  191. package/dist/util/fixed-sidebar-flyout.js +148 -0
  192. package/dist/util/focus-utils.d.ts +11 -0
  193. package/dist/util/focus-utils.js +94 -0
  194. package/dist/util/ssr-safe.d.ts +16 -0
  195. package/dist/util/ssr-safe.js +48 -0
  196. package/dist/zabi-components-colors.css +360 -324
  197. package/dist/zabi-components-theme-dark-only.css +164 -151
  198. package/dist/zabi-components-theme-dark.css +164 -151
  199. package/dist/zabi-components-theme-only.css +253 -230
  200. package/dist/zabi-components-theme.css +253 -230
  201. package/dist/zabi-components.css +1262 -241
  202. package/docs/theme-imports.md +112 -0
  203. package/package.json +32 -10
  204. package/dist/organisms/Navbar.svelte +0 -92
  205. package/dist/organisms/Navbar.svelte.d.ts +0 -14
  206. package/dist/organisms/Navigation.svelte +0 -119
  207. package/dist/organisms/Navigation.svelte.d.ts +0 -21
@@ -1,28 +1,19 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
- import type { SizeVariant } from "../../types/variants.js";
3
+ import type { SizeVariant } from "../types/variants.js";
4
4
  import Heading from "../atoms/Heading.svelte";
5
5
 
6
6
  interface Props {
7
- /** Section title */
8
7
  title?: string;
9
- /** Section description/subtitle */
10
8
  description?: string;
11
- /** Heading level for title (1-6) */
9
+ /** Title heading level (1–6). */
12
10
  headingLevel?: number;
13
- /** Section size variant */
14
11
  size?: SizeVariant;
15
- /** Maximum width of the section container */
16
12
  maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "none";
17
- /** Background variant */
18
13
  background?: "default" | "muted" | "accent" | "transparent";
19
- /** Padding variant */
20
14
  padding?: "none" | "sm" | "md" | "lg" | "xl";
21
- /** Whether to center the content */
22
15
  centered?: boolean;
23
- /** Additional CSS classes */
24
16
  className?: string;
25
- /** Section content */
26
17
  children?: Snippet;
27
18
  }
28
19
 
@@ -40,7 +31,7 @@
40
31
  ...restProps
41
32
  }: Props = $props();
42
33
 
43
- const sizeClasses = $derived(() => {
34
+ const sizeClasses = $derived.by(() => {
44
35
  switch (size) {
45
36
  case "sm":
46
37
  return "text-sm";
@@ -51,7 +42,7 @@
51
42
  }
52
43
  });
53
44
 
54
- const maxWidthClasses = $derived(() => {
45
+ const maxWidthClasses = $derived.by(() => {
55
46
  switch (maxWidth) {
56
47
  case "sm":
57
48
  return "max-w-screen-sm";
@@ -72,7 +63,7 @@
72
63
  }
73
64
  });
74
65
 
75
- const backgroundClasses = $derived(() => {
66
+ const backgroundClasses = $derived.by(() => {
76
67
  switch (background) {
77
68
  case "muted":
78
69
  return "bg-base-50";
@@ -85,7 +76,7 @@
85
76
  }
86
77
  });
87
78
 
88
- const paddingClasses = $derived(() => {
79
+ const paddingClasses = $derived.by(() => {
89
80
  switch (padding) {
90
81
  case "none":
91
82
  return "";
@@ -102,14 +93,14 @@
102
93
  }
103
94
  });
104
95
 
105
- const containerClasses = $derived(() => {
96
+ const containerClasses = $derived.by(() => {
106
97
  const base = "w-full";
107
- const maxWidthClass = maxWidthClasses();
98
+ const maxWidthClass = maxWidthClasses;
108
99
  const centeredClass = centered ? "mx-auto" : "";
109
100
  return `${base} ${maxWidthClass} ${centeredClass}`.trim();
110
101
  });
111
102
 
112
- const contentClasses = $derived(() => {
103
+ const contentClasses = $derived.by(() => {
113
104
  const base = "px-4 sm:px-6 lg:px-8";
114
105
  const centeredClass = centered ? "text-center" : "";
115
106
  return `${base} ${centeredClass}`.trim();
@@ -117,18 +108,18 @@
117
108
  </script>
118
109
 
119
110
  <section
120
- class="section {backgroundClasses()} {paddingClasses()} {className}"
111
+ class="section {backgroundClasses} {paddingClasses} {className}"
121
112
  {...restProps}
122
113
  >
123
- <div class={containerClasses()}>
124
- <div class={contentClasses()}>
114
+ <div class={containerClasses}>
115
+ <div class={contentClasses}>
125
116
  {#if title}
126
117
  <div class="mb-6 {centered ? 'mx-auto' : ''}">
127
118
  <Heading level={headingLevel} text={title} />
128
119
  {#if description}
129
120
  <p
130
- class="mt-4 {sizeClasses()} text-description {centered
131
- ? 'mx-auto max-w-2xl'
121
+ class="mt-4 {sizeClasses} text-description {centered
122
+ ? 'mx-auto'
132
123
  : ''}"
133
124
  >
134
125
  {description}
@@ -1,25 +1,16 @@
1
1
  import type { Snippet } from "svelte";
2
- import type { SizeVariant } from "../../types/variants.js";
2
+ import type { SizeVariant } from "../types/variants.js";
3
3
  interface Props {
4
- /** Section title */
5
4
  title?: string;
6
- /** Section description/subtitle */
7
5
  description?: string;
8
- /** Heading level for title (1-6) */
6
+ /** Title heading level (1–6). */
9
7
  headingLevel?: number;
10
- /** Section size variant */
11
8
  size?: SizeVariant;
12
- /** Maximum width of the section container */
13
9
  maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full" | "none";
14
- /** Background variant */
15
10
  background?: "default" | "muted" | "accent" | "transparent";
16
- /** Padding variant */
17
11
  padding?: "none" | "sm" | "md" | "lg" | "xl";
18
- /** Whether to center the content */
19
12
  centered?: boolean;
20
- /** Additional CSS classes */
21
13
  className?: string;
22
- /** Section content */
23
14
  children?: Snippet;
24
15
  }
25
16
  declare const Section: import("svelte").Component<Props, {}, "">;
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ collapsed?: boolean;
4
+ brandName?: string;
5
+ logoSrc?: string;
6
+ logoAlt?: string;
7
+ className?: string;
8
+ }
9
+
10
+ let {
11
+ collapsed = false,
12
+ brandName = "",
13
+ logoSrc = "",
14
+ logoAlt = "",
15
+ className = "",
16
+ }: Props = $props();
17
+
18
+ const trimmedName = $derived(brandName.trim());
19
+ const monogram = $derived(
20
+ trimmedName
21
+ ? trimmedName
22
+ .split(/\s+/)
23
+ .map((w) => w[0])
24
+ .join("")
25
+ .slice(0, 2)
26
+ .toUpperCase()
27
+ : "",
28
+ );
29
+ const showLogo = $derived(Boolean(logoSrc));
30
+ const showWordmark = $derived(Boolean(trimmedName) && !collapsed);
31
+ const showMonogram = $derived(
32
+ collapsed && !showLogo && Boolean(monogram),
33
+ );
34
+ </script>
35
+
36
+ {#if showLogo || trimmedName}
37
+ <div
38
+ class={`flex w-full min-w-0 items-center gap-2.5 ${className}`.trim()}
39
+ >
40
+ {#if showLogo}
41
+ <span
42
+ class="flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-background ring-1 ring-border"
43
+ >
44
+ <img
45
+ src={logoSrc}
46
+ alt={logoAlt || trimmedName || "Brand"}
47
+ class="size-full object-cover"
48
+ width="36"
49
+ height="36"
50
+ />
51
+ </span>
52
+ {:else if showMonogram}
53
+ <span
54
+ class="flex size-9 shrink-0 items-center justify-center rounded-xl bg-action-primary-subtle text-xs font-bold text-headline ring-1 ring-border"
55
+ aria-hidden="true"
56
+ >
57
+ {monogram}
58
+ </span>
59
+ {/if}
60
+ {#if showWordmark}
61
+ <span
62
+ class="truncate text-base font-semibold tracking-tight text-headline"
63
+ >
64
+ {trimmedName}
65
+ </span>
66
+ {/if}
67
+ </div>
68
+ {/if}
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ collapsed?: boolean;
3
+ brandName?: string;
4
+ logoSrc?: string;
5
+ logoAlt?: string;
6
+ className?: string;
7
+ }
8
+ declare const SidebarBrandHeader: import("svelte").Component<Props, {}, "">;
9
+ type SidebarBrandHeader = ReturnType<typeof SidebarBrandHeader>;
10
+ export default SidebarBrandHeader;
@@ -0,0 +1,119 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { fixedSidebarFlyout } from "../util/fixed-sidebar-flyout.js";
4
+
5
+ interface Props {
6
+ collapsed?: boolean;
7
+ showProfile?: boolean;
8
+ profileName?: string;
9
+ profileEmail?: string;
10
+ profileInitials?: string;
11
+ showLogout?: boolean;
12
+ logoutLabel?: string;
13
+ showThemeToggle?: boolean;
14
+ lightModeLabel?: string;
15
+ isLightMode?: boolean;
16
+ onLogout?: () => void;
17
+ onThemeToggle?: (nextIsLightMode: boolean) => void;
18
+ className?: string;
19
+ profilePanelOpen?: boolean;
20
+ profilePanelControlsId?: string;
21
+ onProfileClick?: (event?: MouseEvent) => void;
22
+ profilePanel?: Snippet;
23
+ }
24
+
25
+ let {
26
+ collapsed = false,
27
+ showProfile = true,
28
+ profileName = "Zabi",
29
+ profileEmail = "hello@zabi.dev",
30
+ profileInitials = "ZA",
31
+ showLogout = true,
32
+ logoutLabel = "Logout",
33
+ showThemeToggle = true,
34
+ lightModeLabel = "Light mode",
35
+ isLightMode = $bindable(false),
36
+ onLogout,
37
+ onThemeToggle,
38
+ className = "",
39
+ profilePanelOpen = false,
40
+ profilePanelControlsId = "",
41
+ onProfileClick,
42
+ profilePanel,
43
+ }: Props = $props();
44
+
45
+ const showFooter = $derived(showProfile || showLogout || showThemeToggle);
46
+ const showPanelLauncher = $derived(showProfile);
47
+
48
+ const avatarClasses =
49
+ "size-10 rounded-xl bg-action-primary text-action-primary flex items-center justify-center text-sm font-semibold shrink-0 ring-1 ring-border-focus";
50
+
51
+ function getTextToneClass(isMuted = false): string {
52
+ return isMuted ? "text-description" : "text-headline";
53
+ }
54
+
55
+ function handleProfileClick(event: MouseEvent): void {
56
+ onProfileClick?.(event);
57
+ }
58
+ </script>
59
+
60
+ {#if showFooter}
61
+ <div class="w-full shrink-0" data-sidebar-flyout-root>
62
+ <footer
63
+ class={`flex w-full shrink-0 flex-col gap-2.5 border-t border-border pt-3 pb-1 ${className}`.trim()}
64
+ aria-label="Account and settings"
65
+ >
66
+ {#if showPanelLauncher}
67
+ <button
68
+ type="button"
69
+ data-sidebar-flyout-anchor="profile"
70
+ class="w-full cursor-pointer rounded-xl px-2 py-2 outline-none transition-colors hover:bg-nav-menu-hover hover:text-nav-menu-item-hover hover:ring-1 hover:ring-border/60 active:bg-base-200 focus-ring focus-ring--nav"
71
+ aria-haspopup="dialog"
72
+ aria-expanded={profilePanelOpen}
73
+ aria-controls={profilePanelControlsId.trim()
74
+ ? profilePanelControlsId.trim()
75
+ : undefined}
76
+ aria-label={collapsed
77
+ ? `Open account panel`
78
+ : `Open account panel for ${profileName}`}
79
+ onclick={handleProfileClick}
80
+ >
81
+ <span class="flex w-full items-center gap-3 text-left">
82
+ <span class={avatarClasses} aria-hidden="true">
83
+ {profileInitials}
84
+ </span>
85
+ {#if !collapsed}
86
+ <span class="min-w-0">
87
+ <span
88
+ class="block truncate text-sm font-semibold {getTextToneClass()}"
89
+ >
90
+ {profileName}
91
+ </span>
92
+ <span
93
+ class="block truncate text-xs {getTextToneClass(true)}"
94
+ >
95
+ {profileEmail}
96
+ </span>
97
+ </span>
98
+ {/if}
99
+ </span>
100
+ </button>
101
+ {/if}
102
+ </footer>
103
+
104
+ {#if showPanelLauncher && profilePanelOpen && profilePanel}
105
+ <div
106
+ class="max-h-[min(32rem,calc(100dvh-6rem))] overflow-y-auto overscroll-contain outline-none"
107
+ role="presentation"
108
+ use:fixedSidebarFlyout={{
109
+ open: profilePanelOpen,
110
+ align: "bottom",
111
+ gap: 12,
112
+ anchorRole: "profile",
113
+ }}
114
+ >
115
+ {@render profilePanel()}
116
+ </div>
117
+ {/if}
118
+ </div>
119
+ {/if}
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ collapsed?: boolean;
4
+ showProfile?: boolean;
5
+ profileName?: string;
6
+ profileEmail?: string;
7
+ profileInitials?: string;
8
+ showLogout?: boolean;
9
+ logoutLabel?: string;
10
+ showThemeToggle?: boolean;
11
+ lightModeLabel?: string;
12
+ isLightMode?: boolean;
13
+ onLogout?: () => void;
14
+ onThemeToggle?: (nextIsLightMode: boolean) => void;
15
+ className?: string;
16
+ profilePanelOpen?: boolean;
17
+ profilePanelControlsId?: string;
18
+ onProfileClick?: (event?: MouseEvent) => void;
19
+ profilePanel?: Snippet;
20
+ }
21
+ declare const SidebarFooter: import("svelte").Component<Props, {}, "isLightMode">;
22
+ type SidebarFooter = ReturnType<typeof SidebarFooter>;
23
+ export default SidebarFooter;
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ interface Props {
5
+ title?: string;
6
+ /** Disambiguates generated ids when section titles repeat. */
7
+ sectionKey?: string;
8
+ /** `aria-label` on the `<ul>` (required when title is hidden in collapsed mode). */
9
+ listAriaLabel: string;
10
+ collapsed?: boolean;
11
+ className?: string;
12
+ children: Snippet;
13
+ }
14
+
15
+ let {
16
+ title = "",
17
+ sectionKey = "",
18
+ listAriaLabel,
19
+ collapsed = false,
20
+ className = "",
21
+ children,
22
+ }: Props = $props();
23
+
24
+ const trimmedTitle = $derived(title.trim());
25
+ const headingId = $derived(
26
+ trimmedTitle
27
+ ? `sidebar-section-${sectionKey || "group"}`
28
+ : undefined,
29
+ );
30
+ const showHeading = $derived(Boolean(trimmedTitle) && !collapsed);
31
+ </script>
32
+
33
+ <div class={`flex w-full flex-col gap-1.5 ${className}`.trim()}>
34
+ {#if showHeading && headingId}
35
+ <h2
36
+ class="px-0 text-[10px] font-semibold uppercase tracking-[0.12em] text-description"
37
+ id={headingId}
38
+ >
39
+ {trimmedTitle}
40
+ </h2>
41
+ {/if}
42
+ <ul
43
+ class="flex w-full flex-col gap-0.5 px-0"
44
+ aria-labelledby={showHeading && headingId ? headingId : undefined}
45
+ aria-label={showHeading ? undefined : listAriaLabel}
46
+ >
47
+ {@render children()}
48
+ </ul>
49
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ title?: string;
4
+ /** Disambiguates generated ids when section titles repeat. */
5
+ sectionKey?: string;
6
+ /** `aria-label` on the `<ul>` (required when title is hidden in collapsed mode). */
7
+ listAriaLabel: string;
8
+ collapsed?: boolean;
9
+ className?: string;
10
+ children: Snippet;
11
+ }
12
+ declare const SidebarNavSection: import("svelte").Component<Props, {}, "">;
13
+ type SidebarNavSection = ReturnType<typeof SidebarNavSection>;
14
+ export default SidebarNavSection;
@@ -4,7 +4,8 @@
4
4
  returnFocus,
5
5
  saveFocus,
6
6
  trapFocus,
7
- } from "../../routes/lib/focus-utils.js";
7
+ } from '../util/focus-utils.js';
8
+ import { generateId } from "../util/ssr-safe.js";
8
9
 
9
10
  interface Props {
10
11
  isOpen?: boolean;
@@ -14,12 +15,16 @@
14
15
  }
15
16
 
16
17
  let {
17
- isOpen = false,
18
- title = "",
18
+ isOpen = $bindable(false),
19
+ title = '',
20
+ onclick,
21
+ onkeydown,
19
22
  children,
20
23
  ...restProps
21
24
  } = $props<Props & { children?: any }>();
22
25
 
26
+ const slideTitleId = generateId('slideup-title');
27
+
23
28
  let slideUpContainer = $state<HTMLDivElement>();
24
29
  let cleanupFocusTrap: (() => void) | null = null;
25
30
 
@@ -31,7 +36,7 @@
31
36
  }
32
37
  returnFocus();
33
38
  if (onclick && event) {
34
- (onclick as (event: Event) => void)(event);
39
+ onclick(event);
35
40
  }
36
41
  }
37
42
 
@@ -46,6 +51,7 @@
46
51
  } else if (!isOpen && cleanupFocusTrap) {
47
52
  cleanupFocusTrap();
48
53
  cleanupFocusTrap = null;
54
+ returnFocus();
49
55
  }
50
56
  });
51
57
 
@@ -57,41 +63,40 @@
57
63
 
58
64
  function handleKeydown(event: Event) {
59
65
  const keyboardEvent = event as KeyboardEvent;
60
- if (keyboardEvent.key === "Escape") {
66
+ if (keyboardEvent.key === 'Escape') {
61
67
  closeSlideUp(event);
62
68
  }
63
- if (onkeydown) {
64
- (onkeydown as (event: Event) => void)(event);
65
- }
69
+ onkeydown?.(event);
66
70
  }
67
71
  </script>
68
72
 
69
73
  {#if isOpen}
70
74
  <div
71
- class="fixed inset-0 bg-overlay z-modal"
75
+ class="fixed inset-0 z-modal cursor-pointer bg-overlay"
72
76
  onclick={handleBackdropClick}
73
77
  onkeydown={handleKeydown}
74
78
  role="dialog"
75
79
  aria-modal="true"
76
- aria-labelledby={title ? "slideup-title" : undefined}
80
+ aria-labelledby={title ? slideTitleId : undefined}
77
81
  tabindex="-1"
82
+ {...restProps}
78
83
  >
79
84
  <div
80
85
  bind:this={slideUpContainer}
81
- class="fixed bottom-0 left-0 right-0 bg-card rounded-t-3xl shadow-xl z-modal max-h-[90vh] overflow-y-auto animate-[slideUp_0.3s_ease-out] flex flex-col"
86
+ class="fixed bottom-0 left-0 right-0 z-modal flex max-h-[90vh] cursor-default flex-col overflow-y-auto rounded-t-3xl bg-card shadow-xl animate-[slideUp_0.3s_ease-out]"
82
87
  >
83
88
  {#if title}
84
- <div class="flex items-center justify-between px-6 pt-6 pb-4">
89
+ <div class="flex items-center justify-between px-6 pb-4 pt-6">
85
90
  <h2
86
- id="slideup-title"
87
- class="text-2xl font-normal leading-8 text-headline tracking-normal"
91
+ id={slideTitleId}
92
+ class="text-2xl font-normal leading-8 tracking-normal text-headline"
88
93
  >
89
94
  {title}
90
95
  </h2>
91
96
  <button
92
97
  type="button"
93
98
  onclick={closeSlideUp}
94
- class="text-description hover:text-headline text-2xl cursor-pointer transition-colors w-8 h-8 flex items-center justify-center rounded-full hover:bg-base-100"
99
+ class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full text-2xl text-description transition-colors hover:bg-base-100 hover:text-headline"
95
100
  aria-label="Close"
96
101
  >
97
102
  ×
@@ -99,7 +104,7 @@
99
104
  </div>
100
105
  {/if}
101
106
 
102
- <div class="px-6 pb-6 flex-1">
107
+ <div class="flex-1 px-6 pb-6">
103
108
  {@render children?.()}
104
109
  </div>
105
110
  </div>
@@ -7,6 +7,6 @@ interface Props {
7
7
  type $$ComponentProps = Props & {
8
8
  children?: any;
9
9
  };
10
- declare const SlideUp: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ declare const SlideUp: import("svelte").Component<$$ComponentProps, {}, "isOpen">;
11
11
  type SlideUp = ReturnType<typeof SlideUp>;
12
12
  export default SlideUp;
@@ -1,10 +1,5 @@
1
1
  <script lang="ts">
2
- function createId(prefix: string): string {
3
- if (typeof window !== "undefined") {
4
- return `${prefix}-${Math.random().toString(36).slice(2, 11)}`;
5
- }
6
- return `${prefix}-ssr-${Date.now()}`;
7
- }
2
+ import { generateId } from "../util/ssr-safe.js";
8
3
 
9
4
  interface Props {
10
5
  tabs?: Array<{
@@ -26,7 +21,7 @@
26
21
  ...restProps
27
22
  }: Props & { children?: any } = $props();
28
23
 
29
- const tabsBaseId = createId("tabs");
24
+ const tabsBaseId = generateId("tabs");
30
25
 
31
26
  function selectTab(tabId: string) {
32
27
  activeTab = tabId;
@@ -100,17 +95,17 @@
100
95
  tabindex="0"
101
96
  onkeydown={handleKeydown}
102
97
  >
103
- {#each tabs as tab}
98
+ {#each tabs as tab (tab.id)}
104
99
  <button
105
100
  type="button"
106
101
  role="tab"
107
102
  id={getTabId(tab.id)}
108
- class="px-4 py-2 text-sm font-medium border-b-2 transition-colors {activeTab ===
103
+ class="focus-ring cursor-pointer border-b-2 px-4 py-2 text-sm font-medium transition-colors focus:outline-none focus-visible:outline-none active:bg-base-100 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-transparent disabled:hover:text-description {activeTab ===
109
104
  tab.id
110
105
  ? variant === 'pills'
111
- ? 'bg-brand-100 text-brand-700 border-brand-500'
106
+ ? 'border-brand-500 bg-brand-100 text-brand-700'
112
107
  : 'border-brand-500 text-body'
113
- : 'border-transparent text-description hover:text-body hover:border-base-300'}"
108
+ : 'border-transparent text-description hover:border-base-300 hover:text-body'}"
114
109
  onclick={() => selectTab(tab.id)}
115
110
  disabled={tab.disabled}
116
111
  aria-selected={activeTab === tab.id}
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import ToasterToast from './ToasterToast.svelte';
3
+ import { toastStore } from './toast-store.js';
4
+
5
+ interface Props {
6
+ class?: string;
7
+ }
8
+
9
+ let { class: className = '' }: Props = $props();
10
+ </script>
11
+
12
+ <div
13
+ class="pointer-events-none fixed bottom-4 right-4 z-toast flex min-w-0 w-[min(24rem,calc(100vw-2rem))] flex-col gap-2 p-4 {className}"
14
+ role="region"
15
+ aria-label="Notifications"
16
+ aria-live="polite"
17
+ >
18
+ {#each $toastStore as toast (toast.id)}
19
+ <ToasterToast {toast} />
20
+ {/each}
21
+ </div>
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ class?: string;
3
+ }
4
+ declare const Toaster: import("svelte").Component<Props, {}, "">;
5
+ type Toaster = ReturnType<typeof Toaster>;
6
+ export default Toaster;