@aspect-ops/exon-ui 0.0.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 (106) hide show
  1. package/dist/components/Badge/Badge.svelte +60 -0
  2. package/dist/components/Badge/Badge.svelte.d.ts +9 -0
  3. package/dist/components/Badge/index.d.ts +2 -0
  4. package/dist/components/Badge/index.js +1 -0
  5. package/dist/components/BottomNav/BottomNav.svelte +128 -0
  6. package/dist/components/BottomNav/BottomNav.svelte.d.ts +8 -0
  7. package/dist/components/BottomNav/BottomNavItem.svelte +133 -0
  8. package/dist/components/BottomNav/BottomNavItem.svelte.d.ts +8 -0
  9. package/dist/components/Breadcrumbs/BreadcrumbItem.svelte +65 -0
  10. package/dist/components/Breadcrumbs/BreadcrumbItem.svelte.d.ts +7 -0
  11. package/dist/components/Breadcrumbs/BreadcrumbSeparator.svelte +27 -0
  12. package/dist/components/Breadcrumbs/BreadcrumbSeparator.svelte.d.ts +7 -0
  13. package/dist/components/Breadcrumbs/Breadcrumbs.svelte +114 -0
  14. package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +8 -0
  15. package/dist/components/Button/Button.svelte +209 -0
  16. package/dist/components/Button/Button.svelte.d.ts +14 -0
  17. package/dist/components/Button/index.d.ts +2 -0
  18. package/dist/components/Button/index.js +1 -0
  19. package/dist/components/Checkbox/Checkbox.svelte +166 -0
  20. package/dist/components/Checkbox/Checkbox.svelte.d.ts +16 -0
  21. package/dist/components/Checkbox/CheckboxGroup.svelte +55 -0
  22. package/dist/components/Checkbox/CheckboxGroup.svelte.d.ts +12 -0
  23. package/dist/components/Checkbox/index.d.ts +2 -0
  24. package/dist/components/Checkbox/index.js +2 -0
  25. package/dist/components/FormField/FormField.svelte +101 -0
  26. package/dist/components/FormField/FormField.svelte.d.ts +13 -0
  27. package/dist/components/FormField/index.d.ts +1 -0
  28. package/dist/components/FormField/index.js +1 -0
  29. package/dist/components/Icon/Icon.svelte +47 -0
  30. package/dist/components/Icon/Icon.svelte.d.ts +11 -0
  31. package/dist/components/Icon/index.d.ts +2 -0
  32. package/dist/components/Icon/index.js +1 -0
  33. package/dist/components/Link/Link.svelte +56 -0
  34. package/dist/components/Link/Link.svelte.d.ts +9 -0
  35. package/dist/components/Link/index.d.ts +2 -0
  36. package/dist/components/Link/index.js +1 -0
  37. package/dist/components/Menu/Menu.svelte +14 -0
  38. package/dist/components/Menu/Menu.svelte.d.ts +7 -0
  39. package/dist/components/Menu/MenuContent.svelte +71 -0
  40. package/dist/components/Menu/MenuContent.svelte.d.ts +10 -0
  41. package/dist/components/Menu/MenuItem.svelte +73 -0
  42. package/dist/components/Menu/MenuItem.svelte.d.ts +7 -0
  43. package/dist/components/Menu/MenuSeparator.svelte +19 -0
  44. package/dist/components/Menu/MenuSeparator.svelte.d.ts +6 -0
  45. package/dist/components/Menu/MenuSub.svelte +14 -0
  46. package/dist/components/Menu/MenuSub.svelte.d.ts +7 -0
  47. package/dist/components/Menu/MenuSubContent.svelte +60 -0
  48. package/dist/components/Menu/MenuSubContent.svelte.d.ts +7 -0
  49. package/dist/components/Menu/MenuSubTrigger.svelte +84 -0
  50. package/dist/components/Menu/MenuSubTrigger.svelte.d.ts +7 -0
  51. package/dist/components/Menu/MenuTrigger.svelte +32 -0
  52. package/dist/components/Menu/MenuTrigger.svelte.d.ts +7 -0
  53. package/dist/components/Navbar/NavItem.svelte +99 -0
  54. package/dist/components/Navbar/NavItem.svelte.d.ts +8 -0
  55. package/dist/components/Navbar/Navbar.svelte +243 -0
  56. package/dist/components/Navbar/Navbar.svelte.d.ts +10 -0
  57. package/dist/components/Radio/Radio.svelte +93 -0
  58. package/dist/components/Radio/Radio.svelte.d.ts +10 -0
  59. package/dist/components/Radio/RadioGroup.svelte +72 -0
  60. package/dist/components/Radio/RadioGroup.svelte.d.ts +14 -0
  61. package/dist/components/Radio/index.d.ts +2 -0
  62. package/dist/components/Radio/index.js +2 -0
  63. package/dist/components/Select/Select.svelte +251 -0
  64. package/dist/components/Select/Select.svelte.d.ts +17 -0
  65. package/dist/components/Select/index.d.ts +1 -0
  66. package/dist/components/Select/index.js +1 -0
  67. package/dist/components/Sidebar/Sidebar.svelte +48 -0
  68. package/dist/components/Sidebar/Sidebar.svelte.d.ts +7 -0
  69. package/dist/components/Sidebar/SidebarGroup.svelte +141 -0
  70. package/dist/components/Sidebar/SidebarGroup.svelte.d.ts +7 -0
  71. package/dist/components/Sidebar/SidebarItem.svelte +151 -0
  72. package/dist/components/Sidebar/SidebarItem.svelte.d.ts +8 -0
  73. package/dist/components/Switch/Switch.svelte +120 -0
  74. package/dist/components/Switch/Switch.svelte.d.ts +13 -0
  75. package/dist/components/Switch/index.d.ts +1 -0
  76. package/dist/components/Switch/index.js +1 -0
  77. package/dist/components/Tabs/TabContent.svelte +91 -0
  78. package/dist/components/Tabs/TabContent.svelte.d.ts +7 -0
  79. package/dist/components/Tabs/TabList.svelte +46 -0
  80. package/dist/components/Tabs/TabList.svelte.d.ts +7 -0
  81. package/dist/components/Tabs/TabTrigger.svelte +68 -0
  82. package/dist/components/Tabs/TabTrigger.svelte.d.ts +7 -0
  83. package/dist/components/Tabs/Tabs.svelte +69 -0
  84. package/dist/components/Tabs/Tabs.svelte.d.ts +7 -0
  85. package/dist/components/TextInput/TextInput.svelte +200 -0
  86. package/dist/components/TextInput/TextInput.svelte.d.ts +35 -0
  87. package/dist/components/TextInput/index.d.ts +1 -0
  88. package/dist/components/TextInput/index.js +1 -0
  89. package/dist/components/Textarea/Textarea.svelte +198 -0
  90. package/dist/components/Textarea/Textarea.svelte.d.ts +33 -0
  91. package/dist/components/Textarea/index.d.ts +1 -0
  92. package/dist/components/Textarea/index.js +1 -0
  93. package/dist/components/Typography/Typography.svelte +99 -0
  94. package/dist/components/Typography/Typography.svelte.d.ts +10 -0
  95. package/dist/components/Typography/index.d.ts +2 -0
  96. package/dist/components/Typography/index.js +1 -0
  97. package/dist/index.d.ts +37 -0
  98. package/dist/index.js +41 -0
  99. package/dist/styles/tokens.css +431 -0
  100. package/dist/test-setup.d.ts +1 -0
  101. package/dist/test-setup.js +1 -0
  102. package/dist/types/index.d.ts +129 -0
  103. package/dist/types/index.js +1 -0
  104. package/dist/types/navigation.d.ts +118 -0
  105. package/dist/types/navigation.js +1 -0
  106. package/package.json +130 -0
