@karbonjs/ui-svelte 0.2.4 → 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/image/Image.svelte
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
fallback?: string
|
|
12
12
|
imgbox?: boolean
|
|
13
13
|
class?: string
|
|
14
|
+
classes?: { root?: string, img?: string }
|
|
14
15
|
onclick?: () => void
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
fallback = '',
|
|
24
25
|
imgbox = false,
|
|
25
26
|
class: className = '',
|
|
27
|
+
classes = {},
|
|
26
28
|
onclick
|
|
27
29
|
}: Props = $props()
|
|
28
30
|
|
|
@@ -65,12 +67,12 @@
|
|
|
65
67
|
</script>
|
|
66
68
|
|
|
67
69
|
{#if isClickable}
|
|
68
|
-
<button type="button" onclick={handleClick} class="group overflow-hidden {roundedClasses[rounded]} {aspectClasses[aspect]} cursor-pointer bg-transparent border-none p-0 m-0 block {className}">
|
|
69
|
-
<img src={imgSrc} {alt} onerror={handleError} class="w-full h-full object-cover transition-all duration-300 {hoverClasses[hover]}" loading="lazy" />
|
|
70
|
+
<button type="button" onclick={handleClick} class="group overflow-hidden {roundedClasses[rounded]} {aspectClasses[aspect]} cursor-pointer bg-transparent border-none p-0 m-0 block {classes?.root ?? className}">
|
|
71
|
+
<img src={imgSrc} {alt} onerror={handleError} class="w-full h-full object-cover transition-all duration-300 {hoverClasses[hover]} {classes?.img ?? ''}" loading="lazy" />
|
|
70
72
|
</button>
|
|
71
73
|
{:else}
|
|
72
|
-
<div class="group overflow-hidden {roundedClasses[rounded]} {aspectClasses[aspect]} {className}">
|
|
73
|
-
<img src={imgSrc} {alt} onerror={handleError} class="w-full h-full object-cover transition-all duration-300 {hoverClasses[hover]}" loading="lazy" />
|
|
74
|
+
<div class="group overflow-hidden {roundedClasses[rounded]} {aspectClasses[aspect]} {classes?.root ?? className}">
|
|
75
|
+
<img src={imgSrc} {alt} onerror={handleError} class="w-full h-full object-cover transition-all duration-300 {hoverClasses[hover]} {classes?.img ?? ''}" loading="lazy" />
|
|
74
76
|
</div>
|
|
75
77
|
{/if}
|
|
76
78
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ButtonColor } from '@karbonjs/ui-core'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
before: string
|
|
6
|
+
after: string
|
|
7
|
+
beforeLabel?: string
|
|
8
|
+
afterLabel?: string
|
|
9
|
+
initialPosition?: number
|
|
10
|
+
orientation?: 'horizontal' | 'vertical'
|
|
11
|
+
color?: ButtonColor
|
|
12
|
+
rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl'
|
|
13
|
+
showLabels?: boolean
|
|
14
|
+
showHandle?: boolean
|
|
15
|
+
width?: string
|
|
16
|
+
height?: string
|
|
17
|
+
class?: string
|
|
18
|
+
classes?: { root?: string, before?: string, after?: string, handle?: string, label?: string }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
before,
|
|
23
|
+
after,
|
|
24
|
+
beforeLabel = 'Avant',
|
|
25
|
+
afterLabel = 'Apres',
|
|
26
|
+
initialPosition = 50,
|
|
27
|
+
orientation = 'horizontal',
|
|
28
|
+
color,
|
|
29
|
+
rounded = 'lg',
|
|
30
|
+
showLabels = true,
|
|
31
|
+
showHandle = true,
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
class: className = '',
|
|
35
|
+
classes = {}
|
|
36
|
+
}: Props = $props()
|
|
37
|
+
|
|
38
|
+
let position = $state(initialPosition)
|
|
39
|
+
let dragging = $state(false)
|
|
40
|
+
let containerEl: HTMLDivElement
|
|
41
|
+
|
|
42
|
+
const accent = $derived(color ? `var(--karbon-${color}-500)` : 'var(--karbon-primary)')
|
|
43
|
+
const isHorizontal = $derived(orientation === 'horizontal')
|
|
44
|
+
|
|
45
|
+
const roundedMap: Record<string, string> = {
|
|
46
|
+
none: '0', sm: '0.25rem', md: '0.5rem', lg: '0.75rem', xl: '1rem'
|
|
47
|
+
}
|
|
48
|
+
const rad = $derived(roundedMap[rounded])
|
|
49
|
+
|
|
50
|
+
function updatePosition(e: MouseEvent | TouchEvent) {
|
|
51
|
+
if (!containerEl) return
|
|
52
|
+
const rect = containerEl.getBoundingClientRect()
|
|
53
|
+
let clientPos: number
|
|
54
|
+
let size: number
|
|
55
|
+
|
|
56
|
+
if ('touches' in e) {
|
|
57
|
+
clientPos = isHorizontal ? e.touches[0].clientX - rect.left : e.touches[0].clientY - rect.top
|
|
58
|
+
} else {
|
|
59
|
+
clientPos = isHorizontal ? e.clientX - rect.left : e.clientY - rect.top
|
|
60
|
+
}
|
|
61
|
+
size = isHorizontal ? rect.width : rect.height
|
|
62
|
+
position = Math.min(Math.max((clientPos / size) * 100, 0), 100)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleMouseDown(e: MouseEvent) {
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
dragging = true
|
|
68
|
+
updatePosition(e)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleTouchStart(e: TouchEvent) {
|
|
72
|
+
dragging = true
|
|
73
|
+
updatePosition(e)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handleMove(e: MouseEvent) {
|
|
77
|
+
if (dragging) updatePosition(e)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleTouchMove(e: TouchEvent) {
|
|
81
|
+
if (dragging) { e.preventDefault(); updatePosition(e) }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handleEnd() {
|
|
85
|
+
dragging = false
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<svelte:window
|
|
90
|
+
onmousemove={handleMove}
|
|
91
|
+
onmouseup={handleEnd}
|
|
92
|
+
ontouchmove={handleTouchMove}
|
|
93
|
+
ontouchend={handleEnd}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<div
|
|
97
|
+
bind:this={containerEl}
|
|
98
|
+
class="karbon-imgcompare relative select-none overflow-hidden {classes?.root ?? className}"
|
|
99
|
+
style="border-radius:{rad};{width ? `width:${width};` : 'width:100%;'}{height ? `height:${height};` : ''}aspect-ratio:{height ? 'auto' : '16/10'};cursor:{dragging ? 'grabbing' : 'col-resize'};"
|
|
100
|
+
onmousedown={handleMouseDown}
|
|
101
|
+
ontouchstart={handleTouchStart}
|
|
102
|
+
role="slider"
|
|
103
|
+
aria-valuenow={Math.round(position)}
|
|
104
|
+
aria-valuemin={0}
|
|
105
|
+
aria-valuemax={100}
|
|
106
|
+
aria-label="Comparateur d'images"
|
|
107
|
+
tabindex={0}
|
|
108
|
+
onkeydown={(e) => {
|
|
109
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { e.preventDefault(); position = Math.max(position - 2, 0) }
|
|
110
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { e.preventDefault(); position = Math.min(position + 2, 100) }
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<!-- After image (background, full) -->
|
|
114
|
+
<img
|
|
115
|
+
src={after}
|
|
116
|
+
alt={afterLabel}
|
|
117
|
+
class="absolute inset-0 w-full h-full object-cover {classes?.after ?? ''}"
|
|
118
|
+
draggable="false"
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
<!-- Before image (clipped via clip-path) -->
|
|
122
|
+
<img
|
|
123
|
+
src={before}
|
|
124
|
+
alt={beforeLabel}
|
|
125
|
+
class="absolute inset-0 w-full h-full object-cover {classes?.before ?? ''}"
|
|
126
|
+
style="clip-path:{isHorizontal ? `inset(0 ${100 - position}% 0 0)` : `inset(0 0 ${100 - position}% 0)`};"
|
|
127
|
+
draggable="false"
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
<!-- Divider line -->
|
|
131
|
+
<div
|
|
132
|
+
class="absolute"
|
|
133
|
+
style="{isHorizontal
|
|
134
|
+
? `left:${position}%;top:0;bottom:0;width:2px;transform:translateX(-1px);`
|
|
135
|
+
: `top:${position}%;left:0;right:0;height:2px;transform:translateY(-1px);`
|
|
136
|
+
}background:{accent};z-index:5;pointer-events:none;"
|
|
137
|
+
></div>
|
|
138
|
+
|
|
139
|
+
<!-- Handle -->
|
|
140
|
+
{#if showHandle}
|
|
141
|
+
<div
|
|
142
|
+
class="absolute z-10"
|
|
143
|
+
style="{isHorizontal
|
|
144
|
+
? `left:${position}%;top:50%;transform:translate(-50%,-50%);`
|
|
145
|
+
: `top:${position}%;left:50%;transform:translate(-50%,-50%);`
|
|
146
|
+
}pointer-events:none;"
|
|
147
|
+
>
|
|
148
|
+
<div
|
|
149
|
+
class="rounded-full flex items-center justify-center shadow-lg {classes?.handle ?? ''}"
|
|
150
|
+
style="width:36px;height:36px;background:{accent};color:white;box-shadow:0 2px 10px rgba(0,0,0,0.3),0 0 0 3px rgba(255,255,255,0.3);"
|
|
151
|
+
>
|
|
152
|
+
{#if isHorizontal}
|
|
153
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15-5-5 5-5"/><path d="m17 9 5 5-5 5"/></svg>
|
|
154
|
+
{:else}
|
|
155
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m15 7-5-5-5 5"/><path d="m9 17 5 5 5-5"/></svg>
|
|
156
|
+
{/if}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
{/if}
|
|
160
|
+
|
|
161
|
+
<!-- Labels -->
|
|
162
|
+
{#if showLabels}
|
|
163
|
+
<div
|
|
164
|
+
class="absolute z-5 pointer-events-none {classes?.label ?? ''}"
|
|
165
|
+
style="{isHorizontal ? 'top:12px;left:12px;' : 'top:12px;left:12px;'}"
|
|
166
|
+
>
|
|
167
|
+
<span
|
|
168
|
+
class="rounded-md px-2 py-1 text-[11px] font-semibold"
|
|
169
|
+
style="background:rgba(0,0,0,0.5);color:white;backdrop-filter:blur(4px);"
|
|
170
|
+
>{beforeLabel}</span>
|
|
171
|
+
</div>
|
|
172
|
+
<div
|
|
173
|
+
class="absolute z-5 pointer-events-none {classes?.label ?? ''}"
|
|
174
|
+
style="{isHorizontal ? 'top:12px;right:12px;' : 'bottom:12px;right:12px;'}"
|
|
175
|
+
>
|
|
176
|
+
<span
|
|
177
|
+
class="rounded-md px-2 py-1 text-[11px] font-semibold"
|
|
178
|
+
style="background:rgba(0,0,0,0.5);color:white;backdrop-filter:blur(4px);"
|
|
179
|
+
>{afterLabel}</span>
|
|
180
|
+
</div>
|
|
181
|
+
{/if}
|
|
182
|
+
</div>
|
package/src/image/ImgZoom.svelte
CHANGED
|
@@ -1,56 +1,76 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type { ButtonColor } from '@karbonjs/ui-core'
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
src: string
|
|
6
6
|
zoomSrc?: string
|
|
7
7
|
alt?: string
|
|
8
8
|
zoom?: number
|
|
9
|
-
trigger?:
|
|
10
|
-
|
|
9
|
+
trigger?: 'hover' | 'click'
|
|
10
|
+
mode?: 'overlay' | 'lens' | 'side'
|
|
11
|
+
lensSize?: number
|
|
12
|
+
rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'
|
|
13
|
+
showHint?: boolean
|
|
14
|
+
color?: ButtonColor
|
|
15
|
+
width?: string
|
|
16
|
+
height?: string
|
|
11
17
|
class?: string
|
|
18
|
+
classes?: { root?: string, img?: string, lens?: string, overlay?: string }
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
let {
|
|
15
22
|
src,
|
|
16
23
|
zoomSrc,
|
|
17
24
|
alt = '',
|
|
18
|
-
zoom = 2,
|
|
25
|
+
zoom = 2.5,
|
|
19
26
|
trigger = 'hover',
|
|
27
|
+
mode = 'overlay',
|
|
28
|
+
lensSize = 120,
|
|
20
29
|
rounded = 'md',
|
|
21
|
-
|
|
30
|
+
showHint = true,
|
|
31
|
+
color,
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
class: className = '',
|
|
35
|
+
classes = {}
|
|
22
36
|
}: Props = $props()
|
|
23
37
|
|
|
24
|
-
let container
|
|
38
|
+
let container: HTMLElement
|
|
25
39
|
let zooming = $state(false)
|
|
26
40
|
let posX = $state(50)
|
|
27
41
|
let posY = $state(50)
|
|
42
|
+
let mouseX = $state(0)
|
|
43
|
+
let mouseY = $state(0)
|
|
44
|
+
let hovered = $state(false)
|
|
28
45
|
|
|
29
|
-
const
|
|
30
|
-
none: 'rounded-none',
|
|
31
|
-
sm: 'rounded',
|
|
32
|
-
md: 'rounded-lg',
|
|
33
|
-
lg: 'rounded-xl',
|
|
34
|
-
full: 'rounded-full'
|
|
35
|
-
}
|
|
36
|
-
|
|
46
|
+
const accent = $derived(color ? `var(--karbon-${color}-500)` : 'var(--karbon-primary)')
|
|
37
47
|
const zoomImage = $derived(zoomSrc || src)
|
|
38
48
|
|
|
49
|
+
const roundedMap: Record<string, string> = {
|
|
50
|
+
none: '0', sm: '0.25rem', md: '0.5rem', lg: '0.75rem', xl: '1rem', full: '9999px'
|
|
51
|
+
}
|
|
52
|
+
const rad = $derived(roundedMap[rounded])
|
|
53
|
+
|
|
39
54
|
function updatePosition(e: MouseEvent) {
|
|
55
|
+
if (!container) return
|
|
40
56
|
const rect = container.getBoundingClientRect()
|
|
41
57
|
posX = ((e.clientX - rect.left) / rect.width) * 100
|
|
42
58
|
posY = ((e.clientY - rect.top) / rect.height) * 100
|
|
59
|
+
mouseX = e.clientX - rect.left
|
|
60
|
+
mouseY = e.clientY - rect.top
|
|
43
61
|
}
|
|
44
62
|
|
|
45
63
|
function handleMouseEnter(e: MouseEvent) {
|
|
64
|
+
hovered = true
|
|
46
65
|
if (trigger === 'hover') { zooming = true; updatePosition(e) }
|
|
47
66
|
}
|
|
48
67
|
|
|
49
68
|
function handleMouseMove(e: MouseEvent) {
|
|
50
|
-
if (zooming) updatePosition(e)
|
|
69
|
+
if (zooming || hovered) updatePosition(e)
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
function handleMouseLeave() {
|
|
73
|
+
hovered = false
|
|
54
74
|
if (trigger === 'hover') zooming = false
|
|
55
75
|
}
|
|
56
76
|
|
|
@@ -58,39 +78,101 @@
|
|
|
58
78
|
zooming = !zooming
|
|
59
79
|
if (zooming) updatePosition(e)
|
|
60
80
|
}
|
|
61
|
-
|
|
62
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
63
|
-
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); zooming = !zooming }
|
|
64
|
-
}
|
|
65
81
|
</script>
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
<div
|
|
84
|
+
bind:this={container}
|
|
85
|
+
class="karbon-imgzoom relative overflow-hidden inline-block {classes?.root ?? className}"
|
|
86
|
+
style="border-radius:{rad};{width ? `width:${width};` : ''}{height ? `height:${height};` : ''}cursor:{zooming ? 'crosshair' : trigger === 'click' ? 'zoom-in' : 'crosshair'};"
|
|
87
|
+
role="img"
|
|
88
|
+
aria-label={alt || 'Image zoomable'}
|
|
89
|
+
tabindex={0}
|
|
90
|
+
onmouseenter={handleMouseEnter}
|
|
91
|
+
onmousemove={handleMouseMove}
|
|
92
|
+
onmouseleave={handleMouseLeave}
|
|
93
|
+
onclick={trigger === 'click' ? handleClick : undefined}
|
|
94
|
+
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); zooming = !zooming } }}
|
|
95
|
+
>
|
|
96
|
+
<!-- Base image -->
|
|
97
|
+
<img
|
|
98
|
+
{src}
|
|
99
|
+
{alt}
|
|
100
|
+
class="w-full h-full object-cover block transition-transform duration-200 {classes?.img ?? ''}"
|
|
101
|
+
style={zooming && mode === 'overlay' ? `transform:scale(${zoom});transform-origin:${posX}% ${posY}%;` : ''}
|
|
102
|
+
loading="lazy"
|
|
103
|
+
draggable="false"
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<!-- Hint icon -->
|
|
107
|
+
{#if showHint && !zooming && hovered}
|
|
108
|
+
<div
|
|
109
|
+
class="absolute top-3 right-3 rounded-full px-2 py-1 flex items-center gap-1 pointer-events-none"
|
|
110
|
+
style="background:rgba(0,0,0,0.5);color:white;font-size:11px;backdrop-filter:blur(4px);opacity:0.8;"
|
|
111
|
+
>
|
|
112
|
+
<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"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/><path d="M11 8v6"/><path d="M8 11h6"/></svg>
|
|
113
|
+
<span>Zoom</span>
|
|
114
|
+
</div>
|
|
115
|
+
{/if}
|
|
116
|
+
|
|
117
|
+
<!-- Lens mode -->
|
|
118
|
+
{#if mode === 'lens' && zooming}
|
|
119
|
+
<div
|
|
120
|
+
class="absolute rounded-full pointer-events-none shadow-xl {classes?.lens ?? ''}"
|
|
121
|
+
style="
|
|
122
|
+
width:{lensSize}px;height:{lensSize}px;
|
|
123
|
+
left:{mouseX - lensSize / 2}px;top:{mouseY - lensSize / 2}px;
|
|
124
|
+
background-image:url({zoomImage});
|
|
125
|
+
background-size:{zoom * 100}%;
|
|
126
|
+
background-position:{posX}% {posY}%;
|
|
127
|
+
background-repeat:no-repeat;
|
|
128
|
+
border:3px solid {accent};
|
|
129
|
+
box-shadow:0 0 0 1px rgba(0,0,0,0.1),0 8px 25px rgba(0,0,0,0.3);
|
|
130
|
+
z-index:10;
|
|
131
|
+
"
|
|
132
|
+
></div>
|
|
133
|
+
<!-- Crosshair on source -->
|
|
134
|
+
<div
|
|
135
|
+
class="absolute pointer-events-none"
|
|
136
|
+
style="
|
|
137
|
+
width:{lensSize / zoom}px;height:{lensSize / zoom}px;
|
|
138
|
+
left:{mouseX - lensSize / zoom / 2}px;top:{mouseY - lensSize / zoom / 2}px;
|
|
139
|
+
border:1.5px solid {accent};
|
|
140
|
+
border-radius:2px;
|
|
141
|
+
background:color-mix(in srgb,{accent} 8%,transparent);
|
|
142
|
+
z-index:5;
|
|
143
|
+
"
|
|
144
|
+
></div>
|
|
145
|
+
{/if}
|
|
146
|
+
|
|
147
|
+
<!-- Overlay mode zoom layer (CSS transform on img handles this) -->
|
|
148
|
+
|
|
149
|
+
<!-- Side mode -->
|
|
150
|
+
{#if mode === 'side' && zooming}
|
|
151
|
+
<div
|
|
152
|
+
class="absolute top-0 shadow-2xl pointer-events-none {classes?.overlay ?? ''}"
|
|
153
|
+
style="
|
|
154
|
+
left:calc(100% + 12px);
|
|
155
|
+
width:{container?.offsetWidth || 300}px;
|
|
156
|
+
height:{container?.offsetHeight || 300}px;
|
|
157
|
+
background-image:url({zoomImage});
|
|
158
|
+
background-size:{zoom * 100}%;
|
|
159
|
+
background-position:{posX}% {posY}%;
|
|
160
|
+
background-repeat:no-repeat;
|
|
161
|
+
border-radius:{rad};
|
|
162
|
+
border:1px solid var(--karbon-border);
|
|
163
|
+
z-index:10;
|
|
164
|
+
"
|
|
165
|
+
></div>
|
|
166
|
+
<!-- Crosshair indicator -->
|
|
167
|
+
<div
|
|
168
|
+
class="absolute pointer-events-none"
|
|
169
|
+
style="
|
|
170
|
+
width:{100 / zoom}%;height:{100 / zoom}%;
|
|
171
|
+
left:{posX - 50 / zoom}%;top:{posY - 50 / zoom}%;
|
|
172
|
+
border:2px solid {accent};
|
|
173
|
+
background:color-mix(in srgb,{accent} 10%,transparent);
|
|
174
|
+
z-index:5;
|
|
175
|
+
"
|
|
176
|
+
></div>
|
|
177
|
+
{/if}
|
|
178
|
+
</div>
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// button
|
|
2
2
|
export { default as Button } from './button/Button.svelte'
|
|
3
|
+
export { default as ButtonBrand } from './button/ButtonBrand.svelte'
|
|
3
4
|
|
|
4
5
|
// form
|
|
5
|
-
export { default as
|
|
6
|
+
export { default as Input } from './form/Input.svelte'
|
|
7
|
+
export { default as FormInput } from './form/Input.svelte' // alias retro-compat
|
|
6
8
|
export { default as Select } from './form/Select.svelte'
|
|
7
9
|
export { default as Checkbox } from './form/Checkbox.svelte'
|
|
8
10
|
export { default as Toggle } from './form/Toggle.svelte'
|
|
@@ -30,8 +32,12 @@ export { default as PageHeader } from './layout/PageHeader.svelte'
|
|
|
30
32
|
export { default as EmptyState } from './layout/EmptyState.svelte'
|
|
31
33
|
|
|
32
34
|
// image
|
|
35
|
+
// code
|
|
36
|
+
export { default as CodeBlock } from './code/CodeBlock.svelte'
|
|
37
|
+
|
|
33
38
|
export { default as Image } from './image/Image.svelte'
|
|
34
39
|
export { default as ImgZoom } from './image/ImgZoom.svelte'
|
|
40
|
+
export { default as ImageCompare } from './image/ImageCompare.svelte'
|
|
35
41
|
|
|
36
42
|
// carousel
|
|
37
43
|
export { default as Carousel } from './carousel/Carousel.svelte'
|
package/src/kbd/Kbd.svelte
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
interface Props {
|
|
3
3
|
keys: string[]
|
|
4
4
|
class?: string
|
|
5
|
+
classes?: { root?: string, key?: string }
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
let { keys, class: className = '' }: Props = $props()
|
|
8
|
+
let { keys, class: className = '', classes = {} }: Props = $props()
|
|
8
9
|
</script>
|
|
9
10
|
|
|
10
|
-
<span class="inline-flex items-center gap-1 {className}">
|
|
11
|
+
<span class="inline-flex items-center gap-1 {classes?.root ?? className}">
|
|
11
12
|
{#each keys as key, i}
|
|
12
13
|
{#if i > 0}
|
|
13
14
|
<span class="text-[var(--karbon-text-4,#b5b2cc)] text-xs">+</span>
|
|
14
15
|
{/if}
|
|
15
|
-
<kbd class="inline-flex items-center justify-center min-w-[1.5rem] h-6 px-1.5 rounded-md border border-[var(--karbon-border,rgba(0,0,0,0.07))] bg-[var(--karbon-bg-2,#e8e6f0)] text-[var(--karbon-text-2,#5a567e)] text-[11px] font-mono font-medium shadow-sm">
|
|
16
|
+
<kbd class="inline-flex items-center justify-center min-w-[1.5rem] h-6 px-1.5 rounded-md border border-[var(--karbon-border,rgba(0,0,0,0.07))] bg-[var(--karbon-bg-2,#e8e6f0)] text-[var(--karbon-text-2,#5a567e)] text-[11px] font-mono font-medium shadow-sm {classes?.key ?? ''}">
|
|
16
17
|
{key}
|
|
17
18
|
</kbd>
|
|
18
19
|
{/each}
|
package/src/layout/Card.svelte
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
variant?: CardVariant
|
|
7
7
|
padding?: CardPadding
|
|
8
8
|
hoverable?: boolean
|
|
9
|
+
noPadding?: boolean
|
|
9
10
|
title?: string
|
|
10
11
|
icon?: any
|
|
11
12
|
class?: string
|
|
13
|
+
classes?: { root?: string, header?: string, body?: string }
|
|
12
14
|
children: Snippet
|
|
13
15
|
header?: Snippet
|
|
14
16
|
footer?: Snippet
|
|
@@ -18,17 +20,19 @@
|
|
|
18
20
|
variant = 'default',
|
|
19
21
|
padding = 'md',
|
|
20
22
|
hoverable = false,
|
|
23
|
+
noPadding = false,
|
|
21
24
|
title = '',
|
|
22
25
|
icon: Icon,
|
|
23
26
|
class: className = '',
|
|
27
|
+
classes = {},
|
|
24
28
|
children,
|
|
25
29
|
header,
|
|
26
30
|
footer
|
|
27
31
|
}: Props = $props()
|
|
28
32
|
|
|
29
33
|
const variantClasses: Record<string, string> = {
|
|
30
|
-
default: 'bg-[var(--karbon-bg-card,#fff)] border border-[var(--karbon-border,rgba(0,0,0,0.07))]',
|
|
31
|
-
elevated: 'bg-[var(--karbon-bg-card,#fff)] shadow-lg',
|
|
34
|
+
default: 'bg-[var(--karbon-bg-card,#fff)] border border-[var(--karbon-border,rgba(0,0,0,0.07))] shadow-sm',
|
|
35
|
+
elevated: 'bg-[var(--karbon-bg-card,#fff)] border border-[var(--karbon-border,rgba(0,0,0,0.07))] shadow-lg',
|
|
32
36
|
outlined: 'border-2 border-[var(--karbon-border,rgba(0,0,0,0.07))]',
|
|
33
37
|
ghost: 'bg-transparent'
|
|
34
38
|
}
|
|
@@ -39,15 +43,17 @@
|
|
|
39
43
|
md: 'p-5',
|
|
40
44
|
lg: 'p-8'
|
|
41
45
|
}
|
|
46
|
+
|
|
47
|
+
const bodyPadding = $derived(noPadding ? '' : paddingClasses[padding])
|
|
42
48
|
</script>
|
|
43
49
|
|
|
44
|
-
<div class="rounded-xl overflow-hidden {variantClasses[variant]} {hoverable ? 'transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5' : ''} {className}">
|
|
50
|
+
<div class="rounded-xl overflow-hidden {variantClasses[variant]} {hoverable ? 'transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5' : ''} {classes?.root ?? className}">
|
|
45
51
|
{#if header}
|
|
46
|
-
<div class="px-5 py-3.5 border-b border-[var(--karbon-border,rgba(0,0,0,0.07))]">
|
|
52
|
+
<div class="px-5 py-3.5 border-b border-[var(--karbon-border,rgba(0,0,0,0.07))] {classes?.header ?? ''}">
|
|
47
53
|
{@render header()}
|
|
48
54
|
</div>
|
|
49
55
|
{:else if title}
|
|
50
|
-
<div class="flex items-center gap-2 px-5 py-3.5 border-b border-[var(--karbon-border,rgba(0,0,0,0.07))] text-[var(--karbon-text-2,#5a567e)] text-[0.825rem] font-semibold">
|
|
56
|
+
<div class="flex items-center gap-2 px-5 py-3.5 border-b border-[var(--karbon-border,rgba(0,0,0,0.07))] text-[var(--karbon-text-2,#5a567e)] text-[0.825rem] font-semibold {classes?.header ?? ''}">
|
|
51
57
|
{#if Icon}
|
|
52
58
|
<Icon class="w-4 h-4" />
|
|
53
59
|
{/if}
|
|
@@ -55,7 +61,7 @@
|
|
|
55
61
|
</div>
|
|
56
62
|
{/if}
|
|
57
63
|
|
|
58
|
-
<div class={
|
|
64
|
+
<div class="{bodyPadding} {classes?.body ?? ''}">
|
|
59
65
|
{@render children()}
|
|
60
66
|
</div>
|
|
61
67
|
|
|
@@ -1,25 +1,92 @@
|
|
|
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?:
|
|
8
|
+
icon?: Snippet
|
|
9
|
+
color?: ButtonColor
|
|
10
|
+
size?: 'sm' | 'md' | 'lg'
|
|
11
|
+
variant?: 'default' | 'bordered' | 'filled' | 'minimal'
|
|
12
|
+
actions?: Snippet
|
|
13
|
+
illustration?: Snippet
|
|
14
|
+
class?: string
|
|
15
|
+
classes?: { root?: string, icon?: string, title?: string, description?: string, actions?: string }
|
|
6
16
|
}
|
|
7
17
|
|
|
8
18
|
let {
|
|
9
19
|
title,
|
|
10
20
|
description = '',
|
|
11
|
-
icon
|
|
21
|
+
icon,
|
|
22
|
+
color,
|
|
23
|
+
size = 'md',
|
|
24
|
+
variant = 'default',
|
|
25
|
+
actions,
|
|
26
|
+
illustration,
|
|
27
|
+
class: className = '',
|
|
28
|
+
classes = {}
|
|
12
29
|
}: Props = $props()
|
|
30
|
+
|
|
31
|
+
const accent = $derived(color ? `var(--karbon-${color}-500)` : 'var(--karbon-primary)')
|
|
32
|
+
const accentLight = $derived(color ? `var(--karbon-${color}-400)` : 'var(--karbon-text-4)')
|
|
33
|
+
|
|
34
|
+
const sizeMap = {
|
|
35
|
+
sm: { py: 'py-8', title: 'text-sm', desc: 'text-xs', iconBox: 40, iconSize: 20, maxW: '18rem', gap: 'gap-2' },
|
|
36
|
+
md: { py: 'py-12', title: 'text-base', desc: 'text-sm', iconBox: 52, iconSize: 24, maxW: '22rem', gap: 'gap-3' },
|
|
37
|
+
lg: { py: 'py-16', title: 'text-lg', desc: 'text-base', iconBox: 64, iconSize: 28, maxW: '26rem', gap: 'gap-4' },
|
|
38
|
+
}
|
|
39
|
+
const s = $derived(sizeMap[size])
|
|
40
|
+
|
|
41
|
+
function rootStyle(): string {
|
|
42
|
+
switch (variant) {
|
|
43
|
+
case 'bordered': return `border:1px solid var(--karbon-border);border-radius:0.75rem;background:var(--karbon-bg-card);`
|
|
44
|
+
case 'filled': return `border-radius:0.75rem;background:color-mix(in srgb,${accent} 5%,transparent);border:1px solid color-mix(in srgb,${accent} 10%,transparent);`
|
|
45
|
+
case 'minimal': return ''
|
|
46
|
+
default: return ''
|
|
47
|
+
}
|
|
48
|
+
}
|
|
13
49
|
</script>
|
|
14
50
|
|
|
15
|
-
<div
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
|
|
51
|
+
<div
|
|
52
|
+
class="text-center {s.py} px-6 {classes?.root ?? className}"
|
|
53
|
+
style={rootStyle()}
|
|
54
|
+
>
|
|
55
|
+
<!-- Illustration -->
|
|
56
|
+
{#if illustration}
|
|
57
|
+
<div class="mb-4 flex justify-center">
|
|
58
|
+
{@render illustration()}
|
|
59
|
+
</div>
|
|
60
|
+
{:else if icon}
|
|
61
|
+
<!-- Icon -->
|
|
62
|
+
<div
|
|
63
|
+
class="mx-auto mb-4 rounded-2xl flex items-center justify-center {classes?.icon ?? ''}"
|
|
64
|
+
style="width:{s.iconBox}px;height:{s.iconBox}px;background:color-mix(in srgb,{accent} 10%,transparent);color:{accentLight};"
|
|
65
|
+
>
|
|
66
|
+
{@render icon()}
|
|
67
|
+
</div>
|
|
68
|
+
{:else}
|
|
69
|
+
<!-- Default icon -->
|
|
70
|
+
<div
|
|
71
|
+
class="mx-auto mb-4 rounded-2xl flex items-center justify-center {classes?.icon ?? ''}"
|
|
72
|
+
style="width:{s.iconBox}px;height:{s.iconBox}px;background:var(--karbon-nav-hover-bg);color:var(--karbon-text-4);"
|
|
73
|
+
>
|
|
74
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={s.iconSize} height={s.iconSize} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>
|
|
19
75
|
</div>
|
|
20
76
|
{/if}
|
|
21
|
-
|
|
77
|
+
|
|
78
|
+
<!-- Title -->
|
|
79
|
+
<p class="{s.title} font-semibold {classes?.title ?? ''}" style="color:var(--karbon-text);margin:0;">{title}</p>
|
|
80
|
+
|
|
81
|
+
<!-- Description -->
|
|
22
82
|
{#if description}
|
|
23
|
-
<p class="
|
|
83
|
+
<p class="{s.desc} mt-1.5 mx-auto {classes?.description ?? ''}" style="color:var(--karbon-text-3);margin-top:0.375rem;max-width:{s.maxW};">{description}</p>
|
|
84
|
+
{/if}
|
|
85
|
+
|
|
86
|
+
<!-- Actions -->
|
|
87
|
+
{#if actions}
|
|
88
|
+
<div class="mt-5 flex items-center justify-center {s.gap} {classes?.actions ?? ''}">
|
|
89
|
+
{@render actions()}
|
|
90
|
+
</div>
|
|
24
91
|
{/if}
|
|
25
92
|
</div>
|