@sentropic/design-system-svelte 0.4.1 → 0.6.0

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 (43) hide show
  1. package/dist/ContentSwitcher.svelte +126 -0
  2. package/dist/ContentSwitcher.svelte.d.ts +17 -0
  3. package/dist/ContentSwitcher.svelte.d.ts.map +1 -0
  4. package/dist/CopyButton.svelte +132 -0
  5. package/dist/CopyButton.svelte.d.ts +15 -0
  6. package/dist/CopyButton.svelte.d.ts.map +1 -0
  7. package/dist/DatePicker.svelte +680 -0
  8. package/dist/DatePicker.svelte.d.ts +34 -0
  9. package/dist/DatePicker.svelte.d.ts.map +1 -0
  10. package/dist/FileUploader.svelte +431 -0
  11. package/dist/FileUploader.svelte.d.ts +30 -0
  12. package/dist/FileUploader.svelte.d.ts.map +1 -0
  13. package/dist/Form.svelte +117 -0
  14. package/dist/Form.svelte.d.ts +16 -0
  15. package/dist/Form.svelte.d.ts.map +1 -0
  16. package/dist/FormGroup.svelte +71 -0
  17. package/dist/FormGroup.svelte.d.ts +13 -0
  18. package/dist/FormGroup.svelte.d.ts.map +1 -0
  19. package/dist/Header.svelte +117 -0
  20. package/dist/Header.svelte.d.ts +16 -0
  21. package/dist/Header.svelte.d.ts.map +1 -0
  22. package/dist/InlineLoading.svelte +81 -0
  23. package/dist/InlineLoading.svelte.d.ts +10 -0
  24. package/dist/InlineLoading.svelte.d.ts.map +1 -0
  25. package/dist/OverflowMenu.svelte +222 -0
  26. package/dist/OverflowMenu.svelte.d.ts +21 -0
  27. package/dist/OverflowMenu.svelte.d.ts.map +1 -0
  28. package/dist/PaginationNav.svelte +219 -0
  29. package/dist/PaginationNav.svelte.d.ts +15 -0
  30. package/dist/PaginationNav.svelte.d.ts.map +1 -0
  31. package/dist/ProgressIndicator.svelte +283 -0
  32. package/dist/ProgressIndicator.svelte.d.ts +18 -0
  33. package/dist/ProgressIndicator.svelte.d.ts.map +1 -0
  34. package/dist/SkeletonText.svelte +79 -0
  35. package/dist/SkeletonText.svelte.d.ts +12 -0
  36. package/dist/SkeletonText.svelte.d.ts.map +1 -0
  37. package/dist/Toggletip.svelte +148 -0
  38. package/dist/Toggletip.svelte.d.ts +14 -0
  39. package/dist/Toggletip.svelte.d.ts.map +1 -0
  40. package/dist/index.d.ts +17 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +13 -0
  43. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatePicker.svelte.d.ts","sourceRoot":"","sources":["../src/lib/DatePicker.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;CAClB,CAAC;AAGJ,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,GAAG;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,IAAI,GAAG,eAAe,GAAG,IAAI,CAAC;IACtC,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA4UJ,QAAA,MAAM,UAAU,0DAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,431 @@
