@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
@@ -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';
@@ -246,14 +246,14 @@ export declare function paginatedResponseSchema<T extends z.ZodTypeAny>(itemSche
246
246
  pageSize: z.ZodNumber;
247
247
  totalPages: z.ZodNumber;
248
248
  }, "strip", z.ZodTypeAny, {
249
- items: T["_output"][];
250
249
  page: number;
250
+ items: T["_output"][];
251
251
  pageSize: number;
252
252
  total: number;
253
253
  totalPages: number;
254
254
  }, {
255
- items: T["_input"][];
256
255
  page: number;
256
+ items: T["_input"][];
257
257
  pageSize: number;
258
258
  total: number;
259
259
  totalPages: number;
@@ -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
  }
@@ -174,8 +235,18 @@ export interface PublicLayoutProps {
174
235
  copyright?: string;
175
236
  /** Custom logo snippet */
176
237
  logo?: Snippet;
238
+ /** Logo subtitle for default logo (e.g., "THEME", "MY HOME") */
239
+ logoSubtitle?: string;
240
+ /** Logo environment indicator for default logo */
241
+ logoEnvironment?: 'local' | 'dev' | 'demo';
177
242
  /** Custom header end content */
178
243
  headerEnd?: Snippet;
244
+ /** Use strong accent border on footer (10px top border) */
245
+ strongFooterBorder?: boolean;
246
+ /** Use dark footer variant */
247
+ darkFooter?: boolean;
248
+ /** Header search configuration */
249
+ headerSearch?: HeaderSearchConfig;
179
250
  /** Main content */
180
251
  children: Snippet;
181
252
  }
@@ -226,19 +297,53 @@ export interface SidebarProps {
226
297
  }
227
298
  /**
228
299
  * Props for Header component
300
+ *
301
+ * A unified header component that supports both dashboard and public layouts.
302
+ * Use mobileBreakpoint to control responsive behavior and mobileNav for
303
+ * inline mobile navigation drawers.
229
304
  */
230
305
  export interface HeaderProps {
231
306
  /** Whether to show hamburger menu button (mobile) */
232
307
  showMenuButton?: boolean;
308
+ /** Whether the mobile menu is currently open (for aria-expanded) */
309
+ menuOpen?: boolean;
233
310
  /** Callback when menu button is clicked */
234
311
  onMenuClick?: () => void;
235
- /** Custom start content (left side) */
312
+ /** Whether to show collapse/expand button (desktop sidebar toggle) */
313
+ showCollapseButton?: boolean;
314
+ /** Whether the sidebar is collapsed (for aria-expanded and icon direction) */
315
+ sidebarCollapsed?: boolean;
316
+ /** Callback when collapse button is clicked */
317
+ onCollapseClick?: () => void;
318
+ /** Page title displayed in header */
319
+ title?: string;
320
+ /** Custom start content (left side, after menu/collapse buttons) */
236
321
  start?: Snippet;
322
+ /** Navigation content (center/right on desktop, hidden on mobile) */
323
+ nav?: Snippet;
237
324
  /** Custom end content (right side) */
238
325
  end?: Snippet;
326
+ /** Mobile navigation drawer content (appears below header bar when menu is open) */
327
+ mobileNav?: Snippet;
328
+ /** Breakpoint at which to switch between mobile and desktop layouts */
329
+ mobileBreakpoint?: 'sm' | 'md' | 'lg';
330
+ /** Use stronger/thicker border */
331
+ strongBorder?: boolean;
332
+ /** Enable backdrop blur effect */
333
+ backdropBlur?: boolean;
334
+ /** Use elevated z-index (z-50 instead of z-30) */
335
+ elevated?: boolean;
336
+ /** Constrain content to max-width container */
337
+ maxWidth?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl';
338
+ /** Search configuration */
339
+ search?: HeaderSearchConfig;
239
340
  /** Additional classes */
240
341
  class?: string;
241
342
  }
