@mrintel/villain-ui 0.3.0 → 0.7.1

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 (140) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3490 -1296
  3. package/dist/components/buttons/Button.svelte +27 -33
  4. package/dist/components/buttons/Button.svelte.d.ts +4 -1
  5. package/dist/components/buttons/ButtonGroup.svelte +17 -30
  6. package/dist/components/buttons/FloatingActionButton.svelte +20 -44
  7. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
  8. package/dist/components/buttons/IconButton.svelte +23 -53
  9. package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
  10. package/dist/components/buttons/LinkButton.svelte +24 -37
  11. package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
  12. package/dist/components/buttons/buttonClasses.d.ts +5 -0
  13. package/dist/components/buttons/buttonClasses.js +8 -3
  14. package/dist/components/cards/Card.svelte +54 -46
  15. package/dist/components/cards/Card.svelte.d.ts +9 -2
  16. package/dist/components/cards/Container.svelte +17 -33
  17. package/dist/components/cards/Divider.svelte +36 -52
  18. package/dist/components/cards/Divider.svelte.d.ts +2 -0
  19. package/dist/components/cards/Grid.svelte +55 -44
  20. package/dist/components/cards/Panel.svelte +18 -32
  21. package/dist/components/cards/Panel.svelte.d.ts +2 -1
  22. package/dist/components/cards/SectionHeader.svelte +24 -38
  23. package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
  24. package/dist/components/data/Avatar.svelte +48 -67
  25. package/dist/components/data/Badge.svelte +45 -32
  26. package/dist/components/data/Badge.svelte.d.ts +7 -1
  27. package/dist/components/data/CalendarGrid.svelte +433 -0
  28. package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
  29. package/dist/components/data/CalendarGrid.types.d.ts +7 -0
  30. package/dist/components/data/CalendarGrid.types.js +1 -0
  31. package/dist/components/data/CodeBlock.svelte +119 -121
  32. package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
  33. package/dist/components/data/List.svelte +87 -64
  34. package/dist/components/data/List.svelte.d.ts +7 -0
  35. package/dist/components/data/Pagination.svelte +121 -123
  36. package/dist/components/data/Pagination.svelte.d.ts +5 -0
  37. package/dist/components/data/Sparkline.svelte +117 -0
  38. package/dist/components/data/Sparkline.svelte.d.ts +43 -0
  39. package/dist/components/data/Stat.svelte +92 -103
  40. package/dist/components/data/Table.svelte +443 -76
  41. package/dist/components/data/Table.svelte.d.ts +23 -2
  42. package/dist/components/data/Table.types.d.ts +14 -0
  43. package/dist/components/data/Table.types.js +1 -0
  44. package/dist/components/data/Tag.svelte +51 -53
  45. package/dist/components/data/Tag.svelte.d.ts +5 -1
  46. package/dist/components/data/index.d.ts +4 -0
  47. package/dist/components/data/index.js +2 -0
  48. package/dist/components/forms/Checkbox.svelte +39 -51
  49. package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
  50. package/dist/components/forms/DatePicker.svelte +61 -0
  51. package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
  52. package/dist/components/forms/DateTimePicker.svelte +63 -0
  53. package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
  54. package/dist/components/forms/FileUpload.svelte +136 -164
  55. package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
  56. package/dist/components/forms/Input.svelte +284 -57
  57. package/dist/components/forms/Input.svelte.d.ts +10 -3
  58. package/dist/components/forms/InputGroup.svelte +7 -7
  59. package/dist/components/forms/RadioGroup.svelte +77 -87
  60. package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
  61. package/dist/components/forms/RangeSlider.svelte +90 -116
  62. package/dist/components/forms/Select.svelte +106 -71
  63. package/dist/components/forms/Select.svelte.d.ts +3 -1
  64. package/dist/components/forms/Step.svelte +25 -0
  65. package/dist/components/forms/Step.svelte.d.ts +12 -0
  66. package/dist/components/forms/StepContext.d.ts +3 -0
  67. package/dist/components/forms/StepContext.js +5 -0
  68. package/dist/components/forms/Stepper.types.d.ts +37 -0
  69. package/dist/components/forms/Stepper.types.js +1 -0
  70. package/dist/components/forms/StepperForm.svelte +183 -0
  71. package/dist/components/forms/StepperForm.svelte.d.ts +17 -0
  72. package/dist/components/forms/Switch.svelte +44 -56
  73. package/dist/components/forms/Switch.svelte.d.ts +3 -1
  74. package/dist/components/forms/Textarea.svelte +52 -57
  75. package/dist/components/forms/Textarea.svelte.d.ts +3 -1
  76. package/dist/components/forms/TimePicker.svelte +63 -0
  77. package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
  78. package/dist/components/forms/formClasses.d.ts +3 -0
  79. package/dist/components/forms/formClasses.js +3 -0
  80. package/dist/components/forms/index.d.ts +6 -0
  81. package/dist/components/forms/index.js +5 -0
  82. package/dist/components/navigation/Breadcrumbs.svelte +56 -59
  83. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
  84. package/dist/components/navigation/ContextMenu.svelte +133 -83
  85. package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
  86. package/dist/components/navigation/DropdownMenu.svelte +139 -80
  87. package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
  88. package/dist/components/navigation/Menu.svelte +72 -48
  89. package/dist/components/navigation/Navbar.svelte +111 -32
  90. package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
  91. package/dist/components/navigation/Sidebar.svelte +236 -35
  92. package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
  93. package/dist/components/navigation/Stepper.svelte +204 -0
  94. package/dist/components/navigation/Stepper.svelte.d.ts +34 -0
  95. package/dist/components/navigation/Tabs.svelte +86 -54
  96. package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
  97. package/dist/components/navigation/index.d.ts +1 -0
  98. package/dist/components/navigation/index.js +1 -0
  99. package/dist/components/overlays/Alert.svelte +81 -99
  100. package/dist/components/overlays/Alert.svelte.d.ts +5 -1
  101. package/dist/components/overlays/CommandPalette.svelte +182 -217
  102. package/dist/components/overlays/Drawer.svelte +158 -167
  103. package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
  104. package/dist/components/overlays/Dropdown.svelte +62 -30
  105. package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
  106. package/dist/components/overlays/Modal.svelte +125 -130
  107. package/dist/components/overlays/Modal.svelte.d.ts +3 -1
  108. package/dist/components/overlays/Popover.svelte +106 -131
  109. package/dist/components/overlays/ProgressBar.svelte +29 -45
  110. package/dist/components/overlays/SkeletonLoader.svelte +66 -82
  111. package/dist/components/overlays/Spinner.svelte +33 -43
  112. package/dist/components/overlays/Toast.svelte +111 -140
  113. package/dist/components/overlays/Toast.svelte.d.ts +3 -0
  114. package/dist/components/overlays/Tooltip.svelte +94 -115
  115. package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
  116. package/dist/components/typography/Code.svelte +10 -14
  117. package/dist/components/typography/Heading.svelte +15 -22
  118. package/dist/components/typography/Heading.svelte.d.ts +1 -0
  119. package/dist/components/typography/Text.svelte +21 -24
  120. package/dist/components/typography/Text.svelte.d.ts +2 -1
  121. package/dist/components/utilities/Accordion.svelte +54 -67
  122. package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
  123. package/dist/components/utilities/Carousel.svelte +124 -152
  124. package/dist/components/utilities/Collapse.svelte +46 -60
  125. package/dist/components/utilities/Hero.svelte +42 -0
  126. package/dist/components/utilities/Hero.svelte.d.ts +10 -0
  127. package/dist/components/utilities/Portal.svelte +47 -72
  128. package/dist/components/utilities/ScrollArea.svelte +33 -41
  129. package/dist/components/utilities/SystemConsole.svelte +310 -0
  130. package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
  131. package/dist/components/utilities/SystemInterface.svelte +726 -0
  132. package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
  133. package/dist/components/utilities/index.d.ts +4 -0
  134. package/dist/components/utilities/index.js +3 -0
  135. package/dist/components/utilities/utilities.types.d.ts +46 -0
  136. package/dist/components/utilities/utilities.types.js +4 -0
  137. package/dist/index.d.ts +57 -5
  138. package/dist/index.js +5 -5
  139. package/dist/theme.css +2889 -218
  140. package/package.json +83 -76
