@classic-homes/theme-svelte 0.1.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 +305 -0
- package/dist/lib/components/Alert.svelte +51 -0
- package/dist/lib/components/Alert.svelte.d.ts +9 -0
- package/dist/lib/components/AlertDescription.svelte +16 -0
- package/dist/lib/components/AlertDescription.svelte.d.ts +9 -0
- package/dist/lib/components/AlertDialog.svelte +136 -0
- package/dist/lib/components/AlertDialog.svelte.d.ts +79 -0
- package/dist/lib/components/AlertTitle.svelte +16 -0
- package/dist/lib/components/AlertTitle.svelte.d.ts +9 -0
- package/dist/lib/components/Avatar.svelte +56 -0
- package/dist/lib/components/Avatar.svelte.d.ts +26 -0
- package/dist/lib/components/AvatarFallback.svelte +31 -0
- package/dist/lib/components/AvatarFallback.svelte.d.ts +17 -0
- package/dist/lib/components/AvatarImage.svelte +29 -0
- package/dist/lib/components/AvatarImage.svelte.d.ts +12 -0
- package/dist/lib/components/Badge.svelte +73 -0
- package/dist/lib/components/Badge.svelte.d.ts +11 -0
- package/dist/lib/components/Button.svelte +130 -0
- package/dist/lib/components/Button.svelte.d.ts +17 -0
- package/dist/lib/components/Card.svelte +58 -0
- package/dist/lib/components/Card.svelte.d.ts +26 -0
- package/dist/lib/components/CardContent.svelte +16 -0
- package/dist/lib/components/CardContent.svelte.d.ts +9 -0
- package/dist/lib/components/CardDescription.svelte +16 -0
- package/dist/lib/components/CardDescription.svelte.d.ts +9 -0
- package/dist/lib/components/CardFooter.svelte +16 -0
- package/dist/lib/components/CardFooter.svelte.d.ts +9 -0
- package/dist/lib/components/CardHeader.svelte +16 -0
- package/dist/lib/components/CardHeader.svelte.d.ts +9 -0
- package/dist/lib/components/CardTitle.svelte +16 -0
- package/dist/lib/components/CardTitle.svelte.d.ts +9 -0
- package/dist/lib/components/Checkbox.svelte +65 -0
- package/dist/lib/components/Checkbox.svelte.d.ts +14 -0
- package/dist/lib/components/DataTable.svelte +334 -0
- package/dist/lib/components/DataTable.svelte.d.ts +103 -0
- package/dist/lib/components/Dialog.svelte +111 -0
- package/dist/lib/components/Dialog.svelte.d.ts +22 -0
- package/dist/lib/components/DropdownMenu.svelte +135 -0
- package/dist/lib/components/DropdownMenu.svelte.d.ts +33 -0
- package/dist/lib/components/FileUpload.svelte +448 -0
- package/dist/lib/components/FileUpload.svelte.d.ts +42 -0
- package/dist/lib/components/FormField.svelte +134 -0
- package/dist/lib/components/FormField.svelte.d.ts +37 -0
- package/dist/lib/components/Input.svelte +61 -0
- package/dist/lib/components/Input.svelte.d.ts +19 -0
- package/dist/lib/components/Label.svelte +33 -0
- package/dist/lib/components/Label.svelte.d.ts +11 -0
- package/dist/lib/components/LoadingLogo.svelte +124 -0
- package/dist/lib/components/LoadingLogo.svelte.d.ts +16 -0
- package/dist/lib/components/LogoMain.svelte +237 -0
- package/dist/lib/components/LogoMain.svelte.d.ts +20 -0
- package/dist/lib/components/PageHeader.svelte +90 -0
- package/dist/lib/components/PageHeader.svelte.d.ts +28 -0
- package/dist/lib/components/Section.svelte +44 -0
- package/dist/lib/components/Section.svelte.d.ts +28 -0
- package/dist/lib/components/Select.svelte +174 -0
- package/dist/lib/components/Select.svelte.d.ts +32 -0
- package/dist/lib/components/Separator.svelte +29 -0
- package/dist/lib/components/Separator.svelte.d.ts +9 -0
- package/dist/lib/components/Skeleton.svelte +35 -0
- package/dist/lib/components/Skeleton.svelte.d.ts +7 -0
- package/dist/lib/components/Spinner.svelte +50 -0
- package/dist/lib/components/Spinner.svelte.d.ts +8 -0
- package/dist/lib/components/Switch.svelte +56 -0
- package/dist/lib/components/Switch.svelte.d.ts +14 -0
- package/dist/lib/components/TabPanel.svelte +44 -0
- package/dist/lib/components/TabPanel.svelte.d.ts +12 -0
- package/dist/lib/components/Tabs.svelte +125 -0
- package/dist/lib/components/Tabs.svelte.d.ts +19 -0
- package/dist/lib/components/Textarea.svelte +54 -0
- package/dist/lib/components/Textarea.svelte.d.ts +16 -0
- package/dist/lib/components/Toast.svelte +116 -0
- package/dist/lib/components/Toast.svelte.d.ts +12 -0
- package/dist/lib/components/ToastContainer.svelte +56 -0
- package/dist/lib/components/ToastContainer.svelte.d.ts +8 -0
- package/dist/lib/components/Tooltip.svelte +55 -0
- package/dist/lib/components/Tooltip.svelte.d.ts +18 -0
- package/dist/lib/components/layout/AppShell.svelte +82 -0
- package/dist/lib/components/layout/AppShell.svelte.d.ts +44 -0
- package/dist/lib/components/layout/DashboardLayout.svelte +248 -0
- package/dist/lib/components/layout/DashboardLayout.svelte.d.ts +62 -0
- package/dist/lib/components/layout/Footer.svelte +130 -0
- package/dist/lib/components/layout/Footer.svelte.d.ts +32 -0
- package/dist/lib/components/layout/FormPageLayout.svelte +92 -0
- package/dist/lib/components/layout/FormPageLayout.svelte.d.ts +33 -0
- package/dist/lib/components/layout/Header.svelte +94 -0
- package/dist/lib/components/layout/Header.svelte.d.ts +30 -0
- package/dist/lib/components/layout/PublicLayout.svelte +180 -0
- package/dist/lib/components/layout/PublicLayout.svelte.d.ts +39 -0
- package/dist/lib/components/layout/QuickLinks.svelte +112 -0
- package/dist/lib/components/layout/QuickLinks.svelte.d.ts +27 -0
- package/dist/lib/components/layout/Sidebar.svelte +243 -0
- package/dist/lib/components/layout/Sidebar.svelte.d.ts +48 -0
- package/dist/lib/composables/index.d.ts +8 -0
- package/dist/lib/composables/index.js +10 -0
- package/dist/lib/composables/useAsync.svelte.d.ts +102 -0
- package/dist/lib/composables/useAsync.svelte.js +210 -0
- package/dist/lib/composables/useForm.svelte.d.ts +123 -0
- package/dist/lib/composables/useForm.svelte.js +245 -0
- package/dist/lib/index.d.ts +65 -0
- package/dist/lib/index.js +83 -0
- package/dist/lib/performance.d.ts +79 -0
- package/dist/lib/performance.js +170 -0
- package/dist/lib/schemas/auth.d.ts +410 -0
- package/dist/lib/schemas/auth.js +216 -0
- package/dist/lib/schemas/common.d.ts +267 -0
- package/dist/lib/schemas/common.js +268 -0
- package/dist/lib/schemas/index.d.ts +24 -0
- package/dist/lib/schemas/index.js +32 -0
- package/dist/lib/stores/sidebar.svelte.d.ts +25 -0
- package/dist/lib/stores/sidebar.svelte.js +38 -0
- package/dist/lib/stores/theme.svelte.d.ts +72 -0
- package/dist/lib/stores/theme.svelte.js +150 -0
- package/dist/lib/stores/toast.svelte.d.ts +62 -0
- package/dist/lib/stores/toast.svelte.js +93 -0
- package/dist/lib/types/components.d.ts +85 -0
- package/dist/lib/types/components.js +7 -0
- package/dist/lib/types/layout.d.ts +258 -0
- package/dist/lib/types/layout.js +7 -0
- package/dist/lib/utils.d.ts +6 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/validation.d.ts +101 -0
- package/dist/lib/validation.js +170 -0
- package/package.json +56 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header - Application header component
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Hamburger menu button for mobile
|
|
6
|
+
* - Customizable start and end slots
|
|
7
|
+
* - Optional navigation slot with uppercase styling
|
|
8
|
+
* - Responsive design
|
|
9
|
+
* - Stronger border styling to match brand guidelines
|
|
10
|
+
*/
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Whether to show hamburger menu button (mobile) */
|
|
14
|
+
showMenuButton?: boolean;
|
|
15
|
+
/** Callback when menu button is clicked */
|
|
16
|
+
onMenuClick?: () => void;
|
|
17
|
+
/** Custom start content (left side) */
|
|
18
|
+
start?: Snippet;
|
|
19
|
+
/** Navigation content (center/right on desktop, hidden on mobile) */
|
|
20
|
+
nav?: Snippet;
|
|
21
|
+
/** Custom end content (right side) */
|
|
22
|
+
end?: Snippet;
|
|
23
|
+
/** Use stronger/thicker border */
|
|
24
|
+
strongBorder?: boolean;
|
|
25
|
+
/** Additional classes */
|
|
26
|
+
class?: string;
|
|
27
|
+
}
|
|
28
|
+
declare const Header: import("svelte").Component<Props, {}, "">;
|
|
29
|
+
type Header = ReturnType<typeof Header>;
|
|
30
|
+
export default Header;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* PublicLayout - Layout for public-facing pages
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Header with navigation (uppercase bold links)
|
|
7
|
+
* - Footer with link sections
|
|
8
|
+
* - Main content area
|
|
9
|
+
* - Responsive design
|
|
10
|
+
* - Strong border options for brand consistency
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import type { NavItem, NavSection } from '../../types/layout.js';
|
|
14
|
+
import { cn } from '../../utils.js';
|
|
15
|
+
import AppShell from './AppShell.svelte';
|
|
16
|
+
import Footer from './Footer.svelte';
|
|
17
|
+
import LogoMain from '../LogoMain.svelte';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
/** Navigation items for header */
|
|
21
|
+
navigation?: NavItem[];
|
|
22
|
+
/** Whether to show footer */
|
|
23
|
+
showFooter?: boolean;
|
|
24
|
+
/** Footer link sections */
|
|
25
|
+
footerLinks?: NavSection[];
|
|
26
|
+
/** Footer copyright text */
|
|
27
|
+
copyright?: string;
|
|
28
|
+
/** Custom logo snippet */
|
|
29
|
+
logo?: Snippet;
|
|
30
|
+
/** Logo subtitle for default logo (e.g., "THEME", "MY HOME") */
|
|
31
|
+
logoSubtitle?: string;
|
|
32
|
+
/** Logo environment indicator for default logo */
|
|
33
|
+
logoEnvironment?: 'local' | 'dev' | 'demo';
|
|
34
|
+
/** Custom header end content */
|
|
35
|
+
headerEnd?: Snippet;
|
|
36
|
+
/** Use strong accent border on footer (10px top border) */
|
|
37
|
+
strongFooterBorder?: boolean;
|
|
38
|
+
/** Use dark footer variant */
|
|
39
|
+
darkFooter?: boolean;
|
|
40
|
+
/** Main content */
|
|
41
|
+
children: Snippet;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let {
|
|
45
|
+
navigation = [],
|
|
46
|
+
showFooter = true,
|
|
47
|
+
footerLinks = [],
|
|
48
|
+
copyright,
|
|
49
|
+
logo,
|
|
50
|
+
logoSubtitle,
|
|
51
|
+
logoEnvironment,
|
|
52
|
+
headerEnd,
|
|
53
|
+
strongFooterBorder = false,
|
|
54
|
+
darkFooter = false,
|
|
55
|
+
children,
|
|
56
|
+
}: Props = $props();
|
|
57
|
+
|
|
58
|
+
let mobileMenuOpen = $state(false);
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<AppShell>
|
|
62
|
+
<!-- Header -->
|
|
63
|
+
<header
|
|
64
|
+
class="sticky top-0 z-50 border-b border-black bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
|
|
65
|
+
>
|
|
66
|
+
<div class="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
|
|
67
|
+
<!-- Logo -->
|
|
68
|
+
<a href="/" class="flex items-center">
|
|
69
|
+
{#if logo}
|
|
70
|
+
{@render logo()}
|
|
71
|
+
{:else}
|
|
72
|
+
<LogoMain
|
|
73
|
+
variant="horizontal"
|
|
74
|
+
color="dark"
|
|
75
|
+
size="md"
|
|
76
|
+
subtitle={logoSubtitle}
|
|
77
|
+
environment={logoEnvironment}
|
|
78
|
+
/>
|
|
79
|
+
{/if}
|
|
80
|
+
</a>
|
|
81
|
+
|
|
82
|
+
<!-- Desktop Navigation -->
|
|
83
|
+
{#if navigation.length > 0}
|
|
84
|
+
<nav class="hidden md:flex md:items-center md:gap-6">
|
|
85
|
+
{#each navigation as item}
|
|
86
|
+
<a
|
|
87
|
+
href={item.href}
|
|
88
|
+
class={cn(
|
|
89
|
+
'text-sm font-bold uppercase transition-colors hover:text-primary',
|
|
90
|
+
item.active ? 'text-primary' : 'text-muted-foreground'
|
|
91
|
+
)}
|
|
92
|
+
target={item.external ? '_blank' : undefined}
|
|
93
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
94
|
+
>
|
|
95
|
+
{item.name}
|
|
96
|
+
</a>
|
|
97
|
+
{/each}
|
|
98
|
+
</nav>
|
|
99
|
+
{/if}
|
|
100
|
+
|
|
101
|
+
<!-- Header End (Actions) -->
|
|
102
|
+
<div class="flex items-center gap-4">
|
|
103
|
+
{#if headerEnd}
|
|
104
|
+
{@render headerEnd()}
|
|
105
|
+
{/if}
|
|
106
|
+
|
|
107
|
+
<!-- Mobile Menu Button -->
|
|
108
|
+
{#if navigation.length > 0}
|
|
109
|
+
<button
|
|
110
|
+
class="rounded-md p-2 hover:bg-accent md:hidden"
|
|
111
|
+
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
|
112
|
+
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
|
|
113
|
+
aria-expanded={mobileMenuOpen}
|
|
114
|
+
>
|
|
115
|
+
{#if mobileMenuOpen}
|
|
116
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
117
|
+
<path
|
|
118
|
+
stroke-linecap="round"
|
|
119
|
+
stroke-linejoin="round"
|
|
120
|
+
stroke-width="2"
|
|
121
|
+
d="M6 18L18 6M6 6l12 12"
|
|
122
|
+
/>
|
|
123
|
+
</svg>
|
|
124
|
+
{:else}
|
|
125
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
126
|
+
<path
|
|
127
|
+
stroke-linecap="round"
|
|
128
|
+
stroke-linejoin="round"
|
|
129
|
+
stroke-width="2"
|
|
130
|
+
d="M4 6h16M4 12h16M4 18h16"
|
|
131
|
+
/>
|
|
132
|
+
</svg>
|
|
133
|
+
{/if}
|
|
134
|
+
</button>
|
|
135
|
+
{/if}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<!-- Mobile Navigation -->
|
|
140
|
+
{#if mobileMenuOpen && navigation.length > 0}
|
|
141
|
+
<nav class="border-t border-black px-4 py-4 md:hidden">
|
|
142
|
+
<ul class="space-y-2">
|
|
143
|
+
{#each navigation as item}
|
|
144
|
+
<li>
|
|
145
|
+
<a
|
|
146
|
+
href={item.href}
|
|
147
|
+
class={cn(
|
|
148
|
+
'block rounded-md px-3 py-2 text-sm font-bold uppercase transition-colors',
|
|
149
|
+
item.active
|
|
150
|
+
? 'bg-primary/10 text-primary'
|
|
151
|
+
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
|
152
|
+
)}
|
|
153
|
+
target={item.external ? '_blank' : undefined}
|
|
154
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
155
|
+
onclick={() => (mobileMenuOpen = false)}
|
|
156
|
+
>
|
|
157
|
+
{item.name}
|
|
158
|
+
</a>
|
|
159
|
+
</li>
|
|
160
|
+
{/each}
|
|
161
|
+
</ul>
|
|
162
|
+
</nav>
|
|
163
|
+
{/if}
|
|
164
|
+
</header>
|
|
165
|
+
|
|
166
|
+
<!-- Main Content -->
|
|
167
|
+
<main id="main-content" class="flex-1">
|
|
168
|
+
{@render children()}
|
|
169
|
+
</main>
|
|
170
|
+
|
|
171
|
+
<!-- Footer -->
|
|
172
|
+
{#if showFooter}
|
|
173
|
+
<Footer
|
|
174
|
+
links={footerLinks}
|
|
175
|
+
{copyright}
|
|
176
|
+
strongBorder={strongFooterBorder}
|
|
177
|
+
variant={darkFooter ? 'dark' : 'default'}
|
|
178
|
+
/>
|
|
179
|
+
{/if}
|
|
180
|
+
</AppShell>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PublicLayout - Layout for public-facing pages
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Header with navigation (uppercase bold links)
|
|
6
|
+
* - Footer with link sections
|
|
7
|
+
* - Main content area
|
|
8
|
+
* - Responsive design
|
|
9
|
+
* - Strong border options for brand consistency
|
|
10
|
+
*/
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
12
|
+
import type { NavItem, NavSection } from '../../types/layout.js';
|
|
13
|
+
interface Props {
|
|
14
|
+
/** Navigation items for header */
|
|
15
|
+
navigation?: NavItem[];
|
|
16
|
+
/** Whether to show footer */
|
|
17
|
+
showFooter?: boolean;
|
|
18
|
+
/** Footer link sections */
|
|
19
|
+
footerLinks?: NavSection[];
|
|
20
|
+
/** Footer copyright text */
|
|
21
|
+
copyright?: string;
|
|
22
|
+
/** Custom logo snippet */
|
|
23
|
+
logo?: Snippet;
|
|
24
|
+
/** Logo subtitle for default logo (e.g., "THEME", "MY HOME") */
|
|
25
|
+
logoSubtitle?: string;
|
|
26
|
+
/** Logo environment indicator for default logo */
|
|
27
|
+
logoEnvironment?: 'local' | 'dev' | 'demo';
|
|
28
|
+
/** Custom header end content */
|
|
29
|
+
headerEnd?: Snippet;
|
|
30
|
+
/** Use strong accent border on footer (10px top border) */
|
|
31
|
+
strongFooterBorder?: boolean;
|
|
32
|
+
/** Use dark footer variant */
|
|
33
|
+
darkFooter?: boolean;
|
|
34
|
+
/** Main content */
|
|
35
|
+
children: Snippet;
|
|
36
|
+
}
|
|
37
|
+
declare const PublicLayout: import("svelte").Component<Props, {}, "">;
|
|
38
|
+
type PublicLayout = ReturnType<typeof PublicLayout>;
|
|
39
|
+
export default PublicLayout;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* QuickLinks - Quick navigation links section for sidebar
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Two display modes: 'list' (stacked with labels) and 'icons' (horizontal icons only)
|
|
7
|
+
* - Light/dark variant support
|
|
8
|
+
* - External link indicator
|
|
9
|
+
* - Custom icon renderer support
|
|
10
|
+
* - Accessible with proper ARIA labels
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import type { QuickLink } from '../../types/layout.js';
|
|
14
|
+
import { cn } from '../../utils.js';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Array of quick link items */
|
|
18
|
+
links: QuickLink[];
|
|
19
|
+
/** Display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
|
|
20
|
+
display?: 'list' | 'icons';
|
|
21
|
+
/** Visual variant - light (default) or dark */
|
|
22
|
+
variant?: 'light' | 'dark';
|
|
23
|
+
/** Custom icon renderer */
|
|
24
|
+
icon?: Snippet<[QuickLink]>;
|
|
25
|
+
/** Additional classes */
|
|
26
|
+
class?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let { links, display = 'list', variant = 'light', icon, class: className }: Props = $props();
|
|
30
|
+
|
|
31
|
+
const isLight = $derived(variant === 'light');
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
{#if links.length > 0}
|
|
35
|
+
<div
|
|
36
|
+
class={cn(
|
|
37
|
+
display === 'list'
|
|
38
|
+
? 'flex flex-col gap-1'
|
|
39
|
+
: 'flex flex-row items-center justify-center gap-2',
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
role="navigation"
|
|
43
|
+
aria-label="Quick links"
|
|
44
|
+
>
|
|
45
|
+
{#each links as link}
|
|
46
|
+
<a
|
|
47
|
+
href={link.href}
|
|
48
|
+
class={cn(
|
|
49
|
+
'flex items-center transition-colors',
|
|
50
|
+
// List mode styles
|
|
51
|
+
display === 'list' && 'gap-3 rounded-md px-3 py-2 text-sm',
|
|
52
|
+
display === 'list' &&
|
|
53
|
+
isLight &&
|
|
54
|
+
'text-muted-foreground hover:bg-accent hover:text-foreground',
|
|
55
|
+
display === 'list' &&
|
|
56
|
+
!isLight &&
|
|
57
|
+
'text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-foreground',
|
|
58
|
+
// Icon mode styles
|
|
59
|
+
display === 'icons' && 'rounded-md p-2',
|
|
60
|
+
display === 'icons' &&
|
|
61
|
+
isLight &&
|
|
62
|
+
'text-muted-foreground hover:bg-accent hover:text-foreground',
|
|
63
|
+
display === 'icons' &&
|
|
64
|
+
!isLight &&
|
|
65
|
+
'text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-foreground'
|
|
66
|
+
)}
|
|
67
|
+
target={link.external ? '_blank' : undefined}
|
|
68
|
+
rel={link.external ? 'noopener noreferrer' : undefined}
|
|
69
|
+
aria-label={display === 'icons' ? link.ariaLabel || link.label : undefined}
|
|
70
|
+
title={display === 'icons' ? link.label : undefined}
|
|
71
|
+
>
|
|
72
|
+
{#if icon}
|
|
73
|
+
<span class={cn('shrink-0', display === 'list' ? 'h-5 w-5' : 'h-5 w-5')}>
|
|
74
|
+
{@render icon(link)}
|
|
75
|
+
</span>
|
|
76
|
+
{:else if link.icon}
|
|
77
|
+
<!-- Default icon placeholder - apps should provide icon snippet -->
|
|
78
|
+
<span
|
|
79
|
+
class={cn(
|
|
80
|
+
'shrink-0 flex items-center justify-center',
|
|
81
|
+
display === 'list' ? 'h-5 w-5' : 'h-5 w-5'
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
85
|
+
<circle cx="12" cy="12" r="10" stroke-width="2" />
|
|
86
|
+
</svg>
|
|
87
|
+
</span>
|
|
88
|
+
{/if}
|
|
89
|
+
|
|
90
|
+
{#if display === 'list'}
|
|
91
|
+
<span class="flex-1 truncate">{link.label}</span>
|
|
92
|
+
|
|
93
|
+
{#if link.external}
|
|
94
|
+
<svg
|
|
95
|
+
class="h-4 w-4 shrink-0 opacity-50"
|
|
96
|
+
fill="none"
|
|
97
|
+
viewBox="0 0 24 24"
|
|
98
|
+
stroke="currentColor"
|
|
99
|
+
>
|
|
100
|
+
<path
|
|
101
|
+
stroke-linecap="round"
|
|
102
|
+
stroke-linejoin="round"
|
|
103
|
+
stroke-width="2"
|
|
104
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
105
|
+
/>
|
|
106
|
+
</svg>
|
|
107
|
+
{/if}
|
|
108
|
+
{/if}
|
|
109
|
+
</a>
|
|
110
|
+
{/each}
|
|
111
|
+
</div>
|
|
112
|
+
{/if}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuickLinks - Quick navigation links section for sidebar
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Two display modes: 'list' (stacked with labels) and 'icons' (horizontal icons only)
|
|
6
|
+
* - Light/dark variant support
|
|
7
|
+
* - External link indicator
|
|
8
|
+
* - Custom icon renderer support
|
|
9
|
+
* - Accessible with proper ARIA labels
|
|
10
|
+
*/
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
12
|
+
import type { QuickLink } from '../../types/layout.js';
|
|
13
|
+
interface Props {
|
|
14
|
+
/** Array of quick link items */
|
|
15
|
+
links: QuickLink[];
|
|
16
|
+
/** Display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
|
|
17
|
+
display?: 'list' | 'icons';
|
|
18
|
+
/** Visual variant - light (default) or dark */
|
|
19
|
+
variant?: 'light' | 'dark';
|
|
20
|
+
/** Custom icon renderer */
|
|
21
|
+
icon?: Snippet<[QuickLink]>;
|
|
22
|
+
/** Additional classes */
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
declare const QuickLinks: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
type QuickLinks = ReturnType<typeof QuickLinks>;
|
|
27
|
+
export default QuickLinks;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Sidebar - Responsive sidebar navigation component
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Collapsible on desktop
|
|
7
|
+
* - Slide-out drawer on mobile
|
|
8
|
+
* - Grouped navigation sections
|
|
9
|
+
* - Icon support via snippets
|
|
10
|
+
* - Active state highlighting
|
|
11
|
+
* - Light/dark variant support
|
|
12
|
+
* - Optional stronger border styling
|
|
13
|
+
* - Quick links section at bottom
|
|
14
|
+
*/
|
|
15
|
+
import type { Snippet } from 'svelte';
|
|
16
|
+
import type { NavSection, NavItem, QuickLink } from '../../types/layout.js';
|
|
17
|
+
import { cn } from '../../utils.js';
|
|
18
|
+
import LogoMain from '../LogoMain.svelte';
|
|
19
|
+
import QuickLinks from './QuickLinks.svelte';
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
/** Navigation sections */
|
|
23
|
+
navigation: NavSection[];
|
|
24
|
+
/** Whether sidebar is collapsed (desktop) */
|
|
25
|
+
collapsed?: boolean;
|
|
26
|
+
/** Whether currently on mobile */
|
|
27
|
+
isMobile?: boolean;
|
|
28
|
+
/** Whether mobile sidebar is open */
|
|
29
|
+
mobileOpen?: boolean;
|
|
30
|
+
/** Callback when mobile sidebar should close */
|
|
31
|
+
onClose?: () => void;
|
|
32
|
+
/** Custom logo snippet */
|
|
33
|
+
logo?: Snippet;
|
|
34
|
+
/** Custom icon renderer for nav items */
|
|
35
|
+
icon?: Snippet<[NavItem]>;
|
|
36
|
+
/** Quick links displayed at bottom of sidebar */
|
|
37
|
+
quickLinks?: QuickLink[];
|
|
38
|
+
/** Quick links display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
|
|
39
|
+
quickLinksDisplay?: 'list' | 'icons';
|
|
40
|
+
/** Custom icon renderer for quick links */
|
|
41
|
+
quickLinkIcon?: Snippet<[QuickLink]>;
|
|
42
|
+
/** Custom footer content */
|
|
43
|
+
footer?: Snippet;
|
|
44
|
+
/** Visual variant - light (default) or dark */
|
|
45
|
+
variant?: 'light' | 'dark';
|
|
46
|
+
/** Use stronger/thicker border */
|
|
47
|
+
strongBorder?: boolean;
|
|
48
|
+
/** Additional classes */
|
|
49
|
+
class?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let {
|
|
53
|
+
navigation,
|
|
54
|
+
collapsed = false,
|
|
55
|
+
isMobile = false,
|
|
56
|
+
mobileOpen = false,
|
|
57
|
+
onClose,
|
|
58
|
+
logo,
|
|
59
|
+
icon,
|
|
60
|
+
quickLinks,
|
|
61
|
+
quickLinksDisplay = 'list',
|
|
62
|
+
quickLinkIcon,
|
|
63
|
+
footer,
|
|
64
|
+
variant = 'light',
|
|
65
|
+
strongBorder = false,
|
|
66
|
+
class: className,
|
|
67
|
+
}: Props = $props();
|
|
68
|
+
|
|
69
|
+
// Variant-based styling
|
|
70
|
+
const isLight = $derived(variant === 'light');
|
|
71
|
+
|
|
72
|
+
const sidebarWidth = $derived(collapsed ? 'w-16' : 'w-64');
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<!-- Mobile Overlay: z-40 positions below sidebar (z-50) but above main content -->
|
|
76
|
+
{#if isMobile && mobileOpen}
|
|
77
|
+
<div
|
|
78
|
+
class="fixed inset-0 z-40 bg-black/50 lg:hidden"
|
|
79
|
+
onclick={() => onClose?.()}
|
|
80
|
+
onkeydown={(e) => e.key === 'Escape' && onClose?.()}
|
|
81
|
+
role="button"
|
|
82
|
+
tabindex="0"
|
|
83
|
+
aria-label="Close sidebar"
|
|
84
|
+
></div>
|
|
85
|
+
{/if}
|
|
86
|
+
|
|
87
|
+
<!-- Sidebar: z-50 positions above overlay and content, below modals (z-100+) -->
|
|
88
|
+
<aside
|
|
89
|
+
class={cn(
|
|
90
|
+
'fixed left-0 top-0 z-[var(--z-sidebar,50)] flex h-full flex-col transition-all duration-300',
|
|
91
|
+
// Variant-based background and text colors
|
|
92
|
+
isLight ? 'bg-background text-foreground' : 'bg-sidebar text-sidebar-foreground',
|
|
93
|
+
strongBorder ? 'border-r-2 border-black' : 'border-r border-black',
|
|
94
|
+
isMobile ? (mobileOpen ? 'translate-x-0 w-64' : '-translate-x-full w-64') : sidebarWidth,
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
aria-label="Main navigation"
|
|
98
|
+
>
|
|
99
|
+
<!-- Logo Section -->
|
|
100
|
+
<div
|
|
101
|
+
class={cn(
|
|
102
|
+
'flex h-16 items-center border-b border-black px-4',
|
|
103
|
+
collapsed && !isMobile && 'justify-center'
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
{#if logo}
|
|
107
|
+
{@render logo()}
|
|
108
|
+
{:else}
|
|
109
|
+
<LogoMain
|
|
110
|
+
variant={collapsed && !isMobile ? 'icon' : 'horizontal'}
|
|
111
|
+
color={isLight ? 'dark' : 'light'}
|
|
112
|
+
class={collapsed && !isMobile ? 'h-8 w-8' : 'h-8'}
|
|
113
|
+
/>
|
|
114
|
+
{/if}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<!-- Navigation -->
|
|
118
|
+
<nav class="flex-1 overflow-y-auto py-4">
|
|
119
|
+
{#each navigation as section}
|
|
120
|
+
<div class="mb-4">
|
|
121
|
+
{#if section.title && !collapsed}
|
|
122
|
+
<h2
|
|
123
|
+
class={cn(
|
|
124
|
+
'mb-2 px-4 text-xs font-semibold uppercase tracking-wider',
|
|
125
|
+
isLight ? 'text-muted-foreground' : 'text-sidebar-foreground/60'
|
|
126
|
+
)}
|
|
127
|
+
>
|
|
128
|
+
{section.title}
|
|
129
|
+
</h2>
|
|
130
|
+
{/if}
|
|
131
|
+
|
|
132
|
+
<ul class="space-y-1 px-2">
|
|
133
|
+
{#each section.items as item}
|
|
134
|
+
<li>
|
|
135
|
+
<a
|
|
136
|
+
href={item.href}
|
|
137
|
+
class={cn(
|
|
138
|
+
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
|
139
|
+
// Light variant styling
|
|
140
|
+
isLight &&
|
|
141
|
+
!item.active &&
|
|
142
|
+
'text-foreground hover:bg-accent hover:text-accent-foreground',
|
|
143
|
+
isLight && item.active && 'bg-primary/10 text-primary',
|
|
144
|
+
// Dark variant styling
|
|
145
|
+
!isLight &&
|
|
146
|
+
!item.active &&
|
|
147
|
+
'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
|
148
|
+
!isLight && item.active && 'bg-sidebar-primary text-sidebar-primary-foreground',
|
|
149
|
+
collapsed && !isMobile && 'justify-center px-2'
|
|
150
|
+
)}
|
|
151
|
+
target={item.external ? '_blank' : undefined}
|
|
152
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
153
|
+
aria-current={item.active ? 'page' : undefined}
|
|
154
|
+
title={collapsed && !isMobile ? item.name : undefined}
|
|
155
|
+
>
|
|
156
|
+
{#if icon}
|
|
157
|
+
<span class="h-5 w-5 shrink-0">
|
|
158
|
+
{@render icon(item)}
|
|
159
|
+
</span>
|
|
160
|
+
{:else if item.icon}
|
|
161
|
+
<span class="h-5 w-5 shrink-0 flex items-center justify-center">
|
|
162
|
+
<!-- Default icon placeholder - apps should provide icon snippet -->
|
|
163
|
+
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
164
|
+
<circle cx="12" cy="12" r="10" stroke-width="2" />
|
|
165
|
+
</svg>
|
|
166
|
+
</span>
|
|
167
|
+
{/if}
|
|
168
|
+
|
|
169
|
+
{#if !collapsed || isMobile}
|
|
170
|
+
<span class="flex-1 truncate">{item.name}</span>
|
|
171
|
+
|
|
172
|
+
{#if item.badge !== undefined}
|
|
173
|
+
<span
|
|
174
|
+
class="ml-auto rounded-full bg-primary px-2 py-0.5 text-xs font-medium text-primary-foreground"
|
|
175
|
+
>
|
|
176
|
+
{item.badge}
|
|
177
|
+
</span>
|
|
178
|
+
{/if}
|
|
179
|
+
|
|
180
|
+
{#if item.external}
|
|
181
|
+
<svg
|
|
182
|
+
class="h-4 w-4 opacity-50"
|
|
183
|
+
fill="none"
|
|
184
|
+
viewBox="0 0 24 24"
|
|
185
|
+
stroke="currentColor"
|
|
186
|
+
>
|
|
187
|
+
<path
|
|
188
|
+
stroke-linecap="round"
|
|
189
|
+
stroke-linejoin="round"
|
|
190
|
+
stroke-width="2"
|
|
191
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
192
|
+
/>
|
|
193
|
+
</svg>
|
|
194
|
+
{/if}
|
|
195
|
+
{/if}
|
|
196
|
+
</a>
|
|
197
|
+
</li>
|
|
198
|
+
{/each}
|
|
199
|
+
</ul>
|
|
200
|
+
</div>
|
|
201
|
+
{/each}
|
|
202
|
+
</nav>
|
|
203
|
+
|
|
204
|
+
<!-- Quick Links -->
|
|
205
|
+
{#if quickLinks && quickLinks.length > 0}
|
|
206
|
+
<div class={cn('border-t border-black px-2 py-3', collapsed && !isMobile && 'px-2')}>
|
|
207
|
+
<QuickLinks
|
|
208
|
+
links={quickLinks}
|
|
209
|
+
display={collapsed && !isMobile ? 'icons' : quickLinksDisplay}
|
|
210
|
+
{variant}
|
|
211
|
+
icon={quickLinkIcon}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
{/if}
|
|
215
|
+
|
|
216
|
+
<!-- Footer -->
|
|
217
|
+
{#if footer}
|
|
218
|
+
<div class="border-t border-black p-4">
|
|
219
|
+
{@render footer()}
|
|
220
|
+
</div>
|
|
221
|
+
{/if}
|
|
222
|
+
|
|
223
|
+
<!-- Mobile Close Button -->
|
|
224
|
+
{#if isMobile}
|
|
225
|
+
<button
|
|
226
|
+
class={cn(
|
|
227
|
+
'absolute right-2 top-2 rounded-md p-2',
|
|
228
|
+
isLight ? 'hover:bg-accent' : 'hover:bg-sidebar-accent'
|
|
229
|
+
)}
|
|
230
|
+
onclick={() => onClose?.()}
|
|
231
|
+
aria-label="Close sidebar"
|
|
232
|
+
>
|
|
233
|
+
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
234
|
+
<path
|
|
235
|
+
stroke-linecap="round"
|
|
236
|
+
stroke-linejoin="round"
|
|
237
|
+
stroke-width="2"
|
|
238
|
+
d="M6 18L18 6M6 6l12 12"
|
|
239
|
+
/>
|
|
240
|
+
</svg>
|
|
241
|
+
</button>
|
|
242
|
+
{/if}
|
|
243
|
+
</aside>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar - Responsive sidebar navigation component
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Collapsible on desktop
|
|
6
|
+
* - Slide-out drawer on mobile
|
|
7
|
+
* - Grouped navigation sections
|
|
8
|
+
* - Icon support via snippets
|
|
9
|
+
* - Active state highlighting
|
|
10
|
+
* - Light/dark variant support
|
|
11
|
+
* - Optional stronger border styling
|
|
12
|
+
* - Quick links section at bottom
|
|
13
|
+
*/
|
|
14
|
+
import type { Snippet } from 'svelte';
|
|
15
|
+
import type { NavSection, NavItem, QuickLink } from '../../types/layout.js';
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Navigation sections */
|
|
18
|
+
navigation: NavSection[];
|
|
19
|
+
/** Whether sidebar is collapsed (desktop) */
|
|
20
|
+
collapsed?: boolean;
|
|
21
|
+
/** Whether currently on mobile */
|
|
22
|
+
isMobile?: boolean;
|
|
23
|
+
/** Whether mobile sidebar is open */
|
|
24
|
+
mobileOpen?: boolean;
|
|
25
|
+
/** Callback when mobile sidebar should close */
|
|
26
|
+
onClose?: () => void;
|
|
27
|
+
/** Custom logo snippet */
|
|
28
|
+
logo?: Snippet;
|
|
29
|
+
/** Custom icon renderer for nav items */
|
|
30
|
+
icon?: Snippet<[NavItem]>;
|
|
31
|
+
/** Quick links displayed at bottom of sidebar */
|
|
32
|
+
quickLinks?: QuickLink[];
|
|
33
|
+
/** Quick links display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
|
|
34
|
+
quickLinksDisplay?: 'list' | 'icons';
|
|
35
|
+
/** Custom icon renderer for quick links */
|
|
36
|
+
quickLinkIcon?: Snippet<[QuickLink]>;
|
|
37
|
+
/** Custom footer content */
|
|
38
|
+
footer?: Snippet;
|
|
39
|
+
/** Visual variant - light (default) or dark */
|
|
40
|
+
variant?: 'light' | 'dark';
|
|
41
|
+
/** Use stronger/thicker border */
|
|
42
|
+
strongBorder?: boolean;
|
|
43
|
+
/** Additional classes */
|
|
44
|
+
class?: string;
|
|
45
|
+
}
|
|
46
|
+
declare const Sidebar: import("svelte").Component<Props, {}, "">;
|
|
47
|
+
type Sidebar = ReturnType<typeof Sidebar>;
|
|
48
|
+
export default Sidebar;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composables - Reusable Svelte 5 runes-based state management utilities
|
|
3
|
+
*
|
|
4
|
+
* These composables provide common patterns for form handling and async operations,
|
|
5
|
+
* built with Svelte 5 runes for optimal reactivity.
|
|
6
|
+
*/
|
|
7
|
+
export { useForm, type UseFormOptions, type UseFormReturn, type FieldError, type FormState, type InferFormData, } from './useForm.svelte.js';
|
|
8
|
+
export { useAsync, runAsync, type UseAsyncOptions, type UseAsyncReturn, } from './useAsync.svelte.js';
|