ui-svelte 0.2.10 → 0.2.11

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.
@@ -1,5 +1,6 @@
1
+
1
2
  @utility fade-in {
2
- animation: fade-in 0.6s ease-out;
3
+ animation: fade-in 0.6s ease-out forwards;
3
4
  }
4
5
 
5
6
  @keyframes fade-in {
@@ -11,8 +12,69 @@
11
12
  }
12
13
  }
13
14
 
15
+ @utility fade-in-up {
16
+ animation: fade-in-up 0.6s ease-out forwards;
17
+ }
18
+
19
+ @keyframes fade-in-up {
20
+ from {
21
+ opacity: 0;
22
+ transform: translateY(30px);
23
+ }
24
+ to {
25
+ opacity: 1;
26
+ transform: translateY(0);
27
+ }
28
+ }
29
+
30
+ @utility fade-in-down {
31
+ animation: fade-in-down 0.6s ease-out forwards;
32
+ }
33
+
34
+ @keyframes fade-in-down {
35
+ from {
36
+ opacity: 0;
37
+ transform: translateY(-30px);
38
+ }
39
+ to {
40
+ opacity: 1;
41
+ transform: translateY(0);
42
+ }
43
+ }
44
+
45
+ @utility fade-in-left {
46
+ animation: fade-in-left 0.6s ease-out forwards;
47
+ }
48
+
49
+ @keyframes fade-in-left {
50
+ from {
51
+ opacity: 0;
52
+ transform: translateX(-30px);
53
+ }
54
+ to {
55
+ opacity: 1;
56
+ transform: translateX(0);
57
+ }
58
+ }
59
+
60
+ @utility fade-in-right {
61
+ animation: fade-in-right 0.6s ease-out forwards;
62
+ }
63
+
64
+ @keyframes fade-in-right {
65
+ from {
66
+ opacity: 0;
67
+ transform: translateX(30px);
68
+ }
69
+ to {
70
+ opacity: 1;
71
+ transform: translateX(0);
72
+ }
73
+ }
74
+
75
+ /* Zoom Animations */
14
76
  @utility zoom-in {
15
- animation: zoom-in 0.4s ease-out;
77
+ animation: zoom-in 0.5s ease-out forwards;
16
78
  }
17
79
 
18
80
  @keyframes zoom-in {
@@ -25,3 +87,370 @@
25
87
  transform: scale(1);
26
88
  }
27
89
  }
