@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
|
@@ -1,32 +1,143 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type { ButtonColor } from '@karbonjs/ui-core'
|
|
3
|
+
|
|
4
|
+
interface BreadcrumbItem {
|
|
5
|
+
label: string
|
|
6
|
+
href?: string
|
|
7
|
+
icon?: string
|
|
8
|
+
}
|
|
3
9
|
|
|
4
10
|
interface Props {
|
|
5
11
|
items: BreadcrumbItem[]
|
|
6
|
-
separator?: string
|
|
12
|
+
separator?: 'chevron' | 'slash' | 'dot' | 'arrow' | 'dash' | string
|
|
13
|
+
variant?: 'default' | 'pills' | 'bordered'
|
|
14
|
+
color?: ButtonColor
|
|
15
|
+
size?: 'sm' | 'md' | 'lg'
|
|
16
|
+
collapse?: number
|
|
7
17
|
class?: string
|
|
18
|
+
classes?: { root?: string, item?: string, separator?: string, active?: string, link?: string }
|
|
8
19
|
}
|
|
9
20
|
|
|
10
21
|
let {
|
|
11
22
|
items,
|
|
12
|
-
separator = '
|
|
13
|
-
|
|
23
|
+
separator = 'chevron',
|
|
24
|
+
variant = 'default',
|
|
25
|
+
color,
|
|
26
|
+
size = 'md',
|
|
27
|
+
collapse = 0,
|
|
28
|
+
class: className = '',
|
|
29
|
+
classes = {}
|
|
14
30
|
}: Props = $props()
|
|
31
|
+
|
|
32
|
+
function sanitizeSvg(html: string): string {
|
|
33
|
+
return html.replace(/on\w+\s*=/gi, '').replace(/<script/gi, '<script')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const accent = $derived(color ? `var(--karbon-${color}-500)` : 'var(--karbon-primary)')
|
|
37
|
+
|
|
38
|
+
const sizeMap = {
|
|
39
|
+
sm: { text: 'text-xs', gap: 'gap-1', icon: 12, sepSize: 10, px: 'px-1.5 py-0.5' },
|
|
40
|
+
md: { text: 'text-sm', gap: 'gap-1.5', icon: 14, sepSize: 12, px: 'px-2 py-0.5' },
|
|
41
|
+
lg: { text: 'text-base', gap: 'gap-2', icon: 16, sepSize: 14, px: 'px-2.5 py-1' },
|
|
42
|
+
}
|
|
43
|
+
const s = $derived(sizeMap[size])
|
|
44
|
+
|
|
45
|
+
const separators: Record<string, string> = {
|
|
46
|
+
chevron: '<path d="m9 18 6-6-6-6"/>',
|
|
47
|
+
slash: '<line x1="16" y1="4" x2="8" y2="20"/>',
|
|
48
|
+
dot: '<circle cx="12" cy="12" r="3"/>',
|
|
49
|
+
arrow: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
|
|
50
|
+
dash: '<line x1="5" y1="12" x2="19" y2="12"/>',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Collapse: show first, ellipsis, then last N items
|
|
54
|
+
const displayItems = $derived.by(() => {
|
|
55
|
+
if (collapse <= 0 || items.length <= collapse + 2) return items
|
|
56
|
+
return [
|
|
57
|
+
items[0],
|
|
58
|
+
{ label: '...', href: undefined, icon: undefined } as BreadcrumbItem,
|
|
59
|
+
...items.slice(-(collapse))
|
|
60
|
+
]
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
let expanded = $state(false)
|
|
64
|
+
const finalItems = $derived(expanded ? items : displayItems)
|
|
15
65
|
</script>
|
|
16
66
|
|
|
17
|
-
<nav aria-label="Breadcrumb" class={className}>
|
|
18
|
-
<ol class="flex items-center gap
|
|
19
|
-
{#each
|
|
67
|
+
<nav aria-label="Breadcrumb" class="{classes?.root ?? className}">
|
|
68
|
+
<ol class="flex flex-wrap items-center {s.gap} {s.text}">
|
|
69
|
+
{#each finalItems as item, i}
|
|
70
|
+
{@const isLast = i === finalItems.length - 1}
|
|
71
|
+
{@const isEllipsis = item.label === '...'}
|
|
72
|
+
|
|
20
73
|
{#if i > 0}
|
|
21
|
-
<li class="
|
|
74
|
+
<li class="select-none {classes?.separator ?? ''}" aria-hidden="true" style="color:var(--karbon-text-4);">
|
|
75
|
+
{#if separators[separator]}
|
|
76
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={s.sepSize} height={s.sepSize} viewBox="0 0 24 24" fill={separator === 'dot' ? 'currentColor' : 'none'} stroke={separator === 'dot' ? 'none' : 'currentColor'} stroke-width="2" stroke-linecap="round" stroke-linejoin="round">{@html separators[separator]}</svg>
|
|
77
|
+
{:else}
|
|
78
|
+
<span>{separator}</span>
|
|
79
|
+
{/if}
|
|
80
|
+
</li>
|
|
22
81
|
{/if}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
82
|
+
|
|
83
|
+
<li class="{classes?.item ?? ''}">
|
|
84
|
+
{#if isEllipsis}
|
|
85
|
+
<button
|
|
86
|
+
onclick={() => expanded = true}
|
|
87
|
+
class="rounded-md transition-colors cursor-pointer {s.px}"
|
|
88
|
+
style="color:var(--karbon-text-3);"
|
|
89
|
+
onmouseenter={(e) => { (e.currentTarget as HTMLElement).style.background = 'var(--karbon-nav-hover-bg)' }}
|
|
90
|
+
onmouseleave={(e) => { (e.currentTarget as HTMLElement).style.background = 'transparent' }}
|
|
91
|
+
aria-label="Afficher le chemin complet"
|
|
92
|
+
>···</button>
|
|
93
|
+
{:else if isLast}
|
|
94
|
+
<!-- Active (last) item -->
|
|
95
|
+
{#if variant === 'pills'}
|
|
96
|
+
<span
|
|
97
|
+
class="inline-flex items-center {s.gap} rounded-full font-medium {s.px}"
|
|
98
|
+
style="background:color-mix(in srgb,{accent} 15%,transparent);color:{accent};"
|
|
99
|
+
>
|
|
100
|
+
{#if item.icon}<span class="shrink-0">{@html sanitizeSvg(item.icon)}</span>{/if}
|
|
101
|
+
{item.label}
|
|
102
|
+
</span>
|
|
103
|
+
{:else if variant === 'bordered'}
|
|
104
|
+
<span
|
|
105
|
+
class="inline-flex items-center {s.gap} rounded-md font-medium {s.px}"
|
|
106
|
+
style="border:1px solid {accent};color:{accent};"
|
|
107
|
+
>
|
|
108
|
+
{#if item.icon}<span class="shrink-0">{@html sanitizeSvg(item.icon)}</span>{/if}
|
|
109
|
+
{item.label}
|
|
110
|
+
</span>
|
|
111
|
+
{:else}
|
|
112
|
+
<span
|
|
113
|
+
class="inline-flex items-center {s.gap} font-semibold {classes?.active ?? ''}"
|
|
114
|
+
style="color:{color ? accent : 'var(--karbon-text)'};"
|
|
115
|
+
>
|
|
116
|
+
{#if item.icon}<span class="shrink-0">{@html sanitizeSvg(item.icon)}</span>{/if}
|
|
117
|
+
{item.label}
|
|
118
|
+
</span>
|
|
119
|
+
{/if}
|
|
120
|
+
{:else}
|
|
121
|
+
<!-- Link item -->
|
|
122
|
+
<a
|
|
123
|
+
href={item.href || '#'}
|
|
124
|
+
class="inline-flex items-center {s.gap} transition-colors {classes?.link ?? ''} {variant === 'pills' ? `rounded-full ${s.px}` : ''} {variant === 'bordered' ? `rounded-md ${s.px}` : ''}"
|
|
125
|
+
style="color:var(--karbon-text-3);"
|
|
126
|
+
onmouseenter={(e) => {
|
|
127
|
+
const el = e.currentTarget as HTMLElement
|
|
128
|
+
el.style.color = color ? accent : 'var(--karbon-text)'
|
|
129
|
+
if (variant === 'pills') el.style.background = 'var(--karbon-nav-hover-bg)'
|
|
130
|
+
if (variant === 'bordered') el.style.background = 'var(--karbon-nav-hover-bg)'
|
|
131
|
+
}}
|
|
132
|
+
onmouseleave={(e) => {
|
|
133
|
+
const el = e.currentTarget as HTMLElement
|
|
134
|
+
el.style.color = 'var(--karbon-text-3)'
|
|
135
|
+
el.style.background = 'transparent'
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
{#if item.icon}<span class="shrink-0">{@html sanitizeSvg(item.icon)}</span>{/if}
|
|
26
139
|
{item.label}
|
|
27
140
|
</a>
|
|
28
|
-
{:else}
|
|
29
|
-
<span class="text-[var(--karbon-text,#1a1635)] font-medium">{item.label}</span>
|
|
30
141
|
{/if}
|
|
31
142
|
</li>
|
|
32
143
|
{/each}
|
package/src/button/Button.svelte
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
|
-
import type { ButtonVariant, ButtonSize } from '@karbonjs/ui-core'
|
|
3
|
+
import type { ButtonVariant, ButtonSize, ButtonColor, ButtonShape } from '@karbonjs/ui-core'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
variant?: ButtonVariant
|
|
7
7
|
size?: ButtonSize
|
|
8
|
+
color?: ButtonColor
|
|
9
|
+
shape?: ButtonShape
|
|
8
10
|
type?: 'button' | 'submit'
|
|
9
11
|
disabled?: boolean
|
|
10
12
|
loading?: boolean
|
|
@@ -12,13 +14,16 @@
|
|
|
12
14
|
arrow?: boolean
|
|
13
15
|
fullWidth?: boolean
|
|
14
16
|
class?: string
|
|
17
|
+
classes?: { root?: string }
|
|
15
18
|
onclick?: () => void
|
|
16
19
|
children: Snippet
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
let {
|
|
20
|
-
variant = '
|
|
23
|
+
variant = 'solid',
|
|
21
24
|
size = 'md',
|
|
25
|
+
color,
|
|
26
|
+
shape = 'rounded',
|
|
22
27
|
type = 'button',
|
|
23
28
|
disabled = false,
|
|
24
29
|
loading = false,
|
|
@@ -26,24 +31,83 @@
|
|
|
26
31
|
arrow = false,
|
|
27
32
|
fullWidth = false,
|
|
28
33
|
class: className = '',
|
|
34
|
+
classes = {},
|
|
29
35
|
onclick,
|
|
30
36
|
children
|
|
31
37
|
}: Props = $props()
|
|
32
38
|
|
|
33
39
|
const isDisabled = $derived(disabled || loading)
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
secondary: 'bg-[var(--karbon-bg-2)] text-[var(--karbon-text-2)] hover:bg-[var(--karbon-border)] focus:ring-[var(--karbon-primary)]',
|
|
38
|
-
danger: 'bg-[var(--karbon-danger)] text-white hover:bg-red-600 focus:ring-[var(--karbon-danger)]',
|
|
39
|
-
ghost: 'text-[var(--karbon-text-3)] hover:bg-[var(--karbon-nav-hover-bg)] focus:ring-[var(--karbon-primary)]',
|
|
40
|
-
outline: 'border border-[var(--karbon-border)] text-[var(--karbon-text-2)] hover:bg-[var(--karbon-nav-hover-bg)] focus:ring-[var(--karbon-primary)]'
|
|
41
|
+
function c(shade: number): string {
|
|
42
|
+
return color ? `var(--karbon-${color}-${shade})` : ''
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
// Primary color references
|
|
46
|
+
const pri = $derived(color ? c(500) : 'var(--karbon-primary)')
|
|
47
|
+
const priHover = $derived(color ? c(600) : 'var(--karbon-primary-hover)')
|
|
48
|
+
const priLight = $derived(color ? c(400) : 'var(--karbon-primary)')
|
|
49
|
+
const priFg = $derived(color ? 'white' : 'var(--karbon-primary-foreground, white)')
|
|
50
|
+
|
|
51
|
+
// Build base + hover as CSS custom properties on the element
|
|
52
|
+
// This way hover never "loses" base styles
|
|
53
|
+
const style = $derived.by(() => {
|
|
54
|
+
let vars = ''
|
|
55
|
+
switch (variant) {
|
|
56
|
+
case 'solid':
|
|
57
|
+
vars = `--kb-bg:${pri};--kb-bg-h:${priHover};--kb-c:${priFg};--kb-c-h:${priFg};--kb-b:none;--kb-b-h:none;--kb-sh:none;--kb-sh-h:none`
|
|
58
|
+
break
|
|
59
|
+
case 'flat':
|
|
60
|
+
vars = `--kb-bg:color-mix(in srgb,${pri} 15%,transparent);--kb-bg-h:color-mix(in srgb,${pri} 25%,transparent);--kb-c:${priLight};--kb-c-h:${priLight};--kb-b:none;--kb-b-h:none;--kb-sh:none;--kb-sh-h:none`
|
|
61
|
+
break
|
|
62
|
+
case 'bordered':
|
|
63
|
+
vars = `--kb-bg:transparent;--kb-bg-h:color-mix(in srgb,${pri} 8%,transparent);--kb-c:${priLight};--kb-c-h:${priLight};--kb-b:2px solid ${priLight};--kb-b-h:2px solid ${pri};--kb-sh:none;--kb-sh-h:none`
|
|
64
|
+
break
|
|
65
|
+
case 'light':
|
|
66
|
+
vars = `--kb-bg:transparent;--kb-bg-h:color-mix(in srgb,${pri} 10%,transparent);--kb-c:${priLight};--kb-c-h:${pri};--kb-b:none;--kb-b-h:none;--kb-sh:none;--kb-sh-h:none`
|
|
67
|
+
break
|
|
68
|
+
case 'outline':
|
|
69
|
+
vars = `--kb-bg:transparent;--kb-bg-h:color-mix(in srgb,${pri} 6%,transparent);--kb-c:${priLight};--kb-c-h:${priLight};--kb-b:1px solid color-mix(in srgb,${priLight} 35%,transparent);--kb-b-h:1px solid ${priLight};--kb-sh:none;--kb-sh-h:none`
|
|
70
|
+
break
|
|
71
|
+
case 'ghost':
|
|
72
|
+
vars = color
|
|
73
|
+
? `--kb-bg:transparent;--kb-bg-h:color-mix(in srgb,${pri} 10%,transparent);--kb-c:${priLight};--kb-c-h:${priLight};--kb-b:none;--kb-b-h:none;--kb-sh:none;--kb-sh-h:none`
|
|
74
|
+
: `--kb-bg:transparent;--kb-bg-h:var(--karbon-nav-hover-bg,rgba(255,255,255,0.05));--kb-c:var(--karbon-text-3);--kb-c-h:var(--karbon-text-2);--kb-b:none;--kb-b-h:none;--kb-sh:none;--kb-sh-h:none`
|
|
75
|
+
break
|
|
76
|
+
case 'shadow':
|
|
77
|
+
vars = `--kb-bg:${pri};--kb-bg-h:${priHover};--kb-c:${priFg};--kb-c-h:${priFg};--kb-b:none;--kb-b-h:none;--kb-sh:0 4px 14px 0 color-mix(in srgb,${pri} 40%,transparent);--kb-sh-h:0 6px 20px 0 color-mix(in srgb,${pri} 55%,transparent)`
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
return vars
|
|
81
|
+
})
|
|
82
|
+
|
|
43
83
|
const sizeClasses: Record<string, string> = {
|
|
84
|
+
'2xs': 'px-1.5 py-0.5 text-[10px] rounded-sm',
|
|
85
|
+
xs: 'px-2.5 py-1 text-xs',
|
|
44
86
|
sm: 'px-3 py-1.5 text-sm',
|
|
45
87
|
md: 'px-4 py-2 text-sm',
|
|
46
|
-
lg: 'px-
|
|
88
|
+
lg: 'px-5 py-2.5 text-base',
|
|
89
|
+
xl: 'px-6 py-3 text-base',
|
|
90
|
+
'2xl': 'px-8 py-3.5 text-lg',
|
|
91
|
+
'3xl': 'px-10 py-4 text-xl',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const circleSizeClasses: Record<string, string> = {
|
|
95
|
+
'2xs': 'w-5 h-5 text-[10px]',
|
|
96
|
+
xs: 'w-7 h-7 text-xs',
|
|
97
|
+
sm: 'w-8 h-8 text-sm',
|
|
98
|
+
md: 'w-10 h-10 text-sm',
|
|
99
|
+
lg: 'w-12 h-12 text-base',
|
|
100
|
+
xl: 'w-14 h-14 text-base',
|
|
101
|
+
'2xl': 'w-16 h-16 text-lg',
|
|
102
|
+
'3xl': 'w-20 h-20 text-xl',
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const shapeClasses: Record<string, string> = {
|
|
106
|
+
sharp: 'rounded-none',
|
|
107
|
+
soft: 'rounded-md',
|
|
108
|
+
rounded: 'rounded-lg',
|
|
109
|
+
pill: 'rounded-full',
|
|
110
|
+
circle: 'rounded-full',
|
|
47
111
|
}
|
|
48
112
|
</script>
|
|
49
113
|
|
|
@@ -51,21 +115,21 @@
|
|
|
51
115
|
{type}
|
|
52
116
|
disabled={isDisabled}
|
|
53
117
|
{onclick}
|
|
118
|
+
{style}
|
|
54
119
|
class="
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{fullWidth ? 'w-full' : ''}
|
|
120
|
+
karbon-btn
|
|
121
|
+
inline-flex items-center justify-center gap-2 font-semibold
|
|
122
|
+
{shapeClasses[shape]}
|
|
123
|
+
transition-all duration-200 ease-out
|
|
124
|
+
focus-visible:outline-2 focus-visible:outline-offset-2
|
|
125
|
+
cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none
|
|
126
|
+
{arrow ? 'relative overflow-hidden py-3 md:py-3.5 px-8 text-[0.8125rem] md:text-sm' : fullWidth ? 'w-full ' + sizeClasses[size] : shape === 'circle' ? circleSizeClasses[size] : sizeClasses[size]}
|
|
62
127
|
{arrow ? 'group' : ''}
|
|
63
|
-
|
|
64
|
-
{className}
|
|
128
|
+
{classes?.root ?? className}
|
|
65
129
|
"
|
|
66
130
|
>
|
|
67
131
|
{#if arrow}
|
|
68
|
-
<span class="flex items-center gap-2 transition-transform duration-300 group-hover
|
|
132
|
+
<span class="flex items-center gap-2 transition-transform duration-300 group-hover:-translate-x-3">
|
|
69
133
|
{#if loading}
|
|
70
134
|
<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>
|
|
71
135
|
{#if loadingText}<span>{loadingText}</span>{/if}
|
|
@@ -74,16 +138,37 @@
|
|
|
74
138
|
{/if}
|
|
75
139
|
</span>
|
|
76
140
|
{#if !loading}
|
|
77
|
-
<span class="absolute right-
|
|
141
|
+
<span class="absolute right-5 flex items-center opacity-0 translate-x-1 transition-all duration-300 group-hover:opacity-100 group-hover:translate-x-0">
|
|
78
142
|
<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="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
|
79
143
|
</span>
|
|
80
144
|
{/if}
|
|
81
145
|
{:else}
|
|
82
146
|
{#if loading}
|
|
83
|
-
<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
|
|
147
|
+
<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>
|
|
84
148
|
{#if loadingText}<span>{loadingText}</span>{:else}{@render children()}{/if}
|
|
85
149
|
{:else}
|
|
86
150
|
{@render children()}
|
|
87
151
|
{/if}
|
|
88
152
|
{/if}
|
|
89
153
|
</button>
|
|
154
|
+
|
|
155
|
+
<style>
|
|
156
|
+
.karbon-btn {
|
|
157
|
+
background: var(--kb-bg);
|
|
158
|
+
color: var(--kb-c);
|
|
159
|
+
border: var(--kb-b);
|
|
160
|
+
box-shadow: var(--kb-sh);
|
|
161
|
+
}
|
|
162
|
+
.karbon-btn:hover:not(:disabled) {
|
|
163
|
+
background: var(--kb-bg-h);
|
|
164
|
+
color: var(--kb-c-h);
|
|
165
|
+
border: var(--kb-b-h);
|
|
166
|
+
box-shadow: var(--kb-sh-h);
|
|
167
|
+
}
|
|
168
|
+
.karbon-btn:active:not(:disabled) {
|
|
169
|
+
transform: scale(0.97);
|
|
170
|
+
}
|
|
171
|
+
.karbon-btn:focus-visible {
|
|
172
|
+
outline-color: var(--kb-bg);
|
|
173
|
+
}
|
|
174
|
+
</style>
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { BrandProvider, BrandButtonVariant } from '@karbonjs/ui-core'
|
|
4
|
+
import type { ButtonSize, ButtonShape } from '@karbonjs/ui-core'
|
|
5
|
+
import * as si from 'simple-icons'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
brand: BrandProvider
|
|
9
|
+
variant?: BrandButtonVariant
|
|
10
|
+
size?: ButtonSize
|
|
11
|
+
shape?: ButtonShape
|
|
12
|
+
type?: 'button' | 'submit'
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
loading?: boolean
|
|
15
|
+
loadingText?: string
|
|
16
|
+
fullWidth?: boolean
|
|
17
|
+
iconOnly?: boolean
|
|
18
|
+
class?: string
|
|
19
|
+
classes?: { root?: string }
|
|
20
|
+
onclick?: () => void
|
|
21
|
+
children?: Snippet
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
brand,
|
|
26
|
+
variant = 'solid',
|
|
27
|
+
size = 'md',
|
|
28
|
+
shape = 'rounded',
|
|
29
|
+
type = 'button',
|
|
30
|
+
disabled = false,
|
|
31
|
+
loading = false,
|
|
32
|
+
loadingText = '',
|
|
33
|
+
fullWidth = false,
|
|
34
|
+
iconOnly = false,
|
|
35
|
+
class: className = '',
|
|
36
|
+
classes = {},
|
|
37
|
+
onclick,
|
|
38
|
+
children
|
|
39
|
+
}: Props = $props()
|
|
40
|
+
|
|
41
|
+
const isDisabled = $derived(disabled || loading)
|
|
42
|
+
|
|
43
|
+
// Map brand names to simple-icons slugs (only those that exist)
|
|
44
|
+
const slugMap: Record<string, string> = {
|
|
45
|
+
google: 'siGoogle', facebook: 'siFacebook', apple: 'siApple',
|
|
46
|
+
github: 'siGithub', gitlab: 'siGitlab', x: 'siX',
|
|
47
|
+
discord: 'siDiscord', reddit: 'siReddit',
|
|
48
|
+
twitch: 'siTwitch', youtube: 'siYoutube', tiktok: 'siTiktok',
|
|
49
|
+
instagram: 'siInstagram', snapchat: 'siSnapchat', pinterest: 'siPinterest',
|
|
50
|
+
spotify: 'siSpotify', netflix: 'siNetflix', hbo: 'siHbomax',
|
|
51
|
+
appletv: 'siAppletv', crunchyroll: 'siCrunchyroll',
|
|
52
|
+
steam: 'siSteam', playstation: 'siPlaystation', epicgames: 'siEpicgames',
|
|
53
|
+
stripe: 'siStripe', paypal: 'siPaypal',
|
|
54
|
+
figma: 'siFigma', notion: 'siNotion', vercel: 'siVercel', netlify: 'siNetlify',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback SVGs for brands not in simple-icons (24x24 viewBox)
|
|
58
|
+
const fallbackSvg: Record<string, string> = {
|
|
59
|
+
microsoft: '<rect x="1" y="1" width="10" height="10"/><rect x="13" y="1" width="10" height="10"/><rect x="1" y="13" width="10" height="10"/><rect x="13" y="13" width="10" height="10"/>',
|
|
60
|
+
twitter: '<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>',
|
|
61
|
+
slack: '<path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"/>',
|
|
62
|
+
linkedin: '<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>',
|
|
63
|
+
disneyplus: '<path d="M1.57 8.793c-.192.14-.257.372-.115.588.167.253.488.318.717.14l6.946-5.126c.192-.14.245-.372.091-.588a.52.52 0 0 0-.142-.152.46.46 0 0 0-.575.012zm16.09 2.184c-.68-1.167-1.698-2.146-2.98-2.87-.744-.42-1.565-.742-2.438-.965l1.14-.825c.77-.557 1.052-1.42.68-2.082-.384-.662-1.321-.84-2.091-.283L2.505 10.88c-.77.558-1.052 1.421-.668 2.082.372.65 1.321.84 2.091.283l3.7-2.677c.744.15 1.437.396 2.065.72 1.744.906 2.864 2.478 2.864 4.205 0 2.735-2.812 4.944-6.275 4.944-1.18 0-2.297-.269-3.206-.751a.447.447 0 0 0-.614.163.464.464 0 0 0 .16.63c1.053.558 2.338.87 3.66.87 4.044 0 7.165-2.644 7.165-5.856 0-1.61-.82-3.09-2.184-4.212l2.132-1.543c.128-.093.2-.258.128-.395a.23.23 0 0 0-.064-.082z"/><circle cx="21.5" cy="8.5" r="1.5"/>',
|
|
64
|
+
primeVideo: '<path d="M1.285 11.953C.41 11.665 0 11.143 0 10.445c0-.476.182-.894.545-1.256.364-.362.79-.543 1.28-.543.42 0 .79.13 1.107.39.318.26.554.63.71 1.108l-1.003.38c-.076-.293-.19-.507-.34-.643a.73.73 0 0 0-.498-.203.783.783 0 0 0-.56.227.757.757 0 0 0-.235.564c0 .372.236.665.708.879zm4.638-2.558L8.17 9.22c-.158-.39-.37-.675-.635-.857a1.501 1.501 0 0 0-.88-.273c-.56 0-1.03.21-1.413.627-.384.419-.575.94-.575 1.563 0 .614.188 1.124.563 1.53.375.406.847.61 1.416.61.36 0 .672-.1.937-.297.265-.198.48-.5.645-.91l-2.23-.173.15-.886h3.38l-.006.115c-.058.846-.34 1.534-.845 2.063-.505.53-1.15.794-1.935.794-.866 0-1.582-.3-2.148-.9-.566-.6-.85-1.366-.85-2.299 0-.95.295-1.73.884-2.342.589-.611 1.327-.917 2.214-.917.646 0 1.2.166 1.665.498.464.332.8.81 1.007 1.434z" transform="translate(4 2) scale(1.2)"/>',
|
|
65
|
+
amazon: '<path d="M.045 18.02c.072-.116.187-.124.348-.024 2.862 1.773 6.093 2.66 9.693 2.66 2.602 0 5.145-.588 7.63-1.764.366-.173.674-.244.92-.211.246.032.373.171.381.415.007.244-.105.46-.337.647-.232.187-.586.424-1.062.71a16.87 16.87 0 0 1-2.742 1.279 16.76 16.76 0 0 1-5.028 1.025c-3.455.06-6.543-.88-9.262-2.822-.21-.153-.262-.308-.157-.467l.373-.448zm13.514-3.533c-.152-.195-.296-.151-.433.133-.137.284-.18.574-.13.87.05.294.207.523.47.685a9.532 9.532 0 0 0 1.768.887c1.032.39 1.69.642 1.976.755.286.113.462.183.53.21.264.1.503.122.717.062.213-.06.32-.222.32-.486v-.14c0-.41-.154-.78-.463-1.112-.31-.33-.765-.626-1.369-.886-.604-.26-1.024-.438-1.26-.534a17.86 17.86 0 0 0-2.127-.443z"/>',
|
|
66
|
+
xbox: '<path d="M6.97 3.846c-.987.567-1.878 1.323-2.604 2.222C2.9 8.088 2.3 10.6 2.9 12.87c.438 1.655 1.47 3.106 2.873 4.072.182-.22 1.263-1.685 3.243-4.593 1.78-2.613 2.282-3.64 2.282-4.282 0-.415-.106-.739-.354-1.175-.622-1.094-1.79-2.156-3.098-2.838-.287-.15-.598-.29-.876-.208zM12 2.04c-1.076 0-2.122.192-3.106.547 1.524.81 2.9 2.07 3.674 3.362.33.552.514 1.106.514 1.775 0 .97-.465 2.046-2.452 4.975-1.734 2.557-2.862 4.153-3.318 4.744.66.384 1.31.59 2.128.722.684.11 1.483.066 2.142-.052 2.688-.48 4.81-2.35 5.678-4.933.737-2.193.414-4.698-.88-6.737A7.893 7.893 0 0 0 12 2.04z"/><path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 1.2c5.934 0 10.8 4.866 10.8 10.8S17.934 22.8 12 22.8 1.2 17.934 1.2 12 6.066 1.2 12 1.2z"/>',
|
|
67
|
+
nintendo: '<path d="M5.818 0A5.818 5.818 0 0 0 0 5.818v12.364A5.818 5.818 0 0 0 5.818 24h12.364A5.818 5.818 0 0 0 24 18.182V5.818A5.818 5.818 0 0 0 18.182 0H5.818zm0 1.745h4.91v20.51h-4.91a4.073 4.073 0 0 1-4.073-4.073V5.818a4.073 4.073 0 0 1 4.073-4.073zm8.509 0h3.855a4.073 4.073 0 0 1 4.073 4.073v12.364a4.073 4.073 0 0 1-4.073 4.073h-3.855V1.745zM7.636 6.545a3.273 3.273 0 1 0 0 6.546 3.273 3.273 0 0 0 0-6.546zm0 1.746a1.527 1.527 0 1 1 0 3.054 1.527 1.527 0 0 1 0-3.054zm8.728 2.182a1.745 1.745 0 1 0 0 3.49 1.745 1.745 0 0 0 0-3.49z"/>',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Brand metadata (label + foreground override for light-colored brands)
|
|
71
|
+
const meta: Record<string, { label: string, fg?: string }> = {
|
|
72
|
+
google: { label: 'Google' }, facebook: { label: 'Facebook' }, apple: { label: 'Apple' },
|
|
73
|
+
microsoft: { label: 'Microsoft' }, github: { label: 'GitHub' }, gitlab: { label: 'GitLab' },
|
|
74
|
+
twitter: { label: 'Twitter' }, x: { label: 'X' }, discord: { label: 'Discord' },
|
|
75
|
+
slack: { label: 'Slack' }, linkedin: { label: 'LinkedIn' }, reddit: { label: 'Reddit' },
|
|
76
|
+
twitch: { label: 'Twitch' }, youtube: { label: 'YouTube' }, tiktok: { label: 'TikTok' },
|
|
77
|
+
instagram: { label: 'Instagram' }, snapchat: { label: 'Snapchat', fg: '#000' },
|
|
78
|
+
pinterest: { label: 'Pinterest' }, spotify: { label: 'Spotify', fg: '#000' },
|
|
79
|
+
netflix: { label: 'Netflix' }, disneyplus: { label: 'Disney+' }, hbo: { label: 'HBO Max' },
|
|
80
|
+
primeVideo: { label: 'Prime Video' }, appletv: { label: 'Apple TV+' },
|
|
81
|
+
crunchyroll: { label: 'Crunchyroll' }, steam: { label: 'Steam' },
|
|
82
|
+
playstation: { label: 'PlayStation' }, xbox: { label: 'Xbox' },
|
|
83
|
+
nintendo: { label: 'Nintendo' }, epicgames: { label: 'Epic Games' },
|
|
84
|
+
stripe: { label: 'Stripe' }, paypal: { label: 'PayPal' },
|
|
85
|
+
amazon: { label: 'Amazon', fg: '#000' }, figma: { label: 'Figma' },
|
|
86
|
+
notion: { label: 'Notion' }, vercel: { label: 'Vercel' },
|
|
87
|
+
netlify: { label: 'Netlify', fg: '#000' },
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Get icon data from simple-icons, fallback to custom SVG
|
|
91
|
+
const icon = $derived.by(() => {
|
|
92
|
+
const key = slugMap[brand]
|
|
93
|
+
if (key && key in si) {
|
|
94
|
+
const data = (si as any)[key] as { svg: string, hex: string, title: string }
|
|
95
|
+
return { svg: data.svg, hex: data.hex, viewBox: '0 0 24 24' }
|
|
96
|
+
}
|
|
97
|
+
if (fallbackSvg[brand]) {
|
|
98
|
+
return { svg: fallbackSvg[brand], hex: fallbackHex[brand] || '666666', viewBox: '0 0 24 24' }
|
|
99
|
+
}
|
|
100
|
+
return null
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Fallback hex colors for brands not in simple-icons
|
|
104
|
+
const fallbackHex: Record<string, string> = {
|
|
105
|
+
microsoft: '00A4EF', twitter: '1DA1F2', slack: '4A154B', linkedin: '0A66C2',
|
|
106
|
+
disneyplus: '113CCF', primeVideo: '00A8E1', amazon: 'FF9900',
|
|
107
|
+
xbox: '107C10', nintendo: 'E60012',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const brandLabel = $derived(meta[brand]?.label || brand)
|
|
111
|
+
const brandFg = $derived(meta[brand]?.fg || 'white')
|
|
112
|
+
|
|
113
|
+
const rawHex = $derived(icon ? `#${icon.hex}` : '#666')
|
|
114
|
+
|
|
115
|
+
// Lighten a hex color by mixing with white
|
|
116
|
+
function lighten(hex: string, amount: number): string {
|
|
117
|
+
const r = parseInt(hex.slice(1, 3), 16)
|
|
118
|
+
const g = parseInt(hex.slice(3, 5), 16)
|
|
119
|
+
const b = parseInt(hex.slice(5, 7), 16)
|
|
120
|
+
const lr = Math.round(r + (255 - r) * amount)
|
|
121
|
+
const lg = Math.round(g + (255 - g) * amount)
|
|
122
|
+
const lb = Math.round(b + (255 - b) * amount)
|
|
123
|
+
return `#${lr.toString(16).padStart(2,'0')}${lg.toString(16).padStart(2,'0')}${lb.toString(16).padStart(2,'0')}`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Detect if brand color is too dark for dark backgrounds
|
|
127
|
+
function luminance(hex: string): number {
|
|
128
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255
|
|
129
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255
|
|
130
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255
|
|
131
|
+
return 0.299 * r + 0.587 * g + 0.114 * b
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const isDark = $derived(luminance(rawHex) < 0.25)
|
|
135
|
+
// For dark brands: lighten the color so it's visible on dark bg
|
|
136
|
+
const visibleColor = $derived(isDark ? lighten(rawHex, 0.6) : rawHex)
|
|
137
|
+
// Solid bg for dark brands: slightly lighter so it pops from the page bg
|
|
138
|
+
const solidBg = $derived(isDark ? lighten(rawHex, 0.15) : rawHex)
|
|
139
|
+
const solidBgHover = $derived(isDark ? lighten(rawHex, 0.25) : rawHex + 'cc')
|
|
140
|
+
|
|
141
|
+
const style = $derived.by(() => {
|
|
142
|
+
switch (variant) {
|
|
143
|
+
case 'solid':
|
|
144
|
+
return `--kb-bg:${solidBg};--kb-bg-h:${solidBgHover};--kb-c:${brandFg};--kb-c-h:${brandFg};--kb-b:none;--kb-b-h:none`
|
|
145
|
+
case 'outline':
|
|
146
|
+
return `--kb-bg:transparent;--kb-bg-h:${visibleColor}15;--kb-c:${visibleColor};--kb-c-h:${visibleColor};--kb-b:1px solid ${visibleColor}40;--kb-b-h:1px solid ${visibleColor}80`
|
|
147
|
+
case 'light':
|
|
148
|
+
return `--kb-bg:${visibleColor}15;--kb-bg-h:${visibleColor}25;--kb-c:${visibleColor};--kb-c-h:${visibleColor};--kb-b:none;--kb-b-h:none`
|
|
149
|
+
default: return ''
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const sizeClasses: Record<string, string> = {
|
|
154
|
+
'2xs': 'px-1.5 py-0.5 text-[10px] gap-1',
|
|
155
|
+
xs: 'px-2.5 py-1 text-xs gap-1.5',
|
|
156
|
+
sm: 'px-3 py-1.5 text-sm gap-2',
|
|
157
|
+
md: 'px-4 py-2 text-sm gap-2',
|
|
158
|
+
lg: 'px-5 py-2.5 text-base gap-2.5',
|
|
159
|
+
xl: 'px-6 py-3 text-base gap-2.5',
|
|
160
|
+
'2xl': 'px-8 py-3.5 text-lg gap-3',
|
|
161
|
+
'3xl': 'px-10 py-4 text-xl gap-3',
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const iconSizes: Record<string, number> = {
|
|
165
|
+
'2xs': 10, xs: 12, sm: 14, md: 16, lg: 18, xl: 20, '2xl': 22, '3xl': 24,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const shapeClasses: Record<string, string> = {
|
|
169
|
+
sharp: 'rounded-none',
|
|
170
|
+
soft: 'rounded-md',
|
|
171
|
+
rounded: 'rounded-lg',
|
|
172
|
+
pill: 'rounded-full',
|
|
173
|
+
circle: 'rounded-full',
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const circleSizes: Record<string, string> = {
|
|
177
|
+
'2xs': 'w-5 h-5', xs: 'w-7 h-7', sm: 'w-8 h-8', md: 'w-10 h-10',
|
|
178
|
+
lg: 'w-12 h-12', xl: 'w-14 h-14', '2xl': 'w-16 h-16', '3xl': 'w-20 h-20',
|
|
179
|
+
}
|
|
180
|
+
</script>
|
|
181
|
+
|
|
182
|
+
<button
|
|
183
|
+
{type}
|
|
184
|
+
disabled={isDisabled}
|
|
185
|
+
{onclick}
|
|
186
|
+
{style}
|
|
187
|
+
class="
|
|
188
|
+
karbon-btn
|
|
189
|
+
inline-flex items-center justify-center font-semibold
|
|
190
|
+
{shapeClasses[shape]}
|
|
191
|
+
{shape === 'circle' && iconOnly ? circleSizes[size] : sizeClasses[size]}
|
|
192
|
+
transition-all duration-200 ease-out
|
|
193
|
+
cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none
|
|
194
|
+
{fullWidth ? 'w-full' : ''}
|
|
195
|
+
{classes?.root ?? className}
|
|
196
|
+
"
|
|
197
|
+
>
|
|
198
|
+
{#if loading}
|
|
199
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={iconSizes[size]} height={iconSizes[size]} 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>
|
|
200
|
+
{#if loadingText}<span>{loadingText}</span>{/if}
|
|
201
|
+
{:else}
|
|
202
|
+
{#if icon}
|
|
203
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={iconSizes[size]} height={iconSizes[size]} viewBox={icon.viewBox} fill="currentColor" role="img" aria-label={brandLabel}>{@html icon.svg}</svg>
|
|
204
|
+
{/if}
|
|
205
|
+
{#if !iconOnly}
|
|
206
|
+
{#if children}
|
|
207
|
+
{@render children()}
|
|
208
|
+
{:else}
|
|
209
|
+
<span>{brandLabel}</span>
|
|
210
|
+
{/if}
|
|
211
|
+
{/if}
|
|
212
|
+
{/if}
|
|
213
|
+
</button>
|
|
214
|
+
|
|
215
|
+
<style>
|
|
216
|
+
.karbon-btn {
|
|
217
|
+
background: var(--kb-bg);
|
|
218
|
+
color: var(--kb-c);
|
|
219
|
+
border: var(--kb-b);
|
|
220
|
+
}
|
|
221
|
+
.karbon-btn:hover:not(:disabled) {
|
|
222
|
+
background: var(--kb-bg-h);
|
|
223
|
+
color: var(--kb-c-h);
|
|
224
|
+
border: var(--kb-b-h);
|
|
225
|
+
}
|
|
226
|
+
.karbon-btn:active:not(:disabled) {
|
|
227
|
+
transform: scale(0.97);
|
|
228
|
+
}
|
|
229
|
+
</style>
|