@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.
Files changed (27) hide show
  1. package/dist/lib/components/CardFooter.svelte +20 -2
  2. package/dist/lib/components/CardFooter.svelte.d.ts +20 -2
  3. package/dist/lib/components/CardHeader.svelte +22 -2
  4. package/dist/lib/components/CardHeader.svelte.d.ts +5 -4
  5. package/dist/lib/components/HeaderSearch.svelte +340 -0
  6. package/dist/lib/components/HeaderSearch.svelte.d.ts +37 -0
  7. package/dist/lib/components/PageHeader.svelte +6 -0
  8. package/dist/lib/components/PageHeader.svelte.d.ts +1 -1
  9. package/dist/lib/components/layout/AuthLayout.svelte +133 -0
  10. package/dist/lib/components/layout/AuthLayout.svelte.d.ts +48 -0
  11. package/dist/lib/components/layout/DashboardLayout.svelte +39 -60
  12. package/dist/lib/components/layout/DashboardLayout.svelte.d.ts +6 -1
  13. package/dist/lib/components/layout/ErrorLayout.svelte +206 -0
  14. package/dist/lib/components/layout/ErrorLayout.svelte.d.ts +52 -0
  15. package/dist/lib/components/layout/Footer.svelte +58 -53
  16. package/dist/lib/components/layout/FormPageLayout.svelte +2 -8
  17. package/dist/lib/components/layout/Header.svelte +232 -41
  18. package/dist/lib/components/layout/Header.svelte.d.ts +71 -5
  19. package/dist/lib/components/layout/PublicLayout.svelte +54 -80
  20. package/dist/lib/components/layout/PublicLayout.svelte.d.ts +3 -1
  21. package/dist/lib/components/layout/sidebar/SidebarNavItem.svelte +16 -7
  22. package/dist/lib/components/layout/sidebar/SidebarSection.svelte +4 -4
  23. package/dist/lib/index.d.ts +4 -1
  24. package/dist/lib/index.js +3 -0
  25. package/dist/lib/schemas/common.d.ts +2 -2
  26. package/dist/lib/types/layout.d.ts +187 -1
  27. package/package.json +1 -1
