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,217 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { cubicOut } from 'svelte/easing';
4
+ import { slide, fade, fly } from 'svelte/transition';
5
+ import {
6
+ AlertCircle,
7
+ AlertTriangle,
8
+ CheckCircle,
9
+ ChevronDown,
10
+ Info,
11
+ X,
12
+ } from '@lucide/svelte';
13
+ import Button from '../atoms/Button.svelte';
14
+ import { toastStore, type ToastItem } from './toast-store.js';
15
+
16
+ interface Props {
17
+ toast: ToastItem;
18
+ }
19
+
20
+ let { toast }: Props = $props();
21
+
22
+ /** Seconds until auto-dismiss; `0` means no countdown (manual dismiss only). */
23
+ function autoDismissSeconds(duration: number | undefined): number {
24
+ if (duration === undefined) return 14;
25
+ if (duration <= 0) return 0;
26
+ return Math.max(1, Math.ceil(duration / 1000));
27
+ }
28
+
29
+ const maxSeconds = $derived(autoDismissSeconds(toast.duration));
30
+
31
+ let count = $state(0);
32
+ let timerActive = $state(false);
33
+ let isExpanded = $state(false);
34
+
35
+ let intervalRef: ReturnType<typeof setInterval> | undefined;
36
+
37
+ const expandableText = $derived((toast.detail ?? toast.message).trim());
38
+
39
+ const hasExpandable = $derived(expandableText.length > 0);
40
+
41
+ const headerTitle = $derived.by(() => {
42
+ const t = toast.title?.trim();
43
+ if (t) return t;
44
+ switch (toast.type) {
45
+ case 'success':
46
+ return 'Changes saved';
47
+ case 'error':
48
+ return 'Something went wrong';
49
+ case 'warning':
50
+ return 'Please review';
51
+ default:
52
+ return 'Notice';
53
+ }
54
+ });
55
+
56
+ const statusIconClass = $derived(
57
+ toast.type === 'success'
58
+ ? 'text-success'
59
+ : toast.type === 'error'
60
+ ? 'text-error'
61
+ : toast.type === 'warning'
62
+ ? 'text-warning'
63
+ : 'text-info',
64
+ );
65
+
66
+ const progressFillClass = $derived(
67
+ toast.type === 'success'
68
+ ? 'bg-success'
69
+ : toast.type === 'error'
70
+ ? 'bg-error'
71
+ : toast.type === 'warning'
72
+ ? 'bg-warning'
73
+ : 'bg-info',
74
+ );
75
+
76
+ function stopTimer() {
77
+ if (intervalRef !== undefined) {
78
+ clearInterval(intervalRef);
79
+ intervalRef = undefined;
80
+ }
81
+ timerActive = false;
82
+ }
83
+
84
+ onMount(() => {
85
+ const max = maxSeconds;
86
+ if (max <= 0) {
87
+ return () => {};
88
+ }
89
+ count = max;
90
+ timerActive = true;
91
+
92
+ intervalRef = setInterval(() => {
93
+ count -= 1;
94
+ if (count <= 0) {
95
+ stopTimer();
96
+ toastStore.dismiss(toast.id);
97
+ }
98
+ }, 1000);
99
+
100
+ return () => {
101
+ if (intervalRef !== undefined) {
102
+ clearInterval(intervalRef);
103
+ intervalRef = undefined;
104
+ }
105
+ };
106
+ });
107
+
108
+ function handleDismiss() {
109
+ toastStore.dismiss(toast.id);
110
+ }
111
+
112
+ function handleOkay() {
113
+ isExpanded = false;
114
+ }
115
+
116
+ const toastEnter = { y: 18, duration: 260, opacity: 0, easing: cubicOut };
117
+ const toastLeave = { y: 14, duration: 300, opacity: 0, easing: cubicOut };
118
+ </script>
119
+
120
+ <div
121
+ class="pointer-events-auto relative w-full min-w-[18rem] overflow-hidden rounded-2xl border border-border bg-surface-1 shadow-xl"
122
+ in:fly={toastEnter}
123
+ out:fly={toastLeave}
124
+ role={toast.type === 'error' ? 'alert' : 'status'}
125
+ >
126
+ <div class="p-4">
127
+ <div class="flex items-start gap-3">
128
+ <div class="shrink-0 pt-0.5">
129
+ {#if toast.type === 'success'}
130
+ <CheckCircle class="size-5 {statusIconClass}" aria-hidden="true" />
131
+ {:else if toast.type === 'error'}
132
+ <AlertCircle class="size-5 {statusIconClass}" aria-hidden="true" />
133
+ {:else if toast.type === 'warning'}
134
+ <AlertTriangle class="size-5 {statusIconClass}" aria-hidden="true" />
135
+ {:else}
136
+ <Info class="size-5 {statusIconClass}" aria-hidden="true" />
137
+ {/if}
138
+ </div>
139
+ <h4 class="min-w-0 flex-1 text-base font-semibold text-headline">
140
+ {headerTitle}
141
+ </h4>
142
+ <div class="flex shrink-0 items-center gap-0.5">
143
+ {#if hasExpandable}
144
+ <button
145
+ type="button"
146
+ class="focus-ring cursor-pointer rounded-md p-1 text-description transition-colors hover:bg-base-100 hover:text-headline focus:outline-none"
147
+ aria-expanded={isExpanded}
148
+ aria-controls="toaster-expand-{toast.id}"
149
+ aria-label={isExpanded ? 'Collapse details' : 'Expand details'}
150
+ onclick={() => (isExpanded = !isExpanded)}
151
+ >
152
+ <ChevronDown
153
+ class="size-5 transition-transform duration-200 {isExpanded
154
+ ? 'rotate-180'
155
+ : ''}"
156
+ aria-hidden="true"
157
+ />
158
+ </button>
159
+ {/if}
160
+ <button
161
+ type="button"
162
+ class="focus-ring cursor-pointer rounded-md p-1 text-description transition-colors hover:bg-base-100 hover:text-headline focus:outline-none"
163
+ onclick={handleDismiss}
164
+ aria-label="Dismiss notification"
165
+ >
166
+ <X class="size-5" aria-hidden="true" />
167
+ </button>
168
+ </div>
169
+ </div>
170
+
171
+ {#if hasExpandable && isExpanded}
172
+ <div
173
+ id="toaster-expand-{toast.id}"
174
+ class="mt-3"
175
+ transition:slide={{ duration: 200 }}
176
+ >
177
+ <p class="text-sm text-description">{expandableText}</p>
178
+ <Button
179
+ variant="primary"
180
+ size="sm"
181
+ text="Okay"
182
+ type="button"
183
+ class="mt-3"
184
+ onclick={handleOkay}
185
+ />
186
+ </div>
187
+ {/if}
188
+ </div>
189
+
190
+ {#if timerActive}
191
+ <div class="px-4 pb-3" transition:fade={{ duration: 180 }}>
192
+ <p class="text-xs text-description">
193
+ This message will close in {count} seconds.
194
+ <button
195
+ type="button"
196
+ class="focus-ring cursor-pointer rounded-sm text-link underline-offset-2 hover:underline focus:outline-none"
197
+ onclick={stopTimer}
198
+ >
199
+ Click to stop
200
+ </button>
201
+ </p>
202
+ </div>
203
+ {/if}
204
+
205
+ {#if timerActive}
206
+ <div
207
+ class="pointer-events-none absolute bottom-0 left-0 right-0 h-1 bg-border/70"
208
+ transition:fade={{ duration: 180 }}
209
+ aria-hidden="true"
210
+ >
211
+ <div
212
+ class="h-full {progressFillClass} transition-all duration-1000 ease-linear"
213
+ style="width: {(count / maxSeconds) * 100}%"
214
+ ></div>
215
+ </div>
216
+ {/if}
217
+ </div>
@@ -0,0 +1,7 @@
1
+ import { type ToastItem } from './toast-store.js';
2
+ interface Props {
3
+ toast: ToastItem;
4
+ }
5
+ declare const ToasterToast: import("svelte").Component<Props, {}, "">;
6
+ type ToasterToast = ReturnType<typeof ToasterToast>;
7
+ export default ToasterToast;
@@ -11,6 +11,16 @@ export { default as NavigationMenuItem } from './NavigationMenuItem.svelte';
11
11
  export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.svelte';
12
12
  export { default as NavigationMenuContent } from './NavigationMenuContent.svelte';
13
13
  export { default as NavigationMenuLink } from './NavigationMenuLink.svelte';
14
+ export { default as Page } from './Page.svelte';
14
15
  export { default as Section } from './Section.svelte';
16
+ export { default as SidebarBrandHeader } from './SidebarBrandHeader.svelte';
17
+ export { default as SidebarFooter } from './SidebarFooter.svelte';
18
+ export { default as SidebarNavSection } from './SidebarNavSection.svelte';
15
19
  export { default as SlideUp } from './SlideUp.svelte';
16
20
  export { default as Tabs } from './Tabs.svelte';
21
+ export { default as FormField } from './FormField.svelte';
22
+ export { default as Header } from './Header.svelte';
23
+ export { default as EmptyState } from './EmptyState.svelte';
24
+ export { default as Toaster } from './Toaster.svelte';
25
+ export { default as RadioGroup } from './RadioGroup.svelte';
26
+ export * from './toast-store.js';
@@ -11,6 +11,16 @@ export { default as NavigationMenuItem } from './NavigationMenuItem.svelte';
11
11
  export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.svelte';
12
12
  export { default as NavigationMenuContent } from './NavigationMenuContent.svelte';
13
13
  export { default as NavigationMenuLink } from './NavigationMenuLink.svelte';
14
+ export { default as Page } from './Page.svelte';
14
15
  export { default as Section } from './Section.svelte';
16
+ export { default as SidebarBrandHeader } from './SidebarBrandHeader.svelte';
17
+ export { default as SidebarFooter } from './SidebarFooter.svelte';
18
+ export { default as SidebarNavSection } from './SidebarNavSection.svelte';
15
19
  export { default as SlideUp } from './SlideUp.svelte';
16
20
  export { default as Tabs } from './Tabs.svelte';
21
+ export { default as FormField } from './FormField.svelte';
22
+ export { default as Header } from './Header.svelte';
23
+ export { default as EmptyState } from './EmptyState.svelte';
24
+ export { default as Toaster } from './Toaster.svelte';
25
+ export { default as RadioGroup } from './RadioGroup.svelte';
26
+ export * from './toast-store.js';
@@ -2,5 +2,16 @@ export interface NavigationMenuContextValue {
2
2
  activeItem: string | null;
3
3
  setActiveItem: (item: string | null) => void;
4
4
  isMobile: boolean;
5
+ /** Prefix for panel `id` / trigger `aria-controls` (one menu instance). */
6
+ menuInstanceId: string;
5
7
  }
6
8
  export declare const NAVIGATION_MENU_CONTEXT_KEY: unique symbol;
9
+ /** Panel element id; `itemValue` is sanitized for HTML id rules. */
10
+ export declare function navigationMenuPanelId(menuInstanceId: string, itemValue: string): string;
11
+ /**
12
+ * Deterministic menu id from item order + values (FNV-1a).
13
+ * SSR and client must agree — do not use `generateId()` for this pairing.
14
+ */
15
+ export declare function navigationMenuStableInstanceId(items: ReadonlyArray<{
16
+ value: string;
17
+ }>): string;
@@ -1 +1,19 @@
1
1
  export const NAVIGATION_MENU_CONTEXT_KEY = Symbol("navigation-menu");
2
+ /** Panel element id; `itemValue` is sanitized for HTML id rules. */
3
+ export function navigationMenuPanelId(menuInstanceId, itemValue) {
4
+ const safe = itemValue.replace(/[^a-zA-Z0-9_-]/g, "-");
5
+ return `${menuInstanceId}-${safe}-panel`;
6
+ }
7
+ /**
8
+ * Deterministic menu id from item order + values (FNV-1a).
9
+ * SSR and client must agree — do not use `generateId()` for this pairing.
10
+ */
11
+ export function navigationMenuStableInstanceId(items) {
12
+ const raw = items.map((i) => i.value).join("\u001f");
13
+ let h = 2166136261;
14
+ for (let i = 0; i < raw.length; i++) {
15
+ h ^= raw.charCodeAt(i);
16
+ h = Math.imul(h, 16777619);
17
+ }
18
+ return `zabi-nav-${(h >>> 0).toString(36)}`;
19
+ }
@@ -0,0 +1,38 @@
1
+ export type ToastLevel = 'success' | 'error' | 'warning' | 'info';
2
+ export interface ToastItem {
3
+ id: string;
4
+ /** Primary line (always shown). */
5
+ message: string;
6
+ type: ToastLevel;
7
+ title?: string;
8
+ /** Expandable body; UI can fall back to `message`. */
9
+ detail?: string;
10
+ /**
11
+ * Auto-dismiss duration in milliseconds.
12
+ * When omitted, the toaster uses 14s. Use `0` to keep the toast until dismissed manually.
13
+ */
14
+ duration?: number;
15
+ }
16
+ export declare const toastStore: {
17
+ subscribe: (this: void, run: import("svelte/store").Subscriber<ToastItem[]>, invalidate?: () => void) => import("svelte/store").Unsubscriber;
18
+ push(options: {
19
+ message: string;
20
+ type?: ToastLevel;
21
+ /** Same semantics as {@link ToastItem.duration}. */
22
+ duration?: number;
23
+ id?: string;
24
+ title?: string;
25
+ detail?: string;
26
+ }): string;
27
+ dismiss: (id: string) => void;
28
+ clear(): void;
29
+ };
30
+ export declare function pushToast(options: {
31
+ message: string;
32
+ type?: ToastLevel;
33
+ /** Same semantics as {@link ToastItem.duration}. */
34
+ duration?: number;
35
+ title?: string;
36
+ detail?: string;
37
+ }): string;
38
+ export declare function dismissToast(id: string): void;
@@ -0,0 +1,34 @@
1
+ import { writable } from 'svelte/store';
2
+ function createToastStore() {
3
+ const { subscribe, update } = writable([]);
4
+ function dismiss(id) {
5
+ update((list) => list.filter((t) => t.id !== id));
6
+ }
7
+ return {
8
+ subscribe,
9
+ push(options) {
10
+ const id = options.id ?? `toast-${Math.random().toString(36).slice(2, 11)}`;
11
+ const item = {
12
+ id,
13
+ message: options.message,
14
+ type: options.type ?? 'info',
15
+ duration: options.duration,
16
+ title: options.title,
17
+ detail: options.detail,
18
+ };
19
+ update((list) => [...list, item]);
20
+ return id;
21
+ },
22
+ dismiss,
23
+ clear() {
24
+ update(() => []);
25
+ },
26
+ };
27
+ }
28
+ export const toastStore = createToastStore();
29
+ export function pushToast(options) {
30
+ return toastStore.push(options);
31
+ }
32
+ export function dismissToast(id) {
33
+ toastStore.dismiss(id);
34
+ }
@@ -0,0 +1,118 @@
1
+ <script lang="ts">
2
+ import SidebarPanel, {
3
+ type SidebarPanelItem,
4
+ } from "./SidebarPanel.svelte";
5
+ import { generateId } from "../util/ssr-safe.js";
6
+ import { LogOut, Moon, Sun, User } from "@lucide/svelte";
7
+
8
+ interface Props {
9
+ /** Target id for `aria-controls` from the sidebar footer/nav. */
10
+ panelId?: string;
11
+ style?: string;
12
+ profileName?: string;
13
+ profileEmail?: string;
14
+ logoutLabel?: string;
15
+ showThemeToggle?: boolean;
16
+ showLogout?: boolean;
17
+ isLightMode?: boolean;
18
+ onThemeToggle?: (nextIsLightMode: boolean) => void;
19
+ onLogout?: () => void;
20
+ onAccount?: () => void;
21
+ onClose?: () => void;
22
+ widthClass?: string;
23
+ variant?: "plain" | "elevated";
24
+ }
25
+
26
+ let {
27
+ panelId = generateId("account-panel"),
28
+ style = "",
29
+ profileName = "Account",
30
+ profileEmail = "",
31
+ logoutLabel = "Log out",
32
+ showThemeToggle = true,
33
+ showLogout = true,
34
+ isLightMode = $bindable(false),
35
+ onThemeToggle,
36
+ onLogout,
37
+ onAccount,
38
+ onClose,
39
+ widthClass = "w-[320px]",
40
+ variant = "plain",
41
+ ...restProps
42
+ }: Props = $props();
43
+
44
+ let selectedItemId = $state("");
45
+
46
+ const items = $derived.by((): SidebarPanelItem[] => {
47
+ const list: SidebarPanelItem[] = [
48
+ {
49
+ id: "account",
50
+ label: "Account",
51
+ description: profileName,
52
+ icon: User,
53
+ },
54
+ ];
55
+
56
+ if (showThemeToggle) {
57
+ list.push({
58
+ id: "theme",
59
+ label: "Theme",
60
+ description: isLightMode ? "Light mode" : "Dark mode",
61
+ badgeText: isLightMode ? "Light" : "Dark",
62
+ icon: isLightMode ? Sun : Moon,
63
+ });
64
+ }
65
+
66
+ if (showLogout) {
67
+ list.push({
68
+ id: "logout",
69
+ label: logoutLabel,
70
+ description: "Sign out of this account",
71
+ icon: LogOut,
72
+ });
73
+ }
74
+
75
+ return list;
76
+ });
77
+
78
+ function toggleTheme(): void {
79
+ isLightMode = !isLightMode;
80
+ onThemeToggle?.(isLightMode);
81
+ }
82
+
83
+ function handleSelect(item: SidebarPanelItem): void {
84
+ if (item.id === "theme") {
85
+ toggleTheme();
86
+ } else if (item.id === "logout") {
87
+ onLogout?.();
88
+ } else if (item.id === "account") {
89
+ onAccount?.();
90
+ }
91
+
92
+ onClose?.();
93
+ }
94
+ </script>
95
+
96
+ <div
97
+ id={panelId}
98
+ {style}
99
+ role="dialog"
100
+ aria-modal="false"
101
+ aria-label="Account panel"
102
+ {...restProps}
103
+ >
104
+ <SidebarPanel
105
+ title="Account"
106
+ subtitle={profileEmail}
107
+ ariaLabel="Account panel"
108
+ closeLabel="Close account panel"
109
+ {widthClass}
110
+ {variant}
111
+ showSearch={false}
112
+ items={items}
113
+ bind:selectedItemId={selectedItemId}
114
+ onSelect={handleSelect}
115
+ onClose={onClose}
116
+ />
117
+ </div>
118
+
@@ -0,0 +1,20 @@
1
+ interface Props {
2
+ /** Target id for `aria-controls` from the sidebar footer/nav. */
3
+ panelId?: string;
4
+ style?: string;
5
+ profileName?: string;
6
+ profileEmail?: string;
7
+ logoutLabel?: string;
8
+ showThemeToggle?: boolean;
9
+ showLogout?: boolean;
10
+ isLightMode?: boolean;
11
+ onThemeToggle?: (nextIsLightMode: boolean) => void;
12
+ onLogout?: () => void;
13
+ onAccount?: () => void;
14
+ onClose?: () => void;
15
+ widthClass?: string;
16
+ variant?: "plain" | "elevated";
17
+ }
18
+ declare const SidebarAccountPanel: import("svelte").Component<Props, {}, "isLightMode">;
19
+ type SidebarAccountPanel = ReturnType<typeof SidebarAccountPanel>;
20
+ export default SidebarAccountPanel;