@@ -1,32 +1,111 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
-
4
- interface Props {
5
- position?: 'sticky' | 'fixed';
6
- height?: 'sm' | 'md' | 'lg';
7
- children?: Snippet;
8
- }
9
-
10
- let {
11
- position = 'sticky',
12
- height = 'md',
13
- children
14
- }: Props = $props();
15
-
16
- const positionClasses = {
17
- sticky: 'sticky top-0',
18
- fixed: 'fixed top-0 left-0 right-0'
19
- };
20
-
21
- const heightClasses = {
22
- sm: 'h-14',
23
- md: 'h-16',
24
- lg: 'h-20'
25
- };
26
-
27
- const baseClasses = 'z-40 glass-panel flex items-center justify-between px-4 md:px-6 lg:px-8 transition-all duration-300 ease-[var(--ease-luxe)]';
28
- </script>
29
-
30
- <nav class="{baseClasses} {positionClasses[position]} {heightClasses[height]}">
31
- {@render children?.()}
32
- </nav>
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>
@@ -2,7 +2,13 @@ import type { Snippet } from 'svelte';
2
2
  interface Props {
3
3
  position?: 'sticky' | 'fixed';
4
4
  height?: 'sm' | 'md' | 'lg';
5
+ navigationAlign?: 'left' | 'center';
6
+ toggleButton?: Snippet;
7
+ logo?: Snippet;
8
+ navigation?: Snippet;
9
+ actions?: Snippet;
5
10
  children?: Snippet;
11
+ currentPath?: string;
6
12
  }
