@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.
- package/LICENSE +21 -21
- package/README.md +3490 -1296
- package/dist/components/buttons/Button.svelte +27 -33
- package/dist/components/buttons/Button.svelte.d.ts +4 -1
- package/dist/components/buttons/ButtonGroup.svelte +17 -30
- package/dist/components/buttons/FloatingActionButton.svelte +20 -44
- package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
- package/dist/components/buttons/IconButton.svelte +23 -53
- package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
- package/dist/components/buttons/LinkButton.svelte +24 -37
- package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
- package/dist/components/buttons/buttonClasses.d.ts +5 -0
- package/dist/components/buttons/buttonClasses.js +8 -3
- package/dist/components/cards/Card.svelte +60 -46
- package/dist/components/cards/Card.svelte.d.ts +6 -2
- package/dist/components/cards/Container.svelte +17 -33
- package/dist/components/cards/Divider.svelte +36 -52
- package/dist/components/cards/Divider.svelte.d.ts +2 -0
- package/dist/components/cards/Grid.svelte +55 -44
- package/dist/components/cards/Panel.svelte +18 -32
- package/dist/components/cards/Panel.svelte.d.ts +2 -1
- package/dist/components/cards/SectionHeader.svelte +24 -38
- package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
- package/dist/components/data/Avatar.svelte +48 -67
- package/dist/components/data/Badge.svelte +45 -32
- package/dist/components/data/Badge.svelte.d.ts +7 -1
- package/dist/components/data/CalendarGrid.svelte +433 -0
- package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
- package/dist/components/data/CalendarGrid.types.d.ts +7 -0
- package/dist/components/data/CalendarGrid.types.js +1 -0
- package/dist/components/data/CodeBlock.svelte +119 -121
- package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
- package/dist/components/data/List.svelte +87 -64
- package/dist/components/data/List.svelte.d.ts +7 -0
- package/dist/components/data/Pagination.svelte +121 -123
- package/dist/components/data/Pagination.svelte.d.ts +5 -0
- package/dist/components/data/Sparkline.svelte +117 -0
- package/dist/components/data/Sparkline.svelte.d.ts +43 -0
- package/dist/components/data/Stat.svelte +92 -103
- package/dist/components/data/Table.svelte +443 -76
- package/dist/components/data/Table.svelte.d.ts +23 -2
- package/dist/components/data/Table.types.d.ts +14 -0
- package/dist/components/data/Table.types.js +1 -0
- package/dist/components/data/Tag.svelte +51 -53
- package/dist/components/data/Tag.svelte.d.ts +5 -1
- package/dist/components/data/index.d.ts +4 -0
- package/dist/components/data/index.js +2 -0
- package/dist/components/forms/Checkbox.svelte +39 -51
- package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
- package/dist/components/forms/DatePicker.svelte +61 -0
- package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
- package/dist/components/forms/DateTimePicker.svelte +63 -0
- package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
- package/dist/components/forms/FileUpload.svelte +136 -164
- package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
- package/dist/components/forms/Input.svelte +282 -57
- package/dist/components/forms/Input.svelte.d.ts +9 -3
- package/dist/components/forms/InputGroup.svelte +7 -7
- package/dist/components/forms/RadioGroup.svelte +77 -87
- package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
- package/dist/components/forms/RangeSlider.svelte +90 -116
- package/dist/components/forms/Select.svelte +106 -71
- package/dist/components/forms/Select.svelte.d.ts +3 -1
- package/dist/components/forms/Switch.svelte +44 -56
- package/dist/components/forms/Switch.svelte.d.ts +3 -1
- package/dist/components/forms/Textarea.svelte +52 -57
- package/dist/components/forms/Textarea.svelte.d.ts +3 -1
- package/dist/components/forms/TimePicker.svelte +63 -0
- package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
- package/dist/components/forms/formClasses.d.ts +3 -0
- package/dist/components/forms/formClasses.js +3 -0
- package/dist/components/forms/index.d.ts +3 -0
- package/dist/components/forms/index.js +3 -0
- package/dist/components/navigation/Breadcrumbs.svelte +56 -59
- package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
- package/dist/components/navigation/ContextMenu.svelte +133 -83
- package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
- package/dist/components/navigation/DropdownMenu.svelte +139 -80
- package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
- package/dist/components/navigation/Menu.svelte +72 -48
- package/dist/components/navigation/Navbar.svelte +111 -32
- package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
- package/dist/components/navigation/Sidebar.svelte +236 -35
- package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
- package/dist/components/navigation/Tabs.svelte +86 -54
- package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
- package/dist/components/overlays/Alert.svelte +81 -99
- package/dist/components/overlays/Alert.svelte.d.ts +5 -1
- package/dist/components/overlays/CommandPalette.svelte +182 -217
- package/dist/components/overlays/Drawer.svelte +158 -167
- package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
- package/dist/components/overlays/Dropdown.svelte +62 -30
- package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
- package/dist/components/overlays/Modal.svelte +125 -130
- package/dist/components/overlays/Modal.svelte.d.ts +3 -1
- package/dist/components/overlays/Popover.svelte +106 -131
- package/dist/components/overlays/ProgressBar.svelte +29 -45
- package/dist/components/overlays/SkeletonLoader.svelte +66 -82
- package/dist/components/overlays/Spinner.svelte +33 -43
- package/dist/components/overlays/Toast.svelte +111 -140
- package/dist/components/overlays/Toast.svelte.d.ts +3 -0
- package/dist/components/overlays/Tooltip.svelte +94 -115
- package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
- package/dist/components/typography/Code.svelte +10 -14
- package/dist/components/typography/Heading.svelte +15 -22
- package/dist/components/typography/Heading.svelte.d.ts +1 -0
- package/dist/components/typography/Text.svelte +21 -24
- package/dist/components/typography/Text.svelte.d.ts +2 -1
- package/dist/components/utilities/Accordion.svelte +54 -67
- package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
- package/dist/components/utilities/Carousel.svelte +124 -152
- package/dist/components/utilities/Collapse.svelte +46 -60
- package/dist/components/utilities/Hero.svelte +42 -0
- package/dist/components/utilities/Hero.svelte.d.ts +10 -0
- package/dist/components/utilities/Portal.svelte +47 -72
- package/dist/components/utilities/ScrollArea.svelte +33 -41
- package/dist/components/utilities/SystemConsole.svelte +310 -0
- package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
- package/dist/components/utilities/SystemInterface.svelte +726 -0
- package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
- package/dist/components/utilities/index.d.ts +4 -0
- package/dist/components/utilities/index.js +3 -0
- package/dist/components/utilities/utilities.types.d.ts +46 -0
- package/dist/components/utilities/utilities.types.js +4 -0
- package/dist/index.d.ts +49 -4
- package/dist/index.js +4 -4
- package/dist/theme.css +2821 -218
- package/package.json +83 -76
|
@@ -1,35 +1,236 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
tabs
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
info: '
|
|
24
|
-
success: '
|
|
25
|
-
warning: '
|
|
26
|
-
error: '
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
</
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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>;
|