sv5ui 1.2.0 → 1.4.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 (71) hide show
  1. package/README.md +16 -11
  2. package/dist/CheckboxGroup/CheckboxGroup.svelte +215 -0
  3. package/dist/CheckboxGroup/CheckboxGroup.svelte.d.ts +5 -0
  4. package/dist/CheckboxGroup/checkbox-group.types.d.ts +130 -0
  5. package/dist/CheckboxGroup/checkbox-group.types.js +1 -0
  6. package/dist/CheckboxGroup/checkbox-group.variants.d.ts +553 -0
  7. package/dist/CheckboxGroup/checkbox-group.variants.js +231 -0
  8. package/dist/CheckboxGroup/index.d.ts +2 -0
  9. package/dist/CheckboxGroup/index.js +1 -0
  10. package/dist/Collapsible/Collapsible.svelte +69 -0
  11. package/dist/Collapsible/Collapsible.svelte.d.ts +6 -0
  12. package/dist/Collapsible/CollapsibleTestWrapper.svelte +17 -0
  13. package/dist/Collapsible/CollapsibleTestWrapper.svelte.d.ts +4 -0
  14. package/dist/Collapsible/collapsible.types.d.ts +75 -0
  15. package/dist/Collapsible/collapsible.types.js +1 -0
  16. package/dist/Collapsible/collapsible.variants.d.ts +53 -0
  17. package/dist/Collapsible/collapsible.variants.js +21 -0
  18. package/dist/Collapsible/index.d.ts +2 -0
  19. package/dist/Collapsible/index.js +1 -0
  20. package/dist/Command/Command.svelte +183 -0
  21. package/dist/Command/Command.svelte.d.ts +6 -0
  22. package/dist/Command/CommandTestWrapper.svelte +13 -0
  23. package/dist/Command/CommandTestWrapper.svelte.d.ts +4 -0
  24. package/dist/Command/command.types.d.ts +98 -0
  25. package/dist/Command/command.types.js +1 -0
  26. package/dist/Command/command.variants.d.ts +226 -0
  27. package/dist/Command/command.variants.js +86 -0
  28. package/dist/Command/index.d.ts +2 -0
  29. package/dist/Command/index.js +1 -0
  30. package/dist/FileUpload/FileUpload.svelte +561 -0
  31. package/dist/FileUpload/FileUpload.svelte.d.ts +8 -0
  32. package/dist/FileUpload/file-upload.types.d.ts +164 -0
  33. package/dist/FileUpload/file-upload.types.js +1 -0
  34. package/dist/FileUpload/file-upload.variants.d.ts +397 -0
  35. package/dist/FileUpload/file-upload.variants.js +224 -0
  36. package/dist/FileUpload/index.d.ts +2 -0
  37. package/dist/FileUpload/index.js +1 -0
  38. package/dist/PinInput/PinInput.svelte +150 -0
  39. package/dist/PinInput/PinInput.svelte.d.ts +6 -0
  40. package/dist/PinInput/index.d.ts +2 -0
  41. package/dist/PinInput/index.js +1 -0
  42. package/dist/PinInput/pin-input.types.d.ts +99 -0
  43. package/dist/PinInput/pin-input.types.js +1 -0
  44. package/dist/PinInput/pin-input.variants.d.ts +303 -0
  45. package/dist/PinInput/pin-input.variants.js +196 -0
  46. package/dist/Select/select.variants.js +1 -1
  47. package/dist/SelectMenu/select-menu.variants.js +1 -1
  48. package/dist/Slider/Slider.svelte +135 -0
  49. package/dist/Slider/Slider.svelte.d.ts +6 -0
  50. package/dist/Slider/index.d.ts +2 -0
  51. package/dist/Slider/index.js +1 -0
  52. package/dist/Slider/slider.types.d.ts +55 -0
  53. package/dist/Slider/slider.types.js +1 -0
  54. package/dist/Slider/slider.variants.d.ts +383 -0
  55. package/dist/Slider/slider.variants.js +102 -0
  56. package/dist/Toast/Toaster.svelte +618 -0
  57. package/dist/Toast/Toaster.svelte.d.ts +5 -0
  58. package/dist/Toast/index.d.ts +4 -0
  59. package/dist/Toast/index.js +2 -0
  60. package/dist/Toast/toast.d.ts +38 -0
  61. package/dist/Toast/toast.js +73 -0
  62. package/dist/Toast/toast.types.d.ts +19 -0
  63. package/dist/Toast/toast.types.js +1 -0
  64. package/dist/Toast/toast.variants.d.ts +7 -0
  65. package/dist/Toast/toast.variants.js +5 -0
  66. package/dist/config.d.ts +5 -0
  67. package/dist/config.js +6 -1
  68. package/dist/index.d.ts +7 -0
  69. package/dist/index.js +7 -0
  70. package/dist/theme.css +36 -0
  71. package/package.json +2 -1
