@classic-homes/theme-svelte 0.1.4 → 0.1.6
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/dist/lib/components/CardHeader.svelte +22 -2
- package/dist/lib/components/CardHeader.svelte.d.ts +5 -4
- package/dist/lib/components/Combobox.svelte +187 -0
- package/dist/lib/components/Combobox.svelte.d.ts +38 -0
- package/dist/lib/components/DateTimePicker.svelte +415 -0
- package/dist/lib/components/DateTimePicker.svelte.d.ts +31 -0
- package/dist/lib/components/HeaderSearch.svelte +340 -0
- package/dist/lib/components/HeaderSearch.svelte.d.ts +37 -0
- package/dist/lib/components/MultiSelect.svelte +244 -0
- package/dist/lib/components/MultiSelect.svelte.d.ts +40 -0
- package/dist/lib/components/NumberInput.svelte +205 -0
- package/dist/lib/components/NumberInput.svelte.d.ts +33 -0
- package/dist/lib/components/OTPInput.svelte +213 -0
- package/dist/lib/components/OTPInput.svelte.d.ts +23 -0
- package/dist/lib/components/PageHeader.svelte +6 -0
- package/dist/lib/components/PageHeader.svelte.d.ts +1 -1
- package/dist/lib/components/RadioGroup.svelte +124 -0
- package/dist/lib/components/RadioGroup.svelte.d.ts +31 -0
- package/dist/lib/components/Signature.svelte +1070 -0
- package/dist/lib/components/Signature.svelte.d.ts +74 -0
- package/dist/lib/components/Slider.svelte +136 -0
- package/dist/lib/components/Slider.svelte.d.ts +30 -0
- package/dist/lib/components/layout/AuthLayout.svelte +133 -0
- package/dist/lib/components/layout/AuthLayout.svelte.d.ts +48 -0
- package/dist/lib/components/layout/DashboardLayout.svelte +100 -74
- package/dist/lib/components/layout/DashboardLayout.svelte.d.ts +17 -10
- package/dist/lib/components/layout/ErrorLayout.svelte +206 -0
- package/dist/lib/components/layout/ErrorLayout.svelte.d.ts +52 -0
- package/dist/lib/components/layout/FormPageLayout.svelte +2 -8
- package/dist/lib/components/layout/Header.svelte +232 -41
- package/dist/lib/components/layout/Header.svelte.d.ts +71 -5
- package/dist/lib/components/layout/PublicLayout.svelte +54 -80
- package/dist/lib/components/layout/PublicLayout.svelte.d.ts +3 -1
- package/dist/lib/components/layout/QuickLinks.svelte +49 -29
- package/dist/lib/components/layout/QuickLinks.svelte.d.ts +4 -2
- package/dist/lib/components/layout/Sidebar.svelte +345 -86
- package/dist/lib/components/layout/Sidebar.svelte.d.ts +12 -0
- package/dist/lib/components/layout/sidebar/SidebarFlyout.svelte +182 -0
- package/dist/lib/components/layout/sidebar/SidebarFlyout.svelte.d.ts +18 -0
- package/dist/lib/components/layout/sidebar/SidebarNavItem.svelte +378 -0
- package/dist/lib/components/layout/sidebar/SidebarNavItem.svelte.d.ts +25 -0
- package/dist/lib/components/layout/sidebar/SidebarSearch.svelte +121 -0
- package/dist/lib/components/layout/sidebar/SidebarSearch.svelte.d.ts +17 -0
- package/dist/lib/components/layout/sidebar/SidebarSection.svelte +144 -0
- package/dist/lib/components/layout/sidebar/SidebarSection.svelte.d.ts +25 -0
- package/dist/lib/components/layout/sidebar/index.d.ts +10 -0
- package/dist/lib/components/layout/sidebar/index.js +10 -0
- package/dist/lib/index.d.ts +13 -2
- package/dist/lib/index.js +11 -0
- package/dist/lib/schemas/auth.d.ts +6 -6
- package/dist/lib/stores/sidebar.svelte.d.ts +54 -0
- package/dist/lib/stores/sidebar.svelte.js +171 -1
- package/dist/lib/types/components.d.ts +105 -0
- package/dist/lib/types/layout.d.ts +203 -3
- package/package.json +1 -1
|
@@ -10,15 +10,8 @@
|
|
|
10
10
|
* - Integrated with sidebar store for state management
|
|
11
11
|
*/
|
|
12
12
|
import type { Snippet } from 'svelte';
|
|
13
|
-
import type { NavSection, NavItem, User, QuickLink } from '../../types/layout.js';
|
|
14
|
-
|
|
15
|
-
/** Link label (e.g., "Back to Dashboard") */
|
|
16
|
-
label: string;
|
|
17
|
-
/** Link URL */
|
|
18
|
-
href: string;
|
|
19
|
-
/** Icon name for the icon snippet */
|
|
20
|
-
icon?: string;
|
|
21
|
-
}
|
|
13
|
+
import type { NavSection, NavItem, User, QuickLink, BackLink, HeaderSearchConfig } from '../../types/layout.js';
|
|
14
|
+
type Breakpoint = 'sm' | 'md' | 'lg';
|
|
22
15
|
interface Props {
|
|
23
16
|
/** Navigation sections for sidebar */
|
|
24
17
|
navigation: NavSection[];
|
|
@@ -42,10 +35,12 @@ interface Props {
|
|
|
42
35
|
icon?: Snippet<[NavItem]>;
|
|
43
36
|
/** Quick links displayed at bottom of sidebar */
|
|
44
37
|
quickLinks?: QuickLink[];
|
|
45
|
-
/** Quick links display mode: 'list' for stacked with labels, 'icons' for
|
|
38
|
+
/** Quick links display mode: 'list' for stacked with labels, 'icons' for stacked centered icons only */
|
|
46
39
|
quickLinksDisplay?: 'list' | 'icons';
|
|
47
40
|
/** Custom icon renderer for quick links */
|
|
48
41
|
quickLinkIcon?: Snippet<[QuickLink]>;
|
|
42
|
+
/** Callback when a navigation item is clicked */
|
|
43
|
+
onNavigate?: (item: NavItem) => void;
|
|
49
44
|
/** Custom content at start of header (after toggle button) */
|
|
50
45
|
headerStart?: Snippet;
|
|
51
46
|
/** Custom content at end of header */
|
|
@@ -54,6 +49,18 @@ interface Props {
|
|
|
54
49
|
userMenu?: Snippet<[User]>;
|
|
55
50
|
/** Sidebar footer content */
|
|
56
51
|
sidebarFooter?: Snippet;
|
|
52
|
+
/** Sidebar width when expanded in pixels (default: 256) */
|
|
53
|
+
expandedWidth?: number;
|
|
54
|
+
/** Sidebar width when collapsed in pixels (default: 64) */
|
|
55
|
+
collapsedWidth?: number;
|
|
56
|
+
/** Enable search/filter input for navigation */
|
|
57
|
+
searchable?: boolean;
|
|
58
|
+
/** Placeholder text for search input */
|
|
59
|
+
searchPlaceholder?: string;
|
|
60
|
+
/** Header search configuration */
|
|
61
|
+
headerSearch?: HeaderSearchConfig;
|
|
62
|
+
/** Breakpoint at which to switch between mobile and desktop layouts */
|
|
63
|
+
mobileBreakpoint?: Breakpoint;
|
|
57
64
|
/** Main content */
|
|
58
65
|
children: Snippet;
|
|
59
66
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ErrorLayout - Layout for error pages
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Centered error message display
|
|
7
|
+
* - Large status code or custom illustration
|
|
8
|
+
* - Configurable title and description
|
|
9
|
+
* - Default messages for common HTTP status codes
|
|
10
|
+
* - Action buttons (Go Back, Go Home)
|
|
11
|
+
* - Optional logo and footer
|
|
12
|
+
* - Responsive design
|
|
13
|
+
* - Composes AppShell for consistent base structure
|
|
14
|
+
*
|
|
15
|
+
* Use this layout for: 404 Not Found, 500 Server Error, 403 Forbidden,
|
|
16
|
+
* maintenance pages, and other error states.
|
|
17
|
+
*/
|
|
18
|
+
import type { Snippet } from 'svelte';
|
|
19
|
+
import { cn } from '../../utils.js';
|
|
20
|
+
import AppShell from './AppShell.svelte';
|
|
21
|
+
import LogoMain from '../LogoMain.svelte';
|
|
22
|
+
import Button from '../Button.svelte';
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
/** HTTP status code (404, 500, etc.) */
|
|
26
|
+
statusCode?: number;
|
|
27
|
+
/** Custom title (overrides default for status code) */
|
|
28
|
+
title?: string;
|
|
29
|
+
/** Error description/message */
|
|
30
|
+
description?: string;
|
|
31
|
+
/** Show logo at top */
|
|
32
|
+
showLogo?: boolean;
|
|
33
|
+
/** Custom logo snippet */
|
|
34
|
+
logo?: Snippet;
|
|
35
|
+
/** Show "Go Home" button */
|
|
36
|
+
showHomeButton?: boolean;
|
|
37
|
+
/** Home button URL */
|
|
38
|
+
homeUrl?: string;
|
|
39
|
+
/** Home button text */
|
|
40
|
+
homeText?: string;
|
|
41
|
+
/** Show "Go Back" button */
|
|
42
|
+
showBackButton?: boolean;
|
|
43
|
+
/** Go back button text */
|
|
44
|
+
backText?: string;
|
|
45
|
+
/** Custom illustration/icon snippet (replaces status code display) */
|
|
46
|
+
illustration?: Snippet;
|
|
47
|
+
/** Additional action buttons or content */
|
|
48
|
+
actions?: Snippet;
|
|
49
|
+
/** Footer content */
|
|
50
|
+
footer?: Snippet;
|
|
51
|
+
/** Additional classes for the container */
|
|
52
|
+
class?: string;
|
|
53
|
+
/** Main content (custom error content, replaces default display) */
|
|
54
|
+
children?: Snippet;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let {
|
|
58
|
+
statusCode,
|
|
59
|
+
title,
|
|
60
|
+
description,
|
|
61
|
+
showLogo = true,
|
|
62
|
+
logo,
|
|
63
|
+
showHomeButton = true,
|
|
64
|
+
homeUrl = '/',
|
|
65
|
+
homeText = 'Go Home',
|
|
66
|
+
showBackButton = true,
|
|
67
|
+
backText = 'Go Back',
|
|
68
|
+
illustration,
|
|
69
|
+
actions,
|
|
70
|
+
footer,
|
|
71
|
+
class: className,
|
|
72
|
+
children,
|
|
73
|
+
}: Props = $props();
|
|
74
|
+
|
|
75
|
+
// Default messages by status code
|
|
76
|
+
const defaultMessages: Record<number, { title: string; description: string }> = {
|
|
77
|
+
400: {
|
|
78
|
+
title: 'Bad Request',
|
|
79
|
+
description: 'The request could not be understood. Please check your input and try again.',
|
|
80
|
+
},
|
|
81
|
+
401: {
|
|
82
|
+
title: 'Unauthorized',
|
|
83
|
+
description: 'Please sign in to access this page.',
|
|
84
|
+
},
|
|
85
|
+
403: {
|
|
86
|
+
title: 'Access Denied',
|
|
87
|
+
description: "You don't have permission to access this page.",
|
|
88
|
+
},
|
|
89
|
+
404: {
|
|
90
|
+
title: 'Page Not Found',
|
|
91
|
+
description: "The page you're looking for doesn't exist or has been moved.",
|
|
92
|
+
},
|
|
93
|
+
500: {
|
|
94
|
+
title: 'Server Error',
|
|
95
|
+
description: 'Something went wrong on our end. Please try again later.',
|
|
96
|
+
},
|
|
97
|
+
502: {
|
|
98
|
+
title: 'Bad Gateway',
|
|
99
|
+
description: 'We received an invalid response from the server. Please try again later.',
|
|
100
|
+
},
|
|
101
|
+
503: {
|
|
102
|
+
title: 'Service Unavailable',
|
|
103
|
+
description: "We're temporarily offline for maintenance. Please check back soon.",
|
|
104
|
+
},
|
|
105
|
+
504: {
|
|
106
|
+
title: 'Gateway Timeout',
|
|
107
|
+
description: 'The server took too long to respond. Please try again later.',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const effectiveTitle = $derived(
|
|
112
|
+
title || (statusCode && defaultMessages[statusCode]?.title) || 'Something Went Wrong'
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const effectiveDescription = $derived(
|
|
116
|
+
description ||
|
|
117
|
+
(statusCode && defaultMessages[statusCode]?.description) ||
|
|
118
|
+
'An unexpected error occurred. Please try again.'
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
function goBack() {
|
|
122
|
+
if (typeof window !== 'undefined') {
|
|
123
|
+
window.history.back();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<AppShell>
|
|
129
|
+
<div
|
|
130
|
+
class={cn(
|
|
131
|
+
'flex h-screen flex-col items-center justify-center overflow-auto bg-content-bg px-4 py-8 sm:px-6 lg:px-8',
|
|
132
|
+
className
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
135
|
+
<!-- Logo -->
|
|
136
|
+
{#if showLogo}
|
|
137
|
+
<div class="mb-6">
|
|
138
|
+
{#if logo}
|
|
139
|
+
{@render logo()}
|
|
140
|
+
{:else}
|
|
141
|
+
<a href="/" class="block">
|
|
142
|
+
<LogoMain variant="horizontal" color="dark" size="md" />
|
|
143
|
+
</a>
|
|
144
|
+
{/if}
|
|
145
|
+
</div>
|
|
146
|
+
{/if}
|
|
147
|
+
|
|
148
|
+
<!-- Main Content -->
|
|
149
|
+
<main id="main-content" class="w-full max-w-md text-center">
|
|
150
|
+
{#if children}
|
|
151
|
+
{@render children()}
|
|
152
|
+
{:else}
|
|
153
|
+
<!-- Status Code or Illustration -->
|
|
154
|
+
{#if illustration}
|
|
155
|
+
<div class="mb-6">
|
|
156
|
+
{@render illustration()}
|
|
157
|
+
</div>
|
|
158
|
+
{:else if statusCode}
|
|
159
|
+
<div class="mb-6">
|
|
160
|
+
<span class="text-8xl font-bold text-muted-foreground/30 sm:text-9xl">
|
|
161
|
+
{statusCode}
|
|
162
|
+
</span>
|
|
163
|
+
</div>
|
|
164
|
+
{/if}
|
|
165
|
+
|
|
166
|
+
<!-- Title -->
|
|
167
|
+
<h1 class="mb-4 text-2xl font-bold text-foreground sm:text-3xl">
|
|
168
|
+
{effectiveTitle}
|
|
169
|
+
</h1>
|
|
170
|
+
|
|
171
|
+
<!-- Description -->
|
|
172
|
+
<p class="mb-8 text-muted-foreground">
|
|
173
|
+
{effectiveDescription}
|
|
174
|
+
</p>
|
|
175
|
+
|
|
176
|
+
<!-- Action Buttons -->
|
|
177
|
+
<div class="flex flex-col items-center justify-center gap-3 sm:flex-row">
|
|
178
|
+
{#if showBackButton}
|
|
179
|
+
<Button variant="outline" onclick={goBack}>
|
|
180
|
+
{backText}
|
|
181
|
+
</Button>
|
|
182
|
+
{/if}
|
|
183
|
+
{#if showHomeButton}
|
|
184
|
+
<Button href={homeUrl}>
|
|
185
|
+
{homeText}
|
|
186
|
+
</Button>
|
|
187
|
+
{/if}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- Additional Actions -->
|
|
191
|
+
{#if actions}
|
|
192
|
+
<div class="mt-6">
|
|
193
|
+
{@render actions()}
|
|
194
|
+
</div>
|
|
195
|
+
{/if}
|
|
196
|
+
{/if}
|
|
197
|
+
</main>
|
|
198
|
+
|
|
199
|
+
<!-- Footer -->
|
|
200
|
+
{#if footer}
|
|
201
|
+
<footer class="mt-8">
|
|
202
|
+
{@render footer()}
|
|
203
|
+
</footer>
|
|
204
|
+
{/if}
|
|
205
|
+
</div>
|
|
206
|
+
</AppShell>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ErrorLayout - Layout for error pages
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Centered error message display
|
|
6
|
+
* - Large status code or custom illustration
|
|
7
|
+
* - Configurable title and description
|
|
8
|
+
* - Default messages for common HTTP status codes
|
|
9
|
+
* - Action buttons (Go Back, Go Home)
|
|
10
|
+
* - Optional logo and footer
|
|
11
|
+
* - Responsive design
|
|
12
|
+
* - Composes AppShell for consistent base structure
|
|
13
|
+
*
|
|
14
|
+
* Use this layout for: 404 Not Found, 500 Server Error, 403 Forbidden,
|
|
15
|
+
* maintenance pages, and other error states.
|
|
16
|
+
*/
|
|
17
|
+
import type { Snippet } from 'svelte';
|
|
18
|
+
interface Props {
|
|
19
|
+
/** HTTP status code (404, 500, etc.) */
|
|
20
|
+
statusCode?: number;
|
|
21
|
+
/** Custom title (overrides default for status code) */
|
|
22
|
+
title?: string;
|
|
23
|
+
/** Error description/message */
|
|
24
|
+
description?: string;
|
|
25
|
+
/** Show logo at top */
|
|
26
|
+
showLogo?: boolean;
|
|
27
|
+
/** Custom logo snippet */
|
|
28
|
+
logo?: Snippet;
|
|
29
|
+
/** Show "Go Home" button */
|
|
30
|
+
showHomeButton?: boolean;
|
|
31
|
+
/** Home button URL */
|
|
32
|
+
homeUrl?: string;
|
|
33
|
+
/** Home button text */
|
|
34
|
+
homeText?: string;
|
|
35
|
+
/** Show "Go Back" button */
|
|
36
|
+
showBackButton?: boolean;
|
|
37
|
+
/** Go back button text */
|
|
38
|
+
backText?: string;
|
|
39
|
+
/** Custom illustration/icon snippet (replaces status code display) */
|
|
40
|
+
illustration?: Snippet;
|
|
41
|
+
/** Additional action buttons or content */
|
|
42
|
+
actions?: Snippet;
|
|
43
|
+
/** Footer content */
|
|
44
|
+
footer?: Snippet;
|
|
45
|
+
/** Additional classes for the container */
|
|
46
|
+
class?: string;
|
|
47
|
+
/** Main content (custom error content, replaces default display) */
|
|
48
|
+
children?: Snippet;
|
|
49
|
+
}
|
|
50
|
+
declare const ErrorLayout: import("svelte").Component<Props, {}, "">;
|
|
51
|
+
type ErrorLayout = ReturnType<typeof ErrorLayout>;
|
|
52
|
+
export default ErrorLayout;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { Snippet } from 'svelte';
|
|
15
15
|
import { cn } from '../../utils.js';
|
|
16
|
+
import PageHeader from '../PageHeader.svelte';
|
|
16
17
|
|
|
17
18
|
interface Props {
|
|
18
19
|
/** Page title (h1) */
|
|
@@ -53,14 +54,7 @@
|
|
|
53
54
|
<div class={cn('min-h-screen py-8 sm:py-12', sageBackground && 'bg-brand-core-4-tint', className)}>
|
|
54
55
|
<div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
|
55
56
|
<!-- Header Section -->
|
|
56
|
-
<
|
|
57
|
-
<h1 class="text-3xl font-light tracking-tight text-brand-core-2 md:text-4xl">
|
|
58
|
-
{title}
|
|
59
|
-
</h1>
|
|
60
|
-
<p class="mt-3 text-base text-gray-600 md:text-lg">
|
|
61
|
-
{description}
|
|
62
|
-
</p>
|
|
63
|
-
</header>
|
|
57
|
+
<PageHeader {title} subtitle={description} variant="form" />
|
|
64
58
|
|
|
65
59
|
<!-- Notices Section -->
|
|
66
60
|
{#if showNotices && notices}
|
|
@@ -1,94 +1,285 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* Header -
|
|
3
|
+
* Header - Unified application header component
|
|
4
|
+
*
|
|
5
|
+
* A flexible header component that supports both dashboard and public layouts.
|
|
4
6
|
*
|
|
5
7
|
* Features:
|
|
6
8
|
* - Hamburger menu button for mobile
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
9
|
+
* - Collapse/expand button for desktop sidebar
|
|
10
|
+
* - Customizable start, nav, and end slots
|
|
11
|
+
* - Mobile navigation drawer support
|
|
12
|
+
* - Optional page title
|
|
13
|
+
* - Backdrop blur support
|
|
14
|
+
* - Configurable responsive breakpoint (sm, md, lg)
|
|
15
|
+
* - Responsive design with container width options
|
|
10
16
|
* - Stronger border styling to match brand guidelines
|
|
17
|
+
*
|
|
18
|
+
* @example Dashboard layout usage:
|
|
19
|
+
* ```svelte
|
|
20
|
+
* <Header
|
|
21
|
+
* showMenuButton={isMobile}
|
|
22
|
+
* menuOpen={sidebarOpen}
|
|
23
|
+
* onMenuClick={() => toggleSidebar()}
|
|
24
|
+
* showCollapseButton={!isMobile}
|
|
25
|
+
* sidebarCollapsed={!sidebarOpen}
|
|
26
|
+
* onCollapseClick={() => toggleSidebar()}
|
|
27
|
+
* title="Dashboard"
|
|
28
|
+
* />
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example Public layout usage:
|
|
32
|
+
* ```svelte
|
|
33
|
+
* <Header
|
|
34
|
+
* showMenuButton={true}
|
|
35
|
+
* menuOpen={mobileMenuOpen}
|
|
36
|
+
* onMenuClick={() => mobileMenuOpen = !mobileMenuOpen}
|
|
37
|
+
* mobileBreakpoint="md"
|
|
38
|
+
* backdropBlur
|
|
39
|
+
* elevated
|
|
40
|
+
* maxWidth="7xl"
|
|
41
|
+
* >
|
|
42
|
+
* {#snippet start()}
|
|
43
|
+
* <a href="/"><Logo /></a>
|
|
44
|
+
* {/snippet}
|
|
45
|
+
* {#snippet nav()}
|
|
46
|
+
* <NavLinks />
|
|
47
|
+
* {/snippet}
|
|
48
|
+
* {#snippet mobileNav()}
|
|
49
|
+
* <MobileNavDrawer />
|
|
50
|
+
* {/snippet}
|
|
51
|
+
* </Header>
|
|
52
|
+
* ```
|
|
11
53
|
*/
|
|
12
54
|
import type { Snippet } from 'svelte';
|
|
55
|
+
import type { HeaderSearchConfig } from '../../types/layout.js';
|
|
13
56
|
import { cn } from '../../utils.js';
|
|
57
|
+
import HeaderSearch from '../HeaderSearch.svelte';
|
|
58
|
+
|
|
59
|
+
type Breakpoint = 'sm' | 'md' | 'lg';
|
|
14
60
|
|
|
15
61
|
interface Props {
|
|
16
62
|
/** Whether to show hamburger menu button (mobile) */
|
|
17
63
|
showMenuButton?: boolean;
|
|
64
|
+
/** Whether the mobile menu is currently open (for aria-expanded) */
|
|
65
|
+
menuOpen?: boolean;
|
|
18
66
|
/** Callback when menu button is clicked */
|
|
19
67
|
onMenuClick?: () => void;
|
|
20
|
-
/**
|
|
68
|
+
/** Whether to show collapse/expand button (desktop sidebar toggle) */
|
|
69
|
+
showCollapseButton?: boolean;
|
|
70
|
+
/** Whether the sidebar is collapsed (for aria-expanded and icon direction) */
|
|
71
|
+
sidebarCollapsed?: boolean;
|
|
72
|
+
/** Callback when collapse button is clicked */
|
|
73
|
+
onCollapseClick?: () => void;
|
|
74
|
+
/** Page title displayed in header */
|
|
75
|
+
title?: string;
|
|
76
|
+
/** Custom start content (left side, after menu/collapse buttons) */
|
|
21
77
|
start?: Snippet;
|
|
22
78
|
/** Navigation content (center/right on desktop, hidden on mobile) */
|
|
23
79
|
nav?: Snippet;
|
|
24
80
|
/** Custom end content (right side) */
|
|
25
81
|
end?: Snippet;
|
|
82
|
+
/** Mobile navigation drawer content (appears below header bar when menu is open) */
|
|
83
|
+
mobileNav?: Snippet;
|
|
84
|
+
/** Breakpoint at which to switch between mobile and desktop layouts */
|
|
85
|
+
mobileBreakpoint?: Breakpoint;
|
|
26
86
|
/** Use stronger/thicker border */
|
|
27
87
|
strongBorder?: boolean;
|
|
88
|
+
/** Enable backdrop blur effect */
|
|
89
|
+
backdropBlur?: boolean;
|
|
90
|
+
/** Use elevated z-index (z-50 instead of z-30) */
|
|
91
|
+
elevated?: boolean;
|
|
92
|
+
/** Constrain content to max-width container */
|
|
93
|
+
maxWidth?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl';
|
|
94
|
+
/** Search configuration */
|
|
95
|
+
search?: HeaderSearchConfig;
|
|
28
96
|
/** Additional classes */
|
|
29
97
|
class?: string;
|
|
30
98
|
}
|
|
31
99
|
|
|
32
100
|
let {
|
|
33
101
|
showMenuButton = false,
|
|
102
|
+
menuOpen = false,
|
|
34
103
|
onMenuClick,
|
|
104
|
+
showCollapseButton = false,
|
|
105
|
+
sidebarCollapsed = false,
|
|
106
|
+
onCollapseClick,
|
|
107
|
+
title,
|
|
35
108
|
start,
|
|
36
109
|
nav,
|
|
37
110
|
end,
|
|
111
|
+
mobileNav,
|
|
112
|
+
mobileBreakpoint = 'lg',
|
|
38
113
|
strongBorder = false,
|
|
114
|
+
backdropBlur = false,
|
|
115
|
+
elevated = false,
|
|
116
|
+
maxWidth = 'none',
|
|
117
|
+
search,
|
|
39
118
|
class: className,
|
|
40
119
|
}: Props = $props();
|
|
120
|
+
|
|
121
|
+
// Responsive class mappings based on breakpoint
|
|
122
|
+
const breakpointClasses = {
|
|
123
|
+
sm: {
|
|
124
|
+
menuButtonHide: 'sm:hidden',
|
|
125
|
+
collapseButtonShow: 'hidden sm:block',
|
|
126
|
+
navShow: 'hidden sm:flex',
|
|
127
|
+
mobileNavHide: 'sm:hidden',
|
|
128
|
+
},
|
|
129
|
+
md: {
|
|
130
|
+
menuButtonHide: 'md:hidden',
|
|
131
|
+
collapseButtonShow: 'hidden md:block',
|
|
132
|
+
navShow: 'hidden md:flex',
|
|
133
|
+
mobileNavHide: 'md:hidden',
|
|
134
|
+
},
|
|
135
|
+
lg: {
|
|
136
|
+
menuButtonHide: 'lg:hidden',
|
|
137
|
+
collapseButtonShow: 'hidden lg:block',
|
|
138
|
+
navShow: 'hidden lg:flex',
|
|
139
|
+
mobileNavHide: 'lg:hidden',
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const maxWidthClasses: Record<string, string> = {
|
|
144
|
+
none: '',
|
|
145
|
+
sm: 'max-w-sm',
|
|
146
|
+
md: 'max-w-md',
|
|
147
|
+
lg: 'max-w-lg',
|
|
148
|
+
xl: 'max-w-xl',
|
|
149
|
+
'2xl': 'max-w-2xl',
|
|
150
|
+
'3xl': 'max-w-3xl',
|
|
151
|
+
'4xl': 'max-w-4xl',
|
|
152
|
+
'5xl': 'max-w-5xl',
|
|
153
|
+
'6xl': 'max-w-6xl',
|
|
154
|
+
'7xl': 'max-w-7xl',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const bp = $derived(breakpointClasses[mobileBreakpoint]);
|
|
41
158
|
</script>
|
|
42
159
|
|
|
43
160
|
<!--
|
|
44
161
|
Header Layout Dimensions:
|
|
45
162
|
- h-16 (64px): Standard header height, matches sidebar logo section
|
|
46
163
|
- z-30: Below sidebar (z-50) and overlays (z-40), above main content
|
|
47
|
-
-
|
|
164
|
+
- z-50: Elevated mode for public layouts
|
|
165
|
+
- Responsive padding adapts to breakpoint and container width settings
|
|
48
166
|
-->
|
|
49
167
|
<header
|
|
50
168
|
class={cn(
|
|
51
|
-
'sticky top-0
|
|
52
|
-
|
|
169
|
+
'sticky top-0',
|
|
170
|
+
elevated ? 'z-50' : 'z-30',
|
|
171
|
+
backdropBlur
|
|
172
|
+
? 'bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60'
|
|
173
|
+
: 'bg-background',
|
|
53
174
|
className
|
|
54
175
|
)}
|
|
55
176
|
>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
177
|
+
<!-- Main header bar wrapper - border spans full width, h-16 includes border for sidebar alignment -->
|
|
178
|
+
<div class={cn('h-16', strongBorder ? 'border-b-2 border-black' : 'border-b border-black')}>
|
|
179
|
+
<div
|
|
180
|
+
class={cn(
|
|
181
|
+
'flex h-full items-center justify-between px-4 sm:px-6 lg:px-8',
|
|
182
|
+
maxWidth !== 'none' && 'mx-auto',
|
|
183
|
+
maxWidthClasses[maxWidth]
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
<div class="flex items-center gap-4">
|
|
187
|
+
<!-- Mobile Menu Button -->
|
|
188
|
+
{#if showMenuButton}
|
|
189
|
+
<button
|
|
190
|
+
class={cn(
|
|
191
|
+
'rounded-md p-2 hover:bg-accent hover:text-accent-foreground',
|
|
192
|
+
bp.menuButtonHide
|
|
193
|
+
)}
|
|
194
|
+
onclick={() => onMenuClick?.()}
|
|
195
|
+
aria-label={menuOpen ? 'Close menu' : 'Open menu'}
|
|
196
|
+
aria-expanded={menuOpen}
|
|
197
|
+
>
|
|
198
|
+
{#if menuOpen}
|
|
199
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
200
|
+
<path
|
|
201
|
+
stroke-linecap="round"
|
|
202
|
+
stroke-linejoin="round"
|
|
203
|
+
stroke-width="2"
|
|
204
|
+
d="M6 18L18 6M6 6l12 12"
|
|
205
|
+
/>
|
|
206
|
+
</svg>
|
|
207
|
+
{:else}
|
|
208
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
209
|
+
<path
|
|
210
|
+
stroke-linecap="round"
|
|
211
|
+
stroke-linejoin="round"
|
|
212
|
+
stroke-width="2"
|
|
213
|
+
d="M4 6h16M4 12h16M4 18h16"
|
|
214
|
+
/>
|
|
215
|
+
</svg>
|
|
216
|
+
{/if}
|
|
217
|
+
</button>
|
|
218
|
+
{/if}
|
|
74
219
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
220
|
+
<!-- Collapse/Expand Button (Desktop sidebar toggle) -->
|
|
221
|
+
{#if showCollapseButton}
|
|
222
|
+
<button
|
|
223
|
+
class={cn(
|
|
224
|
+
'rounded-md p-2 hover:bg-accent hover:text-accent-foreground',
|
|
225
|
+
bp.collapseButtonShow
|
|
226
|
+
)}
|
|
227
|
+
onclick={() => onCollapseClick?.()}
|
|
228
|
+
aria-label={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
229
|
+
aria-expanded={!sidebarCollapsed}
|
|
230
|
+
>
|
|
231
|
+
<svg
|
|
232
|
+
class={cn('h-5 w-5 transition-transform', sidebarCollapsed && 'rotate-180')}
|
|
233
|
+
fill="none"
|
|
234
|
+
viewBox="0 0 24 24"
|
|
235
|
+
stroke="currentColor"
|
|
236
|
+
>
|
|
237
|
+
<path
|
|
238
|
+
stroke-linecap="round"
|
|
239
|
+
stroke-linejoin="round"
|
|
240
|
+
stroke-width="2"
|
|
241
|
+
d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
|
|
242
|
+
/>
|
|
243
|
+
</svg>
|
|
244
|
+
</button>
|
|
245
|
+
{/if}
|
|
80
246
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
247
|
+
<!-- Page Title -->
|
|
248
|
+
{#if title}
|
|
249
|
+
<h2 class="text-lg font-semibold">{title}</h2>
|
|
250
|
+
{/if}
|
|
251
|
+
|
|
252
|
+
<!-- Start Content -->
|
|
253
|
+
{#if start}
|
|
254
|
+
{@render start()}
|
|
255
|
+
{/if}
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<!-- Navigation (hidden on mobile, shown on desktop) -->
|
|
259
|
+
{#if nav}
|
|
260
|
+
<nav class={cn('items-center gap-6', bp.navShow)} aria-label="Main navigation">
|
|
261
|
+
{@render nav()}
|
|
262
|
+
</nav>
|
|
263
|
+
{/if}
|
|
87
264
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
265
|
+
<!-- End Section (search + custom end content) -->
|
|
266
|
+
{#if search?.enabled || end}
|
|
267
|
+
<div class="flex items-center gap-4">
|
|
268
|
+
{#if search?.enabled}
|
|
269
|
+
<HeaderSearch {...search} />
|
|
270
|
+
{/if}
|
|
271
|
+
{#if end}
|
|
272
|
+
{@render end()}
|
|
273
|
+
{/if}
|
|
274
|
+
</div>
|
|
275
|
+
{/if}
|
|
92
276
|
</div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<!-- Mobile Navigation Drawer (appears below header bar when open) -->
|
|
280
|
+
{#if mobileNav && menuOpen}
|
|
281
|
+
<nav class={cn(bp.mobileNavHide)} aria-label="Mobile navigation">
|
|
282
|
+
{@render mobileNav()}
|
|
283
|
+
</nav>
|
|
93
284
|
{/if}
|
|
94
285
|
</header>
|