@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,230 @@
1
+ <!--
2
+ @component SidebarGroup
3
+
4
+ Expandable navigation group with subroutes.
5
+ Shows chevron indicator and animates subnav list.
6
+ Triggers hover popover when sidebar is collapsed.
7
+
8
+ @example
9
+ <SidebarGroup
10
+ icon={Settings}
11
+ label="Settings"
12
+ items={[
13
+ { href: '/settings/general', label: 'General' },
14
+ { href: '/settings/security', label: 'Security' },
15
+ { href: '/settings/billing', label: 'Billing', badge: 'New' }
16
+ ]}
17
+ activeMatcher={(item) => currentPath.startsWith(item.href)}
18
+ />
19
+ -->
20
+ <script>
21
+ import { getContext } from 'svelte';
22
+ import { slide, fade, fly } from 'svelte/transition';
23
+ import { cubicOut } from 'svelte/easing';
24
+ import { SIDEBAR_CONTEXT_KEY } from '../../utils/sidebar.svelte.js';
25
+
26
+ let {
27
+ id = null,
28
+ icon: Icon = null,
29
+ label = '',
30
+ expanded = $bindable(false),
31
+ items = [],
32
+ activeMatcher = null,
33
+ class: className = ''
34
+ } = $props();
35
+
36
+ const sidebar = getContext(SIDEBAR_CONTEXT_KEY);
37
+ const isCollapsed = $derived(sidebar?.collapsed ?? false);
38
+
39
+ // Generate unique ID if not provided
40
+ const groupId = $derived(id || label.toLowerCase().replace(/\s+/g, '-'));
41
+
42
+ function toggleExpanded() {
43
+ expanded = !expanded;
44
+ }
45
+
46
+ function handleMouseEnter(event) {
47
+ if (!isCollapsed || !sidebar?.showPopover) return;
48
+
49
+ const rect = event.currentTarget.getBoundingClientRect();
50
+ sidebar.showPopover(groupId, {
51
+ top: rect.top,
52
+ left: rect.right + 8
53
+ });
54
+ }
55
+
56
+ function handleMouseLeave() {
57
+ if (sidebar?.hidePopover) {
58
+ sidebar.hidePopover();
59
+ }
60
+ }
61
+
62
+ function isItemActive(item) {
63
+ if (activeMatcher) return activeMatcher(item);
64
+ return false;
65
+ }
66
+ </script>
67
+
68
+ <li>
69
+ <button
70
+ type="button"
71
+ class="nav-item expandable {isCollapsed ? 'collapsed' : ''} {className}"
72
+ onclick={toggleExpanded}
73
+ onmouseenter={handleMouseEnter}
74
+ onmouseleave={handleMouseLeave}
75
+ title={isCollapsed ? label : null}
76
+ aria-expanded={expanded}
77
+ >
78
+ {#if Icon}
79
+ <Icon size={18} class="nav-icon" />
80
+ {/if}
81
+ {#if !isCollapsed}
82
+ <span class="nav-label" transition:fly={{ x: -20, duration: 200, delay: 50 }}>{label}</span>
83
+ <span class="expand-icon-wrapper" transition:fly={{ x: -15, duration: 200, delay: 75 }}>
84
+ <svg
85
+ class="expand-icon {expanded ? 'rotate-180' : ''}"
86
+ xmlns="http://www.w3.org/2000/svg"
87
+ width="14"
88
+ height="14"
89
+ viewBox="0 0 24 24"
90
+ fill="none"
91
+ stroke="currentColor"
92
+ stroke-width="2"
93
+ stroke-linecap="round"
94
+ stroke-linejoin="round"
95
+ >
96
+ <polyline points="6 9 12 15 18 9"></polyline>
97
+ </svg>
98
+ </span>
99
+ {/if}
100
+ </button>
101
+
102
+ {#if expanded && !isCollapsed && items.length > 0}
103
+ <ul class="subnav-list" transition:slide={{ duration: 200, easing: cubicOut }}>
104
+ {#each items as item}
105
+ <li>
106
+ <a
107
+ href={item.href}
108
+ class="subnav-item {isItemActive(item) ? 'active' : ''}"
109
+ >
110
+ {item.label}
111
+ {#if item.badge}
112
+ <span class="subnav-badge">{item.badge}</span>
113
+ {/if}
114
+ </a>
115
+ </li>
116
+ {/each}
117
+ </ul>
118
+ {/if}
119
+ </li>
120
+
121
+ <style>
122
+ li {
123
+ list-style: none;
124
+ }
125
+
126
+ .nav-item {
127
+ width: calc(100% - 1rem);
128
+ padding: 0.375rem 0.75rem;
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 0.5rem;
132
+ font-size: 0.875rem;
133
+ color: var(--color-text, var(--color-base06, #F3F4F7));
134
+ cursor: pointer;
135
+ border-radius: 0.375rem;
136
+ margin: 0 0.5rem;
137
+ transition: all 200ms ease;
138
+ overflow: hidden;
139
+ border: none;
140
+ background: transparent;
141
+ font-family: inherit;
142
+ position: relative;
143
+ }
144
+
145
+ .nav-item:hover {
146
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
147
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 10%, transparent);
148
+ }
149
+
150
+ .nav-item.collapsed {
151
+ justify-content: center;
152
+ padding: 0.5rem;
153
+ margin: 0 0.25rem;
154
+ width: calc(100% - 0.5rem);
155
+ }
156
+
157
+ .nav-item:hover .nav-icon,
158
+ .nav-item:hover .expand-icon {
159
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
160
+ }
161
+
162
+ .nav-icon {
163
+ flex-shrink: 0;
164
+ transition: color 150ms ease;
165
+ }
166
+
167
+ .nav-label {
168
+ flex: 1;
169
+ text-align: left;
170
+ white-space: nowrap;
171
+ overflow: hidden;
172
+ }
173
+
174
+ .expand-icon-wrapper {
175
+ margin-left: auto;
176
+ flex-shrink: 0;
177
+ }
178
+
179
+ .expand-icon {
180
+ color: var(--color-text-muted, var(--color-base05, #D0D2DB));
181
+ transition: all 200ms ease;
182
+ }
183
+
184
+ .expand-icon.rotate-180 {
185
+ transform: rotate(180deg);
186
+ }
187
+
188
+ /* Subnav list */
189
+ .subnav-list {
190
+ margin-left: 1.75rem;
191
+ display: flex;
192
+ flex-direction: column;
193
+ list-style: none;
194
+ padding: 0;
195
+ }
196
+
197
+ .subnav-item {
198
+ display: block;
199
+ padding: 0.25rem 0.5rem;
200
+ font-size: 0.875rem;
201
+ color: var(--color-text-muted, var(--color-base05, #D0D2DB));
202
+ text-decoration: none;
203
+ border-radius: 0.375rem;
204
+ transition: all 200ms ease;
205
+ cursor: pointer;
206
+ text-align: left;
207
+ }
208
+
209
+ .subnav-item:hover {
210
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
211
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 10%, transparent);
212
+ }
213
+
214
+ .subnav-item.active {
215
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
216
+ font-weight: 500;
217
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 15%, transparent);
218
+ }
219
+
220
+ .subnav-badge {
221
+ display: inline-block;
222
+ margin-left: 0.5rem;
223
+ padding: 0.125rem 0.375rem;
224
+ font-size: 0.625rem;
225
+ font-weight: 600;
226
+ border-radius: 9999px;
227
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 10%, transparent);
228
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
229
+ }
230
+ </style>
@@ -0,0 +1,262 @@
1
+ <!--
2
+ @component SidebarGroupSwitcher
3
+
4
+ Investment group switcher with create group functionality.
5
+ Matches dash.selify.ai group switching behavior.
6
+
7
+ @example
8
+ <SidebarGroupSwitcher
9
+ groups={investmentGroups}
10
+ currentGroup={currentGroup}
11
+ onGroupChange={(group) => switchToGroup(group)}
12
+ onCreateGroup={() => showCreateModal = true}
13
+ bind:expanded={groupsExpanded}
14
+ />
15
+ -->
16
+ <script>
17
+ import { getContext } from 'svelte';
18
+ import { fade, fly, slide } from 'svelte/transition';
19
+ import { cubicOut } from 'svelte/easing';
20
+ import { SIDEBAR_CONTEXT_KEY } from '../../utils/sidebar.svelte.js';
21
+
22
+ let {
23
+ groups = [],
24
+ currentGroup = null,
25
+ expanded = $bindable(false),
26
+ onGroupChange = null,
27
+ onCreateGroup = null,
28
+ searchQuery = '',
29
+ class: className = ''
30
+ } = $props();
31
+
32
+ const sidebar = getContext(SIDEBAR_CONTEXT_KEY);
33
+ const isCollapsed = $derived(sidebar?.collapsed ?? false);
34
+
35
+ // Filter groups based on search
36
+ const filteredGroups = $derived(
37
+ groups.filter(group =>
38
+ !searchQuery ||
39
+ group.name?.toLowerCase().includes(searchQuery.toLowerCase())
40
+ )
41
+ );
42
+
43
+ function toggleExpanded() {
44
+ expanded = !expanded;
45
+ }
46
+
47
+ function handleGroupClick(group) {
48
+ if (onGroupChange) {
49
+ onGroupChange(group);
50
+ }
51
+ // Close mobile overlay if in mobile mode
52
+ if (sidebar?.closeMobileOverlay) {
53
+ sidebar.closeMobileOverlay();
54
+ }
55
+ }
56
+
57
+ function handleCreateGroup() {
58
+ if (onCreateGroup) {
59
+ onCreateGroup();
60
+ }
61
+ }
62
+
63
+ function isGroupActive(group) {
64
+ return currentGroup?.id === group.id;
65
+ }
66
+ </script>
67
+
68
+ {#if groups.length > 0}
69
+ <div class="sidebar-section {className}">
70
+ <button
71
+ class="section-header"
72
+ onclick={toggleExpanded}
73
+ aria-expanded={expanded}
74
+ >
75
+ <span class="section-title">Groups</span>
76
+ <span class="section-count">{groups.length}</span>
77
+ <svg
78
+ class="section-chevron {expanded ? 'rotate-180' : ''}"
79
+ xmlns="http://www.w3.org/2000/svg"
80
+ width="14"
81
+ height="14"
82
+ viewBox="0 0 24 24"
83
+ fill="none"
84
+ stroke="currentColor"
85
+ stroke-width="2"
86
+ stroke-linecap="round"
87
+ stroke-linejoin="round"
88
+ >
89
+ <polyline points="6 9 12 15 18 9"></polyline>
90
+ </svg>
91
+ </button>
92
+
93
+ {#if expanded}
94
+ <div class="groups-list" transition:slide={{ duration: 200, easing: cubicOut }}>
95
+ {#each filteredGroups as group}
96
+ <button
97
+ class="group-item {isGroupActive(group) ? 'active' : ''}"
98
+ onclick={() => handleGroupClick(group)}
99
+ title={group.name}
100
+ >
101
+ <svg
102
+ xmlns="http://www.w3.org/2000/svg"
103
+ width="14"
104
+ height="14"
105
+ viewBox="0 0 24 24"
106
+ fill="none"
107
+ stroke="currentColor"
108
+ stroke-width="2"
109
+ stroke-linecap="round"
110
+ stroke-linejoin="round"
111
+ >
112
+ <path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path>
113
+ <line x1="3" y1="6" x2="21" y2="6"></line>
114
+ <path d="M16 10a4 4 0 0 1-8 0"></path>
115
+ </svg>
116
+ <span class="group-name">{group.name}</span>
117
+ </button>
118
+ {/each}
119
+
120
+ <button class="add-group" onclick={handleCreateGroup} title="Create new group">
121
+ <svg
122
+ xmlns="http://www.w3.org/2000/svg"
123
+ width="14"
124
+ height="14"
125
+ viewBox="0 0 24 24"
126
+ fill="none"
127
+ stroke="currentColor"
128
+ stroke-width="2"
129
+ stroke-linecap="round"
130
+ stroke-linejoin="round"
131
+ >
132
+ <line x1="12" y1="5" x2="12" y2="19"></line>
133
+ <line x1="5" y1="12" x2="19" y2="12"></line>
134
+ </svg>
135
+ <span>Create Group</span>
136
+ </button>
137
+ </div>
138
+ {/if}
139
+ </div>
140
+ {/if}
141
+
142
+ <style>
143
+ .sidebar-section {
144
+ padding: 0.125rem 0;
145
+ }
146
+
147
+ .section-header {
148
+ width: 100%;
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 0.5rem;
152
+ padding: 0.5rem 0.75rem;
153
+ font-size: 0.75rem;
154
+ font-weight: 600;
155
+ color: var(--color-text-muted, var(--color-base04, #737E99));
156
+ text-transform: uppercase;
157
+ letter-spacing: 0.05em;
158
+ cursor: pointer;
159
+ transition: all 200ms ease;
160
+ border: none;
161
+ background: transparent;
162
+ font-family: inherit;
163
+ justify-content: flex-start;
164
+ }
165
+
166
+ .section-header:hover {
167
+ color: var(--color-text, var(--color-base05, #D0D2DB));
168
+ }
169
+
170
+ .section-title {
171
+ flex: 1;
172
+ text-align: left;
173
+ }
174
+
175
+ .section-count {
176
+ padding: 0.125rem 0.375rem;
177
+ font-size: 0.625rem;
178
+ background-color: var(--color-surface-alt, var(--color-base02, #3E4359));
179
+ color: var(--color-text-muted, var(--color-base05, #D0D2DB));
180
+ border-radius: 9999px;
181
+ flex-shrink: 0;
182
+ }
183
+
184
+ .section-chevron {
185
+ color: var(--color-text-muted, var(--color-base04, #737E99));
186
+ transition: all 200ms ease;
187
+ flex-shrink: 0;
188
+ }
189
+
190
+ .section-chevron.rotate-180 {
191
+ transform: rotate(180deg);
192
+ }
193
+
194
+ .groups-list {
195
+ display: flex;
196
+ flex-direction: column;
197
+ gap: 0.125rem;
198
+ padding: 0 0.5rem;
199
+ }
200
+
201
+ .group-item {
202
+ width: 100%;
203
+ padding: 0.5rem 0.75rem;
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 0.75rem;
207
+ font-size: 0.875rem;
208
+ color: var(--color-text, var(--color-base06, #F3F4F7));
209
+ cursor: pointer;
210
+ border-radius: 0.375rem;
211
+ transition: all 200ms ease;
212
+ overflow: hidden;
213
+ border: none;
214
+ background: transparent;
215
+ font-family: inherit;
216
+ text-align: left;
217
+ }
218
+
219
+ .group-item:hover {
220
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
221
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 10%, transparent);
222
+ }
223
+
224
+ .group-item.active {
225
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 15%, transparent);
226
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
227
+ font-weight: 500;
228
+ }
229
+
230
+ .group-name {
231
+ flex: 1;
232
+ text-align: left;
233
+ white-space: nowrap;
234
+ overflow: hidden;
235
+ text-overflow: ellipsis;
236
+ font-weight: 500;
237
+ }
238
+
239
+ .add-group {
240
+ width: 100%;
241
+ padding: 0.5rem 0.75rem;
242
+ margin-top: 0.25rem;
243
+ display: flex;
244
+ align-items: center;
245
+ gap: 0.5rem;
246
+ justify-content: center;
247
+ font-size: 0.875rem;
248
+ color: var(--color-text-muted, var(--color-base05, #D0D2DB));
249
+ cursor: pointer;
250
+ border-radius: 0.375rem;
251
+ transition: all 200ms ease;
252
+ border: 1px solid color-mix(in srgb, var(--color-base03, #565E78) 30%, transparent);
253
+ background: transparent;
254
+ font-family: inherit;
255
+ }
256
+
257
+ .add-group:hover {
258
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
259
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 5%, transparent);
260
+ border-color: var(--color-primary, var(--color-base0D, #83D2FC));
261
+ }
262
+ </style>
@@ -0,0 +1,210 @@
1
+ <!--
2
+ @component SidebarItem
3
+
4
+ Navigation link or button item within sidebar.
5
+ Supports icons, badges, and active states.
6
+
7
+ @example Link item
8
+ <SidebarItem
9
+ href="/settings"
10
+ icon={Settings}
11
+ label="Settings"
12
+ active={currentPath === '/settings'}
13
+ />
14
+
15
+ @example Button item
16
+ <SidebarItem
17
+ onclick={() => doSomething()}
18
+ icon={Plus}
19
+ label="Add New"
20
+ />
21
+
22
+ @example With badge
23
+ <SidebarItem
24
+ href="/messages"
25
+ icon={Mail}
26
+ label="Messages"
27
+ badge={5}
28
+ badgeVariant="primary"
29
+ />
30
+ -->
31
+ <script>
32
+ import { getContext } from 'svelte';
33
+ import { fade, fly } from 'svelte/transition';
34
+ import { SIDEBAR_CONTEXT_KEY } from '../../utils/sidebar.svelte.js';
35
+
36
+ let {
37
+ href = null,
38
+ onclick = null,
39
+ icon: Icon = null,
40
+ label = '',
41
+ badge = null,
42
+ badgeVariant = 'default',
43
+ active = false,
44
+ disabled = false,
45
+ variant = 'default',
46
+ class: className = ''
47
+ } = $props();
48
+
49
+ const sidebar = getContext(SIDEBAR_CONTEXT_KEY);
50
+ const isCollapsed = $derived(sidebar?.collapsed ?? false);
51
+
52
+ const isLink = $derived(href !== null);
53
+ const itemClass = $derived([
54
+ 'nav-item',
55
+ active && 'active',
56
+ disabled && 'disabled',
57
+ variant !== 'default' && `variant-${variant}`,
58
+ isCollapsed && 'collapsed',
59
+ className
60
+ ].filter(Boolean).join(' '));
61
+ </script>
62
+
63
+ <li>
64
+ {#if isLink}
65
+ <a
66
+ {href}
67
+ class={itemClass}
68
+ title={isCollapsed ? label : null}
69
+ aria-current={active ? 'page' : undefined}
70
+ aria-disabled={disabled}
71
+ >
72
+ {#if Icon}
73
+ <Icon size={18} class="nav-icon" />
74
+ {/if}
75
+ {#if !isCollapsed}
76
+ <span class="nav-label" transition:fly={{ x: -20, duration: 200, delay: 50 }}>{label}</span>
77
+ {#if badge !== null}
78
+ <span class="nav-badge badge-{badgeVariant}" transition:fly={{ x: -15, duration: 200, delay: 75 }}>
79
+ {badge}
80
+ </span>
81
+ {/if}
82
+ {/if}
83
+ </a>
84
+ {:else}
85
+ <button
86
+ type="button"
87
+ class={itemClass}
88
+ {onclick}
89
+ {disabled}
90
+ title={isCollapsed ? label : null}
91
+ >
92
+ {#if Icon}
93
+ <Icon size={18} class="nav-icon" />
94
+ {/if}
95
+ {#if !isCollapsed}
96
+ <span class="nav-label" transition:fly={{ x: -20, duration: 200, delay: 50 }}>{label}</span>
97
+ {#if badge !== null}
98
+ <span class="nav-badge badge-{badgeVariant}" transition:fly={{ x: -15, duration: 200, delay: 75 }}>
99
+ {badge}
100
+ </span>
101
+ {/if}
102
+ {/if}
103
+ </button>
104
+ {/if}
105
+ </li>
106
+
107
+ <style>
108
+ li {
109
+ list-style: none;
110
+ }
111
+
112
+ .nav-item {
113
+ width: calc(100% - 1rem);
114
+ padding: 0.375rem 0.75rem;
115
+ display: flex;
116
+ align-items: center;
117
+ gap: 0.5rem;
118
+ font-size: 0.875rem;
119
+ color: var(--color-text, var(--color-base06, #F3F4F7));
120
+ cursor: pointer;
121
+ border-radius: 0.375rem;
122
+ margin: 0 0.5rem;
123
+ transition: all 200ms ease;
124
+ overflow: hidden;
125
+ text-decoration: none;
126
+ border: none;
127
+ background: transparent;
128
+ font-family: inherit;
129
+ }
130
+
131
+ .nav-item:hover {
132
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
133
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 10%, transparent);
134
+ }
135
+
136
+ .nav-item.collapsed {
137
+ justify-content: center;
138
+ padding: 0.5rem;
139
+ margin: 0 0.25rem;
140
+ width: calc(100% - 0.5rem);
141
+ }
142
+
143
+ .nav-item.active {
144
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 15%, transparent);
145
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
146
+ font-weight: 500;
147
+ }
148
+
149
+ .nav-item.disabled {
150
+ opacity: 0.5;
151
+ cursor: not-allowed;
152
+ pointer-events: none;
153
+ }
154
+
155
+ /* Variant: warning */
156
+ .nav-item.variant-warning {
157
+ color: var(--color-warning, var(--color-base0A, #E8D176));
158
+ }
159
+
160
+ .nav-item.variant-warning:hover {
161
+ color: var(--color-warning, var(--color-base0A, #E8D176));
162
+ background-color: color-mix(in srgb, var(--color-warning, var(--color-base0A, #E8D176)) 15%, transparent);
163
+ }
164
+
165
+ /* Variant: danger */
166
+ .nav-item.variant-danger {
167
+ color: var(--color-error, var(--color-base08, #EB3137));
168
+ }
169
+
170
+ .nav-item.variant-danger:hover {
171
+ color: var(--color-error, var(--color-base08, #EB3137));
172
+ background-color: color-mix(in srgb, var(--color-error, var(--color-base08, #EB3137)) 15%, transparent);
173
+ }
174
+
175
+ .nav-icon {
176
+ flex-shrink: 0;
177
+ transition: color 200ms ease;
178
+ }
179
+
180
+ .nav-label {
181
+ flex: 1;
182
+ text-align: left;
183
+ white-space: nowrap;
184
+ overflow: hidden;
185
+ }
186
+
187
+ .nav-badge {
188
+ padding: 0.125rem 0.375rem;
189
+ font-size: 0.625rem;
190
+ font-weight: 600;
191
+ border-radius: 9999px;
192
+ background-color: color-mix(in srgb, var(--color-primary, var(--color-base0D, #83D2FC)) 10%, transparent);
193
+ color: var(--color-primary, var(--color-base0D, #83D2FC));
194
+ }
195
+
196
+ .nav-badge.badge-success {
197
+ background-color: color-mix(in srgb, var(--color-success, var(--color-base0B, #6DD672)) 10%, transparent);
198
+ color: var(--color-success, var(--color-base0B, #6DD672));
199
+ }
200
+
201
+ .nav-badge.badge-warning {
202
+ background-color: color-mix(in srgb, var(--color-warning, var(--color-base0A, #E8D176)) 10%, transparent);
203
+ color: var(--color-warning, var(--color-base0A, #E8D176));
204
+ }
205
+
206
+ .nav-badge.badge-danger {
207
+ background-color: color-mix(in srgb, var(--color-error, var(--color-base08, #EB3137)) 10%, transparent);
208
+ color: var(--color-error, var(--color-base08, #EB3137));
209
+ }
210
+ </style>