@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
|
@@ -1,27 +1,127 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { ButtonColor } from '@karbonjs/ui-core'
|
|
4
|
+
|
|
2
5
|
interface Props {
|
|
3
6
|
title: string
|
|
4
7
|
description?: string
|
|
5
|
-
icon?:
|
|
6
|
-
|
|
8
|
+
icon?: Snippet
|
|
9
|
+
color?: ButtonColor
|
|
10
|
+
size?: 'sm' | 'md' | 'lg'
|
|
11
|
+
variant?: 'default' | 'bordered' | 'filled' | 'clean'
|
|
12
|
+
backHref?: string
|
|
13
|
+
backLabel?: string
|
|
14
|
+
badge?: string
|
|
15
|
+
breadcrumbs?: { label: string, href?: string }[]
|
|
16
|
+
actions?: Snippet
|
|
17
|
+
class?: string
|
|
18
|
+
classes?: { root?: string, title?: string, description?: string, icon?: string, actions?: string, breadcrumb?: string }
|
|
7
19
|
}
|
|
8
20
|
|
|
9
21
|
let {
|
|
10
22
|
title,
|
|
11
23
|
description = '',
|
|
12
|
-
icon
|
|
13
|
-
|
|
24
|
+
icon,
|
|
25
|
+
color,
|
|
26
|
+
size = 'md',
|
|
27
|
+
variant = 'default',
|
|
28
|
+
backHref,
|
|
29
|
+
backLabel = 'Retour',
|
|
30
|
+
badge,
|
|
31
|
+
breadcrumbs,
|
|
32
|
+
actions,
|
|
33
|
+
class: className = '',
|
|
34
|
+
classes = {}
|
|
14
35
|
}: Props = $props()
|
|
36
|
+
|
|
37
|
+
const accent = $derived(color ? `var(--karbon-${color}-500)` : 'var(--karbon-primary)')
|
|
38
|
+
const accentLight = $derived(color ? `var(--karbon-${color}-400)` : 'var(--karbon-primary)')
|
|
39
|
+
|
|
40
|
+
const sizeMap = {
|
|
41
|
+
sm: { title: 'text-lg', desc: 'text-xs', iconBox: 32, iconSize: 16, pad: 'pb-3', gap: 'gap-2.5' },
|
|
42
|
+
md: { title: 'text-xl', desc: 'text-sm', iconBox: 40, iconSize: 20, pad: 'pb-4', gap: 'gap-3' },
|
|
43
|
+
lg: { title: 'text-2xl', desc: 'text-base', iconBox: 48, iconSize: 24, pad: 'pb-5', gap: 'gap-4' },
|
|
44
|
+
}
|
|
45
|
+
const s = $derived(sizeMap[size])
|
|
46
|
+
|
|
47
|
+
function rootStyle(): string {
|
|
48
|
+
switch (variant) {
|
|
49
|
+
case 'default': return `padding-bottom:${size === 'sm' ? '0.75rem' : size === 'lg' ? '1.25rem' : '1rem'};border-bottom:1px solid var(--karbon-border);margin-bottom:0.25rem;`
|
|
50
|
+
case 'bordered': return `padding:${size === 'sm' ? '0.75rem' : size === 'lg' ? '1.25rem' : '1rem'};border:1px solid var(--karbon-border);border-radius:0.75rem;background:var(--karbon-bg-card);`
|
|
51
|
+
case 'filled': return `padding:${size === 'sm' ? '0.75rem' : size === 'lg' ? '1.5rem' : '1rem'};border-radius:0.75rem;background:color-mix(in srgb,${accent} 8%,transparent);border:1px solid color-mix(in srgb,${accent} 15%,transparent);`
|
|
52
|
+
case 'clean': return ''
|
|
53
|
+
default: return ''
|
|
54
|
+
}
|
|
55
|
+
}
|
|
15
56
|
</script>
|
|
16
57
|
|
|
17
|
-
<div class="
|
|
18
|
-
|
|
19
|
-
|
|
58
|
+
<div class="{classes?.root ?? className}" style={rootStyle()}>
|
|
59
|
+
<!-- Breadcrumbs -->
|
|
60
|
+
{#if breadcrumbs && breadcrumbs.length > 0}
|
|
61
|
+
<nav class="flex items-center gap-1.5 mb-2 {classes?.breadcrumb ?? ''}" aria-label="Breadcrumb">
|
|
62
|
+
{#each breadcrumbs as crumb, i}
|
|
63
|
+
{#if i > 0}
|
|
64
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--karbon-text-4);"><path d="m9 18 6-6-6-6"/></svg>
|
|
65
|
+
{/if}
|
|
66
|
+
{#if crumb.href && i < breadcrumbs.length - 1}
|
|
67
|
+
<a href={crumb.href} class="text-xs transition-colors" style="color:var(--karbon-text-3);"
|
|
68
|
+
onmouseenter={(e) => { (e.currentTarget as HTMLElement).style.color = accent }}
|
|
69
|
+
onmouseleave={(e) => { (e.currentTarget as HTMLElement).style.color = 'var(--karbon-text-3)' }}
|
|
70
|
+
>{crumb.label}</a>
|
|
71
|
+
{:else}
|
|
72
|
+
<span class="text-xs font-medium" style="color:var(--karbon-text-2);">{crumb.label}</span>
|
|
73
|
+
{/if}
|
|
74
|
+
{/each}
|
|
75
|
+
</nav>
|
|
20
76
|
{/if}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
77
|
+
|
|
78
|
+
<!-- Back button -->
|
|
79
|
+
{#if backHref}
|
|
80
|
+
<a
|
|
81
|
+
href={backHref}
|
|
82
|
+
class="inline-flex items-center gap-1.5 text-xs font-medium mb-2 transition-colors"
|
|
83
|
+
style="color:var(--karbon-text-3);"
|
|
84
|
+
onmouseenter={(e) => { (e.currentTarget as HTMLElement).style.color = accent }}
|
|
85
|
+
onmouseleave={(e) => { (e.currentTarget as HTMLElement).style.color = 'var(--karbon-text-3)' }}
|
|
86
|
+
>
|
|
87
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>
|
|
88
|
+
{backLabel}
|
|
89
|
+
</a>
|
|
90
|
+
{/if}
|
|
91
|
+
|
|
92
|
+
<!-- Main row -->
|
|
93
|
+
<div class="flex items-start {s.gap}">
|
|
94
|
+
<!-- Icon -->
|
|
95
|
+
{#if icon}
|
|
96
|
+
<div
|
|
97
|
+
class="shrink-0 rounded-xl flex items-center justify-center {classes?.icon ?? ''}"
|
|
98
|
+
style="width:{s.iconBox}px;height:{s.iconBox}px;background:color-mix(in srgb,{accent} 12%,transparent);color:{accentLight};"
|
|
99
|
+
>
|
|
100
|
+
{@render icon()}
|
|
101
|
+
</div>
|
|
102
|
+
{/if}
|
|
103
|
+
|
|
104
|
+
<!-- Title + description -->
|
|
105
|
+
<div class="flex-1 min-w-0">
|
|
106
|
+
<div class="flex items-center gap-2">
|
|
107
|
+
<h1 class="{s.title} font-bold {classes?.title ?? ''}" style="color:var(--karbon-text);margin:0;">{title}</h1>
|
|
108
|
+
{#if badge}
|
|
109
|
+
<span
|
|
110
|
+
class="rounded-full px-2 py-0.5 text-[10px] font-semibold"
|
|
111
|
+
style="background:color-mix(in srgb,{accent} 15%,transparent);color:{accentLight};"
|
|
112
|
+
>{badge}</span>
|
|
113
|
+
{/if}
|
|
114
|
+
</div>
|
|
115
|
+
{#if description}
|
|
116
|
+
<p class="{s.desc} mt-1 {classes?.description ?? ''}" style="color:var(--karbon-text-3);margin:0;">{description}</p>
|
|
117
|
+
{/if}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<!-- Actions -->
|
|
121
|
+
{#if actions}
|
|
122
|
+
<div class="shrink-0 flex items-center gap-2 {classes?.actions ?? ''}">
|
|
123
|
+
{@render actions()}
|
|
124
|
+
</div>
|
|
25
125
|
{/if}
|
|
26
126
|
</div>
|
|
27
127
|
</div>
|
|
@@ -1,128 +1,208 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
|
-
import type {
|
|
3
|
+
import type { ButtonColor } from '@karbonjs/ui-core'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
open: boolean
|
|
7
7
|
title: string
|
|
8
|
-
|
|
9
|
-
variant?:
|
|
10
|
-
backdrop?:
|
|
8
|
+
message?: string
|
|
9
|
+
variant?: 'info' | 'warning' | 'danger' | 'success'
|
|
10
|
+
backdrop?: 'blur' | 'dark' | 'transparent'
|
|
11
|
+
color?: ButtonColor
|
|
11
12
|
confirmLabel?: string
|
|
12
13
|
cancelLabel?: string
|
|
14
|
+
confirmInput?: string
|
|
15
|
+
confirmInputLabel?: string
|
|
16
|
+
confirmInputPlaceholder?: string
|
|
13
17
|
loading?: boolean
|
|
14
18
|
class?: string
|
|
19
|
+
classes?: { overlay?: string, content?: string }
|
|
15
20
|
onconfirm: () => void
|
|
16
21
|
oncancel: () => void
|
|
17
22
|
icon?: Snippet
|
|
23
|
+
children?: Snippet
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
let {
|
|
21
27
|
open = $bindable(false),
|
|
22
28
|
title,
|
|
23
|
-
|
|
29
|
+
message = '',
|
|
24
30
|
variant = 'info',
|
|
25
|
-
backdrop = '
|
|
31
|
+
backdrop = 'blur',
|
|
32
|
+
color,
|
|
26
33
|
confirmLabel = 'Confirmer',
|
|
27
34
|
cancelLabel = 'Annuler',
|
|
35
|
+
confirmInput,
|
|
36
|
+
confirmInputLabel,
|
|
37
|
+
confirmInputPlaceholder,
|
|
28
38
|
loading = false,
|
|
29
39
|
class: className = '',
|
|
40
|
+
classes = {},
|
|
30
41
|
onconfirm,
|
|
31
42
|
oncancel,
|
|
32
|
-
icon
|
|
43
|
+
icon,
|
|
44
|
+
children
|
|
33
45
|
}: Props = $props()
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
let visible = $state(false)
|
|
48
|
+
let inputValue = $state('')
|
|
49
|
+
|
|
50
|
+
$effect(() => {
|
|
51
|
+
if (open) {
|
|
52
|
+
inputValue = ''
|
|
53
|
+
requestAnimationFrame(() => { visible = true })
|
|
54
|
+
document.body.style.overflow = 'hidden'
|
|
55
|
+
} else {
|
|
56
|
+
visible = false
|
|
57
|
+
document.body.style.overflow = ''
|
|
58
|
+
}
|
|
59
|
+
return () => { document.body.style.overflow = '' }
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const confirmInputLabelText = $derived(confirmInputLabel ?? `Tapez "${confirmInput}" pour confirmer`)
|
|
63
|
+
const isConfirmDisabled = $derived(loading || (confirmInput ? inputValue !== confirmInput : false))
|
|
64
|
+
|
|
65
|
+
// Variant colors
|
|
66
|
+
const variantColors: Record<string, { bg: string, text: string, btn: string, btnHover: string }> = {
|
|
67
|
+
info: { bg: 'var(--karbon-blue-500)', text: 'var(--karbon-blue-400)', btn: 'var(--karbon-blue-500)', btnHover: 'var(--karbon-blue-600)' },
|
|
68
|
+
warning: { bg: 'var(--karbon-amber-500)', text: 'var(--karbon-amber-400)', btn: 'var(--karbon-amber-500)', btnHover: 'var(--karbon-amber-600)' },
|
|
69
|
+
danger: { bg: 'var(--karbon-red-500)', text: 'var(--karbon-red-400)', btn: 'var(--karbon-red-500)', btnHover: 'var(--karbon-red-600)' },
|
|
70
|
+
success: { bg: 'var(--karbon-emerald-500)', text: 'var(--karbon-emerald-400)', btn: 'var(--karbon-emerald-500)', btnHover: 'var(--karbon-emerald-600)' },
|
|
40
71
|
}
|
|
41
72
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
const vc = $derived(color
|
|
74
|
+
? { bg: `var(--karbon-${color}-500)`, text: `var(--karbon-${color}-400)`, btn: `var(--karbon-${color}-500)`, btnHover: `var(--karbon-${color}-600)` }
|
|
75
|
+
: variantColors[variant]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
// SVG icons per variant
|
|
79
|
+
const variantIcons: Record<string, string> = {
|
|
80
|
+
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
|
81
|
+
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"/>',
|
|
82
|
+
danger: '<circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/>',
|
|
83
|
+
success: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/>',
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const backdropStyles: Record<string, string> = {
|
|
87
|
+
blur: 'background:rgba(0,0,0,0.5);backdrop-filter:blur(8px);',
|
|
88
|
+
dark: 'background:rgba(0,0,0,0.6);',
|
|
89
|
+
transparent: 'background:transparent;',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let btnHovered = $state(false)
|
|
93
|
+
|
|
94
|
+
function cancel() {
|
|
95
|
+
visible = false
|
|
96
|
+
setTimeout(() => oncancel(), 150)
|
|
47
97
|
}
|
|
48
98
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
danger: 'bg-red-500 hover:bg-red-600',
|
|
53
|
-
success: 'bg-emerald-500 hover:bg-emerald-600'
|
|
99
|
+
function confirm() {
|
|
100
|
+
if (isConfirmDisabled) return
|
|
101
|
+
onconfirm()
|
|
54
102
|
}
|
|
55
103
|
|
|
56
104
|
function handleKeydown(e: KeyboardEvent) {
|
|
57
|
-
if (e.key === 'Escape')
|
|
105
|
+
if (e.key === 'Escape' && open) cancel()
|
|
106
|
+
if (e.key === 'Enter' && open && !isConfirmDisabled) confirm()
|
|
58
107
|
}
|
|
59
108
|
</script>
|
|
60
109
|
|
|
61
|
-
<svelte:window onkeydown={
|
|
110
|
+
<svelte:window onkeydown={handleKeydown} />
|
|
62
111
|
|
|
63
112
|
{#if open}
|
|
64
|
-
|
|
65
|
-
|
|
113
|
+
<div
|
|
114
|
+
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
115
|
+
style="opacity:{visible ? 1 : 0};transition:opacity 0.15s ease;"
|
|
116
|
+
>
|
|
117
|
+
<!-- Backdrop -->
|
|
66
118
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
67
|
-
|
|
68
|
-
<div class="fixed inset-0 {backdropClasses[backdrop]}" onclick={oncancel}></div>
|
|
69
|
-
{/if}
|
|
119
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
70
120
|
<div
|
|
71
|
-
class="
|
|
121
|
+
class="fixed inset-0 {classes?.overlay ?? ''}"
|
|
122
|
+
style="{backdropStyles[backdrop]}transition:opacity 0.15s ease;opacity:{visible ? 1 : 0};"
|
|
123
|
+
onclick={cancel}
|
|
124
|
+
></div>
|
|
125
|
+
|
|
126
|
+
<!-- Content -->
|
|
127
|
+
<div
|
|
128
|
+
class="relative z-10 w-full max-w-md rounded-2xl p-6 {classes?.content ?? className}"
|
|
129
|
+
style="background:var(--karbon-bg-card);border:1px solid var(--karbon-border);
|
|
130
|
+
box-shadow:0 25px 60px -12px rgba(0,0,0,0.4);
|
|
131
|
+
transform:{visible ? 'scale(1)' : 'scale(0.95)'};
|
|
132
|
+
transition:transform 0.2s cubic-bezier(0.16,1,0.3,1);
|
|
133
|
+
opacity:{visible ? 1 : 0};"
|
|
72
134
|
role="alertdialog"
|
|
73
135
|
aria-modal="true"
|
|
74
|
-
aria-labelledby="karbon-dialog-title"
|
|
75
|
-
aria-describedby={description ? 'karbon-dialog-desc' : undefined}
|
|
76
136
|
>
|
|
137
|
+
<!-- Icon + Title -->
|
|
77
138
|
<div class="flex flex-col items-center text-center">
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{:else if variant === 'success'}
|
|
89
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>
|
|
90
|
-
{:else}
|
|
91
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
|
|
92
|
-
{/if}
|
|
93
|
-
</div>
|
|
94
|
-
{/if}
|
|
139
|
+
<div
|
|
140
|
+
class="w-14 h-14 rounded-2xl flex items-center justify-center mb-4"
|
|
141
|
+
style="background:color-mix(in srgb,{vc.bg} 12%,transparent);"
|
|
142
|
+
>
|
|
143
|
+
{#if icon}
|
|
144
|
+
<span style="color:{vc.text};">{@render icon()}</span>
|
|
145
|
+
{:else}
|
|
146
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke={vc.text} stroke-width="2" stroke-linecap="round" stroke-linejoin="round">{@html variantIcons[variant] || variantIcons.info}</svg>
|
|
147
|
+
{/if}
|
|
148
|
+
</div>
|
|
95
149
|
|
|
96
|
-
<h3
|
|
97
|
-
{title}
|
|
98
|
-
</h3>
|
|
150
|
+
<h3 class="text-lg font-semibold" style="color:var(--karbon-text);">{title}</h3>
|
|
99
151
|
|
|
100
|
-
{#if
|
|
101
|
-
<p
|
|
102
|
-
{description}
|
|
103
|
-
</p>
|
|
152
|
+
{#if message}
|
|
153
|
+
<p class="mt-2 text-sm leading-relaxed" style="color:var(--karbon-text-3);">{message}</p>
|
|
104
154
|
{/if}
|
|
105
155
|
</div>
|
|
106
156
|
|
|
157
|
+
<!-- Custom children -->
|
|
158
|
+
{#if children}
|
|
159
|
+
<div class="mt-4">
|
|
160
|
+
{@render children()}
|
|
161
|
+
</div>
|
|
162
|
+
{/if}
|
|
163
|
+
|
|
164
|
+
<!-- Confirm input -->
|
|
165
|
+
{#if confirmInput}
|
|
166
|
+
<div class="mt-5 text-left">
|
|
167
|
+
<label class="block text-xs font-medium mb-1.5" style="color:var(--karbon-text-2);">
|
|
168
|
+
{confirmInputLabelText}
|
|
169
|
+
</label>
|
|
170
|
+
<input
|
|
171
|
+
type="text"
|
|
172
|
+
bind:value={inputValue}
|
|
173
|
+
placeholder={confirmInputPlaceholder ?? confirmInput}
|
|
174
|
+
class="w-full px-3 py-2.5 rounded-lg text-sm outline-none transition-colors"
|
|
175
|
+
style="background:var(--karbon-bg-input);border:1px solid {inputValue === confirmInput ? vc.bg : 'var(--karbon-border-input)'};color:var(--karbon-text);"
|
|
176
|
+
onfocus={(e) => { (e.currentTarget as HTMLElement).style.boxShadow = `0 0 0 3px color-mix(in srgb, ${vc.bg} 15%, transparent)` }}
|
|
177
|
+
onblur={(e) => { (e.currentTarget as HTMLElement).style.boxShadow = 'none' }}
|
|
178
|
+
/>
|
|
179
|
+
{#if confirmInput && inputValue && inputValue !== confirmInput}
|
|
180
|
+
<p class="mt-1 text-[11px]" style="color:var(--karbon-red-400);">Le texte ne correspond pas</p>
|
|
181
|
+
{/if}
|
|
182
|
+
</div>
|
|
183
|
+
{/if}
|
|
184
|
+
|
|
185
|
+
<!-- Buttons -->
|
|
107
186
|
<div class="mt-6 flex gap-3">
|
|
108
187
|
<button
|
|
109
|
-
onclick={
|
|
188
|
+
onclick={cancel}
|
|
110
189
|
disabled={loading}
|
|
111
|
-
class="flex-1 px-4 py-2.5 rounded-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
|
190
|
+
class="flex-1 px-4 py-2.5 rounded-xl text-sm font-medium transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
191
|
+
style="color:var(--karbon-text-2);border:1px solid var(--karbon-border);"
|
|
192
|
+
onmouseenter={(e) => { (e.currentTarget as HTMLElement).style.background = 'var(--karbon-nav-hover-bg)' }}
|
|
193
|
+
onmouseleave={(e) => { (e.currentTarget as HTMLElement).style.background = 'transparent' }}
|
|
116
194
|
>
|
|
117
195
|
{cancelLabel}
|
|
118
196
|
</button>
|
|
119
197
|
<button
|
|
120
|
-
onclick={
|
|
121
|
-
disabled={
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
198
|
+
onclick={confirm}
|
|
199
|
+
disabled={isConfirmDisabled}
|
|
200
|
+
onmouseenter={() => btnHovered = true}
|
|
201
|
+
onmouseleave={() => btnHovered = false}
|
|
202
|
+
class="flex-1 px-4 py-2.5 rounded-xl text-sm font-semibold text-white transition-all cursor-pointer
|
|
203
|
+
disabled:opacity-40 disabled:cursor-not-allowed
|
|
125
204
|
inline-flex items-center justify-center gap-2"
|
|
205
|
+
style="background:{btnHovered && !isConfirmDisabled ? vc.btnHover : vc.btn};"
|
|
126
206
|
>
|
|
127
207
|
{#if loading}
|
|
128
208
|
<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" class="animate-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
|