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