@noahbaumgartner/svelte-ui 0.0.4 → 0.0.5

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 (36) hide show
  1. package/README.md +8 -8
  2. package/dist/components/accordion/Accordion.svelte +34 -0
  3. package/dist/components/accordion/Accordion.svelte.d.ts +11 -0
  4. package/dist/components/accordion/AccordionItem.svelte +106 -0
  5. package/dist/components/accordion/AccordionItem.svelte.d.ts +13 -0
  6. package/dist/components/accordion/context.d.ts +5 -0
  7. package/dist/components/accordion/context.js +2 -0
  8. package/dist/components/avatar-group/AvatarGroup.svelte +76 -0
  9. package/dist/components/avatar-group/AvatarGroup.svelte.d.ts +15 -0
  10. package/dist/components/drawer/Drawer.svelte +368 -0
  11. package/dist/components/drawer/Drawer.svelte.d.ts +23 -0
  12. package/dist/components/empty-state/EmptyState.svelte +86 -0
  13. package/dist/components/empty-state/EmptyState.svelte.d.ts +16 -0
  14. package/dist/components/entry/Entry.svelte +83 -0
  15. package/dist/components/entry/Entry.svelte.d.ts +15 -0
  16. package/dist/components/footer/Footer.svelte +89 -0
  17. package/dist/components/footer/Footer.svelte.d.ts +13 -0
  18. package/dist/components/footer/FooterColumn.svelte +34 -0
  19. package/dist/components/footer/FooterColumn.svelte.d.ts +10 -0
  20. package/dist/components/mesh-gradient/MeshGradient.svelte +86 -0
  21. package/dist/components/mesh-gradient/MeshGradient.svelte.d.ts +12 -0
  22. package/dist/components/number-stepper/NumberStepper.svelte +82 -0
  23. package/dist/components/number-stepper/NumberStepper.svelte.d.ts +16 -0
  24. package/dist/components/preview-card/PreviewCard.svelte +166 -0
  25. package/dist/components/preview-card/PreviewCard.svelte.d.ts +20 -0
  26. package/dist/components/progress/Progress.svelte +88 -0
  27. package/dist/components/progress/Progress.svelte.d.ts +12 -0
  28. package/dist/components/select/Select.svelte +391 -0
  29. package/dist/components/select/Select.svelte.d.ts +39 -0
  30. package/dist/components/sidebar/Sidebar.svelte +37 -0
  31. package/dist/components/sidebar/SidebarHeader.svelte +26 -2
  32. package/dist/components/tabs/Tabs.svelte +280 -0
  33. package/dist/components/tabs/Tabs.svelte.d.ts +48 -0
  34. package/dist/index.d.ts +15 -0
  35. package/dist/index.js +14 -0
  36. package/package.json +1 -1
