@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.
Files changed (124) hide show
  1. package/README.md +305 -0
  2. package/dist/lib/components/Alert.svelte +51 -0
  3. package/dist/lib/components/Alert.svelte.d.ts +9 -0
  4. package/dist/lib/components/AlertDescription.svelte +16 -0
  5. package/dist/lib/components/AlertDescription.svelte.d.ts +9 -0
  6. package/dist/lib/components/AlertDialog.svelte +136 -0
  7. package/dist/lib/components/AlertDialog.svelte.d.ts +79 -0
  8. package/dist/lib/components/AlertTitle.svelte +16 -0
  9. package/dist/lib/components/AlertTitle.svelte.d.ts +9 -0
  10. package/dist/lib/components/Avatar.svelte +56 -0
  11. package/dist/lib/components/Avatar.svelte.d.ts +26 -0
  12. package/dist/lib/components/AvatarFallback.svelte +31 -0
  13. package/dist/lib/components/AvatarFallback.svelte.d.ts +17 -0
  14. package/dist/lib/components/AvatarImage.svelte +29 -0
  15. package/dist/lib/components/AvatarImage.svelte.d.ts +12 -0
  16. package/dist/lib/components/Badge.svelte +73 -0
  17. package/dist/lib/components/Badge.svelte.d.ts +11 -0
  18. package/dist/lib/components/Button.svelte +130 -0
  19. package/dist/lib/components/Button.svelte.d.ts +17 -0
  20. package/dist/lib/components/Card.svelte +58 -0
  21. package/dist/lib/components/Card.svelte.d.ts +26 -0
  22. package/dist/lib/components/CardContent.svelte +16 -0
  23. package/dist/lib/components/CardContent.svelte.d.ts +9 -0
  24. package/dist/lib/components/CardDescription.svelte +16 -0
  25. package/dist/lib/components/CardDescription.svelte.d.ts +9 -0
  26. package/dist/lib/components/CardFooter.svelte +16 -0
  27. package/dist/lib/components/CardFooter.svelte.d.ts +9 -0
  28. package/dist/lib/components/CardHeader.svelte +16 -0
  29. package/dist/lib/components/CardHeader.svelte.d.ts +9 -0
  30. package/dist/lib/components/CardTitle.svelte +16 -0
  31. package/dist/lib/components/CardTitle.svelte.d.ts +9 -0
  32. package/dist/lib/components/Checkbox.svelte +65 -0
  33. package/dist/lib/components/Checkbox.svelte.d.ts +14 -0
  34. package/dist/lib/components/DataTable.svelte +334 -0
  35. package/dist/lib/components/DataTable.svelte.d.ts +103 -0
  36. package/dist/lib/components/Dialog.svelte +111 -0
  37. package/dist/lib/components/Dialog.svelte.d.ts +22 -0
  38. package/dist/lib/components/DropdownMenu.svelte +135 -0
  39. package/dist/lib/components/DropdownMenu.svelte.d.ts +33 -0
  40. package/dist/lib/components/FileUpload.svelte +448 -0
  41. package/dist/lib/components/FileUpload.svelte.d.ts +42 -0
  42. package/dist/lib/components/FormField.svelte +134 -0
  43. package/dist/lib/components/FormField.svelte.d.ts +37 -0
  44. package/dist/lib/components/Input.svelte +61 -0
  45. package/dist/lib/components/Input.svelte.d.ts +19 -0
  46. package/dist/lib/components/Label.svelte +33 -0
  47. package/dist/lib/components/Label.svelte.d.ts +11 -0
  48. package/dist/lib/components/LoadingLogo.svelte +124 -0
  49. package/dist/lib/components/LoadingLogo.svelte.d.ts +16 -0
  50. package/dist/lib/components/LogoMain.svelte +237 -0
  51. package/dist/lib/components/LogoMain.svelte.d.ts +20 -0
  52. package/dist/lib/components/PageHeader.svelte +90 -0
  53. package/dist/lib/components/PageHeader.svelte.d.ts +28 -0
  54. package/dist/lib/components/Section.svelte +44 -0
  55. package/dist/lib/components/Section.svelte.d.ts +28 -0
  56. package/dist/lib/components/Select.svelte +174 -0
  57. package/dist/lib/components/Select.svelte.d.ts +32 -0
  58. package/dist/lib/components/Separator.svelte +29 -0
  59. package/dist/lib/components/Separator.svelte.d.ts +9 -0
  60. package/dist/lib/components/Skeleton.svelte +35 -0
  61. package/dist/lib/components/Skeleton.svelte.d.ts +7 -0
  62. package/dist/lib/components/Spinner.svelte +50 -0
  63. package/dist/lib/components/Spinner.svelte.d.ts +8 -0
  64. package/dist/lib/components/Switch.svelte +56 -0
  65. package/dist/lib/components/Switch.svelte.d.ts +14 -0
  66. package/dist/lib/components/TabPanel.svelte +44 -0
  67. package/dist/lib/components/TabPanel.svelte.d.ts +12 -0
  68. package/dist/lib/components/Tabs.svelte +125 -0
  69. package/dist/lib/components/Tabs.svelte.d.ts +19 -0
  70. package/dist/lib/components/Textarea.svelte +54 -0
  71. package/dist/lib/components/Textarea.svelte.d.ts +16 -0
  72. package/dist/lib/components/Toast.svelte +116 -0
  73. package/dist/lib/components/Toast.svelte.d.ts +12 -0
  74. package/dist/lib/components/ToastContainer.svelte +56 -0
  75. package/dist/lib/components/ToastContainer.svelte.d.ts +8 -0
  76. package/dist/lib/components/Tooltip.svelte +55 -0
  77. package/dist/lib/components/Tooltip.svelte.d.ts +18 -0
  78. package/dist/lib/components/layout/AppShell.svelte +82 -0
  79. package/dist/lib/components/layout/AppShell.svelte.d.ts +44 -0
  80. package/dist/lib/components/layout/DashboardLayout.svelte +248 -0
  81. package/dist/lib/components/layout/DashboardLayout.svelte.d.ts +62 -0
  82. package/dist/lib/components/layout/Footer.svelte +130 -0
  83. package/dist/lib/components/layout/Footer.svelte.d.ts +32 -0
  84. package/dist/lib/components/layout/FormPageLayout.svelte +92 -0
  85. package/dist/lib/components/layout/FormPageLayout.svelte.d.ts +33 -0
  86. package/dist/lib/components/layout/Header.svelte +94 -0
  87. package/dist/lib/components/layout/Header.svelte.d.ts +30 -0
  88. package/dist/lib/components/layout/PublicLayout.svelte +180 -0
  89. package/dist/lib/components/layout/PublicLayout.svelte.d.ts +39 -0
  90. package/dist/lib/components/layout/QuickLinks.svelte +112 -0
  91. package/dist/lib/components/layout/QuickLinks.svelte.d.ts +27 -0
  92. package/dist/lib/components/layout/Sidebar.svelte +243 -0
  93. package/dist/lib/components/layout/Sidebar.svelte.d.ts +48 -0
  94. package/dist/lib/composables/index.d.ts +8 -0
  95. package/dist/lib/composables/index.js +10 -0
  96. package/dist/lib/composables/useAsync.svelte.d.ts +102 -0
  97. package/dist/lib/composables/useAsync.svelte.js +210 -0
  98. package/dist/lib/composables/useForm.svelte.d.ts +123 -0
  99. package/dist/lib/composables/useForm.svelte.js +245 -0
  100. package/dist/lib/index.d.ts +65 -0
  101. package/dist/lib/index.js +83 -0
  102. package/dist/lib/performance.d.ts +79 -0
  103. package/dist/lib/performance.js +170 -0
  104. package/dist/lib/schemas/auth.d.ts +410 -0
  105. package/dist/lib/schemas/auth.js +216 -0
  106. package/dist/lib/schemas/common.d.ts +267 -0
  107. package/dist/lib/schemas/common.js +268 -0
  108. package/dist/lib/schemas/index.d.ts +24 -0
  109. package/dist/lib/schemas/index.js +32 -0
  110. package/dist/lib/stores/sidebar.svelte.d.ts +25 -0
  111. package/dist/lib/stores/sidebar.svelte.js +38 -0
  112. package/dist/lib/stores/theme.svelte.d.ts +72 -0
  113. package/dist/lib/stores/theme.svelte.js +150 -0
  114. package/dist/lib/stores/toast.svelte.d.ts +62 -0
  115. package/dist/lib/stores/toast.svelte.js +93 -0
  116. package/dist/lib/types/components.d.ts +85 -0
  117. package/dist/lib/types/components.js +7 -0
  118. package/dist/lib/types/layout.d.ts +258 -0
  119. package/dist/lib/types/layout.js +7 -0
  120. package/dist/lib/utils.d.ts +6 -0
  121. package/dist/lib/utils.js +9 -0
  122. package/dist/lib/validation.d.ts +101 -0
  123. package/dist/lib/validation.js +170 -0
  124. package/package.json +56 -0
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Header - Application header component
3
+ *
4
+ * Features:
5
+ * - Hamburger menu button for mobile
6
+ * - Customizable start and end slots
7
+ * - Optional navigation slot with uppercase styling
8
+ * - Responsive design
9
+ * - Stronger border styling to match brand guidelines
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ interface Props {
13
+ /** Whether to show hamburger menu button (mobile) */
14
+ showMenuButton?: boolean;
15
+ /** Callback when menu button is clicked */
16
+ onMenuClick?: () => void;
17
+ /** Custom start content (left side) */
18
+ start?: Snippet;
19
+ /** Navigation content (center/right on desktop, hidden on mobile) */
20
+ nav?: Snippet;
21
+ /** Custom end content (right side) */
22
+ end?: Snippet;
23
+ /** Use stronger/thicker border */
24
+ strongBorder?: boolean;
25
+ /** Additional classes */
26
+ class?: string;
27
+ }
28
+ declare const Header: import("svelte").Component<Props, {}, "">;
29
+ type Header = ReturnType<typeof Header>;
30
+ export default Header;
@@ -0,0 +1,180 @@
1
+ <script lang="ts">
2
+ /**
3
+ * PublicLayout - Layout for public-facing pages
4
+ *
5
+ * Features:
6
+ * - Header with navigation (uppercase bold links)
7
+ * - Footer with link sections
8
+ * - Main content area
9
+ * - Responsive design
10
+ * - Strong border options for brand consistency
11
+ */
12
+ import type { Snippet } from 'svelte';
13
+ import type { NavItem, NavSection } from '../../types/layout.js';
14
+ import { cn } from '../../utils.js';
15
+ import AppShell from './AppShell.svelte';
16
+ import Footer from './Footer.svelte';
17
+ import LogoMain from '../LogoMain.svelte';
18
+
19
+ interface Props {
20
+ /** Navigation items for header */
21
+ navigation?: NavItem[];
22
+ /** Whether to show footer */
23
+ showFooter?: boolean;
24
+ /** Footer link sections */
25
+ footerLinks?: NavSection[];
26
+ /** Footer copyright text */
27
+ copyright?: string;
28
+ /** Custom logo snippet */
29
+ logo?: Snippet;
30
+ /** Logo subtitle for default logo (e.g., "THEME", "MY HOME") */
31
+ logoSubtitle?: string;
32
+ /** Logo environment indicator for default logo */
33
+ logoEnvironment?: 'local' | 'dev' | 'demo';
34
+ /** Custom header end content */
35
+ headerEnd?: Snippet;
36
+ /** Use strong accent border on footer (10px top border) */
37
+ strongFooterBorder?: boolean;
38
+ /** Use dark footer variant */
39
+ darkFooter?: boolean;
40
+ /** Main content */
41
+ children: Snippet;
42
+ }
43
+
44
+ let {
45
+ navigation = [],
46
+ showFooter = true,
47
+ footerLinks = [],
48
+ copyright,
49
+ logo,
50
+ logoSubtitle,
51
+ logoEnvironment,
52
+ headerEnd,
53
+ strongFooterBorder = false,
54
+ darkFooter = false,
55
+ children,
56
+ }: Props = $props();
57
+
58
+ let mobileMenuOpen = $state(false);
59
+ </script>
60
+
61
+ <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"
65
+ >
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 -->
68
+ <a href="/" class="flex items-center">
69
+ {#if logo}
70
+ {@render logo()}
71
+ {:else}
72
+ <LogoMain
73
+ variant="horizontal"
74
+ color="dark"
75
+ size="md"
76
+ subtitle={logoSubtitle}
77
+ environment={logoEnvironment}
78
+ />
79
+ {/if}
80
+ </a>
81
+
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}
86
+ <a
87
+ href={item.href}
88
+ class={cn(
89
+ 'text-sm font-bold uppercase transition-colors hover:text-primary',
90
+ item.active ? 'text-primary' : 'text-muted-foreground'
91
+ )}
92
+ target={item.external ? '_blank' : undefined}
93
+ rel={item.external ? 'noopener noreferrer' : undefined}
94
+ >
95
+ {item.name}
96
+ </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 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-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>
165
+
166
+ <!-- Main Content -->
167
+ <main id="main-content" class="flex-1">
168
+ {@render children()}
169
+ </main>
170
+
171
+ <!-- Footer -->
172
+ {#if showFooter}
173
+ <Footer
174
+ links={footerLinks}
175
+ {copyright}
176
+ strongBorder={strongFooterBorder}
177
+ variant={darkFooter ? 'dark' : 'default'}
178
+ />
179
+ {/if}
180
+ </AppShell>
@@ -0,0 +1,39 @@
1
+ /**
2
+ * PublicLayout - Layout for public-facing pages
3
+ *
4
+ * Features:
5
+ * - Header with navigation (uppercase bold links)
6
+ * - Footer with link sections
7
+ * - Main content area
8
+ * - Responsive design
9
+ * - Strong border options for brand consistency
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ import type { NavItem, NavSection } from '../../types/layout.js';
13
+ interface Props {
14
+ /** Navigation items for header */
15
+ navigation?: NavItem[];
16
+ /** Whether to show footer */
17
+ showFooter?: boolean;
18
+ /** Footer link sections */
19
+ footerLinks?: NavSection[];
20
+ /** Footer copyright text */
21
+ copyright?: string;
22
+ /** Custom logo snippet */
23
+ logo?: Snippet;
24
+ /** Logo subtitle for default logo (e.g., "THEME", "MY HOME") */
25
+ logoSubtitle?: string;
26
+ /** Logo environment indicator for default logo */
27
+ logoEnvironment?: 'local' | 'dev' | 'demo';
28
+ /** Custom header end content */
29
+ headerEnd?: Snippet;
30
+ /** Use strong accent border on footer (10px top border) */
31
+ strongFooterBorder?: boolean;
32
+ /** Use dark footer variant */
33
+ darkFooter?: boolean;
34
+ /** Main content */
35
+ children: Snippet;
36
+ }
37
+ declare const PublicLayout: import("svelte").Component<Props, {}, "">;
38
+ type PublicLayout = ReturnType<typeof PublicLayout>;
39
+ export default PublicLayout;
@@ -0,0 +1,112 @@
1
+ <script lang="ts">
2
+ /**
3
+ * QuickLinks - Quick navigation links section for sidebar
4
+ *
5
+ * Features:
6
+ * - Two display modes: 'list' (stacked with labels) and 'icons' (horizontal icons only)
7
+ * - Light/dark variant support
8
+ * - External link indicator
9
+ * - Custom icon renderer support
10
+ * - Accessible with proper ARIA labels
11
+ */
12
+ import type { Snippet } from 'svelte';
13
+ import type { QuickLink } from '../../types/layout.js';
14
+ import { cn } from '../../utils.js';
15
+
16
+ interface Props {
17
+ /** Array of quick link items */
18
+ links: QuickLink[];
19
+ /** Display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
20
+ display?: 'list' | 'icons';
21
+ /** Visual variant - light (default) or dark */
22
+ variant?: 'light' | 'dark';
23
+ /** Custom icon renderer */
24
+ icon?: Snippet<[QuickLink]>;
25
+ /** Additional classes */
26
+ class?: string;
27
+ }
28
+
29
+ let { links, display = 'list', variant = 'light', icon, class: className }: Props = $props();
30
+
31
+ const isLight = $derived(variant === 'light');
32
+ </script>
33
+
34
+ {#if links.length > 0}
35
+ <div
36
+ class={cn(
37
+ display === 'list'
38
+ ? 'flex flex-col gap-1'
39
+ : 'flex flex-row items-center justify-center gap-2',
40
+ className
41
+ )}
42
+ role="navigation"
43
+ aria-label="Quick links"
44
+ >
45
+ {#each links as link}
46
+ <a
47
+ href={link.href}
48
+ class={cn(
49
+ 'flex items-center transition-colors',
50
+ // List mode styles
51
+ display === 'list' && 'gap-3 rounded-md px-3 py-2 text-sm',
52
+ display === 'list' &&
53
+ isLight &&
54
+ 'text-muted-foreground hover:bg-accent hover:text-foreground',
55
+ display === 'list' &&
56
+ !isLight &&
57
+ 'text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-foreground',
58
+ // Icon mode styles
59
+ display === 'icons' && 'rounded-md p-2',
60
+ display === 'icons' &&
61
+ isLight &&
62
+ 'text-muted-foreground hover:bg-accent hover:text-foreground',
63
+ display === 'icons' &&
64
+ !isLight &&
65
+ 'text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-foreground'
66
+ )}
67
+ target={link.external ? '_blank' : undefined}
68
+ rel={link.external ? 'noopener noreferrer' : undefined}
69
+ aria-label={display === 'icons' ? link.ariaLabel || link.label : undefined}
70
+ title={display === 'icons' ? link.label : undefined}
71
+ >
72
+ {#if icon}
73
+ <span class={cn('shrink-0', display === 'list' ? 'h-5 w-5' : 'h-5 w-5')}>
74
+ {@render icon(link)}
75
+ </span>
76
+ {:else if link.icon}
77
+ <!-- Default icon placeholder - apps should provide icon snippet -->
78
+ <span
79
+ class={cn(
80
+ 'shrink-0 flex items-center justify-center',
81
+ display === 'list' ? 'h-5 w-5' : 'h-5 w-5'
82
+ )}
83
+ >
84
+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
85
+ <circle cx="12" cy="12" r="10" stroke-width="2" />
86
+ </svg>
87
+ </span>
88
+ {/if}
89
+
90
+ {#if display === 'list'}
91
+ <span class="flex-1 truncate">{link.label}</span>
92
+
93
+ {#if link.external}
94
+ <svg
95
+ class="h-4 w-4 shrink-0 opacity-50"
96
+ fill="none"
97
+ viewBox="0 0 24 24"
98
+ stroke="currentColor"
99
+ >
100
+ <path
101
+ stroke-linecap="round"
102
+ stroke-linejoin="round"
103
+ stroke-width="2"
104
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
105
+ />
106
+ </svg>
107
+ {/if}
108
+ {/if}
109
+ </a>
110
+ {/each}
111
+ </div>
112
+ {/if}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * QuickLinks - Quick navigation links section for sidebar
3
+ *
4
+ * Features:
5
+ * - Two display modes: 'list' (stacked with labels) and 'icons' (horizontal icons only)
6
+ * - Light/dark variant support
7
+ * - External link indicator
8
+ * - Custom icon renderer support
9
+ * - Accessible with proper ARIA labels
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ import type { QuickLink } from '../../types/layout.js';
13
+ interface Props {
14
+ /** Array of quick link items */
15
+ links: QuickLink[];
16
+ /** Display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
17
+ display?: 'list' | 'icons';
18
+ /** Visual variant - light (default) or dark */
19
+ variant?: 'light' | 'dark';
20
+ /** Custom icon renderer */
21
+ icon?: Snippet<[QuickLink]>;
22
+ /** Additional classes */
23
+ class?: string;
24
+ }
25
+ declare const QuickLinks: import("svelte").Component<Props, {}, "">;
26
+ type QuickLinks = ReturnType<typeof QuickLinks>;
27
+ export default QuickLinks;
@@ -0,0 +1,243 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Sidebar - Responsive sidebar navigation component
4
+ *
5
+ * Features:
6
+ * - Collapsible on desktop
7
+ * - Slide-out drawer on mobile
8
+ * - Grouped navigation sections
9
+ * - Icon support via snippets
10
+ * - Active state highlighting
11
+ * - Light/dark variant support
12
+ * - Optional stronger border styling
13
+ * - Quick links section at bottom
14
+ */
15
+ import type { Snippet } from 'svelte';
16
+ import type { NavSection, NavItem, QuickLink } from '../../types/layout.js';
17
+ import { cn } from '../../utils.js';
18
+ import LogoMain from '../LogoMain.svelte';
19
+ import QuickLinks from './QuickLinks.svelte';
20
+
21
+ interface Props {
22
+ /** Navigation sections */
23
+ navigation: NavSection[];
24
+ /** Whether sidebar is collapsed (desktop) */
25
+ collapsed?: boolean;
26
+ /** Whether currently on mobile */
27
+ isMobile?: boolean;
28
+ /** Whether mobile sidebar is open */
29
+ mobileOpen?: boolean;
30
+ /** Callback when mobile sidebar should close */
31
+ onClose?: () => void;
32
+ /** Custom logo snippet */
33
+ logo?: Snippet;
34
+ /** Custom icon renderer for nav items */
35
+ icon?: Snippet<[NavItem]>;
36
+ /** Quick links displayed at bottom of sidebar */
37
+ quickLinks?: QuickLink[];
38
+ /** Quick links display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
39
+ quickLinksDisplay?: 'list' | 'icons';
40
+ /** Custom icon renderer for quick links */
41
+ quickLinkIcon?: Snippet<[QuickLink]>;
42
+ /** Custom footer content */
43
+ footer?: Snippet;
44
+ /** Visual variant - light (default) or dark */
45
+ variant?: 'light' | 'dark';
46
+ /** Use stronger/thicker border */
47
+ strongBorder?: boolean;
48
+ /** Additional classes */
49
+ class?: string;
50
+ }
51
+
52
+ let {
53
+ navigation,
54
+ collapsed = false,
55
+ isMobile = false,
56
+ mobileOpen = false,
57
+ onClose,
58
+ logo,
59
+ icon,
60
+ quickLinks,
61
+ quickLinksDisplay = 'list',
62
+ quickLinkIcon,
63
+ footer,
64
+ variant = 'light',
65
+ strongBorder = false,
66
+ class: className,
67
+ }: Props = $props();
68
+
69
+ // Variant-based styling
70
+ const isLight = $derived(variant === 'light');
71
+
72
+ const sidebarWidth = $derived(collapsed ? 'w-16' : 'w-64');
73
+ </script>
74
+
75
+ <!-- Mobile Overlay: z-40 positions below sidebar (z-50) but above main content -->
76
+ {#if isMobile && mobileOpen}
77
+ <div
78
+ class="fixed inset-0 z-40 bg-black/50 lg:hidden"
79
+ onclick={() => onClose?.()}
80
+ onkeydown={(e) => e.key === 'Escape' && onClose?.()}
81
+ role="button"
82
+ tabindex="0"
83
+ aria-label="Close sidebar"
84
+ ></div>
85
+ {/if}
86
+
87
+ <!-- Sidebar: z-50 positions above overlay and content, below modals (z-100+) -->
88
+ <aside
89
+ class={cn(
90
+ 'fixed left-0 top-0 z-[var(--z-sidebar,50)] flex h-full flex-col transition-all duration-300',
91
+ // Variant-based background and text colors
92
+ isLight ? 'bg-background text-foreground' : 'bg-sidebar text-sidebar-foreground',
93
+ strongBorder ? 'border-r-2 border-black' : 'border-r border-black',
94
+ isMobile ? (mobileOpen ? 'translate-x-0 w-64' : '-translate-x-full w-64') : sidebarWidth,
95
+ className
96
+ )}
97
+ aria-label="Main navigation"
98
+ >
99
+ <!-- Logo Section -->
100
+ <div
101
+ class={cn(
102
+ 'flex h-16 items-center border-b border-black px-4',
103
+ collapsed && !isMobile && 'justify-center'
104
+ )}
105
+ >
106
+ {#if logo}
107
+ {@render logo()}
108
+ {:else}
109
+ <LogoMain
110
+ variant={collapsed && !isMobile ? 'icon' : 'horizontal'}
111
+ color={isLight ? 'dark' : 'light'}
112
+ class={collapsed && !isMobile ? 'h-8 w-8' : 'h-8'}
113
+ />
114
+ {/if}
115
+ </div>
116
+
117
+ <!-- Navigation -->
118
+ <nav class="flex-1 overflow-y-auto py-4">
119
+ {#each navigation as section}
120
+ <div class="mb-4">
121
+ {#if section.title && !collapsed}
122
+ <h2
123
+ class={cn(
124
+ 'mb-2 px-4 text-xs font-semibold uppercase tracking-wider',
125
+ isLight ? 'text-muted-foreground' : 'text-sidebar-foreground/60'
126
+ )}
127
+ >
128
+ {section.title}
129
+ </h2>
130
+ {/if}
131
+
132
+ <ul class="space-y-1 px-2">
133
+ {#each section.items as item}
134
+ <li>
135
+ <a
136
+ href={item.href}
137
+ class={cn(
138
+ 'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
139
+ // Light variant styling
140
+ isLight &&
141
+ !item.active &&
142
+ 'text-foreground hover:bg-accent hover:text-accent-foreground',
143
+ isLight && item.active && 'bg-primary/10 text-primary',
144
+ // Dark variant styling
145
+ !isLight &&
146
+ !item.active &&
147
+ 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
148
+ !isLight && item.active && 'bg-sidebar-primary text-sidebar-primary-foreground',
149
+ collapsed && !isMobile && 'justify-center px-2'
150
+ )}
151
+ target={item.external ? '_blank' : undefined}
152
+ rel={item.external ? 'noopener noreferrer' : undefined}
153
+ aria-current={item.active ? 'page' : undefined}
154
+ title={collapsed && !isMobile ? item.name : undefined}
155
+ >
156
+ {#if icon}
157
+ <span class="h-5 w-5 shrink-0">
158
+ {@render icon(item)}
159
+ </span>
160
+ {:else if item.icon}
161
+ <span class="h-5 w-5 shrink-0 flex items-center justify-center">
162
+ <!-- Default icon placeholder - apps should provide icon snippet -->
163
+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
164
+ <circle cx="12" cy="12" r="10" stroke-width="2" />
165
+ </svg>
166
+ </span>
167
+ {/if}
168
+
169
+ {#if !collapsed || isMobile}
170
+ <span class="flex-1 truncate">{item.name}</span>
171
+
172
+ {#if item.badge !== undefined}
173
+ <span
174
+ class="ml-auto rounded-full bg-primary px-2 py-0.5 text-xs font-medium text-primary-foreground"
175
+ >
176
+ {item.badge}
177
+ </span>
178
+ {/if}
179
+
180
+ {#if item.external}
181
+ <svg
182
+ class="h-4 w-4 opacity-50"
183
+ fill="none"
184
+ viewBox="0 0 24 24"
185
+ stroke="currentColor"
186
+ >
187
+ <path
188
+ stroke-linecap="round"
189
+ stroke-linejoin="round"
190
+ stroke-width="2"
191
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
192
+ />
193
+ </svg>
194
+ {/if}
195
+ {/if}
196
+ </a>
197
+ </li>
198
+ {/each}
199
+ </ul>
200
+ </div>
201
+ {/each}
202
+ </nav>
203
+
204
+ <!-- Quick Links -->
205
+ {#if quickLinks && quickLinks.length > 0}
206
+ <div class={cn('border-t border-black px-2 py-3', collapsed && !isMobile && 'px-2')}>
207
+ <QuickLinks
208
+ links={quickLinks}
209
+ display={collapsed && !isMobile ? 'icons' : quickLinksDisplay}
210
+ {variant}
211
+ icon={quickLinkIcon}
212
+ />
213
+ </div>
214
+ {/if}
215
+
216
+ <!-- Footer -->
217
+ {#if footer}
218
+ <div class="border-t border-black p-4">
219
+ {@render footer()}
220
+ </div>
221
+ {/if}
222
+
223
+ <!-- Mobile Close Button -->
224
+ {#if isMobile}
225
+ <button
226
+ class={cn(
227
+ 'absolute right-2 top-2 rounded-md p-2',
228
+ isLight ? 'hover:bg-accent' : 'hover:bg-sidebar-accent'
229
+ )}
230
+ onclick={() => onClose?.()}
231
+ aria-label="Close sidebar"
232
+ >
233
+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
234
+ <path
235
+ stroke-linecap="round"
236
+ stroke-linejoin="round"
237
+ stroke-width="2"
238
+ d="M6 18L18 6M6 6l12 12"
239
+ />
240
+ </svg>
241
+ </button>
242
+ {/if}
243
+ </aside>
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Sidebar - Responsive sidebar navigation component
3
+ *
4
+ * Features:
5
+ * - Collapsible on desktop
6
+ * - Slide-out drawer on mobile
7
+ * - Grouped navigation sections
8
+ * - Icon support via snippets
9
+ * - Active state highlighting
10
+ * - Light/dark variant support
11
+ * - Optional stronger border styling
12
+ * - Quick links section at bottom
13
+ */
14
+ import type { Snippet } from 'svelte';
15
+ import type { NavSection, NavItem, QuickLink } from '../../types/layout.js';
16
+ interface Props {
17
+ /** Navigation sections */
18
+ navigation: NavSection[];
19
+ /** Whether sidebar is collapsed (desktop) */
20
+ collapsed?: boolean;
21
+ /** Whether currently on mobile */
22
+ isMobile?: boolean;
23
+ /** Whether mobile sidebar is open */
24
+ mobileOpen?: boolean;
25
+ /** Callback when mobile sidebar should close */
26
+ onClose?: () => void;
27
+ /** Custom logo snippet */
28
+ logo?: Snippet;
29
+ /** Custom icon renderer for nav items */
30
+ icon?: Snippet<[NavItem]>;
31
+ /** Quick links displayed at bottom of sidebar */
32
+ quickLinks?: QuickLink[];
33
+ /** Quick links display mode: 'list' for stacked with labels, 'icons' for horizontal icons only */
34
+ quickLinksDisplay?: 'list' | 'icons';
35
+ /** Custom icon renderer for quick links */
36
+ quickLinkIcon?: Snippet<[QuickLink]>;
37
+ /** Custom footer content */
38
+ footer?: Snippet;
39
+ /** Visual variant - light (default) or dark */
40
+ variant?: 'light' | 'dark';
41
+ /** Use stronger/thicker border */
42
+ strongBorder?: boolean;
43
+ /** Additional classes */
44
+ class?: string;
45
+ }
46
+ declare const Sidebar: import("svelte").Component<Props, {}, "">;
47
+ type Sidebar = ReturnType<typeof Sidebar>;
48
+ export default Sidebar;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Composables - Reusable Svelte 5 runes-based state management utilities
3
+ *
4
+ * These composables provide common patterns for form handling and async operations,
5
+ * built with Svelte 5 runes for optimal reactivity.
6
+ */
7
+ export { useForm, type UseFormOptions, type UseFormReturn, type FieldError, type FormState, type InferFormData, } from './useForm.svelte.js';
8
+ export { useAsync, runAsync, type UseAsyncOptions, type UseAsyncReturn, } from './useAsync.svelte.js';