@karbonjs/ui-svelte 0.2.5 → 0.3.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/package.json +3 -2
- package/src/accordion/Accordion.svelte +198 -23
- package/src/alert/AlertMessage.svelte +114 -20
- package/src/avatar/Avatar.svelte +16 -2
- package/src/badge/Badge.svelte +99 -10
- package/src/breadcrumb/Breadcrumb.svelte +124 -13
- package/src/button/Button.svelte +106 -21
- package/src/button/ButtonBrand.svelte +229 -0
- package/src/carousel/Carousel.svelte +161 -28
- package/src/code/CodeBlock.svelte +323 -0
- package/src/data/DataTable.svelte +319 -8
- package/src/data/Pagination.svelte +168 -28
- package/src/divider/Divider.svelte +91 -10
- package/src/dropdown/Dropdown.svelte +171 -27
- package/src/editor/RichTextEditor.svelte +861 -107
- package/src/form/Checkbox.svelte +110 -18
- package/src/form/ColorPicker.svelte +28 -16
- package/src/form/DatePicker.svelte +20 -10
- package/src/form/{FormInput.svelte → Input.svelte} +41 -14
- package/src/form/Radio.svelte +86 -18
- package/src/form/Select.svelte +246 -33
- package/src/form/Slider.svelte +22 -7
- package/src/form/Textarea.svelte +53 -10
- package/src/form/Toggle.svelte +72 -18
- package/src/image/Image.svelte +6 -4
- package/src/image/ImageCompare.svelte +182 -0
- package/src/image/ImgZoom.svelte +131 -49
- package/src/index.ts +7 -1
- package/src/kbd/Kbd.svelte +4 -3
- package/src/layout/Card.svelte +12 -6
- package/src/layout/EmptyState.svelte +75 -8
- package/src/layout/PageHeader.svelte +111 -11
- package/src/overlay/Dialog.svelte +147 -67
- package/src/overlay/ImgBox.svelte +125 -21
- package/src/overlay/Modal.svelte +110 -28
- package/src/overlay/Toast.svelte +152 -55
- package/src/progress/Progress.svelte +137 -26
- package/src/skeleton/Skeleton.svelte +6 -4
- package/src/tabs/Tabs.svelte +133 -22
- package/src/tooltip/Tooltip.svelte +110 -20
package/src/overlay/Toast.svelte
CHANGED
|
@@ -1,91 +1,188 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { ButtonColor } from '@karbonjs/ui-core'
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
|
-
|
|
6
|
-
variant?:
|
|
6
|
+
type?: 'success' | 'error' | 'warning' | 'info'
|
|
7
|
+
variant?: 'default' | 'filled' | 'bordered'
|
|
8
|
+
color?: ButtonColor
|
|
9
|
+
title?: string
|
|
10
|
+
message?: string
|
|
7
11
|
duration?: number
|
|
8
12
|
dismissible?: boolean
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
showProgress?: boolean
|
|
14
|
+
position?: 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center'
|
|
15
|
+
icon?: Snippet | false
|
|
16
|
+
action?: Snippet
|
|
11
17
|
class?: string
|
|
12
|
-
|
|
18
|
+
classes?: { root?: string, icon?: string, close?: string, progress?: string }
|
|
19
|
+
onclose?: () => void
|
|
20
|
+
children?: Snippet
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
let {
|
|
16
|
-
|
|
17
|
-
variant = '
|
|
18
|
-
|
|
24
|
+
type = 'info',
|
|
25
|
+
variant = 'default',
|
|
26
|
+
color,
|
|
27
|
+
title = '',
|
|
28
|
+
message = '',
|
|
29
|
+
duration = 5000,
|
|
19
30
|
dismissible = true,
|
|
31
|
+
showProgress = true,
|
|
20
32
|
position = 'top-right',
|
|
21
|
-
|
|
33
|
+
icon,
|
|
34
|
+
action,
|
|
22
35
|
class: className = '',
|
|
23
|
-
|
|
36
|
+
classes = {},
|
|
37
|
+
onclose,
|
|
38
|
+
children
|
|
24
39
|
}: Props = $props()
|
|
25
40
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
let visible = $state(false)
|
|
42
|
+
let alive = $state(true)
|
|
43
|
+
let progress = $state(100)
|
|
44
|
+
let paused = $state(false)
|
|
45
|
+
|
|
46
|
+
const typeColors: Record<string, { bg: string, text: string, border: string }> = {
|
|
47
|
+
success: { bg: 'var(--karbon-emerald-500)', text: 'var(--karbon-emerald-400)', border: 'var(--karbon-emerald-500)' },
|
|
48
|
+
error: { bg: 'var(--karbon-red-500)', text: 'var(--karbon-red-400)', border: 'var(--karbon-red-500)' },
|
|
49
|
+
warning: { bg: 'var(--karbon-amber-500)', text: 'var(--karbon-amber-400)', border: 'var(--karbon-amber-500)' },
|
|
50
|
+
info: { bg: 'var(--karbon-blue-500)', text: 'var(--karbon-blue-400)', border: 'var(--karbon-blue-500)' },
|
|
31
51
|
}
|
|
32
52
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
const tc = $derived(color
|
|
54
|
+
? { bg: `var(--karbon-${color}-500)`, text: `var(--karbon-${color}-400)`, border: `var(--karbon-${color}-500)` }
|
|
55
|
+
: typeColors[type]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const typeIcons: Record<string, string> = {
|
|
59
|
+
success: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/>',
|
|
60
|
+
error: '<circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/>',
|
|
61
|
+
warning: '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
62
|
+
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
|
40
63
|
}
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
const posStyles: Record<string, string> = {
|
|
66
|
+
'top-right': 'top:1rem;right:1rem;',
|
|
67
|
+
'top-left': 'top:1rem;left:1rem;',
|
|
68
|
+
'top-center': 'top:1rem;left:50%;transform:translateX(-50%);',
|
|
69
|
+
'bottom-right': 'bottom:1rem;right:1rem;',
|
|
70
|
+
'bottom-left': 'bottom:1rem;left:1rem;',
|
|
71
|
+
'bottom-center': 'bottom:1rem;left:50%;transform:translateX(-50%);',
|
|
45
72
|
}
|
|
46
73
|
|
|
74
|
+
const isTop = $derived(position.startsWith('top'))
|
|
75
|
+
const slideFrom = $derived(isTop ? '-20px' : '20px')
|
|
76
|
+
|
|
77
|
+
const toastStyle = $derived.by(() => {
|
|
78
|
+
switch (variant) {
|
|
79
|
+
case 'filled':
|
|
80
|
+
return `background:${tc.bg};color:white;border:none;`
|
|
81
|
+
case 'bordered':
|
|
82
|
+
return `background:var(--karbon-bg-card);color:var(--karbon-text);border-left:3px solid ${tc.border};border-top:1px solid var(--karbon-border);border-right:1px solid var(--karbon-border);border-bottom:1px solid var(--karbon-border);`
|
|
83
|
+
default:
|
|
84
|
+
return `background:var(--karbon-bg-card);color:var(--karbon-text);border:1px solid var(--karbon-border);`
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const iconColor = $derived(variant === 'filled' ? 'white' : tc.text)
|
|
89
|
+
|
|
90
|
+
// Animation + countdown
|
|
47
91
|
$effect(() => {
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
if (alive) {
|
|
93
|
+
requestAnimationFrame(() => { visible = true })
|
|
94
|
+
|
|
95
|
+
if (duration > 0) {
|
|
96
|
+
const interval = 30
|
|
97
|
+
const step = (100 / duration) * interval
|
|
98
|
+
const timer = setInterval(() => {
|
|
99
|
+
if (!paused) {
|
|
100
|
+
progress -= step
|
|
101
|
+
if (progress <= 0) {
|
|
102
|
+
clearInterval(timer)
|
|
103
|
+
dismiss()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, interval)
|
|
107
|
+
return () => clearInterval(timer)
|
|
108
|
+
}
|
|
51
109
|
}
|
|
52
110
|
})
|
|
111
|
+
|
|
112
|
+
function dismiss() {
|
|
113
|
+
visible = false
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
alive = false
|
|
116
|
+
onclose?.()
|
|
117
|
+
}, 200)
|
|
118
|
+
}
|
|
53
119
|
</script>
|
|
54
120
|
|
|
55
|
-
{#if
|
|
121
|
+
{#if alive}
|
|
56
122
|
<div
|
|
57
|
-
class="fixed z-[60]
|
|
123
|
+
class="fixed z-[60] w-full max-w-sm pointer-events-auto"
|
|
124
|
+
style={posStyles[position]}
|
|
58
125
|
role="alert"
|
|
59
126
|
aria-live="assertive"
|
|
127
|
+
onmouseenter={() => paused = true}
|
|
128
|
+
onmouseleave={() => paused = false}
|
|
60
129
|
>
|
|
61
130
|
<div
|
|
62
|
-
class="
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
"
|
|
131
|
+
class="rounded-xl shadow-xl overflow-hidden {classes?.root ?? className}"
|
|
132
|
+
style="{toastStyle}
|
|
133
|
+
opacity:{visible ? 1 : 0};
|
|
134
|
+
transform:translateY({visible ? '0' : slideFrom});
|
|
135
|
+
transition:transform 0.25s cubic-bezier(0.16,1,0.3,1),opacity 0.2s ease;"
|
|
68
136
|
>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
137
|
+
<div class="flex items-start gap-3 px-4 py-3">
|
|
138
|
+
<!-- Icon -->
|
|
139
|
+
{#if icon !== false}
|
|
140
|
+
<div class="shrink-0 mt-0.5 {classes?.icon ?? ''}">
|
|
141
|
+
{#if icon}
|
|
142
|
+
{@render icon()}
|
|
143
|
+
{:else}
|
|
144
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={iconColor} stroke-width="2" stroke-linecap="round" stroke-linejoin="round">{@html typeIcons[type] || typeIcons.info}</svg>
|
|
145
|
+
{/if}
|
|
146
|
+
</div>
|
|
147
|
+
{/if}
|
|
148
|
+
|
|
149
|
+
<!-- Content -->
|
|
150
|
+
<div class="flex-1 min-w-0">
|
|
151
|
+
{#if title}
|
|
152
|
+
<p class="text-sm font-semibold {message || children ? 'mb-0.5' : ''}"
|
|
153
|
+
style="color:white;"
|
|
154
|
+
>{title}</p>
|
|
155
|
+
{/if}
|
|
156
|
+
{#if children}
|
|
157
|
+
<div class="text-[13px]" style="color:rgba(255,255,255,0.75);">{@render children()}</div>
|
|
158
|
+
{:else if message}
|
|
159
|
+
<p class="text-[13px]" style="color:rgba(255,255,255,0.75);">{message}</p>
|
|
160
|
+
{/if}
|
|
161
|
+
{#if action}
|
|
162
|
+
<div class="mt-2">{@render action()}</div>
|
|
163
|
+
{/if}
|
|
164
|
+
</div>
|
|
78
165
|
|
|
79
|
-
|
|
166
|
+
<!-- Close -->
|
|
167
|
+
{#if dismissible}
|
|
168
|
+
<button
|
|
169
|
+
onclick={dismiss}
|
|
170
|
+
aria-label="Fermer"
|
|
171
|
+
class="shrink-0 rounded-md p-0.5 transition-opacity opacity-50 hover:opacity-100 cursor-pointer {classes?.close ?? ''}"
|
|
172
|
+
>
|
|
173
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
174
|
+
</button>
|
|
175
|
+
{/if}
|
|
176
|
+
</div>
|
|
80
177
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</
|
|
178
|
+
<!-- Progress bar -->
|
|
179
|
+
{#if showProgress && duration > 0}
|
|
180
|
+
<div class="h-[2px] w-full {classes?.progress ?? ''}" style="background:color-mix(in srgb,{tc.bg} 15%,transparent);">
|
|
181
|
+
<div
|
|
182
|
+
class="h-full transition-none"
|
|
183
|
+
style="width:{progress}%;background:{variant === 'filled' ? 'rgba(255,255,255,0.4)' : tc.bg};"
|
|
184
|
+
></div>
|
|
185
|
+
</div>
|
|
89
186
|
{/if}
|
|
90
187
|
</div>
|
|
91
188
|
</div>
|
|
@@ -1,50 +1,161 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { ButtonColor } from '@karbonjs/ui-core'
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
value: number
|
|
6
7
|
max?: number
|
|
7
|
-
|
|
8
|
-
size?:
|
|
9
|
-
|
|
8
|
+
color?: ButtonColor
|
|
9
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
10
|
+
variant?: 'default' | 'striped' | 'gradient' | 'glow'
|
|
11
|
+
shape?: 'rounded' | 'square' | 'pill'
|
|
12
|
+
label?: boolean | 'inside' | 'outside' | 'top'
|
|
13
|
+
labelFormat?: (value: number, max: number) => string
|
|
14
|
+
indeterminate?: boolean
|
|
15
|
+
animated?: boolean
|
|
16
|
+
segments?: { value: number, color?: ButtonColor, label?: string }[]
|
|
10
17
|
class?: string
|
|
18
|
+
classes?: { root?: string, track?: string, bar?: string, label?: string }
|
|
19
|
+
children?: Snippet
|
|
11
20
|
}
|
|
12
21
|
|
|
13
22
|
let {
|
|
14
|
-
value,
|
|
23
|
+
value = 0,
|
|
15
24
|
max = 100,
|
|
16
|
-
|
|
25
|
+
color,
|
|
17
26
|
size = 'md',
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
variant = 'default',
|
|
28
|
+
shape = 'rounded',
|
|
29
|
+
label = false,
|
|
30
|
+
labelFormat,
|
|
31
|
+
indeterminate = false,
|
|
32
|
+
animated = false,
|
|
33
|
+
segments,
|
|
34
|
+
class: className = '',
|
|
35
|
+
classes = {},
|
|
36
|
+
children
|
|
20
37
|
}: Props = $props()
|
|
21
38
|
|
|
22
39
|
const percent = $derived(Math.min(Math.max((value / max) * 100, 0), 100))
|
|
40
|
+
const accent = $derived(color ? `var(--karbon-${color}-500)` : 'var(--karbon-primary)')
|
|
41
|
+
const accentLight = $derived(color ? `var(--karbon-${color}-400)` : 'var(--karbon-primary)')
|
|
23
42
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
const sizeMap = {
|
|
44
|
+
xs: { track: 2, fontSize: '9px', showInside: false },
|
|
45
|
+
sm: { track: 4, fontSize: '10px', showInside: false },
|
|
46
|
+
md: { track: 8, fontSize: '11px', showInside: false },
|
|
47
|
+
lg: { track: 12, fontSize: '11px', showInside: true },
|
|
48
|
+
xl: { track: 20, fontSize: '12px', showInside: true },
|
|
29
49
|
}
|
|
50
|
+
const s = $derived(sizeMap[size])
|
|
30
51
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
const shapeMap = { rounded: '9999px', square: '0', pill: '9999px' }
|
|
53
|
+
const radius = $derived(shapeMap[shape])
|
|
54
|
+
|
|
55
|
+
function formatLabel(v: number, m: number): string {
|
|
56
|
+
if (labelFormat) return labelFormat(v, m)
|
|
57
|
+
return `${Math.round((v / m) * 100)}%`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function barStyle(clr?: string): string {
|
|
61
|
+
const bg = clr ? `var(--karbon-${clr}-500)` : accent
|
|
62
|
+
const bgLight = clr ? `var(--karbon-${clr}-400)` : accentLight
|
|
63
|
+
switch (variant) {
|
|
64
|
+
case 'striped':
|
|
65
|
+
return `background:repeating-linear-gradient(45deg,${bg},${bg} 10px,color-mix(in srgb,${bg} 70%,transparent) 10px,color-mix(in srgb,${bg} 70%,transparent) 20px);`
|
|
66
|
+
case 'gradient':
|
|
67
|
+
return `background:linear-gradient(90deg,color-mix(in srgb,${bg} 60%,transparent),${bg},${bgLight});`
|
|
68
|
+
case 'glow':
|
|
69
|
+
return `background:${bg};box-shadow:0 0 8px color-mix(in srgb,${bg} 50%,transparent),0 0 20px color-mix(in srgb,${bg} 20%,transparent);`
|
|
70
|
+
default:
|
|
71
|
+
return `background:${bg};`
|
|
72
|
+
}
|
|
35
73
|
}
|
|
74
|
+
|
|
75
|
+
const showLabel = $derived(label === true || label === 'outside' || label === 'top' || label === 'inside')
|
|
76
|
+
const labelPos = $derived(label === 'inside' ? 'inside' : label === 'top' ? 'top' : 'outside')
|
|
36
77
|
</script>
|
|
37
78
|
|
|
38
|
-
<div class={className}>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
79
|
+
<div class="{classes?.root ?? className}">
|
|
80
|
+
<!-- Top label -->
|
|
81
|
+
{#if showLabel && labelPos === 'top'}
|
|
82
|
+
<div class="flex items-center justify-between mb-1.5">
|
|
83
|
+
{#if children}
|
|
84
|
+
<span class="text-xs font-medium {classes?.label ?? ''}" style="color:var(--karbon-text-2);">{@render children()}</span>
|
|
85
|
+
{:else}
|
|
86
|
+
<span></span>
|
|
87
|
+
{/if}
|
|
88
|
+
<span class="text-xs font-semibold {classes?.label ?? ''}" style="color:{accent};">{formatLabel(value, max)}</span>
|
|
42
89
|
</div>
|
|
43
90
|
{/if}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
91
|
+
|
|
92
|
+
<!-- Track -->
|
|
93
|
+
<div
|
|
94
|
+
class="w-full overflow-hidden relative {classes?.track ?? ''}"
|
|
95
|
+
style="height:{s.track}px;border-radius:{radius};background:var(--karbon-border,rgba(255,255,255,0.08));"
|
|
96
|
+
role="progressbar"
|
|
97
|
+
aria-valuenow={indeterminate ? undefined : value}
|
|
98
|
+
aria-valuemax={max}
|
|
99
|
+
>
|
|
100
|
+
{#if indeterminate}
|
|
101
|
+
<!-- Indeterminate animation -->
|
|
102
|
+
<div
|
|
103
|
+
class="absolute h-full {classes?.bar ?? ''}"
|
|
104
|
+
style="{barStyle()};border-radius:{radius};animation:karbon-progress-indeterminate 1.5s ease-in-out infinite;width:40%;"
|
|
105
|
+
></div>
|
|
106
|
+
{:else if segments && segments.length > 0}
|
|
107
|
+
<!-- Multi-segments -->
|
|
108
|
+
<div class="flex h-full">
|
|
109
|
+
{#each segments as seg}
|
|
110
|
+
{@const segPercent = Math.min(Math.max((seg.value / max) * 100, 0), 100)}
|
|
111
|
+
<div
|
|
112
|
+
class="h-full transition-all duration-500 ease-out first:rounded-l-inherit last:rounded-r-inherit {classes?.bar ?? ''}"
|
|
113
|
+
style="width:{segPercent}%;{barStyle(seg.color)}"
|
|
114
|
+
title={seg.label || `${Math.round(segPercent)}%`}
|
|
115
|
+
></div>
|
|
116
|
+
{/each}
|
|
117
|
+
</div>
|
|
118
|
+
{:else}
|
|
119
|
+
<!-- Single bar -->
|
|
120
|
+
<div
|
|
121
|
+
class="h-full transition-all duration-500 ease-out {classes?.bar ?? ''} {animated ? 'karbon-progress-animated' : ''}"
|
|
122
|
+
style="width:{percent}%;border-radius:{radius};{barStyle()}"
|
|
123
|
+
></div>
|
|
124
|
+
|
|
125
|
+
<!-- Inside label -->
|
|
126
|
+
{#if showLabel && labelPos === 'inside' && s.showInside && percent > 10}
|
|
127
|
+
<span
|
|
128
|
+
class="absolute right-2 top-1/2 -translate-y-1/2 font-bold {classes?.label ?? ''}"
|
|
129
|
+
style="font-size:{s.fontSize};color:white;text-shadow:0 1px 2px rgba(0,0,0,0.3);"
|
|
130
|
+
>{formatLabel(value, max)}</span>
|
|
131
|
+
{/if}
|
|
132
|
+
{/if}
|
|
49
133
|
</div>
|
|
134
|
+
|
|
135
|
+
<!-- Outside label -->
|
|
136
|
+
{#if showLabel && labelPos === 'outside'}
|
|
137
|
+
<div class="flex items-center justify-between mt-1.5">
|
|
138
|
+
{#if children}
|
|
139
|
+
<span class="text-xs {classes?.label ?? ''}" style="color:var(--karbon-text-3);">{@render children()}</span>
|
|
140
|
+
{:else}
|
|
141
|
+
<span></span>
|
|
142
|
+
{/if}
|
|
143
|
+
<span class="text-xs font-semibold {classes?.label ?? ''}" style="color:{accent};">{formatLabel(value, max)}</span>
|
|
144
|
+
</div>
|
|
145
|
+
{/if}
|
|
50
146
|
</div>
|
|
147
|
+
|
|
148
|
+
<style>
|
|
149
|
+
@keyframes karbon-progress-indeterminate {
|
|
150
|
+
0% { left: -40%; }
|
|
151
|
+
100% { left: 100%; }
|
|
152
|
+
}
|
|
153
|
+
.karbon-progress-animated {
|
|
154
|
+
background-size: 30px 30px !important;
|
|
155
|
+
animation: karbon-progress-stripe 1s linear infinite;
|
|
156
|
+
}
|
|
157
|
+
@keyframes karbon-progress-stripe {
|
|
158
|
+
0% { background-position: 30px 0; }
|
|
159
|
+
100% { background-position: 0 0; }
|
|
160
|
+
}
|
|
161
|
+
</style>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
height?: string
|
|
8
8
|
lines?: number
|
|
9
9
|
class?: string
|
|
10
|
+
classes?: { root?: string }
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
let {
|
|
@@ -14,7 +15,8 @@
|
|
|
14
15
|
width = '100%',
|
|
15
16
|
height,
|
|
16
17
|
lines = 1,
|
|
17
|
-
class: className = ''
|
|
18
|
+
class: className = '',
|
|
19
|
+
classes = {}
|
|
18
20
|
}: Props = $props()
|
|
19
21
|
|
|
20
22
|
const baseClass = 'animate-pulse bg-[var(--karbon-border,rgba(0,0,0,0.07))]'
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
</script>
|
|
30
32
|
|
|
31
33
|
{#if variant === 'text' && lines > 1}
|
|
32
|
-
<div class="space-y-2 {className}">
|
|
34
|
+
<div class="space-y-2 {classes?.root ?? className}">
|
|
33
35
|
{#each Array(lines) as _, i}
|
|
34
36
|
<div
|
|
35
37
|
class="{baseClass} {v.rounded}"
|
|
@@ -39,12 +41,12 @@
|
|
|
39
41
|
</div>
|
|
40
42
|
{:else if variant === 'circle'}
|
|
41
43
|
<div
|
|
42
|
-
class="{baseClass} {v.rounded} aspect-square {className}"
|
|
44
|
+
class="{baseClass} {v.rounded} aspect-square {classes?.root ?? className}"
|
|
43
45
|
style="width: {width === '100%' ? height ?? v.h : width}; height: {height ?? v.h}"
|
|
44
46
|
></div>
|
|
45
47
|
{:else}
|
|
46
48
|
<div
|
|
47
|
-
class="{baseClass} {v.rounded} {className}"
|
|
49
|
+
class="{baseClass} {v.rounded} {classes?.root ?? className}"
|
|
48
50
|
style="width: {width}; height: {height ?? v.h}"
|
|
49
51
|
></div>
|
|
50
52
|
{/if}
|