@classic-homes/theme-svelte 0.1.5 → 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.
@@ -77,7 +77,9 @@
77
77
  return cn(
78
78
  isLight && !active && 'text-foreground hover:bg-accent hover:text-accent-foreground',
79
79
  isLight && active && 'bg-primary/10 text-primary',
80
- !isLight && !active && 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
80
+ !isLight &&
81
+ !active &&
82
+ 'text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
81
83
  !isLight && active && 'bg-sidebar-primary text-sidebar-primary-foreground',
82
84
  disabled && 'opacity-50 pointer-events-none cursor-not-allowed'
83
85
  );
@@ -161,7 +163,7 @@
161
163
  <div>
162
164
  <div
163
165
  class={cn(
164
- 'flex w-full items-center rounded-md transition-colors overflow-hidden',
166
+ 'group flex w-full items-center rounded-md transition-colors overflow-hidden',
165
167
  isLight && !item.active && 'hover:bg-accent',
166
168
  isLight && item.active && 'bg-primary/10',
167
169
  !isLight && !item.active && 'hover:bg-sidebar-accent',
@@ -175,9 +177,12 @@
175
177
  class={cn(
176
178
  'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium transition-colors overflow-hidden',
177
179
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
178
- isLight && !item.active && 'text-foreground hover:text-accent-foreground',
180
+ 'group-hover:underline underline-offset-2',
181
+ isLight && !item.active && 'text-foreground group-hover:text-accent-foreground',
179
182
  isLight && item.active && 'text-primary',
180
- !isLight && !item.active && 'hover:text-sidebar-accent-foreground',
183
+ !isLight &&
184
+ !item.active &&
185
+ 'text-sidebar-foreground group-hover:text-sidebar-accent-foreground',
181
186
  !isLight && item.active && 'text-sidebar-primary-foreground',
182
187
  item.disabled && 'opacity-50 pointer-events-none cursor-not-allowed'
183
188
  )}
@@ -200,9 +205,11 @@
200
205
  class={cn(
201
206
  'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium transition-colors overflow-hidden text-left',
202
207
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
203
- isLight && !item.active && 'text-foreground',
208
+ isLight && !item.active && 'text-foreground group-hover:text-accent-foreground',
204
209
  isLight && item.active && 'text-primary',
205
- !isLight && !item.active && '',
210
+ !isLight &&
211
+ !item.active &&
212
+ 'text-sidebar-foreground group-hover:text-sidebar-accent-foreground',
206
213
  !isLight && item.active && 'text-sidebar-primary-foreground',
207
214
  item.disabled && 'opacity-50 pointer-events-none cursor-not-allowed'
208
215
  )}
@@ -223,7 +230,9 @@
223
230
  class={cn(
224
231
  'flex items-center justify-center p-2 transition-colors rounded-md',
225
232
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
226
- isLight ? 'hover:bg-accent/50' : 'hover:bg-sidebar-accent/50'
233
+ isLight
234
+ ? 'hover:bg-accent/50 group-hover:text-accent-foreground'
235
+ : 'text-sidebar-foreground hover:bg-sidebar-accent/50 group-hover:text-sidebar-accent-foreground'
227
236
  )}
228
237
  onclick={(e) => {
229
238
  e.preventDefault();
@@ -58,7 +58,7 @@
58
58
  <button
59
59
  onclick={toggleSection}
60
60
  class={cn(
61
- 'flex w-full items-center justify-between mb-2 px-4 text-xs font-semibold uppercase tracking-wider transition-colors rounded-md',
61
+ 'flex w-full items-center justify-between mb-2 px-4 text-xs font-semibold uppercase tracking-wider transition-colors rounded-md overflow-hidden',
62
62
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
63
63
  isLight
64
64
  ? 'text-muted-foreground hover:text-foreground'
@@ -67,10 +67,10 @@
67
67
  aria-expanded={isExpanded}
68
68
  aria-controls={`sidebar-section-${section.id}`}
69
69
  >
70
- <span class="whitespace-nowrap">{section.title}</span>
70
+ <span class="truncate">{section.title}</span>
71
71
  <svg
72
72
  class={cn(
73
- 'h-4 w-4 transition-transform duration-200',
73
+ 'h-4 w-4 shrink-0 transition-transform duration-200',
74
74
  isExpanded ? 'rotate-0' : '-rotate-90'
75
75
  )}
76
76
  fill="none"
@@ -89,7 +89,7 @@
89
89
  <!-- Non-collapsible section header -->
90
90
  <h2
91
91
  class={cn(
92
- 'mb-2 px-4 text-xs font-semibold uppercase tracking-wider',
92
+ 'mb-2 px-4 text-xs font-semibold uppercase tracking-wider overflow-hidden truncate',
93
93
  isLight ? 'text-muted-foreground' : 'text-sidebar-foreground/60'
94
94
  )}
95
95
  >
@@ -56,12 +56,15 @@ export { default as PageHeader } from './components/PageHeader.svelte';
56
56
  export { default as AppShell } from './components/layout/AppShell.svelte';
57
57
  export { default as DashboardLayout } from './components/layout/DashboardLayout.svelte';
58
58
  export { default as PublicLayout } from './components/layout/PublicLayout.svelte';
59
+ export { default as AuthLayout } from './components/layout/AuthLayout.svelte';
60
+ export { default as ErrorLayout } from './components/layout/ErrorLayout.svelte';
59
61
  export { default as FormPageLayout } from './components/layout/FormPageLayout.svelte';
60
62
  export { default as Sidebar } from './components/layout/Sidebar.svelte';
61
63
  export { default as Header } from './components/layout/Header.svelte';
62
64
  export { default as Footer } from './components/layout/Footer.svelte';
63
65
  export { default as QuickLinks } from './components/layout/QuickLinks.svelte';
64
- export type { NavItem, NavSection, User, BackLink, QuickLink, QuickLinksProps, DashboardLayoutProps, PublicLayoutProps, FormPageLayoutProps, SidebarProps, HeaderProps, FooterProps, AppShellProps, } from './types/layout.js';
66
+ export { default as HeaderSearch } from './components/HeaderSearch.svelte';
67
+ export type { NavItem, NavSection, User, BackLink, QuickLink, QuickLinksProps, DashboardLayoutProps, PublicLayoutProps, AuthLayoutProps, AuthFooterLink, ErrorLayoutProps, FormPageLayoutProps, SidebarProps, HeaderProps, FooterProps, AppShellProps, SearchResultItem, SearchResultGroup, HeaderSearchConfig, } from './types/layout.js';
65
68
  export { toastStore, type Toast as ToastType, type ToastInput } from './stores/toast.svelte.js';
66
69
  export { sidebarStore } from './stores/sidebar.svelte.js';
67
70
  export { themeStore, type ThemeMode } from './stores/theme.svelte.js';
package/dist/lib/index.js CHANGED
@@ -68,11 +68,14 @@ export { default as PageHeader } from './components/PageHeader.svelte';
68
68
  export { default as AppShell } from './components/layout/AppShell.svelte';
69
69
  export { default as DashboardLayout } from './components/layout/DashboardLayout.svelte';
70
70
  export { default as PublicLayout } from './components/layout/PublicLayout.svelte';
71
+ export { default as AuthLayout } from './components/layout/AuthLayout.svelte';
72
+ export { default as ErrorLayout } from './components/layout/ErrorLayout.svelte';
71
73
  export { default as FormPageLayout } from './components/layout/FormPageLayout.svelte';
72
74
  export { default as Sidebar } from './components/layout/Sidebar.svelte';
73
75
  export { default as Header } from './components/layout/Header.svelte';
74
76
  export { default as Footer } from './components/layout/Footer.svelte';
75
77
  export { default as QuickLinks } from './components/layout/QuickLinks.svelte';
78
+ export { default as HeaderSearch } from './components/HeaderSearch.svelte';
76
79
  // Stores
77
80
  export { toastStore } from './stores/toast.svelte.js';
78
81
  export { sidebarStore } from './stores/sidebar.svelte.js';
@@ -5,6 +5,65 @@
5
5
  * navigation, user data, and layout props.
6
6
  */
7
7
  import type { Snippet } from 'svelte';
8
+ /**
9
+ * Generic search result item for header search
10
+ * Consumers can extend this interface for custom result types
11
+ */
12
+ export interface SearchResultItem {
13
+ /** Unique identifier */
14
+ id: string;
15
+ /** Display title */
16
+ title: string;
17
+ /** Optional description/excerpt */
18
+ description?: string;
19
+ /** Navigation URL */
20
+ href?: string;
21
+ /** Optional category/section for grouping */
22
+ category?: string;
23
+ /** Optional icon name */
24
+ icon?: string;
25
+ }
26
+ /**
27
+ * Search result group for categorized results
28
+ */
29
+ export interface SearchResultGroup<T = SearchResultItem> {
30
+ /** Group title (e.g., "Pages", "Documentation") */
31
+ title: string;
32
+ /** Items in this group */
33
+ items: T[];
34
+ }
35
+ /**
36
+ * Search configuration for Header component
37
+ * Backend-agnostic - works with any search provider (Algolia, Fuse.js, custom API, etc.)
38
+ */
39
+ export interface HeaderSearchConfig<T = SearchResultItem> {
40
+ /** Whether search is enabled/visible */
41
+ enabled?: boolean;
42
+ /** Placeholder text for search input */
43
+ placeholder?: string;
44
+ /** Called when user types (debounced). App should update results prop. */
45
+ onSearch?: (query: string) => void;
46
+ /** Called when user selects a result */
47
+ onSelect?: (item: T) => void;
48
+ /** Called when search dialog opens/closes */
49
+ onOpenChange?: (open: boolean) => void;
50
+ /** Search results to display (flat list or grouped) */
51
+ results?: T[] | SearchResultGroup<T>[];
52
+ /** Whether results are currently loading */
53
+ loading?: boolean;
54
+ /** Message when no results found */
55
+ emptyMessage?: string;
56
+ /** Enable Cmd/Ctrl+K shortcut (default: true) */
57
+ enableShortcut?: boolean;
58
+ /** Custom shortcut key (default: 'k') */
59
+ shortcutKey?: string;
60
+ /** Trigger button variant: 'default' shows full search box, 'icon' shows icon-only button */
61
+ variant?: 'default' | 'icon';
62
+ /** Custom result item renderer */
63
+ renderResult?: Snippet<[T, boolean]>;
64
+ /** Custom empty state renderer */
65
+ renderEmpty?: Snippet;
66
+ }
8
67
  /**
9
68
  * Navigation item for sidebar and header navigation
10
69
  */
@@ -157,6 +216,8 @@ export interface DashboardLayoutProps {
157
216
  expandedWidth?: number;
158
217
  /** Sidebar width when collapsed in pixels (default: 64) */
159
218
  collapsedWidth?: number;
219
+ /** Header search configuration */
220
+ headerSearch?: HeaderSearchConfig;
160
221
  /** Main content */
161
222
  children: Snippet;
162
223
  }
@@ -176,6 +237,8 @@ export interface PublicLayoutProps {
176
237
  logo?: Snippet;
177
238
  /** Custom header end content */
178
239
  headerEnd?: Snippet;
240
+ /** Header search configuration */
241
+ headerSearch?: HeaderSearchConfig;
179
242
  /** Main content */
180
243
  children: Snippet;
181
244
  }
@@ -226,16 +289,46 @@ export interface SidebarProps {
226
289
  }
227
290
  /**
228
291
  * Props for Header component
292
+ *
293
+ * A unified header component that supports both dashboard and public layouts.
294
+ * Use mobileBreakpoint to control responsive behavior and mobileNav for
295
+ * inline mobile navigation drawers.
229
296
  */
230
297
  export interface HeaderProps {
231
298
  /** Whether to show hamburger menu button (mobile) */
232
299
  showMenuButton?: boolean;
300
+ /** Whether the mobile menu is currently open (for aria-expanded) */
301
+ menuOpen?: boolean;
233
302
  /** Callback when menu button is clicked */
234
303
  onMenuClick?: () => void;
235
- /** Custom start content (left side) */
304
+ /** Whether to show collapse/expand button (desktop sidebar toggle) */
305
+ showCollapseButton?: boolean;
306
+ /** Whether the sidebar is collapsed (for aria-expanded and icon direction) */
307
+ sidebarCollapsed?: boolean;
308
+ /** Callback when collapse button is clicked */
309
+ onCollapseClick?: () => void;
310
+ /** Page title displayed in header */
311
+ title?: string;
312
+ /** Custom start content (left side, after menu/collapse buttons) */
236
313
  start?: Snippet;
314
+ /** Navigation content (center/right on desktop, hidden on mobile) */
315
+ nav?: Snippet;
237
316
  /** Custom end content (right side) */
238
317
  end?: Snippet;
318
+ /** Mobile navigation drawer content (appears below header bar when menu is open) */
319
+ mobileNav?: Snippet;
320
+ /** Breakpoint at which to switch between mobile and desktop layouts */
321
+ mobileBreakpoint?: 'sm' | 'md' | 'lg';
322
+ /** Use stronger/thicker border */
323
+ strongBorder?: boolean;
324
+ /** Enable backdrop blur effect */
325
+ backdropBlur?: boolean;
326
+ /** Use elevated z-index (z-50 instead of z-30) */
327
+ elevated?: boolean;
328
+ /** Constrain content to max-width container */
329
+ maxWidth?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl';
330
+ /** Search configuration */
331
+ search?: HeaderSearchConfig;
239
332
  /** Additional classes */
240
333
  class?: string;
241
334
  }
@@ -286,3 +379,80 @@ export interface FormPageLayoutProps {
286
379
  /** Additional classes for the container */
287
380
  class?: string;
288
381
  }
382
+ /**
383
+ * Footer link for AuthLayout
384
+ */
385
+ export interface AuthFooterLink {
386
+ /** Link label */
387
+ label: string;
388
+ /** Link URL */
389
+ href: string;
390
+ /** Opens in new tab */
391
+ external?: boolean;
392
+ }
393
+ /**
394
+ * Props for AuthLayout component
395
+ *
396
+ * A centered layout for authentication pages (login, signup, password reset, etc.)
397
+ * Composes AppShell for consistent base structure.
398
+ */
399
+ export interface AuthLayoutProps {
400
+ /** Custom logo snippet */
401
+ logo?: Snippet;
402
+ /** Logo subtitle for default logo (e.g., "Sign in to your account") */
403
+ logoSubtitle?: string;
404
+ /** Logo environment indicator for default logo */
405
+ logoEnvironment?: 'local' | 'dev' | 'demo';
406
+ /** Footer links (privacy policy, terms, etc.) */
407
+ footerLinks?: AuthFooterLink[];
408
+ /** Custom footer content (replaces footer links) */
409
+ footer?: Snippet;
410
+ /** Show decorative background */
411
+ showBackground?: boolean;
412
+ /** Background variant */
413
+ backgroundVariant?: 'default' | 'gradient' | 'pattern';
414
+ /** Maximum width of content card */
415
+ maxWidth?: 'sm' | 'md' | 'lg';
416
+ /** Additional classes for the container */
417
+ class?: string;
418
+ /** Main content (auth form) */
419
+ children: Snippet;
420
+ }
421
+ /**
422
+ * Props for ErrorLayout component
423
+ *
424
+ * A centered layout for error pages (404, 500, etc.)
425
+ * Composes AppShell for consistent base structure.
426
+ */
427
+ export interface ErrorLayoutProps {
428
+ /** HTTP status code (404, 500, etc.) */
429
+ statusCode?: number;
430
+ /** Custom title (overrides default for status code) */
431
+ title?: string;
432
+ /** Error description/message */
433
+ description?: string;
434
+ /** Show logo at top */
435
+ showLogo?: boolean;
436
+ /** Custom logo snippet */
437
+ logo?: Snippet;
438
+ /** Show "Go Home" button */
439
+ showHomeButton?: boolean;
440
+ /** Home button URL */
441
+ homeUrl?: string;
442
+ /** Home button text */
443
+ homeText?: string;
444
+ /** Show "Go Back" button */
445
+ showBackButton?: boolean;
446
+ /** Go back button text */
447
+ backText?: string;
448
+ /** Custom illustration/icon snippet (replaces status code display) */
449
+ illustration?: Snippet;
450
+ /** Additional action buttons or content */
451
+ actions?: Snippet;
452
+ /** Footer content */
453
+ footer?: Snippet;
454
+ /** Additional classes for the container */
455
+ class?: string;
456
+ /** Main content (custom error content, replaces default display) */
457
+ children?: Snippet;
458
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/theme-svelte",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Svelte components for the Classic theme system",
5
5
  "type": "module",
6
6
  "svelte": "./dist/lib/index.js",