@@ -1,94 +1,285 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * Header - Application header component
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
- * - Customizable start and end slots
8
- * - Optional navigation slot with uppercase styling
9
- * - Responsive design
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
- /** Custom start content (left side) */
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
- - px-4 / lg:px-6: Responsive horizontal padding (16px mobile, 24px desktop)
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 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',
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
- <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 hover:text-accent-foreground 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}
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
- <!-- Start Content -->
76
- {#if start}
77
- {@render start()}
78
- {/if}
79
- </div>
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
- <!-- 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}
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
- <!-- End Content -->
89
- {#if end}
90
- <div class="flex items-center gap-4">
91
- {@render end()}
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>
@@ -1,27 +1,93 @@
1
1
  /**
2
- * Header - Application header component
2
+ * Header - Unified application header component
3
+ *
4
+ * A flexible header component that supports both dashboard and public layouts.
3
5
  *
4
6
  * Features:
5
7
  * - Hamburger menu button for mobile
6
- * - Customizable start and end slots
7
- * - Optional navigation slot with uppercase styling
8
- * - Responsive design
8
+ * - Collapse/expand button for desktop sidebar
9
+ * - Customizable start, nav, and end slots
10
+ * - Mobile navigation drawer support
11
+ * - Optional page title
12
+ * - Backdrop blur support
13
+ * - Configurable responsive breakpoint (sm, md, lg)
14
+ * - Responsive design with container width options
9
15
  * - Stronger border styling to match brand guidelines
16
+ *
17
+ * @example Dashboard layout usage:
18
+ * ```svelte
19
+ * <Header
20
+ * showMenuButton={isMobile}
21
+ * menuOpen={sidebarOpen}
22
+ * onMenuClick={() => toggleSidebar()}
23
+ * showCollapseButton={!isMobile}
24
+ * sidebarCollapsed={!sidebarOpen}
25
+ * onCollapseClick={() => toggleSidebar()}
26
+ * title="Dashboard"
27
+ * />
28
+ * ```
29
+ *
30
+ * @example Public layout usage:
31
+ * ```svelte
32
+ * <Header
33
+ * showMenuButton={true}
34
+ * menuOpen={mobileMenuOpen}
35
+ * onMenuClick={() => mobileMenuOpen = !mobileMenuOpen}
36
+ * mobileBreakpoint="md"
37
+ * backdropBlur
38
+ * elevated
39
+ * maxWidth="7xl"
40
+ * >
41
+ * {#snippet start()}
42
+ * <a href="/"><Logo /></a>
43
+ * {/snippet}
44
+ * {#snippet nav()}
45
+ * <NavLinks />
46
+ * {/snippet}
47
+ * {#snippet mobileNav()}
48
+ * <MobileNavDrawer />
49
+ * {/snippet}
50
+ * </Header>
51
+ * ```
10
52
  */
11
53
  import type { Snippet } from 'svelte';
54
+ import type { HeaderSearchConfig } from '../../types/layout.js';
55
+ type Breakpoint = 'sm' | 'md' | 'lg';
12
56
  interface Props {
13
57
  /** Whether to show hamburger menu button (mobile) */
14
58
  showMenuButton?: boolean;
59
+ /** Whether the mobile menu is currently open (for aria-expanded) */
60
+ menuOpen?: boolean;
15
61
  /** Callback when menu button is clicked */
16
62
  onMenuClick?: () => void;
17
- /** Custom start content (left side) */
63
+ /** Whether to show collapse/expand button (desktop sidebar toggle) */
64
+ showCollapseButton?: boolean;
65
+ /** Whether the sidebar is collapsed (for aria-expanded and icon direction) */
66
+ sidebarCollapsed?: boolean;
67
+ /** Callback when collapse button is clicked */
68
+ onCollapseClick?: () => void;
69
+ /** Page title displayed in header */
70
+ title?: string;
71
+ /** Custom start content (left side, after menu/collapse buttons) */
18
72
  start?: Snippet;
19
73
  /** Navigation content (center/right on desktop, hidden on mobile) */
20
74
  nav?: Snippet;
21
75
  /** Custom end content (right side) */
22
76
  end?: Snippet;
77
+ /** Mobile navigation drawer content (appears below header bar when menu is open) */
78
+ mobileNav?: Snippet;
79
+ /** Breakpoint at which to switch between mobile and desktop layouts */
80
+ mobileBreakpoint?: Breakpoint;
23
81
  /** Use stronger/thicker border */
24
82
  strongBorder?: boolean;
83
+ /** Enable backdrop blur effect */
84
+ backdropBlur?: boolean;
85
+ /** Use elevated z-index (z-50 instead of z-30) */
86
+ elevated?: boolean;
87
+ /** Constrain content to max-width container */
88
+ maxWidth?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl';
89
+ /** Search configuration */
90
+ search?: HeaderSearchConfig;
25
91
  /** Additional classes */
26
92
  class?: string;
27
93
  }
@@ -10,10 +10,11 @@
10
10
  * - Strong border options for brand consistency
11
11
  */
12
12
  import type { Snippet } from 'svelte';
13
- import type { NavItem, NavSection } from '../../types/layout.js';
13
+ import type { NavItem, NavSection, HeaderSearchConfig } from '../../types/layout.js';
14
14
  import { cn } from '../../utils.js';
15
15
  import AppShell from './AppShell.svelte';
16
16
  import Footer from './Footer.svelte';
17
+ import Header from './Header.svelte';
17
18
  import LogoMain from '../LogoMain.svelte';
18
19
 
19
20
  interface Props {
@@ -37,6 +38,8 @@
37
38
  strongFooterBorder?: boolean;
38
39
  /** Use dark footer variant */
39
40
  darkFooter?: boolean;
41
+ /** Header search configuration */
42
+ headerSearch?: HeaderSearchConfig;
40
43
  /** Main content */
41
44
  children: Snippet;
42
45
  }
@@ -52,6 +55,7 @@
52
55
  headerEnd,
53
56
  strongFooterBorder = false,
54
57
  darkFooter = false,
58
+ headerSearch,
55
59
  children,
56
60
  }: Props = $props();