@@ -0,0 +1,243 @@
1
+ <script lang="ts">
2
+ import type { NavbarProps } from '../../types/index.js';
3
+
4
+ interface Props extends NavbarProps {
5
+ logo?: import('svelte').Snippet;
6
+ actions?: import('svelte').Snippet;
7
+ mobileMenu?: import('svelte').Snippet;
8
+ children?: import('svelte').Snippet;
9
+ }
10
+
11
+ let { class: className = '', logo, actions, mobileMenu, children }: Props = $props();
12
+
13
+ let mobileMenuOpen = $state(false);
14
+
15
+ function toggleMobileMenu() {
16
+ mobileMenuOpen = !mobileMenuOpen;
17
+ }
18
+
19
+ function closeMobileMenu() {
20
+ mobileMenuOpen = false;
21
+ }
22
+ </script>
23
+
24
+ <header class="navbar {className}">
25
+ <div class="navbar__container">
26
+ {#if logo}
27
+ <div class="navbar__logo">
28
+ {@render logo()}
29
+ </div>
30
+ {/if}
31
+
32
+ <nav class="navbar__nav" aria-label="Main navigation">
33
+ {#if children}
34
+ {@render children()}
35
+ {/if}
36
+ </nav>
37
+
38
+ {#if actions}
39
+ <div class="navbar__actions">
40
+ {@render actions()}
41
+ </div>
42
+ {/if}
43
+
44
+ <button
45
+ type="button"
46
+ class="navbar__hamburger"
47
+ onclick={toggleMobileMenu}
48
+ aria-expanded={mobileMenuOpen}
49
+ aria-controls="navbar-mobile-menu"
50
+ aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
51
+ >
52
+ <span class="navbar__hamburger-icon" class:navbar__hamburger-icon--open={mobileMenuOpen}>
53
+ <span></span>
54
+ <span></span>
55
+ <span></span>
56
+ </span>
57
+ </button>
58
+ </div>
59
+
60
+ {#if mobileMenuOpen}
61
+ <div
62
+ id="navbar-mobile-menu"
63
+ class="navbar__mobile-menu"
64
+ class:navbar__mobile-menu--open={mobileMenuOpen}
65
+ >
66
+ {#if mobileMenu}
67
+ {@render mobileMenu()}
68
+ {:else if children}
69
+ {@render children()}
70
+ {/if}
71
+ </div>
72
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
73
+ <div
74
+ class="navbar__overlay"
75
+ role="button"
76
+ tabindex="-1"
77
+ aria-label="Close menu"
78
+ onclick={closeMobileMenu}
79
+ ></div>
80
+ {/if}
81
+ </header>
82
+
83
+ <style>
84
+ .navbar {
85
+ position: sticky;
86
+ top: 0;
87
+ z-index: 40;
88
+ background: var(--color-bg, #ffffff);
89
+ border-bottom: 1px solid var(--color-border, #e5e7eb);
90
+ font-family: inherit;
91
+ }
92
+
93
+ .navbar__container {
94
+ display: flex;
95
+ align-items: center;
96
+ justify-content: space-between;
97
+ gap: var(--space-md, 1rem);
98
+ max-width: var(--max-width, 80rem);
99
+ margin: 0 auto;
100
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
101
+ }
102
+
103
+ .navbar__logo {
104
+ display: flex;
105
+ align-items: center;
106
+ flex-shrink: 0;
107
+ }
108
+
109
+ .navbar__nav {
110
+ display: none;
111
+ align-items: center;
112
+ gap: var(--space-sm, 0.5rem);
113
+ }
114
+
115
+ .navbar__actions {
116
+ display: none;
117
+ align-items: center;
118
+ gap: var(--space-sm, 0.5rem);
119
+ }
120
+
121
+ .navbar__hamburger {
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ /* Touch target minimum (F20) */
126
+ min-width: var(--touch-target-min, 44px);
127
+ min-height: var(--touch-target-min, 44px);
128
+ padding: var(--space-sm, 0.5rem);
129
+ background: transparent;
130
+ border: none;
131
+ cursor: pointer;
132
+ -webkit-tap-highlight-color: transparent;
133
+ }
134
+
135
+ .navbar__hamburger:focus-visible {
136
+ outline: 2px solid var(--color-primary, #3b82f6);
137
+ outline-offset: 2px;
138
+ border-radius: var(--radius-sm, 0.25rem);
139
+ }
140
+
141
+ .navbar__hamburger-icon {
142
+ position: relative;
143
+ width: 1.5rem;
144
+ height: 1.25rem;
145
+ }
146
+
147
+ .navbar__hamburger-icon span {
148
+ position: absolute;
149
+ left: 0;
150
+ width: 100%;
151
+ height: 2px;
152
+ background: var(--color-text, #1f2937);
153
+ border-radius: var(--radius-full, 9999px);
154
+ transition: all var(--transition-fast, 150ms ease);
155
+ }
156
+
157
+ .navbar__hamburger-icon span:nth-child(1) {
158
+ top: 0;
159
+ }
160
+
161
+ .navbar__hamburger-icon span:nth-child(2) {
162
+ top: 50%;
163
+ transform: translateY(-50%);
164
+ }
165
+
166
+ .navbar__hamburger-icon span:nth-child(3) {
167
+ bottom: 0;
168
+ }
169
+
170
+ .navbar__hamburger-icon--open span:nth-child(1) {
171
+ top: 50%;
172
+ transform: translateY(-50%) rotate(45deg);
173
+ }
174
+
175
+ .navbar__hamburger-icon--open span:nth-child(2) {
176
+ opacity: 0;
177
+ }
178
+
179
+ .navbar__hamburger-icon--open span:nth-child(3) {
180
+ bottom: 50%;
181
+ transform: translateY(50%) rotate(-45deg);
182
+ }
183
+
184
+ .navbar__mobile-menu {
185
+ position: fixed;
186
+ top: 0;
187
+ right: 0;
188
+ bottom: 0;
189
+ width: min(80vw, 20rem);
190
+ background: var(--color-bg, #ffffff);
191
+ border-left: 1px solid var(--color-border, #e5e7eb);
192
+ padding: var(--space-lg, 1.5rem);
193
+ overflow-y: auto;
194
+ z-index: 51;
195
+ animation: slide-in 200ms ease-out;
196
+ }
197
+
198
+ .navbar__overlay {
199
+ position: fixed;
200
+ inset: 0;
201
+ background: rgba(0, 0, 0, 0.4);
202
+ z-index: 50;
203
+ animation: fade-in 200ms ease-out;
204
+ }
205
+
206
+ @keyframes slide-in {
207
+ from {
208
+ transform: translateX(100%);
209
+ }
210
+ to {
211
+ transform: translateX(0);
212
+ }
213
+ }
214
+
215
+ @keyframes fade-in {
216
+ from {
217
+ opacity: 0;
218
+ }
219
+ to {
220
+ opacity: 1;
221
+ }
222
+ }
223
+
224
+ /* Desktop breakpoint (F10) */
225
+ @media (min-width: 768px) {
226
+ .navbar__nav {
227
+ display: flex;
228
+ }
229
+
230
+ .navbar__actions {
231
+ display: flex;
232
+ }
233
+
234
+ .navbar__hamburger {
235
+ display: none;
236
+ }
237
+
238
+ .navbar__mobile-menu,
239
+ .navbar__overlay {
240
+ display: none;
241
+ }
242
+ }
243
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { NavbarProps } from '../../types/index.js';
2
+ interface Props extends NavbarProps {
3
+ logo?: import('svelte').Snippet;
4
+ actions?: import('svelte').Snippet;
5
+ mobileMenu?: import('svelte').Snippet;
6
+ children?: import('svelte').Snippet;
7
+ }
8
+ declare const Navbar: import("svelte").Component<Props, {}, "">;
9
+ type Navbar = ReturnType<typeof Navbar>;
10
+ export default Navbar;
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ import { RadioGroup as RadioPrimitive } from 'bits-ui';
3
+
4
+ interface Props {
5
+ value: string;
6
+ id?: string;
7
+ disabled?: boolean;
8
+ class?: string;
9
+ children?: import('svelte').Snippet;
10
+ }
11
+
12
+ let { value, id, disabled = false, class: className = '', children }: Props = $props();
13
+ </script>
14
+
15
+ <div class="radio-wrapper {className}">
16
+ <RadioPrimitive.Item class="radio {disabled ? 'radio--disabled' : ''}" {value} {id} {disabled}>
17
+ {#snippet children({ checked })}
18
+ {#if checked}
19
+ <span class="radio__indicator"></span>
20
+ {/if}
21
+ {/snippet}
22
+ </RadioPrimitive.Item>
23
+
24
+ {#if children}
25
+ <label for={id} class="radio__label" class:radio__label--disabled={disabled}>
26
+ {@render children()}
27
+ </label>
28
+ {/if}
29
+ </div>
30
+
31
+ <style>
32
+ .radio-wrapper {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ gap: var(--space-sm, 0.5rem);
36
+ }
37
+
38
+ :global(.radio) {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ width: 1.25rem;
43
+ height: 1.25rem;
44
+ min-width: 1.25rem;
45
+ min-height: 1.25rem;
46
+ border: 2px solid var(--color-border, #e5e7eb);
47
+ border-radius: 50%;
48
+ background: var(--color-bg, #ffffff);
49
+ cursor: pointer;
50
+ transition:
51
+ border-color var(--transition-fast, 150ms ease),
52
+ box-shadow var(--transition-fast, 150ms ease);
53
+ -webkit-tap-highlight-color: transparent;
54
+ }
55
+
56
+ :global(.radio:focus-visible) {
57
+ outline: none;
58
+ border-color: var(--color-primary, #3b82f6);
59
+ box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
60
+ }
61
+
62
+ :global(.radio[data-state='checked']) {
63
+ border-color: var(--color-primary, #3b82f6);
64
+ }
65
+
66
+ :global(.radio--disabled) {
67
+ opacity: 0.5;
68
+ cursor: not-allowed;
69
+ }
70
+
71
+ .radio__indicator {
72
+ width: 0.625rem;
73
+ height: 0.625rem;
74
+ border-radius: 50%;
75
+ background: var(--color-primary, #3b82f6);
76
+ }
77
+
78
+ .radio__label {
79
+ font-family: inherit;
80
+ font-size: var(--text-sm, 0.875rem);
81
+ color: var(--color-text, #1f2937);
82
+ cursor: pointer;
83
+ user-select: none;
84
+ min-height: var(--touch-target-min, 44px);
85
+ display: flex;
86
+ align-items: center;
87
+ }
88
+
89
+ .radio__label--disabled {
90
+ opacity: 0.5;
91
+ cursor: not-allowed;
92
+ }
93
+ </style>
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ value: string;
3
+ id?: string;
4
+ disabled?: boolean;
5
+ class?: string;
6
+ children?: import('svelte').Snippet;
7
+ }
8
+ declare const Radio: import("svelte").Component<Props, {}, "">;
9
+ type Radio = ReturnType<typeof Radio>;
10
+ export default Radio;
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import { RadioGroup as RadioPrimitive } from 'bits-ui';
3
+
4
+ interface Props {
5
+ value?: string;
6
+ name?: string;
7
+ disabled?: boolean;
8
+ required?: boolean;
9
+ error?: boolean;
10
+ orientation?: 'horizontal' | 'vertical';
11
+ class?: string;
12
+ onchange?: (value: string) => void;
13
+ children?: import('svelte').Snippet;
14
+ }
15
+
16
+ let {
17
+ value = $bindable(''),
18
+ name,
19
+ disabled = false,
20
+ required = false,
21
+ error = false,
22
+ orientation = 'vertical',
23
+ class: className = '',
24
+ onchange,
25
+ children
26
+ }: Props = $props();
27
+
28
+ function handleValueChange(newValue: string | undefined) {
29
+ if (newValue !== undefined) {
30
+ value = newValue;
31
+ onchange?.(newValue);
32
+ }
33
+ }
34
+ </script>
35
+
36
+ <RadioPrimitive.Root
37
+ class="radio-group radio-group--{orientation} {error ? 'radio-group--error' : ''} {disabled
38
+ ? 'radio-group--disabled'
39
+ : ''} {className}"
40
+ {name}
41
+ {value}
42
+ {disabled}
43
+ {required}
44
+ aria-invalid={error}
45
+ aria-required={required}
46
+ onValueChange={handleValueChange}
47
+ >
48
+ {#if children}
49
+ {@render children()}
50
+ {/if}
51
+ </RadioPrimitive.Root>
52
+
53
+ <style>
54
+ :global(.radio-group) {
55
+ display: flex;
56
+ gap: var(--space-sm, 0.5rem);
57
+ }
58
+
59
+ :global(.radio-group--vertical) {
60
+ flex-direction: column;
61
+ }
62
+
63
+ :global(.radio-group--horizontal) {
64
+ flex-direction: row;
65
+ flex-wrap: wrap;
66
+ }
67
+
68
+ :global(.radio-group--disabled) {
69
+ opacity: 0.5;
70
+ pointer-events: none;
71
+ }
72
+ </style>
@@ -0,0 +1,14 @@
1
+ interface Props {
2
+ value?: string;
3
+ name?: string;
4
+ disabled?: boolean;
5
+ required?: boolean;
6
+ error?: boolean;
7
+ orientation?: 'horizontal' | 'vertical';
8
+ class?: string;
9
+ onchange?: (value: string) => void;
10
+ children?: import('svelte').Snippet;
11
+ }
12
+ declare const RadioGroup: import("svelte").Component<Props, {}, "value">;
13
+ type RadioGroup = ReturnType<typeof RadioGroup>;
14
+ export default RadioGroup;
@@ -0,0 +1,2 @@
1
+ export { default as Radio } from './Radio.svelte';
2
+ export { default as RadioGroup } from './RadioGroup.svelte';
@@ -0,0 +1,2 @@
1
+ export { default as Radio } from './Radio.svelte';
2
+ export { default as RadioGroup } from './RadioGroup.svelte';
@@ -0,0 +1,251 @@
1
+ <script lang="ts">
2
+ import { Select as SelectPrimitive } from 'bits-ui';
3
+ import type { InputSize, SelectOption } from '../../types/index.js';
4
+
5
+ interface Props {
6
+ value?: string;
7
+ options: SelectOption[];
8
+ placeholder?: string;
9
+ name?: string;
10
+ id?: string;
11
+ size?: InputSize;
12
+ disabled?: boolean;
13
+ required?: boolean;
14
+ error?: boolean;
15
+ class?: string;
16
+ onchange?: (value: string) => void;
17
+ }
18
+
19
+ let {
20
+ value = $bindable(''),
21
+ options,
22
+ placeholder = 'Select an option',
23
+ name,
24
+ id,
25
+ size = 'md',
26
+ disabled = false,
27
+ required = false,
28
+ error = false,
29
+ class: className = '',
30
+ onchange
31
+ }: Props = $props();
32
+
33
+ const selectedLabel = $derived(options.find((o) => o.value === value)?.label ?? '');
34
+
35
+ function handleValueChange(newValue: string | undefined) {
36
+ if (newValue !== undefined) {
37
+ value = newValue;
38
+ onchange?.(newValue);
39
+ }
40
+ }
41
+ </script>
42
+
43
+ <SelectPrimitive.Root
44
+ type="single"
45
+ {name}
46
+ {disabled}
47
+ {required}
48
+ {value}
49
+ onValueChange={handleValueChange}
50
+ >
51
+ <SelectPrimitive.Trigger
52
+ class="select select--{size} {error ? 'select--error' : ''} {disabled
53
+ ? 'select--disabled'
54
+ : ''} {className}"
55
+ {id}
56
+ aria-invalid={error}
57
+ aria-required={required}
58
+ >
59
+ <span class="select__value" class:select__value--placeholder={!value}>
60
+ {selectedLabel || placeholder}
61
+ </span>
62
+ <span class="select__icon" aria-hidden="true">
63
+ <svg
64
+ width="16"
65
+ height="16"
66
+ viewBox="0 0 16 16"
67
+ fill="none"
68
+ xmlns="http://www.w3.org/2000/svg"
69
+ >
70
+ <path
71
+ d="M4 6L8 10L12 6"
72
+ stroke="currentColor"
73
+ stroke-width="1.5"
74
+ stroke-linecap="round"
75
+ stroke-linejoin="round"
76
+ />
77
+ </svg>
78
+ </span>
79
+ </SelectPrimitive.Trigger>
80
+
81
+ <SelectPrimitive.Portal>
82
+ <SelectPrimitive.Content class="select__content" sideOffset={4}>
83
+ <SelectPrimitive.Viewport class="select__viewport">
84
+ {#each options as option (option.value)}
85
+ <SelectPrimitive.Item
86
+ class="select__item"
87
+ value={option.value}
88
+ disabled={option.disabled}
89
+ >
90
+ {#snippet children({ selected })}
91
+ <span class="select__item-text">{option.label}</span>
92
+ {#if selected}
93
+ <span class="select__item-indicator">
94
+ <svg
95
+ width="16"
96
+ height="16"
97
+ viewBox="0 0 16 16"
98
+ fill="none"
99
+ xmlns="http://www.w3.org/2000/svg"
100
+ >
101
+ <path
102
+ d="M13.5 4.5L6.5 11.5L3 8"
103
+ stroke="currentColor"
104
+ stroke-width="1.5"
105
+ stroke-linecap="round"
106
+ stroke-linejoin="round"
107
+ />
108
+ </svg>
109
+ </span>
110
+ {/if}
111
+ {/snippet}
112
+ </SelectPrimitive.Item>
113
+ {/each}
114
+ </SelectPrimitive.Viewport>
115
+ </SelectPrimitive.Content>
116
+ </SelectPrimitive.Portal>
117
+ </SelectPrimitive.Root>
118
+
119
+ <style>
120
+ :global(.select) {
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: space-between;
124
+ width: 100%;
125
+ min-height: var(--touch-target-min, 44px);
126
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
127
+ border: 1px solid var(--color-border, #e5e7eb);
128
+ border-radius: var(--radius-md, 0.375rem);
129
+ background: var(--color-bg, #ffffff);
130
+ color: var(--color-text, #1f2937);
131
+ font-family: inherit;
132
+ font-size: var(--text-sm, 0.875rem);
133
+ line-height: 1.5;
134
+ cursor: pointer;
135
+ transition:
136
+ border-color var(--transition-fast, 150ms ease),
137
+ box-shadow var(--transition-fast, 150ms ease);
138
+ -webkit-tap-highlight-color: transparent;
139
+ }
140
+
141
+ :global(.select:focus) {
142
+ outline: none;
143
+ border-color: var(--color-primary, #3b82f6);
144
+ box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
145
+ }
146
+
147
+ :global(.select--disabled) {
148
+ opacity: 0.5;
149
+ cursor: not-allowed;
150
+ background: var(--color-bg-muted, #f3f4f6);
151
+ }
152
+
153
+ :global(.select--error) {
154
+ border-color: var(--color-error, #ef4444);
155
+ }
156
+
157
+ :global(.select--error:focus) {
158
+ border-color: var(--color-error, #ef4444);
159
+ box-shadow: 0 0 0 3px var(--color-error-alpha, rgba(239, 68, 68, 0.1));
160
+ }
161
+
162
+ /* Sizes */
163
+ :global(.select--sm) {
164
+ min-height: var(--touch-target-min, 44px);
165
+ padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem);
166
+ font-size: var(--text-xs, 0.75rem);
167
+ }
168
+
169
+ :global(.select--lg) {
170
+ min-height: 3.25rem;
171
+ padding: var(--space-md, 1rem) var(--space-lg, 1.5rem);
172
+ font-size: var(--text-base, 1rem);
173
+ }
174
+
175
+ .select__value {
176
+ flex: 1;
177
+ text-align: left;
178
+ overflow: hidden;
179
+ text-overflow: ellipsis;
180
+ white-space: nowrap;
181
+ }
182
+
183
+ .select__value--placeholder {
184
+ color: var(--color-text-muted, #9ca3af);
185
+ }
186
+
187
+ .select__icon {
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ color: var(--color-text-muted, #6b7280);
192
+ flex-shrink: 0;
193
+ margin-left: var(--space-sm, 0.5rem);
194
+ }
195
+
196
+ :global(.select__content) {
197
+ z-index: 50;
198
+ min-width: 8rem;
199
+ overflow: hidden;
200
+ border: 1px solid var(--color-border, #e5e7eb);
201
+ border-radius: var(--radius-md, 0.375rem);
202
+ background: var(--color-bg, #ffffff);
203
+ box-shadow: var(--shadow-md, 0 4px 6px rgba(0, 0, 0, 0.1));
204
+ font-family: var(--font-family, system-ui, -apple-system, sans-serif);
205
+ }
206
+
207
+ :global(.select__viewport) {
208
+ padding: var(--space-xs, 0.25rem);
209
+ }
210
+
211
+ :global(.select__item) {
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: space-between;
215
+ padding: 0.375rem 0.5rem;
216
+ border-radius: var(--radius-sm, 0.25rem);
217
+ font-size: var(--text-sm, 0.875rem);
218
+ line-height: 1.25;
219
+ color: var(--color-text, #1f2937);
220
+ cursor: pointer;
221
+ outline: none;
222
+ transition: background var(--transition-fast, 150ms ease);
223
+ }
224
+
225
+ :global(.select__item:hover),
226
+ :global(.select__item[data-highlighted]) {
227
+ background: var(--color-bg-muted, #f3f4f6);
228
+ }
229
+
230
+ :global(.select__item[data-selected]) {
231
+ background: var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
232
+ color: var(--color-primary, #3b82f6);
233
+ }
234
+
235
+ :global(.select__item[data-disabled]) {
236
+ opacity: 0.5;
237
+ cursor: not-allowed;
238
+ }
239
+
240
+ .select__item-text {
241
+ flex: 1;
242
+ }
243
+
244
+ .select__item-indicator {
245
+ display: flex;
246
+ align-items: center;
247
+ justify-content: center;
248
+ color: var(--color-primary, #3b82f6);
249
+ margin-left: var(--space-sm, 0.5rem);
250
+ }
251
+ </style>