343
+ /**
344
+ * Footer visual variant
345
+ */
346
+ export type FooterVariant = 'default' | 'dark';
242
347
  /**
243
348
  * Props for Footer component
244
349
  */
@@ -249,6 +354,10 @@ export interface FooterProps {
249
354
  copyright?: string;
250
355
  /** Show logo in footer */
251
356
  showLogo?: boolean;
357
+ /** Use strong accent border (10px top border) */
358
+ strongBorder?: boolean;
359
+ /** Dark variant with brand colors */
360
+ variant?: FooterVariant;
252
361
  /** Custom content */
253
362
  children?: Snippet;
254
363
  /** Additional classes */
@@ -286,3 +395,80 @@ export interface FormPageLayoutProps {
286
395
  /** Additional classes for the container */
287
396
  class?: string;
288
397
  }
398
+ /**
399
+ * Footer link for AuthLayout
400
+ */
401
+ export interface AuthFooterLink {
402
+ /** Link label */
403
+ label: string;
404
+ /** Link URL */
405
+ href: string;
406
+ /** Opens in new tab */
407
+ external?: boolean;
408
+ }
409
+ /**
410
+ * Props for AuthLayout component
411
+ *
412
+ * A centered layout for authentication pages (login, signup, password reset, etc.)
413
+ * Composes AppShell for consistent base structure.
414
+ */
415
+ export interface AuthLayoutProps {
416
+ /** Custom logo snippet */
417
+ logo?: Snippet;
418
+ /** Logo subtitle for default logo (e.g., "Sign in to your account") */
419
+ logoSubtitle?: string;
420
+ /** Logo environment indicator for default logo */
421
+ logoEnvironment?: 'local' | 'dev' | 'demo';
422
+ /** Footer links (privacy policy, terms, etc.) */
423
+ footerLinks?: AuthFooterLink[];
424
+ /** Custom footer content (replaces footer links) */
425
+ footer?: Snippet;
426
+ /** Show decorative background */
427
+ showBackground?: boolean;
428
+ /** Background variant */
429
+ backgroundVariant?: 'default' | 'gradient' | 'pattern';
430
+ /** Maximum width of content card */
431
+ maxWidth?: 'sm' | 'md' | 'lg';
432
+ /** Additional classes for the container */
433
+ class?: string;
434
+ /** Main content (auth form) */
435
+ children: Snippet;
436
+ }
437
+ /**
438
+ * Props for ErrorLayout component
439
+ *
440
+ * A centered layout for error pages (404, 500, etc.)
441
+ * Composes AppShell for consistent base structure.
442
+ */
443
+ export interface ErrorLayoutProps {
444
+ /** HTTP status code (404, 500, etc.) */
445
+ statusCode?: number;
446
+ /** Custom title (overrides default for status code) */
447
+ title?: string;
448
+ /** Error description/message */
449
+ description?: string;
450
+ /** Show logo at top */
451
+ showLogo?: boolean;
452
+ /** Custom logo snippet */
453
+ logo?: Snippet;
454
+ /** Show "Go Home" button */
455
+ showHomeButton?: boolean;
456
+ /** Home button URL */
457
+ homeUrl?: string;
458
+ /** Home button text */
459
+ homeText?: string;
460
+ /** Show "Go Back" button */
461
+ showBackButton?: boolean;
462
+ /** Go back button text */
463
+ backText?: string;
464
+ /** Custom illustration/icon snippet (replaces status code display) */
465
+ illustration?: Snippet;
466
+ /** Additional action buttons or content */
467
+ actions?: Snippet;
468
+ /** Footer content */
469
+ footer?: Snippet;
470
+ /** Additional classes for the container */
471
+ class?: string;
472
+ /** Main content (custom error content, replaces default display) */
473
+ children?: Snippet;
474
+ }
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.7",
4
4
  "description": "Svelte components for the Classic theme system",
5
5
  "type": "module",
6
6
  "svelte": "./dist/lib/index.js",