@@ -0,0 +1,224 @@
1
+ import { tv } from 'tailwind-variants';
2
+ export const fileUploadVariants = tv({
3
+ slots: {
4
+ root: 'relative flex flex-col',
5
+ base: [
6
+ 'w-full bg-surface-container border border-outline-variant select-none',
7
+ 'flex flex-col items-stretch justify-center rounded-lg',
8
+ 'focus-visible:outline-2 focus-visible:outline-offset-2',
9
+ 'transition-colors duration-200'
10
+ ],
11
+ wrapper: 'flex flex-col items-center justify-center text-center',
12
+ icon: 'shrink-0 text-on-surface-variant',
13
+ label: 'font-medium text-on-surface mt-2',
14
+ description: 'text-on-surface-variant mt-1',
15
+ actions: 'flex flex-wrap gap-1.5 shrink-0 mt-4',
16
+ files: '',
17
+ file: 'relative',
18
+ fileLeading: 'shrink-0',
19
+ fileWrapper: 'flex flex-col min-w-0',
20
+ fileName: 'text-on-surface truncate',
21
+ fileSize: 'text-on-surface-variant truncate',
22
+ fileTrailing: '',
23
+ previewContent: '',
24
+ previewBody: ''
25
+ },
26
+ variants: {
27
+ color: {
28
+ primary: { base: 'focus-visible:outline-primary' },
29
+ secondary: { base: 'focus-visible:outline-secondary' },
30
+ tertiary: { base: 'focus-visible:outline-tertiary' },
31
+ success: { base: 'focus-visible:outline-success' },
32
+ warning: { base: 'focus-visible:outline-warning' },
33
+ error: { base: 'focus-visible:outline-error' },
34
+ info: { base: 'focus-visible:outline-info' },
35
+ surface: { base: 'focus-visible:outline-outline' }
36
+ },
37
+ variant: {
38
+ area: {
39
+ base: 'p-4 min-h-[120px]',
40
+ wrapper: 'py-6'
41
+ },
42
+ button: {}
43
+ },
44
+ size: {
45
+ xs: {
46
+ base: 'text-xs',
47
+ icon: 'size-5',
48
+ label: 'text-xs',
49
+ description: 'text-xs',
50
+ file: 'text-xs px-2 py-1 gap-1',
51
+ fileWrapper: 'flex-row gap-1'
52
+ },
53
+ sm: {
54
+ base: 'text-xs',
55
+ icon: 'size-6',
56
+ label: 'text-sm',
57
+ description: 'text-xs',
58
+ file: 'text-xs px-2.5 py-1.5 gap-1.5',
59
+ fileWrapper: 'flex-row gap-1'
60
+ },
61
+ md: {
62
+ base: 'text-sm',
63
+ icon: 'size-8',
64
+ label: 'text-sm',
65
+ description: 'text-sm',
66
+ file: 'text-xs px-2.5 py-1.5 gap-1.5'
67
+ },
68
+ lg: {
69
+ base: 'text-sm',
70
+ icon: 'size-10',
71
+ label: 'text-base',
72
+ description: 'text-sm',
73
+ file: 'text-sm px-3 py-2 gap-2',
74
+ fileSize: 'text-xs'
75
+ },
76
+ xl: {
77
+ base: 'text-base',
78
+ icon: 'size-12',
79
+ label: 'text-base',
80
+ description: 'text-base',
81
+ file: 'text-sm px-3 py-2 gap-2'
82
+ }
83
+ },
84
+ layout: {
85
+ list: {
86
+ root: 'gap-2 items-start',
87
+ files: 'flex flex-col w-full gap-1.5',
88
+ file: 'min-w-0 flex items-center bg-surface-container border border-outline-variant rounded-md w-full',
89
+ fileTrailing: 'ms-auto'
90
+ },
91
+ grid: {
92
+ root: 'gap-2',
93
+ files: 'grid grid-cols-2 gap-3 w-full',
94
+ file: 'relative aspect-square rounded-lg border border-outline-variant',
95
+ fileWrapper: 'hidden',
96
+ fileLeading: 'size-full',
97
+ fileTrailing: ''
98
+ }
99
+ },
100
+ dropzone: {
101
+ true: {
102
+ base: 'border-dashed data-[dragging=true]:bg-surface-container-high data-[dragging=true]:border-solid'
103
+ }
104
+ },
105
+ interactive: {
106
+ true: {
107
+ base: 'cursor-pointer hover:bg-surface-container-high'
108
+ }
109
+ },
110
+ highlight: {
111
+ true: {}
112
+ },
113
+ multiple: {
114
+ true: {}
115
+ },
116
+ disabled: {
117
+ true: {
118
+ base: 'cursor-not-allowed opacity-75 hover:bg-surface-container'
119
+ }
120
+ }
121
+ },
122
+ compoundVariants: [
123
+ // Highlight: colored border
124
+ { color: 'primary', highlight: true, class: { base: 'border-primary' } },
125
+ { color: 'secondary', highlight: true, class: { base: 'border-secondary' } },
126
+ { color: 'tertiary', highlight: true, class: { base: 'border-tertiary' } },
127
+ { color: 'success', highlight: true, class: { base: 'border-success' } },
128
+ { color: 'warning', highlight: true, class: { base: 'border-warning' } },
129
+ { color: 'error', highlight: true, class: { base: 'border-error' } },
130
+ { color: 'info', highlight: true, class: { base: 'border-info' } },
131
+ { color: 'surface', highlight: true, class: { base: 'border-outline' } },
132
+ // Dragging: colored border per color
133
+ {
134
+ color: 'primary',
135
+ dropzone: true,
136
+ class: { base: 'data-[dragging=true]:border-primary' }
137
+ },
138
+ {
139
+ color: 'secondary',
140
+ dropzone: true,
141
+ class: { base: 'data-[dragging=true]:border-secondary' }
142
+ },
143
+ {
144
+ color: 'tertiary',
145
+ dropzone: true,
146
+ class: { base: 'data-[dragging=true]:border-tertiary' }
147
+ },
148
+ {
149
+ color: 'success',
150
+ dropzone: true,
151
+ class: { base: 'data-[dragging=true]:border-success' }
152
+ },
153
+ {
154
+ color: 'warning',
155
+ dropzone: true,
156
+ class: { base: 'data-[dragging=true]:border-warning' }
157
+ },
158
+ { color: 'error', dropzone: true, class: { base: 'data-[dragging=true]:border-error' } },
159
+ { color: 'info', dropzone: true, class: { base: 'data-[dragging=true]:border-info' } },
160
+ // Button variant size padding
161
+ { variant: 'button', size: 'xs', class: { base: 'p-1' } },
162
+ { variant: 'button', size: 'sm', class: { base: 'p-1.5' } },
163
+ { variant: 'button', size: 'md', class: { base: 'p-1.5' } },
164
+ { variant: 'button', size: 'lg', class: { base: 'p-2' } },
165
+ { variant: 'button', size: 'xl', class: { base: 'p-2' } },
166
+ // Grid + multiple: 3 cols on larger screens, remove button outside top-right corner
167
+ {
168
+ layout: 'grid',
169
+ multiple: true,
170
+ class: { files: 'sm:grid-cols-3', fileTrailing: 'absolute -top-1.5 -end-1.5' }
171
+ },
172
+ // Grid + single: file overlays the base, remove button top-right inside
173
+ // variant:'area' guard is required — without it, variant='button' would also get
174
+ // absolute inset-0 on the external file item, making it invisible.
175
+ {
176
+ layout: 'grid',
177
+ multiple: false,
178
+ variant: 'area',
179
+ class: {
180
+ base: 'relative overflow-hidden',
181
+ file: 'absolute inset-0 p-0 border-0 rounded-none aspect-auto',
182
+ fileTrailing: 'absolute top-2 end-2'
183
+ }
184
+ },
185
+ // List size trailing adjustments
186
+ { size: 'xs', layout: 'list', class: { fileTrailing: '-me-1' } },
187
+ { size: 'sm', layout: 'list', class: { fileTrailing: '-me-1.5' } },
188
+ { size: 'md', layout: 'list', class: { fileTrailing: '-me-1.5' } },
189
+ { size: 'lg', layout: 'list', class: { fileTrailing: '-me-2' } },
190
+ { size: 'xl', layout: 'list', class: { fileTrailing: '-me-2' } }
191
+ ],
192
+ defaultVariants: {
193
+ color: 'primary',
194
+ variant: 'area',
195
+ size: 'md',
196
+ layout: 'list'
197
+ }
198
+ });
199
+ export const fileUploadDefaults = {
200
+ defaultVariants: {
201
+ color: 'primary',
202
+ variant: 'area',
203
+ size: 'md',
204
+ layout: 'list'
205
+ },
206
+ slots: {
207
+ root: '',
208
+ base: '',
209
+ wrapper: '',
210
+ icon: '',
211
+ label: '',
212
+ description: '',
213
+ actions: '',
214
+ files: '',
215
+ file: '',
216
+ fileLeading: '',
217
+ fileWrapper: '',
218
+ fileName: '',
219
+ fileSize: '',
220
+ fileTrailing: '',
221
+ previewContent: '',
222
+ previewBody: ''
223
+ }
224
+ };
@@ -0,0 +1,2 @@
1
+ export { default as FileUpload } from './FileUpload.svelte';
2
+ export type { FileUploadProps } from './file-upload.types.js';
@@ -0,0 +1 @@
1
+ export { default as FileUpload } from './FileUpload.svelte';
@@ -0,0 +1,150 @@
1
+ <script lang="ts" module>
2
+ import type { PinInputProps } from './pin-input.types.js'
3
+
4
+ export type Props = PinInputProps
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import { PinInput, useId } from 'bits-ui'
9
+ import { getContext } from 'svelte'
10
+ import { pinInputVariants, pinInputDefaults } from './pin-input.variants.js'
11
+ import { getComponentConfig } from '../config.js'
12
+ import type { FormFieldProps } from '../FormField/form-field.types.js'
13
+
14
+ const config = getComponentConfig('pinInput', pinInputDefaults)
15
+
16
+ let {
17
+ ref = $bindable(null),
18
+ defaultValue = '',
19
+ value = $bindable(defaultValue),
20
+ onValueChange,
21
+ onComplete,
22
+ length = 5,
23
+ type = 'text',
24
+ mask = false,
25
+ otp = false,
26
+ disabled = false,
27
+ required,
28
+ textalign,
29
+ pasteTransformer,
30
+ pushPasswordManagerStrategy,
31
+ inputId,
32
+ name,
33
+ placeholder = '○',
34
+ autofocus = false,
35
+ autofocusDelay = 0,
36
+ highlight = false,
37
+ fixed = false,
38
+ color = config.defaultVariants.color,
39
+ size,
40
+ variant = config.defaultVariants.variant,
41
+ class: className,
42
+ ui,
43
+ ...restProps
44
+ }: Props = $props()
45
+
46
+ const formFieldContext = getContext<
47
+ | {
48
+ name?: string
49
+ size: NonNullable<FormFieldProps['size']>
50
+ error?: string | boolean
51
+ ariaId: string
52
+ }
53
+ | undefined
54
+ >('formField')
55
+
56
+ const autoInputId = useId()
57
+ const hasError = $derived(
58
+ formFieldContext?.error !== undefined && formFieldContext?.error !== false
59
+ )
60
+ const resolvedInputId = $derived(inputId ?? formFieldContext?.ariaId ?? autoInputId)
61
+ const resolvedName = $derived(name ?? formFieldContext?.name)
62
+ const resolvedSize = $derived(size ?? formFieldContext?.size ?? config.defaultVariants.size)
63
+ const resolvedColor = $derived(hasError ? 'error' : color)
64
+ const resolvedHighlight = $derived(hasError || highlight)
65
+ const ariaDescribedBy = $derived(
66
+ !formFieldContext
67
+ ? undefined
68
+ : hasError
69
+ ? `${formFieldContext.ariaId}-error`
70
+ : `${formFieldContext.ariaId}-description ${formFieldContext.ariaId}-help`
71
+ )
72
+
73
+ const resolvedPasteTransformer = $derived(
74
+ pasteTransformer ??
75
+ (type === 'number' ? (text: string) => text.replace(/\D/g, '') : undefined)
76
+ )
77
+
78
+ function handleValueChange(v: string) {
79
+ const filtered = type === 'number' ? v.replace(/\D/g, '') : v
80
+ value = filtered
81
+ onValueChange?.(filtered)
82
+ }
83
+
84
+ const slots = $derived(
85
+ pinInputVariants({
86
+ variant,
87
+ color: resolvedColor,
88
+ size: resolvedSize,
89
+ highlight: resolvedHighlight,
90
+ fixed,
91
+ disabled
92
+ })
93
+ )
94
+
95
+ const classes = $derived.by(() => {
96
+ const u = ui ?? {}
97
+ return {
98
+ root: slots.root({ class: [config.slots.root, className, u.root] }),
99
+ base: slots.base({ class: [config.slots.base, u.base] })
100
+ }
101
+ })
102
+
103
+ $effect(() => {
104
+ if (!autofocus) return
105
+ const input = ref?.querySelector('[data-pin-input-input]') as HTMLInputElement | null
106
+ if (!input) return
107
+ const timer = setTimeout(() => input.focus(), autofocusDelay)
108
+ return () => clearTimeout(timer)
109
+ })
110
+ </script>
111
+
112
+ <div class="contents" {...restProps}>
113
+ {#if resolvedName}
114
+ <input type="hidden" name={resolvedName} {value} />
115
+ {/if}
116
+ <PinInput.Root
117
+ bind:ref
118
+ {value}
119
+ maxlength={length}
120
+ {disabled}
121
+ {textalign}
122
+ {onComplete}
123
+ pasteTransformer={resolvedPasteTransformer}
124
+ {pushPasswordManagerStrategy}
125
+ inputId={resolvedInputId}
126
+ autocomplete={otp ? 'one-time-code' : undefined}
127
+ inputmode={type === 'number' ? 'numeric' : 'text'}
128
+ {required}
129
+ aria-describedby={ariaDescribedBy}
130
+ onValueChange={handleValueChange}
131
+ class={classes.root}
132
+ >
133
+ {#snippet children({ cells })}
134
+ {#each cells as cell, i (i)}
135
+ <PinInput.Cell {cell} class={classes.base}>
136
+ {#if mask && cell.char}
137
+ <span class="block size-2 rounded-full bg-current"></span>
138
+ {:else if cell.char}
139
+ {cell.char}
140
+ {:else if cell.hasFakeCaret}
141
+ <span class="pointer-events-none absolute animate-pulse select-none">|</span
142
+ >
143
+ {:else}
144
+ <span class="text-on-surface/30">{placeholder}</span>
145
+ {/if}
146
+ </PinInput.Cell>
147
+ {/each}
148
+ {/snippet}
149
+ </PinInput.Root>
150
+ </div>
@@ -0,0 +1,6 @@
1
+ import type { PinInputProps } from './pin-input.types.js';
2
+ export type Props = PinInputProps;
3
+ import { PinInput } from 'bits-ui';
4
+ declare const PinInput: import("svelte").Component<PinInputProps, {}, "ref" | "value">;
5
+ type PinInput = ReturnType<typeof PinInput>;
6
+ export default PinInput;
@@ -0,0 +1,2 @@
1
+ export { default as PinInput } from './PinInput.svelte';
2
+ export type { PinInputProps } from './pin-input.types.js';
@@ -0,0 +1 @@
1
+ export { default as PinInput } from './PinInput.svelte';
@@ -0,0 +1,99 @@
1
+ import type { PinInput as PinInputPrimitive } from 'bits-ui';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { ClassNameValue } from 'tailwind-merge';
4
+ import type { PinInputVariantProps, PinInputSlots } from './pin-input.variants.js';
5
+ export type PinInputProps = Pick<PinInputPrimitive.RootProps, 'disabled' | 'textalign' | 'onComplete' | 'pasteTransformer' | 'pushPasswordManagerStrategy' | 'inputId'> & Omit<HTMLAttributes<HTMLElement>, 'class' | 'children'> & {
6
+ /**
7
+ * Bindable reference to the root DOM element.
8
+ */
9
+ ref?: HTMLElement | null;
10
+ /**
11
+ * The current value. Supports two-way binding with `bind:value`.
12
+ * @default ''
13
+ */
14
+ value?: string;
15
+ /**
16
+ * The initial value when uncontrolled.
17
+ */
18
+ defaultValue?: string;
19
+ /**
20
+ * Callback fired when the value changes.
21
+ */
22
+ onValueChange?: (value: string) => void;
23
+ /**
24
+ * The name attribute for the hidden input (used in form submission).
25
+ */
26
+ name?: string;
27
+ /**
28
+ * Whether the field is required.
29
+ */
30
+ required?: boolean;
31
+ /**
32
+ * Number of cells (characters).
33
+ * @default 5
34
+ */
35
+ length?: number;
36
+ /**
37
+ * Input type. Use 'number' to restrict to digits only.
38
+ * @default 'text'
39
+ */
40
+ type?: 'text' | 'number';
41
+ /**
42
+ * Mask the input, showing ● instead of typed characters.
43
+ * @default false
44
+ */
45
+ mask?: boolean;
46
+ /**
47
+ * Enable OTP mode (sets autocomplete="one-time-code").
48
+ * @default false
49
+ */
50
+ otp?: boolean;
51
+ /**
52
+ * Placeholder character displayed in each empty cell.
53
+ * @default '○'
54
+ */
55
+ placeholder?: string;
56
+ /**
57
+ * Automatically focus the input on mount.
58
+ * @default false
59
+ */
60
+ autofocus?: boolean;
61
+ /**
62
+ * Delay in milliseconds before autofocusing.
63
+ * @default 0
64
+ */
65
+ autofocusDelay?: number;
66
+ /**
67
+ * Highlight all cells with the active color ring (e.g. for error state).
68
+ * @default false
69
+ */
70
+ highlight?: boolean;
71
+ /**
72
+ * Prevent responsive text size changes on mobile.
73
+ * @default false
74
+ */
75
+ fixed?: boolean;
76
+ /**
77
+ * Color scheme of the input cells.
78
+ * @default 'primary'
79
+ */
80
+ color?: NonNullable<PinInputVariantProps['color']>;
81
+ /**
82
+ * Size of each cell.
83
+ * @default 'md'
84
+ */
85
+ size?: NonNullable<PinInputVariantProps['size']>;
86
+ /**
87
+ * Visual style of the input cells.
88
+ * @default 'outline'
89
+ */
90
+ variant?: NonNullable<PinInputVariantProps['variant']>;
91
+ /**
92
+ * Additional CSS classes for the root element.
93
+ */
94
+ class?: ClassNameValue;
95
+ /**
96
+ * Override styles for specific slots.
97
+ */
98
+ ui?: Partial<Record<PinInputSlots, ClassNameValue>>;
99
+ };
@@ -0,0 +1 @@
1
+ export {};