zabi-components 5.0.21 → 6.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 (89) hide show
  1. package/README.md +42 -44
  2. package/THEME.md +473 -0
  3. package/THEMING.md +248 -0
  4. package/dist/atoms/Card.svelte +40 -67
  5. package/dist/atoms/Card.svelte.d.ts +3 -3
  6. package/dist/atoms/CardContent.svelte +1 -1
  7. package/dist/atoms/CardHeader.svelte +10 -1
  8. package/dist/atoms/CardHeader.svelte.d.ts +3 -0
  9. package/dist/atoms/Checkbox.svelte +1 -7
  10. package/dist/atoms/CodeBlock.svelte +7 -7
  11. package/dist/atoms/ColorPicker.svelte +81 -73
  12. package/dist/atoms/Input.svelte +16 -12
  13. package/dist/atoms/Input.svelte.d.ts +2 -0
  14. package/dist/atoms/List.svelte +47 -0
  15. package/dist/atoms/List.svelte.d.ts +19 -0
  16. package/dist/atoms/ListItem.svelte +148 -0
  17. package/dist/atoms/ListItem.svelte.d.ts +35 -0
  18. package/dist/atoms/Progress.svelte +4 -6
  19. package/dist/atoms/Select.svelte +65 -20
  20. package/dist/atoms/Select.svelte.d.ts +13 -1
  21. package/dist/atoms/Textarea.svelte +13 -11
  22. package/dist/atoms/Textarea.svelte.d.ts +1 -0
  23. package/dist/atoms/Toast.svelte +1 -1
  24. package/dist/atoms/Toggle.svelte +1 -7
  25. package/dist/atoms/Tooltip.svelte +8 -15
  26. package/dist/atoms/index.d.ts +2 -0
  27. package/dist/atoms/index.js +2 -0
  28. package/dist/components/atoms/index.d.ts +2 -0
  29. package/dist/components/atoms/index.d.ts.map +1 -1
  30. package/dist/components/index.d.ts +4 -1
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/molecules/index.d.ts +0 -1
  33. package/dist/components/molecules/index.d.ts.map +1 -1
  34. package/dist/components/molecules/navigation-menu-context.d.ts +7 -0
  35. package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -0
  36. package/dist/components/organisms/index.d.ts +2 -1
  37. package/dist/components/organisms/index.d.ts.map +1 -1
  38. package/dist/index.d.ts +4 -1
  39. package/dist/index.js +5 -9
  40. package/dist/lib/showcase/components-catalog.d.ts +3 -0
  41. package/dist/lib/showcase/components-catalog.d.ts.map +1 -0
  42. package/dist/lib/showcase/components-showcase-constants.d.ts +63 -0
  43. package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -0
  44. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +15 -0
  45. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +1 -0
  46. package/dist/molecules/Alert.svelte +9 -4
  47. package/dist/molecules/ComponentDemo.svelte +53 -50
  48. package/dist/molecules/ContactForm.svelte +113 -76
  49. package/dist/molecules/ContactForm.svelte.d.ts +1 -4
  50. package/dist/molecules/Dropdown.svelte +4 -2
  51. package/dist/molecules/ImageUpload.svelte +27 -4
  52. package/dist/molecules/ImageUpload.svelte.d.ts +1 -0
  53. package/dist/molecules/Modal.svelte +46 -45
  54. package/dist/molecules/NavigationMenu.svelte +7 -10
  55. package/dist/molecules/NavigationMenuContent.svelte +9 -9
  56. package/dist/molecules/NavigationMenuItem.svelte +7 -6
  57. package/dist/molecules/NavigationMenuLink.svelte +8 -6
  58. package/dist/molecules/NavigationMenuList.svelte +0 -7
  59. package/dist/molecules/NavigationMenuTrigger.svelte +8 -11
  60. package/dist/molecules/Section.svelte +12 -12
  61. package/dist/molecules/SlideUp.svelte +32 -2
  62. package/dist/molecules/Tabs.svelte +74 -7
  63. package/dist/molecules/index.d.ts +0 -1
  64. package/dist/molecules/index.js +0 -1
  65. package/dist/molecules/navigation-menu-context.d.ts +6 -0
  66. package/dist/molecules/navigation-menu-context.js +1 -0
  67. package/dist/organisms/Navbar.svelte +170 -53
  68. package/dist/organisms/Navbar.svelte.d.ts +31 -4
  69. package/dist/organisms/SidebarNavigation.svelte +464 -0
  70. package/dist/organisms/SidebarNavigation.svelte.d.ts +49 -0
  71. package/dist/organisms/SidebarPanel.svelte +175 -0
  72. package/dist/organisms/SidebarPanel.svelte.d.ts +32 -0
  73. package/dist/organisms/index.d.ts +2 -1
  74. package/dist/organisms/index.js +2 -1
  75. package/dist/types/page.types.d.ts +32 -0
  76. package/dist/types/page.types.d.ts.map +1 -0
  77. package/dist/types/variants.ts +76 -0
  78. package/dist/zabi-components-colors.css +347 -324
  79. package/dist/zabi-components-theme-dark-only.css +159 -151
  80. package/dist/zabi-components-theme-dark.css +159 -151
  81. package/dist/zabi-components-theme-only.css +245 -230
  82. package/dist/zabi-components-theme.css +245 -230
  83. package/dist/zabi-components.css +722 -84
  84. package/docs/theme-imports.md +112 -0
  85. package/package.json +20 -9
  86. package/dist/molecules/Sidebar.svelte +0 -324
  87. package/dist/molecules/Sidebar.svelte.d.ts +0 -30
  88. package/dist/organisms/Navigation.svelte +0 -116
  89. package/dist/organisms/Navigation.svelte.d.ts +0 -20
