@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,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface MenuItem {
3
+ id: string;
4
+ label: string;
5
+ onclick?: () => void;
6
+ disabled?: boolean;
7
+ icon?: Snippet;
8
+ }
9
+ interface Props {
10
+ items: MenuItem[];
11
+ open?: boolean;
12
+ x?: number;
13
+ y?: number;
14
+ trigger?: Snippet;
15
+ }
16
+ declare const ContextMenu: import("svelte").Component<Props, {}, "open" | "x" | "y">;
17
+ type ContextMenu = ReturnType<typeof ContextMenu>;
18
+ export default ContextMenu;
@@ -0,0 +1,139 @@
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ let { items, open = $bindable(false), placement = 'bottom-start', trigger } = $props();
3
+ let menuElement = $state();
4
+ let wrapperElement;
5
+ let selectedIndex = $state(0);
6
+ const menuId = createId('dropdown-menu');
7
+ const placementClasses = {
8
+ 'bottom-start': 'top-full left-0 mt-2',
9
+ 'bottom-end': 'top-full right-0 mt-2',
10
+ 'top-start': 'bottom-full left-0 mb-2',
11
+ 'top-end': 'bottom-full right-0 mb-2'
12
+ };
13
+ function toggleMenu() {
14
+ open = !open;
15
+ if (open) {
16
+ selectedIndex = 0;
17
+ }
18
+ }
19
+ function handleItemClick(item) {
20
+ if (item.disabled)
21
+ return;
22
+ item.onclick?.();
23
+ open = false;
24
+ }
25
+ function handleMenuKeyDown(event) {
26
+ // Derive current index from focused element to stay in sync
27
+ const focusedElement = document.activeElement;
28
+ const dataIndex = focusedElement?.getAttribute('data-index');
29
+ if (dataIndex !== null) {
30
+ const currentFocusedIndex = parseInt(dataIndex || '0', 10);
31
+ if (!isNaN(currentFocusedIndex) && currentFocusedIndex >= 0 && currentFocusedIndex < items.length) {
32
+ selectedIndex = currentFocusedIndex;
33
+ }
34
+ }
35
+ const enabledItems = items.filter(item => !item.disabled);
36
+ const currentEnabledIndex = enabledItems.findIndex(item => item.id === items[selectedIndex]?.id);
37
+ let nextIndex = currentEnabledIndex;
38
+ switch (event.key) {
39
+ case 'ArrowDown':
40
+ event.preventDefault();
41
+ nextIndex = currentEnabledIndex < enabledItems.length - 1 ? currentEnabledIndex + 1 : 0;
42
+ selectedIndex = items.findIndex(item => item.id === enabledItems[nextIndex]?.id);
43
+ break;
44
+ case 'ArrowUp':
45
+ event.preventDefault();
46
+ nextIndex = currentEnabledIndex > 0 ? currentEnabledIndex - 1 : enabledItems.length - 1;
47
+ selectedIndex = items.findIndex(item => item.id === enabledItems[nextIndex]?.id);
48
+ break;
49
+ case 'Home':
50
+ event.preventDefault();
51
+ selectedIndex = items.findIndex(item => item.id === enabledItems[0]?.id);
52
+ break;
53
+ case 'End':
54
+ event.preventDefault();
55
+ selectedIndex = items.findIndex(item => item.id === enabledItems[enabledItems.length - 1]?.id);
56
+ break;
57
+ case 'Enter':
58
+ case ' ':
59
+ event.preventDefault();
60
+ if (items[selectedIndex] && !items[selectedIndex].disabled) {
61
+ handleItemClick(items[selectedIndex]);
62
+ }
63
+ break;
64
+ case 'Escape':
65
+ event.preventDefault();
66
+ open = false;
67
+ break;
68
+ }
69
+ }
70
+ function handleClickOutside(event) {
71
+ if (menuElement && !wrapperElement.contains(event.target)) {
72
+ open = false;
73
+ }
74
+ }
75
+ $effect(() => {
76
+ if (open) {
77
+ document.addEventListener('click', handleClickOutside);
78
+ requestAnimationFrame(() => {
79
+ const firstItem = menuElement?.querySelector('[role="menuitem"]');
80
+ if (firstItem) {
81
+ const dataIndex = firstItem.getAttribute('data-index');
82
+ if (dataIndex !== null) {
83
+ selectedIndex = parseInt(dataIndex, 10);
84
+ }
85
+ firstItem.focus();
86
+ }
87
+ });
88
+ return () => {
89
+ document.removeEventListener('click', handleClickOutside);
90
+ };
91
+ }
92
+ });
93
+ </script>
94
+
95
+ <div bind:this={wrapperElement} class="relative">
96
+ <button
97
+ type="button"
98
+ onclick={toggleMenu}
99
+ onkeydown={(e) => {
100
+ if (e.key === 'Enter' || e.key === ' ') {
101
+ e.preventDefault();
102
+ toggleMenu();
103
+ }
104
+ }}
105
+ aria-haspopup="menu"
106
+ aria-expanded={open}
107
+ aria-controls={menuId}
108
+ class="bg-transparent border-none p-0 cursor-pointer"
109
+ >
110
+ {@render trigger?.()}
111
+ </button>
112
+
113
+ {#if open}
114
+ <div
115
+ bind:this={menuElement}
116
+ id={menuId}
117
+ role="menu"
118
+ tabindex="-1"
119
+ onkeydown={handleMenuKeyDown}
120
+ class="absolute {placementClasses[placement]} z-[var(--z-50)] glass-panel rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-up_0.2s_var(--ease-luxe)]"
121
+ >
122
+ {#each items as item, index}
123
+ <button
124
+ role="menuitem"
125
+ data-index={index}
126
+ tabindex={index === selectedIndex ? 0 : -1}
127
+ onclick={() => handleItemClick(item)}
128
+ disabled={item.disabled}
129
+ class="w-full text-left px-4 py-2 text-sm text-text hover:bg-base-3 transition-colors duration-200 flex items-center gap-2 {item.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
130
+ >
131
+ {#if item.icon}
132
+ {@render item.icon()}
133
+ {/if}
134
+ {item.label}
135
+ </button>
136
+ {/each}
137
+ </div>
138
+ {/if}
139
+ </div>
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface MenuItem {
3
+ id: string;
4
+ label: string;
5
+ onclick?: () => void;
6
+ disabled?: boolean;
7
+ icon?: Snippet;
8
+ }
9
+ interface Props {
10
+ items: MenuItem[];
11
+ open?: boolean;
12
+ placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
13
+ trigger?: Snippet;
14
+ }
15
+ declare const DropdownMenu: import("svelte").Component<Props, {}, "open">;
16
+ type DropdownMenu = ReturnType<typeof DropdownMenu>;
17
+ export default DropdownMenu;
@@ -0,0 +1,72 @@
1
+ <script lang="ts">let { items, children } = $props();
2
+ let selectedIndex = $state(0);
3
+ let menuContainer;
4
+ function handleItemClick(item) {
5
+ if (!item.disabled && item.onclick) {
6
+ item.onclick();
7
+ }
8
+ }
9
+ function handleMenuKeyDown(event) {
10
+ if (!items)
11
+ return;
12
+ const enabledItems = items.filter(item => !item.disabled);
13
+ const currentEnabledIndex = enabledItems.findIndex(item => item.id === items[selectedIndex]?.id);
14
+ let nextIndex = currentEnabledIndex;
15
+ switch (event.key) {
16
+ case 'ArrowDown':
17
+ event.preventDefault();
18
+ nextIndex = currentEnabledIndex < enabledItems.length - 1 ? currentEnabledIndex + 1 : 0;
19
+ selectedIndex = items.findIndex(item => item.id === enabledItems[nextIndex]?.id);
20
+ break;
21
+ case 'ArrowUp':
22
+ event.preventDefault();
23
+ nextIndex = currentEnabledIndex > 0 ? currentEnabledIndex - 1 : enabledItems.length - 1;
24
+ selectedIndex = items.findIndex(item => item.id === enabledItems[nextIndex]?.id);
25
+ break;
26
+ case 'Home':
27
+ event.preventDefault();
28
+ selectedIndex = items.findIndex(item => item.id === enabledItems[0]?.id);
29
+ break;
30
+ case 'End':
31
+ event.preventDefault();
32
+ selectedIndex = items.findIndex(item => item.id === enabledItems[enabledItems.length - 1]?.id);
33
+ break;
34
+ case 'Enter':
35
+ case ' ':
36
+ event.preventDefault();
37
+ if (items[selectedIndex] && !items[selectedIndex].disabled) {
38
+ handleItemClick(items[selectedIndex]);
39
+ }
40
+ break;
41
+ }
42
+ }
43
+ $effect(() => {
44
+ if (items && selectedIndex >= 0 && menuContainer) {
45
+ const selectedButton = menuContainer.querySelector(`[role="menuitem"][tabindex="0"]`);
46
+ selectedButton?.focus();
47
+ }
48
+ });
49
+ export {};
50
+ </script>
51
+
52
+ <div bind:this={menuContainer} role="menu" tabindex="-1" onkeydown={handleMenuKeyDown} class="glass-panel rounded-[var(--radius-lg)] p-3 shadow-[var(--shadow-deep)]">
53
+ {#if children}
54
+ {@render children()}
55
+ {:else if items}
56
+ {#each items as item, index}
57
+ <button
58
+ role="menuitem"
59
+ aria-disabled={item.disabled}
60
+ tabindex={index === selectedIndex ? 0 : -1}
61
+ onclick={() => handleItemClick(item)}
62
+ disabled={item.disabled}
63
+ class="flex items-center gap-2 w-full px-4 py-3 rounded-[var(--radius-md)] text-[var(--color-text)] text-sm font-[var(--font-body)] hover:bg-[var(--color-base-3)] hover-lift transition-all duration-200 ease-[var(--ease-luxe)] {item.disabled ? 'opacity-50 pointer-events-none' : 'cursor-pointer'}"
64
+ >
65
+ {#if item.icon}
66
+ {@render item.icon()}
67
+ {/if}
68
+ <span>{item.label}</span>
69
+ </button>
70
+ {/each}
71
+ {/if}
72
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface MenuItem {
3
+ id: string;
4
+ label: string;
5
+ icon?: Snippet;
6
+ disabled?: boolean;
7
+ onclick?: () => void;
8
+ }
9
+ interface Props {
10
+ items?: MenuItem[];
11
+ children?: Snippet;
12
+ }
13
+ declare const Menu: import("svelte").Component<Props, {}, "">;
14
+ type Menu = ReturnType<typeof Menu>;
15
+ export default Menu;
@@ -0,0 +1,111 @@
1
+ <script lang="ts">let { position = 'sticky', height = 'md', navigationAlign = 'center', toggleButton, logo, navigation, actions, children, currentPath } = $props();
2
+ const positionClasses = {
3
+ sticky: 'sticky top-0',
4
+ fixed: 'fixed top-0 left-0 right-0'
5
+ };
6
+ const heightClasses = {
7
+ sm: 'h-16',
8
+ md: 'h-18',
9
+ lg: 'h-24'
10
+ };
11
+ const baseClasses = 'z-[var(--z-50)] glass-panel flex items-center justify-between px-4 md:px-6 lg:px-8 transition-all duration-300 ease-[var(--ease-luxe)]';
12
+ // Track elements modified by the effect to preserve manual .active classes
13
+ let autoManagedElements = $state(new Set());
14
+ let rootElement = $state(null);
15
+ $effect(() => {
16
+ if (typeof document === 'undefined')
17
+ return;
18
+ if (!rootElement)
19
+ return;
20
+ // Clear auto-managed active classes when currentPath becomes falsy
21
+ if (!currentPath) {
22
+ autoManagedElements.forEach((element) => {
23
+ element.classList.remove('active');
24
+ });
25
+ autoManagedElements.clear();
26
+ return;
27
+ }
28
+ const elements = rootElement.querySelectorAll('a, button');
29
+ elements.forEach((element) => {
30
+ const href = element.getAttribute('href');
31
+ const dataHref = element.getAttribute('data-href');
32
+ const targetPath = href || dataHref;
33
+ // Match exact path or nested routes (e.g., /buttons matches /buttons/icon-button)
34
+ const isActive = targetPath === currentPath ||
35
+ (targetPath && currentPath.startsWith(targetPath + '/'));
36
+ if (isActive) {
37
+ // Only add to autoManagedElements if we're adding the class ourselves
38
+ if (!element.classList.contains('active')) {
39
+ element.classList.add('active');
40
+ autoManagedElements.add(element);
41
+ }
42
+ }
43
+ else if (autoManagedElements.has(element)) {
44
+ element.classList.remove('active');
45
+ autoManagedElements.delete(element);
46
+ }
47
+ });
48
+ });
49
+ export {};
50
+ </script>
51
+
52
+ <nav bind:this={rootElement} data-navbar class="{baseClasses} {positionClasses[position]} {heightClasses[height]}">
53
+ <!-- Left Section: Toggle Button and Logo -->
54
+ <div class="flex items-center gap-3">
55
+ {#if toggleButton}
56
+ {@render toggleButton()}
57
+ {/if}
58
+ {#if logo}
59
+ {@render logo()}
60
+ {/if}
61
+ </div>
62
+
63
+ <!-- Center Section: Navigation Links -->
64
+ {#if navigation}
65
+ <div class="flex-1 flex items-center {navigationAlign === 'center' ? 'justify-center' : ''} gap-6 {logo ? 'ml-4' : ''}">
66
+ {@render navigation()}
67
+ </div>
68
+ {:else if children}
69
+ <div class="flex-1 flex items-center {navigationAlign === 'center' ? 'justify-center' : ''} gap-4 {logo ? 'ml-4' : ''}">
70
+ {@render children()}
71
+ </div>
72
+ {/if}
73
+
74
+ <!-- Right Section: Actions -->
75
+ {#if actions}
76
+ <div class="flex items-center gap-3">
77
+ {@render actions()}
78
+ </div>
79
+ {/if}
80
+ </nav>
81
+
82
+ <style>
83
+ nav :global(.navbar-logo) {
84
+ color: var(--color-accent);
85
+ font-weight: 600;
86
+ font-size: var(--text-lg);
87
+ }
88
+
89
+ nav :global(a.active),
90
+ nav :global(button.active) {
91
+ color: var(--color-accent);
92
+ font-weight: 600;
93
+ position: relative;
94
+ }
95
+
96
+ nav :global(a.active::after),
97
+ nav :global(button.active::after) {
98
+ content: '';
99
+ position: absolute;
100
+ bottom: -0.5rem;
101
+ left: 0;
102
+ right: 0;
103
+ height: 2px;
104
+ background: var(--color-accent);
105
+ box-shadow: var(--shadow-accent-glow);
106
+ }
107
+
108
+ nav :global(a:not(.active):hover),
109
+ nav :global(button:not(.active):hover) {
110
+ color: var(--color-accent-soft);
111
+ }</style>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ position?: 'sticky' | 'fixed';
4
+ height?: 'sm' | 'md' | 'lg';
5
+ navigationAlign?: 'left' | 'center';
6
+ toggleButton?: Snippet;
7
+ logo?: Snippet;
8
+ navigation?: Snippet;
9
+ actions?: Snippet;
10
+ children?: Snippet;
11
+ currentPath?: string;
12
+ }
13
+ declare const Navbar: import("svelte").Component<Props, {}, "">;
14
+ type Navbar = ReturnType<typeof Navbar>;
15
+ export default Navbar;
@@ -0,0 +1,236 @@
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>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ open?: boolean;
4
+ side?: 'left' | 'right';
5
+ width?: 'sm' | 'md' | 'lg';
6
+ header?: Snippet;
7
+ children?: Snippet;
8
+ currentPath?: string;
9
+ }
10
+ declare const Sidebar: import("svelte").Component<Props, {}, "open">;
11
+ type Sidebar = ReturnType<typeof Sidebar>;
12
+ export default Sidebar;