v-uixy 1.3.0 → 1.3.1
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 +1 -1
- package/templates/assets/css/main.css +4 -0
- package/templates/components/Badge/Badge.styles.ts +2 -2
- package/templates/components/Checkbox/Checkbox.styles.ts +1 -1
- package/templates/components/ColorPicker/ColorPicker.component.vue +287 -0
- package/templates/components/ColorPicker/ColorPicker.types.ts +3 -0
- package/templates/components/ColorPicker/index.ts +4 -0
- package/templates/components/Input/Input.component.vue +23 -13
- package/templates/components/Input/Input.styles.ts +1 -1
- package/templates/components/Input/Input.types.ts +0 -1
- package/templates/components/Select/Select.component.vue +23 -14
- package/templates/components/Select/Select.styles.ts +1 -1
- package/templates/components/Select/Select.types.ts +0 -1
- package/templates/components/Sheet/Sheet.component.vue +6 -0
- package/templates/components/Textarea/Textarea.component.vue +20 -14
- package/templates/components/Textarea/Textarea.styles.ts +1 -1
- package/templates/components/Textarea/Textarea.types.ts +0 -1
- package/templates/components/index.ts +35 -0
- package/templates/components.json +8 -2
- package/templates/composables/useInputValidation.ts +89 -0
package/package.json
CHANGED
|
@@ -164,6 +164,10 @@
|
|
|
164
164
|
@apply cursor-default border-gray-500 dark:border-gray-500;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
.slider-color-picker::-webkit-slider-thumb {
|
|
168
|
+
@apply relative z-10 size-4.5 cursor-pointer appearance-none rounded-full border-2 border-solid border-gray-900 bg-gray-100;
|
|
169
|
+
}
|
|
170
|
+
|
|
167
171
|
.disable-transitions * {
|
|
168
172
|
transition: none !important;
|
|
169
173
|
}
|
|
@@ -5,9 +5,9 @@ export const badgeStyles = styles(
|
|
|
5
5
|
{
|
|
6
6
|
variant: {
|
|
7
7
|
primary:
|
|
8
|
-
"bg-gray-800 hover:bg-gray-700 dark:hover:bg-gray-200 dark:bg-gray-300 text-gray-100 dark:text-black dark:disabled:bg-gray-400 dark:disabled:text-gray-500 disabled:text-gray-600",
|
|
8
|
+
"bg-gray-800 hover:bg-gray-700 border border-transparent dark:hover:bg-gray-200 dark:bg-gray-300 text-gray-100 dark:text-black dark:disabled:bg-gray-400 dark:disabled:text-gray-500 disabled:text-gray-600",
|
|
9
9
|
secondary:
|
|
10
|
-
"bg-gray-300 hover:bg-gray-200 dark:bg-gray-900 dark:hover:bg-gray-700 dark:text-gray-100 text-black dark:disabled:bg-gray-600 dark:disabled:text-gray-700 disabled:text-gray-500",
|
|
10
|
+
"bg-gray-300 hover:bg-gray-200 border border-transparent dark:bg-gray-900 dark:hover:bg-gray-700 dark:text-gray-100 text-black dark:disabled:bg-gray-600 dark:disabled:text-gray-700 disabled:text-gray-500",
|
|
11
11
|
tertiary:
|
|
12
12
|
"bg-transparent border border-solid border-gray-300 dark:border-gray-800 hover:bg-gray-200 dark:hover:bg-gray-800 text-black dark:text-gray-100 dark:disabled:border-gray-900 dark:disabled:text-gray-500 dark:disabled:bg-gray-800 disabled:bg-gray-300 disabled:text-gray-500 disabled:border-gray-400",
|
|
13
13
|
},
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<uixy-card class="p-2 flex flex-col gap-4">
|
|
3
|
+
<div
|
|
4
|
+
ref="svRef"
|
|
5
|
+
class="relative cursor-crosshair overflow-hidden rounded-1 w-64 h-48"
|
|
6
|
+
:style="svBackgroundStyle"
|
|
7
|
+
@mousedown="onSvMouseDown"
|
|
8
|
+
@touchstart.prevent="onSvTouchStart"
|
|
9
|
+
>
|
|
10
|
+
<div
|
|
11
|
+
class="pointer-events-none absolute -mt-2 -ml-2 size-4 rounded-full border-2 shadow-sm transition-colors border-white"
|
|
12
|
+
:style="svHandleStyle"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="flex flex-col gap-4">
|
|
16
|
+
<input
|
|
17
|
+
type="range"
|
|
18
|
+
min="0"
|
|
19
|
+
max="360"
|
|
20
|
+
step="1"
|
|
21
|
+
v-model.number="hue"
|
|
22
|
+
:disabled="props.disabled"
|
|
23
|
+
class="h-2.5 w-full appearance-none rounded-full outline-none slider-color-picker border border-gray-300 dark:border-gray-900"
|
|
24
|
+
:style="hueSliderStyle"
|
|
25
|
+
/>
|
|
26
|
+
<input
|
|
27
|
+
type="range"
|
|
28
|
+
min="0"
|
|
29
|
+
max="100"
|
|
30
|
+
step="1"
|
|
31
|
+
v-model.number="alpha"
|
|
32
|
+
:disabled="props.disabled"
|
|
33
|
+
class="h-2.5 w-full appearance-none rounded-full outline-none slider-color-picker border border-gray-300 dark:border-gray-900"
|
|
34
|
+
:style="alphaSliderStyle"
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="flex gap-2 items-center mt-2 justify-between">
|
|
38
|
+
<uixy-card class="p-1">
|
|
39
|
+
<div
|
|
40
|
+
class="w-12 h-4 rounded-1 shadow-sm"
|
|
41
|
+
:style="{ backgroundColor: model }"
|
|
42
|
+
></div>
|
|
43
|
+
</uixy-card>
|
|
44
|
+
<p class="text-xs font-500">{{ model }}</p>
|
|
45
|
+
</div>
|
|
46
|
+
</uixy-card>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import type { UixyColorPickerProps } from "./ColorPicker.types";
|
|
51
|
+
import { UixyCard } from "../Card";
|
|
52
|
+
|
|
53
|
+
const props = defineProps<UixyColorPickerProps>();
|
|
54
|
+
|
|
55
|
+
const model = defineModel<string>({ default: "#ff0000" });
|
|
56
|
+
|
|
57
|
+
const hue = ref(0);
|
|
58
|
+
const sat = ref(100);
|
|
59
|
+
const val = ref(100);
|
|
60
|
+
const alpha = ref(100);
|
|
61
|
+
|
|
62
|
+
const clamp = (n: number, min: number, max: number) =>
|
|
63
|
+
Math.min(max, Math.max(min, n));
|
|
64
|
+
|
|
65
|
+
const componentToHex = (c: number) => c.toString(16).padStart(2, "0");
|
|
66
|
+
|
|
67
|
+
const rgbToHex = (r: number, g: number, b: number) =>
|
|
68
|
+
`#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
|
|
69
|
+
|
|
70
|
+
const rgbToHex8 = (r: number, g: number, b: number, a: number) =>
|
|
71
|
+
`#${componentToHex(r)}${componentToHex(g)}${componentToHex(
|
|
72
|
+
b
|
|
73
|
+
)}${componentToHex(clamp(a, 0, 255))}`;
|
|
74
|
+
|
|
75
|
+
const hexToRgbA = (
|
|
76
|
+
hex?: string
|
|
77
|
+
): {
|
|
78
|
+
r: number;
|
|
79
|
+
g: number;
|
|
80
|
+
b: number;
|
|
81
|
+
a: number;
|
|
82
|
+
hasAlpha: boolean;
|
|
83
|
+
} | null => {
|
|
84
|
+
if (!hex) return null;
|
|
85
|
+
const cleaned = hex.trim().replace(/^#/, "");
|
|
86
|
+
if (cleaned.length === 6) {
|
|
87
|
+
const r = parseInt(cleaned.slice(0, 2), 16);
|
|
88
|
+
const g = parseInt(cleaned.slice(2, 4), 16);
|
|
89
|
+
const b = parseInt(cleaned.slice(4, 6), 16);
|
|
90
|
+
return { r, g, b, a: 255, hasAlpha: false } as const;
|
|
91
|
+
}
|
|
92
|
+
if (cleaned.length === 8) {
|
|
93
|
+
const r = parseInt(cleaned.slice(0, 2), 16);
|
|
94
|
+
const g = parseInt(cleaned.slice(2, 4), 16);
|
|
95
|
+
const b = parseInt(cleaned.slice(4, 6), 16);
|
|
96
|
+
const a = parseInt(cleaned.slice(6, 8), 16);
|
|
97
|
+
return { r, g, b, a, hasAlpha: true } as const;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const hsvToRgb = (h: number, s: number, v: number) => {
|
|
103
|
+
const c = v * s;
|
|
104
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
105
|
+
const m = v - c;
|
|
106
|
+
|
|
107
|
+
let r1 = 0,
|
|
108
|
+
g1 = 0,
|
|
109
|
+
b1 = 0;
|
|
110
|
+
|
|
111
|
+
if (h >= 0 && h < 60) {
|
|
112
|
+
r1 = c;
|
|
113
|
+
g1 = x;
|
|
114
|
+
b1 = 0;
|
|
115
|
+
} else if (h < 120) {
|
|
116
|
+
r1 = x;
|
|
117
|
+
g1 = c;
|
|
118
|
+
b1 = 0;
|
|
119
|
+
} else if (h < 180) {
|
|
120
|
+
r1 = 0;
|
|
121
|
+
g1 = c;
|
|
122
|
+
b1 = x;
|
|
123
|
+
} else if (h < 240) {
|
|
124
|
+
r1 = 0;
|
|
125
|
+
g1 = x;
|
|
126
|
+
b1 = c;
|
|
127
|
+
} else if (h < 300) {
|
|
128
|
+
r1 = x;
|
|
129
|
+
g1 = 0;
|
|
130
|
+
b1 = c;
|
|
131
|
+
} else {
|
|
132
|
+
r1 = c;
|
|
133
|
+
g1 = 0;
|
|
134
|
+
b1 = x;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
r: Math.round((r1 + m) * 255),
|
|
138
|
+
g: Math.round((g1 + m) * 255),
|
|
139
|
+
b: Math.round((b1 + m) * 255),
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const rgbToHsv = (r: number, g: number, b: number) => {
|
|
144
|
+
r /= 255;
|
|
145
|
+
g /= 255;
|
|
146
|
+
b /= 255;
|
|
147
|
+
const max = Math.max(r, g, b),
|
|
148
|
+
min = Math.min(r, g, b);
|
|
149
|
+
const d = max - min;
|
|
150
|
+
let h = 0;
|
|
151
|
+
if (d === 0) h = 0;
|
|
152
|
+
else if (max === r) h = ((g - b) / d) % 6;
|
|
153
|
+
else if (max === g) h = (b - r) / d + 2;
|
|
154
|
+
else h = (r - g) / d + 4;
|
|
155
|
+
h = Math.round(h * 60);
|
|
156
|
+
if (h < 0) h += 360;
|
|
157
|
+
const s = max === 0 ? 0 : d / max;
|
|
158
|
+
const v = max;
|
|
159
|
+
return { h, s, v };
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const svRef = ref<HTMLElement | null>(null);
|
|
163
|
+
|
|
164
|
+
const rgb = computed(() =>
|
|
165
|
+
hsvToRgb(hue.value, sat.value / 100, val.value / 100)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const updatingFromModel = ref(false);
|
|
169
|
+
|
|
170
|
+
watch([hue, sat, val, alpha], () => {
|
|
171
|
+
if (updatingFromModel.value) return;
|
|
172
|
+
const a = Math.round((alpha.value / 100) * 255);
|
|
173
|
+
if (a === 255) {
|
|
174
|
+
model.value = rgbToHex(rgb.value.r, rgb.value.g, rgb.value.b);
|
|
175
|
+
} else {
|
|
176
|
+
model.value = rgbToHex8(rgb.value.r, rgb.value.g, rgb.value.b, a);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const applyModel = (value?: string) => {
|
|
181
|
+
const parsed = hexToRgbA(value ?? model.value ?? "#ff0000");
|
|
182
|
+
if (!parsed) return;
|
|
183
|
+
const hsv = rgbToHsv(parsed.r, parsed.g, parsed.b);
|
|
184
|
+
updatingFromModel.value = true;
|
|
185
|
+
hue.value = hsv.h;
|
|
186
|
+
sat.value = Math.round(hsv.s * 100);
|
|
187
|
+
val.value = Math.round(hsv.v * 100);
|
|
188
|
+
alpha.value = clamp(Math.round((parsed.a / 255) * 100), 0, 100);
|
|
189
|
+
|
|
190
|
+
let normalized: string;
|
|
191
|
+
if (!parsed.hasAlpha || parsed.a === 255) {
|
|
192
|
+
normalized = rgbToHex(parsed.r, parsed.g, parsed.b);
|
|
193
|
+
} else {
|
|
194
|
+
normalized = rgbToHex8(parsed.r, parsed.g, parsed.b, parsed.a);
|
|
195
|
+
}
|
|
196
|
+
if (model.value !== normalized) model.value = normalized;
|
|
197
|
+
updatingFromModel.value = false;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
onMounted(() => applyModel());
|
|
201
|
+
watch(model, (v) => applyModel(v));
|
|
202
|
+
|
|
203
|
+
const svBackgroundStyle = computed(() => ({
|
|
204
|
+
background: `linear-gradient(to top, black, transparent), linear-gradient(to right, white, hsl(${hue.value}, 100%, 50%))`,
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
const svHandleStyle = computed(() => ({
|
|
208
|
+
left: `${sat.value}%`,
|
|
209
|
+
top: `${100 - val.value}%`,
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
const hueSliderStyle = computed(() => ({
|
|
213
|
+
background:
|
|
214
|
+
"linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)",
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
const alphaSliderStyle = computed(() => {
|
|
218
|
+
const { r, g, b } = rgb.value;
|
|
219
|
+
return {
|
|
220
|
+
background: `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0), rgba(${r}, ${g}, ${b}, 1))`,
|
|
221
|
+
} as Record<string, string>;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const updateSvFromEvent = (e: MouseEvent) => {
|
|
225
|
+
if (!svRef.value) return;
|
|
226
|
+
const rect = svRef.value.getBoundingClientRect();
|
|
227
|
+
const x = clamp(((e.clientX - rect.left) / rect.width) * 100, 0, 100);
|
|
228
|
+
const y = clamp(((e.clientY - rect.top) / rect.height) * 100, 0, 100);
|
|
229
|
+
sat.value = Math.round(x);
|
|
230
|
+
val.value = Math.round(100 - y);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const updateSvFromTouch = (e: TouchEvent) => {
|
|
234
|
+
if (!svRef.value) return;
|
|
235
|
+
const t = e.touches?.[0] ?? e.changedTouches?.[0];
|
|
236
|
+
if (!t) return;
|
|
237
|
+
const rect = svRef.value.getBoundingClientRect();
|
|
238
|
+
const x = clamp(((t.clientX - rect.left) / rect.width) * 100, 0, 100);
|
|
239
|
+
const y = clamp(((t.clientY - rect.top) / rect.height) * 100, 0, 100);
|
|
240
|
+
sat.value = Math.round(x);
|
|
241
|
+
val.value = Math.round(100 - y);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
let dragging = false;
|
|
245
|
+
|
|
246
|
+
const onSvMouseDown = (e: MouseEvent) => {
|
|
247
|
+
if (props.disabled) return;
|
|
248
|
+
|
|
249
|
+
dragging = true;
|
|
250
|
+
|
|
251
|
+
updateSvFromEvent(e);
|
|
252
|
+
|
|
253
|
+
window.addEventListener("mousemove", onMouseMove);
|
|
254
|
+
window.addEventListener("mouseup", onMouseUp);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
258
|
+
if (!dragging) return;
|
|
259
|
+
|
|
260
|
+
updateSvFromEvent(e);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const onMouseUp = () => {
|
|
264
|
+
dragging = false;
|
|
265
|
+
|
|
266
|
+
window.removeEventListener("mousemove", onMouseMove);
|
|
267
|
+
window.removeEventListener("mouseup", onMouseUp);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const onSvTouchStart = (e: TouchEvent) => {
|
|
271
|
+
if (props.disabled) return;
|
|
272
|
+
|
|
273
|
+
updateSvFromTouch(e);
|
|
274
|
+
|
|
275
|
+
window.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
276
|
+
window.addEventListener("touchend", onTouchEnd);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const onTouchMove = (e: TouchEvent) => {
|
|
280
|
+
updateSvFromTouch(e);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const onTouchEnd = () => {
|
|
284
|
+
window.removeEventListener("touchmove", onTouchMove);
|
|
285
|
+
window.removeEventListener("touchend", onTouchEnd);
|
|
286
|
+
};
|
|
287
|
+
</script>
|
|
@@ -27,19 +27,29 @@
|
|
|
27
27
|
{{ props.label }}
|
|
28
28
|
</label>
|
|
29
29
|
</div>
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
<animate-presence mode="wait" :initial="false">
|
|
31
|
+
<motion.div
|
|
32
|
+
v-if="!!text"
|
|
33
|
+
class="overflow-hidden"
|
|
34
|
+
:initial="{ height: 0 }"
|
|
35
|
+
:animate="{ height: 'auto' }"
|
|
36
|
+
:exit="{ height: 0 }"
|
|
37
|
+
:transition="{ duration: 0.1, ease: 'easeInOut' }"
|
|
38
|
+
>
|
|
39
|
+
<animate-presence mode="wait" :initial="false">
|
|
40
|
+
<motion.p
|
|
41
|
+
:key="status + text"
|
|
42
|
+
:class="helperStyles({ status })"
|
|
43
|
+
:initial="{ opacity: 0, y: -4 }"
|
|
44
|
+
:animate="{ opacity: 1, y: 0 }"
|
|
45
|
+
:exit="{ opacity: 0, y: 4 }"
|
|
46
|
+
:transition="{ duration: 0.12, ease: 'easeInOut', delay: 0.1 }"
|
|
47
|
+
>
|
|
48
|
+
{{ text }}
|
|
49
|
+
</motion.p>
|
|
50
|
+
</animate-presence>
|
|
51
|
+
</motion.div>
|
|
52
|
+
</animate-presence>
|
|
43
53
|
</div>
|
|
44
54
|
</template>
|
|
45
55
|
|
|
@@ -52,7 +52,6 @@
|
|
|
52
52
|
icon="search"
|
|
53
53
|
iconPositon="right"
|
|
54
54
|
placeholder="Search..."
|
|
55
|
-
hide-helper
|
|
56
55
|
auto-focus
|
|
57
56
|
/>
|
|
58
57
|
</div>
|
|
@@ -90,19 +89,29 @@
|
|
|
90
89
|
{{ props.label }}
|
|
91
90
|
</div>
|
|
92
91
|
</div>
|
|
93
|
-
<
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
92
|
+
<animate-presence mode="wait" :initial="false">
|
|
93
|
+
<motion.div
|
|
94
|
+
v-if="!!text"
|
|
95
|
+
class="overflow-hidden"
|
|
96
|
+
:initial="{ height: 0 }"
|
|
97
|
+
:animate="{ height: 'auto' }"
|
|
98
|
+
:exit="{ height: 0 }"
|
|
99
|
+
:transition="{ duration: 0.1, ease: 'easeInOut' }"
|
|
100
|
+
>
|
|
101
|
+
<animate-presence mode="wait" :initial="false">
|
|
102
|
+
<motion.p
|
|
103
|
+
:key="status + text"
|
|
104
|
+
:class="helperStyles({ status })"
|
|
105
|
+
:initial="{ opacity: 0, y: -4 }"
|
|
106
|
+
:animate="{ opacity: 1, y: 0 }"
|
|
107
|
+
:exit="{ opacity: 0, y: 4 }"
|
|
108
|
+
:transition="{ duration: 0.12, ease: 'easeInOut', delay: 0.1 }"
|
|
109
|
+
>
|
|
110
|
+
{{ text }}
|
|
111
|
+
</motion.p>
|
|
112
|
+
</animate-presence>
|
|
113
|
+
</motion.div>
|
|
114
|
+
</animate-presence>
|
|
106
115
|
</div>
|
|
107
116
|
</template>
|
|
108
117
|
|
|
@@ -19,28 +19,34 @@
|
|
|
19
19
|
{{ props.label }}
|
|
20
20
|
</label>
|
|
21
21
|
</div>
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
<animate-presence mode="wait" :initial="false">
|
|
23
|
+
<motion.div
|
|
24
|
+
v-if="!!text"
|
|
25
|
+
class="overflow-hidden"
|
|
26
|
+
:initial="{ height: 0 }"
|
|
27
|
+
:animate="{ height: 'auto' }"
|
|
28
|
+
:exit="{ height: 0 }"
|
|
29
|
+
:transition="{ duration: 0.1, ease: 'easeInOut' }"
|
|
30
|
+
>
|
|
31
|
+
<animate-presence mode="wait" :initial="false">
|
|
32
|
+
<motion.p
|
|
33
|
+
:key="status + text"
|
|
32
34
|
:class="
|
|
33
35
|
helperStyles({
|
|
34
36
|
status,
|
|
35
37
|
align: props.alignText ?? 'left',
|
|
36
38
|
})
|
|
37
39
|
"
|
|
40
|
+
:initial="{ opacity: 0, y: -4 }"
|
|
41
|
+
:animate="{ opacity: 1, y: 0 }"
|
|
42
|
+
:exit="{ opacity: 0, y: 4 }"
|
|
43
|
+
:transition="{ duration: 0.12, ease: 'easeInOut', delay: 0.1 }"
|
|
38
44
|
>
|
|
39
45
|
{{ text }}
|
|
40
|
-
</p>
|
|
41
|
-
</
|
|
42
|
-
</
|
|
43
|
-
</
|
|
46
|
+
</motion.p>
|
|
47
|
+
</animate-presence>
|
|
48
|
+
</motion.div>
|
|
49
|
+
</animate-presence>
|
|
44
50
|
</div>
|
|
45
51
|
</template>
|
|
46
52
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export * from "./Accordion";
|
|
2
|
+
export * from "./Alert";
|
|
3
|
+
export * from "./Avatar";
|
|
4
|
+
export * from "./Badge";
|
|
5
|
+
export * from "./Button";
|
|
6
|
+
export * from "./Calendar";
|
|
7
|
+
export * from "./Card";
|
|
8
|
+
export * from "./Checkbox";
|
|
9
|
+
export * from "./Command";
|
|
10
|
+
export * from "./Icon";
|
|
11
|
+
export * from "./IconButton";
|
|
12
|
+
export * from "./Input";
|
|
13
|
+
export * from "./InputOTP";
|
|
14
|
+
export * from "./Link";
|
|
15
|
+
export * from "./Loader";
|
|
16
|
+
export * from "./Modal";
|
|
17
|
+
export * from "./PieChart";
|
|
18
|
+
export * from "./Popover";
|
|
19
|
+
export * from "./Radio";
|
|
20
|
+
export * from "./Scale";
|
|
21
|
+
export * from "./Select";
|
|
22
|
+
export * from "./Separator";
|
|
23
|
+
export * from "./Sheet";
|
|
24
|
+
export * from "./Skeleton";
|
|
25
|
+
export * from "./Slider";
|
|
26
|
+
export * from "./Switch";
|
|
27
|
+
export * from "./TableOfContents";
|
|
28
|
+
export * from "./Tabs";
|
|
29
|
+
export * from "./Textarea";
|
|
30
|
+
export * from "./Theme";
|
|
31
|
+
export * from "./TimePicker";
|
|
32
|
+
export * from "./Toast";
|
|
33
|
+
export * from "./Toggle";
|
|
34
|
+
export * from "./Tooltip";
|
|
35
|
+
export * from "./ColorPicker";
|
|
@@ -48,6 +48,12 @@
|
|
|
48
48
|
"packages": ["motion-v"],
|
|
49
49
|
"composables": []
|
|
50
50
|
},
|
|
51
|
+
{
|
|
52
|
+
"name": "ColorPicker",
|
|
53
|
+
"related-components": ["Card"],
|
|
54
|
+
"packages": [],
|
|
55
|
+
"composables": []
|
|
56
|
+
},
|
|
51
57
|
{
|
|
52
58
|
"name": "Command",
|
|
53
59
|
"related-components": ["Modal", "Card", "Link", "Icon", "Separator"],
|
|
@@ -70,7 +76,7 @@
|
|
|
70
76
|
"name": "Input",
|
|
71
77
|
"related-components": ["Icon"],
|
|
72
78
|
"packages": ["motion-v"],
|
|
73
|
-
"composables": []
|
|
79
|
+
"composables": ["useInputValidation"]
|
|
74
80
|
},
|
|
75
81
|
{
|
|
76
82
|
"name": "InputOTP",
|
|
@@ -124,7 +130,7 @@
|
|
|
124
130
|
"name": "Select",
|
|
125
131
|
"related-components": ["Badge", "Icon", "Input"],
|
|
126
132
|
"packages": ["motion-v"],
|
|
127
|
-
"composables": []
|
|
133
|
+
"composables": ["useInputValidation"]
|
|
128
134
|
},
|
|
129
135
|
{
|
|
130
136
|
"name": "Separator",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ref, computed, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export type UseValidationProps = {
|
|
4
|
+
validation: ((value: string) => true | string)[];
|
|
5
|
+
base?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function useInputValidation<
|
|
9
|
+
T extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement
|
|
10
|
+
>({ validation, base }: UseValidationProps) {
|
|
11
|
+
const value = ref(base ?? "");
|
|
12
|
+
const touched = ref(false);
|
|
13
|
+
const error = ref<string | undefined>(undefined);
|
|
14
|
+
const elRef: Ref<T | null> = ref(null);
|
|
15
|
+
|
|
16
|
+
const onInput = (e: Event) => {
|
|
17
|
+
const target = e.target as T | null;
|
|
18
|
+
if (error.value) error.value = undefined;
|
|
19
|
+
if (target) value.value = target.value;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const onBlur = () => {
|
|
23
|
+
touched.value = true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const valid = computed(
|
|
27
|
+
() => !error.value && validation.every((v) => v(value.value) === true)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const status = computed<"valid" | "error" | "default">(() => {
|
|
31
|
+
if (!touched.value) return "default";
|
|
32
|
+
if (error.value || !valid.value) return "error";
|
|
33
|
+
if (valid.value) return "valid";
|
|
34
|
+
|
|
35
|
+
return "default";
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const parsedValidation = computed(() => {
|
|
39
|
+
const found = validation.find((v) => v(value.value) !== true);
|
|
40
|
+
return found as ((v: string) => string) | undefined;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const errorText = computed(() =>
|
|
44
|
+
touched.value
|
|
45
|
+
? error.value || parsedValidation.value?.(value.value)
|
|
46
|
+
: undefined
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const reset = (hard = false) => {
|
|
50
|
+
touched.value = !hard;
|
|
51
|
+
error.value = undefined;
|
|
52
|
+
value.value = "";
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const setError = async (message: string) => {
|
|
56
|
+
error.value = message;
|
|
57
|
+
touched.value = true;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const bindings = computed<Record<string, any>>(() => ({
|
|
61
|
+
ref: elRef,
|
|
62
|
+
modelValue: value.value,
|
|
63
|
+
["onUpdate:modelValue"]: (v: string) => {
|
|
64
|
+
value.value = v;
|
|
65
|
+
},
|
|
66
|
+
status: status.value,
|
|
67
|
+
errorText: errorText.value,
|
|
68
|
+
onInput,
|
|
69
|
+
onBlur,
|
|
70
|
+
"aria-invalid": status.value === "error",
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
ref: elRef,
|
|
75
|
+
value,
|
|
76
|
+
touched,
|
|
77
|
+
errorText,
|
|
78
|
+
status,
|
|
79
|
+
valid,
|
|
80
|
+
error,
|
|
81
|
+
reset,
|
|
82
|
+
setError,
|
|
83
|
+
onInput,
|
|
84
|
+
onBlur,
|
|
85
|
+
bindings,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default useInputValidation;
|