@mrintel/villain-ui 0.3.0 → 0.6.3

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 (128) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3490 -1296
  3. package/dist/components/buttons/Button.svelte +27 -33
  4. package/dist/components/buttons/Button.svelte.d.ts +4 -1
  5. package/dist/components/buttons/ButtonGroup.svelte +17 -30
  6. package/dist/components/buttons/FloatingActionButton.svelte +20 -44
  7. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
  8. package/dist/components/buttons/IconButton.svelte +23 -53
  9. package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
  10. package/dist/components/buttons/LinkButton.svelte +24 -37
  11. package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
  12. package/dist/components/buttons/buttonClasses.d.ts +5 -0
  13. package/dist/components/buttons/buttonClasses.js +8 -3
  14. package/dist/components/cards/Card.svelte +60 -46
  15. package/dist/components/cards/Card.svelte.d.ts +6 -2
  16. package/dist/components/cards/Container.svelte +17 -33
  17. package/dist/components/cards/Divider.svelte +36 -52
  18. package/dist/components/cards/Divider.svelte.d.ts +2 -0
  19. package/dist/components/cards/Grid.svelte +55 -44
  20. package/dist/components/cards/Panel.svelte +18 -32
  21. package/dist/components/cards/Panel.svelte.d.ts +2 -1
  22. package/dist/components/cards/SectionHeader.svelte +24 -38
  23. package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
  24. package/dist/components/data/Avatar.svelte +48 -67
  25. package/dist/components/data/Badge.svelte +45 -32
  26. package/dist/components/data/Badge.svelte.d.ts +7 -1
  27. package/dist/components/data/CalendarGrid.svelte +433 -0
  28. package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
  29. package/dist/components/data/CalendarGrid.types.d.ts +7 -0
  30. package/dist/components/data/CalendarGrid.types.js +1 -0
  31. package/dist/components/data/CodeBlock.svelte +119 -121
  32. package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
  33. package/dist/components/data/List.svelte +87 -64
  34. package/dist/components/data/List.svelte.d.ts +7 -0
  35. package/dist/components/data/Pagination.svelte +121 -123
  36. package/dist/components/data/Pagination.svelte.d.ts +5 -0
  37. package/dist/components/data/Sparkline.svelte +117 -0
  38. package/dist/components/data/Sparkline.svelte.d.ts +43 -0
  39. package/dist/components/data/Stat.svelte +92 -103
  40. package/dist/components/data/Table.svelte +443 -76
  41. package/dist/components/data/Table.svelte.d.ts +23 -2
  42. package/dist/components/data/Table.types.d.ts +14 -0
  43. package/dist/components/data/Table.types.js +1 -0
  44. package/dist/components/data/Tag.svelte +51 -53
  45. package/dist/components/data/Tag.svelte.d.ts +5 -1
  46. package/dist/components/data/index.d.ts +4 -0
  47. package/dist/components/data/index.js +2 -0
  48. package/dist/components/forms/Checkbox.svelte +39 -51
  49. package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
  50. package/dist/components/forms/DatePicker.svelte +61 -0
  51. package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
  52. package/dist/components/forms/DateTimePicker.svelte +63 -0
  53. package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
  54. package/dist/components/forms/FileUpload.svelte +136 -164
  55. package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
  56. package/dist/components/forms/Input.svelte +282 -57
  57. package/dist/components/forms/Input.svelte.d.ts +9 -3
  58. package/dist/components/forms/InputGroup.svelte +7 -7
  59. package/dist/components/forms/RadioGroup.svelte +77 -87
  60. package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
  61. package/dist/components/forms/RangeSlider.svelte +90 -116
  62. package/dist/components/forms/Select.svelte +106 -71
  63. package/dist/components/forms/Select.svelte.d.ts +3 -1
  64. package/dist/components/forms/Switch.svelte +44 -56
  65. package/dist/components/forms/Switch.svelte.d.ts +3 -1
  66. package/dist/components/forms/Textarea.svelte +52 -57
  67. package/dist/components/forms/Textarea.svelte.d.ts +3 -1
  68. package/dist/components/forms/TimePicker.svelte +63 -0
  69. package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
  70. package/dist/components/forms/formClasses.d.ts +3 -0
  71. package/dist/components/forms/formClasses.js +3 -0
  72. package/dist/components/forms/index.d.ts +3 -0
  73. package/dist/components/forms/index.js +3 -0
  74. package/dist/components/navigation/Breadcrumbs.svelte +56 -59
  75. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
  76. package/dist/components/navigation/ContextMenu.svelte +133 -83
  77. package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
  78. package/dist/components/navigation/DropdownMenu.svelte +139 -80
  79. package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
  80. package/dist/components/navigation/Menu.svelte +72 -48
  81. package/dist/components/navigation/Navbar.svelte +111 -32
  82. package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
  83. package/dist/components/navigation/Sidebar.svelte +236 -35
  84. package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
  85. package/dist/components/navigation/Tabs.svelte +86 -54
  86. package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
  87. package/dist/components/overlays/Alert.svelte +81 -99
  88. package/dist/components/overlays/Alert.svelte.d.ts +5 -1
  89. package/dist/components/overlays/CommandPalette.svelte +182 -217
  90. package/dist/components/overlays/Drawer.svelte +158 -167
  91. package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
  92. package/dist/components/overlays/Dropdown.svelte +62 -30
  93. package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
  94. package/dist/components/overlays/Modal.svelte +125 -130
  95. package/dist/components/overlays/Modal.svelte.d.ts +3 -1
  96. package/dist/components/overlays/Popover.svelte +106 -131
  97. package/dist/components/overlays/ProgressBar.svelte +29 -45
  98. package/dist/components/overlays/SkeletonLoader.svelte +66 -82
  99. package/dist/components/overlays/Spinner.svelte +33 -43
  100. package/dist/components/overlays/Toast.svelte +111 -140
  101. package/dist/components/overlays/Toast.svelte.d.ts +3 -0
  102. package/dist/components/overlays/Tooltip.svelte +94 -115
  103. package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
  104. package/dist/components/typography/Code.svelte +10 -14
  105. package/dist/components/typography/Heading.svelte +15 -22
  106. package/dist/components/typography/Heading.svelte.d.ts +1 -0
  107. package/dist/components/typography/Text.svelte +21 -24
  108. package/dist/components/typography/Text.svelte.d.ts +2 -1
  109. package/dist/components/utilities/Accordion.svelte +54 -67
  110. package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
  111. package/dist/components/utilities/Carousel.svelte +124 -152
  112. package/dist/components/utilities/Collapse.svelte +46 -60
  113. package/dist/components/utilities/Hero.svelte +42 -0
  114. package/dist/components/utilities/Hero.svelte.d.ts +10 -0
  115. package/dist/components/utilities/Portal.svelte +47 -72
  116. package/dist/components/utilities/ScrollArea.svelte +33 -41
  117. package/dist/components/utilities/SystemConsole.svelte +310 -0
  118. package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
  119. package/dist/components/utilities/SystemInterface.svelte +726 -0
  120. package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
  121. package/dist/components/utilities/index.d.ts +4 -0
  122. package/dist/components/utilities/index.js +3 -0
  123. package/dist/components/utilities/utilities.types.d.ts +46 -0
  124. package/dist/components/utilities/utilities.types.js +4 -0
  125. package/dist/index.d.ts +49 -4
  126. package/dist/index.js +4 -4
  127. package/dist/theme.css +2821 -218
  128. package/package.json +83 -76
