@mrintel/villain-ui 0.2.0 → 0.3.0

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 (133) hide show
  1. package/dist/components/buttons/Button.svelte +33 -0
  2. package/dist/components/buttons/Button.svelte.d.ts +11 -0
  3. package/dist/components/buttons/ButtonGroup.svelte +30 -0
  4. package/dist/components/buttons/ButtonGroup.svelte.d.ts +8 -0
  5. package/dist/components/buttons/FloatingActionButton.svelte +44 -0
  6. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +11 -0
  7. package/dist/components/buttons/IconButton.svelte +53 -0
  8. package/dist/components/buttons/IconButton.svelte.d.ts +13 -0
  9. package/dist/components/buttons/LinkButton.svelte +37 -0
  10. package/dist/components/buttons/LinkButton.svelte.d.ts +12 -0
  11. package/dist/components/buttons/buttonClasses.d.ts +10 -0
  12. package/dist/components/buttons/buttonClasses.js +10 -0
  13. package/dist/components/buttons/index.d.ts +5 -0
  14. package/dist/components/buttons/index.js +5 -0
  15. package/dist/components/cards/Card.svelte +46 -0
  16. package/dist/components/cards/Card.svelte.d.ts +11 -0
  17. package/dist/components/cards/Container.svelte +33 -0
  18. package/dist/components/cards/Container.svelte.d.ts +10 -0
  19. package/dist/components/cards/Divider.svelte +52 -0
  20. package/dist/components/cards/Divider.svelte.d.ts +9 -0
  21. package/dist/components/cards/Grid.svelte +44 -0
  22. package/dist/components/cards/Grid.svelte.d.ts +10 -0
  23. package/dist/components/cards/Panel.svelte +32 -0
  24. package/dist/components/cards/Panel.svelte.d.ts +10 -0
  25. package/dist/components/cards/SectionHeader.svelte +38 -0
  26. package/dist/components/cards/SectionHeader.svelte.d.ts +11 -0
  27. package/dist/components/cards/index.d.ts +6 -0
  28. package/dist/components/cards/index.js +6 -0
  29. package/dist/components/data/Avatar.svelte +67 -0
  30. package/dist/components/data/Avatar.svelte.d.ts +10 -0
  31. package/dist/components/data/Badge.svelte +32 -0
  32. package/dist/components/data/Badge.svelte.d.ts +8 -0
  33. package/dist/components/data/CodeBlock.svelte +121 -0
  34. package/dist/components/data/CodeBlock.svelte.d.ts +32 -0
  35. package/dist/components/data/List.svelte +64 -0
  36. package/dist/components/data/List.svelte.d.ts +8 -0
  37. package/dist/components/data/Pagination.svelte +123 -0
  38. package/dist/components/data/Pagination.svelte.d.ts +9 -0
  39. package/dist/components/data/Stat.svelte +103 -0
  40. package/dist/components/data/Stat.svelte.d.ts +11 -0
  41. package/dist/components/data/Table.svelte +76 -0
  42. package/dist/components/data/Table.svelte.d.ts +9 -0
  43. package/dist/components/data/Tag.svelte +53 -0
  44. package/dist/components/data/Tag.svelte.d.ts +9 -0
  45. package/dist/components/data/index.d.ts +8 -0
  46. package/dist/components/data/index.js +8 -0
  47. package/dist/components/forms/Checkbox.svelte +51 -0
  48. package/dist/components/forms/Checkbox.svelte.d.ts +10 -0
  49. package/dist/components/forms/FileUpload.svelte +164 -0
  50. package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
  51. package/dist/components/forms/Input.svelte +57 -0
  52. package/dist/components/forms/Input.svelte.d.ts +13 -0
  53. package/dist/components/forms/InputGroup.svelte +7 -0
  54. package/dist/components/forms/InputGroup.svelte.d.ts +20 -0
  55. package/dist/components/forms/RadioGroup.svelte +87 -0
  56. package/dist/components/forms/RadioGroup.svelte.d.ts +15 -0
  57. package/dist/components/forms/RangeSlider.svelte +116 -0
  58. package/dist/components/forms/RangeSlider.svelte.d.ts +14 -0
  59. package/dist/components/forms/Select.svelte +71 -0
  60. package/dist/components/forms/Select.svelte.d.ts +16 -0
  61. package/dist/components/forms/Switch.svelte +56 -0
  62. package/dist/components/forms/Switch.svelte.d.ts +10 -0
  63. package/dist/components/forms/Textarea.svelte +57 -0
  64. package/dist/components/forms/Textarea.svelte.d.ts +13 -0
  65. package/dist/components/forms/index.d.ts +9 -0
  66. package/dist/components/forms/index.js +9 -0
  67. package/dist/components/navigation/Breadcrumbs.svelte +59 -0
  68. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +14 -0
  69. package/dist/components/navigation/ContextMenu.svelte +83 -0
  70. package/dist/components/navigation/ContextMenu.svelte.d.ts +11 -0
  71. package/dist/components/navigation/DropdownMenu.svelte +80 -0
  72. package/dist/components/navigation/DropdownMenu.svelte.d.ts +10 -0
  73. package/dist/components/navigation/Menu.svelte +48 -0
  74. package/dist/components/navigation/Menu.svelte.d.ts +15 -0
  75. package/dist/components/navigation/Navbar.svelte +32 -0
  76. package/dist/components/navigation/Navbar.svelte.d.ts +9 -0
  77. package/dist/components/navigation/Sidebar.svelte +35 -0
  78. package/dist/components/navigation/Sidebar.svelte.d.ts +10 -0
  79. package/dist/components/navigation/Tabs.svelte +54 -0
  80. package/dist/components/navigation/Tabs.svelte.d.ts +15 -0
  81. package/dist/components/navigation/index.d.ts +7 -0
  82. package/dist/components/navigation/index.js +7 -0
  83. package/dist/components/overlays/Alert.svelte +99 -0
  84. package/dist/components/overlays/Alert.svelte.d.ts +11 -0
  85. package/dist/components/overlays/CommandPalette.svelte +217 -0
  86. package/dist/components/overlays/CommandPalette.svelte.d.ts +16 -0
  87. package/dist/components/overlays/Drawer.svelte +167 -0
  88. package/dist/components/overlays/Drawer.svelte.d.ts +14 -0
  89. package/dist/components/overlays/Dropdown.svelte +30 -0
  90. package/dist/components/overlays/Dropdown.svelte.d.ts +9 -0
  91. package/dist/components/overlays/Modal.svelte +130 -0
  92. package/dist/components/overlays/Modal.svelte.d.ts +13 -0
  93. package/dist/components/overlays/Popover.svelte +131 -0
  94. package/dist/components/overlays/Popover.svelte.d.ts +11 -0
  95. package/dist/components/overlays/ProgressBar.svelte +45 -0
  96. package/dist/components/overlays/ProgressBar.svelte.d.ts +10 -0
  97. package/dist/components/overlays/SkeletonLoader.svelte +82 -0
  98. package/dist/components/overlays/SkeletonLoader.svelte.d.ts +9 -0
  99. package/dist/components/overlays/Spinner.svelte +43 -0
  100. package/dist/components/overlays/Spinner.svelte.d.ts +7 -0
  101. package/dist/components/overlays/Toast.svelte +140 -0
  102. package/dist/components/overlays/Toast.svelte.d.ts +13 -0
  103. package/dist/components/overlays/Tooltip.svelte +115 -0
  104. package/dist/components/overlays/Tooltip.svelte.d.ts +10 -0
  105. package/dist/components/overlays/index.d.ts +11 -0
  106. package/dist/components/overlays/index.js +11 -0
  107. package/dist/components/typography/Code.svelte +14 -0
  108. package/dist/components/typography/Code.svelte.d.ts +6 -0
  109. package/dist/components/typography/Heading.svelte +22 -0
  110. package/dist/components/typography/Heading.svelte.d.ts +9 -0
  111. package/dist/components/typography/Text.svelte +24 -0
  112. package/dist/components/typography/Text.svelte.d.ts +9 -0
  113. package/dist/components/typography/index.d.ts +3 -0
  114. package/dist/components/typography/index.js +3 -0
  115. package/dist/components/utilities/Accordion.svelte +67 -0
  116. package/dist/components/utilities/Accordion.svelte.d.ts +14 -0
  117. package/dist/components/utilities/Carousel.svelte +152 -0
  118. package/dist/components/utilities/Carousel.svelte.d.ts +16 -0
  119. package/dist/components/utilities/Collapse.svelte +60 -0
  120. package/dist/components/utilities/Collapse.svelte.d.ts +10 -0
  121. package/dist/components/utilities/Portal.svelte +72 -0
  122. package/dist/components/utilities/Portal.svelte.d.ts +21 -0
  123. package/dist/components/utilities/ScrollArea.svelte +41 -0
  124. package/dist/components/utilities/ScrollArea.svelte.d.ts +8 -0
  125. package/dist/components/utilities/index.d.ts +5 -0
  126. package/dist/components/utilities/index.js +5 -0
  127. package/dist/index.d.ts +15 -175
  128. package/dist/index.js +24 -4560
  129. package/dist/lib/internal/id.d.ts +12 -0
  130. package/dist/lib/internal/id.js +15 -0
  131. package/dist/theme.css +218 -0
  132. package/package.json +14 -7
  133. package/dist/index.css +0 -1