90
+
91
+ @utility zoom-in-up {
92
+ animation: zoom-in-up 0.6s ease-out forwards;
93
+ }
94
+
95
+ @keyframes zoom-in-up {
96
+ from {
97
+ opacity: 0;
98
+ transform: scale(0.9) translateY(20px);
99
+ }
100
+ to {
101
+ opacity: 1;
102
+ transform: scale(1) translateY(0);
103
+ }
104
+ }
105
+
106
+ /* Slide Animations (sin fade) */
107
+ @utility slide-up {
108
+ animation: slide-up 0.5s ease-out forwards;
109
+ }
110
+
111
+ @keyframes slide-up {
112
+ from {
113
+ transform: translateY(100%);
114
+ }
115
+ to {
116
+ transform: translateY(0);
117
+ }
118
+ }
119
+
120
+ @utility slide-down {
121
+ animation: slide-down 0.5s ease-out forwards;
122
+ }
123
+
124
+ @keyframes slide-down {
125
+ from {
126
+ transform: translateY(-100%);
127
+ }
128
+ to {
129
+ transform: translateY(0);
130
+ }
131
+ }
132
+
133
+ @utility slide-left {
134
+ animation: slide-left 0.5s ease-out forwards;
135
+ }
136
+
137
+ @keyframes slide-left {
138
+ from {
139
+ transform: translateX(100%);
140
+ }
141
+ to {
142
+ transform: translateX(0);
143
+ }
144
+ }
145
+
146
+ @utility slide-right {
147
+ animation: slide-right 0.5s ease-out forwards;
148
+ }
149
+
150
+ @keyframes slide-right {
151
+ from {
152
+ transform: translateX(-100%);
153
+ }
154
+ to {
155
+ transform: translateX(0);
156
+ }
157
+ }
158
+
159
+ /* Bounce Animations */
160
+ @utility bounce-in {
161
+ animation: bounce-in 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
162
+ }
163
+
164
+ @keyframes bounce-in {
165
+ 0% {
166
+ opacity: 0;
167
+ transform: scale(0.3);
168
+ }
169
+ 50% {
170
+ transform: scale(1.05);
171
+ }
172
+ 70% {
173
+ transform: scale(0.95);
174
+ }
175
+ 100% {
176
+ opacity: 1;
177
+ transform: scale(1);
178
+ }
179
+ }
180
+
181
+ @utility bounce-in-up {
182
+ animation: bounce-in-up 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
183
+ }
184
+
185
+ @keyframes bounce-in-up {
186
+ 0% {
187
+ opacity: 0;
188
+ transform: translateY(50px);
189
+ }
190
+ 60% {
191
+ opacity: 1;
192
+ transform: translateY(-10px);
193
+ }
194
+ 80% {
195
+ transform: translateY(5px);
196
+ }
197
+ 100% {
198
+ transform: translateY(0);
199
+ }
200
+ }
201
+
202
+ /* Reveal Animations (para textos y títulos) */
203
+ @utility reveal-up {
204
+ animation: reveal-up 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
205
+ }
206
+
207
+ @keyframes reveal-up {
208
+ from {
209
+ opacity: 0;
210
+ transform: translateY(60px);
211
+ filter: blur(10px);
212
+ }
213
+ to {
214
+ opacity: 1;
215
+ transform: translateY(0);
216
+ filter: blur(0);
217
+ }
218
+ }
219
+
220
+ @utility reveal-scale {
221
+ animation: reveal-scale 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
222
+ }
223
+
224
+ @keyframes reveal-scale {
225
+ from {
226
+ opacity: 0;
227
+ transform: scale(0.95) translateY(20px);
228
+ }
229
+ to {
230
+ opacity: 1;
231
+ transform: scale(1) translateY(0);
232
+ }
233
+ }
234
+
235
+ @utility flip-in-x {
236
+ animation: flip-in-x 0.6s ease-out forwards;
237
+ }
238
+
239
+ @keyframes flip-in-x {
240
+ from {
241
+ opacity: 0;
242
+ transform: perspective(400px) rotateX(90deg);
243
+ }
244
+ to {
245
+ opacity: 1;
246
+ transform: perspective(400px) rotateX(0);
247
+ }
248
+ }
249
+
250
+ @utility flip-in-y {
251
+ animation: flip-in-y 0.6s ease-out forwards;
252
+ }
253
+
254
+ @keyframes flip-in-y {
255
+ from {
256
+ opacity: 0;
257
+ transform: perspective(400px) rotateY(90deg);
258
+ }
259
+ to {
260
+ opacity: 1;
261
+ transform: perspective(400px) rotateY(0);
262
+ }
263
+ }
264
+
265
+ @utility float {
266
+ animation: float 3s ease-in-out infinite;
267
+ }
268
+
269
+ @keyframes float {
270
+ 0%,
271
+ 100% {
272
+ transform: translateY(0);
273
+ }
274
+ 50% {
275
+ transform: translateY(-10px);
276
+ }
277
+ }
278
+
279
+ @utility pulse-soft {
280
+ animation: pulse-soft 2s ease-in-out infinite;
281
+ }
282
+
283
+ @keyframes pulse-soft {
284
+ 0%,
285
+ 100% {
286
+ opacity: 1;
287
+ transform: scale(1);
288
+ }
289
+ 50% {
290
+ opacity: 0.8;
291
+ transform: scale(1.02);
292
+ }
293
+ }
294
+
295
+ @utility delay-0 {
296
+ animation-delay: 0ms;
297
+ }
298
+
299
+ @utility delay-75 {
300
+ animation-delay: 75ms;
301
+ }
302
+
303
+ @utility delay-100 {
304
+ animation-delay: 100ms;
305
+ }
306
+
307
+ @utility delay-150 {
308
+ animation-delay: 150ms;
309
+ }
310
+
311
+ @utility delay-200 {
312
+ animation-delay: 200ms;
313
+ }
314
+
315
+ @utility delay-300 {
316
+ animation-delay: 300ms;
317
+ }
318
+
319
+ @utility delay-400 {
320
+ animation-delay: 400ms;
321
+ }
322
+
323
+ @utility delay-500 {
324
+ animation-delay: 500ms;
325
+ }
326
+
327
+ @utility delay-600 {
328
+ animation-delay: 600ms;
329
+ }
330
+
331
+ @utility delay-700 {
332
+ animation-delay: 700ms;
333
+ }
334
+
335
+ @utility delay-800 {
336
+ animation-delay: 800ms;
337
+ }
338
+
339
+ @utility delay-1000 {
340
+ animation-delay: 1000ms;
341
+ }
342
+
343
+ @utility duration-300 {
344
+ animation-duration: 300ms;
345
+ }
346
+
347
+ @utility duration-500 {
348
+ animation-duration: 500ms;
349
+ }
350
+
351
+ @utility duration-700 {
352
+ animation-duration: 700ms;
353
+ }
354
+
355
+ @utility duration-1000 {
356
+ animation-duration: 1000ms;
357
+ }
358
+
359
+ @utility reveal {
360
+ opacity: 0;
361
+ transform: translateY(40px);
362
+ transition:
363
+ opacity 0.6s ease-out,
364
+ transform 0.6s ease-out;
365
+ }
366
+
367
+ @utility reveal.is-visible {
368
+ opacity: 1;
369
+ transform: translateY(0);
370
+ }
371
+
372
+ @utility reveal-left {
373
+ opacity: 0;
374
+ transform: translateX(-40px);
375
+ transition:
376
+ opacity 0.6s ease-out,
377
+ transform 0.6s ease-out;
378
+ }
379
+
380
+ @utility reveal-left.is-visible {
381
+ opacity: 1;
382
+ transform: translateX(0);
383
+ }
384
+
385
+ @utility reveal-right {
386
+ opacity: 0;
387
+ transform: translateX(40px);
388
+ transition:
389
+ opacity 0.6s ease-out,
390
+ transform 0.6s ease-out;
391
+ }
392
+
393
+ @utility reveal-right.is-visible {
394
+ opacity: 1;
395
+ transform: translateX(0);
396
+ }
397
+
398
+ @utility reveal-zoom {
399
+ opacity: 0;
400
+ transform: scale(0.9);
401
+ transition:
402
+ opacity 0.6s ease-out,
403
+ transform 0.6s ease-out;
404
+ }
405
+
406
+ @utility reveal-zoom.is-visible {
407
+ opacity: 1;
408
+ transform: scale(1);
409
+ }
410
+
411
+ @utility shake {
412
+ animation: shake 0.5s ease-in-out;
413
+ }
414
+
415
+ @keyframes shake {
416
+ 0%,
417
+ 100% {
418
+ transform: translateX(0);
419
+ }
420
+ 20%,
421
+ 60% {
422
+ transform: translateX(-5px);
423
+ }
424
+ 40%,
425
+ 80% {
426
+ transform: translateX(5px);
427
+ }
428
+ }
429
+
430
+ @utility wiggle {
431
+ animation: wiggle 1s ease-in-out infinite;
432
+ }
433
+
434
+ @keyframes wiggle {
435
+ 0%,
436
+ 100% {
437
+ transform: rotate(-3deg);
438
+ }
439
+ 50% {
440
+ transform: rotate(3deg);
441
+ }
442
+ }
443
+
444
+ @utility glow {
445
+ animation: glow 2s ease-in-out infinite;
446
+ }
447
+
448
+ @keyframes glow {
449
+ 0%,
450
+ 100% {
451
+ box-shadow: 0 0 5px var(--primary);
452
+ }
453
+ 50% {
454
+ box-shadow: 0 0 20px var(--primary), 0 0 30px var(--primary);
455
+ }
456
+ }
@@ -5,6 +5,9 @@
5
5
  src?: string;