@@ -0,0 +1,112 @@
1
+ # Theme CSS imports (consumers)
2
+
3
+ Published CSS files are **generated** from `src/app.css` when you run `npm run build:css`. Do not edit files under `dist/` by hand.
4
+
5
+ ## Canonical recommendation
6
+
7
+ For most Tailwind v4 apps, use:
8
+
9
+ ```css
10
+ @import "tailwindcss";
11
+ @import "zabi-components/theme-only";
12
+ @import "zabi-components/theme-dark-only";
13
+ ```
14
+
15
+ Use short package exports by default. Legacy deep `dist` imports remain supported for compatibility.
16
+
17
+ ## Package export paths
18
+
19
+ Use these subpaths with your bundler or `npm`/`pnpm` resolution:
20
+
21
+ | Export | Resolves to | Use case |
22
+ |--------|-------------|----------|
23
+ | `zabi-components/theme` | `zabi-components-theme.css` | Tailwind **not** already in the project; ships `@import "tailwindcss"` + `@theme`. |
24
+ | `zabi-components/theme-only` | `zabi-components-theme-only.css` | Tailwind **already** configured; merge only the `@theme` block. |
25
+ | `zabi-components/theme-dark` | `zabi-components-theme-dark.css` | Same as `theme` but for **dark**; includes Tailwind import + `.dark { … }`. |
26
+ | `zabi-components/theme-dark-only` | `zabi-components-theme-dark-only.css` | Tailwind present; **only** `.dark { … }` overrides (import after light theme). |
27
+ | `zabi-components/colors` | `zabi-components-colors.css` | **No Tailwind**: `:root` + `.dark` CSS variables only. |
28
+ | `zabi-components/css` | `zabi-components.css` | Full compiled CSS (utilities, components styles, dark). |
29
+
30
+ ### Legacy export paths (supported, deprecated in docs)
31
+
32
+ These are still exported for backward compatibility but are not the recommended imports for new integration:
33
+
34
+ | Legacy export | Resolves to |
35
+ |--------|-------------|
36
+ | `zabi-components/dist/zabi-components.css` | `zabi-components.css` |
37
+ | `zabi-components/dist/zabi-components-colors.css` | `zabi-components-colors.css` |
38
+ | `zabi-components/dist/zabi-components-theme.css` | `zabi-components-theme.css` |
39
+ | `zabi-components/dist/zabi-components-theme-only.css` | `zabi-components-theme-only.css` |
40
+ | `zabi-components/dist/zabi-components-theme-dark.css` | `zabi-components-theme-dark.css` |
41
+ | `zabi-components/dist/zabi-components-theme-dark-only.css` | `zabi-components-theme-dark-only.css` |
42
+
43
+ ## Decision matrix
44
+
45
+ 1. **Vanilla CSS or no Tailwind** → `colors` (or full `css` if you need utility classes from the bundle).
46
+ 2. **Tailwind v4 app, extend design tokens** → `theme-only` (+ `theme-dark-only` if you use `class="dark"` / dark mode).
47
+ 3. **Minimal setup, one import for light** → `theme`.
48
+ 4. **Dark as a second file** → `theme` + `theme-dark`, or `theme-only` + `theme-dark-only`.
49
+
50
+ ## Examples
51
+
52
+ ### Vite / SvelteKit (Tailwind already installed)
53
+
54
+ ```css
55
+ /* src/app.css */
56
+ @import "tailwindcss";
57
+ @import "zabi-components/theme-only";
58
+ @import "zabi-components/theme-dark-only";
59
+ ```
60
+
61
+ ### Vite (no Tailwind; variables only)
62
+
63
+ ```css
64
+ @import "zabi-components/colors";
65
+ ```
66
+
67
+ ### Full stylesheet (Storybook, demos)
68
+
69
+ ```css
70
+ @import "zabi-components/css";
71
+ ```
72
+
73
+ ### Next.js App Router (global CSS)
74
+
75
+ ```css
76
+ /* app/globals.css */
77
+ @import "tailwindcss";
78
+ @import "zabi-components/theme-only";
79
+ @import "zabi-components/theme-dark-only";
80
+ ```
81
+
82
+ ### Vanilla CSS app (no Tailwind)
83
+
84
+ ```css
85
+ @import "zabi-components/colors";
86
+ ```
87
+
88
+ Toggle `.dark` at the root element to activate dark tokens:
89
+
90
+ ```html
91
+ <html class="dark">
92
+ ```
93
+
94
+ ## Maintainer checklist (token changes)
95
+
96
+ 1. Edit **`src/app.css`** only (`@theme` for light tokens, `.dark` for overrides).
97
+ 2. If you change **physical base ramp** hexes, keep the **mirrored** `--zabi-base-*` block inside `.dark` in sync with `@theme` (required for standalone dark imports).
98
+ 3. Run **`npm run build:css`** (or **`npm run build:lib`** before publish).
99
+ 4. Confirm **`node scripts/verify-build.js`** passes in CI or locally after builds.
100
+
101
+ ## Generated outputs (reference)
102
+
103
+ | File | Contents |
104
+ |------|----------|
105
+ | `zabi-components.css` | PostCSS/Tailwind output: utilities + theme + `.dark`. |
106
+ | `zabi-components-theme.css` | `@import tailwind` + `@theme`. |
107
+ | `zabi-components-theme-only.css` | `@theme` only. |
108
+ | `zabi-components-theme-dark.css` | `@import tailwind` + `.dark`. |
109
+ | `zabi-components-theme-dark-only.css` | `.dark` only. |
110
+ | `zabi-components-colors.css` | `:root` + `.dark` as plain custom properties. |
111
+
112
+ Build logic lives in **`scripts/build-css.js`** (see file header for behavior).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zabi-components",
3
- "version": "5.0.21",
4
- "description": "A modern Svelte 5 component library with TypeScript, Tailwind CSS v4, and 100% SSR-safe components",
3
+ "version": "6.0.0",
4
+ "description": "Svelte 5 UI components with TypeScript and Tailwind CSS v4. Accessible, easy to theme, works with SSR.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -53,11 +53,16 @@
53
53
  "./theme-only": "./dist/zabi-components-theme-only.css",
