plasma-svelte 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 (95) hide show
  1. package/README.md +65 -0
  2. package/dist/accordion/Accordion.svelte +28 -0
  3. package/dist/accordion/Accordion.svelte.d.ts +8 -0
  4. package/dist/accordion/AccordionItem.svelte +193 -0
  5. package/dist/accordion/AccordionItem.svelte.d.ts +14 -0
  6. package/dist/avatar/Avatar.svelte +178 -0
  7. package/dist/avatar/Avatar.svelte.d.ts +16 -0
  8. package/dist/badge/Badge.svelte +162 -0
  9. package/dist/badge/Badge.svelte.d.ts +16 -0
  10. package/dist/breadcrumb/Breadcrumb.svelte +59 -0
  11. package/dist/breadcrumb/Breadcrumb.svelte.d.ts +9 -0
  12. package/dist/breadcrumb/BreadcrumbItem.svelte +151 -0
  13. package/dist/breadcrumb/BreadcrumbItem.svelte.d.ts +14 -0
  14. package/dist/button/Button.svelte +274 -0
  15. package/dist/button/Button.svelte.d.ts +22 -0
  16. package/dist/button/ButtonGroup.svelte +98 -0
  17. package/dist/button/ButtonGroup.svelte.d.ts +12 -0
  18. package/dist/card/Card.svelte +191 -0
  19. package/dist/card/Card.svelte.d.ts +18 -0
  20. package/dist/checkbox/Checkbox.svelte +199 -0
  21. package/dist/checkbox/Checkbox.svelte.d.ts +12 -0
  22. package/dist/datepicker/DatePicker.svelte +509 -0
  23. package/dist/datepicker/DatePicker.svelte.d.ts +14 -0
  24. package/dist/divider/Divider.svelte +112 -0
  25. package/dist/divider/Divider.svelte.d.ts +13 -0
  26. package/dist/drawer/Drawer.svelte +330 -0
  27. package/dist/drawer/Drawer.svelte.d.ts +21 -0
  28. package/dist/dropzone/DropZone.svelte +282 -0
  29. package/dist/dropzone/DropZone.svelte.d.ts +17 -0
  30. package/dist/emptystate/EmptyState.svelte +169 -0
  31. package/dist/emptystate/EmptyState.svelte.d.ts +15 -0
  32. package/dist/headerbar/HeaderBar.svelte +210 -0
  33. package/dist/headerbar/HeaderBar.svelte.d.ts +17 -0
  34. package/dist/index.d.ts +44 -0
  35. package/dist/index.js +44 -0
  36. package/dist/input/Input.svelte +227 -0
  37. package/dist/input/Input.svelte.d.ts +14 -0
  38. package/dist/layout/Flex.svelte +110 -0
  39. package/dist/layout/Flex.svelte.d.ts +20 -0
  40. package/dist/layout/Grid.svelte +78 -0
  41. package/dist/layout/Grid.svelte.d.ts +19 -0
  42. package/dist/layout/Space.svelte +64 -0
  43. package/dist/layout/Space.svelte.d.ts +16 -0
  44. package/dist/layout/utils.d.ts +2 -0
  45. package/dist/layout/utils.js +19 -0
  46. package/dist/menu/Menu.svelte +156 -0
  47. package/dist/menu/Menu.svelte.d.ts +14 -0
  48. package/dist/menu/MenuDivider.svelte +19 -0
  49. package/dist/menu/MenuDivider.svelte.d.ts +6 -0
  50. package/dist/menu/MenuItem.svelte +126 -0
  51. package/dist/menu/MenuItem.svelte.d.ts +13 -0
  52. package/dist/message/InlineMessage.svelte +226 -0
  53. package/dist/message/InlineMessage.svelte.d.ts +15 -0
  54. package/dist/modal/ConfirmDialog.svelte +151 -0
  55. package/dist/modal/ConfirmDialog.svelte.d.ts +19 -0
  56. package/dist/modal/Modal.svelte +279 -0
  57. package/dist/modal/Modal.svelte.d.ts +18 -0
  58. package/dist/pagination/Pagination.svelte +258 -0
  59. package/dist/pagination/Pagination.svelte.d.ts +16 -0
  60. package/dist/progress/ProgressBar.svelte +179 -0
  61. package/dist/progress/ProgressBar.svelte.d.ts +15 -0
  62. package/dist/radio/Radio.svelte +161 -0
  63. package/dist/radio/Radio.svelte.d.ts +13 -0
  64. package/dist/searchfield/SearchField.svelte +246 -0
  65. package/dist/searchfield/SearchField.svelte.d.ts +15 -0
  66. package/dist/select/Select.svelte +392 -0
  67. package/dist/select/Select.svelte.d.ts +20 -0
  68. package/dist/sidebar/Sidebar.svelte +78 -0
  69. package/dist/sidebar/Sidebar.svelte.d.ts +11 -0
  70. package/dist/sidebar/SidebarGroup.svelte +51 -0
  71. package/dist/sidebar/SidebarGroup.svelte.d.ts +9 -0
  72. package/dist/sidebar/SidebarItem.svelte +185 -0
  73. package/dist/sidebar/SidebarItem.svelte.d.ts +16 -0
  74. package/dist/slider/Slider.svelte +204 -0
  75. package/dist/slider/Slider.svelte.d.ts +16 -0
  76. package/dist/spinbox/SpinBox.svelte +299 -0
  77. package/dist/spinbox/SpinBox.svelte.d.ts +20 -0
  78. package/dist/spinner/Spinner.svelte +130 -0
  79. package/dist/spinner/Spinner.svelte.d.ts +11 -0
  80. package/dist/style/base.css +178 -0
  81. package/dist/style/index.css +2 -0
  82. package/dist/style/token.css +230 -0
  83. package/dist/switch/Switch.svelte +199 -0
  84. package/dist/switch/Switch.svelte.d.ts +13 -0
  85. package/dist/table/Table.svelte +305 -0
  86. package/dist/table/Table.svelte.d.ts +32 -0
  87. package/dist/tabs/Tabs.svelte +216 -0
  88. package/dist/tabs/Tabs.svelte.d.ts +21 -0
  89. package/dist/textarea/TextArea.svelte +114 -0
  90. package/dist/textarea/TextArea.svelte.d.ts +14 -0
  91. package/dist/timepicker/TimePicker.svelte +452 -0
  92. package/dist/timepicker/TimePicker.svelte.d.ts +16 -0
  93. package/dist/tooltip/Tooltip.svelte +122 -0
  94. package/dist/tooltip/Tooltip.svelte.d.ts +12 -0
  95. package/package.json +66 -0