7
13
  declare const Navbar: import("svelte").Component<Props, {}, "">;
8
14
  type Navbar = ReturnType<typeof Navbar>;
@@ -1,35 +1,236 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
-
4
- interface Props {
5
- open?: boolean;
6
- side?: 'left' | 'right';
7
- width?: 'sm' | 'md' | 'lg';
8
- children?: Snippet;
9
- }
10
-
11
- let {
12
- open = $bindable(true),
13
- side = 'left',
14
- width = 'md',
15
- children
16
- }: Props = $props();
17
-
18
- const widthClasses = {
19
- sm: { open: 'w-56', collapsed: 'w-14' },
20
- md: { open: 'w-64', collapsed: 'w-16' },
21
- lg: { open: 'w-80', collapsed: 'w-20' }
22
- };
23
-
24
- const sideClasses = {
25
- left: 'left-0',
26
- right: 'right-0'
27
- };
28
-
29
- const currentWidth = $derived(open ? widthClasses[width].open : widthClasses[width].collapsed);
30
- const baseClasses = 'fixed top-0 bottom-0 z-30 glass-panel overflow-y-auto overflow-x-hidden transition-all duration-300 ease-[var(--ease-luxe)]';
31
- </script>
32
-
33
- <aside class="{baseClasses} {sideClasses[side]} {currentWidth}">
34
- {@render children?.()}
35
- </aside>
1
+ <script lang="ts">let { open = $bindable(true), side = 'left', width = 'md', header, children, currentPath } = $props();
2
+ let navbarHeight = $state(0);
3
+ $effect(() => {
4
+ if (typeof document === 'undefined')
5
+ return;
6
+ const navbarElement = document.querySelector('[data-navbar]');
7
+ if (navbarElement) {
8
+ // Initial height
9
+ navbarHeight = navbarElement.offsetHeight;
10
+ // Watch for height changes (responsive behavior, window resize, etc.)
11
+ if (typeof ResizeObserver !== 'undefined') {
12
+ const resizeObserver = new ResizeObserver(() => {
13
+ navbarHeight = navbarElement.offsetHeight;
14
+ });
15
+ resizeObserver.observe(navbarElement);
16
+ return () => {
17
+ resizeObserver.disconnect();
18
+ };
19
+ }
20
+ }
21
+ else {
22
+ navbarHeight = 0;
23
+ }
24
+ });
25
+ // Track elements modified by the effect to preserve manual .active classes
26
+ let autoManagedElements = $state(new Set());
27
+ let rootElement = $state(null);
28
+ $effect(() => {
29
+ if (typeof document === 'undefined')
30
+ return;
31
+ if (!rootElement)
32
+ return;
33
+ // Clear auto-managed active classes when currentPath becomes falsy
34
+ if (!currentPath) {
35
+ autoManagedElements.forEach((element) => {
36
+ element.classList.remove('active');
37
+ });
38
+ autoManagedElements.clear();
39
+ return;
40
+ }
41
+ const elements = rootElement.querySelectorAll('a, button');
42
+ elements.forEach((element) => {
43
+ const href = element.getAttribute('href');
44
+ const dataHref = element.getAttribute('data-href');
45
+ const targetPath = href || dataHref;
46
+ // Match exact path or nested routes (e.g., /buttons matches /buttons/icon-button)
47
+ const isActive = targetPath === currentPath ||
48
+ (targetPath && currentPath.startsWith(targetPath + '/'));
49
+ if (isActive) {
50
+ // Only add to autoManagedElements if we're adding the class ourselves
51
+ if (!element.classList.contains('active')) {
52
+ element.classList.add('active');
53
+ autoManagedElements.add(element);
54
+ }
55
+ }
56
+ else if (autoManagedElements.has(element)) {
57
+ element.classList.remove('active');
58
+ autoManagedElements.delete(element);
59
+ }
60
+ });
61
+ });
62
+ // Collapsed state icon/text detection
63
+ $effect(() => {
64
+ if (typeof document === 'undefined')
65
+ return;
66
+ if (!rootElement)
67
+ return;
68
+ // Reference open to make effect reactive to collapse/expand state changes
69
+ // This ensures detection runs when sidebar toggles
70
+ if (open === undefined)
71
+ return;
72
+ const elements = rootElement.querySelectorAll('a, button');
73
+ elements.forEach((element) => {
74
+ // Check for icon presence using documented .sidebar-item-icon class
75
+ const hasIcon = element.querySelector('.sidebar-item-icon');
76
+ if (hasIcon) {
77
+ element.setAttribute('data-sidebar-has-icon', 'true');
78
+ element.removeAttribute('data-sidebar-first-letter');
79
+ }
80
+ else {
81
+ // Extract first letter from .sidebar-item-label or fallback to text content
82
+ const label = element.querySelector('.sidebar-item-label');
83
+ const textContent = (label?.textContent || element.textContent)?.trim();
84
+ const firstChar = textContent?.[0];
85
+ // Only set first-letter if it's alphanumeric
86
+ if (firstChar && /[a-zA-Z0-9]/.test(firstChar)) {
87
+ element.setAttribute('data-sidebar-first-letter', firstChar.toUpperCase());
88
+ element.removeAttribute('data-sidebar-has-icon');
89
+ }
90
+ else {
91
+ // No valid first letter - don't show anything in collapsed mode
92
+ element.removeAttribute('data-sidebar-first-letter');
93
+ element.removeAttribute('data-sidebar-has-icon');
94
+ }
95
+ }
96
+ });
97
+ });
98
+ const widthClasses = {
99
+ sm: { open: 'w-56', collapsed: 'w-14' },
100
+ md: { open: 'w-64', collapsed: 'w-16' },
101
+ lg: { open: 'w-80', collapsed: 'w-20' }
102
+ };
103
+ const sideClasses = {
104
+ left: 'left-0',
105
+ right: 'right-0'
106
+ };
107
+ const currentWidth = $derived(open ? widthClasses[width].open : widthClasses[width].collapsed);
108
+ const baseClasses = 'fixed bottom-0 z-[var(--z-40)] glass-panel overflow-y-auto overflow-x-hidden transition-all duration-300 ease-[var(--ease-luxe)]';
109
+ export {};
110
+ </script>
111
+
112
+ <aside bind:this={rootElement} data-sidebar class="{baseClasses} {sideClasses[side]} {currentWidth}" style="top: {navbarHeight}px">
113
+ {#if header && open}
114
+ {@render header()}
115
+ {/if}
116
+ {@render children?.()}
117
+ </aside>
118
+
119
+ <style>
120
+ /* Header styling */
121
+ aside :global(.sidebar-header) {
122
+ padding: 1rem;
123
+ font-weight: 600;
124
+ color: var(--color-accent);
125
+ font-size: var(--text-sm);
126
+ text-transform: uppercase;
127
+ letter-spacing: 0.05em;
128
+ transition: all 0.2s var(--ease-luxe);
129
+ }
130
+
131
+ /* Collapsed state - center icons and hide text */
132
+ aside[class*="w-14"] :global(a[data-sidebar-has-icon]),
133
+ aside[class*="w-16"] :global(a[data-sidebar-has-icon]),
134
+ aside[class*="w-20"] :global(a[data-sidebar-has-icon]),
135
+ aside[class*="w-14"] :global(button[data-sidebar-has-icon]),
136
+ aside[class*="w-16"] :global(button[data-sidebar-has-icon]),
137
+ aside[class*="w-20"] :global(button[data-sidebar-has-icon]) {
138
+ justify-content: center;
139
+ }
140
+
141
+ aside[class*="w-14"] :global(a[data-sidebar-has-icon] .sidebar-item-label),
142
+ aside[class*="w-16"] :global(a[data-sidebar-has-icon] .sidebar-item-label),
143
+ aside[class*="w-20"] :global(a[data-sidebar-has-icon] .sidebar-item-label),
144
+ aside[class*="w-14"] :global(button[data-sidebar-has-icon] .sidebar-item-label),
145
+ aside[class*="w-16"] :global(button[data-sidebar-has-icon] .sidebar-item-label),
146
+ aside[class*="w-20"] :global(button[data-sidebar-has-icon] .sidebar-item-label) {
147
+ display: none;
148
+ }
149
+
150
+ aside[class*="w-14"] :global(a[data-sidebar-has-icon] .sidebar-item-icon),
151
+ aside[class*="w-16"] :global(a[data-sidebar-has-icon] .sidebar-item-icon),
152
+ aside[class*="w-20"] :global(a[data-sidebar-has-icon] .sidebar-item-icon),
153
+ aside[class*="w-14"] :global(button[data-sidebar-has-icon] .sidebar-item-icon),
154
+ aside[class*="w-16"] :global(button[data-sidebar-has-icon] .sidebar-item-icon),
155
+ aside[class*="w-20"] :global(button[data-sidebar-has-icon] .sidebar-item-icon) {
156
+ font-size: var(--text-2xl);
157
+ width: 1.5rem;
158
+ height: 1.5rem;
159
+ }
160
+
161
+ /* Collapsed state - first letter circle for items without icons */
162
+ aside[class*="w-14"] :global(a:not([data-sidebar-has-icon]))::before,
163
+ aside[class*="w-16"] :global(a:not([data-sidebar-has-icon]))::before,
164
+ aside[class*="w-20"] :global(a:not([data-sidebar-has-icon]))::before,
165
+ aside[class*="w-14"] :global(button:not([data-sidebar-has-icon]))::before,
166
+ aside[class*="w-16"] :global(button:not([data-sidebar-has-icon]))::before,
167
+ aside[class*="w-20"] :global(button:not([data-sidebar-has-icon]))::before {
168
+ content: attr(data-sidebar-first-letter);
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ width: 2.5rem;
173
+ height: 2.5rem;
174
+ border-radius: var(--radius-pill);
175
+ background: var(--color-accent-overlay-20);
176
+ color: var(--color-accent);
177
+ font-weight: 600;
178
+ font-size: var(--text-lg);
179
+ }
180
+
181
+ aside[class*="w-14"] :global(a:not([data-sidebar-has-icon])),
182
+ aside[class*="w-16"] :global(a:not([data-sidebar-has-icon])),
183
+ aside[class*="w-20"] :global(a:not([data-sidebar-has-icon])),
184
+ aside[class*="w-14"] :global(button:not([data-sidebar-has-icon])),
185
+ aside[class*="w-16"] :global(button:not([data-sidebar-has-icon])),
186
+ aside[class*="w-20"] :global(button:not([data-sidebar-has-icon])) {
187
+ justify-content: center;
188
+ }
189
+
190
+ aside[class*="w-14"] :global(a:not([data-sidebar-has-icon]) .sidebar-item-label),
191
+ aside[class*="w-16"] :global(a:not([data-sidebar-has-icon]) .sidebar-item-label),
192
+ aside[class*="w-20"] :global(a:not([data-sidebar-has-icon]) .sidebar-item-label),
193
+ aside[class*="w-14"] :global(button:not([data-sidebar-has-icon]) .sidebar-item-label),
194
+ aside[class*="w-16"] :global(button:not([data-sidebar-has-icon]) .sidebar-item-label),
195
+ aside[class*="w-20"] :global(button:not([data-sidebar-has-icon]) .sidebar-item-label) {
196
+ display: none;
197
+ }
198
+
199
+ aside :global(a.active),
200
+ aside :global(button.active) {
201
+ background: var(--color-accent-overlay-15);
202
+ color: var(--color-accent);
203
+ border-left: 3px solid var(--color-accent);
204
+ font-weight: 600;
205
+ }
206
+
207
+ aside :global(a.active),
208
+ aside :global(button.active) {
209
+ box-shadow: var(--shadow-accent-glow);
210
+ }
211
+
212
+ aside :global(a:not(.active):hover),
213
+ aside :global(button:not(.active):hover) {
214
+ background: var(--color-base-3);
215
+ color: var(--color-accent-soft);
216
+ }
217
+
218
+ aside :global(a),
219
+ aside :global(button) {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 0.75rem;
223
+ padding: 0.75rem 1rem;
224
+ margin: 0.25rem 0.5rem;
225
+ border-radius: var(--radius-md);
226
+ color: var(--color-text-soft);
227
+ text-decoration: none;
228
+ transition: all 0.2s var(--ease-luxe);
229
+ cursor: pointer;
230
+ border: none;
231
+ background: transparent;
232
+ width: calc(100% - 1rem);
233
+ text-align: left;
234
+ font-family: var(--font-body);
235
+ font-size: 0.875rem;
236
+ }</style>
@@ -3,7 +3,9 @@ interface Props {
3
3
  open?: boolean;
4
4
  side?: 'left' | 'right';
5
5
  width?: 'sm' | 'md' | 'lg';
6
+ header?: Snippet;
6
7
  children?: Snippet;
8
+ currentPath?: string;
7
9
  }
8
10
  declare const Sidebar: import("svelte").Component<Props, {}, "open">;
9
11
  type Sidebar = ReturnType<typeof Sidebar>;
@@ -0,0 +1,204 @@
1
+ <script lang="ts">let { steps, current = 0, states, variant = 'default', orientation = 'horizontal', showNumbers = true, showProgress = true, clickable = false, onStepClick, class: className = '' } = $props();
2
+ /* ---------------------------------------------
3
+ * Normalize steps (stable IDs, StepForm friendly)
4
+ * --------------------------------------------- */
5
+ const normalizedSteps = $derived(steps.map((step, _) => typeof step === 'string'
6
+ ? {
7
+ id: step.toLowerCase().replace(/\s+/g, '-'),
8
+ label: step
9
+ }
10
+ : step));
11
+ /* ---------------------------------------------
12
+ * Resolve step states
13
+ * Priority:
14
+ * 1. explicit `states` prop
15
+ * 2. per-step `state`
16
+ * 3. derive from `current`
17
+ * --------------------------------------------- */
18
+ const derivedStates = $derived(states && states.length === normalizedSteps.length
19
+ ? states
20
+ : normalizedSteps.map((step, i) => {
21
+ if (step.state)
22
+ return step.state;
23
+ if (i < current)
24
+ return 'completed';
25
+ if (i === current)
26
+ return 'active';
27
+ return 'idle';
28
+ }));
29
+ /* ---------------------------------------------
30
+ * Progress bar percentage
31
+ * --------------------------------------------- */
32
+ const progressPercent = $derived(normalizedSteps.length > 1
33
+ ? ((current + 1) / normalizedSteps.length) * 100
34
+ : 100);
35
+ /* ---------------------------------------------
36
+ * Interaction handlers
37
+ * --------------------------------------------- */
38
+ function handleStepClick(index) {
39
+ if (!clickable)
40
+ return;
41
+ const state = derivedStates[index];
42
+ if (state !== 'completed')
43
+ return;
44
+ onStepClick?.(index);
45
+ }
46
+ function handleKeyDown(event, index) {
47
+ if (event.key === 'Enter' || event.key === ' ') {
48
+ event.preventDefault();
49
+ handleStepClick(index);
50
+ }
51
+ }
52
+ /* ---------------------------------------------
53
+ * Styling maps
54
+ * --------------------------------------------- */
55
+ const stateStyles = {
56
+ idle: {
57
+ circle: 'bg-[var(--color-base-2)] text-[var(--color-text-tertiary)]',
58
+ label: 'text-[var(--color-text-tertiary)]',
59
+ connector: 'bg-[var(--color-base-2)]'
60
+ },
61
+ active: {
62
+ circle: 'bg-[var(--color-accent-primary)] text-white shadow-[0_0_0_4px_rgba(139,92,246,0.3)]',
63
+ label: 'text-[var(--color-accent-primary)]',
64
+ connector: 'bg-[var(--color-base-2)]'
65
+ },
66
+ completed: {
67
+ circle: 'bg-[var(--color-accent-primary)] text-white',
68
+ label: 'text-[var(--color-text-secondary)]',
69
+ connector: 'bg-[var(--color-accent-primary)]'
70
+ },
71
+ error: {
72
+ circle: 'bg-[var(--color-error)] text-white',
73
+ label: 'text-[var(--color-error)]',
74
+ connector: 'bg-[var(--color-error)]'
75
+ },
76
+ disabled: {
77
+ circle: 'bg-[var(--color-base-1)] text-[var(--color-text-tertiary)] opacity-50',
78
+ label: 'text-[var(--color-text-tertiary)] opacity-50',
79
+ connector: 'bg-[var(--color-base-1)]'
80
+ }
81
+ };
82
+ const sizeConfig = {
83
+ default: { circle: 'w-8 h-8', text: 'text-sm', connector: 'h-0.5', gap: 'gap-3' },
84
+ compact: { circle: 'w-6 h-6', text: 'text-xs', connector: 'h-0.5', gap: 'gap-2' },
85
+ minimal: { circle: 'w-3 h-3', text: 'text-xs', connector: 'h-0.5', gap: 'gap-1' }
86
+ };
87
+ const sizes = $derived(sizeConfig[variant]);
88
+ export {};
89
+ </script>
90
+
91
+ <!-- ========================================================= -->
92
+ <!-- Root -->
93
+ <!-- ========================================================= -->
94
+ <div
95
+ class="stepper {className}"
96
+ class:stepper-horizontal={orientation === 'horizontal'}
97
+ class:stepper-vertical={orientation === 'vertical'}
98
+ role="group"
99
+ aria-label="Progress"
100
+ >
101
+ <!-- ========================================================= -->
102
+ <!-- Horizontal -->
103
+ <!-- ========================================================= -->
104
+ {#if orientation === 'horizontal'}
105
+ <div class="w-full">
106
+ {#if showProgress && variant !== 'minimal'}
107
+ <div class="relative mb-8">
108
+ <div class="h-2 rounded-full overflow-hidden bg-[var(--color-base-2)]">
109
+ <div
110
+ class="h-full transition-all duration-500 ease-out bg-[var(--color-accent-primary)]"
111
+ style="width: {progressPercent}%"
112
+ ></div>
113
+ </div>
114
+
115
+ <div class="absolute top-0 left-0 right-0 flex justify-between items-center -mt-3">
116
+ {#each normalizedSteps as step, i}
117
+ {@const state = derivedStates[i]}
118
+ {@const styles = stateStyles[state]}
119
+ <div class="flex flex-col items-center">
120
+ <button
121
+ type="button"
122
+ class="{sizes.circle} rounded-full flex items-center justify-center transition-all duration-300 {styles.circle}"
123
+ class:cursor-pointer={clickable && state === 'completed'}
124
+ disabled={state === 'disabled'}
125
+ onclick={() => handleStepClick(i)}
126
+ onkeydown={(e) => handleKeyDown(e, i)}
127
+ aria-current={state === 'active' ? 'step' : undefined}
128
+ >
129
+ {#if state === 'completed' && variant !== 'minimal'}
130
+
131
+ {:else if step.icon}
132
+ {@render step.icon()}
133
+ {:else if showNumbers && variant !== 'minimal'}
134
+ <span class="{sizes.text} font-bold">{i + 1}</span>
135
+ {/if}
136
+ </button>
137
+
138
+ {#if variant !== 'minimal'}
139
+ <span class="hidden md:block mt-2 {sizes.text} font-medium {styles.label}">
140
+ {step.label}
141
+ </span>
142
+ {/if}
143
+ </div>
144
+ {/each}
145
+ </div>
146
+ </div>
147
+ {/if}
148
+ </div>
149
+
150
+ <!-- ========================================================= -->
151
+ <!-- Vertical -->
152
+ <!-- ========================================================= -->
153
+ {:else}
154
+ <div class="flex flex-col {sizes.gap}">
155
+ {#each normalizedSteps as step, i}
156
+ {@const state = derivedStates[i]}
157
+ {@const styles = stateStyles[state]}
158
+ {@const isLast = i === normalizedSteps.length - 1}
159
+
160
+ <div class="flex">
161
+ <div class="flex flex-col items-center mr-4">
162
+ <button
163
+ type="button"
164
+ class="{sizes.circle} rounded-full flex items-center justify-center transition-all duration-300 {styles.circle}"
165
+ class:cursor-pointer={clickable && state === 'completed'}
166
+ disabled={state === 'disabled'}
167
+ onclick={() => handleStepClick(i)}
168
+ onkeydown={(e) => handleKeyDown(e, i)}
169
+ aria-current={state === 'active' ? 'step' : undefined}
170
+ >
171
+ {#if state === 'completed' && variant !== 'minimal'}✓{/if}
172
+ </button>
173
+
174
+ {#if !isLast}
175
+ <div
176
+ class="w-0.5 flex-1 min-h-8 my-2 rounded-full transition-colors duration-300"
177
+ class:bg-[var(--color-accent-primary)]={state === 'completed'}
178
+ class:bg-[var(--color-base-2)]={state !== 'completed'}
179
+ ></div>
180
+ {/if}
181
+ </div>
182
+
183
+ <div class="flex-1 pb-8">
184
+ <span class="{sizes.text} font-medium {styles.label}">
185
+ {step.label}
186
+ </span>
187
+ {#if step.description && variant === 'default'}
188
+ <p class="mt-1 text-xs text-[var(--color-text-tertiary)]">
189
+ {step.description}
190
+ </p>
191
+ {/if}
192
+ </div>
193
+ </div>
194
+ {/each}
195
+ </div>
196
+ {/if}
197
+ </div>
198
+
199
+ <style>
200
+ @media (prefers-reduced-motion: reduce) {
201
+ .stepper * {
202
+ transition-duration: 0.01ms !important;
203
+ }
204
+ }</style>
@@ -0,0 +1,34 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type StepState = 'idle' | 'active' | 'completed' | 'error' | 'disabled';
3
+ export interface StepConfig {
4
+ id: string;
5
+ label: string;
6
+ description?: string;
7
+ icon?: Snippet;
8
+ state?: StepState;
9
+ }
10
+ interface Props {
11
+ /** Array of step labels or full step configs */
12
+ steps: (string | StepConfig)[];
13
+ /** Current step index (0-based) */
14
+ current?: number;
15
+ /** Optional explicit states (must match steps length) */
16
+ states?: StepState[];
17
+ /** Visual variant */
18
+ variant?: 'default' | 'compact' | 'minimal';
19
+ /** Orientation */
20
+ orientation?: 'horizontal' | 'vertical';
21
+ /** Show step numbers */
22
+ showNumbers?: boolean;
23
+ /** Show progress bar (horizontal only) */
24
+ showProgress?: boolean;
25
+ /** Allow clicking on completed steps */
26
+ clickable?: boolean;
27
+ /** Callback when a step is clicked */
28
+ onStepClick?: (index: number) => void;
29
+ /** Additional CSS class */
30
+ class?: string;
31
+ }
32
+ declare const Stepper: import("svelte").Component<Props, {}, "">;
33
+ type Stepper = ReturnType<typeof Stepper>;
34
+ export default Stepper;