zabi-components 5.0.19 → 5.0.20
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/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/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 +17 -22
- 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 +0 -2
- package/dist/atoms/index.js +0 -2
- package/dist/components/atoms/index.d.ts +0 -2
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +7 -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/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 +7 -0
- package/dist/molecules/index.js +7 -0
- 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 +192 -27
- package/package.json +1 -1
- 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/Button.svelte
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
type?: "button" | "submit" | "reset";
|
|
10
10
|
text?: string;
|
|
11
|
+
isFullWidth?: boolean;
|
|
11
12
|
onclick?: (event: MouseEvent) => void;
|
|
12
13
|
children?: Snippet;
|
|
13
14
|
}
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
disabled = false,
|
|
19
20
|
type = "button",
|
|
20
21
|
text = "",
|
|
22
|
+
isFullWidth = false,
|
|
21
23
|
onclick,
|
|
22
24
|
children,
|
|
23
25
|
...restProps
|
|
@@ -75,10 +77,11 @@
|
|
|
75
77
|
|
|
76
78
|
const buttonClasses = $derived(() => {
|
|
77
79
|
const sizeStyles = sizeClass();
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
+
const flexClass = isFullWidth ? "flex" : "inline-flex";
|
|
81
|
+
const widthClass = isFullWidth ? "w-full" : "";
|
|
82
|
+
const baseClasses = `${flexClass} items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none focus:outline-none`;
|
|
80
83
|
|
|
81
|
-
return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.font} ${sizeStyles.leading} ${sizeStyles.tracking} ${sizeStyles.radius} ${sizeStyles.gap} ${variantClass()}`.trim();
|
|
84
|
+
return `${baseClasses} ${widthClass} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.font} ${sizeStyles.leading} ${sizeStyles.tracking} ${sizeStyles.radius} ${sizeStyles.gap} ${variantClass()}`.trim();
|
|
82
85
|
});
|
|
83
86
|
</script>
|
|
84
87
|
|
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>
|
package/dist/atoms/Input.svelte
CHANGED
|
@@ -21,10 +21,12 @@
|
|
|
21
21
|
variant?: SemanticVariant;
|
|
22
22
|
message?: string;
|
|
23
23
|
oninput?: (event: Event) => void;
|
|
24
|
+
onblur?: (event: Event) => void;
|
|
25
|
+
"aria-label"?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
let {
|
|
27
|
-
value = "",
|
|
29
|
+
value = $bindable(""),
|
|
28
30
|
type = "text",
|
|
29
31
|
name = "",
|
|
30
32
|
label = "",
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
variant = "default",
|
|
35
37
|
message = "",
|
|
36
38
|
oninput,
|
|
39
|
+
onblur,
|
|
37
40
|
...restProps
|
|
38
41
|
}: Props = $props();
|
|
39
42
|
|
|
@@ -74,7 +77,7 @@
|
|
|
74
77
|
const inputClasses = $derived(() => {
|
|
75
78
|
const sizeStyles = sizeClass();
|
|
76
79
|
const baseClasses =
|
|
77
|
-
"w-full bg-input hover:bg-input-hover focus:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
80
|
+
"w-full min-w-48 bg-input hover:bg-input-hover focus:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
78
81
|
|
|
79
82
|
return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()}`.trim();
|
|
80
83
|
});
|
|
@@ -102,9 +105,6 @@
|
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
function handleInput(event: Event) {
|
|
105
|
-
const target = event.target as HTMLInputElement;
|
|
106
|
-
value = target.value;
|
|
107
|
-
|
|
108
108
|
if (oninput) {
|
|
109
109
|
oninput(event);
|
|
110
110
|
}
|
|
@@ -119,11 +119,12 @@
|
|
|
119
119
|
id={inputId}
|
|
120
120
|
{type}
|
|
121
121
|
{name}
|
|
122
|
-
|
|
122
|
+
bind:value
|
|
123
123
|
{placeholder}
|
|
124
124
|
{disabled}
|
|
125
125
|
class={inputClasses()}
|
|
126
126
|
oninput={handleInput}
|
|
127
|
+
{onblur}
|
|
127
128
|
aria-invalid={variant === "error" ? "true" : undefined}
|
|
128
129
|
aria-describedby={message ? `${inputId}-message` : undefined}
|
|
129
130
|
{...restProps}
|
|
@@ -10,7 +10,9 @@ interface Props {
|
|
|
10
10
|
variant?: SemanticVariant;
|
|
11
11
|
message?: string;
|
|
12
12
|
oninput?: (event: Event) => void;
|
|
13
|
+
onblur?: (event: Event) => void;
|
|
14
|
+
"aria-label"?: string;
|
|
13
15
|
}
|
|
14
|
-
declare const Input: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
declare const Input: import("svelte").Component<Props, {}, "value">;
|
|
15
17
|
type Input = ReturnType<typeof Input>;
|
|
16
18
|
export default Input;
|
|
@@ -41,9 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div
|
|
43
43
|
id={progressId}
|
|
44
|
-
class="w-full bg-
|
|
45
|
-
size
|
|
46
|
-
]}"
|
|
44
|
+
class="w-full bg-input rounded-full overflow-hidden {sizeClasses[size]}"
|
|
47
45
|
role="progressbar"
|
|
48
46
|
aria-valuenow={value}
|
|
49
47
|
aria-valuemin="0"
|