@@ -0,0 +1,246 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from "svelte/elements";
3
+
4
+ type SearchFieldSize = "sm" | "md" | "lg" | "small" | "medium" | "large";
5
+
6
+ interface Props extends Omit<HTMLInputAttributes, "size"> {
7
+ value?: string;
8
+ placeholder?: string;
9
+ loading?: boolean;
10
+ clearable?: boolean;
11
+ size?: SearchFieldSize;
12
+ disabled?: boolean;
13
+ onsearch?: (query: string) => void;
14
+ onclear?: () => void;
15
+ }
16
+
17
+ let {
18
+ value = $bindable(""),
19
+ placeholder = "Search...",
20
+ loading = false,
21
+ clearable = true,
22
+ size = "md",
23
+ disabled = false,
24
+ onsearch,
25
+ onclear,
26
+ class: customClass = "",
27
+ id,
28
+ ...restProps
29
+ }: Props = $props();
30
+
31
+ let inputEl = $state<HTMLInputElement | null>(null);
32
+
33
+ const resolvedSize = $derived(
34
+ size === "small"
35
+ ? "sm"
36
+ : size === "large"
37
+ ? "lg"
38
+ : size === "medium"
39
+ ? "md"
40
+ : size,
41
+ );
42
+
43
+ function handleInput(e: Event) {
44
+ const target = e.target as HTMLInputElement;
45
+ value = target.value;
46
+ onsearch?.(value);
47
+ }
48
+
49
+ function handleClear() {
50
+ value = "";
51
+ onclear?.();
52
+ onsearch?.("");
53
+ inputEl?.focus();
54
+ }
55
+
56
+ function handleKeydown(e: KeyboardEvent) {
57
+ if (e.key === "Escape" && value) {
58
+ e.stopPropagation();
59
+ handleClear();
60
+ } else if (e.key === "Enter") {
61
+ onsearch?.(value);
62
+ }
63
+ }
64
+ </script>
65
+
66
+ <div
67
+ class="plasma-searchfield plasma-searchfield--{resolvedSize} {customClass}"
68
+ class:plasma-searchfield--disabled={disabled}
69
+ >
70
+ <span class="plasma-searchfield-icon">
71
+ <svg
72
+ aria-hidden="true"
73
+ viewBox="0 0 16 16"
74
+ width="14"
75
+ height="14"
76
+ fill="currentColor"
77
+ >
78
+ <path
79
+ d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
80
+ />
81
+ </svg>
82
+ </span>
83
+
84
+ <input
85
+ {id}
86
+ type="search"
87
+ bind:this={inputEl}
88
+ {disabled}
89
+ {placeholder}
90
+ {value}
91
+ oninput={handleInput}
92
+ onkeydown={handleKeydown}
93
+ class="plasma-searchfield-input"
94
+ {...restProps}
95
+ />
96
+
97
+ {#if loading}
98
+ <span class="plasma-searchfield-spinner">Loading...</span>
99
+ {:else if clearable && value && !disabled}
100
+ <button
101
+ type="button"
102
+ class="plasma-searchfield-clear"
103
+ onclick={handleClear}
104
+ aria-label="Clear search"
105
+ tabindex="-1"
106
+ >
107
+ <svg
108
+ aria-hidden="true"
109
+ viewBox="0 0 16 16"
110
+ width="12"
111
+ height="12"
112
+ fill="currentColor"
113
+ >
114
+ <path
115
+ d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"
116
+ />
117
+ </svg>
118
+ </button>
119
+ {/if}
120
+ </div>
121
+
122
+ <style>
123
+ .plasma-searchfield {
124
+ position: relative;
125
+ display: inline-flex;
126
+ align-items: center;
127
+ width: 100%;
128
+ box-sizing: border-box;
129
+ font-family: var(--plasma-font-sans);
130
+ background-color: var(--plasma-color-view-bg);
131
+ border: 1px solid var(--plasma-color-border);
132
+ border-radius: var(--plasma-radius-full);
133
+ color: var(--plasma-color-text);
134
+ box-shadow: var(--plasma-shadow-xs);
135
+ transition: var(--plasma-transition-fast);
136
+ }
137
+
138
+ .plasma-searchfield:hover:not(.plasma-searchfield--disabled):not(
139
+ :focus-within
140
+ ) {
141
+ border-color: var(--plasma-color-border-hover);
142
+ }
143
+
144
+ .plasma-searchfield:focus-within:not(.plasma-searchfield--disabled) {
145
+ border-color: var(--plasma-color-highlight);
146
+ box-shadow: var(--plasma-shadow-focus);
147
+ }
148
+
149
+ .plasma-searchfield--disabled {
150
+ opacity: 0.45;
151
+ cursor: not-allowed;
152
+ background-color: var(--plasma-color-surface-sunken);
153
+ }
154
+
155
+ /* --------------------------------------------------------------------------
156
+ Icons & Input
157
+ -------------------------------------------------------------------------- */
158
+ .plasma-searchfield-icon {
159
+ display: inline-flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ padding-left: var(--plasma-space-md);
163
+ color: var(--plasma-color-text-muted);
164
+ flex-shrink: 0;
165
+ }
166
+
167
+ .plasma-searchfield-input {
168
+ flex: 1;
169
+ min-width: 0;
170
+ height: 100%;
171
+ padding: 0 var(--plasma-space-sm);
172
+ border: none;
173
+ outline: none;
174
+ background: transparent;
175
+ color: inherit;
176
+ font-family: inherit;
177
+ font-size: inherit;
178
+ }
179
+
180
+ .plasma-searchfield-input::-webkit-search-decoration,
181
+ .plasma-searchfield-input::-webkit-search-cancel-button {
182
+ display: none;
183
+ }
184
+
185
+ .plasma-searchfield-input::placeholder {
186
+ color: var(--plasma-color-text-muted);
187
+ opacity: 0.75;
188
+ }
189
+
190
+ .plasma-searchfield-clear {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ justify-content: center;
194
+ width: 20px;
195
+ height: 20px;
196
+ margin-right: var(--plasma-space-sm);
197
+ border: none;
198
+ border-radius: var(--plasma-radius-full);
199
+ background: transparent;
200
+ color: var(--plasma-color-text-muted);
201
+ cursor: pointer;
202
+ transition: var(--plasma-transition-fast);
203
+ flex-shrink: 0;
204
+ }
205
+
206
+ .plasma-searchfield-clear:hover {
207
+ background-color: rgba(0, 0, 0, 0.15);
208
+ color: var(--plasma-color-text);
209
+ }
210
+
211
+ /* Loading Spinner */
212
+ .plasma-searchfield-spinner {
213
+ width: 14px;
214
+ height: 14px;
215
+ margin-right: var(--plasma-space-md);
216
+ border: 2px solid var(--plasma-color-border-subtle);
217
+ border-top-color: var(--plasma-color-highlight);
218
+ border-radius: var(--plasma-radius-full);
219
+ animation: plasma-spin 0.8s linear infinite;
220
+ flex-shrink: 0;
221
+ }
222
+
223
+ @keyframes plasma-spin {
224
+ to {
225
+ transform: rotate(360deg);
226
+ }
227
+ }
228
+
229
+ /* --------------------------------------------------------------------------
230
+ Sizing
231
+ -------------------------------------------------------------------------- */
232
+ .plasma-searchfield--sm {
233
+ height: var(--plasma-control-height-sm);
234
+ font-size: var(--plasma-font-size-xs);
235
+ }
236
+
237
+ .plasma-searchfield--md {
238
+ height: var(--plasma-control-height-md);
239
+ font-size: var(--plasma-font-size-base);
240
+ }
241
+
242
+ .plasma-searchfield--lg {
243
+ height: var(--plasma-control-height-lg);
244
+ font-size: var(--plasma-font-size-md);
245
+ }
246
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { HTMLInputAttributes } from "svelte/elements";
2
+ type SearchFieldSize = "sm" | "md" | "lg" | "small" | "medium" | "large";
3
+ interface Props extends Omit<HTMLInputAttributes, "size"> {
4
+ value?: string;
5
+ placeholder?: string;
6
+ loading?: boolean;
7
+ clearable?: boolean;
8
+ size?: SearchFieldSize;
9
+ disabled?: boolean;
10
+ onsearch?: (query: string) => void;
11
+ onclear?: () => void;
12
+ }
13
+ declare const SearchField: import("svelte").Component<Props, {}, "value">;
14
+ type SearchField = ReturnType<typeof SearchField>;
15
+ export default SearchField;
@@ -0,0 +1,392 @@
1
+ <!-- biome-ignore-all lint/a11y/noNoninteractiveElementToInteractiveRole: experimental only -->
2
+ <!-- biome-ignore-all lint/a11y/useFocusableInteractive: experimental only -->
3
+ <!-- biome-ignore-all lint/a11y/useKeyWithClickEvents: experimental only -->
4
+ <script lang="ts">
5
+ import type { HTMLAttributes } from "svelte/elements";
6
+
7
+ export interface SelectOption {
8
+ value: string | number;
9
+ label: string;
10
+ disabled?: boolean;
11
+ }
12
+
13
+ type SelectSize = "sm" | "md" | "lg" | "small" | "medium" | "large";
14
+
15
+ interface Props
16
+ extends Omit<HTMLAttributes<HTMLDivElement>, "size" | "onchange"> {
17
+ options: (SelectOption | string)[];
18
+ value?: string | number;
19
+ placeholder?: string;
20
+ size?: SelectSize;
21
+ disabled?: boolean;
22
+ invalid?: boolean;
23
+ name?: string;
24
+ onchange?: (val: string | number) => void;
25
+ }
26
+
27
+ let {
28
+ options,
29
+ value = $bindable(""),
30
+ placeholder = "Select an option...",
31
+ size = "md",
32
+ disabled = false,
33
+ invalid = false,
34
+ name,
35
+ onchange,
36
+ class: customClass = "",
37
+ id,
38
+ ...restProps
39
+ }: Props = $props();
40
+
41
+ let isOpen = $state(false);
42
+ let triggerEl = $state<HTMLButtonElement | null>(null);
43
+ let highlightedIndex = $state(-1);
44
+
45
+ // Normalize options into { value, label, disabled } objects
46
+ const normalizedOptions: SelectOption[] = $derived(
47
+ options.map((opt) =>
48
+ typeof opt === "string" ? { value: opt, label: opt } : opt,
49
+ ),
50
+ );
51
+
52
+ const selectedOption = $derived(
53
+ normalizedOptions.find((opt) => opt.value === value),
54
+ );
55
+
56
+ const resolvedSize = $derived(
57
+ size === "small"
58
+ ? "sm"
59
+ : size === "large"
60
+ ? "lg"
61
+ : size === "medium"
62
+ ? "md"
63
+ : size,
64
+ );
65
+
66
+ function toggleDropdown() {
67
+ if (disabled) return;
68
+ isOpen = !isOpen;
69
+ if (isOpen) {
70
+ highlightedIndex = normalizedOptions.findIndex(
71
+ (opt) => opt.value === value,
72
+ );
73
+ if (highlightedIndex === -1) highlightedIndex = 0;
74
+ }
75
+ }
76
+
77
+ function selectOption(opt: SelectOption) {
78
+ if (opt.disabled) return;
79
+ value = opt.value;
80
+ isOpen = false;
81
+ onchange?.(opt.value);
82
+ triggerEl?.focus();
83
+ }
84
+
85
+ function handleKeydown(e: KeyboardEvent) {
86
+ if (disabled) return;
87
+
88
+ if (!isOpen) {
89
+ if (
90
+ e.key === "ArrowDown" ||
91
+ e.key === "ArrowUp" ||
92
+ e.key === "Enter" ||
93
+ e.key === " "
94
+ ) {
95
+ e.preventDefault();
96
+ isOpen = true;
97
+ highlightedIndex = normalizedOptions.findIndex(
98
+ (opt) => opt.value === value,
99
+ );
100
+ if (highlightedIndex === -1) highlightedIndex = 0;
101
+ }
102
+ return;
103
+ }
104
+
105
+ if (e.key === "Escape") {
106
+ e.preventDefault();
107
+ isOpen = false;
108
+ triggerEl?.focus();
109
+ } else if (e.key === "ArrowDown") {
110
+ e.preventDefault();
111
+ highlightedIndex = (highlightedIndex + 1) % normalizedOptions.length;
112
+ } else if (e.key === "ArrowUp") {
113
+ e.preventDefault();
114
+ highlightedIndex =
115
+ (highlightedIndex - 1 + normalizedOptions.length) %
116
+ normalizedOptions.length;
117
+ } else if (e.key === "Enter" || e.key === " ") {
118
+ e.preventDefault();
119
+ if (
120
+ highlightedIndex >= 0 &&
121
+ highlightedIndex < normalizedOptions.length
122
+ ) {
123
+ selectOption(normalizedOptions[highlightedIndex]);
124
+ }
125
+ } else if (e.key === "Tab") {
126
+ isOpen = false;
127
+ }
128
+ }
129
+
130
+ function handleWindowClick(e: MouseEvent) {
131
+ if (
132
+ isOpen &&
133
+ triggerEl &&
134
+ !triggerEl.parentElement?.contains(e.target as Node)
135
+ ) {
136
+ isOpen = false;
137
+ }
138
+ }
139
+ </script>
140
+
141
+ <svelte:window onclick={handleWindowClick} />
142
+
143
+ <div
144
+ class="plasma-select-container plasma-select--{resolvedSize} {customClass}"
145
+ class:plasma-select--disabled={disabled}
146
+ class:plasma-select--invalid={invalid}
147
+ class:plasma-select--open={isOpen}
148
+ {...restProps}
149
+ >
150
+ {#if name}
151
+ <input type="hidden" {name} {value} />
152
+ {/if}
153
+
154
+ <button
155
+ {id}
156
+ type="button"
157
+ bind:this={triggerEl}
158
+ {disabled}
159
+ aria-haspopup="listbox"
160
+ aria-expanded={isOpen}
161
+ class="plasma-select-trigger"
162
+ onclick={toggleDropdown}
163
+ onkeydown={handleKeydown}
164
+ >
165
+ <span
166
+ class="plasma-select-label"
167
+ class:plasma-select-label--placeholder={!selectedOption}
168
+ >
169
+ {selectedOption ? selectedOption.label : placeholder}
170
+ </span>
171
+
172
+ <span class="plasma-select-chevron">
173
+ <svg
174
+ aria-hidden="true"
175
+ viewBox="0 0 16 16"
176
+ width="12"
177
+ height="12"
178
+ fill="currentColor"
179
+ >
180
+ <path
181
+ fill-rule="evenodd"
182
+ d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
183
+ />
184
+ </svg>
185
+ </span>
186
+ </button>
187
+
188
+ {#if isOpen}
189
+ <ul tabindex="-1" class="plasma-select-popup">
190
+ {#each normalizedOptions as opt, i}
191
+ {@const isSelected = opt.value === value}
192
+ {@const isHighlighted = i === highlightedIndex}
193
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
194
+ <li
195
+ role="option"
196
+ aria-selected={isSelected}
197
+ aria-disabled={opt.disabled}
198
+ class="plasma-select-option"
199
+ class:plasma-select-option--selected={isSelected}
200
+ class:plasma-select-option--highlighted={isHighlighted}
201
+ class:plasma-select-option--disabled={opt.disabled}
202
+ onclick={() => selectOption(opt)}
203
+ onmouseenter={() => {
204
+ if (!opt.disabled) highlightedIndex = i;
205
+ }}
206
+ >
207
+ <span>{opt.label}</span>
208
+ {#if isSelected}
209
+ <svg
210
+ aria-hidden="true"
211
+ viewBox="0 0 16 16"
212
+ width="12"
213
+ height="12"
214
+ fill="currentColor"
215
+ class="plasma-select-option-check"
216
+ >
217
+ <path
218
+ d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"
219
+ />
220
+ </svg>
221
+ {/if}
222
+ </li>
223
+ {/each}
224
+ </ul>
225
+ {/if}
226
+ </div>
227
+
228
+ <style>
229
+ .plasma-select-container {
230
+ position: relative;
231
+ display: inline-flex;
232
+ width: 100%;
233
+ box-sizing: border-box;
234
+ font-family: var(--plasma-font-sans);
235
+ }
236
+
237
+ .plasma-select--disabled {
238
+ opacity: 0.45;
239
+ cursor: not-allowed;
240
+ }
241
+
242
+ /* --------------------------------------------------------------------------
243
+ Trigger Button
244
+ -------------------------------------------------------------------------- */
245
+ .plasma-select-trigger {
246
+ display: inline-flex;
247
+ align-items: center;
248
+ justify-content: space-between;
249
+ width: 100%;
250
+ font-family: inherit;
251
+ font-size: inherit;
252
+ color: var(--plasma-color-text);
253
+ background-color: var(--plasma-color-surface);
254
+ border: 1px solid var(--plasma-color-border);
255
+ border-radius: var(--plasma-radius-md);
256
+ cursor: pointer;
257
+ user-select: none;
258
+ box-shadow: var(--plasma-shadow-xs);
259
+ transition: var(--plasma-transition-fast);
260
+ }
261
+
262
+ .plasma-select-trigger:hover:not(:disabled) {
263
+ border-color: var(--plasma-color-border-hover);
264
+ background-color: var(--plasma-color-surface-hover);
265
+ }
266
+
267
+ .plasma-select-trigger:focus-visible,
268
+ .plasma-select--open .plasma-select-trigger {
269
+ outline: none;
270
+ border-color: var(--plasma-color-highlight);
271
+ box-shadow: var(--plasma-shadow-focus);
272
+ }
273
+
274
+ .plasma-select--invalid .plasma-select-trigger {
275
+ border-color: var(--plasma-color-negative);
276
+ }
277
+
278
+ .plasma-select-label {
279
+ overflow: hidden;
280
+ text-overflow: ellipsis;
281
+ white-space: nowrap;
282
+ text-align: left;
283
+ }
284
+
285
+ .plasma-select-label--placeholder {
286
+ color: var(--plasma-color-text-muted);
287
+ }
288
+
289
+ .plasma-select-chevron {
290
+ display: inline-flex;
291
+ align-items: center;
292
+ color: var(--plasma-color-text-muted);
293
+ transition: transform var(--plasma-duration-base) var(--plasma-ease-default);
294
+ flex-shrink: 0;
295
+ }
296
+
297
+ .plasma-select--open .plasma-select-chevron {
298
+ transform: rotate(180deg);
299
+ }
300
+
301
+ /* --------------------------------------------------------------------------
302
+ Sizing
303
+ -------------------------------------------------------------------------- */
304
+ .plasma-select--sm .plasma-select-trigger {
305
+ height: var(--plasma-control-height-sm);
306
+ padding: 0 var(--plasma-space-sm);
307
+ font-size: var(--plasma-font-size-xs);
308
+ }
309
+
310
+ .plasma-select--md .plasma-select-trigger {
311
+ height: var(--plasma-control-height-md);
312
+ padding: 0 var(--plasma-space-md);
313
+ font-size: var(--plasma-font-size-base);
314
+ }
315
+
316
+ .plasma-select--lg .plasma-select-trigger {
317
+ height: var(--plasma-control-height-lg);
318
+ padding: 0 var(--plasma-space-lg);
319
+ font-size: var(--plasma-font-size-md);
320
+ }
321
+
322
+ /* --------------------------------------------------------------------------
323
+ Popup Menu
324
+ -------------------------------------------------------------------------- */
325
+ .plasma-select-popup {
326
+ position: absolute;
327
+ top: calc(100% + 4px);
328
+ left: 0;
329
+ right: 0;
330
+ z-index: 1000;
331
+ max-height: 240px;
332
+ overflow-y: auto;
333
+ margin: 0;
334
+ padding: 4px;
335
+ list-style: none;
336
+ background-color: var(--plasma-color-surface);
337
+ border: 1px solid var(--plasma-color-border);
338
+ border-radius: var(--plasma-radius-md);
339
+ box-shadow: var(--plasma-shadow-md);
340
+ animation: plasma-select-in var(--plasma-duration-fast)
341
+ var(--plasma-ease-default);
342
+ }
343
+
344
+ .plasma-select-option {
345
+ display: flex;
346
+ align-items: center;
347
+ justify-content: space-between;
348
+ padding: var(--plasma-space-xs) var(--plasma-space-sm);
349
+ border-radius: var(--plasma-radius-sm);
350
+ font-size: var(--plasma-font-size-base);
351
+ color: var(--plasma-color-text);
352
+ cursor: pointer;
353
+ user-select: none;
354
+ transition: var(--plasma-transition-fast);
355
+ }
356
+
357
+ .plasma-select-option--highlighted {
358
+ background-color: var(--plasma-color-surface-hover);
359
+ color: var(--plasma-color-text);
360
+ }
361
+
362
+ .plasma-select-option--selected {
363
+ background-color: var(--plasma-color-highlight-soft);
364
+ color: var(--plasma-color-highlight);
365
+ font-weight: var(--plasma-font-weight-medium);
366
+ }
367
+
368
+ .plasma-select-option--selected.plasma-select-option--highlighted {
369
+ background-color: var(--plasma-color-highlight);
370
+ color: #ffffff;
371
+ }
372
+
373
+ .plasma-select-option--disabled {
374
+ opacity: 0.4;
375
+ cursor: not-allowed;
376
+ }
377
+
378
+ .plasma-select-option-check {
379
+ flex-shrink: 0;
380
+ }
381
+
382
+ @keyframes plasma-select-in {
383
+ from {
384
+ opacity: 0;
385
+ transform: translateY(-4px);
386
+ }
387
+ to {
388
+ opacity: 1;
389
+ transform: translateY(0);
390
+ }
391
+ }
392
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ export interface SelectOption {
3
+ value: string | number;
4
+ label: string;
5
+ disabled?: boolean;
6
+ }
7
+ type SelectSize = "sm" | "md" | "lg" | "small" | "medium" | "large";
8
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "size" | "onchange"> {
9
+ options: (SelectOption | string)[];
10
+ value?: string | number;
11
+ placeholder?: string;
12
+ size?: SelectSize;
13
+ disabled?: boolean;
14
+ invalid?: boolean;
15
+ name?: string;
16
+ onchange?: (val: string | number) => void;
17
+ }
18
+ declare const Select: import("svelte").Component<Props, {}, "value">;
19
+ type Select = ReturnType<typeof Select>;
20
+ export default Select;