@@ -1,35 +1,236 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
-
4
- interface Props {
5
- open?: boolean;
6
- side?: 'left' | 'right';
7
- width?: 'sm' | 'md' | 'lg';
8
- children?: Snippet;
9
- }
10
-
11
- let {
12
- open = $bindable(true),
13
- side = 'left',
14
- width = 'md',
15
- children
16
- }: Props = $props();
17
-
18
- const widthClasses = {
19
- sm: { open: 'w-56', collapsed: 'w-14' },
20
- md: { open: 'w-64', collapsed: 'w-16' },
21
- lg: { open: 'w-80', collapsed: 'w-20' }
22
- };
23
-
24
- const sideClasses = {
25
- left: 'left-0',
26
- right: 'right-0'
27
- };
28
-
29
- const currentWidth = $derived(open ? widthClasses[width].open : widthClasses[width].collapsed);
30
- const baseClasses = 'fixed top-0 bottom-0 z-30 glass-panel overflow-y-auto overflow-x-hidden transition-all duration-300 ease-[var(--ease-luxe)]';
31
- </script>
32
-
33
- <aside class="{baseClasses} {sideClasses[side]} {currentWidth}">
34
- {@render children?.()}
35
- </aside>
1
+ <script lang="ts">let { open = $bindable(true), side = 'left', width = 'md', header, children, currentPath } = $props();
2
+ let navbarHeight = $state(0);
3
+ $effect(() => {
4
+ if (typeof document === 'undefined')
5
+ return;
6
+ const navbarElement = document.querySelector('[data-navbar]');
7
+ if (navbarElement) {
8
+ // Initial height
9
+ navbarHeight = navbarElement.offsetHeight;
10
+ // Watch for height changes (responsive behavior, window resize, etc.)
11
+ if (typeof ResizeObserver !== 'undefined') {
12
+ const resizeObserver = new ResizeObserver(() => {
13
+ navbarHeight = navbarElement.offsetHeight;
14
+ });
15
+ resizeObserver.observe(navbarElement);
16
+ return () => {
17
+ resizeObserver.disconnect();
18
+ };
19
+ }
20
+ }
21
+ else {
22
+ navbarHeight = 0;
23
+ }
24
+ });
25
+ // Track elements modified by the effect to preserve manual .active classes
26
+ let autoManagedElements = $state(new Set());
27
+ let rootElement = $state(null);
28
+ $effect(() => {
29
+ if (typeof document === 'undefined')
30
+ return;
31
+ if (!rootElement)
32
+ return;
33
+ // Clear auto-managed active classes when currentPath becomes falsy
34
+ if (!currentPath) {
35
+ autoManagedElements.forEach((element) => {
36
+ element.classList.remove('active');
37
+ });
38
+ autoManagedElements.clear();
39
+ return;
40
+ }
41
+ const elements = rootElement.querySelectorAll('a, button');
42
+ elements.forEach((element) => {
43
+ const href = element.getAttribute('href');
44
+ const dataHref = element.getAttribute('data-href');
45
+ const targetPath = href || dataHref;
46
+ // Match exact path or nested routes (e.g., /buttons matches /buttons/icon-button)
47
+ const isActive = targetPath === currentPath ||
48
+ (targetPath && currentPath.startsWith(targetPath + '/'));
49
+ if (isActive) {
50
+ // Only add to autoManagedElements if we're adding the class ourselves
51
+ if (!element.classList.contains('active')) {
52
+ element.classList.add('active');
53
+ autoManagedElements.add(element);
54
+ }
55
+ }
56
+ else if (autoManagedElements.has(element)) {
57
+ element.classList.remove('active');
58
+ autoManagedElements.delete(element);
59
+ }
60
+ });
61
+ });
62
+ // Collapsed state icon/text detection
63
+ $effect(() => {
64
+ if (typeof document === 'undefined')
65
+ return;
66
+ if (!rootElement)
67
+ return;
68
+ // Reference open to make effect reactive to collapse/expand state changes
69
+ // This ensures detection runs when sidebar toggles
70
+ if (open === undefined)
71
+ return;
72
+ const elements = rootElement.querySelectorAll('a, button');
73
+ elements.forEach((element) => {
74
+ // Check for icon presence using documented .sidebar-item-icon class
75
+ const hasIcon = element.querySelector('.sidebar-item-icon');
76
+ if (hasIcon) {
77
+ element.setAttribute('data-sidebar-has-icon', 'true');
78
+ element.removeAttribute('data-sidebar-first-letter');
79
+ }
80
+ else {
81
+ // Extract first letter from .sidebar-item-label or fallback to text content
82
+ const label = element.querySelector('.sidebar-item-label');
83
+ const textContent = (label?.textContent || element.textContent)?.trim();
84
+ const firstChar = textContent?.[0];
85
+ // Only set first-letter if it's alphanumeric
86
+ if (firstChar && /[a-zA-Z0-9]/.test(firstChar)) {
87
+ element.setAttribute('data-sidebar-first-letter', firstChar.toUpperCase());
88
+ element.removeAttribute('data-sidebar-has-icon');
89
+ }
90
+ else {
91
+ // No valid first letter - don't show anything in collapsed mode
92
+ element.removeAttribute('data-sidebar-first-letter');
93
+ element.removeAttribute('data-sidebar-has-icon');
94
+ }
95
+ }
96
+ });
97
+ });
98
+ const widthClasses = {
99
+ sm: { open: 'w-56', collapsed: 'w-14' },
100
+ md: { open: 'w-64', collapsed: 'w-16' },
101
+ lg: { open: 'w-80', collapsed: 'w-20' }
102
+ };
103
+ const sideClasses = {
104
+ left: 'left-0',
105
+ right: 'right-0'
106
+ };
107
+ const currentWidth = $derived(open ? widthClasses[width].open : widthClasses[width].collapsed);
108
+ const baseClasses = 'fixed bottom-0 z-[var(--z-40)] glass-panel overflow-y-auto overflow-x-hidden transition-all duration-300 ease-[var(--ease-luxe)]';
109
+ export {};
110
+ </script>
111
+
112
+ <aside bind:this={rootElement} data-sidebar class="{baseClasses} {sideClasses[side]} {currentWidth}" style="top: {navbarHeight}px">
113
+ {#if header && open}
114
+ {@render header()}
115
+ {/if}
116
+ {@render children?.()}
117
+ </aside>
118
+
119
+ <style>
120
+ /* Header styling */
121
+ aside :global(.sidebar-header) {
122
+ padding: 1rem;
123
+ font-weight: 600;
124
+ color: var(--color-accent);
125
+ font-size: var(--text-sm);
126
+ text-transform: uppercase;
127
+ letter-spacing: 0.05em;
128
+ transition: all 0.2s var(--ease-luxe);
129
+ }
130
+
131
+ /* Collapsed state - center icons and hide text */
132
+ aside[class*="w-14"] :global(a[data-sidebar-has-icon]),
133
+ aside[class*="w-16"] :global(a[data-sidebar-has-icon]),
134
+ aside[class*="w-20"] :global(a[data-sidebar-has-icon]),
135
+ aside[class*="w-14"] :global(button[data-sidebar-has-icon]),
136
+ aside[class*="w-16"] :global(button[data-sidebar-has-icon]),
137
+ aside[class*="w-20"] :global(button[data-sidebar-has-icon]) {
138
+ justify-content: center;
139
+ }
140
+
141
+ aside[class*="w-14"] :global(a[data-sidebar-has-icon] .sidebar-item-label),
142
+ aside[class*="w-16"] :global(a[data-sidebar-has-icon] .sidebar-item-label),
143
+ aside[class*="w-20"] :global(a[data-sidebar-has-icon] .sidebar-item-label),
144
+ aside[class*="w-14"] :global(button[data-sidebar-has-icon] .sidebar-item-label),
145
+ aside[class*="w-16"] :global(button[data-sidebar-has-icon] .sidebar-item-label),
146
+ aside[class*="w-20"] :global(button[data-sidebar-has-icon] .sidebar-item-label) {
147
+ display: none;
148
+ }
149
+
150
+ aside[class*="w-14"] :global(a[data-sidebar-has-icon] .sidebar-item-icon),
151
+ aside[class*="w-16"] :global(a[data-sidebar-has-icon] .sidebar-item-icon),
152
+ aside[class*="w-20"] :global(a[data-sidebar-has-icon] .sidebar-item-icon),
153
+ aside[class*="w-14"] :global(button[data-sidebar-has-icon] .sidebar-item-icon),
154
+ aside[class*="w-16"] :global(button[data-sidebar-has-icon] .sidebar-item-icon),
155
+ aside[class*="w-20"] :global(button[data-sidebar-has-icon] .sidebar-item-icon) {
156
+ font-size: var(--text-2xl);
157
+ width: 1.5rem;
158
+ height: 1.5rem;
159
+ }
160
+
161
+ /* Collapsed state - first letter circle for items without icons */
162
+ aside[class*="w-14"] :global(a:not([data-sidebar-has-icon]))::before,
163
+ aside[class*="w-16"] :global(a:not([data-sidebar-has-icon]))::before,
164
+ aside[class*="w-20"] :global(a:not([data-sidebar-has-icon]))::before,
165
+ aside[class*="w-14"] :global(button:not([data-sidebar-has-icon]))::before,
166
+ aside[class*="w-16"] :global(button:not([data-sidebar-has-icon]))::before,
167
+ aside[class*="w-20"] :global(button:not([data-sidebar-has-icon]))::before {
168
+ content: attr(data-sidebar-first-letter);
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ width: 2.5rem;
173
+ height: 2.5rem;
174
+ border-radius: var(--radius-pill);
175
+ background: var(--color-accent-overlay-20);
176
+ color: var(--color-accent);
177
+ font-weight: 600;
178
+ font-size: var(--text-lg);
179
+ }
180
+
181
+ aside[class*="w-14"] :global(a:not([data-sidebar-has-icon])),
182
+ aside[class*="w-16"] :global(a:not([data-sidebar-has-icon])),
183
+ aside[class*="w-20"] :global(a:not([data-sidebar-has-icon])),
184
+ aside[class*="w-14"] :global(button:not([data-sidebar-has-icon])),
185
+ aside[class*="w-16"] :global(button:not([data-sidebar-has-icon])),
186
+ aside[class*="w-20"] :global(button:not([data-sidebar-has-icon])) {
187
+ justify-content: center;
188
+ }
189
+
190
+ aside[class*="w-14"] :global(a:not([data-sidebar-has-icon]) .sidebar-item-label),
191
+ aside[class*="w-16"] :global(a:not([data-sidebar-has-icon]) .sidebar-item-label),
192
+ aside[class*="w-20"] :global(a:not([data-sidebar-has-icon]) .sidebar-item-label),
193
+ aside[class*="w-14"] :global(button:not([data-sidebar-has-icon]) .sidebar-item-label),
194
+ aside[class*="w-16"] :global(button:not([data-sidebar-has-icon]) .sidebar-item-label),
195
+ aside[class*="w-20"] :global(button:not([data-sidebar-has-icon]) .sidebar-item-label) {
196
+ display: none;
197
+ }
198
+
199
+ aside :global(a.active),
200
+ aside :global(button.active) {
201
+ background: var(--color-accent-overlay-15);
202
+ color: var(--color-accent);
203
+ border-left: 3px solid var(--color-accent);
204
+ font-weight: 600;
205
+ }
206
+
207
+ aside :global(a.active),
208
+ aside :global(button.active) {
209
+ box-shadow: var(--shadow-accent-glow);
210
+ }
211
+
212
+ aside :global(a:not(.active):hover),
213
+ aside :global(button:not(.active):hover) {
214
+ background: var(--color-base-3);
215
+ color: var(--color-accent-soft);
216
+ }
217
+
218
+ aside :global(a),
219
+ aside :global(button) {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 0.75rem;
223
+ padding: 0.75rem 1rem;
224
+ margin: 0.25rem 0.5rem;
225
+ border-radius: var(--radius-md);
226
+ color: var(--color-text-soft);
227
+ text-decoration: none;
228
+ transition: all 0.2s var(--ease-luxe);
229
+ cursor: pointer;
230
+ border: none;
231
+ background: transparent;
232
+ width: calc(100% - 1rem);
233
+ text-align: left;
234
+ font-family: var(--font-body);
235
+ font-size: 0.875rem;
236
+ }</style>
@@ -3,7 +3,9 @@ interface Props {
3
3
  open?: boolean;
4
4
  side?: 'left' | 'right';
5
5
  width?: 'sm' | 'md' | 'lg';
6
+ header?: Snippet;
6
7
  children?: Snippet;
8
+ currentPath?: string;
7
9
  }
8
10
  declare const Sidebar: import("svelte").Component<Props, {}, "open">;
9
11
  type Sidebar = ReturnType<typeof Sidebar>;
@@ -1,54 +1,86 @@
1
- <script lang="ts">
2
-
3
- interface Tab {
4
- id: string;
5
- label: string;
6
- disabled?: boolean;
7
- }
8
-
9
- interface Props {
10
- tabs: Tab[];
11
- activeTab?: string;
12
- orientation?: 'horizontal' | 'vertical';
13
- variant?: 'default' | 'pills';
14
- onTabChange?: (tabId: string) => void;
15
- }
16
-
17
- let {
18
- tabs,
19
- activeTab = $bindable(''),
20
- orientation = 'horizontal',
21
- variant = 'default',
22
- onTabChange
23
- }: Props = $props();
24
-
25
- function handleTabClick(tabId: string, disabled?: boolean) {
26
- if (disabled) return;
27
- activeTab = tabId;
28
- if (onTabChange) {
29
- onTabChange(tabId);
30
- }
31
- }
32
-
33
- const orientationClasses = {
34
- horizontal: 'flex flex-row',
35
- vertical: 'flex flex-col'
36
- };
37
-
38
- const radiusClass = variant === 'pills' ? 'rounded-pill' : 'rounded-md';
39
- </script>
40
-
41
- <div role="tablist" class="{orientationClasses[orientation]} gap-1 p-1">
42
- {#each tabs as tab}
43
- <button
44
- role="tab"
45
- aria-selected={activeTab === tab.id}
46
- aria-disabled={tab.disabled}
47
- onclick={() => handleTabClick(tab.id, tab.disabled)}
48
- disabled={tab.disabled}
49
- class="px-4 py-2 {radiusClass} font-body text-sm transition-all duration-300 ease-luxe {activeTab === tab.id ? 'bg-accent text-text accent-glow' : 'text-text-soft hover:bg-base-3'} {tab.disabled ? 'opacity-50 pointer-events-none' : 'cursor-pointer'}"
50
- >
51
- {tab.label}
52
- </button>
53
- {/each}
54
- </div>
1
+ <script lang="ts">let { tabs, activeTab = $bindable(''), orientation = 'horizontal', variant = 'default', onTabChange, ontabchange, class: className = '' } = $props();
2
+ const onTabChangeCallback = $derived(onTabChange ?? ontabchange);
3
+ function handleTabClick(tabId, disabled) {
4
+ if (disabled)
5
+ return;
6
+ activeTab = tabId;
7
+ onTabChangeCallback?.(tabId);
8
+ }
9
+ function handleKeyDown(event, currentTabId) {
10
+ const enabledTabs = tabs.filter(tab => !tab.disabled);
11
+ const currentIndex = enabledTabs.findIndex(tab => tab.id === currentTabId);
12
+ let nextIndex = currentIndex;
13
+ switch (event.key) {
14
+ case 'ArrowLeft':
15
+ if (orientation !== 'horizontal')
16
+ return;
17
+ event.preventDefault();
18
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : enabledTabs.length - 1;
19
+ break;
20
+ case 'ArrowRight':
21
+ if (orientation !== 'horizontal')
22
+ return;
23
+ event.preventDefault();
24
+ nextIndex = currentIndex < enabledTabs.length - 1 ? currentIndex + 1 : 0;
25
+ break;
26
+ case 'ArrowUp':
27
+ if (orientation !== 'vertical')
28
+ return;
29
+ event.preventDefault();
30
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : enabledTabs.length - 1;
31
+ break;
32
+ case 'ArrowDown':
33
+ if (orientation !== 'vertical')
34
+ return;
35
+ event.preventDefault();
36
+ nextIndex = currentIndex < enabledTabs.length - 1 ? currentIndex + 1 : 0;
37
+ break;
38
+ case 'Home':
39
+ event.preventDefault();
40
+ nextIndex = 0;
41
+ break;
42
+ case 'End':
43
+ event.preventDefault();
44
+ nextIndex = enabledTabs.length - 1;
45
+ break;
46
+ default:
47
+ return;
48
+ }
49
+ const nextTab = enabledTabs[nextIndex];
50
+ if (nextTab) {
51
+ handleTabClick(nextTab.id);
52
+ requestAnimationFrame(() => {
53
+ const button = document.querySelector(`[role="tab"][aria-selected="true"]`);
54
+ button?.focus();
55
+ });
56
+ }
57
+ }
58
+ const orientationClasses = {
59
+ horizontal: 'flex flex-row',
60
+ vertical: 'flex flex-col'
61
+ };
62
+ const radiusClass = variant === 'pills' ? 'rounded-pill' : 'rounded-[var(--radius-md)]';
63
+ export {};
64
+ </script>
65
+
66
+ <div role="tablist" class="{orientationClasses[orientation]} gap-1 p-1 {className}">
67
+ {#each tabs as tab}
68
+ <button
69
+ role="tab"
70
+ aria-selected={activeTab === tab.id}
71
+ aria-disabled={tab.disabled}
72
+ tabindex={activeTab === tab.id ? 0 : -1}
73
+ onclick={() => handleTabClick(tab.id, tab.disabled)}
74
+ onkeydown={(e) => handleKeyDown(e, tab.id)}
75
+ disabled={tab.disabled}
76
+ class="flex items-center gap-2 px-6 py-3 {radiusClass} font-body text-sm transition-all duration-300 ease-luxe {activeTab === tab.id ? 'bg-accent text-text accent-glow' : 'text-text-soft hover:bg-base-3'} {tab.disabled ? 'opacity-50 pointer-events-none' : 'cursor-pointer'}"
77
+ >
78
+ {#if tab.iconBefore}
79
+ <span class="inline-flex items-center justify-center">
80
+ {@render tab.iconBefore()}
81
+ </span>
82
+ {/if}
83
+ {tab.label}
84
+ </button>
85
+ {/each}
86
+ </div>
@@ -1,14 +1,18 @@
1
1
  interface Tab {
2
2
  id: string;
3
3
  label: string;
4
+ iconBefore?: import('svelte').Snippet;
4
5
  disabled?: boolean;
5
6
  }
6
- interface Props {
7
+ export interface Props {
7
8
  tabs: Tab[];
8
9
  activeTab?: string;
9
10
  orientation?: 'horizontal' | 'vertical';
10
11
  variant?: 'default' | 'pills';
11
12
  onTabChange?: (tabId: string) => void;
13
+ /** @deprecated Use onTabChange */
14
+ ontabchange?: (tabId: string) => void;
15
+ class?: string;
12
16
  }
13
17
  declare const Tabs: import("svelte").Component<Props, {}, "activeTab">;
14
18
  type Tabs = ReturnType<typeof Tabs>;
@@ -1,99 +1,81 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
-
4
- interface Props {
5
- variant?: 'info' | 'success' | 'warning' | 'error';
6
- title?: string;
7
- dismissible?: boolean;
8
- onclose?: () => void;
9
- children?: Snippet;
10
- }
11
-
12
- let {
13
- variant = 'info',
14
- title,
15
- dismissible = false,
16
- onclose,
17
- children
18
- }: Props = $props();
19
-
20
- let visible = $state(true);
21
-
22
- const variantClasses = {
23
- info: 'border-l-4 border-accent',
24
- success: 'border-l-4 border-success',
25
- warning: 'border-l-4 border-warning',
26
- error: 'border-l-4 border-error'
27
- };
28
-
29
- const variantTextClasses = {
30
- info: 'text-accent-soft',
31
- success: 'text-success',
32
- warning: 'text-warning',
33
- error: 'text-error'
34
- };
35
-
36
- const variantIcons = {
37
- info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
38
- success: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
39
- warning: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
40
- error: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z'
41
- };
42
-
43
- const roleMap = {
44
- info: 'status',
45
- success: 'status',
46
- warning: 'alert',
47
- error: 'alert'
48
- };
49
-
50
- const ariaLiveMap = {
51
- info: 'polite',
52
- success: 'polite',
53
- warning: 'polite',
54
- error: 'assertive'
55
- } satisfies Record<'info' | 'success' | 'warning' | 'error', 'polite' | 'assertive'>;
56
-
57
- function handleClose() {
58
- visible = false;
59
- onclose?.();
60
- }
61
- </script>
62
-
63
- {#if visible}
64
- <div
65
- class="glass-panel rounded-lg p-4 flex gap-3 {variantClasses[variant]} animate-[fade-in_0.2s_var(--ease-luxe)]"
66
- role={roleMap[variant]}
67
- aria-live={ariaLiveMap[variant]}
68
- >
69
- <div class="shrink-0 {variantTextClasses[variant]}">
70
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
71
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={variantIcons[variant]} />
72
- </svg>
73
- </div>
74
-
75
- <div class="flex-1 min-w-0">
76
- {#if title}
77
- <h4 class="font-semibold text-text mb-1">
78
- {title}
79
- </h4>
80
- {/if}
81
- <div class="text-sm text-text-soft">
82
- {@render children?.()}
83
- </div>
84
- </div>
85
-
86
- {#if dismissible}
87
- <button
88
- type="button"
89
- onclick={handleClose}
90
- class="shrink-0 text-text-soft hover:text-text transition-colors"
91
- aria-label="Dismiss alert"
92
- >
93
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
94
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
95
- </svg>
96
- </button>
97
- {/if}
98
- </div>
99
- {/if}
1
+ <script lang="ts">let { variant = 'info', title, dismissible = false, iconBefore, onClose, onclose, children, class: className = '' } = $props();
2
+ const onCloseCallback = $derived(onClose ?? onclose);
3
+ let visible = $state(true);
4
+ const variantClasses = {
5
+ info: 'border-l-4 border-accent',
6
+ success: 'border-l-4 border-success',
7
+ warning: 'border-l-4 border-warning',
8
+ error: 'border-l-4 border-error'
9
+ };
10
+ const variantTextClasses = {
11
+ info: 'text-accent-soft',
12
+ success: 'text-success',
13
+ warning: 'text-warning',
14
+ error: 'text-error'
15
+ };
16
+ const variantIcons = {
17
+ info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
18
+ success: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
19
+ warning: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
20
+ error: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z'
21
+ };
22
+ const roleMap = {
23
+ info: 'status',
24
+ success: 'status',
25
+ warning: 'alert',
26
+ error: 'alert'
27
+ };
28
+ const ariaLiveMap = {
29
+ info: 'polite',
30
+ success: 'polite',
31
+ warning: 'polite',
32
+ error: 'assertive'
33
+ };
34
+ function handleClose() {
35
+ visible = false;
36
+ onCloseCallback?.();
37
+ }
38
+ export {};
39
+ </script>
40
+
41
+ {#if visible}
42
+ <div
43
+ class="glass-panel rounded-[var(--radius-lg)] p-4 flex gap-3 {variantClasses[variant]} {className} animate-[fade-in_0.2s_var(--ease-sharp)]"
44
+ role={roleMap[variant]}
45
+ aria-live={ariaLiveMap[variant]}
46
+ >
47
+ <div class="shrink-0 {variantTextClasses[variant]}">
48
+ {#if iconBefore}
49
+ {@render iconBefore()}
50
+ {:else}
51
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={variantIcons[variant]} />
53
+ </svg>
54
+ {/if}
55
+ </div>
56
+
57
+ <div class="flex-1 min-w-0">
58
+ {#if title}
59
+ <h4 class="font-semibold text-text mb-1">
60
+ {title}
61
+ </h4>
62
+ {/if}
63
+ <div class="text-sm text-text-soft">
64
+ {@render children?.()}
65
+ </div>
66
+ </div>
67
+
68
+ {#if dismissible}
69
+ <button
70
+ type="button"
71
+ onclick={handleClose}
72
+ class="shrink-0 text-text-soft hover:text-text transition-colors"
73
+ aria-label="Dismiss alert"
74
+ >
75
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
76
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
77
+ </svg>
78
+ </button>
79
+ {/if}
80
+ </div>
81
+ {/if}
@@ -1,10 +1,14 @@
1
1
  import type { Snippet } from 'svelte';
2
- interface Props {
2
+ export interface Props {
3
3
  variant?: 'info' | 'success' | 'warning' | 'error';
4
4
  title?: string;
5
5
  dismissible?: boolean;
6
+ iconBefore?: Snippet;
7
+ onClose?: () => void;
8
+ /** @deprecated Use onClose */
6
9
  onclose?: () => void;
7
10
  children?: Snippet;
11
+ class?: string;
8
12
  }
9
13
  declare const Alert: import("svelte").Component<Props, {}, "">;
10
14
  type Alert = ReturnType<typeof Alert>;