@@ -0,0 +1,166 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { LucideIcon } from '@lucide/svelte';
4
+
5
+ type Props = {
6
+ /** Makes the whole card a link. */
7
+ href?: string;
8
+ /** Bold lead-in before the description, followed by a colon. */
9
+ title: string;
10
+ description?: string;
11
+ /** Muted text below, e.g. a date. */
12
+ meta?: string;
13
+ /** Any Lucide icon (import from `svelte-ui/icons`), shown in the top-left corner of the media. */
14
+ icon?: LucideIcon;
15
+ orientation?: 'vertical' | 'horizontal';
16
+ /** Square media that fills the frame, e.g. a MeshGradient or an `<img>`. */
17
+ media: Snippet;
18
+ class?: string;
19
+ };
20
+
21
+ let {
22
+ href,
23
+ title,
24
+ description,
25
+ meta,
26
+ icon: Icon,
27
+ orientation = 'vertical',
28
+ media,
29
+ class: className
30
+ }: Props = $props();
31
+ </script>
32
+
33
+ <svelte:element
34
+ this={href ? 'a' : 'div'}
35
+ {href}
36
+ class={['preview-card', `preview-card--${orientation}`, className]}
37
+ >
38
+ <div class="preview-card-media">
39
+ {@render media()}
40
+ {#if Icon}
41
+ <span class="preview-card-icon"><Icon aria-hidden="true" /></span>
42
+ {/if}
43
+ </div>
44
+ <div class="preview-card-info">
45
+ <p class="preview-card-headline">
46
+ <span class="preview-card-title">{title}{description ? ':' : ''}</span>
47
+ {description}
48
+ </p>
49
+ {#if meta}
50
+ <span class="preview-card-meta">{meta}</span>
51
+ {/if}
52
+ </div>
53
+ </svelte:element>
54
+
55
+ <style>
56
+ .preview-card {
57
+ display: flex;
58
+ gap: 20px;
59
+ color: var(--color-text);
60
+ text-decoration: none;
61
+ border-radius: 14px;
62
+ outline: none;
63
+ }
64
+
65
+ a.preview-card:focus-visible {
66
+ outline: 2px solid var(--color-accent);
67
+ outline-offset: 2px;
68
+ }
69
+
70
+ .preview-card--vertical {
71
+ flex-direction: column;
72
+ }
73
+
74
+ .preview-card--horizontal {
75
+ align-items: center;
76
+ }
77
+
78
+ .preview-card-media {
79
+ position: relative;
80
+ flex-shrink: 0;
81
+ aspect-ratio: 1;
82
+ overflow: hidden;
83
+ border-radius: 14px;
84
+ }
85
+
86
+ .preview-card--horizontal .preview-card-media {
87
+ width: 140px;
88
+ }
89
+
90
+ .preview-card-media > :global(:not(.preview-card-icon)) {
91
+ position: absolute;
92
+ inset: 0;
93
+ width: 100%;
94
+ height: 100%;
95
+ object-fit: cover;
96
+ border-radius: inherit;
97
+ transition: transform 300ms ease;
98
+ }
99
+
100
+ a.preview-card:hover .preview-card-media > :global(:not(.preview-card-icon)) {
101
+ transform: scale(1.05);
102
+ }
103
+
104
+ .preview-card-icon {
105
+ position: absolute;
106
+ top: 0;
107
+ left: 0;
108
+ z-index: 1;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ width: 40px;
113
+ height: 40px;
114
+ color: var(--color-base);
115
+ background-color: var(--color-ink);
116
+ border-radius: 14px 0 0 0;
117
+ }
118
+
119
+ .preview-card-icon :global(svg) {
120
+ width: 20px;
121
+ height: 20px;
122
+ }
123
+
124
+ .preview-card--horizontal .preview-card-icon {
125
+ width: 32px;
126
+ height: 32px;
127
+ }
128
+
129
+ .preview-card--horizontal .preview-card-icon :global(svg) {
130
+ width: 16px;
131
+ height: 16px;
132
+ }
133
+
134
+ .preview-card-info {
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: 8px;
138
+ min-width: 0;
139
+ }
140
+
141
+ .preview-card-headline {
142
+ margin: 0;
143
+ font-size: 16px;
144
+ }
145
+
146
+ .preview-card-title {
147
+ font-weight: 600;
148
+ }
149
+
150
+ .preview-card-meta {
151
+ font-size: 14px;
152
+ color: var(--color-text-muted);
153
+ }
154
+
155
+ @media (min-width: 640px) {
156
+ .preview-card--horizontal .preview-card-media {
157
+ width: 168px;
158
+ }
159
+ }
160
+
161
+ @media (prefers-reduced-motion: reduce) {
162
+ .preview-card-media > :global(*) {
163
+ transition: none;
164
+ }
165
+ }
166
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { LucideIcon } from '@lucide/svelte';
3
+ type Props = {
4
+ /** Makes the whole card a link. */
5
+ href?: string;
6
+ /** Bold lead-in before the description, followed by a colon. */
7
+ title: string;
8
+ description?: string;
9
+ /** Muted text below, e.g. a date. */
10
+ meta?: string;
11
+ /** Any Lucide icon (import from `svelte-ui/icons`), shown in the top-left corner of the media. */
12
+ icon?: LucideIcon;
13
+ orientation?: 'vertical' | 'horizontal';
14
+ /** Square media that fills the frame, e.g. a MeshGradient or an `<img>`. */
15
+ media: Snippet;
16
+ class?: string;
17
+ };
18
+ declare const PreviewCard: import("svelte").Component<Props, {}, "">;
19
+ type PreviewCard = ReturnType<typeof PreviewCard>;
20
+ export default PreviewCard;
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
5
+ /** Progress between 0 and `max`. Omit while the amount is unknown: the bar then runs back and forth. */
6
+ value?: number;
7
+ max?: number;
8
+ size?: 'sm' | 'md';
9
+ /** Accessible name, e.g. what is loading. */
10
+ label?: string;
11
+ };
12
+
13
+ let { value, max = 100, size = 'md', label, class: className, ...rest }: Props = $props();
14
+
15
+ let fraction = $derived(value === undefined ? undefined : Math.min(Math.max(value / max, 0), 1));
16
+ </script>
17
+
18
+ <div
19
+ {...rest}
20
+ role="progressbar"
21
+ aria-label={label}
22
+ aria-valuemin={0}
23
+ aria-valuemax={max}
24
+ aria-valuenow={fraction === undefined ? undefined : fraction * max}
25
+ class={[
26
+ 'progress',
27
+ `progress--${size}`,
28
+ { 'progress--indeterminate': fraction === undefined },
29
+ className
30
+ ]}
31
+ >
32
+ <div
33
+ class="progress-bar"
34
+ style:width={fraction === undefined ? undefined : `${fraction * 100}%`}
35
+ ></div>
36
+ </div>
37
+
38
+ <style>
39
+ .progress {
40
+ width: 100%;
41
+ overflow: hidden;
42
+ background-color: var(--color-surface);
43
+ border-radius: 999px;
44
+ }
45
+
46
+ .progress--sm {
47
+ height: 4px;
48
+ }
49
+
50
+ .progress--md {
51
+ height: 8px;
52
+ }
53
+
54
+ .progress-bar {
55
+ height: 100%;
56
+ background-color: var(--color-accent);
57
+ border-radius: inherit;
58
+ transition: width 300ms ease;
59
+ }
60
+
61
+ .progress--indeterminate .progress-bar {
62
+ width: 40%;
63
+ animation: progress-slide 1.4s ease-in-out infinite;
64
+ }
65
+
66
+ @keyframes progress-slide {
67
+ from {
68
+ translate: -100% 0;
69
+ }
70
+
71
+ to {
72
+ translate: 250% 0;
73
+ }
74
+ }
75
+
76
+ @media (prefers-reduced-motion: reduce) {
77
+ .progress-bar {
78
+ transition: none;
79
+ }
80
+
81
+ /* A full, dimmed bar instead of the movement */
82
+ .progress--indeterminate .progress-bar {
83
+ width: 100%;
84
+ opacity: 0.4;
85
+ animation: none;
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
3
+ /** Progress between 0 and `max`. Omit while the amount is unknown: the bar then runs back and forth. */
4
+ value?: number;
5
+ max?: number;
6
+ size?: 'sm' | 'md';
7
+ /** Accessible name, e.g. what is loading. */
8
+ label?: string;
9
+ };
10
+ declare const Progress: import("svelte").Component<Props, {}, "">;
11
+ type Progress = ReturnType<typeof Progress>;
12
+ export default Progress;
@@ -0,0 +1,391 @@
1
+ <script lang="ts" module>
2
+ export type SelectOption<T extends string | number | null = string> = {
3
+ value: T;
4
+ label: string;
5
+ disabled?: boolean;
6
+ };
7
+ </script>
8
+
9
+ <script lang="ts" generics="T extends string | number | null">
10
+ import { tick } from 'svelte';
11
+ import { scale } from 'svelte/transition';
12
+ import type { Attachment } from 'svelte/attachments';
13
+ import type { HTMLButtonAttributes } from 'svelte/elements';
14
+ import { Check, ChevronsUpDown } from '@lucide/svelte';
15
+
16
+ type Props = Omit<
17
+ HTMLButtonAttributes,
18
+ 'value' | 'children' | 'onchange' | 'type' | 'role' | 'name'
19
+ > & {
20
+ /** Value of the chosen option. Bindable. */
21
+ value?: T;
22
+ options: SelectOption<T>[];
23
+ /** Shown while no option is chosen. */
24
+ placeholder?: string;
25
+ /** Form field name; submits the value through a hidden input. */
26
+ name?: string;
27
+ onchange?: (value: T) => void;
28
+ };
29
+
30
+ let {
31
+ value = $bindable(),
32
+ options,
33
+ placeholder = 'Select',
34
+ name,
35
+ disabled = false,
36
+ class: className,
37
+ onchange,
38
+ ...rest
39
+ }: Props = $props();
40
+
41
+ const id = $props.id();
42
+ const gap = 4;
43
+ const edge = 8;
44
+ const maxHeight = 320;
45
+
46
+ let open = $state(false);
47
+ let wrapperEl: HTMLSpanElement;
48
+ let triggerEl: HTMLButtonElement;
49
+ let listEl: HTMLDivElement | undefined = $state();
50
+ let placement: 'top' | 'bottom' = $state('bottom');
51
+ let position: { top?: number; bottom?: number; left: number; width: number; height: number } =
52
+ $state({ left: 0, width: 0, height: maxHeight });
53
+
54
+ let current = $derived(options.find((option) => option.value === value));
55
+
56
+ function items() {
57
+ return Array.from(
58
+ listEl?.querySelectorAll<HTMLElement>('[role="option"]:not(:disabled)') ?? []
59
+ );
60
+ }
61
+
62
+ async function show(focus: 'first' | 'last' = 'first') {
63
+ open = true;
64
+ await tick();
65
+ const list = items();
66
+ const selected = list.find((item) => item.getAttribute('aria-selected') === 'true');
67
+ (selected ?? (focus === 'first' ? list[0] : list.at(-1)))?.focus();
68
+ }
69
+
70
+ function close({ restoreFocus = false } = {}) {
71
+ open = false;
72
+ if (restoreFocus) triggerEl.focus();
73
+ }
74
+
75
+ function choose(option: SelectOption<T>) {
76
+ value = option.value;
77
+ onchange?.(option.value);
78
+ close({ restoreFocus: true });
79
+ }
80
+
81
+ /* The list sits in the top layer so dialogs and scroll containers cannot clip it */
82
+ function place() {
83
+ if (!listEl) return;
84
+ const rect = triggerEl.getBoundingClientRect();
85
+ const below = window.innerHeight - rect.bottom - gap - edge;
86
+ const above = rect.top - gap - edge;
87
+ const wanted = Math.min(listEl.scrollHeight + 2, maxHeight);
88
+ placement = below < wanted && above > below ? 'top' : 'bottom';
89
+ const left = Math.max(edge, Math.min(rect.left, window.innerWidth - listEl.offsetWidth - edge));
90
+ position =
91
+ placement === 'top'
92
+ ? {
93
+ bottom: window.innerHeight - rect.top + gap,
94
+ left,
95
+ width: rect.width,
96
+ height: Math.min(maxHeight, above)
97
+ }
98
+ : { top: rect.bottom + gap, left, width: rect.width, height: Math.min(maxHeight, below) };
99
+ }
100
+
101
+ const popover: Attachment<HTMLDivElement> = (node) => {
102
+ node.showPopover();
103
+ place();
104
+ };
105
+
106
+ /* Typing jumps to the option that starts with the typed letters */
107
+ let typed = '';
108
+ let typedTimer: ReturnType<typeof setTimeout> | undefined;
109
+
110
+ function typeahead(key: string) {
111
+ clearTimeout(typedTimer);
112
+ typed += key.toLowerCase();
113
+ typedTimer = setTimeout(() => (typed = ''), 500);
114
+ items()
115
+ .find((item) => item.textContent?.trim().toLowerCase().startsWith(typed))
116
+ ?.focus();
117
+ }
118
+
119
+ function handleListKeydown(event: KeyboardEvent) {
120
+ if (event.key === 'Escape') {
121
+ // Only the list closes, not a Dialog or Drawer around it
122
+ event.preventDefault();
123
+ event.stopPropagation();
124
+ close({ restoreFocus: true });
125
+ return;
126
+ }
127
+ if (event.key === 'Tab') {
128
+ event.preventDefault();
129
+ close({ restoreFocus: true });
130
+ return;
131
+ }
132
+
133
+ const list = items();
134
+ const index = list.indexOf(document.activeElement as HTMLElement);
135
+ const last = list.length - 1;
136
+ const target = {
137
+ ArrowDown: index === last ? 0 : index + 1,
138
+ ArrowUp: index <= 0 ? last : index - 1,
139
+ Home: 0,
140
+ End: last
141
+ }[event.key];
142
+
143
+ if (target !== undefined) {
144
+ event.preventDefault();
145
+ list[target]?.focus();
146
+ } else if (event.key.length === 1 && event.key !== ' ' && !event.metaKey && !event.ctrlKey) {
147
+ typeahead(event.key);
148
+ }
149
+ }
150
+
151
+ $effect(() => {
152
+ if (!open) return;
153
+
154
+ function handlePointerDown(event: PointerEvent) {
155
+ if (!wrapperEl.contains(event.target as Node)) close();
156
+ }
157
+
158
+ window.addEventListener('pointerdown', handlePointerDown);
159
+ window.addEventListener('scroll', place, true);
160
+ window.addEventListener('resize', place);
161
+
162
+ return () => {
163
+ window.removeEventListener('pointerdown', handlePointerDown);
164
+ window.removeEventListener('scroll', place, true);
165
+ window.removeEventListener('resize', place);
166
+ };
167
+ });
168
+ </script>
169
+
170
+ <span class={['select', className]} bind:this={wrapperEl}>
171
+ <button
172
+ {...rest}
173
+ bind:this={triggerEl}
174
+ type="button"
175
+ role="combobox"
176
+ aria-haspopup="listbox"
177
+ aria-expanded={open}
178
+ aria-controls="{id}-listbox"
179
+ {disabled}
180
+ class="select-trigger"
181
+ onclick={(event) => {
182
+ rest.onclick?.(event);
183
+ if (open) close();
184
+ else show();
185
+ }}
186
+ onkeydown={(event) => {
187
+ rest.onkeydown?.(event);
188
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
189
+ event.preventDefault();
190
+ show(event.key === 'ArrowDown' ? 'first' : 'last');
191
+ }}
192
+ >
193
+ <span class={['select-value', { 'select-value--placeholder': !current }]}>
194
+ {current?.label ?? placeholder}
195
+ </span>
196
+ <ChevronsUpDown class="select-chevron" aria-hidden="true" />
197
+ </button>
198
+ {#if name}<input type="hidden" {name} value={value ?? ''} />{/if}
199
+
200
+ {#if open}
201
+ <div
202
+ id="{id}-listbox"
203
+ role="listbox"
204
+ tabindex="-1"
205
+ popover="manual"
206
+ class={['select-list', `select-list--${placement}`]}
207
+ style:top={position.top === undefined ? undefined : `${position.top}px`}
208
+ style:bottom={position.bottom === undefined ? undefined : `${position.bottom}px`}
209
+ style:left="{position.left}px"
210
+ style:min-width="{position.width}px"
211
+ style:max-height="{position.height}px"
212
+ bind:this={listEl}
213
+ onkeydown={handleListKeydown}
214
+ onfocusout={(event) => {
215
+ if (!wrapperEl.contains(event.relatedTarget as Node | null)) close();
216
+ }}
217
+ {@attach popover}
218
+ transition:scale={{ duration: 140, start: 0.95 }}
219
+ >
220
+ {#each options as option (option.value)}
221
+ {@const selected = option.value === value}
222
+ <button
223
+ type="button"
224
+ role="option"
225
+ tabindex="-1"
226
+ aria-selected={selected}
227
+ disabled={option.disabled}
228
+ class={['select-option', { 'select-option--selected': selected }]}
229
+ onclick={() => choose(option)}
230
+ >
231
+ <span class="select-option-label">{option.label}</span>
232
+ <span class="select-option-check">
233
+ {#if selected}<Check aria-hidden="true" />{/if}
234
+ </span>
235
+ </button>
236
+ {/each}
237
+ </div>
238
+ {/if}
239
+ </span>
240
+
241
+ <style>
242
+ .select {
243
+ display: flex;
244
+ width: 100%;
245
+ min-width: 0;
246
+ }
247
+
248
+ /* Looks like an Input */
249
+ .select-trigger {
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: space-between;
253
+ gap: 8px;
254
+ box-sizing: border-box;
255
+ width: 100%;
256
+ min-width: 0;
257
+ height: 32px;
258
+ padding: 0 8px 0 10px;
259
+ font-family: inherit;
260
+ font-size: 13px;
261
+ color: var(--color-text);
262
+ text-align: left;
263
+ background-color: transparent;
264
+ border: 1px solid var(--color-border-strong);
265
+ border-radius: 10px;
266
+ outline: none;
267
+ cursor: pointer;
268
+ transition: border-color 200ms ease;
269
+ }
270
+
271
+ .select-trigger:focus-visible,
272
+ .select-trigger[aria-expanded='true'] {
273
+ border-color: var(--color-accent);
274
+ outline: 1px solid var(--color-accent);
275
+ }
276
+
277
+ .select-trigger[aria-invalid='true'] {
278
+ border-color: var(--color-destructive);
279
+ }
280
+
281
+ .select-trigger[aria-invalid='true']:is(:focus-visible, [aria-expanded='true']) {
282
+ outline-color: var(--color-destructive);
283
+ }
284
+
285
+ .select-trigger:disabled {
286
+ cursor: not-allowed;
287
+ opacity: 0.5;
288
+ }
289
+
290
+ .select-value {
291
+ overflow: hidden;
292
+ text-overflow: ellipsis;
293
+ white-space: nowrap;
294
+ }
295
+
296
+ .select-value--placeholder {
297
+ color: var(--color-text-faint);
298
+ }
299
+
300
+ .select-trigger :global(.select-chevron) {
301
+ width: 14px;
302
+ height: 14px;
303
+ flex-shrink: 0;
304
+ color: var(--color-text-muted);
305
+ }
306
+
307
+ .select-list {
308
+ position: fixed;
309
+ inset: auto;
310
+ flex-direction: column;
311
+ gap: 2px;
312
+ box-sizing: border-box;
313
+ max-width: calc(100vw - 16px);
314
+ margin: 0;
315
+ padding: 4px;
316
+ overflow-y: auto;
317
+ overscroll-behavior: contain;
318
+ background-color: var(--color-bg);
319
+ color: var(--color-text);
320
+ border: 1px solid var(--color-border);
321
+ border-radius: 10px;
322
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
323
+ outline: none;
324
+ user-select: none;
325
+ }
326
+
327
+ .select-list:popover-open {
328
+ display: flex;
329
+ }
330
+
331
+ .select-list--bottom {
332
+ transform-origin: top left;
333
+ }
334
+
335
+ .select-list--top {
336
+ transform-origin: bottom left;
337
+ }
338
+
339
+ .select-option {
340
+ display: flex;
341
+ flex-shrink: 0;
342
+ align-items: center;
343
+ gap: 10px;
344
+ width: 100%;
345
+ padding: 8px 10px;
346
+ font: inherit;
347
+ font-size: 13px;
348
+ color: var(--color-text);
349
+ text-align: left;
350
+ background-color: transparent;
351
+ border: none;
352
+ border-radius: 6px;
353
+ outline: none;
354
+ cursor: pointer;
355
+ transition: background-color 150ms ease;
356
+ }
357
+
358
+ /* Plain :focus, so the current option also shows after opening with the mouse */
359
+ .select-option:hover:not(:disabled),
360
+ .select-option:focus {
361
+ background-color: var(--color-surface);
362
+ }
363
+
364
+ .select-option:disabled {
365
+ cursor: not-allowed;
366
+ opacity: 0.5;
367
+ }
368
+
369
+ .select-option--selected {
370
+ font-weight: 600;
371
+ }
372
+
373
+ .select-option-label {
374
+ flex-grow: 1;
375
+ overflow: hidden;
376
+ text-overflow: ellipsis;
377
+ white-space: nowrap;
378
+ }
379
+
380
+ .select-option-check {
381
+ display: flex;
382
+ width: 14px;
383
+ height: 14px;
384
+ flex-shrink: 0;
385
+ }
386
+
387
+ .select-option-check :global(svg) {
388
+ width: 14px;
389
+ height: 14px;
390
+ }
391
+ </style>
@@ -0,0 +1,39 @@
1
+ export type SelectOption<T extends string | number | null = string> = {
2
+ value: T;
3
+ label: string;
4
+ disabled?: boolean;
5
+ };
6
+ import type { HTMLButtonAttributes } from 'svelte/elements';
7
+ declare function $$render<T extends string | number | null>(): {
8
+ props: Omit<HTMLButtonAttributes, "children" | "role" | "onchange" | "name" | "type" | "value"> & {
9
+ /** Value of the chosen option. Bindable. */
10
+ value?: T;
11
+ options: SelectOption<T>[];
12
+ /** Shown while no option is chosen. */
13
+ placeholder?: string;
14
+ /** Form field name; submits the value through a hidden input. */
15
+ name?: string;
16
+ onchange?: (value: T) => void;
17
+ };
18
+ exports: {};
19
+ bindings: "value";
20
+ slots: {};
21
+ events: {};
22
+ };
23
+ declare class __sveltets_Render<T extends string | number | null> {
24
+ props(): ReturnType<typeof $$render<T>>['props'];
25
+ events(): ReturnType<typeof $$render<T>>['events'];
26
+ slots(): ReturnType<typeof $$render<T>>['slots'];
27
+ bindings(): "value";
28
+ exports(): {};
29
+ }
30
+ interface $$IsomorphicComponent {
31
+ new <T extends string | number | null>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
32
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
33
+ } & ReturnType<__sveltets_Render<T>['exports']>;
34
+ <T extends string | number | null>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
35
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
36
+ }
37
+ declare const Select: $$IsomorphicComponent;
38
+ type Select<T extends string | number | null> = InstanceType<typeof Select<T>>;
39
+ export default Select;