1
+ <script lang="ts" module>
2
+ export type FileUploadStatus = "idle" | "uploading" | "complete" | "error";
3
+
4
+ export type FileUploadItem = {
5
+ file: File;
6
+ status: FileUploadStatus;
7
+ progress?: number;
8
+ error?: string;
9
+ };
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ import type { HTMLAttributes } from "svelte/elements";
14
+
15
+ type FileUploaderProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
16
+ label?: string;
17
+ helperText?: string;
18
+ errorText?: string;
19
+ invalid?: boolean;
20
+ accept?: string;
21
+ multiple?: boolean;
22
+ maxSizeBytes?: number;
23
+ disabled?: boolean;
24
+ files?: File[];
25
+ onfiles?: (files: File[]) => void;
26
+ triggerLabel?: string;
27
+ dropzoneLabel?: string;
28
+ removeLabel?: (filename: string) => string;
29
+ maxSizeErrorLabel?: (filename: string, maxSizeBytes: number) => string;
30
+ id?: string;
31
+ class?: string;
32
+ };
33
+
34
+ let {
35
+ label,
36
+ helperText,
37
+ errorText,
38
+ invalid = false,
39
+ accept,
40
+ multiple = false,
41
+ maxSizeBytes,
42
+ disabled = false,
43
+ files = $bindable([]),
44
+ onfiles,
45
+ triggerLabel,
46
+ dropzoneLabel = "Drag and drop files here",
47
+ removeLabel = (filename) => `Remove ${filename}`,
48
+ maxSizeErrorLabel = (filename, max) =>
49
+ `${filename} exceeds the ${formatSize(max)} size limit`,
50
+ id,
51
+ class: className,
52
+ ...rest
53
+ }: FileUploaderProps = $props();
54
+
55
+ const generatedId = `st-file-uploader-${Math.random().toString(36).slice(2, 9)}`;
56
+ const inputId = $derived(id ?? generatedId);
57
+ const helperId = $derived(`${inputId}-help`);
58
+ const errorId = $derived(`${inputId}-error`);
59
+
60
+ let inputRef = $state<HTMLInputElement | null>(null);
61
+ let isDragOver = $state(false);
62
+
63
+ const isInvalid = $derived(invalid || Boolean(errorText));
64
+ const effectiveTriggerLabel = $derived(
65
+ triggerLabel ?? (multiple ? "Choose files" : "Choose file")
66
+ );
67
+
68
+ function fieldClasses() {
69
+ return ["st-field", "st-fileUploader-field", className].filter(Boolean).join(" ");
70
+ }
71
+
72
+ function dropzoneClasses() {
73
+ return [
74
+ "st-fileUploader__dropzone",
75
+ isDragOver ? "st-fileUploader__dropzone--dragover" : null,
76
+ isInvalid ? "st-fileUploader__dropzone--invalid" : null,
77
+ disabled ? "st-fileUploader__dropzone--disabled" : null
78
+ ]
79
+ .filter(Boolean)
80
+ .join(" ");
81
+ }
82
+
83
+ function formatSize(bytes: number): string {
84
+ if (!Number.isFinite(bytes) || bytes < 0) return "";
85
+ if (bytes === 0) return "0 B";
86
+ const units = ["B", "KB", "MB", "GB", "TB"];
87
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
88
+ const value = bytes / Math.pow(1024, i);
89
+ const formatted = value >= 10 || i === 0 ? value.toFixed(0) : value.toFixed(1);
90
+ return `${formatted} ${units[i]}`;
91
+ }
92
+
93
+ function appendFiles(incoming: File[]) {
94
+ if (disabled || incoming.length === 0) return;
95
+ const next = multiple ? [...files, ...incoming] : incoming.slice(0, 1);
96
+ files = next;
97
+ onfiles?.(next);
98
+ }
99
+
100
+ function onChange(event: Event) {
101
+ const input = event.currentTarget as HTMLInputElement;
102
+ const list = input.files ? Array.from(input.files) : [];
103
+ appendFiles(list);
104
+ // Allow re-selecting the same file later.
105
+ input.value = "";
106
+ }
107
+
108
+ function openPicker() {
109
+ if (disabled) return;
110
+ inputRef?.click();
111
+ }
112
+
113
+ function onTriggerKeydown(event: KeyboardEvent) {
114
+ if (disabled) return;
115
+ if (event.key === "Enter" || event.key === " ") {
116
+ event.preventDefault();
117
+ openPicker();
118
+ }
119
+ }
120
+
121
+ function onDragOver(event: DragEvent) {
122
+ if (disabled) return;
123
+ event.preventDefault();
124
+ isDragOver = true;
125
+ }
126
+
127
+ function onDragLeave(event: DragEvent) {
128
+ event.preventDefault();
129
+ isDragOver = false;
130
+ }
131
+
132
+ function onDrop(event: DragEvent) {
133
+ if (disabled) return;
134
+ event.preventDefault();
135
+ isDragOver = false;
136
+ const dropped = event.dataTransfer?.files ? Array.from(event.dataTransfer.files) : [];
137
+ if (dropped.length === 0) return;
138
+ appendFiles(dropped);
139
+ }
140
+
141
+ function removeAt(index: number) {
142
+ if (disabled) return;
143
+ const next = files.filter((_, i) => i !== index);
144
+ files = next;
145
+ onfiles?.(next);
146
+ }
147
+
148
+ function fileError(file: File): string | undefined {
149
+ if (typeof maxSizeBytes === "number" && file.size > maxSizeBytes) {
150
+ return maxSizeErrorLabel(file.name, maxSizeBytes);
151
+ }
152
+ return undefined;
153
+ }
154
+ </script>
155
+
156
+ <div {...rest} class={fieldClasses()}>
157
+ {#if label}
158
+ <label class="st-field__label" for={inputId}>{label}</label>
159
+ {/if}
160
+
161
+ <div
162
+ class={dropzoneClasses()}
163
+ role="presentation"
164
+ ondragover={onDragOver}
165
+ ondragenter={onDragOver}
166
+ ondragleave={onDragLeave}
167
+ ondrop={onDrop}
168
+ >
169
+ <input
170
+ bind:this={inputRef}
171
+ id={inputId}
172
+ type="file"
173
+ class="st-fileUploader__input"
174
+ {accept}
175
+ {multiple}
176
+ {disabled}
177
+ aria-invalid={isInvalid ? "true" : undefined}
178
+ aria-describedby={errorText ? errorId : helperText ? helperId : undefined}
179
+ onchange={onChange}
180
+ />
181
+ <div class="st-fileUploader__content">
182
+ <button
183
+ type="button"
184
+ class="st-fileUploader__trigger"
185
+ {disabled}
186
+ onclick={openPicker}
187
+ onkeydown={onTriggerKeydown}
188
+ >
189
+ {effectiveTriggerLabel}
190
+ </button>
191
+ <span class="st-fileUploader__hint">{dropzoneLabel}</span>
192
+ </div>
193
+ </div>
194
+
195
+ {#if errorText}
196
+ <span class="st-field__error" id={errorId}>{errorText}</span>
197
+ {:else if helperText}
198
+ <span class="st-field__help" id={helperId}>{helperText}</span>
199
+ {/if}
200
+
201
+ {#if files.length > 0}
202
+ <ul class="st-fileUploader__list">
203
+ {#each files as file, index (file.name + index)}
204
+ {@const itemError = fileError(file)}
205
+ <li
206
+ class={[
207
+ "st-fileUploader__item",
208
+ itemError ? "st-fileUploader__item--error" : null
209
+ ]
210
+ .filter(Boolean)
211
+ .join(" ")}
212
+ >
213
+ <span class="st-fileUploader__itemMeta">
214
+ <span class="st-fileUploader__itemName">{file.name}</span>
215
+ <span class="st-fileUploader__itemSize">{formatSize(file.size)}</span>
216
+ {#if itemError}
217
+ <span class="st-fileUploader__itemError">{itemError}</span>
218
+ {/if}
219
+ </span>
220
+ <button
221
+ type="button"
222
+ class="st-fileUploader__remove"
223
+ aria-label={removeLabel(file.name)}
224
+ {disabled}
225
+ onclick={() => removeAt(index)}
226
+ >
227
+ <span aria-hidden="true">×</span>
228
+ </button>
229
+ </li>
230
+ {/each}
231
+ </ul>
232
+ {/if}
233
+ </div>
234
+
235
+ <style>
236
+ .st-fileUploader-field {
237
+ display: grid;
238
+ gap: var(--st-component-field-gap, 0.5rem);
239
+ color: var(--st-component-field-labelText, var(--st-semantic-text-primary));
240
+ max-width: var(--st-component-field-maxWidth, 28rem);
241
+ }
242
+
243
+ .st-field__label {
244
+ font-size: 0.875rem;
245
+ font-weight: 600;
246
+ }
247
+
248
+ .st-field__help,
249
+ .st-field__error {
250
+ font-size: 0.8125rem;
251
+ line-height: 1.4;
252
+ }
253
+
254
+ .st-field__help {
255
+ color: var(--st-component-field-helpText, var(--st-semantic-text-secondary));
256
+ }
257
+
258
+ .st-field__error {
259
+ color: var(--st-component-field-errorText, var(--st-semantic-feedback-error));
260
+ }
261
+
262
+ .st-fileUploader__dropzone {
263
+ align-items: center;
264
+ background: var(--st-component-control-background, var(--st-semantic-surface-default));
265
+ border: 1px dashed var(--st-component-control-border, var(--st-semantic-border-subtle));
266
+ border-radius: var(--st-component-control-radius, 0.375rem);
267
+ color: var(--st-component-control-text, var(--st-semantic-text-primary));
268
+ display: flex;
269
+ flex-wrap: wrap;
270
+ gap: var(--st-spacing-3, 0.75rem);
271
+ justify-content: flex-start;
272
+ padding: var(--st-spacing-4, 1rem);
273
+ transition:
274
+ background var(--st-motion-fast, 120ms) var(--st-motion-easing, ease),
275
+ border-color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
276
+ }
277
+
278
+ .st-fileUploader__dropzone:hover:not(.st-fileUploader__dropzone--disabled) {
279
+ border-color: var(--st-component-control-hoverBorder, var(--st-semantic-border-strong));
280
+ }
281
+
282
+ .st-fileUploader__dropzone--dragover {
283
+ background: var(--st-component-control-hoverBackground, var(--st-semantic-surface-subtle));
284
+ border-color: var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
285
+ }
286
+
287
+ .st-fileUploader__dropzone--invalid {
288
+ border-color: var(--st-component-control-invalidBorder, var(--st-semantic-feedback-error));
289
+ }
290
+
291
+ .st-fileUploader__dropzone--disabled {
292
+ background: var(--st-component-control-disabledBackground, var(--st-semantic-surface-subtle));
293
+ color: var(--st-component-control-disabledText, var(--st-semantic-text-muted));
294
+ cursor: not-allowed;
295
+ opacity: 0.7;
296
+ }
297
+
298
+ .st-fileUploader__input {
299
+ border: 0;
300
+ clip: rect(0 0 0 0);
301
+ height: 1px;
302
+ margin: -1px;
303
+ overflow: hidden;
304
+ padding: 0;
305
+ position: absolute;
306
+ width: 1px;
307
+ }
308
+
309
+ .st-fileUploader__content {
310
+ align-items: center;
311
+ display: flex;
312
+ flex-wrap: wrap;
313
+ gap: var(--st-spacing-3, 0.75rem);
314
+ }
315
+
316
+ .st-fileUploader__trigger {
317
+ align-items: center;
318
+ background: var(--st-semantic-action-primary);
319
+ border: 1px solid transparent;
320
+ border-radius: var(--st-component-button-radius, 0.375rem);
321
+ color: var(--st-semantic-action-primaryText, #ffffff);
322
+ cursor: pointer;
323
+ display: inline-flex;
324
+ font: inherit;
325
+ font-weight: 600;
326
+ justify-content: center;
327
+ min-height: 2.25rem;
328
+ padding: 0 var(--st-spacing-3, 0.75rem);
329
+ transition:
330
+ background var(--st-motion-fast, 120ms) var(--st-motion-easing, ease),
331
+ box-shadow var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
332
+ }
333
+
334
+ .st-fileUploader__trigger:hover:not(:disabled) {
335
+ background: color-mix(in srgb, var(--st-semantic-action-primary) 88%, black);
336
+ }
337
+
338
+ .st-fileUploader__trigger:focus-visible {
339
+ box-shadow: 0 0 0 2px var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
340
+ outline: none;
341
+ }
342
+
343
+ .st-fileUploader__trigger:disabled {
344
+ cursor: not-allowed;
345
+ opacity: 0.6;
346
+ }
347
+
348
+ .st-fileUploader__hint {
349
+ color: var(--st-semantic-text-secondary);
350
+ font-size: 0.875rem;
351
+ }
352
+
353
+ .st-fileUploader__list {
354
+ display: grid;
355
+ gap: var(--st-spacing-2, 0.5rem);
356
+ list-style: none;
357
+ margin: 0;
358
+ padding: 0;
359
+ }
360
+
361
+ .st-fileUploader__item {
362
+ align-items: center;
363
+ background: var(--st-semantic-surface-subtle);
364
+ border: 1px solid var(--st-semantic-border-subtle);
365
+ border-radius: var(--st-component-control-radius, 0.375rem);
366
+ display: flex;
367
+ gap: var(--st-spacing-3, 0.75rem);
368
+ justify-content: space-between;
369
+ padding: var(--st-spacing-2, 0.5rem) var(--st-spacing-3, 0.75rem);
370
+ }
371
+
372
+ .st-fileUploader__item--error {
373
+ border-color: var(--st-semantic-feedback-error);
374
+ }
375
+
376
+ .st-fileUploader__itemMeta {
377
+ display: grid;
378
+ gap: 0.125rem;
379
+ min-width: 0;
380
+ }
381
+
382
+ .st-fileUploader__itemName {
383
+ font-weight: 600;
384
+ overflow: hidden;
385
+ text-overflow: ellipsis;
386
+ white-space: nowrap;
387
+ }
388
+
389
+ .st-fileUploader__itemSize {
390
+ color: var(--st-semantic-text-muted);
391
+ font-size: 0.8125rem;
392
+ }
393
+
394
+ .st-fileUploader__itemError {
395
+ color: var(--st-semantic-feedback-error);
396
+ font-size: 0.8125rem;
397
+ }
398
+
399
+ .st-fileUploader__remove {
400
+ align-items: center;
401
+ background: transparent;
402
+ border: 0;
403
+ border-radius: 50%;
404
+ color: var(--st-semantic-text-secondary);
405
+ cursor: pointer;
406
+ display: inline-flex;
407
+ flex: 0 0 auto;
408
+ font: inherit;
409
+ font-size: 1.125rem;
410
+ height: 1.75rem;
411
+ justify-content: center;
412
+ line-height: 1;
413
+ padding: 0;
414
+ transition: background-color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
415
+ width: 1.75rem;
416
+ }
417
+
418
+ .st-fileUploader__remove:hover:not(:disabled) {
419
+ background: color-mix(in srgb, currentColor 14%, transparent);
420
+ }
421
+
422
+ .st-fileUploader__remove:focus-visible {
423
+ outline: 2px solid var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
424
+ outline-offset: 1px;
425
+ }
426
+
427
+ .st-fileUploader__remove:disabled {
428
+ cursor: not-allowed;
429
+ opacity: 0.6;
430
+ }
431
+ </style>
@@ -0,0 +1,30 @@
1
+ export type FileUploadStatus = "idle" | "uploading" | "complete" | "error";
2
+ export type FileUploadItem = {
3
+ file: File;
4
+ status: FileUploadStatus;
5
+ progress?: number;
6
+ error?: string;
7
+ };
8
+ import type { HTMLAttributes } from "svelte/elements";
9
+ type FileUploaderProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
10
+ label?: string;
11
+ helperText?: string;
12
+ errorText?: string;
13
+ invalid?: boolean;
14
+ accept?: string;
15
+ multiple?: boolean;
16
+ maxSizeBytes?: number;
17
+ disabled?: boolean;
18
+ files?: File[];
19
+ onfiles?: (files: File[]) => void;
20
+ triggerLabel?: string;
21
+ dropzoneLabel?: string;
22
+ removeLabel?: (filename: string) => string;
23
+ maxSizeErrorLabel?: (filename: string, maxSizeBytes: number) => string;
24
+ id?: string;
25
+ class?: string;
26
+ };
27
+ declare const FileUploader: import("svelte").Component<FileUploaderProps, {}, "files">;
28
+ type FileUploader = ReturnType<typeof FileUploader>;
29
+ export default FileUploader;
30
+ //# sourceMappingURL=FileUploader.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileUploader.svelte.d.ts","sourceRoot":"","sources":["../src/lib/FileUploader.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;AAE3E,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGJ,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,iBAAiB,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,GAAG;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IACvE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAoLJ,QAAA,MAAM,YAAY,4DAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,117 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLFormAttributes } from "svelte/elements";
4
+
5
+ type FormStatus = "idle" | "submitting" | "submitted" | "error";
6
+
7
+ type FormProps = Omit<HTMLFormAttributes, "class" | "onsubmit"> & {
8
+ onsubmit?: (event: SubmitEvent) => void | Promise<void>;
9
+ helperText?: string;
10
+ errorText?: string;
11
+ successText?: string;
12
+ submitting?: boolean;
13
+ noNoscript?: boolean;
14
+ class?: string;
15
+ children: Snippet;
16
+ };
17
+
18
+ let {
19
+ onsubmit,
20
+ helperText,
21
+ errorText,
22
+ successText,
23
+ submitting = $bindable(false),
24
+ noNoscript = false,
25
+ class: className,
26
+ children,
27
+ ...rest
28
+ }: FormProps = $props();
29
+
30
+ let status = $state<FormStatus>("idle");
31
+ let internalError = $state<string | undefined>(undefined);
32
+
33
+ const classes = () => ["st-form", className].filter(Boolean).join(" ");
34
+
35
+ async function handleSubmit(event: SubmitEvent) {
36
+ if (!onsubmit) return;
37
+ event.preventDefault();
38
+ internalError = undefined;
39
+ status = "submitting";
40
+ submitting = true;
41
+ try {
42
+ await onsubmit(event);
43
+ status = "submitted";
44
+ } catch (err) {
45
+ status = "error";
46
+ internalError = err instanceof Error ? err.message : String(err);
47
+ } finally {
48
+ submitting = false;
49
+ }
50
+ }
51
+
52
+ const resolvedErrorText = () => errorText ?? internalError;
53
+ const showError = () => status === "error" && Boolean(resolvedErrorText());
54
+ const showSuccess = () => status === "submitted" && Boolean(successText);
55
+ const showHelper = () =>
56
+ Boolean(helperText) && !showError() && !showSuccess();
57
+ </script>
58
+
59
+ <form
60
+ {...rest}
61
+ class={classes()}
62
+ onsubmit={handleSubmit}
63
+ aria-busy={status === "submitting" ? "true" : undefined}
64
+ >
65
+ <div class="st-form__body">
66
+ {@render children()}
67
+ </div>
68
+ {#if showError()}
69
+ <p class="st-form__message st-form__message--error" role="alert">
70
+ {resolvedErrorText()}
71
+ </p>
72
+ {:else if showSuccess()}
73
+ <p class="st-form__message st-form__message--success" role="status">
74
+ {successText}
75
+ </p>
76
+ {:else if showHelper()}
77
+ <p class="st-form__message st-form__message--help">{helperText}</p>
78
+ {/if}
79
+ {#if !noNoscript}
80
+ <noscript>
81
+ <p class="st-form__message st-form__message--error">
82
+ JavaScript is required to submit this form.
83
+ </p>
84
+ </noscript>
85
+ {/if}
86
+ </form>
87
+
88
+ <style>
89
+ .st-form {
90
+ display: grid;
91
+ gap: var(--st-component-form-gap, 1rem);
92
+ width: 100%;
93
+ }
94
+
95
+ .st-form__body {
96
+ display: grid;
97
+ gap: var(--st-component-form-fieldGap, 1rem);
98
+ }
99
+
100
+ .st-form__message {
101
+ font-size: 0.8125rem;
102
+ line-height: 1.4;
103
+ margin: 0;
104
+ }
105
+
106
+ .st-form__message--help {
107
+ color: var(--st-component-form-helpText, var(--st-semantic-text-secondary));
108
+ }
109
+
110
+ .st-form__message--error {
111
+ color: var(--st-component-form-errorText, var(--st-semantic-feedback-error));
112
+ }
113
+
114
+ .st-form__message--success {
115
+ color: var(--st-component-form-successText, var(--st-semantic-feedback-success));
116
+ }
117
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLFormAttributes } from "svelte/elements";
3
+ type FormProps = Omit<HTMLFormAttributes, "class" | "onsubmit"> & {
4
+ onsubmit?: (event: SubmitEvent) => void | Promise<void>;
5
+ helperText?: string;
6
+ errorText?: string;
7
+ successText?: string;
8
+ submitting?: boolean;
9
+ noNoscript?: boolean;
10
+ class?: string;
11
+ children: Snippet;
12
+ };
13
+ declare const Form: import("svelte").Component<FormProps, {}, "submitting">;
14
+ type Form = ReturnType<typeof Form>;
15
+ export default Form;
16
+ //# sourceMappingURL=Form.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Form.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Form.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAKxD,KAAK,SAAS,GAAG,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IAChE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AA4EJ,QAAA,MAAM,IAAI,yDAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLFieldsetAttributes } from "svelte/elements";
4
+
5
+ type FormGroupProps = Omit<HTMLFieldsetAttributes, "class"> & {
6
+ legend?: string;
7
+ helperText?: string;
8
+ disabled?: boolean;
9
+ class?: string;
10
+ children: Snippet;
11
+ };
12
+
13
+ let {
14
+ legend,
15
+ helperText,
16
+ disabled = false,
17
+ class: className,
18
+ children,
19
+ ...rest
20
+ }: FormGroupProps = $props();
21
+
22
+ const classes = () => ["st-form-group", className].filter(Boolean).join(" ");
23
+ </script>
24
+
25
+ <fieldset {...rest} class={classes()} {disabled}>
26
+ {#if legend}<legend class="st-form-group__legend">{legend}</legend>{/if}
27
+ <div class="st-form-group__body">
28
+ {@render children()}
29
+ </div>
30
+ {#if helperText}
31
+ <p class="st-form-group__help">{helperText}</p>
32
+ {/if}
33
+ </fieldset>
34
+
35
+ <style>
36
+ .st-form-group {
37
+ border: 1px solid
38
+ var(--st-component-formGroup-border, var(--st-semantic-border-subtle));
39
+ border-radius: var(--st-component-formGroup-radius, 0.5rem);
40
+ color: var(--st-semantic-text-primary);
41
+ display: grid;
42
+ gap: var(--st-component-formGroup-gap, 1rem);
43
+ margin: 0;
44
+ padding: var(--st-component-formGroup-padding, 1rem);
45
+ }
46
+
47
+ .st-form-group__legend {
48
+ font-size: 0.875rem;
49
+ font-weight: 600;
50
+ padding: 0 0.25rem;
51
+ }
52
+
53
+ .st-form-group__body {
54
+ display: grid;
55
+ gap: var(--st-component-formGroup-fieldGap, 1rem);
56
+ }
57
+
58
+ .st-form-group__help {
59
+ color: var(
60
+ --st-component-formGroup-helpText,
61
+ var(--st-semantic-text-secondary)
62
+ );
63
+ font-size: 0.8125rem;
64
+ line-height: 1.4;
65
+ margin: 0;
66
+ }
67
+
68
+ .st-form-group:disabled {
69
+ opacity: 0.6;
70
+ }
71
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLFieldsetAttributes } from "svelte/elements";
3
+ type FormGroupProps = Omit<HTMLFieldsetAttributes, "class"> & {
4
+ legend?: string;
5
+ helperText?: string;
6
+ disabled?: boolean;
7
+ class?: string;
8
+ children: Snippet;
9
+ };
10
+ declare const FormGroup: import("svelte").Component<FormGroupProps, {}, "">;
11
+ type FormGroup = ReturnType<typeof FormGroup>;
12
+ export default FormGroup;
13
+ //# sourceMappingURL=FormGroup.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormGroup.svelte.d.ts","sourceRoot":"","sources":["../src/lib/FormGroup.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAG5D,KAAK,cAAc,GAAG,IAAI,CAAC,sBAAsB,EAAE,OAAO,CAAC,GAAG;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAgCJ,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}