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.
- package/README.md +42 -44
- package/THEME.md +473 -0
- package/THEMING.md +248 -0
- package/dist/atoms/Card.svelte +40 -67
- package/dist/atoms/Card.svelte.d.ts +3 -3
- package/dist/atoms/CardContent.svelte +1 -1
- package/dist/atoms/CardHeader.svelte +10 -1
- package/dist/atoms/CardHeader.svelte.d.ts +3 -0
- package/dist/atoms/Checkbox.svelte +1 -7
- package/dist/atoms/CodeBlock.svelte +7 -7
- package/dist/atoms/ColorPicker.svelte +81 -73
- package/dist/atoms/Input.svelte +16 -12
- package/dist/atoms/Input.svelte.d.ts +2 -0
- package/dist/atoms/List.svelte +47 -0
- package/dist/atoms/List.svelte.d.ts +19 -0
- package/dist/atoms/ListItem.svelte +148 -0
- package/dist/atoms/ListItem.svelte.d.ts +35 -0
- package/dist/atoms/Progress.svelte +4 -6
- package/dist/atoms/Select.svelte +65 -20
- package/dist/atoms/Select.svelte.d.ts +13 -1
- package/dist/atoms/Textarea.svelte +13 -11
- package/dist/atoms/Textarea.svelte.d.ts +1 -0
- package/dist/atoms/Toast.svelte +1 -1
- package/dist/atoms/Toggle.svelte +1 -7
- package/dist/atoms/Tooltip.svelte +8 -15
- package/dist/atoms/index.d.ts +2 -0
- package/dist/atoms/index.js +2 -0
- package/dist/components/atoms/index.d.ts +2 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +0 -1
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/navigation-menu-context.d.ts +7 -0
- package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -0
- package/dist/components/organisms/index.d.ts +2 -1
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +5 -9
- package/dist/lib/showcase/components-catalog.d.ts +3 -0
- package/dist/lib/showcase/components-catalog.d.ts.map +1 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts +63 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -0
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +15 -0
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +1 -0
- package/dist/molecules/Alert.svelte +9 -4
- package/dist/molecules/ComponentDemo.svelte +53 -50
- package/dist/molecules/ContactForm.svelte +113 -76
- package/dist/molecules/ContactForm.svelte.d.ts +1 -4
- package/dist/molecules/Dropdown.svelte +4 -2
- package/dist/molecules/ImageUpload.svelte +27 -4
- package/dist/molecules/ImageUpload.svelte.d.ts +1 -0
- package/dist/molecules/Modal.svelte +46 -45
- package/dist/molecules/NavigationMenu.svelte +7 -10
- package/dist/molecules/NavigationMenuContent.svelte +9 -9
- package/dist/molecules/NavigationMenuItem.svelte +7 -6
- package/dist/molecules/NavigationMenuLink.svelte +8 -6
- package/dist/molecules/NavigationMenuList.svelte +0 -7
- package/dist/molecules/NavigationMenuTrigger.svelte +8 -11
- package/dist/molecules/Section.svelte +12 -12
- package/dist/molecules/SlideUp.svelte +32 -2
- package/dist/molecules/Tabs.svelte +74 -7
- package/dist/molecules/index.d.ts +0 -1
- package/dist/molecules/index.js +0 -1
- package/dist/molecules/navigation-menu-context.d.ts +6 -0
- package/dist/molecules/navigation-menu-context.js +1 -0
- package/dist/organisms/Navbar.svelte +170 -53
- package/dist/organisms/Navbar.svelte.d.ts +31 -4
- package/dist/organisms/SidebarNavigation.svelte +464 -0
- package/dist/organisms/SidebarNavigation.svelte.d.ts +49 -0
- package/dist/organisms/SidebarPanel.svelte +175 -0
- package/dist/organisms/SidebarPanel.svelte.d.ts +32 -0
- package/dist/organisms/index.d.ts +2 -1
- package/dist/organisms/index.js +2 -1
- package/dist/types/page.types.d.ts +32 -0
- package/dist/types/page.types.d.ts.map +1 -0
- package/dist/types/variants.ts +76 -0
- package/dist/zabi-components-colors.css +347 -324
- package/dist/zabi-components-theme-dark-only.css +159 -151
- package/dist/zabi-components-theme-dark.css +159 -151
- package/dist/zabi-components-theme-only.css +245 -230
- package/dist/zabi-components-theme.css +245 -230
- package/dist/zabi-components.css +722 -84
- package/docs/theme-imports.md +112 -0
- package/package.json +20 -9
- package/dist/molecules/Sidebar.svelte +0 -324
- package/dist/molecules/Sidebar.svelte.d.ts +0 -30
- package/dist/organisms/Navigation.svelte +0 -116
- 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": "
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
96
|
-
"@storybook/sveltekit": "^10.
|
|
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.
|
|
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;
|