@miozu/jera 0.3.0 → 0.4.2

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 (72) hide show
  1. package/CLAUDE.md +350 -59
  2. package/README.md +30 -22
  3. package/llms.txt +37 -4
  4. package/package.json +12 -2
  5. package/src/components/docs/CodeBlock.svelte +203 -0
  6. package/src/components/docs/DocSection.svelte +120 -0
  7. package/src/components/docs/PropsTable.svelte +136 -0
  8. package/src/components/docs/SplitPane.svelte +98 -0
  9. package/src/components/docs/index.js +14 -0
  10. package/src/components/feedback/Alert.svelte +234 -0
  11. package/src/components/feedback/EmptyState.svelte +6 -6
  12. package/src/components/feedback/ProgressBar.svelte +8 -8
  13. package/src/components/feedback/Skeleton.svelte +4 -4
  14. package/src/components/feedback/Spinner.svelte +1 -1
  15. package/src/components/feedback/Toast.svelte +137 -173
  16. package/src/components/forms/Checkbox.svelte +10 -10
  17. package/src/components/forms/Dropzone.svelte +14 -14
  18. package/src/components/forms/FileUpload.svelte +16 -16
  19. package/src/components/forms/IconInput.svelte +4 -4
  20. package/src/components/forms/Input.svelte +14 -14
  21. package/src/components/forms/NumberInput.svelte +13 -13
  22. package/src/components/forms/PinInput.svelte +8 -8
  23. package/src/components/forms/Radio.svelte +8 -8
  24. package/src/components/forms/RangeSlider.svelte +12 -12
  25. package/src/components/forms/SearchInput.svelte +10 -10
  26. package/src/components/forms/Select.svelte +156 -158
  27. package/src/components/forms/Switch.svelte +4 -4
  28. package/src/components/forms/Textarea.svelte +9 -9
  29. package/src/components/navigation/Accordion.svelte +1 -1
  30. package/src/components/navigation/AccordionItem.svelte +6 -6
  31. package/src/components/navigation/NavigationContainer.svelte +344 -0
  32. package/src/components/navigation/Sidebar.svelte +334 -0
  33. package/src/components/navigation/SidebarAccountGroup.svelte +495 -0
  34. package/src/components/navigation/SidebarAccountItem.svelte +492 -0
  35. package/src/components/navigation/SidebarGroup.svelte +230 -0
  36. package/src/components/navigation/SidebarGroupSwitcher.svelte +262 -0
  37. package/src/components/navigation/SidebarItem.svelte +210 -0
  38. package/src/components/navigation/SidebarNavigationItem.svelte +470 -0
  39. package/src/components/navigation/SidebarPopover.svelte +145 -0
  40. package/src/components/navigation/SidebarSearch.svelte +236 -0
  41. package/src/components/navigation/SidebarSection.svelte +158 -0
  42. package/src/components/navigation/SidebarToggle.svelte +86 -0
  43. package/src/components/navigation/Tabs.svelte +18 -18
  44. package/src/components/navigation/WorkspaceMenu.svelte +416 -0
  45. package/src/components/navigation/blocks/NavigationAccountGroup.svelte +396 -0
  46. package/src/components/navigation/blocks/NavigationCustomBlock.svelte +74 -0
  47. package/src/components/navigation/blocks/NavigationGroupSwitcher.svelte +277 -0
  48. package/src/components/navigation/blocks/NavigationSearch.svelte +300 -0
  49. package/src/components/navigation/blocks/NavigationSection.svelte +230 -0
  50. package/src/components/navigation/index.js +22 -0
  51. package/src/components/overlays/ConfirmDialog.svelte +18 -18
  52. package/src/components/overlays/Dropdown.svelte +2 -2
  53. package/src/components/overlays/DropdownDivider.svelte +1 -1
  54. package/src/components/overlays/DropdownItem.svelte +5 -5
  55. package/src/components/overlays/Modal.svelte +13 -13
  56. package/src/components/overlays/Popover.svelte +3 -3
  57. package/src/components/primitives/Avatar.svelte +12 -12
  58. package/src/components/primitives/Badge.svelte +7 -7
  59. package/src/components/primitives/Button.svelte +126 -174
  60. package/src/components/primitives/Card.svelte +15 -15
  61. package/src/components/primitives/Divider.svelte +3 -3
  62. package/src/components/primitives/LazyImage.svelte +1 -1
  63. package/src/components/primitives/Link.svelte +2 -2
  64. package/src/components/primitives/Stat.svelte +197 -0
  65. package/src/components/primitives/StatusBadge.svelte +24 -24
  66. package/src/index.js +62 -7
  67. package/src/tokens/colors.css +96 -128
  68. package/src/utils/highlighter.js +124 -0
  69. package/src/utils/index.js +7 -2
  70. package/src/utils/navigation.svelte.js +423 -0
  71. package/src/utils/reactive.svelte.js +126 -37
  72. package/src/utils/sidebar.svelte.js +211 -0