@@ -0,0 +1,54 @@
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>
@@ -0,0 +1,15 @@
1
+ interface Tab {
2
+ id: string;
3
+ label: string;
4
+ disabled?: boolean;
5
+ }
6
+ interface Props {
7
+ tabs: Tab[];
8
+ activeTab?: string;
9
+ orientation?: 'horizontal' | 'vertical';
10
+ variant?: 'default' | 'pills';
11
+ onTabChange?: (tabId: string) => void;
12
+ }
13
+ declare const Tabs: import("svelte").Component<Props, {}, "activeTab">;
14
+ type Tabs = ReturnType<typeof Tabs>;
15
+ export default Tabs;
@@ -0,0 +1,7 @@
1
+ export { default as Navbar } from './Navbar.svelte';
2
+ export { default as Sidebar } from './Sidebar.svelte';
3
+ export { default as Tabs } from './Tabs.svelte';
4
+ export { default as Breadcrumbs } from './Breadcrumbs.svelte';
5
+ export { default as Menu } from './Menu.svelte';
6
+ export { default as DropdownMenu } from './DropdownMenu.svelte';
7
+ export { default as ContextMenu } from './ContextMenu.svelte';
@@ -0,0 +1,7 @@
1
+ export { default as Navbar } from './Navbar.svelte';
2
+ export { default as Sidebar } from './Sidebar.svelte';
3
+ export { default as Tabs } from './Tabs.svelte';
4
+ export { default as Breadcrumbs } from './Breadcrumbs.svelte';
5
+ export { default as Menu } from './Menu.svelte';
6
+ export { default as DropdownMenu } from './DropdownMenu.svelte';
7
+ export { default as ContextMenu } from './ContextMenu.svelte';
@@ -0,0 +1,99 @@
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}
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ variant?: 'info' | 'success' | 'warning' | 'error';
4
+ title?: string;
5
+ dismissible?: boolean;
6
+ onclose?: () => void;
7
+ children?: Snippet;
8
+ }
9
+ declare const Alert: import("svelte").Component<Props, {}, "">;
10
+ type Alert = ReturnType<typeof Alert>;
11
+ export default Alert;
@@ -0,0 +1,217 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { createId } from '../../lib/internal/id.js';
4
+
5
+ interface Command {
6
+ id: string;
7
+ label: string;
8
+ icon?: Snippet;
9
+ onSelect: () => void;
10
+ keywords?: string[];
11
+ }
12
+
13
+ interface Props {
14
+ open?: boolean;
15
+ commands: Command[];
16
+ placeholder?: string;
17
+ }
18
+
19
+ let {
20
+ open = $bindable(false),
21
+ commands,
22
+ placeholder = 'Search commands...'
23
+ }: Props = $props();
24
+
25
+ let query = $state('');
26
+ let selectedIndex = $state(0);
27
+ let inputElement = $state<HTMLInputElement>();
28
+ let previousFocus = $state<HTMLElement | null>(null);
29
+
30
+ const paletteId = createId('command-palette');
31
+
32
+ // Inline fuzzy matching function
33
+ function fuzzyMatch(text: string, search: string): boolean {
34
+ const searchChars = search.toLowerCase().split('');
35
+ const textLower = text.toLowerCase();
36
+ let searchIndex = 0;
37
+
38
+ for (let i = 0; i < textLower.length && searchIndex < searchChars.length; i++) {
39
+ if (textLower[i] === searchChars[searchIndex]) {
40
+ searchIndex++;
41
+ }
42
+ }
43
+
44
+ return searchIndex === searchChars.length;
45
+ }
46
+
47
+ let filteredCommands = $derived(
48
+ query.trim() === ''
49
+ ? commands
50
+ : commands.filter(cmd => {
51
+ // Match against label
52
+ if (fuzzyMatch(cmd.label, query)) return true;
53
+
54
+ // Match against keywords
55
+ if (cmd.keywords) {
56
+ return cmd.keywords.some(keyword => fuzzyMatch(keyword, query));
57
+ }
58
+
59
+ return false;
60
+ })
61
+ );
62
+
63
+ function handleClose() {
64
+ open = false;
65
+ query = '';
66
+ selectedIndex = 0;
67
+ }
68
+
69
+ function handleBackdropClick(event: MouseEvent) {
70
+ if (event.target === event.currentTarget) {
71
+ handleClose();
72
+ }
73
+ }
74
+
75
+ function handleSelectCommand(command: Command) {
76
+ command.onSelect();
77
+ handleClose();
78
+ }
79
+
80
+ function handleKeyDown(event: KeyboardEvent) {
81
+ if (event.key === 'ArrowDown') {
82
+ if (!filteredCommands.length) return;
83
+ event.preventDefault();
84
+ selectedIndex = (selectedIndex + 1) % filteredCommands.length;
85
+ } else if (event.key === 'ArrowUp') {
86
+ if (!filteredCommands.length) return;
87
+ event.preventDefault();
88
+ selectedIndex = (selectedIndex - 1 + filteredCommands.length) % filteredCommands.length;
89
+ } else if (event.key === 'Enter') {
90
+ event.preventDefault();
91
+ if (filteredCommands[selectedIndex]) {
92
+ handleSelectCommand(filteredCommands[selectedIndex]);
93
+ } else if (filteredCommands.length) {
94
+ selectedIndex = 0;
95
+ }
96
+ } else if (event.key === 'Escape') {
97
+ event.preventDefault();
98
+ handleClose();
99
+ }
100
+ }
101
+
102
+ // Reset and clamp selected index when filtered commands change
103
+ $effect(() => {
104
+ if (filteredCommands.length === 0) {
105
+ selectedIndex = -1;
106
+ } else if (selectedIndex >= filteredCommands.length || selectedIndex < 0) {
107
+ selectedIndex = 0;
108
+ }
109
+ });
110
+
111
+ $effect(() => {
112
+ if (typeof document === 'undefined') return;
113
+
114
+ if (open) {
115
+ // Store previous focus
116
+ previousFocus = document.activeElement as HTMLElement;
117
+
118
+ // Prevent body scroll
119
+ document.body.style.overflow = 'hidden';
120
+
121
+ // Focus input element
122
+ requestAnimationFrame(() => {
123
+ inputElement?.focus();
124
+ });
125
+
126
+ return () => {
127
+ // Restore body scroll
128
+ document.body.style.overflow = '';
129
+
130
+ // Restore previous focus
131
+ previousFocus?.focus();
132
+ };
133
+ }
134
+ });
135
+ </script>
136
+
137
+ {#if open}
138
+ <div
139
+ class="fixed inset-0 z-50 flex items-start justify-center pt-[20vh] p-4 bg-overlay backdrop-blur-md animate-[fade-in_0.2s_var(--ease-luxe)]"
140
+ onclick={handleBackdropClick}
141
+ role="presentation"
142
+ >
143
+ <div
144
+ class="glass-panel rounded-xl shadow-deep w-full max-w-[36rem] animate-[fade-up_0.3s_var(--ease-luxe)]"
145
+ role="combobox"
146
+ aria-expanded="true"
147
+ aria-haspopup="listbox"
148
+ aria-controls={paletteId}
149
+ aria-activedescendant={selectedIndex >= 0 && selectedIndex < filteredCommands.length && filteredCommands[selectedIndex] ? `cmd-${filteredCommands[selectedIndex].id}` : undefined}
150
+ tabindex="0"
151
+ >
152
+ <div class="p-4 border-b border-border">
153
+ <input
154
+ bind:this={inputElement}
155
+ bind:value={query}
156
+ type="text"
157
+ placeholder={placeholder}
158
+ onkeydown={handleKeyDown}
159
+ class="w-full bg-transparent border-none outline-none text-text text-lg placeholder:text-text-muted"
160
+ aria-autocomplete="list"
161
+ />
162
+ </div>
163
+
164
+ <div
165
+ id={paletteId}
166
+ role="listbox"
167
+ class="max-h-[400px] overflow-y-auto"
168
+ style="scrollbar-width: thin; scrollbar-color: var(--color-accent) var(--color-base-3);"
169
+ >
170
+ {#if filteredCommands.length === 0}
171
+ <div class="p-8 text-center text-text-muted">
172
+ No commands found
173
+ </div>
174
+ {:else}
175
+ {#each filteredCommands as command, index (command.id)}
176
+ <button
177
+ id="cmd-{command.id}"
178
+ type="button"
179
+ role="option"
180
+ aria-selected={index === selectedIndex}
181
+ tabindex={index === selectedIndex ? 0 : -1}
182
+ onclick={() => handleSelectCommand(command)}
183
+ class="w-full flex items-center gap-3 p-4 text-left text-text hover:bg-base-3 transition-colors {index === selectedIndex ? 'accent-glow bg-base-3' : ''}"
184
+ >
185
+ {#if command.icon}
186
+ <div class="shrink-0">
187
+ {@render command.icon()}
188
+ </div>
189
+ {/if}
190
+ <span>{command.label}</span>
191
+ </button>
192
+ {/each}
193
+ {/if}
194
+ </div>
195
+ </div>
196
+ </div>
197
+ {/if}
198
+
199
+ <style>
200
+ div[role="listbox"]::-webkit-scrollbar {
201
+ width: 8px;
202
+ }
203
+
204
+ div[role="listbox"]::-webkit-scrollbar-track {
205
+ background: var(--color-base-3);
206
+ border-radius: var(--radius-sm);
207
+ }
208
+
209
+ div[role="listbox"]::-webkit-scrollbar-thumb {
210
+ background: var(--color-accent);
211
+ border-radius: var(--radius-sm);
212
+ }
213
+
214
+ div[role="listbox"]::-webkit-scrollbar-thumb:hover {
215
+ background: var(--color-accent-soft);
216
+ }
217
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Command {
3
+ id: string;
4
+ label: string;
5
+ icon?: Snippet;
6
+ onSelect: () => void;
7
+ keywords?: string[];
8
+ }
9
+ interface Props {
10
+ open?: boolean;
11
+ commands: Command[];
12
+ placeholder?: string;
13
+ }
14
+ declare const CommandPalette: import("svelte").Component<Props, {}, "open">;
15
+ type CommandPalette = ReturnType<typeof CommandPalette>;
16
+ export default CommandPalette;
@@ -0,0 +1,167 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { createId } from '../../lib/internal/id.js';
4
+ import ScrollArea from '../utilities/ScrollArea.svelte';
5
+
6
+ interface Props {
7
+ open?: boolean;
8
+ side?: 'left' | 'right' | 'top' | 'bottom';
9
+ size?: 'sm' | 'md' | 'lg';
10
+ title?: string;
11
+ closeOnBackdrop?: boolean;
12
+ closeOnEscape?: boolean;
13
+ children?: Snippet;
14
+ footer?: Snippet;
15
+ }
16
+
17
+ let {
18
+ open = $bindable(false),
19
+ side = 'right',
20
+ size = 'md',
21
+ title,
22
+ closeOnBackdrop = true,
23
+ closeOnEscape = true,
24
+ children,
25
+ footer
26
+ }: Props = $props();
27
+
28
+ let drawerElement = $state<HTMLDivElement>();
29
+ let previousFocus = $state<HTMLElement | null>(null);
30
+
31
+ const titleId = createId('drawer-title');
32
+
33
+ const sizeClasses = {
34
+ left: {
35
+ sm: 'max-w-[20rem]',
36
+ md: 'max-w-[28rem]',
37
+ lg: 'max-w-[36rem]'
38
+ },
39
+ right: {
40
+ sm: 'max-w-[20rem]',
41
+ md: 'max-w-[28rem]',
42
+ lg: 'max-w-[36rem]'
43
+ },
44
+ top: {
45
+ sm: 'max-h-[20rem]',
46
+ md: 'max-h-[28rem]',
47
+ lg: 'max-h-[36rem]'
48
+ },
49
+ bottom: {
50
+ sm: 'max-h-[20rem]',
51
+ md: 'max-h-[28rem]',
52
+ lg: 'max-h-[36rem]'
53
+ }
54
+ };
55
+
56
+ const positionClasses = {
57
+ left: 'left-0 top-0 h-full',
58
+ right: 'right-0 top-0 h-full',
59
+ top: 'top-0 left-0 w-full',
60
+ bottom: 'bottom-0 left-0 w-full'
61
+ };
62
+
63
+ const animationClasses = {
64
+ left: 'animate-[slide-in-left_0.3s_var(--ease-luxe)]',
65
+ right: 'animate-[slide-in-right_0.3s_var(--ease-luxe)]',
66
+ top: 'animate-[slide-in-top_0.3s_var(--ease-luxe)]',
67
+ bottom: 'animate-[slide-in-bottom_0.3s_var(--ease-luxe)]'
68
+ };
69
+
70
+ function handleClose() {
71
+ open = false;
72
+ }
73
+
74
+ function handleBackdropClick(event: MouseEvent) {
75
+ if (closeOnBackdrop && event.target === event.currentTarget) {
76
+ handleClose();
77
+ }
78
+ }
79
+
80
+ function handleEscape(event: KeyboardEvent) {
81
+ if (closeOnEscape && event.key === 'Escape') {
82
+ handleClose();
83
+ }
84
+ }
85
+
86
+ $effect(() => {
87
+ if (typeof document === 'undefined') return;
88
+
89
+ if (open) {
90
+ // Store previous focus
91
+ previousFocus = document.activeElement as HTMLElement;
92
+
93
+ // Prevent body scroll
94
+ document.body.style.overflow = 'hidden';
95
+
96
+ // Add event listeners
97
+ document.addEventListener('keydown', handleEscape);
98
+
99
+ // Focus first interactive element
100
+ requestAnimationFrame(() => {
101
+ const firstInteractive = drawerElement?.querySelector<HTMLElement>(
102
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
103
+ );
104
+ firstInteractive?.focus();
105
+ });
106
+
107
+ return () => {
108
+ // Restore body scroll
109
+ document.body.style.overflow = '';
110
+
111
+ // Remove event listeners
112
+ document.removeEventListener('keydown', handleEscape);
113
+
114
+ // Restore previous focus
115
+ previousFocus?.focus();
116
+ };
117
+ }
118
+ });
119
+ </script>
120
+
121
+ {#if open}
122
+ <div
123
+ class="fixed inset-0 z-50 bg-overlay backdrop-blur-md animate-[fade-in_0.2s_var(--ease-luxe)]"
124
+ onclick={handleBackdropClick}
125
+ role="presentation"
126
+ >
127
+ <div
128
+ bind:this={drawerElement}
129
+ class="glass-panel shadow-deep fixed {positionClasses[side]} {sizeClasses[side][size]} {animationClasses[side]} flex flex-col"
130
+ role="dialog"
131
+ aria-modal="true"
132
+ aria-labelledby={title ? titleId : undefined}
133
+ >
134
+ {#if title}
135
+ <div class="flex items-center justify-between p-6 border-b border-border">
136
+ <h2 id={titleId} class="text-xl font-semibold text-text">
137
+ {title}
138
+ </h2>
139
+ <button
140
+ type="button"
141
+ onclick={handleClose}
142
+ class="text-text-soft hover:text-text transition-colors"
143
+ aria-label="Close drawer"
144
+ >
145
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
146
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
147
+ </svg>
148
+ </button>
149
+ </div>
150
+ {/if}
151
+
152
+ <div class="flex-1 overflow-hidden">
153
+ <ScrollArea height="100%">
154
+ <div class="p-6">
155
+ {@render children?.()}
156
+ </div>
157
+ </ScrollArea>
158
+ </div>
159
+
160
+ {#if footer}
161
+ <div class="flex items-center justify-end gap-3 p-6 border-t border-border">
162
+ {@render footer?.()}
163
+ </div>
164
+ {/if}
165
+ </div>
166
+ </div>
167
+ {/if}
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ open?: boolean;
4
+ side?: 'left' | 'right' | 'top' | 'bottom';
5
+ size?: 'sm' | 'md' | 'lg';
6
+ title?: string;
7
+ closeOnBackdrop?: boolean;
8
+ closeOnEscape?: boolean;
9
+ children?: Snippet;
10
+ footer?: Snippet;
11
+ }
12
+ declare const Drawer: import("svelte").Component<Props, {}, "open">;
13
+ type Drawer = ReturnType<typeof Drawer>;
14
+ export default Drawer;
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ open?: boolean;
6
+ placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
7
+ children?: Snippet;
8
+ }
9
+
10
+ let {
11
+ open = $bindable(false),
12
+ placement = 'bottom-start',
13
+ children
14
+ }: Props = $props();
15
+
16
+ const placementClasses = {
17
+ 'bottom-start': 'top-full left-0 mt-2',
18
+ 'bottom-end': 'top-full right-0 mt-2',
19
+ 'top-start': 'bottom-full left-0 mb-2',
20
+ 'top-end': 'bottom-full right-0 mb-2'
21
+ };
22
+ </script>
23
+
24
+ {#if open}
25
+ <div
26
+ class="absolute {placementClasses[placement]} z-50 glass-panel rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-up_0.2s_var(--ease-luxe)]"
27
+ >
28
+ {@render children?.()}
29
+ </div>
30
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ open?: boolean;
4
+ placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
5
+ children?: Snippet;
6
+ }
7
+ declare const Dropdown: import("svelte").Component<Props, {}, "open">;
8
+ type Dropdown = ReturnType<typeof Dropdown>;
9
+ export default Dropdown;