57
61
 
@@ -59,12 +63,17 @@
59
63
  </script>
60
64
 
61
65
  <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"
66
+ <Header
67
+ showMenuButton={navigation.length > 0}
68
+ menuOpen={mobileMenuOpen}
69
+ onMenuClick={() => (mobileMenuOpen = !mobileMenuOpen)}
70
+ mobileBreakpoint="md"
71
+ backdropBlur
72
+ elevated
73
+ maxWidth="7xl"
74
+ search={headerSearch}
65
75
  >
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 -->
76
+ {#snippet start()}
68
77
  <a href="/" class="flex items-center">
69
78
  {#if logo}
70
79
  {@render logo()}
@@ -78,90 +87,55 @@
78
87
  />
79
88
  {/if}
80
89
  </a>
90
+ {/snippet}
81
91
 
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}
92
+ {#snippet nav()}
93
+ {#each navigation as item}
94
+ <a
95
+ href={item.href}
96
+ class={cn(
97
+ 'text-sm font-bold uppercase transition-colors hover:text-primary',
98
+ item.active ? 'text-primary' : 'text-muted-foreground'
99
+ )}
100
+ target={item.external ? '_blank' : undefined}
101
+ rel={item.external ? 'noopener noreferrer' : undefined}
102
+ aria-current={item.active ? 'page' : undefined}
103
+ >
104
+ {item.name}
105
+ </a>
106
+ {/each}
107
+ {/snippet}
108
+
109
+ {#snippet end()}
110
+ {#if headerEnd}
111
+ {@render headerEnd()}
112
+ {/if}
113
+ {/snippet}
114
+
115
+ {#snippet mobileNav()}
116
+ <ul class="space-y-2 border-b border-black px-4 py-4">
117
+ {#each navigation as item}
118
+ <li>
86
119
  <a
87
120
  href={item.href}
88
121
  class={cn(
89
- 'text-sm font-bold uppercase transition-colors hover:text-primary',
90
- item.active ? 'text-primary' : 'text-muted-foreground'
122
+ 'block rounded-md px-3 py-2 text-sm font-bold uppercase transition-colors',
123
+ item.active
124
+ ? 'bg-primary/10 text-primary'
125
+ : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
91
126
  )}
92
127
  target={item.external ? '_blank' : undefined}
93
128
  rel={item.external ? 'noopener noreferrer' : undefined}
129
+ aria-current={item.active ? 'page' : undefined}
130
+ onclick={() => (mobileMenuOpen = false)}
94
131
  >
95
132
  {item.name}
96
133
  </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 hover:text-accent-foreground 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-accent-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>
134
+ </li>
135
+ {/each}
136
+ </ul>
137
+ {/snippet}
138
+ </Header>
165
139
 
166
140
  <!-- Main Content -->
167
141
  <main id="main-content" class="flex-1 bg-content-bg">
@@ -9,7 +9,7 @@
9
9
  * - Strong border options for brand consistency
10
10
  */
11
11
  import type { Snippet } from 'svelte';
12
- import type { NavItem, NavSection } from '../../types/layout.js';
12
+ import type { NavItem, NavSection, HeaderSearchConfig } from '../../types/layout.js';
13
13
  interface Props {
14
14
  /** Navigation items for header */
15
15
  navigation?: NavItem[];
@@ -31,6 +31,8 @@ interface Props {
31
31
  strongFooterBorder?: boolean;
32
32
  /** Use dark footer variant */
33
33
  darkFooter?: boolean;
34
+ /** Header search configuration */
35
+ headerSearch?: HeaderSearchConfig;
34
36
  /** Main content */
35
37
  children: Snippet;
36
38
  }