@noahbaumgartner/svelte-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 (149) hide show
  1. package/README.md +65 -0
  2. package/dist/components/alert-dialog/AlertDialog.svelte +161 -0
  3. package/dist/components/alert-dialog/AlertDialog.svelte.d.ts +20 -0
  4. package/dist/components/avatar/Avatar.svelte +85 -0
  5. package/dist/components/avatar/Avatar.svelte.d.ts +12 -0
  6. package/dist/components/badge/Badge.svelte +49 -0
  7. package/dist/components/badge/Badge.svelte.d.ts +12 -0
  8. package/dist/components/breadcrumb/Breadcrumb.svelte +37 -0
  9. package/dist/components/breadcrumb/Breadcrumb.svelte.d.ts +9 -0
  10. package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte +32 -0
  11. package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte.d.ts +10 -0
  12. package/dist/components/breadcrumb/BreadcrumbItem.svelte +71 -0
  13. package/dist/components/breadcrumb/BreadcrumbItem.svelte.d.ts +12 -0
  14. package/dist/components/breadcrumb/BreadcrumbSeparator.svelte +18 -0
  15. package/dist/components/breadcrumb/BreadcrumbSeparator.svelte.d.ts +18 -0
  16. package/dist/components/button/Button.svelte +247 -0
  17. package/dist/components/button/Button.svelte.d.ts +24 -0
  18. package/dist/components/button-group/ButtonGroup.svelte +89 -0
  19. package/dist/components/button-group/ButtonGroup.svelte.d.ts +12 -0
  20. package/dist/components/calendar/Calendar.svelte +401 -0
  21. package/dist/components/calendar/Calendar.svelte.d.ts +17 -0
  22. package/dist/components/calendar/dates.d.ts +12 -0
  23. package/dist/components/calendar/dates.js +48 -0
  24. package/dist/components/card/Card.svelte +55 -0
  25. package/dist/components/card/Card.svelte.d.ts +10 -0
  26. package/dist/components/card/CardAction.svelte +24 -0
  27. package/dist/components/card/CardAction.svelte.d.ts +9 -0
  28. package/dist/components/card/CardContent.svelte +20 -0
  29. package/dist/components/card/CardContent.svelte.d.ts +8 -0
  30. package/dist/components/card/CardDescription.svelte +23 -0
  31. package/dist/components/card/CardDescription.svelte.d.ts +8 -0
  32. package/dist/components/card/CardFooter.svelte +28 -0
  33. package/dist/components/card/CardFooter.svelte.d.ts +9 -0
  34. package/dist/components/card/CardHeader.svelte +29 -0
  35. package/dist/components/card/CardHeader.svelte.d.ts +9 -0
  36. package/dist/components/card/CardTitle.svelte +23 -0
  37. package/dist/components/card/CardTitle.svelte.d.ts +8 -0
  38. package/dist/components/checkbox/Checkbox.svelte +93 -0
  39. package/dist/components/checkbox/Checkbox.svelte.d.ts +5 -0
  40. package/dist/components/color-picker/ColorPicker.svelte +233 -0
  41. package/dist/components/color-picker/ColorPicker.svelte.d.ts +10 -0
  42. package/dist/components/context-menu/ContextMenu.svelte +201 -0
  43. package/dist/components/context-menu/ContextMenu.svelte.d.ts +14 -0
  44. package/dist/components/context-menu/ContextMenuItem.svelte +105 -0
  45. package/dist/components/context-menu/ContextMenuItem.svelte.d.ts +14 -0
  46. package/dist/components/context-menu/context.d.ts +14 -0
  47. package/dist/components/context-menu/context.js +2 -0
  48. package/dist/components/dialog/Dialog.svelte +225 -0
  49. package/dist/components/dialog/Dialog.svelte.d.ts +22 -0
  50. package/dist/components/field/Field.svelte +78 -0
  51. package/dist/components/field/Field.svelte.d.ts +11 -0
  52. package/dist/components/field/FieldContent.svelte +25 -0
  53. package/dist/components/field/FieldContent.svelte.d.ts +9 -0
  54. package/dist/components/field/FieldDescription.svelte +34 -0
  55. package/dist/components/field/FieldDescription.svelte.d.ts +9 -0
  56. package/dist/components/field/FieldError.svelte +45 -0
  57. package/dist/components/field/FieldError.svelte.d.ts +11 -0
  58. package/dist/components/field/FieldGroup.svelte +30 -0
  59. package/dist/components/field/FieldGroup.svelte.d.ts +9 -0
  60. package/dist/components/field/FieldLegend.svelte +34 -0
  61. package/dist/components/field/FieldLegend.svelte.d.ts +10 -0
  62. package/dist/components/field/FieldSeparator.svelte +31 -0
  63. package/dist/components/field/FieldSeparator.svelte.d.ts +9 -0
  64. package/dist/components/field/FieldSet.svelte +33 -0
  65. package/dist/components/field/FieldSet.svelte.d.ts +9 -0
  66. package/dist/components/input/Input.svelte +459 -0
  67. package/dist/components/input/Input.svelte.d.ts +10 -0
  68. package/dist/components/input-group/InputGroup.svelte +88 -0
  69. package/dist/components/input-group/InputGroup.svelte.d.ts +9 -0
  70. package/dist/components/input-group/InputGroupAddon.svelte +88 -0
  71. package/dist/components/input-group/InputGroupAddon.svelte.d.ts +11 -0
  72. package/dist/components/kbd/Kbd.svelte +46 -0
  73. package/dist/components/kbd/Kbd.svelte.d.ts +8 -0
  74. package/dist/components/kbd/KbdGroup.svelte +27 -0
  75. package/dist/components/kbd/KbdGroup.svelte.d.ts +8 -0
  76. package/dist/components/label/Label.svelte +48 -0
  77. package/dist/components/label/Label.svelte.d.ts +9 -0
  78. package/dist/components/link/Link.svelte +40 -0
  79. package/dist/components/link/Link.svelte.d.ts +10 -0
  80. package/dist/components/navbar/Navbar.svelte +266 -0
  81. package/dist/components/navbar/Navbar.svelte.d.ts +26 -0
  82. package/dist/components/navigation-menu/NavigationMenu.svelte +95 -0
  83. package/dist/components/navigation-menu/NavigationMenu.svelte.d.ts +12 -0
  84. package/dist/components/navigation-menu/NavigationMenuItem.svelte +257 -0
  85. package/dist/components/navigation-menu/NavigationMenuItem.svelte.d.ts +15 -0
  86. package/dist/components/navigation-menu/NavigationMenuLink.svelte +91 -0
  87. package/dist/components/navigation-menu/NavigationMenuLink.svelte.d.ts +16 -0
  88. package/dist/components/navigation-menu/context.d.ts +15 -0
  89. package/dist/components/navigation-menu/context.js +2 -0
  90. package/dist/components/popover/Popover.svelte +295 -0
  91. package/dist/components/popover/Popover.svelte.d.ts +34 -0
  92. package/dist/components/radio-group/RadioGroup.svelte +86 -0
  93. package/dist/components/radio-group/RadioGroup.svelte.d.ts +16 -0
  94. package/dist/components/radio-group/RadioGroupItem.svelte +95 -0
  95. package/dist/components/radio-group/RadioGroupItem.svelte.d.ts +7 -0
  96. package/dist/components/radio-group/context.d.ts +9 -0
  97. package/dist/components/radio-group/context.js +2 -0
  98. package/dist/components/separator/Separator.svelte +41 -0
  99. package/dist/components/separator/Separator.svelte.d.ts +10 -0
  100. package/dist/components/sidebar/Sidebar.svelte +180 -0
  101. package/dist/components/sidebar/Sidebar.svelte.d.ts +14 -0
  102. package/dist/components/sidebar/SidebarContent.svelte +30 -0
  103. package/dist/components/sidebar/SidebarContent.svelte.d.ts +9 -0
  104. package/dist/components/sidebar/SidebarFooter.svelte +23 -0
  105. package/dist/components/sidebar/SidebarFooter.svelte.d.ts +8 -0
  106. package/dist/components/sidebar/SidebarGroup.svelte +55 -0
  107. package/dist/components/sidebar/SidebarGroup.svelte.d.ts +11 -0
  108. package/dist/components/sidebar/SidebarHeader.svelte +23 -0
  109. package/dist/components/sidebar/SidebarHeader.svelte.d.ts +8 -0
  110. package/dist/components/sidebar/SidebarInset.svelte +25 -0
  111. package/dist/components/sidebar/SidebarInset.svelte.d.ts +9 -0
  112. package/dist/components/sidebar/SidebarMenu.svelte +27 -0
  113. package/dist/components/sidebar/SidebarMenu.svelte.d.ts +9 -0
  114. package/dist/components/sidebar/SidebarMenuItem.svelte +221 -0
  115. package/dist/components/sidebar/SidebarMenuItem.svelte.d.ts +23 -0
  116. package/dist/components/sidebar/SidebarProvider.svelte +62 -0
  117. package/dist/components/sidebar/SidebarProvider.svelte.d.ts +11 -0
  118. package/dist/components/sidebar/SidebarSeparator.svelte +5 -0
  119. package/dist/components/sidebar/SidebarSeparator.svelte.d.ts +18 -0
  120. package/dist/components/sidebar/SidebarTrigger.svelte +24 -0
  121. package/dist/components/sidebar/SidebarTrigger.svelte.d.ts +7 -0
  122. package/dist/components/sidebar/context.d.ts +15 -0
  123. package/dist/components/sidebar/context.js +2 -0
  124. package/dist/components/skeleton/Skeleton.svelte +30 -0
  125. package/dist/components/skeleton/Skeleton.svelte.d.ts +5 -0
  126. package/dist/components/spinner/Spinner.svelte +72 -0
  127. package/dist/components/spinner/Spinner.svelte.d.ts +9 -0
  128. package/dist/components/switch/Switch.svelte +88 -0
  129. package/dist/components/switch/Switch.svelte.d.ts +5 -0
  130. package/dist/components/table/Table.svelte +276 -0
  131. package/dist/components/table/Table.svelte.d.ts +60 -0
  132. package/dist/components/textarea/Textarea.svelte +57 -0
  133. package/dist/components/textarea/Textarea.svelte.d.ts +4 -0
  134. package/dist/components/time-picker/TimePicker.svelte +162 -0
  135. package/dist/components/time-picker/TimePicker.svelte.d.ts +11 -0
  136. package/dist/components/toaster/Toaster.svelte +151 -0
  137. package/dist/components/toaster/Toaster.svelte.d.ts +7 -0
  138. package/dist/components/toaster/toast.svelte.d.ts +28 -0
  139. package/dist/components/toaster/toast.svelte.js +22 -0
  140. package/dist/components/toggle/Toggle.svelte +31 -0
  141. package/dist/components/toggle/Toggle.svelte.d.ts +8 -0
  142. package/dist/components/tooltip/Tooltip.svelte +224 -0
  143. package/dist/components/tooltip/Tooltip.svelte.d.ts +27 -0
  144. package/dist/icons.d.ts +2 -0
  145. package/dist/icons.js +4 -0
  146. package/dist/index.d.ts +73 -0
  147. package/dist/index.js +66 -0
  148. package/dist/styles/tokens.css +50 -0
  149. package/package.json +88 -0
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLLegendElement>, 'children'> & {
4
+ /** `label` matches a Label, for a group of checkboxes or radios. */
5
+ variant?: 'legend' | 'label';
6
+ children: Snippet;
7
+ };
8
+ declare const FieldLegend: import("svelte").Component<Props, {}, "">;
9
+ type FieldLegend = ReturnType<typeof FieldLegend>;
10
+ export default FieldLegend;
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import Separator from '../separator/Separator.svelte';
5
+
6
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
7
+ /** Optional text in the middle of the line, e.g. "Or continue with". */
8
+ children?: Snippet;
9
+ };
10
+
11
+ let { class: className, children, ...rest }: Props = $props();
12
+ </script>
13
+
14
+ <div {...rest} role="separator" class={['field-separator', className]}>
15
+ <Separator style="flex: 1;" />
16
+ {#if children}
17
+ <span class="field-separator-content">{@render children()}</span>
18
+ <Separator style="flex: 1;" />
19
+ {/if}
20
+ </div>
21
+
22
+ <style>
23
+ .field-separator {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: 8px;
27
+ min-height: 20px;
28
+ font-size: 13px;
29
+ color: var(--color-text-muted);
30
+ }
31
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /** Optional text in the middle of the line, e.g. "Or continue with". */
5
+ children?: Snippet;
6
+ };
7
+ declare const FieldSeparator: import("svelte").Component<Props, {}, "">;
8
+ type FieldSeparator = ReturnType<typeof FieldSeparator>;
9
+ export default FieldSeparator;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLFieldsetAttributes } from 'svelte/elements';
4
+
5
+ type Props = Omit<HTMLFieldsetAttributes, 'children'> & {
6
+ /** A FieldLegend, optionally a FieldDescription, then a FieldGroup. `disabled` disables every control inside. */
7
+ children: Snippet;
8
+ };
9
+
10
+ let { class: className, children, ...rest }: Props = $props();
11
+ </script>
12
+
13
+ <fieldset {...rest} class={['field-set', className]}>
14
+ {@render children()}
15
+ </fieldset>
16
+
17
+ <style>
18
+ .field-set {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 24px;
22
+ min-width: 0;
23
+ margin: 0;
24
+ padding: 0;
25
+ border: none;
26
+ color: var(--color-text);
27
+ }
28
+
29
+ /* The legend sits outside the flex layout, so only its own margin spaces it */
30
+ .field-set > :global(.field-legend + .field-description) {
31
+ margin-top: -8px;
32
+ }
33
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLFieldsetAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLFieldsetAttributes, 'children'> & {
4
+ /** A FieldLegend, optionally a FieldDescription, then a FieldGroup. `disabled` disables every control inside. */
5
+ children: Snippet;
6
+ };
7
+ declare const FieldSet: import("svelte").Component<Props, {}, "">;
8
+ type FieldSet = ReturnType<typeof FieldSet>;
9
+ export default FieldSet;
@@ -0,0 +1,459 @@
1
+ <script lang="ts" module>
2
+ /** Checkbox and radio have their own components; buttons are Button; range and hidden are not text fields. */
3
+ export type InputType =
4
+ | 'text'
5
+ | 'email'
6
+ | 'password'
7
+ | 'tel'
8
+ | 'url'
9
+ | 'search'
10
+ | 'number'
11
+ | 'date'
12
+ | 'time'
13
+ | 'datetime-local'
14
+ | 'month'
15
+ | 'week'
16
+ | 'color'
17
+ | 'file';
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import { tick } from 'svelte';
22
+ import { scale } from 'svelte/transition';
23
+ import type { HTMLInputAttributes } from 'svelte/elements';
24
+ import { Calendar as CalendarIcon, ChevronDown, ChevronUp, Clock } from '@lucide/svelte';
25
+ import Calendar from '../calendar/Calendar.svelte';
26
+ import ColorPicker from '../color-picker/ColorPicker.svelte';
27
+ import TimePicker from '../time-picker/TimePicker.svelte';
28
+ import { formatDate } from '../calendar/dates.js';
29
+
30
+ type Props = Omit<HTMLInputAttributes, 'type'> & {
31
+ /** Number gets a stepper; date, time and color types open this library's pickers. */
32
+ type?: InputType;
33
+ };
34
+
35
+ let {
36
+ value = $bindable(),
37
+ type = 'text',
38
+ class: className,
39
+ style,
40
+ disabled,
41
+ readonly,
42
+ ...rest
43
+ }: Props = $props();
44
+
45
+ const pickers = ['date', 'time', 'datetime-local', 'month', 'week'];
46
+
47
+ const id = $props.id();
48
+ let input: HTMLInputElement | undefined = $state();
49
+ let wrapperEl: HTMLElement | undefined = $state();
50
+ let popoverEl: HTMLElement | undefined = $state();
51
+ let open = $state(false);
52
+ let side: 'top' | 'bottom' = $state('bottom');
53
+
54
+ let picker = $derived(pickers.includes(type));
55
+ let inactive = $derived(disabled || readonly);
56
+ let text = $derived(String(value ?? ''));
57
+ let minuteStep = $derived(Math.max(1, Math.round(Number(rest.step) / 60) || 1));
58
+
59
+ /* Updates the native input and fires the events typing would, so bind:value and handlers follow */
60
+ function set(next: string) {
61
+ if (!input) return;
62
+ input.value = next;
63
+ input.dispatchEvent(new Event('input', { bubbles: true }));
64
+ input.dispatchEvent(new Event('change', { bubbles: true }));
65
+ }
66
+
67
+ function step(direction: 1 | -1) {
68
+ if (!input) return;
69
+ if (direction === 1) input.stepUp();
70
+ else input.stepDown();
71
+ set(input.value);
72
+ input.focus();
73
+ }
74
+
75
+ async function show() {
76
+ open = true;
77
+ await tick();
78
+ popoverEl?.querySelector<HTMLElement>('[tabindex="0"]')?.focus();
79
+ }
80
+
81
+ function close(restoreFocus = false) {
82
+ open = false;
83
+ if (restoreFocus) input?.focus();
84
+ }
85
+
86
+ function toggle() {
87
+ if (open) close(true);
88
+ else show();
89
+ }
90
+
91
+ function handleColorClick(event: MouseEvent & { currentTarget: HTMLInputElement }) {
92
+ rest.onclick?.(event);
93
+ event.preventDefault();
94
+ if (!inactive) toggle();
95
+ }
96
+
97
+ $effect(() => {
98
+ if (!open) return;
99
+
100
+ tick().then(() => {
101
+ if (!popoverEl || !wrapperEl) return;
102
+ const rect = wrapperEl.getBoundingClientRect();
103
+ const height = popoverEl.offsetHeight;
104
+ side =
105
+ window.innerHeight - rect.bottom < height + 8 && rect.top > height + 8 ? 'top' : 'bottom';
106
+ });
107
+
108
+ function handlePointerDown(event: PointerEvent) {
109
+ if (!wrapperEl?.contains(event.target as Node)) close();
110
+ }
111
+
112
+ function handleKeydown(event: KeyboardEvent) {
113
+ if (event.key === 'Escape') close(true);
114
+ }
115
+
116
+ function handleFocusIn(event: FocusEvent) {
117
+ if (!wrapperEl?.contains(event.target as Node)) close();
118
+ }
119
+
120
+ window.addEventListener('pointerdown', handlePointerDown);
121
+ window.addEventListener('keydown', handleKeydown);
122
+ window.addEventListener('focusin', handleFocusIn);
123
+
124
+ return () => {
125
+ window.removeEventListener('pointerdown', handlePointerDown);
126
+ window.removeEventListener('keydown', handleKeydown);
127
+ window.removeEventListener('focusin', handleFocusIn);
128
+ };
129
+ });
130
+ </script>
131
+
132
+ {#if type === 'number' || picker || type === 'color'}
133
+ <span
134
+ class={['input-wrapper', { 'input-wrapper--color': type === 'color' }, className]}
135
+ {style}
136
+ bind:this={wrapperEl}
137
+ >
138
+ <input
139
+ {...rest}
140
+ {type}
141
+ bind:value
142
+ bind:this={input}
143
+ {disabled}
144
+ {readonly}
145
+ class={['input', { 'input--with-controls': type !== 'color' }]}
146
+ onclick={type === 'color' ? handleColorClick : rest.onclick}
147
+ aria-haspopup={type === 'color' ? 'dialog' : undefined}
148
+ aria-expanded={type === 'color' ? open : undefined}
149
+ />
150
+ {#if type !== 'color'}
151
+ <span class="input-controls">
152
+ {#if type === 'number'}
153
+ <button
154
+ type="button"
155
+ tabindex="-1"
156
+ aria-label="Increase"
157
+ disabled={inactive}
158
+ onclick={() => step(1)}
159
+ >
160
+ <ChevronUp aria-hidden="true" />
161
+ </button>
162
+ <button
163
+ type="button"
164
+ tabindex="-1"
165
+ aria-label="Decrease"
166
+ disabled={inactive}
167
+ onclick={() => step(-1)}
168
+ >
169
+ <ChevronDown aria-hidden="true" />
170
+ </button>
171
+ {:else}
172
+ {@const Icon = type === 'time' ? Clock : CalendarIcon}
173
+ <button
174
+ type="button"
175
+ aria-label={type === 'time' ? 'Choose time' : 'Choose date'}
176
+ aria-haspopup="dialog"
177
+ aria-expanded={open}
178
+ aria-controls="{id}-picker"
179
+ disabled={inactive}
180
+ onclick={toggle}
181
+ >
182
+ <Icon aria-hidden="true" />
183
+ </button>
184
+ {/if}
185
+ </span>
186
+ {/if}
187
+
188
+ {#if open}
189
+ <div
190
+ id="{id}-picker"
191
+ role="dialog"
192
+ aria-label={type === 'color'
193
+ ? 'Choose color'
194
+ : type === 'time'
195
+ ? 'Choose time'
196
+ : 'Choose date'}
197
+ class={['input-popover', `input-popover--${side}`]}
198
+ bind:this={popoverEl}
199
+ transition:scale={{ duration: 140, start: 0.95 }}
200
+ >
201
+ {#if type === 'date' || type === 'week' || type === 'month'}
202
+ <Calendar
203
+ mode={type}
204
+ value={text}
205
+ min={rest.min?.toString()}
206
+ max={rest.max?.toString()}
207
+ onselect={(next) => {
208
+ set(next);
209
+ close(true);
210
+ }}
211
+ />
212
+ {:else if type === 'time'}
213
+ <TimePicker value={text.slice(0, 5)} {minuteStep} onselect={set} />
214
+ {:else if type === 'datetime-local'}
215
+ {@const date = text.slice(0, 10)}
216
+ {@const time = text.slice(11, 16)}
217
+ <Calendar
218
+ value={date}
219
+ min={rest.min?.toString().slice(0, 10)}
220
+ max={rest.max?.toString().slice(0, 10)}
221
+ onselect={(next) => set(`${next}T${time || '00:00'}`)}
222
+ />
223
+ <TimePicker
224
+ value={time}
225
+ {minuteStep}
226
+ onselect={(next) => set(`${date || formatDate(new Date())}T${next}`)}
227
+ />
228
+ {:else if type === 'color'}
229
+ <ColorPicker value={text || '#000000'} onselect={set} />
230
+ {/if}
231
+ </div>
232
+ {/if}
233
+ </span>
234
+ {:else}
235
+ <input {...rest} {type} bind:value {disabled} {readonly} {style} class={['input', className]} />
236
+ {/if}
237
+
238
+ <style>
239
+ .input {
240
+ box-sizing: border-box;
241
+ width: 100%;
242
+ min-width: 0;
243
+ height: 32px;
244
+ padding: 0 10px;
245
+ font-family: inherit;
246
+ font-size: 13px;
247
+ color: var(--color-text);
248
+ background-color: transparent;
249
+ border: 1px solid var(--color-border-strong);
250
+ border-radius: 10px;
251
+ outline: none;
252
+ transition: border-color 200ms ease;
253
+ }
254
+
255
+ .input::selection {
256
+ color: var(--color-accent-foreground);
257
+ background-color: var(--color-accent);
258
+ }
259
+
260
+ .input[type='file'] {
261
+ padding: 0 10px 0 4px;
262
+ color: var(--color-text-muted);
263
+ cursor: pointer;
264
+ }
265
+
266
+ .input::file-selector-button {
267
+ height: 24px;
268
+ margin: 3px 8px 0 0;
269
+ padding: 0 8px;
270
+ font-family: inherit;
271
+ font-size: 13px;
272
+ font-weight: 500;
273
+ color: var(--color-text);
274
+ background-color: var(--color-surface);
275
+ border: none;
276
+ border-radius: 6px;
277
+ cursor: pointer;
278
+ }
279
+
280
+ .input[type='search'] {
281
+ padding-right: 4px;
282
+ }
283
+
284
+ /* Native clear button as a ghost button with a gradient X */
285
+ .input::-webkit-search-cancel-button {
286
+ appearance: none;
287
+ width: 24px;
288
+ height: 24px;
289
+ margin: 0;
290
+ border-radius: 6px;
291
+ background:
292
+ linear-gradient(45deg, transparent 46%, var(--color-text) 46% 54%, transparent 54%),
293
+ linear-gradient(-45deg, transparent 46%, var(--color-text) 46% 54%, transparent 54%),
294
+ transparent;
295
+ background-size:
296
+ 10px 10px,
297
+ 10px 10px,
298
+ auto;
299
+ background-position: center;
300
+ background-repeat: no-repeat;
301
+ cursor: pointer;
302
+ }
303
+
304
+ .input::-webkit-search-cancel-button:hover {
305
+ background-color: var(--color-surface);
306
+ }
307
+
308
+ .input[type='color'] {
309
+ width: 64px;
310
+ padding: 3px;
311
+ cursor: pointer;
312
+ }
313
+
314
+ .input::-webkit-color-swatch-wrapper {
315
+ padding: 0;
316
+ }
317
+
318
+ .input::-webkit-color-swatch {
319
+ border: none;
320
+ border-radius: 6px;
321
+ }
322
+
323
+ .input::-moz-color-swatch {
324
+ border: none;
325
+ border-radius: 6px;
326
+ }
327
+
328
+ /* Number and date/time types: custom buttons replace the native spinners and picker icon */
329
+ .input-wrapper {
330
+ position: relative;
331
+ display: flex;
332
+ width: 100%;
333
+ min-width: 0;
334
+ }
335
+
336
+ .input-wrapper--color {
337
+ width: fit-content;
338
+ }
339
+
340
+ .input-popover {
341
+ position: absolute;
342
+ left: 0;
343
+ z-index: 60;
344
+ display: flex;
345
+ gap: 12px;
346
+ padding: 12px;
347
+ background-color: var(--color-bg);
348
+ border: 1px solid var(--color-border);
349
+ border-radius: 14px;
350
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
351
+ }
352
+
353
+ .input-popover--bottom {
354
+ top: calc(100% + 8px);
355
+ transform-origin: top left;
356
+ }
357
+
358
+ .input-popover--top {
359
+ bottom: calc(100% + 8px);
360
+ transform-origin: bottom left;
361
+ }
362
+
363
+ .input--with-controls {
364
+ padding-right: 32px;
365
+ }
366
+
367
+ .input[type='number'] {
368
+ appearance: textfield;
369
+ }
370
+
371
+ .input::-webkit-inner-spin-button,
372
+ .input::-webkit-outer-spin-button,
373
+ .input::-webkit-calendar-picker-indicator {
374
+ display: none;
375
+ appearance: none;
376
+ margin: 0;
377
+ }
378
+
379
+ .input::-webkit-datetime-edit-fields-wrapper {
380
+ padding: 0;
381
+ }
382
+
383
+ .input::-webkit-datetime-edit-text {
384
+ color: var(--color-text-muted);
385
+ }
386
+
387
+ .input-controls {
388
+ position: absolute;
389
+ top: 4px;
390
+ right: 4px;
391
+ bottom: 4px;
392
+ display: flex;
393
+ flex-direction: column;
394
+ }
395
+
396
+ .input-controls button {
397
+ display: flex;
398
+ flex: 1;
399
+ align-items: center;
400
+ justify-content: center;
401
+ width: 24px;
402
+ padding: 0;
403
+ color: var(--color-text-muted);
404
+ background-color: transparent;
405
+ border: none;
406
+ border-radius: 6px;
407
+ cursor: pointer;
408
+ transition:
409
+ background-color 200ms ease,
410
+ color 200ms ease;
411
+ }
412
+
413
+ .input-controls button:hover:not(:disabled) {
414
+ color: var(--color-text);
415
+ background-color: var(--color-surface);
416
+ }
417
+
418
+ .input-controls button:disabled {
419
+ cursor: not-allowed;
420
+ opacity: 0.5;
421
+ }
422
+
423
+ .input-controls :global(svg) {
424
+ width: 14px;
425
+ height: 14px;
426
+ }
427
+
428
+ /* Two stacked stepper buttons are half height */
429
+ .input-controls button:not(:only-child) {
430
+ border-radius: 4px;
431
+ }
432
+
433
+ .input-controls button:not(:only-child) :global(svg) {
434
+ width: 12px;
435
+ height: 12px;
436
+ }
437
+
438
+ .input::placeholder {
439
+ color: var(--color-text-faint);
440
+ }
441
+
442
+ .input:focus-visible {
443
+ border-color: var(--color-accent);
444
+ outline: 1px solid var(--color-accent);
445
+ }
446
+
447
+ .input[aria-invalid='true'] {
448
+ border-color: var(--color-destructive);
449
+ }
450
+
451
+ .input[aria-invalid='true']:focus-visible {
452
+ outline-color: var(--color-destructive);
453
+ }
454
+
455
+ .input:disabled {
456
+ cursor: not-allowed;
457
+ opacity: 0.5;
458
+ }
459
+ </style>
@@ -0,0 +1,10 @@
1
+ /** Checkbox and radio have their own components; buttons are Button; range and hidden are not text fields. */
2
+ export type InputType = 'text' | 'email' | 'password' | 'tel' | 'url' | 'search' | 'number' | 'date' | 'time' | 'datetime-local' | 'month' | 'week' | 'color' | 'file';
3
+ import type { HTMLInputAttributes } from 'svelte/elements';
4
+ type Props = Omit<HTMLInputAttributes, 'type'> & {
5
+ /** Number gets a stepper; date, time and color types open this library's pickers. */
6
+ type?: InputType;
7
+ };
8
+ declare const Input: import("svelte").Component<Props, {}, "value">;
9
+ type Input = ReturnType<typeof Input>;
10
+ export default Input;
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
6
+ /** An Input plus InputGroupAddons, in any order. */
7
+ children: Snippet;
8
+ };
9
+
10
+ let { class: className, children, ...rest }: Props = $props();
11
+ </script>
12
+
13
+ <div {...rest} role="group" class={['input-group', className]}>
14
+ {@render children()}
15
+ </div>
16
+
17
+ <style>
18
+ .input-group {
19
+ position: relative;
20
+ display: flex;
21
+ align-items: center;
22
+ box-sizing: border-box;
23
+ width: 100%;
24
+ min-width: 0;
25
+ height: 32px;
26
+ font-size: 13px;
27
+ color: var(--color-text);
28
+ border: 1px solid var(--color-border-strong);
29
+ border-radius: 10px;
30
+ transition: border-color 200ms ease;
31
+ }
32
+
33
+ /* The group draws border and focus ring instead of the Input */
34
+ .input-group > :global(.input),
35
+ .input-group > :global(.input-wrapper > .input),
36
+ .input-group > :global(.input:is(:focus-visible, [aria-invalid='true'])),
37
+ .input-group > :global(.input-wrapper > .input:is(:focus-visible, [aria-invalid='true'])) {
38
+ height: 30px;
39
+ border: none;
40
+ border-radius: 0;
41
+ outline: none;
42
+ background-color: transparent;
43
+ }
44
+
45
+ .input-group > :global(:is(.input, .input-wrapper)) {
46
+ flex: 1;
47
+ }
48
+
49
+ .input-group:has(:global(.input:focus-visible)) {
50
+ border-color: var(--color-accent);
51
+ outline: 1px solid var(--color-accent);
52
+ }
53
+
54
+ .input-group:has(:global([aria-invalid='true'])) {
55
+ border-color: var(--color-destructive);
56
+ outline-color: var(--color-destructive);
57
+ }
58
+
59
+ .input-group:has(:global(.input:disabled)) {
60
+ cursor: not-allowed;
61
+ opacity: 0.5;
62
+ }
63
+
64
+ .input-group:has(:global(.input:disabled)) :global(.input) {
65
+ opacity: 1;
66
+ }
67
+
68
+ /* Tighten the input's padding next to an inline addon */
69
+ .input-group:has(> :global(.input-group-addon--inline-start)) > :global(.input) {
70
+ padding-left: 6px;
71
+ }
72
+
73
+ .input-group:has(> :global(.input-group-addon--inline-end)) > :global(.input) {
74
+ padding-right: 6px;
75
+ }
76
+
77
+ /* Block addons stack above or below the input */
78
+ .input-group:has(> :global(:is(.input-group-addon--block-start, .input-group-addon--block-end))) {
79
+ flex-direction: column;
80
+ align-items: stretch;
81
+ height: auto;
82
+ }
83
+
84
+ .input-group:has(> :global(:is(.input-group-addon--block-start, .input-group-addon--block-end)))
85
+ > :global(:is(.input, .input-wrapper)) {
86
+ flex: none;
87
+ }
88
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /** An Input plus InputGroupAddons, in any order. */
5
+ children: Snippet;
6
+ };
7
+ declare const InputGroup: import("svelte").Component<Props, {}, "">;
8
+ type InputGroup = ReturnType<typeof InputGroup>;
9
+ export default InputGroup;