6
6
  name?: string;
7
7
  alt?: string;
8
+ href?: string;
9
+ onclick?: () => void;
10
+ target?: '_self' | '_blank' | '_parent' | '_top';
8
11
  variant?:
9
12
  | 'primary'
10
13
  | 'secondary'
@@ -24,6 +27,9 @@
24
27
  src,
25
28
  name,
26
29
  alt = 'Avatar',
30
+ href,
31
+ onclick,
32
+ target,
27
33
  size = 'lg',
28
34
  variant = 'primary',
29
35
  status,
@@ -56,17 +62,20 @@
56
62
  busy: 'is-busy',
57
63
  away: 'is-away'
58
64
  };
65
+
66
+ const baseClasses = $derived(
67
+ cn(
68
+ 'avatar',
69
+ sizeClasses[size],
70
+ variantClasses[variant],
71
+ isBordered && 'is-bordered',
72
+ (href || onclick) && 'is-clickable',
73
+ className
74
+ )
75
+ );
59
76
  </script>
60
77
 
61
- <div
62
- class={cn(
63
- 'avatar',
64
- sizeClasses[size],
65
- variantClasses[variant],
66
- isBordered && 'is-bordered',
67
- className
68
- )}
69
- >
78
+ {#snippet avatarContent()}
70
79
  {#if status}
71
80
  <div class="avatar-status">
72
81
  <div class={cn('avatar-indicator', statusClasses[status])}></div>
@@ -77,4 +86,18 @@
77
86
  {:else}
78
87
  <span class="avatar-name">{name?.charAt(0).toUpperCase() || alt.charAt(0).toUpperCase()}</span>
79
88
  {/if}
80
- </div>
89
+ {/snippet}
90
+
91
+ {#if href}
92
+ <a class={baseClasses} {href} {target}>
93
+ {@render avatarContent()}
94
+ </a>
95
+ {:else if onclick}
96
+ <button type="button" class={baseClasses} {onclick}>
97
+ {@render avatarContent()}
98
+ </button>
99
+ {:else}
100
+ <div class={baseClasses}>
101
+ {@render avatarContent()}
102
+ </div>
103
+ {/if}
@@ -2,6 +2,9 @@ type Props = {
2
2
  src?: string;
3
3
  name?: string;
4
4
  alt?: string;
5
+ href?: string;
6
+ onclick?: () => void;
7
+ target?: '_self' | '_blank' | '_parent' | '_top';
5
8
  variant?: 'primary' | 'secondary' | 'muted' | 'success' | 'warning' | 'danger' | 'info' | 'transparent';
6
9
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
7
10
  status?: 'online' | 'offline' | 'busy' | 'away';
@@ -6,6 +6,9 @@
6
6
  src?: string;
7
7
  name?: string;
8
8
  alt?: string;
9
+ href?: string;
10
+ onclick?: () => void;
11
+ target?: '_self' | '_blank' | '_parent' | '_top';
9
12
  };
10
13
 
11
14
  type Props = {
@@ -46,13 +49,16 @@
46
49
  src={item.src}
47
50
  name={item.name}
48
51
  alt={item.alt || `Avatar ${i + 1}`}
52
+ href={item.href}
53
+ onclick={item.onclick}
54
+ target={item.target}
49
55
  {variant}
50
56
  {size}
51
57
  {isBordered}
52
58
  />
53
59
  {/each}
54
60
  {#if remainingCount > 0}
55
- <div class={cn('avatar-group-counter', `is-${size}`)}>
61
+ <div class={cn('avatar-group-counter', `is-${size}`, `is-${variant}`)}>
56
62
  +{remainingCount}
57
63
  </div>
58
64
  {/if}
@@ -2,6 +2,9 @@ type AvatarItem = {
2
2
  src?: string;
3
3
  name?: string;
4
4
  alt?: string;
5
+ href?: string;
6
+ onclick?: () => void;
7
+ target?: '_self' | '_blank' | '_parent' | '_top';
5
8
  };
6
9
  type Props = {
7
10
  items: AvatarItem[];
@@ -63,12 +63,6 @@
63
63
  lg: 'is-lg'
64
64
  };
65
65
 
66
- const avatarSizes: Record<string, 'sm' | 'md' | 'lg' | 'xl'> = {
67
- sm: 'md',
68
- md: 'lg',
69
- lg: 'xl'
70
- };
71
-
72
66
  const isInteractive = $derived(!!href || !!onclick);
73
67
 
74
68
  let itemClasses = $derived(
@@ -92,10 +86,10 @@
92
86
 
93
87
  {#snippet itemContent()}
94
88
  {#if icon}
95
- <Icon {icon} />
89
+ <Icon {icon} class="h-6 w-auto" />
96
90
  {/if}
97
91
  {#if src}
98
- <Avatar {src} {status} size={avatarSizes[size]} variant="transparent" />
92
+ <Avatar {src} {status} {size} variant="transparent" />
99
93
  {/if}
100
94
  <div class="item-body">
101
95
  <div class="item-label">{label}</div>
@@ -7,21 +7,52 @@
7
7
  @apply gap-0;
8
8
 
9
9
  &>.avatar {
10
- @apply -ml-2 first:ml-0;
11
- @apply outline-2 outline-background rounded-ui;
10
+ @apply -ml-3 first:ml-0;
11
+ @apply ring-3 ring-background rounded-ui;
12
12
  @apply transition-transform hover:z-10 hover:scale-110;
13
13
  }
14
14
 
15
15
  &>.avatar-group-counter {
16
- @apply -ml-2;
16
+ @apply -ml-3 z-10;
17
17
  }
18
18
  }
19
19
 
20
20
  .avatar-group-counter {
21
21
  @apply flex items-center justify-center;
22
- @apply bg-muted text-on-muted;
23
22
  @apply rounded-ui font-medium;
24
- @apply outline-2 outline-background;
23
+ @apply ring-3 ring-background;
24
+ }
25
+
26
+ .avatar-group-counter.is-primary {
27
+ @apply bg-primary text-on-primary;
28
+ }
29
+
30
+ .avatar-group-counter.is-secondary {
31
+ @apply bg-secondary text-on-secondary;
32
+ }
33
+
34
+ .avatar-group-counter.is-muted {
35
+ @apply bg-muted text-on-muted;
36
+ }
37
+
38
+ .avatar-group-counter.is-success {
39
+ @apply bg-success text-on-success;
40
+ }
41
+
42
+ .avatar-group-counter.is-warning {
43
+ @apply bg-warning text-on-warning;
44
+ }
45
+
46
+ .avatar-group-counter.is-danger {
47
+ @apply bg-danger text-on-danger;
48
+ }
49
+
50
+ .avatar-group-counter.is-info {
51
+ @apply bg-info text-on-info;
52
+ }
53
+
54
+ .avatar-group-counter.is-transparent {
55
+ @apply bg-transparent text-on-background;
25
56
  }
26
57
 
27
58
  .avatar-group-counter.is-xs {
@@ -5,6 +5,19 @@
5
5
  @apply transition-all duration-200;
6
6
  }
7
7
 
8
+ .avatar.is-clickable {
9
+ @apply cursor-pointer;
10
+ @apply hover:opacity-80 hover:scale-105;
11
+ @apply active:scale-95;
12
+ }
13
+
14
+ /* Reset button/anchor styles when used as clickable */
15
+ button.avatar,
16
+ a.avatar {
17
+ @apply border-0 bg-transparent p-0;
18
+ @apply no-underline;
19
+ }
20
+
8
21
  .avatar.is-primary {
9
22
  @apply bg-primary text-on-primary;
10
23
  }
@@ -34,9 +47,7 @@
34
47
  }
35
48
 
36
49
  .avatar.is-bordered {
37
- .avatar-image {
38
- @apply ring-2 ring-muted;
39
- }
50
+ @apply ring-3 ring-background;
40
51
  }
41
52
 
42
53
  .avatar.is-transparent {
@@ -1,25 +1,21 @@
1
1
  <script lang="ts">
2
- import Button from '../control/Button.svelte';
3
- import { Icon } from '../index.js';
2
+ import IconButton from '../control/IconButton.svelte';
3
+ import { EyeOffRegularIcon, EyeShowRegularIcon } from '../icons/index.js';
4
4
  import { cn } from '../utils/class-names.js';
5
- import type { HTMLInputAttributes } from 'svelte/elements';
6
5
 
7
6
  type Props = {
8
7
  value?: string;
9
8
  defaultValue?: string;
10
9
  placeholder?: string;
11
- type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';
12
- class?: string;
13
- inputClass?: string;
10
+ rootClass?: string;
11
+ controlClass?: string;
14
12
  onchange?: (value: string | number | undefined) => void;
15
13
  oninput?: (value: string | number | undefined) => void;
16
- variant?: 'solid' | 'outlined' | 'soft' | 'line';
17
- color?: 'primary' | 'secondary' | 'muted';
18
- inputSize?: 'small' | 'medium' | 'large';
14
+ variant?: 'primary' | 'secondary' | 'muted' | 'outlined' | 'line';
15
+ size?: 'sm' | 'md' | 'lg';
19
16
  name?: string;
20
17
  label?: string;
21
- labelOutside?: boolean;
22
- labelActive?: boolean;
18
+ islabelActive?: boolean;
23
19
  helpText?: string;
24
20
  errorText?: string;
25
21
  labels?: {
@@ -27,24 +23,24 @@
27
23
  medium?: string;
28
24
  strong?: string;
29
25
  };
30
- } & HTMLInputAttributes;
26
+ isFloatLabel?: boolean;
27
+ isSolid?: boolean;
28
+ };
31
29
 
32
30
  let {
33
31
  value = $bindable(),
34
32
  defaultValue,
35
33
  placeholder,
36
- type = 'text',
37
- class: className,
38
- inputClass,
34
+ rootClass,
35
+ controlClass,
39
36
  onchange,
40
37
  oninput,
41
- variant = 'soft',
42
- color = 'primary',
43
- inputSize = 'medium',
38
+ variant = 'outlined',
39
+ size = 'md',
44
40
  name,
45
41
  label,
46
- labelOutside,
47
- labelActive,
42
+ isFloatLabel,
43
+ islabelActive,
48
44
  helpText,
49
45
  errorText,
50
46
  labels = {
@@ -52,32 +48,30 @@
52
48
  medium: 'Medium',
53
49
  strong: 'Strong'
54
50
  },
55
- ...rest
51
+ isSolid
56
52
  }: Props = $props();
57
53
 
58
- const variants = {
59
- solid: 'field-solid',
60
- outlined: 'field-outlined',
61
- soft: 'field-soft',
62
- line: 'field-line'
63
- };
64
- const colors = {
65
- primary: 'field-primary',
66
- secondary: 'field-secondary',
67
- muted: 'field-muted'
54
+ const variantClasses = {
55
+ primary: 'is-primary',
56
+ secondary: 'is-secondary',
57
+ muted: 'is-muted',
58
+ outlined: 'is-outlined',
59
+ line: 'is-line'
68
60
  };
69
61
 
70
- const sizes = {
71
- small: 'field-small',
72
- medium: 'field-medium',
73
- large: 'field-large'
62
+ const sizeClasses = {
63
+ sm: 'is-sm',
64
+ md: 'is-md',
65
+ lg: 'is-lg'
74
66
  };
75
67
 
76
68
  const uid = $props.id();
77
69
 
70
+ let isActive = $state(false);
71
+
78
72
  let show = $state(false);
79
73
  let isFocused = $state(false);
80
- let strength = $derived(calculateStrength(value));
74
+ let strength = $derived(calculateStrength(value as string));
81
75
 
82
76
  function isBarActive(barIndex: number, score: number): boolean {
83
77
  const threshold = (barIndex + 1) * 1.5;
@@ -114,40 +108,59 @@
114
108
  }
115
109
  </script>
116
110
 
117
- <div class={cn('field', className)}>
118
- {#if labelOutside && label}
119
- <label for={`${uid}-{name}`} class="label">{label}</label>
111
+ <div class={cn('field', rootClass)}>
112
+ {#if !isFloatLabel && label}
113
+ <span class="field-label">{label}</span>
120
114
  {/if}
121
- <div
122
- class={cn('field-control', variants[variant], colors[color], sizes[inputSize])}
115
+ <label
116
+ class={cn(
117
+ 'control',
118
+ variantClasses[variant],
119
+ sizeClasses[size],
120
+ isSolid && 'is-solid',
121
+ isFloatLabel && 'is-float',
122
+ (isActive || isFocused) && 'is-active',
123
+ controlClass
124
+ )}
125
+ for={`${uid}-{name}`}
123
126
  class:is-error={errorText}
127
+ onmouseenter={() => (isActive = true)}
128
+ onmouseleave={() => (isActive = false)}
124
129
  >
125
- {#if !labelOutside && label}
126
- <label class:is-active={isFocused || value} for={`${uid}-{name}`} class="label-inside"
127
- >{label}</label
130
+ {#if isFloatLabel && label}
131
+ <span
132
+ class={cn(
133
+ 'control-label',
134
+ (isActive || isFocused || islabelActive || value !== '') && 'is-active'
135
+ )}
128
136
  >
137
+ {label}
138
+ </span>
129
139
  {/if}
130
140
  <input
131
141
  type={show ? 'text' : 'password'}
132
142
  bind:value
133
143
  id={`${uid}-{name}`}
134
- class={cn('field-input', inputClass)}
135
- placeholder={isFocused ? placeholder : ''}
144
+ class={cn(
145
+ 'control-input',
146
+ isFloatLabel && !isActive && !isFocused && value == '' && 'invisible',
147
+ controlClass
148
+ )}
149
+ {placeholder}
136
150
  {name}
137
- {defaultValue}
138
151
  onchange={(e) => onchange?.((e.target as HTMLInputElement).value)}
139
152
  oninput={(e) => oninput?.((e.target as HTMLInputElement).value)}
140
- onfocusin={() => {
141
- if (!labelActive) isFocused = true;
142
- }}
143
- onfocusout={() => {
144
- if (!labelActive) isFocused = false;
145
- }}
146
- {...rest}
153
+ onfocusin={() => (isFocused = true)}
154
+ onfocusout={() => (isFocused = false)}
147
155
  />
148
- <Button class={cn('field-eye')} onclick={() => (show = !show)}>eye icon</Button>
149
- </div>
150
- <div class={cn('field-strength', className)}>
156
+ <IconButton
157
+ variant="ghost"
158
+ size="sm"
159
+ icon={show ? EyeShowRegularIcon : EyeOffRegularIcon}
160
+ onclick={() => (show = !show)}
161
+ />
162
+ </label>
163
+ <div class={cn('field-strength')}>
151
164
  <div class="field-strength-bars">
152
165
  {#each Array(4) as _, i}
153
166
  <div
@@ -160,11 +173,9 @@
160
173
  {/each}
161
174
  </div>
162
175
  </div>
163
- {#if errorText || helpText || strength.label}
164
- <div
165
- class={cn('field-help', strength.label && 'field-strength-label', errorText && 'is-error')}
166
- >
167
- {strength.label ? strength.label : errorText ? errorText : helpText}
176
+ {#if errorText || helpText}
177
+ <div class={cn('field-help', errorText && 'is-danger')}>
178
+ {errorText || helpText}
168
179
  </div>
169
180
  {/if}
170
181
  </div>
@@ -0,0 +1,26 @@
1
+ type Props = {
2
+ value?: string;
3
+ defaultValue?: string;
4
+ placeholder?: string;
5
+ rootClass?: string;
6
+ controlClass?: string;
7
+ onchange?: (value: string | number | undefined) => void;
8
+ oninput?: (value: string | number | undefined) => void;
9
+ variant?: 'primary' | 'secondary' | 'muted' | 'outlined' | 'line';
10
+ size?: 'sm' | 'md' | 'lg';
11
+ name?: string;
12
+ label?: string;
13
+ islabelActive?: boolean;
14
+ helpText?: string;
15
+ errorText?: string;
16
+ labels?: {
17
+ weak?: string;
18
+ medium?: string;
19
+ strong?: string;
20
+ };
21
+ isFloatLabel?: boolean;
22
+ isSolid?: boolean;
23
+ };
24
+ declare const PasswordStrength: import("svelte").Component<Props, {}, "value">;
25
+ type PasswordStrength = ReturnType<typeof PasswordStrength>;
26
+ export default PasswordStrength;
@@ -84,6 +84,7 @@
84
84
  let contentEl = $state<HTMLElement>();
85
85
  let optionsEl = $state<HTMLElement>();
86
86
  let phoneInputEl = $state<HTMLInputElement>();
87
+ let searchInputEl = $state<HTMLInputElement>();
87
88
  let focusedIndex = $state(-1);
88
89
  let searchTerm = $state('');
89
90
  let searchTimeout: ReturnType<typeof setTimeout> | null = $state(null);
@@ -284,6 +285,10 @@
284
285
  await tick();
285
286
  await updatePosition();
286
287
  isOpen = true;
288
+ await tick();
289
+ setTimeout(() => {
290
+ searchInputEl?.focus();
291
+ }, 100);
287
292
  } else {
288
293
  stopEventListeners();
289
294
  focusedIndex = -1;
@@ -401,7 +406,7 @@
401
406
  />
402
407
  <span class="phone-dial-code">+{dialCode}</span>
403
408
  {:else}
404
- <Avatar name="Select country" {size} variant="transparent" />
409
+ <Icon icon={countryFlagsIcons[`country-flags:xx`] as IconData} class="h-5 w-5" />
405
410
  <span class="phone-dial-code">+--</span>
406
411
  {/if}
407
412
  <Icon icon={ChevronDown24RegularIcon} class={cn('control-arrow', isOpen && 'is-active')} />
@@ -409,7 +414,7 @@
409
414
 
410
415
  <input
411
416
  bind:this={phoneInputEl}
412
- type="tel"
417
+ type="number"
413
418
  {name}
414
419
  bind:value
415
420
  class={cn(
@@ -431,6 +436,7 @@
431
436
  <div class={cn('combo-box-search', variantClasses[variant])}>
432
437
  <Icon icon={Search24RegularIcon} class="combo-box-search-icon" />
433
438
  <input
439
+ bind:this={searchInputEl}
434
440
  type="text"
435
441
  class="combo-box-search-input"
436
442
  placeholder={searchPlaceholder}
@@ -8,6 +8,8 @@
8
8
  disabled?: boolean;
9
9
  class?: string;
10
10
  label?: string;
11
+ labelLeft?: string;
12
+ labelRight?: string;
11
13
  name?: string;
12
14
  color?: 'primary' | 'secondary' | 'muted';
13
15
  };
@@ -18,6 +20,8 @@
18
20
  disabled = false,
19
21
  class: className,
20
22
  label,
23
+ labelLeft,
24
+ labelRight,
21
25
  name,
22
26
  color = 'primary'
23
27
  }: Props = $props();
@@ -30,6 +34,9 @@
30
34
  </script>
31
35
 
32
36
  <label class={cn('toggle', className)}>
37
+ {#if labelLeft}
38
+ <span class={cn('toggle-label-left', !checked && 'is-active')}>{labelLeft}</span>
39
+ {/if}
33
40
  <input
34
41
  type="checkbox"
35
42
  class={cn('toggle-input', colors[color])}
@@ -39,6 +46,9 @@
39
46
  {disabled}
40
47
  onchange={() => onchange && onchange(checked!)}
41
48
  />
49
+ {#if labelRight}
50
+ <span class={cn('toggle-label-right', checked && 'is-active')}>{labelRight}</span>
51
+ {/if}
42
52
  {#if label}
43
53
  <span class="label">{label}</span>
44
54
  {/if}
@@ -5,6 +5,8 @@ type Props = {
5
5
  disabled?: boolean;
6
6
  class?: string;
7
7
  label?: string;
8
+ labelLeft?: string;
9
+ labelRight?: string;
8
10
  name?: string;
9
11
  color?: 'primary' | 'secondary' | 'muted';
10
12
  };
@@ -24,7 +24,7 @@
24
24
  transition: background-color 0.2s ease;
25
25
  }
26
26
  .field-strength-bar-item.active.weak {
27
- background-color: var(--color-error);
27
+ background-color: var(--color-danger);
28
28
  }
29
29
  .field-strength-bar-item.active.medium {
30
30
  background-color: var(--color-warning);
@@ -23,5 +23,14 @@
23
23
  @apply bg-on-muted;
24
24
  }
25
25
  }
26
+
27
+ .toggle-label-left,
28
+ .toggle-label-right {
29
+ @apply text-muted transition-colors duration-200;
30
+
31
+ &.is-active {
32
+ @apply text-on-muted;
33
+ }
34
+ }
26
35
  }
27
36
  }
@@ -130,3 +130,11 @@ export declare const Warning24RegularIcon: {
130
130
  body: string;
131
131
  viewbox: string;
132
132
  };
133
+ export declare const EyeShowRegularIcon: {
134
+ body: string;
135
+ viewbox: string;
136
+ };
137
+ export declare const EyeOffRegularIcon: {
138
+ body: string;
139
+ viewbox: string;
140
+ };
@@ -130,3 +130,11 @@ export const Warning24RegularIcon = {
130
130
  body: '<path fill="currentColor" d="M13 17a.999.999 0 1 0-1.998 0a.999.999 0 0 0 1.997 0m-.26-7.853a.75.75 0 0 0-1.493.103l.004 4.501l.007.102a.75.75 0 0 0 1.493-.103l-.004-4.502zm1.23-5.488c-.857-1.548-3.082-1.548-3.938 0L2.286 17.66c-.83 1.5.255 3.34 1.97 3.34h15.49c1.714 0 2.799-1.84 1.969-3.34zm-2.626.726a.75.75 0 0 1 1.313 0l7.746 14.002a.75.75 0 0 1-.657 1.113H4.256a.75.75 0 0 1-.657-1.113z"/>',
131
131
  viewbox: '0 0 24 24'
132
132
  };
133
+ export const EyeShowRegularIcon = {
134
+ body: '<g fill="none"><path d="M12 9.005a4 4 0 1 1 0 8a4 4 0 0 1 0-8zm0 1.5a2.5 2.5 0 1 0 0 5a2.5 2.5 0 0 0 0-5zM12 5.5c4.613 0 8.596 3.15 9.701 7.564a.75.75 0 1 1-1.455.365a8.503 8.503 0 0 0-16.493.004a.75.75 0 0 1-1.455-.363A10.003 10.003 0 0 1 12 5.5z" fill="currentColor"/></g>',
135
+ viewbox: '0 0 24 24'
136
+ };
137
+ export const EyeOffRegularIcon = {
138
+ body: '<path fill="currentColor" d="M2.22 2.22a.75.75 0 0 0-.073.976l.073.084l4.034 4.035a10 10 0 0 0-3.955 5.75a.75.75 0 0 0 1.455.364a8.5 8.5 0 0 1 3.58-5.034l1.81 1.81A4 4 0 0 0 14.8 15.86l5.919 5.92a.75.75 0 0 0 1.133-.977l-.073-.084l-6.113-6.114l.001-.002l-1.2-1.198l-2.87-2.87h.002l-2.88-2.877l.001-.002l-1.133-1.13L3.28 2.22a.75.75 0 0 0-1.06 0m7.984 9.045l3.535 3.536a2.5 2.5 0 0 1-3.535-3.535M12 5.5c-1 0-1.97.148-2.889.425l1.237 1.236a8.503 8.503 0 0 1 9.899 6.272a.75.75 0 0 0 1.455-.363A10 10 0 0 0 12 5.5m.195 3.51l3.801 3.8a4.003 4.003 0 0 0-3.801-3.8"/>',
139
+ viewbox: '0 0 24 24'
140
+ };
package/dist/index.d.ts CHANGED
@@ -36,7 +36,7 @@ import DateField from './form/DateField.svelte';
36
36
  import Dropzone from './form/Dropzone.svelte';
37
37
  import ImageCropper from './form/ImageCropper.svelte';
38
38
  import TextField from './form/TextField.svelte';
39
- import PasswordField from './form/PasswordField.svelte';
39
+ import PasswordStrength from './form/PasswordStrength.svelte';
40
40
  import PinField from './form/PinField.svelte';
41
41
  import RadioGroup from './form/RadioGroup.svelte';
42
42
  import PhoneField from './form/PhoneField.svelte';
@@ -76,5 +76,5 @@ import { useAuth } from './hooks/use-auth.svelte.js';
76
76
  import { theme } from './stores/theme.svelte.js';
77
77
  import { useSearch } from './hooks/use-search.svelte.js';
78
78
  import { useChat } from './hooks/use-chat.svelte.js';
79
- export { AreaChart, ArcChart, BarChart, Candlestick, LineChart, PieChart, Alert, AlertDialog, AppBar, Accordion, Avatar, AvatarGroup, Audio, Badge, Button, BottomNav, Carousel, Card, ChatBox, Checkbox, Chip, Code, Collapsible, Command, ComboBox, CsvField, DateField, Drawer, Dropzone, Divider, Dropdown, Empty, Footer, FooterNav, FooterGroup, formatCurrency, formatDate, formatNumber, getWeekdays, i18n, Icon, IconButton, ImageCropper, Item, initLanguage, Modal, Marquee, NavMenu, PasswordField, PhoneField, PinField, plural, PopoverStack, Provider, RadioGroup, Record, Scaffold, Section, Select, setLanguage, Sidebar, SideNav, Slider, t, Table, Tabs, TextField, Textarea, theme, Toast, toast, Toggle, ToggleGroup, ToggleTheme, Tooltip, useAuth, useChat, useClipboard, useFetch, useForm, useLocalStorage, useScroll, useSearch, useTable, useWebSocket, Video };
79
+ export { AreaChart, ArcChart, BarChart, Candlestick, LineChart, PieChart, Alert, AlertDialog, AppBar, Accordion, Avatar, AvatarGroup, Audio, Badge, Button, BottomNav, Carousel, Card, ChatBox, Checkbox, Chip, Code, Collapsible, Command, ComboBox, CsvField, DateField, Drawer, Dropzone, Divider, Dropdown, Empty, Footer, FooterNav, FooterGroup, formatCurrency, formatDate, formatNumber, getWeekdays, i18n, Icon, IconButton, ImageCropper, Item, initLanguage, Modal, Marquee, NavMenu, PasswordStrength, PhoneField, PinField, plural, PopoverStack, Provider, RadioGroup, Record, Scaffold, Section, Select, setLanguage, Sidebar, SideNav, Slider, t, Table, Tabs, TextField, Textarea, theme, Toast, toast, Toggle, ToggleGroup, ToggleTheme, Tooltip, useAuth, useChat, useClipboard, useFetch, useForm, useLocalStorage, useScroll, useSearch, useTable, useWebSocket, Video };
80
80
  export type { IconData, SideNavItem, SideNavSubItem };
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import DateField from './form/DateField.svelte';
36
36
  import Dropzone from './form/Dropzone.svelte';
37
37
  import ImageCropper from './form/ImageCropper.svelte';
38
38
  import TextField from './form/TextField.svelte';
39
- import PasswordField from './form/PasswordField.svelte';
39
+ import PasswordStrength from './form/PasswordStrength.svelte';
40
40
  import PinField from './form/PinField.svelte';
41
41
  import RadioGroup from './form/RadioGroup.svelte';
42
42
  import PhoneField from './form/PhoneField.svelte';
@@ -76,4 +76,4 @@ import { useAuth } from './hooks/use-auth.svelte.js';
76
76
  import { theme } from './stores/theme.svelte.js';
77
77
  import { useSearch } from './hooks/use-search.svelte.js';
78
78
  import { useChat } from './hooks/use-chat.svelte.js';
79
- export { AreaChart, ArcChart, BarChart, Candlestick, LineChart, PieChart, Alert, AlertDialog, AppBar, Accordion, Avatar, AvatarGroup, Audio, Badge, Button, BottomNav, Carousel, Card, ChatBox, Checkbox, Chip, Code, Collapsible, Command, ComboBox, CsvField, DateField, Drawer, Dropzone, Divider, Dropdown, Empty, Footer, FooterNav, FooterGroup, formatCurrency, formatDate, formatNumber, getWeekdays, i18n, Icon, IconButton, ImageCropper, Item, initLanguage, Modal, Marquee, NavMenu, PasswordField, PhoneField, PinField, plural, PopoverStack, Provider, RadioGroup, Record, Scaffold, Section, Select, setLanguage, Sidebar, SideNav, Slider, t, Table, Tabs, TextField, Textarea, theme, Toast, toast, Toggle, ToggleGroup, ToggleTheme, Tooltip, useAuth, useChat, useClipboard, useFetch, useForm, useLocalStorage, useScroll, useSearch, useTable, useWebSocket, Video };
79
+ export { AreaChart, ArcChart, BarChart, Candlestick, LineChart, PieChart, Alert, AlertDialog, AppBar, Accordion, Avatar, AvatarGroup, Audio, Badge, Button, BottomNav, Carousel, Card, ChatBox, Checkbox, Chip, Code, Collapsible, Command, ComboBox, CsvField, DateField, Drawer, Dropzone, Divider, Dropdown, Empty, Footer, FooterNav, FooterGroup, formatCurrency, formatDate, formatNumber, getWeekdays, i18n, Icon, IconButton, ImageCropper, Item, initLanguage, Modal, Marquee, NavMenu, PasswordStrength, PhoneField, PinField, plural, PopoverStack, Provider, RadioGroup, Record, Scaffold, Section, Select, setLanguage, Sidebar, SideNav, Slider, t, Table, Tabs, TextField, Textarea, theme, Toast, toast, Toggle, ToggleGroup, ToggleTheme, Tooltip, useAuth, useChat, useClipboard, useFetch, useForm, useLocalStorage, useScroll, useSearch, useTable, useWebSocket, Video };
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
- import { Item } from '../index.js';
3
- import type { IconData } from '../types.js';
2
+ import { Item, type IconData } from '../index.js';
4
3
  import { onMount, type Snippet } from 'svelte';
5
4
 
6
5
  type Option = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-svelte",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "author": {
5
5
  "name": "SappsDev",
6
6
  "email": "info@sappsdev.com"
@@ -1,28 +0,0 @@
1
- import type { HTMLInputAttributes } from 'svelte/elements';
2
- type Props = {
3
- value?: string;
4
- defaultValue?: string;
5
- placeholder?: string;
6
- type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';
7
- class?: string;
8
- inputClass?: string;
9
- onchange?: (value: string | number | undefined) => void;
10
- oninput?: (value: string | number | undefined) => void;
11
- variant?: 'solid' | 'outlined' | 'soft' | 'line';
12
- color?: 'primary' | 'secondary' | 'muted';
13
- inputSize?: 'small' | 'medium' | 'large';
14
- name?: string;
15
- label?: string;
16
- labelOutside?: boolean;
17
- labelActive?: boolean;
18
- helpText?: string;
19
- errorText?: string;
20
- labels?: {
21
- weak?: string;
22
- medium?: string;
23
- strong?: string;
24
- };
25
- } & HTMLInputAttributes;
26
- declare const PasswordField: import("svelte").Component<Props, {}, "value">;
27
- type PasswordField = ReturnType<typeof PasswordField>;
28
- export default PasswordField;