54
54
  "./theme-dark": "./dist/zabi-components-theme-dark.css",
55
55
  "./theme-dark-only": "./dist/zabi-components-theme-dark-only.css",
56
+ "./colors": "./dist/zabi-components-colors.css",
57
+ "./css": "./dist/zabi-components.css",
56
58
  "./package.json": "./package.json"
57
59
  },
58
60
  "files": [
59
61
  "dist",
60
- "README.md"
62
+ "README.md",
63
+ "THEME.md",
64
+ "THEMING.md",
65
+ "docs/theme-imports.md"
61
66
  ],
62
67
  "keywords": [
63
68
  "svelte",
@@ -80,20 +85,24 @@
80
85
  "dev": "vite dev",
81
86
  "build": "vite build && npm run build:types",
82
87
  "build:components": "svelte-package -o dist --input src/components",
83
- "build:lib-files": "mkdir -p dist/routes/lib && cp src/routes/lib/ssr-safe.ts dist/routes/lib/ && cp src/routes/lib/variant-utils.ts dist/routes/lib/ && cp src/routes/lib/focus-utils.ts dist/routes/lib/ && cp src/routes/lib/focus-utils.d.ts dist/routes/lib/ 2>/dev/null || true",
88
+ "build:lib-files": "mkdir -p dist/routes/lib dist/types && cp src/routes/lib/ssr-safe.ts dist/routes/lib/ && cp src/routes/lib/variant-utils.ts dist/routes/lib/ && cp src/routes/lib/focus-utils.ts dist/routes/lib/ && cp src/routes/lib/focus-utils.d.ts dist/routes/lib/ 2>/dev/null || true && cp src/types/variants.ts dist/types/",
84
89
  "build:fix-imports": "node scripts/fix-modal-import.js",
85
90
  "build:lib": "npm run build:css && npm run build:components && npm run build:lib-files && npm run build:fix-imports && npm run build:types && npm run build:css && node scripts/verify-build.js",
86
- "build:css": "node scripts/build-css.js && node scripts/validate-theme.js && node scripts/cleanup-css.js",
91
+ "sync:tokens": "node scripts/sync-theme-tokens.js",
92
+ "build:css": "npm run sync:tokens && node scripts/build-css.js && node scripts/validate-theme.js",
93
+ "check:tokens": "node scripts/check-token-violations.js",
87
94
  "build:types": "tsc --project tsconfig.lib.json",
88
95
  "preview": "vite preview",
89
96
  "check": "svelte-check --tsconfig ./tsconfig.json",
90
97
  "storybook": "storybook dev -p 6006",
91
98
  "build-storybook": "storybook build",
92
- "prepublishOnly": "npm run build:lib"
99
+ "test:themes": "node --test tests/theme-output.test.js",
100
+ "prepublishOnly": "npm run build:lib",
101
+ "publish:npm": "npm publish --access public --registry https://registry.npmjs.org/"
93
102
  },