@@ -0,0 +1,344 @@
1
+ <!--
2
+ @component NavigationContainer
3
+
4
+ Enterprise-grade navigation container with pluggable block architecture.
5
+ Supports multiple navigation patterns, themes, and enterprise features.
6
+
7
+ @example Basic usage
8
+ <NavigationContainer
9
+ navigationState={navState}
10
+ blocks={[
11
+ { type: 'search', id: 'main-search' },
12
+ { type: 'section', id: 'nav', title: 'Navigation', items: navItems },
13
+ { type: 'account-group', id: 'accounts', accounts: accounts },
14
+ { type: 'custom', id: 'footer', component: CustomFooter }
15
+ ]}
16
+ />
17
+
18
+ @example Enterprise usage
19
+ <NavigationContainer
20
+ navigationState={navState}
21
+ theme="enterprise-dark"
22
+ blocks={complexBlocks}
23
+ plugins={[searchPlugin, analyticsPlugin]}
24
+ permissions={permissionSystem}
25
+ />
26
+ -->
27
+ <script>
28
+ import { setContext, getContext } from 'svelte';
29
+ import { fade } from 'svelte/transition';
30
+ import { NAVIGATION_CONTEXT_KEY } from '../../utils/navigation.svelte.js';
31
+ import { SIDEBAR_CONTEXT_KEY } from '../../utils/sidebar.svelte.js';
32
+
33
+ // Block components
34
+ import NavigationSearch from './blocks/NavigationSearch.svelte';
35
+ import NavigationSection from './blocks/NavigationSection.svelte';
36
+ import NavigationAccountGroup from './blocks/NavigationAccountGroup.svelte';
37
+ import NavigationGroupSwitcher from './blocks/NavigationGroupSwitcher.svelte';
38
+ import NavigationCustomBlock from './blocks/NavigationCustomBlock.svelte';
39
+
40
+ let {
41
+ navigationState,
42
+ blocks = [],
43
+ theme = 'default',
44
+ plugins = [],
45
+ permissions = null,
46
+ renderCustomBlock = null,
47
+ onBlockEvent = null,
48
+ class: className = ''
49
+ } = $props();
50
+
51
+ // Set navigation context for child components
52
+ setContext(NAVIGATION_CONTEXT_KEY, navigationState);
53
+
54
+ // Get sidebar context if available
55
+ const sidebarContext = getContext(SIDEBAR_CONTEXT_KEY);
56
+ const isCollapsed = $derived(sidebarContext?.collapsed ?? false);
57
+
58
+ // Built-in block type registry
59
+ const blockComponents = {
60
+ 'search': NavigationSearch,
61
+ 'section': NavigationSection,
62
+ 'account-group': NavigationAccountGroup,
63
+ 'group-switcher': NavigationGroupSwitcher,
64
+ 'custom': NavigationCustomBlock
65
+ };
66
+
67
+ // Computed theme classes
68
+ const themeClass = $derived(`nav-theme-${theme}`);
69
+ const containerClass = $derived([
70
+ 'navigation-container',
71
+ themeClass,
72
+ isCollapsed && 'collapsed',
73
+ className
74
+ ].filter(Boolean).join(' '));
75
+
76
+ // Initialize plugins
77
+ $effect(() => {
78
+ plugins.forEach(plugin => {
79
+ if (typeof plugin.init === 'function') {
80
+ plugin.init({ navigationState, blocks, theme, permissions });
81
+ }
82
+ });
83
+
84
+ return () => {
85
+ plugins.forEach(plugin => {
86
+ if (typeof plugin.destroy === 'function') {
87
+ plugin.destroy();
88
+ }
89
+ });
90
+ };
91
+ });
92
+
93
+ // Handle block events
94
+ function handleBlockEvent(blockId, eventType, data) {
95
+ // Call plugin event handlers
96
+ plugins.forEach(plugin => {
97
+ if (typeof plugin.onBlockEvent === 'function') {
98
+ plugin.onBlockEvent(blockId, eventType, data);
99
+ }
100
+ });
101
+
102
+ // Call user event handler
103
+ if (onBlockEvent) {
104
+ onBlockEvent(blockId, eventType, data);
105
+ }
106
+ }
107
+
108
+ // Get component for block type
109
+ function getBlockComponent(block) {
110
+ if (block.component) {
111
+ return block.component;
112
+ }
113
+
114
+ if (block.type && blockComponents[block.type]) {
115
+ return blockComponents[block.type];
116
+ }
117
+
118
+ // Fallback to custom block
119
+ return NavigationCustomBlock;
120
+ }
121
+
122
+ // Check if block should be visible
123
+ function isBlockVisible(block) {
124
+ // Check permissions
125
+ if (permissions && block.permissions) {
126
+ const hasPermission = Array.isArray(block.permissions)
127
+ ? block.permissions.some(perm => permissions.check(perm))
128
+ : permissions.check(block.permissions);
129
+
130
+ if (!hasPermission) return false;
131
+ }
132
+
133
+ // Check custom visibility function
134
+ if (typeof block.visible === 'function') {
135
+ return block.visible({ navigationState, isCollapsed });
136
+ }
137
+
138
+ // Check boolean visibility
139
+ if (typeof block.visible === 'boolean') {
140
+ return block.visible;
141
+ }
142
+
143
+ // Default to visible
144
+ return true;
145
+ }
146
+
147
+ // Filter visible blocks
148
+ const visibleBlocks = $derived(blocks.filter(isBlockVisible));
149
+ </script>
150
+
151
+ <nav class={containerClass} data-theme={theme}>
152
+ {#each visibleBlocks as block, index (block.id)}
153
+ <div
154
+ class="nav-block nav-block-{block.type}"
155
+ data-block-id={block.id}
156
+ data-block-type={block.type}
157
+ transition:fade={{ duration: 200, delay: index * 50 }}
158
+ >
159
+ {#if renderCustomBlock && block.type === 'custom'}
160
+ {@render renderCustomBlock(block, { navigationState, isCollapsed, onEvent: (type, data) => handleBlockEvent(block.id, type, data) })}
161
+ {:else}
162
+ {@const BlockComponent = getBlockComponent(block)}
163
+ <BlockComponent
164
+ {block}
165
+ {navigationState}
166
+ onEvent={(type, data) => handleBlockEvent(block.id, type, data)}
167
+ {...(block.props || {})}
168
+ />
169
+ {/if}
170
+ </div>
171
+ {/each}
172
+ </nav>
173
+
174
+ <style>
175
+ .navigation-container {
176
+ display: flex;
177
+ flex-direction: column;
178
+ width: 100%;
179
+ height: 100%;
180
+ overflow-y: auto;
181
+ overflow-x: hidden;
182
+
183
+ /* CSS Custom Properties for theming */
184
+ --nav-container-bg: var(--color-surface, var(--color-base01, #2C3040));
185
+ --nav-container-border: var(--color-border, color-mix(in srgb, var(--color-base03, #565E78) 30%, transparent));
186
+ --nav-container-text: var(--color-text, var(--color-base06, #F3F4F7));
187
+
188
+ /* Spacing */
189
+ --nav-block-spacing: 0.5rem;
190
+ --nav-indent-size: 1rem;
191
+
192
+ /* Item styling */
193
+ --nav-item-padding: 0.375rem 0.75rem;
194
+ --nav-item-margin: 0 0.5rem 0.125rem 0.5rem;
195
+ --nav-item-gap: 0.5rem;
196
+ --nav-item-border-radius: 0.375rem;
197
+ --nav-item-font-size: 0.875rem;
198
+
199
+ /* Colors */
200
+ --nav-item-color: var(--color-text, var(--color-base06, #F3F4F7));
201
+ --nav-item-background: transparent;
202
+ --nav-item-border: none;
203
+
204
+ /* Hover states */
205
+ --nav-hover-opacity: 10%;
206
+ --nav-item-hover-color: var(--color-primary, var(--color-base0D, #83D2FC));
207
+ --nav-item-hover-background: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) var(--nav-hover-opacity), transparent);
208
+
209
+ /* Active states */
210
+ --nav-active-opacity: 15%;
211
+ --nav-item-active-color: var(--color-primary, var(--color-base0D, #83D2FC));
212
+ --nav-item-active-background: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) var(--nav-active-opacity), transparent);
213
+ --nav-item-active-weight: 500;
214
+
215
+ /* Icons */
216
+ --nav-icon-size: 18px;
217
+ --nav-icon-container-size: 1.5rem;
218
+
219
+ /* Badges */
220
+ --nav-badge-padding: 0.125rem 0.375rem;
221
+ --nav-badge-font-size: 0.625rem;
222
+ --nav-badge-weight: 600;
223
+ --nav-badge-opacity: 10%;
224
+ --nav-badge-color: var(--color-primary, var(--color-base0D, #83D2FC));
225
+ --nav-badge-background: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) var(--nav-badge-opacity), transparent);
226
+ --nav-badge-radius: 9999px;
227
+ --nav-badge-min-width: 1rem;
228
+ --nav-badge-height: 1rem;
229
+
230
+ /* Expand buttons */
231
+ --nav-expand-icon-size: 14px;
232
+ --nav-expand-button-size: 1.25rem;
233
+ --nav-expand-button-color: var(--color-text-muted, var(--color-base05, #D0D2DB));
234
+ --nav-expand-button-hover-bg: color-mix(in srgb, var(--color-text-muted, var(--color-base05, #D0D2DB)) 10%, transparent);
235
+ --nav-expand-button-hover-color: var(--color-text, var(--color-base06, #F3F4F7));
236
+ --nav-expand-button-radius: 0.25rem;
237
+
238
+ /* Transitions */
239
+ --nav-transition-duration: 200ms;
240
+ --nav-transition-easing: ease;
241
+
242
+ /* Search */
243
+ --nav-search-highlight-bg: color-mix(in srgb, var(--color-warning, var(--color-base0A, #E8D176)) 10%, transparent);
244
+ --nav-search-highlight-border: 1px solid color-mix(in srgb, var(--color-warning, var(--color-base0A, #E8D176)) 30%, transparent);
245
+ --nav-search-indicator-size: 0.375rem;
246
+ --nav-search-indicator-color: var(--color-warning, var(--color-base0A, #E8D176));
247
+ --nav-search-indicator-offset: 0.25rem;
248
+
249
+ /* Children/nesting */
250
+ --nav-children-border: 1px solid color-mix(in srgb, var(--color-base03, #565E78) 30%, transparent);
251
+ --nav-children-margin-left: calc(var(--nav-icon-container-size) / 2);
252
+ --nav-children-padding-left: 0.5rem;
253
+
254
+ /* Collapsed state */
255
+ --nav-item-collapsed-padding: 0.5rem;
256
+ --nav-item-collapsed-margin: 0 0.25rem 0.125rem 0.25rem;
257
+ --nav-item-collapsed-margin-offset: 0.5rem;
258
+
259
+ /* Depth-based styling */
260
+ --nav-depth-0-weight: 500;
261
+ --nav-depth-1-size: 0.8125rem;
262
+ --nav-depth-2-size: 0.75rem;
263
+ --nav-depth-2-opacity: 0.9;
264
+
265
+ /* Mobile responsive */
266
+ --nav-item-mobile-padding: 0.5rem 0.75rem;
267
+ --nav-children-mobile-margin: 0.75rem;
268
+
269
+ background: var(--nav-container-bg);
270
+ color: var(--nav-container-text);
271
+ }
272
+
273
+ .navigation-container.collapsed {
274
+ --nav-item-padding: var(--nav-item-collapsed-padding);
275
+ --nav-item-margin: var(--nav-item-collapsed-margin);
276
+ }
277
+
278
+ /* Theme variants */
279
+ .nav-theme-enterprise-dark {
280
+ --nav-container-bg: #1a1b23;
281
+ --nav-item-color: #e4e4e7;
282
+ --nav-item-hover-color: #3b82f6;
283
+ --nav-item-active-color: #3b82f6;
284
+ --nav-hover-opacity: 8%;
285
+ --nav-active-opacity: 12%;
286
+ }
287
+
288
+ .nav-theme-enterprise-light {
289
+ --nav-container-bg: #ffffff;
290
+ --nav-item-color: #374151;
291
+ --nav-item-hover-color: #3b82f6;
292
+ --nav-item-active-color: #3b82f6;
293
+ --nav-hover-opacity: 5%;
294
+ --nav-active-opacity: 10%;
295
+ }
296
+
297
+ .nav-theme-minimal {
298
+ --nav-item-border-radius: 0;
299
+ --nav-item-padding: 0.25rem 0.5rem;
300
+ --nav-hover-opacity: 5%;
301
+ --nav-active-opacity: 8%;
302
+ }
303
+
304
+ .nav-theme-colorful {
305
+ --nav-item-hover-color: #10b981;
306
+ --nav-item-active-color: #059669;
307
+ --nav-badge-color: #f59e0b;
308
+ --nav-hover-opacity: 12%;
309
+ --nav-active-opacity: 18%;
310
+ }
311
+
312
+ /* Block containers */
313
+ .nav-block {
314
+ margin-bottom: var(--nav-block-spacing);
315
+ }
316
+
317
+ .nav-block:last-child {
318
+ margin-bottom: 0;
319
+ }
320
+
321
+ /* Scrollbar styling */
322
+ .navigation-container::-webkit-scrollbar {
323
+ width: 4px;
324
+ }
325
+
326
+ .navigation-container::-webkit-scrollbar-track {
327
+ background: transparent;
328
+ }
329
+
330
+ .navigation-container::-webkit-scrollbar-thumb {
331
+ background: var(--color-base03, #565E78);
332
+ border-radius: 2px;
333
+ }
334
+
335
+ .navigation-container::-webkit-scrollbar-thumb:hover {
336
+ background: var(--color-base04, #737E99);
337
+ }
338
+
339
+ /* Firefox scrollbar */
340
+ .navigation-container {
341
+ scrollbar-width: thin;
342
+ scrollbar-color: var(--color-base03, #565E78) transparent;
343
+ }
344
+ </style>
@@ -0,0 +1,334 @@
1
+ <!--
2
+ @component Sidebar
3
+
4
+ Flexible, collapsible sidebar container with smooth width transitions.
5
+ Provides context to child components for collapsed state awareness.
6
+
7
+ @example Basic usage (admin style)
8
+ <Sidebar bind:collapsed persistKey="admin-sidebar">
9
+ {#snippet header()}
10
+ <div class="p-4 border-b border-base02">
11
+ <span class="logo">Selify</span>
12
+ </div>
13
+ {/snippet}
14
+
15
+ <SidebarSection title="Navigation">
16
+ <SidebarItem href="/" icon={Home} label="Home" />
17
+ </SidebarSection>
18
+
19
+ {#snippet footer()}
20
+ <div class="p-4">
21
+ <SidebarToggle />
22
+ </div>
23
+ {/snippet}
24
+ </Sidebar>
25
+
26
+ @example Complex usage (dashboard style with custom header component)
27
+ <Sidebar bind:collapsed persistKey="dash-sidebar">
28
+ {#snippet header()}
29
+ <UserDropdown {collapsed} />
30
+ {/snippet}
31
+
32
+ <SidebarSection title="Main">
33
+ <SidebarItem href="/" icon={Home} label="Home" />
34
+ </SidebarSection>
35
+
36
+ <SidebarSection title="Accounts">
37
+ <SidebarAccountItem platform="instagram" label="@myaccount" />
38
+ </SidebarSection>
39
+
40
+ {#snippet footer()}
41
+ <SidebarToggle />
42
+ {/snippet}
43
+ </Sidebar>
44
+ -->
45
+ <script>
46
+ import { setContext, onMount } from 'svelte';
47
+ import { fly } from 'svelte/transition';
48
+ import { SIDEBAR_CONTEXT_KEY } from '../../utils/sidebar.svelte.js';
49
+
50
+ let {
51
+ collapsed = $bindable(false),
52
+ width = 240,
53
+ collapsedWidth = 64,
54
+ headerHeight = 60,
55
+ persistKey = null,
56
+ mobileOverlay = false,
57
+ overlayOpen = $bindable(false),
58
+ position = 'left',
59
+ class: className = '',
60
+ header,
61
+ footer,
62
+ children
63
+ } = $props();
64
+
65
+ // Internal collapsed state that syncs with prop
66
+ let isCollapsed = $state(collapsed);
67
+
68
+ // Sync internal state with prop
69
+ $effect(() => {
70
+ isCollapsed = collapsed;
71
+ });
72
+
73
+ // Update prop when internal state changes
74
+ $effect(() => {
75
+ collapsed = isCollapsed;
76
+ });
77
+
78
+ // Toggle function
79
+ function toggle() {
80
+ isCollapsed = !isCollapsed;
81
+ }
82
+
83
+ // Hover popover state
84
+ let hoverPopover = $state({
85
+ item: null,
86
+ position: { top: 0, left: 0 }
87
+ });
88
+ let hoverTimeout = null;
89
+
90
+ function showPopover(itemId, position) {
91
+ if (!isCollapsed) return;
92
+
93
+ if (hoverTimeout) {
94
+ clearTimeout(hoverTimeout);
95
+ hoverTimeout = null;
96
+ }
97
+
98
+ hoverPopover = { item: itemId, position };
99
+ }
100
+
101
+ function hidePopover(delay = 150) {
102
+ if (hoverTimeout) clearTimeout(hoverTimeout);
103
+
104
+ hoverTimeout = setTimeout(() => {
105
+ hoverPopover = { item: null, position: { top: 0, left: 0 } };
106
+ }, delay);
107
+ }
108
+
109
+ function keepPopoverOpen() {
110
+ if (hoverTimeout) {
111
+ clearTimeout(hoverTimeout);
112
+ hoverTimeout = null;
113
+ }
114
+ }
115
+
116
+ // Load from localStorage
117
+ onMount(() => {
118
+ if (persistKey && typeof localStorage !== 'undefined') {
119
+ try {
120
+ const saved = localStorage.getItem(persistKey);
121
+ if (saved !== null) {
122
+ isCollapsed = saved === 'true';
123
+ }
124
+ } catch (e) {
125
+ // Ignore localStorage errors
126
+ }
127
+ }
128
+ });
129
+
130
+ // Save to localStorage when state changes
131
+ $effect(() => {
132
+ if (persistKey && typeof localStorage !== 'undefined') {
133
+ try {
134
+ localStorage.setItem(persistKey, String(isCollapsed));
135
+ } catch (e) {
136
+ // Ignore localStorage errors
137
+ }
138
+ }
139
+ });
140
+
141
+ // Mobile functionality
142
+ function closeMobileOverlay() {
143
+ if (mobileOverlay) {
144
+ overlayOpen = false;
145
+ }
146
+ }
147
+
148
+ // Provide context to children
149
+ setContext(SIDEBAR_CONTEXT_KEY, {
150
+ get collapsed() { return isCollapsed; },
151
+ get width() { return width; },
152
+ get collapsedWidth() { return collapsedWidth; },
153
+ get mobileOverlay() { return mobileOverlay; },
154
+ toggle,
155
+ expand() { isCollapsed = false; },
156
+ collapse() { isCollapsed = true; },
157
+ closeMobileOverlay,
158
+ showPopover,
159
+ hidePopover,
160
+ keepPopoverOpen,
161
+ get hoverPopover() { return hoverPopover; }
162
+ });
163
+
164
+ // Computed styles
165
+ const sidebarStyle = $derived(
166
+ `--sidebar-width: ${width}px; --sidebar-collapsed-width: ${collapsedWidth}px; --sidebar-header-height: ${headerHeight}px;`
167
+ );
168
+ </script>
169
+
170
+ {#if mobileOverlay}
171
+ <!-- Mobile overlay mode -->
172
+ {#if overlayOpen}
173
+ <div class="sidebar-overlay" onclick={closeMobileOverlay}></div>
174
+ <aside
175
+ class="sidebar mobile-overlay {className} {position}"
176
+ class:collapsed={isCollapsed}
177
+ style={sidebarStyle}
178
+ transition:fly={{x: position === 'left' ? -300 : 300, duration: 200}}
179
+ >
180
+ {#if header}
181
+ <div class="sidebar-header">
182
+ {@render header()}
183
+ <button class="mobile-close-btn" onclick={closeMobileOverlay}>
184
+ <svg
185
+ xmlns="http://www.w3.org/2000/svg"
186
+ width="16"
187
+ height="16"
188
+ viewBox="0 0 24 24"
189
+ fill="none"
190
+ stroke="currentColor"
191
+ stroke-width="2"
192
+ stroke-linecap="round"
193
+ stroke-linejoin="round"
194
+ >
195
+ <line x1="18" y1="6" x2="6" y2="18"></line>
196
+ <line x1="6" y1="6" x2="18" y2="18"></line>
197
+ </svg>
198
+ </button>
199
+ </div>
200
+ {/if}
201
+
202
+ <nav class="sidebar-content">
203
+ {@render children?.()}
204
+ </nav>
205
+
206
+ {#if footer}
207
+ <div class="sidebar-footer">
208
+ {@render footer()}
209
+ </div>
210
+ {/if}
211
+ </aside>
212
+ {/if}
213
+ {:else}
214
+ <!-- Default desktop mode -->
215
+ <aside
216
+ class="sidebar {className}"
217
+ class:collapsed={isCollapsed}
218
+ style={sidebarStyle}
219
+ >
220
+ {#if header}
221
+ <div class="sidebar-header">
222
+ {@render header()}
223
+ </div>
224
+ {/if}
225
+
226
+ <nav class="sidebar-content">
227
+ {@render children?.()}
228
+ </nav>
229
+
230
+ {#if footer}
231
+ <div class="sidebar-footer">
232
+ {@render footer()}
233
+ </div>
234
+ {/if}
235
+ </aside>
236
+ {/if}
237
+
238
+ <style>
239
+ .sidebar {
240
+ display: flex;
241
+ flex-direction: column;
242
+ background-color: var(--color-surface, var(--color-base01, #2C3040));
243
+ border-right: 1px solid var(--color-border, color-mix(in srgb, var(--color-base03, #565E78) 30%, transparent));
244
+ height: 100vh;
245
+ width: var(--sidebar-width);
246
+ transition: width 200ms cubic-bezier(0.4, 0, 0.2, 1);
247
+ position: sticky;
248
+ top: 0;
249
+ will-change: width;
250
+ overflow: hidden;
251
+ }
252
+
253
+ .sidebar.collapsed {
254
+ width: var(--sidebar-collapsed-width);
255
+ }
256
+
257
+ .sidebar-header {
258
+ flex-shrink: 0;
259
+ min-height: var(--sidebar-header-height);
260
+ display: flex;
261
+ align-items: center;
262
+ }
263
+
264
+ .sidebar-content {
265
+ flex: 1;
266
+ overflow-y: auto;
267
+ overflow-x: hidden;
268
+ padding: 0.5rem 0;
269
+ }
270
+
271
+ .sidebar-footer {
272
+ flex-shrink: 0;
273
+ margin-top: auto;
274
+ }
275
+
276
+ /* Scrollbar styling */
277
+ .sidebar-content::-webkit-scrollbar {
278
+ width: 4px;
279
+ }
280
+
281
+ .sidebar-content::-webkit-scrollbar-track {
282
+ background: transparent;
283
+ }
284
+
285
+ .sidebar-content::-webkit-scrollbar-thumb {
286
+ background: var(--color-base03, #565E78);
287
+ border-radius: 2px;
288
+ }
289
+
290
+ .sidebar-content::-webkit-scrollbar-thumb:hover {
291
+ background: var(--color-base04, #737E99);
292
+ }
293
+
294
+ /* Mobile overlay styles */
295
+ .sidebar-overlay {
296
+ position: fixed;
297
+ inset: 0;
298
+ background-color: rgba(0, 0, 0, 0.5);
299
+ z-index: 40;
300
+ }
301
+
302
+ .sidebar.mobile-overlay {
303
+ position: fixed;
304
+ z-index: 50;
305
+ height: 100vh;
306
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
307
+ }
308
+
309
+ .sidebar.mobile-overlay.left {
310
+ left: 0;
311
+ top: 0;
312
+ }
313
+
314
+ .sidebar.mobile-overlay.right {
315
+ right: 0;
316
+ top: 0;
317
+ }
318
+
319
+ .mobile-close-btn {
320
+ padding: 0.5rem;
321
+ border-radius: 0.5rem;
322
+ color: var(--color-text-muted, var(--color-base05, #D0D2DB));
323
+ cursor: pointer;
324
+ border: none;
325
+ background: transparent;
326
+ transition: all 200ms ease;
327
+ margin-left: auto;
328
+ }
329
+
330
+ .mobile-close-btn:hover {
331
+ background-color: var(--color-surface-alt, var(--color-base02, #3E4359));
332
+ color: var(--color-text, var(--color-base06, #F3F4F7));
333
+ }
334
+ </style>