@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,248 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DashboardLayout - Complete dashboard layout with sidebar and header
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Responsive sidebar (collapsible on desktop, drawer on mobile)
|
|
7
|
+
* - Sticky header with page title and customizable content
|
|
8
|
+
* - Light/dark sidebar variants
|
|
9
|
+
* - Optional back link navigation
|
|
10
|
+
* - Main content area with proper spacing
|
|
11
|
+
* - Integrated with sidebar store for state management
|
|
12
|
+
*/
|
|
13
|
+
import type { Snippet } from 'svelte';
|
|
14
|
+
import type { NavSection, NavItem, User, QuickLink } from '../../types/layout.js';
|
|
15
|
+
import { cn } from '../../utils.js';
|
|
16
|
+
import { sidebarStore } from '../../stores/sidebar.svelte.js';
|
|
17
|
+
import AppShell from './AppShell.svelte';
|
|
18
|
+
import Sidebar from './Sidebar.svelte';
|
|
19
|
+
|
|
20
|
+
interface BackLink {
|
|
21
|
+
/** Link label (e.g., "Back to Dashboard") */
|
|
22
|
+
label: string;
|
|
23
|
+
/** Link URL */
|
|
24
|
+
href: string;
|
|
25
|
+
/** Icon name for the icon snippet */
|
|
26
|
+
icon?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
/** Navigation sections for sidebar */
|
|
31
|
+
navigation: NavSection[];
|
|
32
|
+
/** Current user data */
|
|
33
|
+
user?: User;
|
|
34
|
+
/** Application name */
|
|
35
|
+
appName?: string;
|
|
36
|
+
/** Page title displayed in header */
|
|
37
|
+
pageTitle?: string;
|
|
38
|
+
/** Back link shown at top of sidebar navigation */
|
|
39
|
+
backLink?: BackLink;
|
|
40
|
+
/** Sidebar visual variant */
|
|
41
|
+
sidebarVariant?: 'light' | 'dark';
|
|
42
|
+
/** Whether sidebar starts collapsed */
|
|
43
|
+
sidebarCollapsed?: boolean;
|
|
44
|
+
/** Whether to show the header */
|
|
45
|
+
showHeader?: boolean;
|
|
46
|
+
/** Custom logo snippet */
|
|
47
|
+
logo?: Snippet;
|
|
48
|
+
/** Custom icon renderer for nav items */
|
|
49
|
+
icon?: Snippet<[NavItem]>;
|
|
50
|
+
/** Quick links displayed at bottom of sidebar */
|
|
51
|
+
quickLinks?: QuickLink[];
|
|
52
|
+
/** Quick links display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
|
|
53
|
+
quickLinksDisplay?: 'list' | 'icons';
|
|
54
|
+
/** Custom icon renderer for quick links */
|
|
55
|
+
quickLinkIcon?: Snippet<[QuickLink]>;
|
|
56
|
+
/** Custom content at start of header (after toggle button) */
|
|
57
|
+
headerStart?: Snippet;
|
|
58
|
+
/** Custom content at end of header */
|
|
59
|
+
headerEnd?: Snippet;
|
|
60
|
+
/** Custom user menu snippet */
|
|
61
|
+
userMenu?: Snippet<[User]>;
|
|
62
|
+
/** Sidebar footer content */
|
|
63
|
+
sidebarFooter?: Snippet;
|
|
64
|
+
/** Main content */
|
|
65
|
+
children: Snippet;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let {
|
|
69
|
+
navigation,
|
|
70
|
+
user,
|
|
71
|
+
appName = 'Dashboard',
|
|
72
|
+
pageTitle,
|
|
73
|
+
backLink,
|
|
74
|
+
sidebarVariant = 'light',
|
|
75
|
+
sidebarCollapsed = false,
|
|
76
|
+
showHeader = true,
|
|
77
|
+
logo,
|
|
78
|
+
icon,
|
|
79
|
+
quickLinks,
|
|
80
|
+
quickLinksDisplay = 'list',
|
|
81
|
+
quickLinkIcon,
|
|
82
|
+
headerStart,
|
|
83
|
+
headerEnd,
|
|
84
|
+
userMenu,
|
|
85
|
+
sidebarFooter,
|
|
86
|
+
children,
|
|
87
|
+
}: Props = $props();
|
|
88
|
+
|
|
89
|
+
// Create navigation with back link prepended if provided
|
|
90
|
+
const effectiveNavigation = $derived(
|
|
91
|
+
backLink
|
|
92
|
+
? [
|
|
93
|
+
{
|
|
94
|
+
id: '__back',
|
|
95
|
+
items: [
|
|
96
|
+
{
|
|
97
|
+
id: '__back-link',
|
|
98
|
+
name: backLink.label,
|
|
99
|
+
href: backLink.href,
|
|
100
|
+
icon: backLink.icon || 'arrow-left',
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
...navigation,
|
|
105
|
+
]
|
|
106
|
+
: navigation
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Reactive media query for mobile detection
|
|
110
|
+
let isMobile = $state(false);
|
|
111
|
+
|
|
112
|
+
$effect(() => {
|
|
113
|
+
if (typeof window !== 'undefined') {
|
|
114
|
+
const mediaQuery = window.matchMedia('(max-width: 1023px)');
|
|
115
|
+
isMobile = mediaQuery.matches;
|
|
116
|
+
sidebarStore.setMobile(isMobile);
|
|
117
|
+
|
|
118
|
+
const handler = (e: MediaQueryListEvent) => {
|
|
119
|
+
isMobile = e.matches;
|
|
120
|
+
sidebarStore.setMobile(e.matches);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
mediaQuery.addEventListener('change', handler);
|
|
124
|
+
return () => mediaQuery.removeEventListener('change', handler);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Initialize sidebar state
|
|
129
|
+
$effect(() => {
|
|
130
|
+
if (!sidebarCollapsed && !isMobile) {
|
|
131
|
+
sidebarStore.open();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const sidebarWidth = $derived(isMobile ? 0 : sidebarStore.isOpen ? 256 : 64);
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<AppShell>
|
|
139
|
+
<!-- Sidebar -->
|
|
140
|
+
<Sidebar
|
|
141
|
+
navigation={effectiveNavigation}
|
|
142
|
+
variant={sidebarVariant}
|
|
143
|
+
collapsed={!sidebarStore.isOpen && !isMobile}
|
|
144
|
+
{isMobile}
|
|
145
|
+
mobileOpen={isMobile && sidebarStore.isOpen}
|
|
146
|
+
onClose={() => sidebarStore.close()}
|
|
147
|
+
{logo}
|
|
148
|
+
{icon}
|
|
149
|
+
{quickLinks}
|
|
150
|
+
{quickLinksDisplay}
|
|
151
|
+
{quickLinkIcon}
|
|
152
|
+
footer={sidebarFooter}
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<!-- Main Content Area -->
|
|
156
|
+
<div
|
|
157
|
+
class="flex flex-1 flex-col transition-all duration-300"
|
|
158
|
+
style="margin-left: {sidebarWidth}px;"
|
|
159
|
+
>
|
|
160
|
+
{#if showHeader}
|
|
161
|
+
<header
|
|
162
|
+
class="sticky top-0 z-30 flex h-16 items-center justify-between border-b border-black bg-background px-4 lg:px-6"
|
|
163
|
+
>
|
|
164
|
+
<div class="flex items-center gap-4">
|
|
165
|
+
<!-- Mobile Menu Button -->
|
|
166
|
+
{#if isMobile}
|
|
167
|
+
<button
|
|
168
|
+
class="rounded-md p-2 hover:bg-accent lg:hidden"
|
|
169
|
+
onclick={() => sidebarStore.toggle()}
|
|
170
|
+
aria-label="Open menu"
|
|
171
|
+
>
|
|
172
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
173
|
+
<path
|
|
174
|
+
stroke-linecap="round"
|
|
175
|
+
stroke-linejoin="round"
|
|
176
|
+
stroke-width="2"
|
|
177
|
+
d="M4 6h16M4 12h16M4 18h16"
|
|
178
|
+
/>
|
|
179
|
+
</svg>
|
|
180
|
+
</button>
|
|
181
|
+
{:else}
|
|
182
|
+
<!-- Collapse Toggle Button (Desktop) -->
|
|
183
|
+
<button
|
|
184
|
+
class="rounded-md p-2 hover:bg-accent"
|
|
185
|
+
onclick={() => sidebarStore.toggle()}
|
|
186
|
+
aria-label={sidebarStore.isOpen ? 'Collapse sidebar' : 'Expand sidebar'}
|
|
187
|
+
>
|
|
188
|
+
<svg
|
|
189
|
+
class={cn('h-5 w-5 transition-transform', !sidebarStore.isOpen && 'rotate-180')}
|
|
190
|
+
fill="none"
|
|
191
|
+
viewBox="0 0 24 24"
|
|
192
|
+
stroke="currentColor"
|
|
193
|
+
>
|
|
194
|
+
<path
|
|
195
|
+
stroke-linecap="round"
|
|
196
|
+
stroke-linejoin="round"
|
|
197
|
+
stroke-width="2"
|
|
198
|
+
d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
|
|
199
|
+
/>
|
|
200
|
+
</svg>
|
|
201
|
+
</button>
|
|
202
|
+
{/if}
|
|
203
|
+
|
|
204
|
+
<!-- Page Title -->
|
|
205
|
+
{#if pageTitle}
|
|
206
|
+
<h2 class="text-lg font-semibold">{pageTitle}</h2>
|
|
207
|
+
{/if}
|
|
208
|
+
|
|
209
|
+
<!-- Custom Header Start -->
|
|
210
|
+
{#if headerStart}
|
|
211
|
+
{@render headerStart()}
|
|
212
|
+
{/if}
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div class="flex items-center gap-4">
|
|
216
|
+
<!-- Custom Header End -->
|
|
217
|
+
{#if headerEnd}
|
|
218
|
+
{@render headerEnd()}
|
|
219
|
+
{:else if user}
|
|
220
|
+
{#if userMenu}
|
|
221
|
+
{@render userMenu(user)}
|
|
222
|
+
{:else}
|
|
223
|
+
<!-- Default User Display -->
|
|
224
|
+
<div class="flex items-center gap-3">
|
|
225
|
+
<div
|
|
226
|
+
class="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-sm font-medium text-primary-foreground"
|
|
227
|
+
>
|
|
228
|
+
{user.initials || user.name.charAt(0).toUpperCase()}
|
|
229
|
+
</div>
|
|
230
|
+
<div class="hidden md:block">
|
|
231
|
+
<p class="text-sm font-medium">{user.name}</p>
|
|
232
|
+
{#if user.email}
|
|
233
|
+
<p class="text-xs text-muted-foreground">{user.email}</p>
|
|
234
|
+
{/if}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
{/if}
|
|
238
|
+
{/if}
|
|
239
|
+
</div>
|
|
240
|
+
</header>
|
|
241
|
+
{/if}
|
|
242
|
+
|
|
243
|
+
<!-- Main Content -->
|
|
244
|
+
<main id="main-content" class="flex-1 bg-content-bg p-4 lg:p-6">
|
|
245
|
+
{@render children()}
|
|
246
|
+
</main>
|
|
247
|
+
</div>
|
|
248
|
+
</AppShell>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DashboardLayout - Complete dashboard layout with sidebar and header
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Responsive sidebar (collapsible on desktop, drawer on mobile)
|
|
6
|
+
* - Sticky header with page title and customizable content
|
|
7
|
+
* - Light/dark sidebar variants
|
|
8
|
+
* - Optional back link navigation
|
|
9
|
+
* - Main content area with proper spacing
|
|
10
|
+
* - Integrated with sidebar store for state management
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import type { NavSection, NavItem, User, QuickLink } from '../../types/layout.js';
|
|
14
|
+
interface BackLink {
|
|
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
|
+
}
|
|
22
|
+
interface Props {
|
|
23
|
+
/** Navigation sections for sidebar */
|
|
24
|
+
navigation: NavSection[];
|
|
25
|
+
/** Current user data */
|
|
26
|
+
user?: User;
|
|
27
|
+
/** Application name */
|
|
28
|
+
appName?: string;
|
|
29
|
+
/** Page title displayed in header */
|
|
30
|
+
pageTitle?: string;
|
|
31
|
+
/** Back link shown at top of sidebar navigation */
|
|
32
|
+
backLink?: BackLink;
|
|
33
|
+
/** Sidebar visual variant */
|
|
34
|
+
sidebarVariant?: 'light' | 'dark';
|
|
35
|
+
/** Whether sidebar starts collapsed */
|
|
36
|
+
sidebarCollapsed?: boolean;
|
|
37
|
+
/** Whether to show the header */
|
|
38
|
+
showHeader?: boolean;
|
|
39
|
+
/** Custom logo snippet */
|
|
40
|
+
logo?: Snippet;
|
|
41
|
+
/** Custom icon renderer for nav items */
|
|
42
|
+
icon?: Snippet<[NavItem]>;
|
|
43
|
+
/** Quick links displayed at bottom of sidebar */
|
|
44
|
+
quickLinks?: QuickLink[];
|
|
45
|
+
/** Quick links display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
|
|
46
|
+
quickLinksDisplay?: 'list' | 'icons';
|
|
47
|
+
/** Custom icon renderer for quick links */
|
|
48
|
+
quickLinkIcon?: Snippet<[QuickLink]>;
|
|
49
|
+
/** Custom content at start of header (after toggle button) */
|
|
50
|
+
headerStart?: Snippet;
|
|
51
|
+
/** Custom content at end of header */
|
|
52
|
+
headerEnd?: Snippet;
|
|
53
|
+
/** Custom user menu snippet */
|
|
54
|
+
userMenu?: Snippet<[User]>;
|
|
55
|
+
/** Sidebar footer content */
|
|
56
|
+
sidebarFooter?: Snippet;
|
|
57
|
+
/** Main content */
|
|
58
|
+
children: Snippet;
|
|
59
|
+
}
|
|
60
|
+
declare const DashboardLayout: import("svelte").Component<Props, {}, "">;
|
|
61
|
+
type DashboardLayout = ReturnType<typeof DashboardLayout>;
|
|
62
|
+
export default DashboardLayout;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Footer - Site footer component
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Link sections
|
|
7
|
+
* - Copyright text
|
|
8
|
+
* - Logo option
|
|
9
|
+
* - Custom content support
|
|
10
|
+
* - Strong accent border option (matches brand guidelines)
|
|
11
|
+
* - Dark variant for branded footers
|
|
12
|
+
*/
|
|
13
|
+
import type { Snippet } from 'svelte';
|
|
14
|
+
import type { NavSection } from '../../types/layout.js';
|
|
15
|
+
import { cn } from '../../utils.js';
|
|
16
|
+
import LogoMain from '../LogoMain.svelte';
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
/** Link sections */
|
|
20
|
+
links?: NavSection[];
|
|
21
|
+
/** Copyright text */
|
|
22
|
+
copyright?: string;
|
|
23
|
+
/** Show logo in footer */
|
|
24
|
+
showLogo?: boolean;
|
|
25
|
+
/** Use strong accent border (10px top border) */
|
|
26
|
+
strongBorder?: boolean;
|
|
27
|
+
/** Dark variant with brand colors */
|
|
28
|
+
variant?: 'default' | 'dark';
|
|
29
|
+
/** Custom content */
|
|
30
|
+
children?: Snippet;
|
|
31
|
+
/** Additional classes */
|
|
32
|
+
class?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
links = [],
|
|
37
|
+
copyright,
|
|
38
|
+
showLogo = true,
|
|
39
|
+
strongBorder = false,
|
|
40
|
+
variant = 'default',
|
|
41
|
+
children,
|
|
42
|
+
class: className,
|
|
43
|
+
}: Props = $props();
|
|
44
|
+
|
|
45
|
+
const currentYear = new Date().getFullYear();
|
|
46
|
+
const defaultCopyright = `${currentYear} Classic Homes. All rights reserved.`;
|
|
47
|
+
|
|
48
|
+
const isDark = $derived(variant === 'dark');
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<footer
|
|
52
|
+
class={cn(
|
|
53
|
+
// Base styles
|
|
54
|
+
isDark ? 'bg-[#50504f] text-white' : 'bg-muted/30',
|
|
55
|
+
// Border styles - strong accent border or standard
|
|
56
|
+
strongBorder ? 'border-t-[10px] border-[#787878]' : 'border-t border-black',
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
<div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
|
61
|
+
{#if links.length > 0}
|
|
62
|
+
<div class="grid grid-cols-2 gap-8 md:grid-cols-4">
|
|
63
|
+
{#each links as section}
|
|
64
|
+
<div>
|
|
65
|
+
{#if section.title}
|
|
66
|
+
<h3 class={cn('text-lg font-bold mb-4', isDark ? 'text-white' : 'text-foreground')}>
|
|
67
|
+
{section.title}
|
|
68
|
+
</h3>
|
|
69
|
+
{/if}
|
|
70
|
+
<ul class="mt-4 space-y-2">
|
|
71
|
+
{#each section.items as item}
|
|
72
|
+
<li>
|
|
73
|
+
<a
|
|
74
|
+
href={item.href}
|
|
75
|
+
class={cn(
|
|
76
|
+
'text-sm transition-colors',
|
|
77
|
+
isDark
|
|
78
|
+
? 'text-white hover:text-primary'
|
|
79
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
80
|
+
)}
|
|
81
|
+
target={item.external ? '_blank' : undefined}
|
|
82
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
83
|
+
>
|
|
84
|
+
{item.name}
|
|
85
|
+
{#if item.external}
|
|
86
|
+
<svg
|
|
87
|
+
class="ml-1 inline-block h-3 w-3"
|
|
88
|
+
fill="none"
|
|
89
|
+
viewBox="0 0 24 24"
|
|
90
|
+
stroke="currentColor"
|
|
91
|
+
>
|
|
92
|
+
<path
|
|
93
|
+
stroke-linecap="round"
|
|
94
|
+
stroke-linejoin="round"
|
|
95
|
+
stroke-width="2"
|
|
96
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
97
|
+
/>
|
|
98
|
+
</svg>
|
|
99
|
+
{/if}
|
|
100
|
+
</a>
|
|
101
|
+
</li>
|
|
102
|
+
{/each}
|
|
103
|
+
</ul>
|
|
104
|
+
</div>
|
|
105
|
+
{/each}
|
|
106
|
+
</div>
|
|
107
|
+
{/if}
|
|
108
|
+
|
|
109
|
+
{#if children}
|
|
110
|
+
<div class={cn('mt-8 border-t pt-8', isDark ? 'border-gray-600' : 'border-black')}>
|
|
111
|
+
{@render children()}
|
|
112
|
+
</div>
|
|
113
|
+
{/if}
|
|
114
|
+
|
|
115
|
+
<div
|
|
116
|
+
class={cn(
|
|
117
|
+
'mt-8 flex flex-col items-center justify-between gap-4 border-t pt-8 md:flex-row',
|
|
118
|
+
isDark ? 'border-gray-600' : 'border-black'
|
|
119
|
+
)}
|
|
120
|
+
>
|
|
121
|
+
{#if showLogo}
|
|
122
|
+
<LogoMain variant="horizontal" color={isDark ? 'light' : 'dark'} size="sm" />
|
|
123
|
+
{/if}
|
|
124
|
+
|
|
125
|
+
<p class={cn('text-center text-sm', isDark ? 'text-gray-400' : 'text-muted-foreground')}>
|
|
126
|
+
{copyright || defaultCopyright}
|
|
127
|
+
</p>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</footer>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Footer - Site footer component
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Link sections
|
|
6
|
+
* - Copyright text
|
|
7
|
+
* - Logo option
|
|
8
|
+
* - Custom content support
|
|
9
|
+
* - Strong accent border option (matches brand guidelines)
|
|
10
|
+
* - Dark variant for branded footers
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import type { NavSection } from '../../types/layout.js';
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Link sections */
|
|
16
|
+
links?: NavSection[];
|
|
17
|
+
/** Copyright text */
|
|
18
|
+
copyright?: string;
|
|
19
|
+
/** Show logo in footer */
|
|
20
|
+
showLogo?: boolean;
|
|
21
|
+
/** Use strong accent border (10px top border) */
|
|
22
|
+
strongBorder?: boolean;
|
|
23
|
+
/** Dark variant with brand colors */
|
|
24
|
+
variant?: 'default' | 'dark';
|
|
25
|
+
/** Custom content */
|
|
26
|
+
children?: Snippet;
|
|
27
|
+
/** Additional classes */
|
|
28
|
+
class?: string;
|
|
29
|
+
}
|
|
30
|
+
declare const Footer: import("svelte").Component<Props, {}, "">;
|
|
31
|
+
type Footer = ReturnType<typeof Footer>;
|
|
32
|
+
export default Footer;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* FormPageLayout - Specialized layout for form pages
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Centered title and description header
|
|
7
|
+
* - Optional notice cards section
|
|
8
|
+
* - Two-column layout with optional sidebar
|
|
9
|
+
* - Help text footer
|
|
10
|
+
* - Responsive design (sidebar above main on mobile)
|
|
11
|
+
* - Light sage background for visual distinction
|
|
12
|
+
*/
|
|
13
|
+
import type { Snippet } from 'svelte';
|
|
14
|
+
import { cn } from '../../utils.js';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Page title (h1) */
|
|
18
|
+
title: string;
|
|
19
|
+
/** Page description text */
|
|
20
|
+
description: string;
|
|
21
|
+
/** Footer help text (supports HTML) */
|
|
22
|
+
helpText?: string;
|
|
23
|
+
/** Show the notices section (renders notices snippet if provided) */
|
|
24
|
+
showNotices?: boolean;
|
|
25
|
+
/** Main form content */
|
|
26
|
+
children: Snippet;
|
|
27
|
+
/** Optional notices content (alert cards, etc.) */
|
|
28
|
+
notices?: Snippet;
|
|
29
|
+
/** Optional sidebar content */
|
|
30
|
+
sidebar?: Snippet;
|
|
31
|
+
/** Additional classes for the container */
|
|
32
|
+
class?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
title,
|
|
37
|
+
description,
|
|
38
|
+
helpText,
|
|
39
|
+
showNotices = true,
|
|
40
|
+
children,
|
|
41
|
+
notices,
|
|
42
|
+
sidebar,
|
|
43
|
+
class: className,
|
|
44
|
+
}: Props = $props();
|
|
45
|
+
|
|
46
|
+
const hasSidebar = $derived(sidebar !== undefined);
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<div class={cn('min-h-screen bg-brand-core-4-tint py-8 sm:py-12', className)}>
|
|
50
|
+
<div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
|
51
|
+
<!-- Header Section -->
|
|
52
|
+
<header class="mb-8 text-center">
|
|
53
|
+
<h1 class="text-3xl font-light tracking-tight text-brand-core-2 md:text-4xl">
|
|
54
|
+
{title}
|
|
55
|
+
</h1>
|
|
56
|
+
<p class="mt-3 text-base text-gray-600 md:text-lg">
|
|
57
|
+
{description}
|
|
58
|
+
</p>
|
|
59
|
+
</header>
|
|
60
|
+
|
|
61
|
+
<!-- Notices Section -->
|
|
62
|
+
{#if showNotices && notices}
|
|
63
|
+
<div class="mb-6">
|
|
64
|
+
{@render notices()}
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
67
|
+
|
|
68
|
+
<!-- Main Content Area -->
|
|
69
|
+
<div class={cn('gap-8', hasSidebar ? 'grid lg:grid-cols-[1fr,320px]' : 'mx-auto max-w-2xl')}>
|
|
70
|
+
<!-- Sidebar (shows first on mobile when present) -->
|
|
71
|
+
{#if hasSidebar}
|
|
72
|
+
<aside class="order-first lg:order-last">
|
|
73
|
+
{@render sidebar!()}
|
|
74
|
+
</aside>
|
|
75
|
+
{/if}
|
|
76
|
+
|
|
77
|
+
<!-- Main Content -->
|
|
78
|
+
<main id="form-content" class={hasSidebar ? 'order-last lg:order-first' : ''}>
|
|
79
|
+
{@render children()}
|
|
80
|
+
</main>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<!-- Help Text Footer -->
|
|
84
|
+
{#if helpText}
|
|
85
|
+
<footer class="mt-8 text-center">
|
|
86
|
+
<p class="text-sm text-gray-500">
|
|
87
|
+
{@html helpText}
|
|
88
|
+
</p>
|
|
89
|
+
</footer>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FormPageLayout - Specialized layout for form pages
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Centered title and description header
|
|
6
|
+
* - Optional notice cards section
|
|
7
|
+
* - Two-column layout with optional sidebar
|
|
8
|
+
* - Help text footer
|
|
9
|
+
* - Responsive design (sidebar above main on mobile)
|
|
10
|
+
* - Light sage background for visual distinction
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
interface Props {
|
|
14
|
+
/** Page title (h1) */
|
|
15
|
+
title: string;
|
|
16
|
+
/** Page description text */
|
|
17
|
+
description: string;
|
|
18
|
+
/** Footer help text (supports HTML) */
|
|
19
|
+
helpText?: string;
|
|
20
|
+
/** Show the notices section (renders notices snippet if provided) */
|
|
21
|
+
showNotices?: boolean;
|
|
22
|
+
/** Main form content */
|
|
23
|
+
children: Snippet;
|
|
24
|
+
/** Optional notices content (alert cards, etc.) */
|
|
25
|
+
notices?: Snippet;
|
|
26
|
+
/** Optional sidebar content */
|
|
27
|
+
sidebar?: Snippet;
|
|
28
|
+
/** Additional classes for the container */
|
|
29
|
+
class?: string;
|
|
30
|
+
}
|
|
31
|
+
declare const FormPageLayout: import("svelte").Component<Props, {}, "">;
|
|
32
|
+
type FormPageLayout = ReturnType<typeof FormPageLayout>;
|
|
33
|
+
export default FormPageLayout;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Header - Application header component
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Hamburger menu button for mobile
|
|
7
|
+
* - Customizable start and end slots
|
|
8
|
+
* - Optional navigation slot with uppercase styling
|
|
9
|
+
* - Responsive design
|
|
10
|
+
* - Stronger border styling to match brand guidelines
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import { cn } from '../../utils.js';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
/** Whether to show hamburger menu button (mobile) */
|
|
17
|
+
showMenuButton?: boolean;
|
|
18
|
+
/** Callback when menu button is clicked */
|
|
19
|
+
onMenuClick?: () => void;
|
|
20
|
+
/** Custom start content (left side) */
|
|
21
|
+
start?: Snippet;
|
|
22
|
+
/** Navigation content (center/right on desktop, hidden on mobile) */
|
|
23
|
+
nav?: Snippet;
|
|
24
|
+
/** Custom end content (right side) */
|
|
25
|
+
end?: Snippet;
|
|
26
|
+
/** Use stronger/thicker border */
|
|
27
|
+
strongBorder?: boolean;
|
|
28
|
+
/** Additional classes */
|
|
29
|
+
class?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
showMenuButton = false,
|
|
34
|
+
onMenuClick,
|
|
35
|
+
start,
|
|
36
|
+
nav,
|
|
37
|
+
end,
|
|
38
|
+
strongBorder = false,
|
|
39
|
+
class: className,
|
|
40
|
+
}: Props = $props();
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<!--
|
|
44
|
+
Header Layout Dimensions:
|
|
45
|
+
- h-16 (64px): Standard header height, matches sidebar logo section
|
|
46
|
+
- z-30: Below sidebar (z-50) and overlays (z-40), above main content
|
|
47
|
+
- px-4 / lg:px-6: Responsive horizontal padding (16px mobile, 24px desktop)
|
|
48
|
+
-->
|
|
49
|
+
<header
|
|
50
|
+
class={cn(
|
|
51
|
+
'sticky top-0 z-30 flex h-16 items-center justify-between bg-background px-4 lg:px-6',
|
|
52
|
+
strongBorder ? 'border-b-2 border-black' : 'border-b border-black',
|
|
53
|
+
className
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
<div class="flex items-center gap-4">
|
|
57
|
+
<!-- Mobile Menu Button -->
|
|
58
|
+
{#if showMenuButton}
|
|
59
|
+
<button
|
|
60
|
+
class="rounded-md p-2 hover:bg-accent lg:hidden"
|
|
61
|
+
onclick={() => onMenuClick?.()}
|
|
62
|
+
aria-label="Open menu"
|
|
63
|
+
>
|
|
64
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
65
|
+
<path
|
|
66
|
+
stroke-linecap="round"
|
|
67
|
+
stroke-linejoin="round"
|
|
68
|
+
stroke-width="2"
|
|
69
|
+
d="M4 6h16M4 12h16M4 18h16"
|
|
70
|
+
/>
|
|
71
|
+
</svg>
|
|
72
|
+
</button>
|
|
73
|
+
{/if}
|
|
74
|
+
|
|
75
|
+
<!-- Start Content -->
|
|
76
|
+
{#if start}
|
|
77
|
+
{@render start()}
|
|
78
|
+
{/if}
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Navigation (hidden on mobile, shown on desktop) -->
|
|
82
|
+
{#if nav}
|
|
83
|
+
<nav class="hidden lg:flex items-center gap-6" aria-label="Main navigation">
|
|
84
|
+
{@render nav()}
|
|
85
|
+
</nav>
|
|
86
|
+
{/if}
|
|
87
|
+
|
|
88
|
+
<!-- End Content -->
|
|
89
|
+
{#if end}
|
|
90
|
+
<div class="flex items-center gap-4">
|
|
91
|
+
{@render end()}
|
|
92
|
+
</div>
|
|
93
|
+
{/if}
|
|
94
|
+
</header>
|