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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/templates/assets/css/main.css +8 -0
  3. package/templates/components/Avatar/Avatar.component.vue +7 -3
  4. package/templates/components/Avatar/Avatar.styles.ts +3 -3
  5. package/templates/components/Badge/Badge.styles.ts +2 -2
  6. package/templates/components/Button/Button.styles.ts +3 -3
  7. package/templates/components/Calendar/Calendar.component.vue +26 -15
  8. package/templates/components/Calendar/Calendar.styles.ts +11 -3
  9. package/templates/components/Calendar/Calendar.types.ts +1 -0
  10. package/templates/components/ColorPicker/ColorPicker.component.vue +287 -0
  11. package/templates/components/ColorPicker/ColorPicker.types.ts +3 -0
  12. package/templates/components/ColorPicker/index.ts +4 -0
  13. package/templates/components/Command/Command.component.vue +6 -6
  14. package/templates/components/Command/Command.types.ts +1 -0
  15. package/templates/components/Input/Input.component.vue +14 -4
  16. package/templates/components/Input/Input.styles.ts +4 -4
  17. package/templates/components/Modal/Modal.component.vue +28 -4
  18. package/templates/components/Modal/Modal.types.ts +3 -1
  19. package/templates/components/Popover/Popover.component.vue +33 -2
  20. package/templates/components/Popover/Popover.types.ts +1 -0
  21. package/templates/components/Select/Select.component.vue +241 -87
  22. package/templates/components/Select/Select.styles.ts +5 -5
  23. package/templates/components/Select/Select.types.ts +12 -1
  24. package/templates/components/Select/SelectTreeItem.vue +154 -0
  25. package/templates/components/Select/composables/useSelect.composable.ts +260 -42
  26. package/templates/components/Sheet/Sheet.component.vue +6 -0
  27. package/templates/components/Tabs/Tabs.component.vue +11 -2
  28. package/templates/components/Tabs/Tabs.types.ts +1 -0
  29. package/templates/components/Textarea/Textarea.component.vue +10 -13
  30. package/templates/components/Textarea/Textarea.styles.ts +3 -3
  31. package/templates/components/TimePicker/TimePicker.component.vue +21 -18
  32. package/templates/components/TimePicker/TimePicker.styles.ts +1 -1
  33. package/templates/components/TimePicker/TimePicker.types.ts +1 -0
  34. package/templates/components/index.ts +35 -0
  35. package/templates/components.json +9 -3
  36. package/templates/composables/useInputValidation.ts +89 -0
  37. 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
- let validatedNum = num;
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 valueStr = validateAndFormatTime(target.value, type);
82
- const valueNum = Number.parseInt(valueStr, 10);
76
+ const value = target.value.replace(/\D/g, "");
77
+
78
+ if (value === "") return;
79
+
80
+ let num = parseInt(value, 10);
83
81
 
84
- model.value = {
85
- ...model.value,
86
- [type]: Number.isFinite(valueNum) ? valueNum : 0,
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-between gap-3 rounded-2 border border-gray-300 bg-gray-100 p-2 shadow-xs dark:border-gray-900 dark:bg-black"
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
  );
@@ -1,3 +1,4 @@
1
1
  export interface UixyTimePickerProps {
2
2
  disabled?: boolean;
3
+ allowMidnight?: boolean;
3
4
  }
@@ -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
- top =
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(() => {