v-uixy 1.3.0 → 1.4.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 +1 -1
- package/templates/assets/css/main.css +8 -0
- package/templates/components/Avatar/Avatar.component.vue +7 -3
- package/templates/components/Avatar/Avatar.styles.ts +3 -3
- package/templates/components/Badge/Badge.styles.ts +2 -2
- package/templates/components/Button/Button.styles.ts +3 -3
- package/templates/components/Calendar/Calendar.component.vue +26 -15
- package/templates/components/Calendar/Calendar.styles.ts +11 -3
- package/templates/components/Calendar/Calendar.types.ts +1 -0
- 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/Command/Command.component.vue +6 -6
- package/templates/components/Command/Command.types.ts +1 -0
- package/templates/components/Input/Input.component.vue +14 -4
- package/templates/components/Input/Input.styles.ts +4 -4
- package/templates/components/Modal/Modal.component.vue +28 -4
- package/templates/components/Modal/Modal.types.ts +3 -1
- package/templates/components/Popover/Popover.component.vue +33 -2
- package/templates/components/Popover/Popover.types.ts +1 -0
- package/templates/components/Select/Select.component.vue +241 -87
- package/templates/components/Select/Select.styles.ts +5 -5
- package/templates/components/Select/Select.types.ts +12 -1
- package/templates/components/Select/SelectTreeItem.vue +154 -0
- package/templates/components/Select/composables/useSelect.composable.ts +260 -42
- package/templates/components/Sheet/Sheet.component.vue +6 -0
- package/templates/components/Tabs/Tabs.component.vue +11 -2
- package/templates/components/Tabs/Tabs.types.ts +1 -0
- package/templates/components/Textarea/Textarea.component.vue +10 -13
- package/templates/components/Textarea/Textarea.styles.ts +3 -3
- package/templates/components/TimePicker/TimePicker.component.vue +21 -18
- package/templates/components/TimePicker/TimePicker.styles.ts +1 -1
- package/templates/components/TimePicker/TimePicker.types.ts +1 -0
- package/templates/components/index.ts +35 -0
- package/templates/components.json +9 -3
- package/templates/composables/useInputValidation.ts +89 -0
- package/templates/composables/usePosition.ts +17 -2
|
@@ -62,29 +62,34 @@
|
|
|
62
62
|
|
|
63
63
|
const validateAndFormatTime = (
|
|
64
64
|
value: string,
|
|
65
|
-
type: "hour" | "minute" | "second"
|
|
66
|
-
) => {
|
|
65
|
+
type: "hour" | "minute" | "second",
|
|
66
|
+
): string => {
|
|
67
67
|
const num = parseInt(value, 10);
|
|
68
68
|
if (isNaN(num)) return "00";
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (type === "hour") validatedNum = Math.min(23, Math.max(0, num));
|
|
73
|
-
else if (type === "minute") validatedNum = Math.min(59, Math.max(0, num));
|
|
74
|
-
else validatedNum = Math.min(59, Math.max(0, num));
|
|
75
|
-
|
|
76
|
-
return validatedNum < 10 ? `0${validatedNum}` : `${validatedNum}`;
|
|
69
|
+
const max = type === "hour" ? 23 : 59;
|
|
70
|
+
const clamped = Math.max(0, Math.min(max, num));
|
|
71
|
+
return clamped < 10 ? `0${clamped}` : `${clamped}`;
|
|
77
72
|
};
|
|
78
73
|
|
|
79
74
|
const handleChange = (event: Event, type: "hour" | "minute" | "second") => {
|
|
80
75
|
const target = event.target as HTMLInputElement;
|
|
81
|
-
const
|
|
82
|
-
|
|
76
|
+
const value = target.value.replace(/\D/g, "");
|
|
77
|
+
|
|
78
|
+
if (value === "") return;
|
|
79
|
+
|
|
80
|
+
let num = parseInt(value, 10);
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
if (type === "hour") {
|
|
83
|
+
if (props.allowMidnight && num >= 24) {
|
|
84
|
+
num = 0;
|
|
85
|
+
} else if (num > 23) {
|
|
86
|
+
num = 23;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
if (num > 59) num = 59;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
model.value = { ...model.value, [type]: num };
|
|
88
93
|
};
|
|
89
94
|
|
|
90
95
|
const displaySeparator = (order: "first" | "second") => {
|
|
@@ -92,7 +97,6 @@
|
|
|
92
97
|
|
|
93
98
|
if (order === "first") {
|
|
94
99
|
if (!model.value?.hasOwnProperty("hour")) return false;
|
|
95
|
-
|
|
96
100
|
if (
|
|
97
101
|
model.value?.hasOwnProperty("minute") ||
|
|
98
102
|
model.value?.hasOwnProperty("second")
|
|
@@ -102,7 +106,6 @@
|
|
|
102
106
|
|
|
103
107
|
if (order === "second") {
|
|
104
108
|
if (!model.value?.hasOwnProperty("second")) return false;
|
|
105
|
-
|
|
106
109
|
if (
|
|
107
110
|
model.value?.hasOwnProperty("hour") ||
|
|
108
111
|
model.value?.hasOwnProperty("minute")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import styles from "~/utils/styles";
|
|
2
2
|
|
|
3
3
|
export const timePickerStyles = styles(
|
|
4
|
-
"flex items-center justify-
|
|
4
|
+
"flex items-center justify-start gap-2 rounded-1 border border-gray-300 bg-white dark:bg-gray-1000 px-2 py-0.5 min-h-[38px] text-sm transition-colors ease-in-out duration-150 shadow-xs dark:border-gray-900",
|
|
5
5
|
);
|
|
@@ -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",
|
|
@@ -105,7 +111,7 @@
|
|
|
105
111
|
{
|
|
106
112
|
"name": "Popover",
|
|
107
113
|
"related-components": [],
|
|
108
|
-
"packages": ["motion-v"],
|
|
114
|
+
"packages": ["motion-v", "uuid"],
|
|
109
115
|
"composables": ["usePosition"]
|
|
110
116
|
},
|
|
111
117
|
{
|
|
@@ -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;
|
|
@@ -39,12 +39,27 @@ export function usePosition({ direction = "bottom" }: UsePositionOptions) {
|
|
|
39
39
|
const tooltipRect = refElement.value.getBoundingClientRect();
|
|
40
40
|
const targetRect = target.value.getBoundingClientRect();
|
|
41
41
|
const winW = window.innerWidth;
|
|
42
|
+
const winH = window.innerHeight;
|
|
42
43
|
|
|
43
44
|
let top: number;
|
|
44
45
|
const scrollY = hasFixedParent(target.value) ? 0 : window.scrollY;
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
const spaceBelow = winH - targetRect.bottom;
|
|
48
|
+
const spaceAbove = targetRect.top;
|
|
49
|
+
const fitsBelow = spaceBelow >= tooltipRect.height + 6;
|
|
50
|
+
const fitsAbove = spaceAbove >= tooltipRect.height + 6;
|
|
51
|
+
|
|
52
|
+
const effectiveDirection =
|
|
47
53
|
direction === "top"
|
|
54
|
+
? fitsAbove || !fitsBelow
|
|
55
|
+
? "top"
|
|
56
|
+
: "bottom"
|
|
57
|
+
: fitsBelow || !fitsAbove
|
|
58
|
+
? "bottom"
|
|
59
|
+
: "top";
|
|
60
|
+
|
|
61
|
+
top =
|
|
62
|
+
effectiveDirection === "top"
|
|
48
63
|
? targetRect.top - tooltipRect.height - 6 + scrollY
|
|
49
64
|
: targetRect.bottom + 6 + scrollY;
|
|
50
65
|
|
|
@@ -80,7 +95,7 @@ export function usePosition({ direction = "bottom" }: UsePositionOptions) {
|
|
|
80
95
|
window.removeEventListener("resize", updatePosition);
|
|
81
96
|
}
|
|
82
97
|
},
|
|
83
|
-
{ flush: "post" }
|
|
98
|
+
{ flush: "post" },
|
|
84
99
|
);
|
|
85
100
|
|
|
86
101
|
onBeforeUnmount(() => {
|