@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,130 +1,125 @@
|
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
</div>
|
|
127
|
-
{/if}
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
{/if}
|
|
1
|
+
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
|
+
let { open = $bindable(false), title, size = 'md', closeOnBackdrop = true, closeOnEscape = true, children, footer, iconBefore, class: className = '' } = $props();
|
|
3
|
+
let modalElement = $state();
|
|
4
|
+
let previousFocus = $state(null);
|
|
5
|
+
const sizeClasses = {
|
|
6
|
+
sm: 'max-w-[28rem]',
|
|
7
|
+
md: 'max-w-[36rem]',
|
|
8
|
+
lg: 'max-w-[48rem]',
|
|
9
|
+
xl: 'max-w-[64rem]'
|
|
10
|
+
};
|
|
11
|
+
const titleId = createId('modal-title');
|
|
12
|
+
const focusableSelector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable="true"], summary, details, audio[controls], video[controls]';
|
|
13
|
+
function handleClose() {
|
|
14
|
+
open = false;
|
|
15
|
+
}
|
|
16
|
+
function handleBackdropClick(event) {
|
|
17
|
+
if (closeOnBackdrop && event.target === event.currentTarget) {
|
|
18
|
+
handleClose();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function handleEscape(event) {
|
|
22
|
+
if (closeOnEscape && event.key === 'Escape') {
|
|
23
|
+
handleClose();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function handleFocusTrap(event) {
|
|
27
|
+
if (event.key !== 'Tab' || !modalElement)
|
|
28
|
+
return;
|
|
29
|
+
const focusableElements = Array.from(modalElement.querySelectorAll(focusableSelector));
|
|
30
|
+
if (focusableElements.length === 0)
|
|
31
|
+
return;
|
|
32
|
+
const firstFocusable = focusableElements[0];
|
|
33
|
+
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
34
|
+
if (event.shiftKey) {
|
|
35
|
+
// Shift+Tab: moving backwards
|
|
36
|
+
if (document.activeElement === firstFocusable) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
lastFocusable.focus();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Tab: moving forwards
|
|
43
|
+
if (document.activeElement === lastFocusable) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
firstFocusable.focus();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
$effect(() => {
|
|
50
|
+
if (typeof document === 'undefined')
|
|
51
|
+
return;
|
|
52
|
+
if (open) {
|
|
53
|
+
// Store previous focus
|
|
54
|
+
previousFocus = document.activeElement;
|
|
55
|
+
// Prevent body scroll
|
|
56
|
+
document.body.style.overflow = 'hidden';
|
|
57
|
+
// Add event listeners
|
|
58
|
+
document.addEventListener('keydown', handleEscape);
|
|
59
|
+
document.addEventListener('keydown', handleFocusTrap);
|
|
60
|
+
// Focus first interactive element
|
|
61
|
+
requestAnimationFrame(() => {
|
|
62
|
+
const firstInteractive = modalElement?.querySelector(focusableSelector);
|
|
63
|
+
firstInteractive?.focus();
|
|
64
|
+
});
|
|
65
|
+
return () => {
|
|
66
|
+
// Restore body scroll
|
|
67
|
+
document.body.style.overflow = '';
|
|
68
|
+
// Remove event listeners
|
|
69
|
+
document.removeEventListener('keydown', handleEscape);
|
|
70
|
+
document.removeEventListener('keydown', handleFocusTrap);
|
|
71
|
+
// Restore previous focus
|
|
72
|
+
previousFocus?.focus();
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
{#if open}
|
|
79
|
+
<div
|
|
80
|
+
class="fixed inset-0 z-[var(--z-50)] flex items-center justify-center p-4 bg-overlay backdrop-blur-md animate-[fade-in_0.2s_var(--ease-luxe)]"
|
|
81
|
+
onclick={handleBackdropClick}
|
|
82
|
+
role="presentation"
|
|
83
|
+
>
|
|
84
|
+
<div
|
|
85
|
+
bind:this={modalElement}
|
|
86
|
+
class="panel-floating rounded-[var(--radius-lg)] shadow-deep w-full {sizeClasses[size]} {className} animate-[fade-up_0.3s_var(--ease-luxe)] flex flex-col max-h-[90vh]"
|
|
87
|
+
role="dialog"
|
|
88
|
+
aria-modal="true"
|
|
89
|
+
aria-labelledby={title ? titleId : undefined}
|
|
90
|
+
>
|
|
91
|
+
{#if title}
|
|
92
|
+
<div class="flex items-center justify-between p-8 border-b border-border">
|
|
93
|
+
<h2 id={titleId} class="text-xl font-semibold text-text flex items-center gap-3">
|
|
94
|
+
{#if iconBefore}
|
|
95
|
+
<span class="inline-flex items-center justify-center">
|
|
96
|
+
{@render iconBefore()}
|
|
97
|
+
</span>
|
|
98
|
+
{/if}
|
|
99
|
+
{title}
|
|
100
|
+
</h2>
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onclick={handleClose}
|
|
104
|
+
class="text-text-soft hover:text-text transition-colors"
|
|
105
|
+
aria-label="Close modal"
|
|
106
|
+
>
|
|
107
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
108
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
109
|
+
</svg>
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
{/if}
|
|
113
|
+
|
|
114
|
+
<div class="flex-1 overflow-y-auto p-8 max-h-[70vh]" style="scrollbar-width: thin; scrollbar-color: var(--color-accent) var(--color-base-3);">
|
|
115
|
+
{@render children?.()}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
{#if footer}
|
|
119
|
+
<div class="flex items-center justify-end gap-4 p-8 border-t border-border">
|
|
120
|
+
{@render footer?.()}
|
|
121
|
+
</div>
|
|
122
|
+
{/if}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
{/if}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
interface Props {
|
|
2
|
+
export interface Props {
|
|
3
3
|
open?: boolean;
|
|
4
4
|
title?: string;
|
|
5
5
|
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
@@ -7,6 +7,8 @@ interface Props {
|
|
|
7
7
|
closeOnEscape?: boolean;
|
|
8
8
|
children?: Snippet;
|
|
9
9
|
footer?: Snippet;
|
|
10
|
+
iconBefore?: Snippet;
|
|
11
|
+
class?: string;
|
|
10
12
|
}
|
|
11
13
|
declare const Modal: import("svelte").Component<Props, {}, "open">;
|
|
12
14
|
type Modal = ReturnType<typeof Modal>;
|
|
@@ -1,131 +1,106 @@
|
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
open
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</script>
|
|
108
|
-
|
|
109
|
-
<div bind:this={wrapperElement} class="relative inline-block">
|
|
110
|
-
<button
|
|
111
|
-
type="button"
|
|
112
|
-
onclick={toggleOpen}
|
|
113
|
-
aria-haspopup="true"
|
|
114
|
-
aria-expanded={open}
|
|
115
|
-
aria-controls={open ? popoverId : undefined}
|
|
116
|
-
class="bg-transparent border-none p-0 cursor-pointer"
|
|
117
|
-
>
|
|
118
|
-
{@render trigger?.()}
|
|
119
|
-
</button>
|
|
120
|
-
|
|
121
|
-
{#if open}
|
|
122
|
-
<div
|
|
123
|
-
bind:this={popoverElement}
|
|
124
|
-
id={popoverId}
|
|
125
|
-
class="absolute {placementClasses[actualPlacement]} z-50 glass-panel rounded-lg shadow-deep animate-[fade-up_0.2s_var(--ease-luxe)]"
|
|
126
|
-
role="dialog"
|
|
127
|
-
>
|
|
128
|
-
{@render children?.()}
|
|
129
|
-
</div>
|
|
130
|
-
{/if}
|
|
131
|
-
</div>
|
|
1
|
+
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
|
+
let { open = $bindable(false), placement = 'bottom', closeOnClickOutside = true, trigger, children } = $props();
|
|
3
|
+
let popoverElement = $state();
|
|
4
|
+
let wrapperElement = $state();
|
|
5
|
+
let actualPlacement = $state(placement);
|
|
6
|
+
const popoverId = createId('popover');
|
|
7
|
+
const placementClasses = {
|
|
8
|
+
'top': 'bottom-full left-1/2 -translate-x-1/2 mb-2',
|
|
9
|
+
'top-start': 'bottom-full left-0 mb-2',
|
|
10
|
+
'top-end': 'bottom-full right-0 mb-2',
|
|
11
|
+
'bottom': 'top-full left-1/2 -translate-x-1/2 mt-2',
|
|
12
|
+
'bottom-start': 'top-full left-0 mt-2',
|
|
13
|
+
'bottom-end': 'top-full right-0 mt-2',
|
|
14
|
+
'left': 'right-full top-1/2 -translate-y-1/2 mr-2',
|
|
15
|
+
'right': 'left-full top-1/2 -translate-y-1/2 ml-2'
|
|
16
|
+
};
|
|
17
|
+
const oppositePlacement = {
|
|
18
|
+
'top': 'bottom',
|
|
19
|
+
'top-start': 'bottom-start',
|
|
20
|
+
'top-end': 'bottom-end',
|
|
21
|
+
'bottom': 'top',
|
|
22
|
+
'bottom-start': 'top-start',
|
|
23
|
+
'bottom-end': 'top-end',
|
|
24
|
+
'left': 'right',
|
|
25
|
+
'right': 'left'
|
|
26
|
+
};
|
|
27
|
+
function toggleOpen() {
|
|
28
|
+
open = !open;
|
|
29
|
+
}
|
|
30
|
+
function handleClickOutside(event) {
|
|
31
|
+
if (closeOnClickOutside && popoverElement && wrapperElement && !wrapperElement.contains(event.target)) {
|
|
32
|
+
open = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function handleEscape(event) {
|
|
36
|
+
if (event.key === 'Escape') {
|
|
37
|
+
open = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Reset actualPlacement when visibility changes
|
|
41
|
+
$effect(() => {
|
|
42
|
+
if (!open) {
|
|
43
|
+
actualPlacement = placement;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// Check viewport bounds and flip placement if needed
|
|
47
|
+
$effect(() => {
|
|
48
|
+
if (typeof window === 'undefined')
|
|
49
|
+
return;
|
|
50
|
+
if (open && popoverElement) {
|
|
51
|
+
const rect = popoverElement.getBoundingClientRect();
|
|
52
|
+
const viewportWidth = window.innerWidth;
|
|
53
|
+
const viewportHeight = window.innerHeight;
|
|
54
|
+
// Determine if current placement overflows and flip if needed
|
|
55
|
+
if (actualPlacement.startsWith('top') && rect.top < 0) {
|
|
56
|
+
actualPlacement = oppositePlacement[actualPlacement] || placement;
|
|
57
|
+
}
|
|
58
|
+
else if (actualPlacement.startsWith('bottom') && rect.bottom > viewportHeight) {
|
|
59
|
+
actualPlacement = oppositePlacement[actualPlacement] || placement;
|
|
60
|
+
}
|
|
61
|
+
else if (actualPlacement === 'left' && rect.left < 0) {
|
|
62
|
+
actualPlacement = 'right';
|
|
63
|
+
}
|
|
64
|
+
else if (actualPlacement === 'right' && rect.right > viewportWidth) {
|
|
65
|
+
actualPlacement = 'left';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
$effect(() => {
|
|
70
|
+
if (typeof document === 'undefined')
|
|
71
|
+
return;
|
|
72
|
+
if (open) {
|
|
73
|
+
document.addEventListener('click', handleClickOutside);
|
|
74
|
+
document.addEventListener('keydown', handleEscape);
|
|
75
|
+
return () => {
|
|
76
|
+
document.removeEventListener('click', handleClickOutside);
|
|
77
|
+
document.removeEventListener('keydown', handleEscape);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<div bind:this={wrapperElement} class="relative inline-block">
|
|
84
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
85
|
+
<div
|
|
86
|
+
onclick={toggleOpen}
|
|
87
|
+
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleOpen(); } }}
|
|
88
|
+
aria-haspopup="true"
|
|
89
|
+
aria-expanded={open}
|
|
90
|
+
aria-controls={open ? popoverId : undefined}
|
|
91
|
+
class="inline-block"
|
|
92
|
+
>
|
|
93
|
+
{@render trigger?.()}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{#if open}
|
|
97
|
+
<div
|
|
98
|
+
bind:this={popoverElement}
|
|
99
|
+
id={popoverId}
|
|
100
|
+
class="absolute {placementClasses[actualPlacement]} z-[var(--z-50)] panel-floating rounded-[var(--radius-lg)] shadow-deep p-4 animate-[fade-up_0.2s_var(--ease-luxe)]"
|
|
101
|
+
role="dialog"
|
|
102
|
+
>
|
|
103
|
+
{@render children?.()}
|
|
104
|
+
</div>
|
|
105
|
+
{/if}
|
|
106
|
+
</div>
|
|
@@ -1,45 +1,29 @@
|
|
|
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
|
-
aria-valuenow={value}
|
|
31
|
-
aria-valuemin="0"
|
|
32
|
-
aria-valuemax={max}
|
|
33
|
-
aria-label={label || `${percentage.toFixed(0)}% complete`}
|
|
34
|
-
>
|
|
35
|
-
<div
|
|
36
|
-
class="h-full bg-[var(--color-accent)] accent-glow transition-all duration-500 ease-[var(--ease-luxe)]"
|
|
37
|
-
style="width: {percentage}%"
|
|
38
|
-
/>
|
|
39
|
-
|
|
40
|
-
{#if showLabel}
|
|
41
|
-
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-[var(--color-text)] text-glow">
|
|
42
|
-
{label || `${percentage.toFixed(0)}%`}
|
|
43
|
-
</div>
|
|
44
|
-
{/if}
|
|
45
|
-
</div>
|
|
1
|
+
<script lang="ts">"use strict";
|
|
2
|
+
let { value, max = 100, size = 'md', showLabel = false, label } = $props();
|
|
3
|
+
const percentage = $derived(Math.min(100, Math.max(0, (value / max) * 100)));
|
|
4
|
+
const heightClasses = {
|
|
5
|
+
sm: 'h-2',
|
|
6
|
+
md: 'h-3',
|
|
7
|
+
lg: 'h-4'
|
|
8
|
+
};
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div
|
|
12
|
+
class="relative bg-[var(--color-base-3)] border border-[var(--color-border)] rounded-[var(--radius-pill)] overflow-hidden shadow-[var(--shadow-deep)] {heightClasses[size]}"
|
|
13
|
+
role="progressbar"
|
|
14
|
+
aria-valuenow={value}
|
|
15
|
+
aria-valuemin="0"
|
|
16
|
+
aria-valuemax={max}
|
|
17
|
+
aria-label={label || `${percentage.toFixed(0)}% complete`}
|
|
18
|
+
>
|
|
19
|
+
<div
|
|
20
|
+
class="h-full bg-[var(--color-accent)] accent-glow transition-all duration-500 ease-[var(--ease-luxe)]"
|
|
21
|
+
style="width: {percentage}%"
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
{#if showLabel}
|
|
25
|
+
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-[var(--color-text)] text-glow">
|
|
26
|
+
{label || `${percentage.toFixed(0)}%`}
|
|
27
|
+
</div>
|
|
28
|
+
{/if}
|
|
29
|
+
</div>
|