zabi-components 5.0.19 → 5.0.21
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/LICENSE +21 -0
- package/README.md +354 -81
- package/dist/atoms/Button.svelte +6 -3
- package/dist/atoms/Button.svelte.d.ts +1 -0
- package/dist/atoms/Card.svelte +23 -47
- package/dist/atoms/CardContent.svelte +12 -5
- package/dist/atoms/CardContent.svelte.d.ts +2 -0
- package/dist/atoms/CardHeader.svelte +22 -5
- package/dist/atoms/CardHeader.svelte.d.ts +3 -0
- package/dist/atoms/ColorPicker.svelte +271 -7
- package/dist/atoms/IconButton.svelte +88 -0
- package/dist/atoms/IconButton.svelte.d.ts +21 -0
- package/dist/atoms/Input.svelte +7 -6
- package/dist/atoms/Input.svelte.d.ts +3 -1
- package/dist/atoms/Progress.svelte +1 -3
- package/dist/atoms/Select.svelte +109 -24
- package/dist/atoms/Select.svelte.d.ts +10 -0
- package/dist/atoms/Skeleton.svelte +12 -2
- package/dist/atoms/Skeleton.svelte.d.ts +1 -0
- package/dist/atoms/Tooltip.svelte +15 -17
- package/dist/atoms/index.d.ts +1 -2
- package/dist/atoms/index.js +1 -2
- package/dist/components/atoms/index.d.ts +1 -2
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +8 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/molecules/Dropdown.svelte +101 -14
- package/dist/molecules/Dropdown.svelte.d.ts +7 -0
- package/dist/molecules/ImageUpload.svelte +4 -4
- package/dist/molecules/Modal.svelte +42 -26
- package/dist/molecules/Modal.svelte.d.ts +1 -0
- package/dist/molecules/NavigationMenu.svelte +158 -0
- package/dist/molecules/NavigationMenu.svelte.d.ts +25 -0
- package/dist/molecules/NavigationMenuContent.svelte +83 -0
- package/dist/molecules/NavigationMenuContent.svelte.d.ts +9 -0
- package/dist/molecules/NavigationMenuItem.svelte +36 -0
- package/dist/molecules/NavigationMenuItem.svelte.d.ts +9 -0
- package/dist/molecules/NavigationMenuLink.svelte +53 -0
- package/dist/molecules/NavigationMenuLink.svelte.d.ts +10 -0
- package/dist/molecules/NavigationMenuList.svelte +31 -0
- package/dist/molecules/NavigationMenuList.svelte.d.ts +8 -0
- package/dist/molecules/NavigationMenuTrigger.svelte +81 -0
- package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +9 -0
- package/dist/molecules/Section.svelte +158 -0
- package/dist/molecules/Section.svelte.d.ts +27 -0
- package/dist/molecules/Sidebar.svelte +324 -0
- package/dist/molecules/Sidebar.svelte.d.ts +30 -0
- package/dist/molecules/SlideUp.svelte +2 -2
- package/dist/molecules/index.d.ts +8 -0
- package/dist/molecules/index.js +8 -0
- package/dist/organisms/Navigation.svelte +2 -2
- package/dist/zabi-components-colors.css +2 -2
- package/dist/zabi-components-theme-only.css +2 -2
- package/dist/zabi-components-theme.css +2 -2
- package/dist/zabi-components.css +299 -24
- package/package.json +3 -5
- package/dist/atoms/CardDescription.svelte +0 -24
- package/dist/atoms/CardDescription.svelte.d.ts +0 -8
- package/dist/atoms/CardTitle.svelte +0 -33
- package/dist/atoms/CardTitle.svelte.d.ts +0 -9
package/dist/atoms/Card.svelte
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import type { CardVariant, SizeVariant } from "../../types/variants.js";
|
|
4
|
+
import CardHeader from "./CardHeader.svelte";
|
|
5
|
+
import CardContent from "./CardContent.svelte";
|
|
4
6
|
|
|
5
7
|
interface Props {
|
|
6
8
|
title?: string;
|
|
@@ -42,11 +44,12 @@
|
|
|
42
44
|
}
|
|
43
45
|
});
|
|
44
46
|
|
|
45
|
-
const useOldAPI = $derived(
|
|
46
|
-
|
|
47
|
+
const useOldAPI = $derived(
|
|
48
|
+
title !== "" || description !== "" || image !== "",
|
|
49
|
+
);
|
|
50
|
+
|
|
47
51
|
const cardClasses = $derived(() => {
|
|
48
|
-
const baseClasses =
|
|
49
|
-
`rounded-lg transition-all duration-200 ${variantClasses()}`;
|
|
52
|
+
const baseClasses = `rounded-lg transition-all duration-200 ${variantClasses()}`;
|
|
50
53
|
const interactiveClasses = onclick
|
|
51
54
|
? variant === "elevated"
|
|
52
55
|
? "cursor-pointer hover:shadow-xl hover:bg-card-hover"
|
|
@@ -63,26 +66,6 @@
|
|
|
63
66
|
return `${baseClasses} ${interactiveClasses} ${widthClasses} ${minWidthClass} ${paddingClass}`.trim();
|
|
64
67
|
});
|
|
65
68
|
|
|
66
|
-
const titleClasses = $derived(() => {
|
|
67
|
-
if (size === "sm") {
|
|
68
|
-
return "text-lg font-medium mb-2 text-headline";
|
|
69
|
-
} else if (size === "lg") {
|
|
70
|
-
return "text-2xl font-medium mb-4 text-headline";
|
|
71
|
-
} else {
|
|
72
|
-
return "text-xl font-medium mb-3 text-headline";
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const descriptionClasses = $derived(() => {
|
|
77
|
-
if (size === "sm") {
|
|
78
|
-
return "text-xs text-description mb-3";
|
|
79
|
-
} else if (size === "lg") {
|
|
80
|
-
return "text-base text-description mb-5";
|
|
81
|
-
} else {
|
|
82
|
-
return "text-sm text-description mb-4";
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
69
|
function handleKeydown(event: KeyboardEvent) {
|
|
87
70
|
if (onclick && (event.key === "Enter" || event.key === " ")) {
|
|
88
71
|
event.preventDefault();
|
|
@@ -91,44 +74,37 @@
|
|
|
91
74
|
}
|
|
92
75
|
|
|
93
76
|
const cardRole = $derived(onclick ? "button" : undefined);
|
|
94
|
-
const cardTabIndex = $derived(onclick ? 0 : undefined);
|
|
95
77
|
const cardAriaLabel = $derived(onclick && title ? title : undefined);
|
|
96
78
|
</script>
|
|
97
79
|
|
|
98
|
-
<div
|
|
99
|
-
class={cardClasses()}
|
|
100
|
-
{onclick}
|
|
80
|
+
<div
|
|
81
|
+
class={cardClasses()}
|
|
82
|
+
{onclick}
|
|
101
83
|
role={cardRole}
|
|
102
|
-
tabindex
|
|
84
|
+
{...onclick ? { tabindex: 0 } : {}}
|
|
103
85
|
aria-label={cardAriaLabel}
|
|
104
|
-
onkeydown={handleKeydown}
|
|
86
|
+
onkeydown={onclick ? handleKeydown : undefined}
|
|
105
87
|
{...restProps}
|
|
106
88
|
>
|
|
107
89
|
{#if useOldAPI}
|
|
108
90
|
<!-- Old API: Using title/description/image props -->
|
|
109
|
-
{#if
|
|
110
|
-
<
|
|
111
|
-
src={image}
|
|
112
|
-
alt={title}
|
|
113
|
-
class="w-full h-48 object-cover rounded-lg mb-4"
|
|
114
|
-
/>
|
|
115
|
-
{/if}
|
|
116
|
-
|
|
117
|
-
{#if title}
|
|
118
|
-
<h3 class={titleClasses()}>{title}</h3>
|
|
91
|
+
{#if title || description}
|
|
92
|
+
<CardHeader {title} {description} />
|
|
119
93
|
{/if}
|
|
120
94
|
|
|
121
|
-
{#if
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
95
|
+
{#if image || children}
|
|
96
|
+
<CardContent {image} imageAlt={title}>
|
|
97
|
+
{#if children}
|
|
98
|
+
{@render children()}
|
|
99
|
+
{/if}
|
|
100
|
+
</CardContent>
|
|
127
101
|
{/if}
|
|
128
102
|
{:else}
|
|
129
103
|
<!-- New API: Using compound components (CardHeader, CardContent, CardFooter) -->
|
|
130
104
|
{#if children}
|
|
131
|
-
|
|
105
|
+
<CardContent className="pt-6">
|
|
106
|
+
{@render children()}
|
|
107
|
+
</CardContent>
|
|
132
108
|
{/if}
|
|
133
109
|
{/if}
|
|
134
110
|
</div>
|
|
@@ -2,23 +2,30 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
|
+
image?: string;
|
|
6
|
+
imageAlt?: string;
|
|
5
7
|
className?: string;
|
|
6
8
|
children?: Snippet;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
let {
|
|
12
|
+
image = "",
|
|
13
|
+
imageAlt = "",
|
|
10
14
|
className = "",
|
|
11
15
|
children,
|
|
12
16
|
...restProps
|
|
13
17
|
}: Props = $props();
|
|
14
18
|
</script>
|
|
15
19
|
|
|
16
|
-
<div
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
<div class="p-6 pt-0 {className}" {...restProps}>
|
|
21
|
+
{#if image}
|
|
22
|
+
<img
|
|
23
|
+
src={image}
|
|
24
|
+
alt={imageAlt}
|
|
25
|
+
class="w-full h-48 object-cover rounded-lg mb-4"
|
|
26
|
+
/>
|
|
27
|
+
{/if}
|
|
20
28
|
{#if children}
|
|
21
29
|
{@render children()}
|
|
22
30
|
{/if}
|
|
23
31
|
</div>
|
|
24
|
-
|
|
@@ -2,23 +2,40 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
5
8
|
className?: string;
|
|
6
9
|
children?: Snippet;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
let {
|
|
13
|
+
title = "",
|
|
14
|
+
description = "",
|
|
15
|
+
level = 3,
|
|
10
16
|
className = "",
|
|
11
17
|
children,
|
|
12
18
|
...restProps
|
|
13
19
|
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
const headingTag = $derived(`h${level}`);
|
|
22
|
+
const headingClasses = $derived(() => {
|
|
23
|
+
return "text-2xl font-semibold leading-none tracking-tight text-headline";
|
|
24
|
+
});
|
|
14
25
|
</script>
|
|
15
26
|
|
|
16
|
-
<header
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
<header class="flex flex-col space-y-1.5 p-6 {className}" {...restProps}>
|
|
28
|
+
{#if title}
|
|
29
|
+
<svelte:element this={headingTag} class={headingClasses()}>
|
|
30
|
+
{title}
|
|
31
|
+
</svelte:element>
|
|
32
|
+
{/if}
|
|
20
33
|
{#if children}
|
|
21
34
|
{@render children()}
|
|
22
35
|
{/if}
|
|
36
|
+
{#if description}
|
|
37
|
+
<p class="text-sm text-description">
|
|
38
|
+
{description}
|
|
39
|
+
</p>
|
|
40
|
+
{/if}
|
|
23
41
|
</header>
|
|
24
|
-
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Input from "./Input.svelte";
|
|
3
|
+
import { onMount, onDestroy } from "svelte";
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
value?: string;
|
|
@@ -18,11 +19,83 @@
|
|
|
18
19
|
...restProps
|
|
19
20
|
}: Props = $props();
|
|
20
21
|
|
|
22
|
+
let isOpen = $state(false);
|
|
23
|
+
let pickerContainer: HTMLDivElement;
|
|
24
|
+
let colorMap: HTMLCanvasElement;
|
|
25
|
+
let colorMapContainer: HTMLDivElement;
|
|
26
|
+
let hueSlider: HTMLInputElement;
|
|
27
|
+
let isDragging = $state(false);
|
|
28
|
+
|
|
29
|
+
// HSL values (0-360, 0-100, 0-100)
|
|
30
|
+
let hue = $state(0);
|
|
31
|
+
let saturation = $state(100);
|
|
32
|
+
let lightness = $state(50);
|
|
33
|
+
|
|
21
34
|
function isValidHex(hex: string): boolean {
|
|
22
35
|
const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
23
36
|
return hexPattern.test(hex);
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
function hexToHsl(hex: string): [number, number, number] {
|
|
40
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
41
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
42
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
43
|
+
|
|
44
|
+
const max = Math.max(r, g, b);
|
|
45
|
+
const min = Math.min(r, g, b);
|
|
46
|
+
let h = 0;
|
|
47
|
+
let s = 0;
|
|
48
|
+
const l = (max + min) / 2;
|
|
49
|
+
|
|
50
|
+
if (max !== min) {
|
|
51
|
+
const d = max - min;
|
|
52
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
53
|
+
switch (max) {
|
|
54
|
+
case r:
|
|
55
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
56
|
+
break;
|
|
57
|
+
case g:
|
|
58
|
+
h = ((b - r) / d + 2) / 6;
|
|
59
|
+
break;
|
|
60
|
+
case b:
|
|
61
|
+
h = ((r - g) / d + 4) / 6;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hslToHex(h: number, s: number, l: number): string {
|
|
70
|
+
l /= 100;
|
|
71
|
+
const a = (s * Math.min(l, 1 - l)) / 100;
|
|
72
|
+
const f = (n: number) => {
|
|
73
|
+
const k = (n + h / 30) % 12;
|
|
74
|
+
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
75
|
+
return Math.round(255 * color)
|
|
76
|
+
.toString(16)
|
|
77
|
+
.padStart(2, "0");
|
|
78
|
+
};
|
|
79
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function updateFromHex(hex: string) {
|
|
83
|
+
if (isValidHex(hex)) {
|
|
84
|
+
const [h, s, l] = hexToHsl(hex);
|
|
85
|
+
hue = h;
|
|
86
|
+
saturation = s;
|
|
87
|
+
lightness = l;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function updateHexFromHsl() {
|
|
92
|
+
const hex = hslToHex(hue, saturation, lightness);
|
|
93
|
+
value = hex;
|
|
94
|
+
if (onchange) {
|
|
95
|
+
onchange(new Event("change"));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
26
99
|
const variant = $derived(() => {
|
|
27
100
|
if (value && !isValidHex(value)) {
|
|
28
101
|
return "error";
|
|
@@ -39,6 +112,7 @@
|
|
|
39
112
|
|
|
40
113
|
function handleInput(event: Event) {
|
|
41
114
|
if (onchange) onchange(event);
|
|
115
|
+
updateFromHex(value);
|
|
42
116
|
}
|
|
43
117
|
|
|
44
118
|
function handleBlur(event: Event) {
|
|
@@ -51,9 +125,134 @@
|
|
|
51
125
|
|
|
52
126
|
if (hexValue === "" || isValidHex(hexValue)) {
|
|
53
127
|
value = hexValue;
|
|
128
|
+
updateFromHex(hexValue);
|
|
54
129
|
if (onchange) onchange(event);
|
|
55
130
|
}
|
|
56
131
|
}
|
|
132
|
+
|
|
133
|
+
function togglePicker() {
|
|
134
|
+
if (!disabled) {
|
|
135
|
+
isOpen = !isOpen;
|
|
136
|
+
if (isOpen && value && isValidHex(value)) {
|
|
137
|
+
updateFromHex(value);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function drawColorMap() {
|
|
143
|
+
if (!colorMap) return;
|
|
144
|
+
const ctx = colorMap.getContext("2d");
|
|
145
|
+
if (!ctx) return;
|
|
146
|
+
|
|
147
|
+
const width = colorMap.width;
|
|
148
|
+
const height = colorMap.height;
|
|
149
|
+
|
|
150
|
+
// Draw saturation/lightness gradient
|
|
151
|
+
for (let x = 0; x < width; x++) {
|
|
152
|
+
for (let y = 0; y < height; y++) {
|
|
153
|
+
const s = (x / width) * 100;
|
|
154
|
+
const l = 100 - (y / height) * 100;
|
|
155
|
+
const hex = hslToHex(hue, s, l);
|
|
156
|
+
ctx.fillStyle = hex;
|
|
157
|
+
ctx.fillRect(x, y, 1, 1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function handleColorMapClick(event: MouseEvent) {
|
|
163
|
+
if (!colorMapContainer || !colorMap) return;
|
|
164
|
+
const rect = colorMapContainer.getBoundingClientRect();
|
|
165
|
+
const x = event.clientX - rect.left;
|
|
166
|
+
const y = event.clientY - rect.top;
|
|
167
|
+
|
|
168
|
+
saturation = Math.max(0, Math.min(100, (x / rect.width) * 100));
|
|
169
|
+
lightness = Math.max(0, Math.min(100, 100 - (y / rect.height) * 100));
|
|
170
|
+
|
|
171
|
+
updateHexFromHsl();
|
|
172
|
+
drawColorMap();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function handleColorMapDrag(event: MouseEvent) {
|
|
176
|
+
if (isDragging) {
|
|
177
|
+
handleColorMapClick(event);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function handleHueChange(event: Event) {
|
|
182
|
+
const target = event.target as HTMLInputElement;
|
|
183
|
+
hue = parseInt(target.value);
|
|
184
|
+
updateHexFromHsl();
|
|
185
|
+
drawColorMap();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
$effect(() => {
|
|
189
|
+
if (isOpen && colorMap) {
|
|
190
|
+
// Use setTimeout to ensure canvas is rendered
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
drawColorMap();
|
|
193
|
+
}, 0);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
$effect(() => {
|
|
198
|
+
if (hue !== undefined && colorMap && isOpen) {
|
|
199
|
+
drawColorMap();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
$effect(() => {
|
|
204
|
+
if (value && isValidHex(value) && !isDragging) {
|
|
205
|
+
updateFromHex(value);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
function handleClickOutside(event: MouseEvent) {
|
|
210
|
+
if (
|
|
211
|
+
pickerContainer &&
|
|
212
|
+
!pickerContainer.contains(event.target as Node) &&
|
|
213
|
+
!(event.target as HTMLElement).closest(
|
|
214
|
+
'[aria-label="Open color picker"]',
|
|
215
|
+
)
|
|
216
|
+
) {
|
|
217
|
+
isOpen = false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
onMount(() => {
|
|
222
|
+
if (value && isValidHex(value)) {
|
|
223
|
+
updateFromHex(value);
|
|
224
|
+
}
|
|
225
|
+
window.addEventListener("mousedown", handleClickOutside);
|
|
226
|
+
window.addEventListener("mouseup", () => {
|
|
227
|
+
isDragging = false;
|
|
228
|
+
});
|
|
229
|
+
window.addEventListener("mousemove", handleColorMapDrag);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
onDestroy(() => {
|
|
233
|
+
window.removeEventListener("mousedown", handleClickOutside);
|
|
234
|
+
window.removeEventListener("mouseup", () => {});
|
|
235
|
+
window.removeEventListener("mousemove", handleColorMapDrag);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const displayColor = $derived(() => {
|
|
239
|
+
if (value && isValidHex(value)) {
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
return placeholder;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const currentHslColor = $derived(() => {
|
|
246
|
+
return `hsl(${hue}, 100%, 50%)`;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const pickerIndicatorX = $derived(() => {
|
|
250
|
+
return `${(saturation / 100) * 100}%`;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const pickerIndicatorY = $derived(() => {
|
|
254
|
+
return `${100 - (lightness / 100) * 100}%`;
|
|
255
|
+
});
|
|
57
256
|
</script>
|
|
58
257
|
|
|
59
258
|
<div {...restProps}>
|
|
@@ -71,12 +270,77 @@
|
|
|
71
270
|
aria-label="Hex color input"
|
|
72
271
|
/>
|
|
73
272
|
</div>
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
273
|
+
<div class="relative shrink-0 mt-6">
|
|
274
|
+
<button
|
|
275
|
+
type="button"
|
|
276
|
+
onclick={togglePicker}
|
|
277
|
+
{disabled}
|
|
278
|
+
class="w-11 h-11 rounded-lg border-2 border-card shrink-0 cursor-pointer hover:ring-2 hover:ring-brand-500 transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
279
|
+
style="background-color: {displayColor()};"
|
|
280
|
+
aria-label="Open color picker"
|
|
281
|
+
aria-expanded={isOpen}
|
|
282
|
+
></button>
|
|
283
|
+
|
|
284
|
+
{#if isOpen}
|
|
285
|
+
<div
|
|
286
|
+
bind:this={pickerContainer}
|
|
287
|
+
class="absolute top-12 right-0 z-50 bg-input rounded-2xl shadow-lg p-4 w-80"
|
|
288
|
+
role="dialog"
|
|
289
|
+
aria-label="Color picker"
|
|
290
|
+
>
|
|
291
|
+
<div class="space-y-4">
|
|
292
|
+
<!-- Color Map -->
|
|
293
|
+
<div
|
|
294
|
+
bind:this={colorMapContainer}
|
|
295
|
+
class="relative w-full h-48 rounded-xl overflow-hidden cursor-crosshair"
|
|
296
|
+
onmousedown={(e) => {
|
|
297
|
+
isDragging = true;
|
|
298
|
+
handleColorMapClick(e);
|
|
299
|
+
}}
|
|
300
|
+
role="button"
|
|
301
|
+
tabindex="0"
|
|
302
|
+
>
|
|
303
|
+
<canvas
|
|
304
|
+
bind:this={colorMap}
|
|
305
|
+
width={256}
|
|
306
|
+
height={192}
|
|
307
|
+
class="w-full h-full"
|
|
308
|
+
></canvas>
|
|
309
|
+
<!-- Color indicator -->
|
|
310
|
+
<div
|
|
311
|
+
class="absolute w-4 h-4 border-2 border-white rounded-full shadow-lg pointer-events-none transform -translate-x-1/2 -translate-y-1/2"
|
|
312
|
+
style="left: {pickerIndicatorX()}; top: {pickerIndicatorY()};"
|
|
313
|
+
></div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<!-- Hue Slider -->
|
|
317
|
+
<div class="space-y-2">
|
|
318
|
+
<div
|
|
319
|
+
class="relative h-6 rounded-lg overflow-hidden"
|
|
320
|
+
>
|
|
321
|
+
<div
|
|
322
|
+
class="absolute inset-0"
|
|
323
|
+
style="background: linear-gradient(to right, hsl(0,100%,50%), hsl(60,100%,50%), hsl(120,100%,50%), hsl(180,100%,50%), hsl(240,100%,50%), hsl(300,100%,50%), hsl(360,100%,50%));"
|
|
324
|
+
></div>
|
|
325
|
+
<input
|
|
326
|
+
bind:this={hueSlider}
|
|
327
|
+
type="range"
|
|
328
|
+
min="0"
|
|
329
|
+
max="360"
|
|
330
|
+
value={hue}
|
|
331
|
+
oninput={handleHueChange}
|
|
332
|
+
class="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
333
|
+
aria-label="Hue slider"
|
|
334
|
+
/>
|
|
335
|
+
<div
|
|
336
|
+
class="absolute top-0 bottom-0 w-3 bg-card shadow-md pointer-events-none my-0.25 rounded-full border border-base-950"
|
|
337
|
+
style="left: {(hue / 360) * 100}%;"
|
|
338
|
+
></div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
{/if}
|
|
344
|
+
</div>
|
|
81
345
|
</div>
|
|
82
346
|
</div>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Visual style variant. */
|
|
7
|
+
variant?: ButtonVariant;
|
|
8
|
+
/** Size variant controlling padding and radius. */
|
|
9
|
+
size?: SizeVariant;
|
|
10
|
+
/** Whether the button is disabled. */
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/** Button type attribute. */
|
|
13
|
+
type?: "button" | "submit" | "reset";
|
|
14
|
+
/** Accessible label for icon-only buttons. */
|
|
15
|
+
label?: string;
|
|
16
|
+
/** Click handler for native click events. */
|
|
17
|
+
onclick?: (event: MouseEvent) => void;
|
|
18
|
+
/** Icon content. */
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
variant = "primary",
|
|
24
|
+
size = "md",
|
|
25
|
+
disabled = false,
|
|
26
|
+
type = "button",
|
|
27
|
+
label = "",
|
|
28
|
+
onclick,
|
|
29
|
+
children,
|
|
30
|
+
...restProps
|
|
31
|
+
}: Props = $props();
|
|
32
|
+
|
|
33
|
+
const sizeClass = $derived(() => {
|
|
34
|
+
if (size === "sm") {
|
|
35
|
+
return {
|
|
36
|
+
padding: "p-2",
|
|
37
|
+
radius: "rounded-md",
|
|
38
|
+
};
|
|
39
|
+
} else if (size === "lg") {
|
|
40
|
+
return {
|
|
41
|
+
padding: "p-3",
|
|
42
|
+
radius: "rounded-xl",
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
return {
|
|
46
|
+
padding: "p-2.5",
|
|
47
|
+
radius: "rounded-lg",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const variantClass = $derived(() => {
|
|
53
|
+
return variant === "primary"
|
|
54
|
+
? "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
|
|
55
|
+
: variant === "secondary"
|
|
56
|
+
? "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
|
|
57
|
+
: variant === "danger"
|
|
58
|
+
? "bg-action-danger text-inverse hover:bg-action-danger-hover active:bg-action-danger-active focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
|
|
59
|
+
: variant === "ghost"
|
|
60
|
+
? "bg-transparent text-headline hover:bg-base-100 active:bg-base-200 focus:ring-2 focus:ring-base-500 focus:ring-offset-2 disabled:text-disabled"
|
|
61
|
+
: variant === "outline"
|
|
62
|
+
? "bg-transparent border-2 border-action-primary text-headline hover:bg-action-secondary hover:text-action-primary active:bg-action-primary-active active:text-action-primary focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 disabled:border-disabled disabled:text-disabled"
|
|
63
|
+
: variant === "link"
|
|
64
|
+
? "text-brand-700 active:text-brand-700 underline-offset-4 hover:underline focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 disabled:text-disabled disabled:no-underline"
|
|
65
|
+
: "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2";
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const buttonClasses = $derived(() => {
|
|
69
|
+
const sizeStyles = sizeClass();
|
|
70
|
+
const baseClasses =
|
|
71
|
+
"inline-flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none";
|
|
72
|
+
|
|
73
|
+
return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.radius} ${variantClass()}`.trim();
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<button
|
|
78
|
+
{type}
|
|
79
|
+
class={buttonClasses()}
|
|
80
|
+
{disabled}
|
|
81
|
+
{onclick}
|
|
82
|
+
aria-label={label || undefined}
|
|
83
|
+
{...restProps}
|
|
84
|
+
>
|
|
85
|
+
{#if children}
|
|
86
|
+
{@render children()}
|
|
87
|
+
{/if}
|
|
88
|
+
</button>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Visual style variant. */
|
|
5
|
+
variant?: ButtonVariant;
|
|
6
|
+
/** Size variant controlling padding and radius. */
|
|
7
|
+
size?: SizeVariant;
|
|
8
|
+
/** Whether the button is disabled. */
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** Button type attribute. */
|
|
11
|
+
type?: "button" | "submit" | "reset";
|
|
12
|
+
/** Accessible label for icon-only buttons. */
|
|
13
|
+
label?: string;
|
|
14
|
+
/** Click handler for native click events. */
|
|
15
|
+
onclick?: (event: MouseEvent) => void;
|
|
16
|
+
/** Icon content. */
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
}
|
|
19
|
+
declare const IconButton: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type IconButton = ReturnType<typeof IconButton>;
|
|
21
|
+
export default IconButton;
|