@mrintel/villain-ui 0.7.8 → 0.8.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.
- package/dist/components/buttons/Button.svelte.d.ts +1 -1
- package/dist/components/buttons/IconButton.svelte.d.ts +1 -1
- package/dist/components/buttons/LinkButton.svelte.d.ts +1 -1
- package/dist/components/buttons/buttonClasses.d.ts +2 -0
- package/dist/components/buttons/buttonClasses.js +2 -1
- package/dist/components/cards/SectionHeader.svelte +8 -3
- package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
- package/dist/components/data/Data.types.d.ts +10 -0
- package/dist/components/data/EmptyState.svelte +42 -0
- package/dist/components/data/EmptyState.svelte.d.ts +13 -0
- package/dist/components/data/Stat.svelte +30 -2
- package/dist/components/data/Stat.svelte.d.ts +9 -0
- package/dist/components/data/WeekHeatmap.svelte +150 -0
- package/dist/components/data/WeekHeatmap.svelte.d.ts +47 -0
- package/dist/components/data/index.d.ts +3 -1
- package/dist/components/data/index.js +2 -0
- package/dist/components/forms/Checkbox.svelte +27 -21
- package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/forms/DatePicker.svelte +7 -2
- package/dist/components/forms/DatePicker.svelte.d.ts +1 -0
- package/dist/components/forms/DateTimePicker.svelte +7 -2
- package/dist/components/forms/DateTimePicker.svelte.d.ts +1 -0
- package/dist/components/forms/FileUpload.svelte +7 -2
- package/dist/components/forms/FileUpload.svelte.d.ts +2 -0
- package/dist/components/forms/Input.svelte +95 -10
- package/dist/components/forms/Input.svelte.d.ts +8 -0
- package/dist/components/forms/RadioGroup.svelte +35 -25
- package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
- package/dist/components/forms/RangeSlider.svelte +6 -2
- package/dist/components/forms/RangeSlider.svelte.d.ts +2 -0
- package/dist/components/forms/Select.svelte +39 -30
- package/dist/components/forms/Select.svelte.d.ts +1 -0
- package/dist/components/forms/SelectMenu.svelte +7 -2
- package/dist/components/forms/SelectMenu.svelte.d.ts +1 -0
- package/dist/components/forms/Switch.svelte +31 -23
- package/dist/components/forms/Switch.svelte.d.ts +2 -0
- package/dist/components/forms/Textarea.svelte +28 -19
- package/dist/components/forms/Textarea.svelte.d.ts +1 -0
- package/dist/components/forms/TimePicker.svelte +7 -2
- package/dist/components/forms/TimePicker.svelte.d.ts +1 -0
- package/dist/components/overlays/ToastHost.svelte +19 -0
- package/dist/components/overlays/ToastHost.svelte.d.ts +8 -0
- package/dist/components/overlays/index.d.ts +3 -0
- package/dist/components/overlays/index.js +2 -0
- package/dist/components/overlays/toast.store.d.ts +32 -0
- package/dist/components/overlays/toast.store.js +34 -0
- package/dist/components/typography/Heading.svelte +3 -3
- package/dist/components/typography/Heading.svelte.d.ts +1 -0
- package/dist/components/typography/Text.svelte +2 -2
- package/dist/components/typography/Text.svelte.d.ts +1 -0
- package/dist/components/wizard/StepForm.svelte +22 -1
- package/dist/components/wizard/StepForm.svelte.d.ts +8 -1
- package/dist/components/wizard/step.service.js +89 -21
- package/dist/components/wizard/step.types.d.ts +32 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +3 -3
- package/dist/theme.css +125 -0
- package/package.json +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
<script lang="ts">import { createId } from '../../lib/internal/id
|
|
1
|
+
<script lang="ts">import { createId } from '../../lib/internal/id';
|
|
2
2
|
import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
|
|
3
|
-
let { type = 'text', name, value = $bindable(), placeholder, disabled = false, error = false, label, id = createId('input'), autocomplete, oninput, iconBefore, iconAfter, validate, validationMessage, showValidation = true, class: className = '', } = $props();
|
|
3
|
+
let { type = 'text', name, value = $bindable(), placeholder, disabled = false, error = false, label, id = createId('input'), autocomplete, oninput, iconBefore, iconAfter, validate, validationMessage, showValidation = true, revealable = false, min, max, step = 1, class: className = '', } = $props();
|
|
4
|
+
// Password reveal toggle (revealable only applies to type="password")
|
|
5
|
+
let revealed = $state(false);
|
|
6
|
+
const showReveal = $derived(revealable && type === 'password');
|
|
7
|
+
const effectiveType = $derived(showReveal && revealed ? 'text' : type);
|
|
4
8
|
// Built-in validation patterns
|
|
5
9
|
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
6
10
|
const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
|
|
@@ -65,15 +69,22 @@ $effect(() => {
|
|
|
65
69
|
});
|
|
66
70
|
const hasError = $derived(error || (validationError !== null));
|
|
67
71
|
const errorClasses = $derived(hasError ? 'error-state' : '');
|
|
72
|
+
function clamp(n) {
|
|
73
|
+
if (min !== undefined && n < min)
|
|
74
|
+
return min;
|
|
75
|
+
if (max !== undefined && n > max)
|
|
76
|
+
return max;
|
|
77
|
+
return n;
|
|
78
|
+
}
|
|
68
79
|
function increment() {
|
|
69
80
|
if (disabled || type !== 'number')
|
|
70
81
|
return;
|
|
71
|
-
value = Number(value || 0) +
|
|
82
|
+
value = clamp(Number(value || 0) + step);
|
|
72
83
|
}
|
|
73
84
|
function decrement() {
|
|
74
85
|
if (disabled || type !== 'number')
|
|
75
86
|
return;
|
|
76
|
-
value = Number(value || 0) -
|
|
87
|
+
value = clamp(Number(value || 0) - step);
|
|
77
88
|
}
|
|
78
89
|
</script>
|
|
79
90
|
|
|
@@ -94,23 +105,60 @@ function decrement() {
|
|
|
94
105
|
|
|
95
106
|
<!-- INPUT FIELD -->
|
|
96
107
|
<input
|
|
97
|
-
{
|
|
108
|
+
type={effectiveType}
|
|
98
109
|
{name}
|
|
99
110
|
{id}
|
|
100
111
|
{placeholder}
|
|
101
112
|
{disabled}
|
|
102
113
|
{autocomplete}
|
|
114
|
+
min={type === 'number' ? min : undefined}
|
|
115
|
+
max={type === 'number' ? max : undefined}
|
|
116
|
+
step={type === 'number' ? step : undefined}
|
|
103
117
|
bind:value
|
|
104
118
|
{oninput}
|
|
105
119
|
class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
|
|
106
120
|
? disabledClasses
|
|
107
121
|
: ''} {className}"
|
|
108
122
|
class:pl-12={iconBefore}
|
|
109
|
-
class:pr-12={iconAfter || type === 'number'}
|
|
123
|
+
class:pr-12={iconAfter || showReveal || type === 'number'}
|
|
110
124
|
/>
|
|
111
125
|
|
|
112
126
|
<!-- AFTER ICON (non-number) -->
|
|
113
|
-
{#if
|
|
127
|
+
{#if showReveal}
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
onclick={() => (revealed = !revealed)}
|
|
131
|
+
class="absolute right-4 z-10 inline-flex cursor-pointer items-center justify-center text-text-soft transition-colors hover:text-[var(--color-text)]"
|
|
132
|
+
aria-label={revealed ? 'Hide password' : 'Show password'}
|
|
133
|
+
aria-pressed={revealed}
|
|
134
|
+
>
|
|
135
|
+
{#if revealed}
|
|
136
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
137
|
+
<path
|
|
138
|
+
stroke-linecap="round"
|
|
139
|
+
stroke-linejoin="round"
|
|
140
|
+
stroke-width="2"
|
|
141
|
+
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
142
|
+
/>
|
|
143
|
+
</svg>
|
|
144
|
+
{:else}
|
|
145
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
146
|
+
<path
|
|
147
|
+
stroke-linecap="round"
|
|
148
|
+
stroke-linejoin="round"
|
|
149
|
+
stroke-width="2"
|
|
150
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
151
|
+
/>
|
|
152
|
+
<path
|
|
153
|
+
stroke-linecap="round"
|
|
154
|
+
stroke-linejoin="round"
|
|
155
|
+
stroke-width="2"
|
|
156
|
+
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
157
|
+
/>
|
|
158
|
+
</svg>
|
|
159
|
+
{/if}
|
|
160
|
+
</button>
|
|
161
|
+
{:else if iconAfter && type !== 'number'}
|
|
114
162
|
<span
|
|
115
163
|
class="absolute right-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
|
|
116
164
|
>
|
|
@@ -158,22 +206,59 @@ function decrement() {
|
|
|
158
206
|
{/if}
|
|
159
207
|
|
|
160
208
|
<input
|
|
161
|
-
{
|
|
209
|
+
type={effectiveType}
|
|
162
210
|
{name}
|
|
163
211
|
{id}
|
|
164
212
|
{placeholder}
|
|
165
213
|
{disabled}
|
|
166
214
|
{autocomplete}
|
|
215
|
+
min={type === 'number' ? min : undefined}
|
|
216
|
+
max={type === 'number' ? max : undefined}
|
|
217
|
+
step={type === 'number' ? step : undefined}
|
|
167
218
|
bind:value
|
|
168
219
|
{oninput}
|
|
169
220
|
class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
|
|
170
221
|
? disabledClasses
|
|
171
222
|
: ''} {className}"
|
|
172
223
|
class:pl-12={iconBefore}
|
|
173
|
-
class:pr-12={iconAfter || type === 'number'}
|
|
224
|
+
class:pr-12={iconAfter || showReveal || type === 'number'}
|
|
174
225
|
/>
|
|
175
226
|
|
|
176
|
-
{#if
|
|
227
|
+
{#if showReveal}
|
|
228
|
+
<button
|
|
229
|
+
type="button"
|
|
230
|
+
onclick={() => (revealed = !revealed)}
|
|
231
|
+
class="absolute right-4 z-10 inline-flex cursor-pointer items-center justify-center text-text-soft transition-colors hover:text-[var(--color-text)]"
|
|
232
|
+
aria-label={revealed ? 'Hide password' : 'Show password'}
|
|
233
|
+
aria-pressed={revealed}
|
|
234
|
+
>
|
|
235
|
+
{#if revealed}
|
|
236
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
237
|
+
<path
|
|
238
|
+
stroke-linecap="round"
|
|
239
|
+
stroke-linejoin="round"
|
|
240
|
+
stroke-width="2"
|
|
241
|
+
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
242
|
+
/>
|
|
243
|
+
</svg>
|
|
244
|
+
{:else}
|
|
245
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
246
|
+
<path
|
|
247
|
+
stroke-linecap="round"
|
|
248
|
+
stroke-linejoin="round"
|
|
249
|
+
stroke-width="2"
|
|
250
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
251
|
+
/>
|
|
252
|
+
<path
|
|
253
|
+
stroke-linecap="round"
|
|
254
|
+
stroke-linejoin="round"
|
|
255
|
+
stroke-width="2"
|
|
256
|
+
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
257
|
+
/>
|
|
258
|
+
</svg>
|
|
259
|
+
{/if}
|
|
260
|
+
</button>
|
|
261
|
+
{:else if iconAfter && type !== 'number'}
|
|
177
262
|
<span
|
|
178
263
|
class="absolute right-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
|
|
179
264
|
>
|
|
@@ -14,6 +14,14 @@ export interface Props {
|
|
|
14
14
|
validate?: (value: string | number) => boolean | string;
|
|
15
15
|
validationMessage?: string;
|
|
16
16
|
showValidation?: boolean;
|
|
17
|
+
/** Password inputs only: show an eye toggle to reveal the value. Replaces iconAfter. */
|
|
18
|
+
revealable?: boolean;
|
|
19
|
+
/** Number inputs only: lower bound enforced by the stepper arrows. */
|
|
20
|
+
min?: number;
|
|
21
|
+
/** Number inputs only: upper bound enforced by the stepper arrows. */
|
|
22
|
+
max?: number;
|
|
23
|
+
/** Number inputs only: stepper arrow increment. @default 1 */
|
|
24
|
+
step?: number;
|
|
17
25
|
class?: string;
|
|
18
26
|
}
|
|
19
27
|
declare const Input: import("svelte").Component<Props, {}, "value">;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
<script lang="ts">let { value = $bindable(), options, name, disabled = false, orientation = 'vertical', label, onchange, class: className = '' } = $props();
|
|
1
|
+
<script lang="ts">let { value = $bindable(), options, name, disabled = false, error = false, orientation = 'vertical', label, onchange, validationMessage, class: className = '' } = $props();
|
|
2
|
+
const hasError = $derived(error || !!validationMessage);
|
|
2
3
|
const containerClasses = $derived(orientation === 'vertical' ? 'flex flex-col gap-3' : 'flex flex-row gap-4');
|
|
4
|
+
const borderClass = $derived(hasError ? 'border-[var(--color-error)]' : 'border-[var(--color-border-strong)]');
|
|
3
5
|
export {};
|
|
4
6
|
</script>
|
|
5
7
|
|
|
@@ -20,7 +22,7 @@ export {};
|
|
|
20
22
|
{disabled}
|
|
21
23
|
bind:group={value}
|
|
22
24
|
onchange={onchange}
|
|
23
|
-
class="w-6 h-6 rounded-[var(--radius-pill)] border-2
|
|
25
|
+
class="w-6 h-6 rounded-[var(--radius-pill)] border-2 {borderClass} bg-transparent appearance-none transition-all duration-200 ease-[var(--ease-luxe)] cursor-pointer checked:border-[var(--color-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:ring-offset-2 focus:ring-offset-[var(--color-base-1)] relative {disabled ? 'cursor-not-allowed' : ''}"
|
|
24
26
|
/>
|
|
25
27
|
{#if option.iconBefore}
|
|
26
28
|
<span class="inline-flex items-center justify-center text-text-soft">
|
|
@@ -33,32 +35,40 @@ export {};
|
|
|
33
35
|
</label>
|
|
34
36
|
{/each}
|
|
35
37
|
</div>
|
|
38
|
+
{#if validationMessage}
|
|
39
|
+
<p class="text-error text-xs mt-2">{validationMessage}</p>
|
|
40
|
+
{/if}
|
|
36
41
|
</fieldset>
|
|
37
42
|
{:else}
|
|
38
|
-
<div
|
|
39
|
-
{
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
<div>
|
|
44
|
+
<div class="{containerClasses} {className}">
|
|
45
|
+
{#each options as option}
|
|
46
|
+
{@const radioId = `${name}-${option.value}`}
|
|
47
|
+
<label for={radioId} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''}">
|
|
48
|
+
<input
|
|
49
|
+
type="radio"
|
|
50
|
+
id={radioId}
|
|
51
|
+
{name}
|
|
52
|
+
value={option.value}
|
|
53
|
+
{disabled}
|
|
54
|
+
bind:group={value}
|
|
55
|
+
onchange={onchange}
|
|
56
|
+
class="w-6 h-6 rounded-[var(--radius-pill)] border-2 {borderClass} bg-transparent appearance-none transition-all duration-200 ease-[var(--ease-luxe)] cursor-pointer checked:border-[var(--color-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:ring-offset-2 focus:ring-offset-[var(--color-base-1)] relative {disabled ? 'cursor-not-allowed' : ''}"
|
|
57
|
+
/>
|
|
58
|
+
{#if option.iconBefore}
|
|
59
|
+
<span class="inline-flex items-center justify-center text-text-soft">
|
|
60
|
+
{@render option.iconBefore()}
|
|
61
|
+
</span>
|
|
62
|
+
{/if}
|
|
63
|
+
<span class="text-[var(--color-text)] text-sm select-none">
|
|
64
|
+
{option.label}
|
|
55
65
|
</span>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
</
|
|
61
|
-
{/
|
|
66
|
+
</label>
|
|
67
|
+
{/each}
|
|
68
|
+
</div>
|
|
69
|
+
{#if validationMessage}
|
|
70
|
+
<p class="text-error text-xs mt-2">{validationMessage}</p>
|
|
71
|
+
{/if}
|
|
62
72
|
</div>
|
|
63
73
|
{/if}
|
|
64
74
|
|
|
@@ -7,9 +7,11 @@ export interface Props {
|
|
|
7
7
|
}>;
|
|
8
8
|
name: string;
|
|
9
9
|
disabled?: boolean;
|
|
10
|
+
error?: boolean;
|
|
10
11
|
orientation?: 'vertical' | 'horizontal';
|
|
11
12
|
label?: string;
|
|
12
13
|
onchange?: (event: Event) => void;
|
|
14
|
+
validationMessage?: string;
|
|
13
15
|
class?: string;
|
|
14
16
|
}
|
|
15
17
|
declare const RadioGroup: import("svelte").Component<Props, {}, "value">;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
|
-
let { value = $bindable(0), min = 0, max = 100, step = 1, disabled = false, label, showValue = true, id = createId('range'), name, oninput } = $props();
|
|
2
|
+
let { value = $bindable(0), min = 0, max = 100, step = 1, disabled = false, error = false, label, showValue = true, id = createId('range'), name, oninput, validationMessage } = $props();
|
|
3
|
+
const hasError = $derived(error || !!validationMessage);
|
|
3
4
|
const percentage = $derived(max === min ? 0 : ((value - min) / (max - min)) * 100);
|
|
4
5
|
</script>
|
|
5
6
|
|
|
@@ -32,8 +33,11 @@ const percentage = $derived(max === min ? 0 : ((value - min) / (max - min)) * 10
|
|
|
32
33
|
aria-valuemax={max}
|
|
33
34
|
aria-valuenow={value}
|
|
34
35
|
class="w-full h-2 rounded-pill appearance-none cursor-pointer transition-opacity duration-200 {disabled ? 'opacity-50 cursor-not-allowed' : ''}"
|
|
35
|
-
style="background: linear-gradient(to right, var(--color-accent) 0%, var(--color-accent) {percentage}%, var(--color-base-3) {percentage}%, var(--color-base-3) 100%); border: 1px solid var(--color-border);"
|
|
36
|
+
style="background: linear-gradient(to right, var(--color-accent) 0%, var(--color-accent) {percentage}%, var(--color-base-3) {percentage}%, var(--color-base-3) 100%); border: 1px solid {hasError ? 'var(--color-error)' : 'var(--color-border)'};"
|
|
36
37
|
/>
|
|
38
|
+
{#if validationMessage}
|
|
39
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
40
|
+
{/if}
|
|
37
41
|
</div>
|
|
38
42
|
|
|
39
43
|
<style>
|
|
@@ -4,11 +4,13 @@ interface Props {
|
|
|
4
4
|
max?: number;
|
|
5
5
|
step?: number;
|
|
6
6
|
disabled?: boolean;
|
|
7
|
+
error?: boolean;
|
|
7
8
|
label?: string;
|
|
8
9
|
showValue?: boolean;
|
|
9
10
|
id?: string;
|
|
10
11
|
name?: string;
|
|
11
12
|
oninput?: (event: Event) => void;
|
|
13
|
+
validationMessage?: string;
|
|
12
14
|
}
|
|
13
15
|
declare const RangeSlider: import("svelte").Component<Props, {}, "value">;
|
|
14
16
|
type RangeSlider = ReturnType<typeof RangeSlider>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
2
|
import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
|
|
3
|
-
let { value = $bindable(), options, placeholder, disabled = false, error = false, label, id = createId('select'), name, autocomplete, onchange, iconBefore, class: className = '', } = $props();
|
|
3
|
+
let { value = $bindable(), options, placeholder, disabled = false, error = false, label, id = createId('select'), name, autocomplete, onchange, iconBefore, validationMessage, class: className = '', } = $props();
|
|
4
4
|
const selectClasses = `${baseInputClasses} pr-12 appearance-none bg-no-repeat bg-[right_1rem_center] bg-[length:16px] cursor-pointer select-with-chevron`;
|
|
5
|
-
const
|
|
5
|
+
const hasError = $derived(error || !!validationMessage);
|
|
6
|
+
const errorClasses = $derived(hasError ? 'error-state' : '');
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
{#if label}
|
|
@@ -42,39 +43,47 @@ const errorClasses = $derived(error ? 'error-state' : '');
|
|
|
42
43
|
{/each}
|
|
43
44
|
</select>
|
|
44
45
|
</div>
|
|
46
|
+
{#if validationMessage}
|
|
47
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
48
|
+
{/if}
|
|
45
49
|
</div>
|
|
46
50
|
{:else}
|
|
47
|
-
<div
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
{@render iconBefore()}
|
|
53
|
-
</span>
|
|
54
|
-
{/if}
|
|
55
|
-
|
|
56
|
-
<select
|
|
57
|
-
{id}
|
|
58
|
-
name={name}
|
|
59
|
-
{disabled}
|
|
60
|
-
{autocomplete}
|
|
61
|
-
bind:value
|
|
62
|
-
{onchange}
|
|
63
|
-
class="{selectClasses} {focusClasses} {errorClasses} {disabled
|
|
64
|
-
? disabledClasses
|
|
65
|
-
: ''} {className}"
|
|
66
|
-
class:pl-12={iconBefore}
|
|
67
|
-
>
|
|
68
|
-
{#if placeholder}
|
|
69
|
-
<option disabled value="" aria-hidden="true" hidden
|
|
70
|
-
>{placeholder}</option
|
|
51
|
+
<div>
|
|
52
|
+
<div class="relative">
|
|
53
|
+
{#if iconBefore}
|
|
54
|
+
<span
|
|
55
|
+
class="absolute left-4 top-1/2 -translate-y-1/2 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
|
|
71
56
|
>
|
|
57
|
+
{@render iconBefore()}
|
|
58
|
+
</span>
|
|
72
59
|
{/if}
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
<select
|
|
62
|
+
{id}
|
|
63
|
+
name={name}
|
|
64
|
+
{disabled}
|
|
65
|
+
{autocomplete}
|
|
66
|
+
bind:value
|
|
67
|
+
{onchange}
|
|
68
|
+
class="{selectClasses} {focusClasses} {errorClasses} {disabled
|
|
69
|
+
? disabledClasses
|
|
70
|
+
: ''} {className}"
|
|
71
|
+
class:pl-12={iconBefore}
|
|
72
|
+
>
|
|
73
|
+
{#if placeholder}
|
|
74
|
+
<option disabled value="" aria-hidden="true" hidden
|
|
75
|
+
>{placeholder}</option
|
|
76
|
+
>
|
|
77
|
+
{/if}
|
|
78
|
+
|
|
79
|
+
{#each options as option}
|
|
80
|
+
<option value={option.value}>{option.label}</option>
|
|
81
|
+
{/each}
|
|
82
|
+
</select>
|
|
83
|
+
</div>
|
|
84
|
+
{#if validationMessage}
|
|
85
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
86
|
+
{/if}
|
|
78
87
|
</div>
|
|
79
88
|
{/if}
|
|
80
89
|
|
|
@@ -13,6 +13,7 @@ export interface Props {
|
|
|
13
13
|
autocomplete?: HTMLSelectElement['autocomplete'];
|
|
14
14
|
onchange?: (event: Event) => void;
|
|
15
15
|
iconBefore?: import('svelte').Snippet;
|
|
16
|
+
validationMessage?: string;
|
|
16
17
|
class?: string;
|
|
17
18
|
}
|
|
18
19
|
declare const Select: import("svelte").Component<Props, {}, "value">;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
2
|
import Dropdown from '../overlays/Dropdown.svelte';
|
|
3
3
|
import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
|
|
4
|
-
let { value = $bindable(), options, placeholder = 'Select an option', disabled = false, error = false, label, id = createId('selectmenu'), name, iconBefore, onchange, class: className = '', } = $props();
|
|
4
|
+
let { value = $bindable(), options, placeholder = 'Select an option', disabled = false, error = false, label, id = createId('selectmenu'), name, iconBefore, onchange, validationMessage, class: className = '', } = $props();
|
|
5
|
+
const hasError = $derived(error || !!validationMessage);
|
|
5
6
|
let open = $state(false);
|
|
6
7
|
let highlightedIndex = $state(-1);
|
|
7
8
|
let listboxElement = $state();
|
|
@@ -105,7 +106,7 @@ const chevronIcon = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|
|
105
106
|
const triggerClasses = $derived(`
|
|
106
107
|
${baseInputClasses}
|
|
107
108
|
${focusClasses}
|
|
108
|
-
${
|
|
109
|
+
${hasError ? 'error-state' : ''}
|
|
109
110
|
${disabled ? disabledClasses : ''}
|
|
110
111
|
${iconBefore ? 'pl-12' : ''}
|
|
111
112
|
${className}
|
|
@@ -189,6 +190,10 @@ const triggerClasses = $derived(`
|
|
|
189
190
|
{/each}
|
|
190
191
|
</div>
|
|
191
192
|
</Dropdown>
|
|
193
|
+
|
|
194
|
+
{#if validationMessage}
|
|
195
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
196
|
+
{/if}
|
|
192
197
|
</div>
|
|
193
198
|
|
|
194
199
|
<style>
|
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
|
-
let { checked = $bindable(false), disabled = false, label, id = createId('switch'), name, onchange, iconBefore, class: className = '' } = $props();
|
|
2
|
+
let { checked = $bindable(false), disabled = false, error = false, label, id = createId('switch'), name, onchange, iconBefore, validationMessage, class: className = '' } = $props();
|
|
3
|
+
const hasError = $derived(error || !!validationMessage);
|
|
3
4
|
</script>
|
|
4
5
|
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
<div>
|
|
7
|
+
<label for={id} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''} {className}">
|
|
8
|
+
<input
|
|
9
|
+
type="checkbox"
|
|
10
|
+
role="switch"
|
|
11
|
+
aria-checked={checked}
|
|
12
|
+
{id}
|
|
13
|
+
name={name}
|
|
14
|
+
{disabled}
|
|
15
|
+
bind:checked
|
|
16
|
+
onchange={onchange}
|
|
17
|
+
class="w-14 h-7 rounded-pill bg-base-3 border appearance-none transition-all duration-300 ease-luxe cursor-pointer checked:bg-accent checked:accent-glow focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {hasError ? 'border-error' : 'border-border'} {disabled ? 'cursor-not-allowed' : ''}"
|
|
18
|
+
/>
|
|
19
|
+
{#if iconBefore}
|
|
20
|
+
<span class="inline-flex items-center justify-center text-text-soft">
|
|
21
|
+
{@render iconBefore()}
|
|
22
|
+
</span>
|
|
23
|
+
{/if}
|
|
24
|
+
{#if label}
|
|
25
|
+
<span class="text-text text-sm select-none">
|
|
26
|
+
{label}
|
|
27
|
+
</span>
|
|
28
|
+
{/if}
|
|
29
|
+
</label>
|
|
30
|
+
{#if validationMessage}
|
|
31
|
+
<p class="text-error text-xs mt-1.5 ml-16">{validationMessage}</p>
|
|
21
32
|
{/if}
|
|
22
|
-
|
|
23
|
-
<span class="text-text text-sm select-none">
|
|
24
|
-
{label}
|
|
25
|
-
</span>
|
|
26
|
-
{/if}
|
|
27
|
-
</label>
|
|
33
|
+
</div>
|
|
28
34
|
|
|
29
35
|
<style>
|
|
30
36
|
input[type="checkbox"]::after {
|
|
@@ -37,9 +43,11 @@ let { checked = $bindable(false), disabled = false, label, id = createId('switch
|
|
|
37
43
|
height: 1.25rem;
|
|
38
44
|
border-radius: var(--radius-pill);
|
|
39
45
|
background: var(--color-text);
|
|
46
|
+
background-image: none;
|
|
40
47
|
transition: all 0.3s var(--ease-luxe);
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
input[type="checkbox"]:checked::after {
|
|
44
51
|
transform: translate(1.75rem, -50%);
|
|
52
|
+
background-image: none;
|
|
45
53
|
}</style>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export interface Props {
|
|
2
2
|
checked?: boolean;
|
|
3
3
|
disabled?: boolean;
|
|
4
|
+
error?: boolean;
|
|
4
5
|
label?: string;
|
|
5
6
|
id?: string;
|
|
6
7
|
name?: string;
|
|
7
8
|
onchange?: (event: Event) => void;
|
|
8
9
|
iconBefore?: import('svelte').Snippet;
|
|
10
|
+
validationMessage?: string;
|
|
9
11
|
class?: string;
|
|
10
12
|
}
|
|
11
13
|
declare const Switch: import("svelte").Component<Props, {}, "checked">;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
2
|
import { baseInputClasses, focusClasses, disabledClasses } from './formClasses';
|
|
3
|
-
let { value = $bindable(), placeholder, rows = 4, disabled = false, error = false, label, id = createId('textarea'), name, autocomplete, oninput, iconBefore, class: className = '' } = $props();
|
|
3
|
+
let { value = $bindable(), placeholder, rows = 4, disabled = false, error = false, label, id = createId('textarea'), name, autocomplete, oninput, iconBefore, validationMessage, class: className = '' } = $props();
|
|
4
|
+
const hasError = $derived(error || !!validationMessage);
|
|
4
5
|
// Icon spacing: pl-12 (3rem) = left-4 (1rem) icon position + ~2rem for icon width and spacing
|
|
5
6
|
// top-4 keeps icon fixed at top when textarea is resized
|
|
6
7
|
// This ensures text doesn't overlap with the absolutely positioned icon
|
|
7
8
|
const textareaClasses = `${baseInputClasses} resize-y min-h-[100px]`;
|
|
8
|
-
const errorClasses = $derived(
|
|
9
|
+
const errorClasses = $derived(hasError ? 'error-state' : '');
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
12
|
{#if label}
|
|
@@ -32,25 +33,33 @@ const errorClasses = $derived(error ? 'error-state' : '');
|
|
|
32
33
|
class:pl-12={iconBefore}
|
|
33
34
|
></textarea>
|
|
34
35
|
</div>
|
|
36
|
+
{#if validationMessage}
|
|
37
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
38
|
+
{/if}
|
|
35
39
|
</div>
|
|
36
40
|
{:else}
|
|
37
|
-
<div
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
<div>
|
|
42
|
+
<div class="relative">
|
|
43
|
+
{#if iconBefore}
|
|
44
|
+
<span class="absolute left-4 top-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none">
|
|
45
|
+
{@render iconBefore()}
|
|
46
|
+
</span>
|
|
47
|
+
{/if}
|
|
48
|
+
<textarea
|
|
49
|
+
{id}
|
|
50
|
+
name={name}
|
|
51
|
+
{placeholder}
|
|
52
|
+
{disabled}
|
|
53
|
+
{rows}
|
|
54
|
+
{autocomplete}
|
|
55
|
+
bind:value
|
|
56
|
+
oninput={oninput}
|
|
57
|
+
class="{textareaClasses} {focusClasses} {errorClasses} {disabled ? disabledClasses : ''} {className}"
|
|
58
|
+
class:pl-12={iconBefore}
|
|
59
|
+
></textarea>
|
|
60
|
+
</div>
|
|
61
|
+
{#if validationMessage}
|
|
62
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
42
63
|
{/if}
|
|
43
|
-
<textarea
|
|
44
|
-
{id}
|
|
45
|
-
name={name}
|
|
46
|
-
{placeholder}
|
|
47
|
-
{disabled}
|
|
48
|
-
{rows}
|
|
49
|
-
{autocomplete}
|
|
50
|
-
bind:value
|
|
51
|
-
oninput={oninput}
|
|
52
|
-
class="{textareaClasses} {focusClasses} {errorClasses} {disabled ? disabledClasses : ''} {className}"
|
|
53
|
-
class:pl-12={iconBefore}
|
|
54
|
-
></textarea>
|
|
55
64
|
</div>
|
|
56
65
|
{/if}
|
|
@@ -10,6 +10,7 @@ export interface Props {
|
|
|
10
10
|
autocomplete?: HTMLTextAreaElement['autocomplete'];
|
|
11
11
|
oninput?: (event: Event) => void;
|
|
12
12
|
iconBefore?: import('svelte').Snippet;
|
|
13
|
+
validationMessage?: string;
|
|
13
14
|
class?: string;
|
|
14
15
|
}
|
|
15
16
|
declare const Textarea: import("svelte").Component<Props, {}, "value">;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
|
|
3
3
|
import TimePickerGrid from './TimePickerGrid.svelte';
|
|
4
4
|
import Dropdown from '../overlays/Dropdown.svelte';
|
|
5
|
-
let { variant = 'glass', value = $bindable(), min, max, step = 60, placeholder = 'Select time', disabled = false, error = false, label, id = createId('timepicker'), name, autocomplete, is24Hour = true, onchange, class: className = '', } = $props();
|
|
5
|
+
let { variant = 'glass', value = $bindable(), min, max, step = 60, placeholder = 'Select time', disabled = false, error = false, label, id = createId('timepicker'), name, autocomplete, is24Hour = true, onchange, validationMessage, class: className = '', } = $props();
|
|
6
6
|
let open = $state(false);
|
|
7
7
|
let pickerElement = $state();
|
|
8
8
|
let inputElement = $state();
|
|
@@ -108,7 +108,8 @@ $effect(() => {
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
});
|
|
111
|
-
const
|
|
111
|
+
const hasError = $derived(error || !!validationMessage);
|
|
112
|
+
const errorClasses = $derived(hasError ? 'error-state' : '');
|
|
112
113
|
</script>
|
|
113
114
|
|
|
114
115
|
<div class="time-picker-wrapper w-full">
|
|
@@ -181,6 +182,10 @@ const errorClasses = $derived(error ? 'error-state' : '');
|
|
|
181
182
|
/>
|
|
182
183
|
</div>
|
|
183
184
|
</Dropdown>
|
|
185
|
+
|
|
186
|
+
{#if validationMessage}
|
|
187
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
188
|
+
{/if}
|
|
184
189
|
</div>
|
|
185
190
|
|
|
186
191
|
<style>
|
|
@@ -13,6 +13,7 @@ export interface Props {
|
|
|
13
13
|
autocomplete?: HTMLInputElement['autocomplete'];
|
|
14
14
|
is24Hour?: boolean;
|
|
15
15
|
onchange?: (event: Event) => void;
|
|
16
|
+
validationMessage?: string;
|
|
16
17
|
class?: string;
|
|
17
18
|
}
|
|
18
19
|
declare const TimePicker: import("svelte").Component<Props, {}, "value">;
|