94
103
  "devDependencies": {
95
- "@storybook/addon-docs": "^10.0.7",
96
- "@storybook/sveltekit": "^10.0.7",
104
+ "@storybook/addon-docs": "^10.2.19",
105
+ "@storybook/sveltekit": "^10.2.19",
97
106
  "@sveltejs/adapter-auto": "^7.0.0",
98
107
  "@sveltejs/kit": "^2.47.2",
99
108
  "@sveltejs/package": "^2.5.4",
@@ -103,8 +112,10 @@
103
112
  "@types/node": "^24.5.2",
104
113
  "@types/react": "^19.2.2",
105
114
  "autoprefixer": "^10.4.17",
115
+ "baseline-browser-mapping": "^2.10.18",
116
+ "culori": "^4.0.2",
106
117
  "postcss": "^8.4.35",
107
- "storybook": "^10.0.7",
118
+ "storybook": "^10.2.19",
108
119
  "svelte": "^5.43.8",
109
120
  "svelte-check": "^4.3.1",
110
121
  "tailwindcss": "^4.1.13",
@@ -1,324 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from "svelte";
3
- import { onMount, onDestroy } from "svelte";
4
-
5
- interface SidebarItem {
6
- id: string;
7
- label: string;
8
- description?: string;
9
- icon?: any;
10
- onClick?: () => void;
11
- }
12
-
13
- interface SidebarSection {
14
- title?: string;
15
- items: SidebarItem[];
16
- }
17
-
18
- interface Props {
19
- title?: string;
20
- sections?: SidebarSection[];
21
- items?: SidebarItem[];
22
- selectedItemId?: string;
23
- selectedSectionId?: string;
24
- onItemClick?: (item: SidebarItem) => void;
25
- onSectionClick?: (sectionId: string) => void;
26
- className?: string;
27
- header?: Snippet;
28
- footer?: Snippet;
29
- isOpen?: boolean;
30
- onClose?: () => void;
31
- mobileResponsive?: boolean;
32
- }
33
-
34
- let {
35
- title = "",
36
- sections = [],
37
- items = [],
38
- selectedItemId = "",
39
- selectedSectionId = "",
40
- onItemClick,
41
- onSectionClick,
42
- className = "",
43
- header,
44
- footer,
45
- isOpen = true,
46
- onClose,
47
- mobileResponsive = true,
48
- ...restProps
49
- }: Props = $props();
50
-
51
- let isMobile = $state(false);
52
- let sidebarElement = $state<HTMLElement | null>(null);
53
-
54
- function checkMobile() {
55
- if (mobileResponsive) {
56
- isMobile = window.innerWidth < 768;
57
- } else {
58
- isMobile = false;
59
- }
60
- }
61
-
62
- function handleItemClick(item: SidebarItem) {
63
- if (item.onClick) {
64
- item.onClick();
65
- }
66
- if (onItemClick) {
67
- onItemClick(item);
68
- }
69
- // Close sidebar on mobile when item is clicked
70
- if (isMobile && onClose) {
71
- onClose();
72
- }
73
- }
74
-
75
- function handleSectionClick(sectionId: string) {
76
- if (onSectionClick) {
77
- onSectionClick(sectionId);
78
- }
79
- }
80
-
81
- function handleBackdropClick(event: MouseEvent) {
82
- if (event.target === event.currentTarget && onClose) {
83
- onClose();
84
- }
85
- }
86
-
87
- function handleKeydown(event: KeyboardEvent) {
88
- if (event.key === "Escape" && isOpen && isMobile && onClose) {
89
- onClose();
90
- }
91
- }
92
-
93
- onMount(() => {
94
- checkMobile();
95
- if (mobileResponsive) {
96
- window.addEventListener("resize", checkMobile);
97
- return () => window.removeEventListener("resize", checkMobile);
98
- }
99
- });
100
-
101
- const hasSections = $derived(sections.length > 0);
102
- const hasItems = $derived(items.length > 0);
103
- </script>
104
-
105
- {#if isMobile && mobileResponsive}
106
- <!-- Mobile: Overlay and Drawer -->
107
- {#if isOpen}
108
- <div
109
- class="fixed inset-0 bg-overlay z-modal md:hidden"
110
- onclick={handleBackdropClick}
111
- onkeydown={handleKeydown}
112
- role="dialog"
113
- aria-modal="true"
114
- aria-label="Sidebar navigation"
115
- tabindex="-1"
116
- >
117
- <aside
118
- bind:this={sidebarElement}
119
- class="fixed left-0 top-0 bottom-0 w-64 bg-base-100 border-r border-border p-6 h-full overflow-y-auto transform transition-transform duration-300 ease-in-out {isOpen ? 'translate-x-0' : '-translate-x-full'} {className}"
120
- role="complementary"
121
- aria-label="Sidebar navigation"
122
- onclick={(e) => e.stopPropagation()}
123
- {...restProps}
124
- >
125
- <div class="flex items-center justify-between mb-4 md:hidden">
126
- {#if title}
127
- <h2 class="text-lg font-semibold text-headline">{title}</h2>
128
- {/if}
129
- {#if onClose}
130
- <button
131
- type="button"
132
- onclick={onClose}
133
- 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-50"
134
- aria-label="Close sidebar"
135
- >
136
- ×
137
- </button>
138
- {/if}
139
- </div>
140
-
141
- {#if header}
142
- <div class="mb-6">
143
- {@render header()}
144
- </div>
145
- {:else if title && !isMobile}
146
- <h2 class="text-lg font-semibold text-headline mb-4">{title}</h2>
147
- {/if}
148
-
149
- {#if hasSections}
150
- {#each sections as section}
151
- {#if section.title}
152
- <div class="mb-4">
153
- <h3 class="text-sm font-medium text-headline mb-2 uppercase tracking-wide">
154
- {section.title}
155
- </h3>
156
- </div>
157
- {/if}
158
-
159
- <nav class="space-y-2 mb-8">
160
- {#each section.items as item}
161
- <button
162
- type="button"
163
- onclick={() => handleItemClick(item)}
164
- class="w-full text-left px-3 py-2 rounded-md transition-colors duration-200 {selectedItemId ===
165
- item.id
166
- ? 'bg-action-primary-subtle text-headline border-l-2 border-action-primary'
167
- : 'text-secondary hover:text-headline hover:bg-base-50'}"
168
- aria-current={selectedItemId === item.id ? "page" : undefined}
169
- >
170
- <div class="flex items-center gap-2">
171
- {#if item.icon}
172
- <div class="shrink-0">
173
- <svelte:component this={item.icon} class="h-4 w-4" />
174
- </div>
175
- {/if}
176
- <div class="flex-1">
177
- <div class="font-medium">{item.label}</div>
178
- {#if item.description}
179
- <div class="text-xs text-description">
180
- {item.description}
181
- </div>
182
- {/if}
183
- </div>
184
- </div>
185
- </button>
186
- {/each}
187
- </nav>
188
- {/each}
189
- {:else if hasItems}
190
- <nav class="space-y-1">
191
- {#each items as item}
192
- <button
193
- type="button"
194
- onclick={() => handleItemClick(item)}
195
- class="w-full text-left px-3 py-2 rounded-md transition-colors duration-200 {selectedItemId ===
196
- item.id
197
- ? 'bg-action-primary-subtle text-headline border-l-2 border-action-primary'
198
- : 'text-secondary hover:text-headline hover:bg-base-50'}"
199
- aria-current={selectedItemId === item.id ? "page" : undefined}
200
- >
201
- <div class="flex items-center gap-2">
202
- {#if item.icon}
203
- <div class="shrink-0">
204
- <svelte:component this={item.icon} class="h-4 w-4" />
205
- </div>
206
- {/if}
207
- <div class="flex-1">
208
- <div class="font-medium">{item.label}</div>
209
- {#if item.description}
210
- <div class="text-xs text-description">
211
- {item.description}
212
- </div>
213
- {/if}
214
- </div>
215
- </div>
216
- </button>
217
- {/each}
218
- </nav>
219
- {/if}
220
-
221
- {#if footer}
222
- <div class="mt-auto pt-6 border-t border-border">
223
- {@render footer()}
224
- </div>
225
- {/if}
226
- </aside>
227
- </div>
228
- {/if}
229
- {:else}
230
- <!-- Desktop: Always visible sidebar -->
231
- <aside
232
- bind:this={sidebarElement}
233
- class="w-64 bg-base-100 border-r border-border p-6 h-full overflow-y-auto {className}"
234
- role="complementary"
235
- aria-label="Sidebar navigation"
236
- {...restProps}
237
- >
238
- {#if header}
239
- <div class="mb-6">
240
- {@render header()}
241
- </div>
242
- {:else if title}
243
- <h2 class="text-lg font-semibold text-headline mb-4">{title}</h2>
244
- {/if}
245
-
246
- {#if hasSections}
247
- {#each sections as section}
248
- {#if section.title}
249
- <div class="mb-4">
250
- <h3 class="text-sm font-medium text-headline mb-2 uppercase tracking-wide">
251
- {section.title}
252
- </h3>
253
- </div>
254
- {/if}
255
-
256
- <nav class="space-y-2 mb-8">
257
- {#each section.items as item}
258
- <button
259
- type="button"
260
- onclick={() => handleItemClick(item)}
261
- class="w-full text-left px-3 py-2 rounded-md transition-colors duration-200 {selectedItemId ===
262
- item.id
263
- ? 'bg-action-primary-subtle text-headline border-l-2 border-action-primary'
264
- : 'text-secondary hover:text-headline hover:bg-base-50'}"
265
- aria-current={selectedItemId === item.id ? "page" : undefined}
266
- >
267
- <div class="flex items-center gap-2">
268
- {#if item.icon}
269
- <div class="shrink-0">
270
- <svelte:component this={item.icon} class="h-4 w-4" />
271
- </div>
272
- {/if}
273
- <div class="flex-1">
274
- <div class="font-medium">{item.label}</div>
275
- {#if item.description}
276
- <div class="text-xs text-description">
277
- {item.description}
278
- </div>
279
- {/if}
280
- </div>
281
- </div>
282
- </button>
283
- {/each}
284
- </nav>
285
- {/each}
286
- {:else if hasItems}
287
- <nav class="space-y-1">
288
- {#each items as item}
289
- <button
290
- type="button"
291
- onclick={() => handleItemClick(item)}
292
- class="w-full text-left px-3 py-2 rounded-md transition-colors duration-200 {selectedItemId ===
293
- item.id
294
- ? 'bg-action-primary-subtle text-headline border-l-2 border-action-primary'
295
- : 'text-secondary hover:text-headline hover:bg-base-50'}"
296
- aria-current={selectedItemId === item.id ? "page" : undefined}
297
- >
298
- <div class="flex items-center gap-2">
299
- {#if item.icon}
300
- <div class="shrink-0">
301
- <svelte:component this={item.icon} class="h-4 w-4" />
302
- </div>
303
- {/if}
304
- <div class="flex-1">
305
- <div class="font-medium">{item.label}</div>
306
- {#if item.description}
307
- <div class="text-xs text-description">
308
- {item.description}
309
- </div>
310
- {/if}
311
- </div>
312
- </div>
313
- </button>
314
- {/each}
315
- </nav>
316
- {/if}
317
-
318
- {#if footer}
319
- <div class="mt-auto pt-6 border-t border-border">
320
- {@render footer()}
321
- </div>
322
- {/if}
323
- </aside>
324
- {/if}
@@ -1,30 +0,0 @@
1
- import type { Snippet } from "svelte";
2
- interface SidebarItem {
3
- id: string;
4
- label: string;
5
- description?: string;
6
- icon?: any;
7
- onClick?: () => void;
8
- }
9
- interface SidebarSection {
10
- title?: string;
11
- items: SidebarItem[];
12
- }
13
- interface Props {
14
- title?: string;
15
- sections?: SidebarSection[];
16
- items?: SidebarItem[];
17
- selectedItemId?: string;
18
- selectedSectionId?: string;
19
- onItemClick?: (item: SidebarItem) => void;
20
- onSectionClick?: (sectionId: string) => void;
21
- className?: string;
22
- header?: Snippet;
23
- footer?: Snippet;
24
- isOpen?: boolean;
25
- onClose?: () => void;
26
- mobileResponsive?: boolean;
27
- }
28
- declare const Sidebar: import("svelte").Component<Props, {}, "">;
29
- type Sidebar = ReturnType<typeof Sidebar>;
30
- export default Sidebar;
@@ -1,116 +0,0 @@
1
- <script lang="ts">
2
- import type { Component } from "svelte";
3
-
4
- interface NavigationItem {
5
- label: string;
6
- href: string;
7
- icon?: Component<any>; // Outline icon component
8
- iconFilled?: Component<any>; // Filled icon component (for active state)
9
- }
10
-
11
- interface Props {
12
- variant?: "header" | "sidebar";
13
- items?: NavigationItem[];
14
- currentPath?: string;
15
- onclick?: (event: Event) => void;
16
- className?: string;
17
- }
18
-
19
- let {
20
- variant = "header",
21
- items = [],
22
- currentPath = "",
23
- onclick,
24
- className = "",
25
- ...restProps
26
- }: Props & { children?: any } = $props();
27
-
28
- function handleClick(item: NavigationItem, event: MouseEvent) {
29
- event.preventDefault();
30
- if (onclick) {
31
- onclick(event as any);
32
- }
33
- }
34
-
35
- const ulClasses = $derived(() => {
36
- return variant === "sidebar"
37
- ? "flex flex-col gap-1"
38
- : "flex flex-col gap-1 md:flex-row md:items-center md:justify-between";
39
- });
40
-
41
- function getNavItemClasses(itemHref: string): string {
42
- const baseClasses = "flex flex-col gap-1 grow h-full items-center justify-center min-h-0 min-w-0 relative shrink-0 cursor-pointer w-full md:w-auto";
43
-
44
- return baseClasses;
45
- }
46
-
47
- function getIconContainerClasses(itemHref: string): string {
48
- const isActive = currentPath === itemHref;
49
- const baseClasses = "flex flex-col items-center justify-center overflow-clip relative rounded-[20px] shrink-0 transition-colors duration-200";
50
-
51
- if (isActive) {
52
- return `${baseClasses} bg-action-primary-subtle-hover`;
53
- }
54
- return `${baseClasses}`;
55
- }
56
-
57
- function getStateLayerClasses(): string {
58
- return "box-border flex gap-1 h-10 items-center px-4 py-2 relative shrink-0";
59
- }
60
-
61
- function getLabelClasses(itemHref: string): string {
62
- const isActive = currentPath === itemHref;
63
- const baseClasses = "font-medium leading-4 relative shrink-0 text-center text-nowrap tracking-[0.5px] whitespace-pre text-xs";
64
-
65
- if (isActive) {
66
- return `${baseClasses} text-link`;
67
- }
68
- return `${baseClasses} text-description`;
69
- }
70
-
71
- function getIconClasses(itemHref: string): string {
72
- const isActive = currentPath === itemHref;
73
-
74
- if (isActive) {
75
- return "text-link";
76
- }
77
- return "text-description";
78
- }
79
- </script>
80
-
81
- <ul class="{ulClasses()} {className}" {...restProps}>
82
- {#each items as item}
83
- {@const isActive = currentPath === item.href}
84
- {@const navItemClasses = getNavItemClasses(item.href)}
85
- {@const iconContainerClasses = getIconContainerClasses(item.href)}
86
- {@const labelClasses = getLabelClasses(item.href)}
87
- <li class={navItemClasses}>
88
- <a
89
- href={item.href}
90
- class={iconContainerClasses}
91
- onclick={(e) => handleClick(item, e)}
92
- role="button"
93
- aria-current={isActive ? "page" : undefined}
94
- >
95
- <div class={getStateLayerClasses()}>
96
- {#if item.iconFilled && isActive}
97
- {@const iconClasses = getIconClasses(item.href)}
98
- {@const Icon = item.iconFilled}
99
- <div class="overflow-clip relative shrink-0 size-4 {iconClasses}">
100
- <Icon size={16} class="w-4 h-4" />
101
- </div>
102
- {:else if item.icon}
103
- {@const iconClasses = getIconClasses(item.href)}
104
- {@const Icon = item.icon}
105
- <div class="overflow-clip relative shrink-0 size-4 {iconClasses}">
106
- <Icon size={16} class="w-4 h-4" />
107
- </div>
108
- {/if}
109
- <p class={labelClasses}>
110
- {item.label}
111
- </p>
112
- </div>
113
- </a>
114
- </li>
115
- {/each}
116
- </ul>
@@ -1,20 +0,0 @@
1
- import type { Component } from "svelte";
2
- interface NavigationItem {
3
- label: string;
4
- href: string;
5
- icon?: Component<any>;
6
- iconFilled?: Component<any>;
7
- }
8
- interface Props {
9
- variant?: "header" | "sidebar";
10
- items?: NavigationItem[];
11
- currentPath?: string;
12
- onclick?: (event: Event) => void;
13
- className?: string;
14
- }
15
- type $$ComponentProps = Props & {
16
- children?: any;
17
- };
18
- declare const Navigation: Component<$$ComponentProps, {}, "">;
19
- type Navigation = ReturnType<typeof Navigation>;
20
- export default Navigation;