@classic-homes/theme-svelte 0.1.5 → 0.1.7
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/CardFooter.svelte +20 -2
- package/dist/lib/components/CardFooter.svelte.d.ts +20 -2
- package/dist/lib/components/CardHeader.svelte +22 -2
- package/dist/lib/components/CardHeader.svelte.d.ts +5 -4
- package/dist/lib/components/HeaderSearch.svelte +340 -0
- package/dist/lib/components/HeaderSearch.svelte.d.ts +37 -0
- package/dist/lib/components/PageHeader.svelte +6 -0
- package/dist/lib/components/PageHeader.svelte.d.ts +1 -1
- 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 +39 -60
- package/dist/lib/components/layout/DashboardLayout.svelte.d.ts +6 -1
- 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/Footer.svelte +58 -53
- 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/sidebar/SidebarNavItem.svelte +16 -7
- package/dist/lib/components/layout/sidebar/SidebarSection.svelte +4 -4
- package/dist/lib/index.d.ts +4 -1
- package/dist/lib/index.js +3 -0
- package/dist/lib/schemas/common.d.ts +2 -2
- package/dist/lib/types/layout.d.ts +187 -1
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthLayout - Layout for authentication pages
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Centered card layout for auth forms
|
|
6
|
+
* - Logo with optional subtitle
|
|
7
|
+
* - Footer links (privacy, terms, etc.)
|
|
8
|
+
* - Optional background decoration
|
|
9
|
+
* - Responsive design
|
|
10
|
+
* - Composes AppShell for consistent base structure
|
|
11
|
+
*
|
|
12
|
+
* Use this layout for: login, signup, password reset, forgot password,
|
|
13
|
+
* email verification, 2FA, and other authentication flows.
|
|
14
|
+
*/
|
|
15
|
+
import type { Snippet } from 'svelte';
|
|
16
|
+
interface FooterLink {
|
|
17
|
+
/** Link label */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Link URL */
|
|
20
|
+
href: string;
|
|
21
|
+
/** Opens in new tab */
|
|
22
|
+
external?: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface Props {
|
|
25
|
+
/** Custom logo snippet */
|
|
26
|
+
logo?: Snippet;
|
|
27
|
+
/** Logo subtitle for default logo (e.g., "Sign in to your account") */
|
|
28
|
+
logoSubtitle?: string;
|
|
29
|
+
/** Logo environment indicator for default logo */
|
|
30
|
+
logoEnvironment?: 'local' | 'dev' | 'demo';
|
|
31
|
+
/** Footer links (privacy policy, terms, etc.) */
|
|
32
|
+
footerLinks?: FooterLink[];
|
|
33
|
+
/** Custom footer content (replaces footer links) */
|
|
34
|
+
footer?: Snippet;
|
|
35
|
+
/** Show decorative background */
|
|
36
|
+
showBackground?: boolean;
|
|
37
|
+
/** Background variant */
|
|
38
|
+
backgroundVariant?: 'default' | 'gradient' | 'pattern';
|
|
39
|
+
/** Maximum width of content card */
|
|
40
|
+
maxWidth?: 'sm' | 'md' | 'lg';
|
|
41
|
+
/** Additional classes for the container */
|
|
42
|
+
class?: string;
|
|
43
|
+
/** Main content (auth form) */
|
|
44
|
+
children: Snippet;
|
|
45
|
+
}
|
|
46
|
+
declare const AuthLayout: import("svelte").Component<Props, {}, "">;
|
|
47
|
+
type AuthLayout = ReturnType<typeof AuthLayout>;
|
|
48
|
+
export default AuthLayout;
|
|
@@ -11,11 +11,28 @@
|
|
|
11
11
|
* - Integrated with sidebar store for state management
|
|
12
12
|
*/
|
|
13
13
|
import type { Snippet } from 'svelte';
|
|
14
|
-
import type {
|
|
15
|
-
|
|
14
|
+
import type {
|
|
15
|
+
NavSection,
|
|
16
|
+
NavItem,
|
|
17
|
+
User,
|
|
18
|
+
QuickLink,
|
|
19
|
+
BackLink,
|
|
20
|
+
HeaderSearchConfig,
|
|
21
|
+
} from '../../types/layout.js';
|
|
16
22
|
import { sidebarStore } from '../../stores/sidebar.svelte.js';
|
|
17
23
|
import AppShell from './AppShell.svelte';
|
|
18
24
|
import Sidebar from './Sidebar.svelte';
|
|
25
|
+
import Header from './Header.svelte';
|
|
26
|
+
|
|
27
|
+
type Breakpoint = 'sm' | 'md' | 'lg';
|
|
28
|
+
|
|
29
|
+
// Breakpoint pixel values (max-width for mobile detection)
|
|
30
|
+
// These match Tailwind's breakpoint - 1 (e.g., lg starts at 1024px, so mobile is <= 1023px)
|
|
31
|
+
const breakpointPixels: Record<Breakpoint, number> = {
|
|
32
|
+
sm: 639,
|
|
33
|
+
md: 767,
|
|
34
|
+
lg: 1023,
|
|
35
|
+
};
|
|
19
36
|
|
|
20
37
|
interface Props {
|
|
21
38
|
/** Navigation sections for sidebar */
|
|
@@ -62,6 +79,10 @@
|
|
|
62
79
|
searchable?: boolean;
|
|
63
80
|
/** Placeholder text for search input */
|
|
64
81
|
searchPlaceholder?: string;
|
|
82
|
+
/** Header search configuration */
|
|
83
|
+
headerSearch?: HeaderSearchConfig;
|
|
84
|
+
/** Breakpoint at which to switch between mobile and desktop layouts */
|
|
85
|
+
mobileBreakpoint?: Breakpoint;
|
|
65
86
|
/** Main content */
|
|
66
87
|
children: Snippet;
|
|
67
88
|
}
|
|
@@ -89,6 +110,8 @@
|
|
|
89
110
|
collapsedWidth = 64,
|
|
90
111
|
searchable = false,
|
|
91
112
|
searchPlaceholder = 'Search...',
|
|
113
|
+
headerSearch,
|
|
114
|
+
mobileBreakpoint = 'lg',
|
|
92
115
|
children,
|
|
93
116
|
}: Props = $props();
|
|
94
117
|
|
|
@@ -117,7 +140,7 @@
|
|
|
117
140
|
|
|
118
141
|
$effect(() => {
|
|
119
142
|
if (typeof window !== 'undefined') {
|
|
120
|
-
const mediaQuery = window.matchMedia(
|
|
143
|
+
const mediaQuery = window.matchMedia(`(max-width: ${breakpointPixels[mobileBreakpoint]}px)`);
|
|
121
144
|
isMobile = mediaQuery.matches;
|
|
122
145
|
sidebarStore.setMobile(isMobile);
|
|
123
146
|
|
|
@@ -205,62 +228,18 @@
|
|
|
205
228
|
style="margin-left: {sidebarWidth}px;"
|
|
206
229
|
>
|
|
207
230
|
{#if showHeader}
|
|
208
|
-
<
|
|
209
|
-
|
|
231
|
+
<Header
|
|
232
|
+
showMenuButton={isMobile}
|
|
233
|
+
menuOpen={sidebarStore.isOpen}
|
|
234
|
+
onMenuClick={() => sidebarStore.toggle()}
|
|
235
|
+
showCollapseButton={!isMobile}
|
|
236
|
+
sidebarCollapsed={!sidebarStore.isOpen}
|
|
237
|
+
onCollapseClick={() => sidebarStore.toggle()}
|
|
238
|
+
title={pageTitle}
|
|
239
|
+
start={headerStart}
|
|
240
|
+
search={headerSearch}
|
|
210
241
|
>
|
|
211
|
-
|
|
212
|
-
<!-- Mobile Menu Button -->
|
|
213
|
-
{#if isMobile}
|
|
214
|
-
<button
|
|
215
|
-
class="rounded-md p-2 hover:bg-accent hover:text-accent-foreground lg:hidden"
|
|
216
|
-
onclick={() => sidebarStore.toggle()}
|
|
217
|
-
aria-label="Open menu"
|
|
218
|
-
>
|
|
219
|
-
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
220
|
-
<path
|
|
221
|
-
stroke-linecap="round"
|
|
222
|
-
stroke-linejoin="round"
|
|
223
|
-
stroke-width="2"
|
|
224
|
-
d="M4 6h16M4 12h16M4 18h16"
|
|
225
|
-
/>
|
|
226
|
-
</svg>
|
|
227
|
-
</button>
|
|
228
|
-
{:else}
|
|
229
|
-
<!-- Collapse Toggle Button (Desktop) -->
|
|
230
|
-
<button
|
|
231
|
-
class="rounded-md p-2 hover:bg-accent hover:text-accent-foreground"
|
|
232
|
-
onclick={() => sidebarStore.toggle()}
|
|
233
|
-
aria-label={sidebarStore.isOpen ? 'Collapse sidebar' : 'Expand sidebar'}
|
|
234
|
-
>
|
|
235
|
-
<svg
|
|
236
|
-
class={cn('h-5 w-5 transition-transform', !sidebarStore.isOpen && 'rotate-180')}
|
|
237
|
-
fill="none"
|
|
238
|
-
viewBox="0 0 24 24"
|
|
239
|
-
stroke="currentColor"
|
|
240
|
-
>
|
|
241
|
-
<path
|
|
242
|
-
stroke-linecap="round"
|
|
243
|
-
stroke-linejoin="round"
|
|
244
|
-
stroke-width="2"
|
|
245
|
-
d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
|
|
246
|
-
/>
|
|
247
|
-
</svg>
|
|
248
|
-
</button>
|
|
249
|
-
{/if}
|
|
250
|
-
|
|
251
|
-
<!-- Page Title -->
|
|
252
|
-
{#if pageTitle}
|
|
253
|
-
<h2 class="text-lg font-semibold">{pageTitle}</h2>
|
|
254
|
-
{/if}
|
|
255
|
-
|
|
256
|
-
<!-- Custom Header Start -->
|
|
257
|
-
{#if headerStart}
|
|
258
|
-
{@render headerStart()}
|
|
259
|
-
{/if}
|
|
260
|
-
</div>
|
|
261
|
-
|
|
262
|
-
<div class="flex items-center gap-4">
|
|
263
|
-
<!-- Custom Header End -->
|
|
242
|
+
{#snippet end()}
|
|
264
243
|
{#if headerEnd}
|
|
265
244
|
{@render headerEnd()}
|
|
266
245
|
{:else if user}
|
|
@@ -283,8 +262,8 @@
|
|
|
283
262
|
</div>
|
|
284
263
|
{/if}
|
|
285
264
|
{/if}
|
|
286
|
-
|
|
287
|
-
</
|
|
265
|
+
{/snippet}
|
|
266
|
+
</Header>
|
|
288
267
|
{/if}
|
|
289
268
|
|
|
290
269
|
<!-- Main Content -->
|
|
@@ -10,7 +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, BackLink } from '../../types/layout.js';
|
|
13
|
+
import type { NavSection, NavItem, User, QuickLink, BackLink, HeaderSearchConfig } from '../../types/layout.js';
|
|
14
|
+
type Breakpoint = 'sm' | 'md' | 'lg';
|
|
14
15
|
interface Props {
|
|
15
16
|
/** Navigation sections for sidebar */
|
|
16
17
|
navigation: NavSection[];
|
|
@@ -56,6 +57,10 @@ interface Props {
|
|
|
56
57
|
searchable?: boolean;
|
|
57
58
|
/** Placeholder text for search input */
|
|
58
59
|
searchPlaceholder?: string;
|
|
60
|
+
/** Header search configuration */
|
|
61
|
+
headerSearch?: HeaderSearchConfig;
|
|
62
|
+
/** Breakpoint at which to switch between mobile and desktop layouts */
|
|
63
|
+
mobileBreakpoint?: Breakpoint;
|
|
59
64
|
/** Main content */
|
|
60
65
|
children: Snippet;
|
|
61
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;
|
|
@@ -42,72 +42,77 @@
|
|
|
42
42
|
class: className,
|
|
43
43
|
}: Props = $props();
|
|
44
44
|
|
|
45
|
-
const currentYear = new Date().getFullYear();
|
|
46
|
-
const defaultCopyright = `${currentYear} Classic Homes. All rights reserved.`;
|
|
47
|
-
|
|
48
45
|
const isDark = $derived(variant === 'dark');
|
|
46
|
+
const displayCopyright = $derived(
|
|
47
|
+
copyright || `${new Date().getFullYear()} Classic Homes. All rights reserved.`
|
|
48
|
+
);
|
|
49
49
|
</script>
|
|
50
50
|
|
|
51
51
|
<footer
|
|
52
|
+
aria-label="Site footer"
|
|
52
53
|
class={cn(
|
|
53
54
|
// Base styles
|
|
54
|
-
isDark ? 'bg-
|
|
55
|
+
isDark ? 'bg-gray-600 text-white' : 'bg-muted/30',
|
|
55
56
|
// Border styles - strong accent border or standard
|
|
56
|
-
strongBorder ? 'border-t-[10px] border-
|
|
57
|
+
strongBorder ? 'border-t-[10px] border-gray-500' : 'border-t border-border',
|
|
57
58
|
className
|
|
58
59
|
)}
|
|
59
60
|
>
|
|
60
61
|
<div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
|
61
62
|
{#if links.length > 0}
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
class="
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
stroke
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
63
|
+
<nav aria-label="Footer navigation">
|
|
64
|
+
<div class="grid grid-cols-2 gap-6 sm:grid-cols-3 md:gap-8 lg:grid-cols-4">
|
|
65
|
+
{#each links as section}
|
|
66
|
+
<div>
|
|
67
|
+
{#if section.title}
|
|
68
|
+
<h3 class={cn('text-lg font-bold mb-4', isDark ? 'text-white' : 'text-foreground')}>
|
|
69
|
+
{section.title}
|
|
70
|
+
</h3>
|
|
71
|
+
{/if}
|
|
72
|
+
<ul class="mt-4 space-y-2">
|
|
73
|
+
{#each section.items as item}
|
|
74
|
+
<li>
|
|
75
|
+
<a
|
|
76
|
+
href={item.href}
|
|
77
|
+
class={cn(
|
|
78
|
+
'text-sm transition-colors',
|
|
79
|
+
isDark
|
|
80
|
+
? 'text-white hover:text-primary'
|
|
81
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
82
|
+
)}
|
|
83
|
+
target={item.external ? '_blank' : undefined}
|
|
84
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
85
|
+
>
|
|
86
|
+
{item.name}
|
|
87
|
+
{#if item.external}
|
|
88
|
+
<span class="sr-only">(opens in new tab)</span>
|
|
89
|
+
<svg
|
|
90
|
+
aria-hidden="true"
|
|
91
|
+
class="ml-1 inline-block h-3 w-3"
|
|
92
|
+
fill="none"
|
|
93
|
+
viewBox="0 0 24 24"
|
|
94
|
+
stroke="currentColor"
|
|
95
|
+
>
|
|
96
|
+
<path
|
|
97
|
+
stroke-linecap="round"
|
|
98
|
+
stroke-linejoin="round"
|
|
99
|
+
stroke-width="2"
|
|
100
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
101
|
+
/>
|
|
102
|
+
</svg>
|
|
103
|
+
{/if}
|
|
104
|
+
</a>
|
|
105
|
+
</li>
|
|
106
|
+
{/each}
|
|
107
|
+
</ul>
|
|
108
|
+
</div>
|
|
109
|
+
{/each}
|
|
110
|
+
</div>
|
|
111
|
+
</nav>
|
|
107
112
|
{/if}
|
|
108
113
|
|
|
109
114
|
{#if children}
|
|
110
|
-
<div class={cn('mt-8 border-t pt-8', isDark ? 'border-gray-
|
|
115
|
+
<div class={cn('mt-8 border-t pt-8', isDark ? 'border-gray-500' : 'border-border')}>
|
|
111
116
|
{@render children()}
|
|
112
117
|
</div>
|
|
113
118
|
{/if}
|
|
@@ -115,7 +120,7 @@
|
|
|
115
120
|
<div
|
|
116
121
|
class={cn(
|
|
117
122
|
'mt-8 flex flex-col items-center justify-between gap-4 border-t pt-8 md:flex-row',
|
|
118
|
-
isDark ? 'border-gray-
|
|
123
|
+
isDark ? 'border-gray-500' : 'border-border'
|
|
119
124
|
)}
|
|
120
125
|
>
|
|
121
126
|
{#if showLogo}
|
|
@@ -123,7 +128,7 @@
|
|
|
123
128
|
{/if}
|
|
124
129
|
|
|
125
130
|
<p class={cn('text-center text-sm', isDark ? 'text-gray-400' : 'text-muted-foreground')}>
|
|
126
|
-
{
|
|
131
|
+
{displayCopyright}
|
|
127
132
|
</p>
|
|
128
133
|
</div>
|
|
129
134
|
</div>
|
|
@@ -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}
|