v-uixy 1.5.0 → 1.7.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 (26) hide show
  1. package/package.json +1 -1
  2. package/templates/components/Alert/Alert.component.vue +53 -11
  3. package/templates/components/Alert/Alert.types.ts +2 -0
  4. package/templates/components/Alert/AlertItem.component.vue +21 -2
  5. package/templates/components/Button/Button.styles.ts +4 -4
  6. package/templates/components/Checkbox/Checkbox.component.vue +19 -5
  7. package/templates/components/Checkbox/Checkbox.styles.ts +10 -0
  8. package/templates/components/Checkbox/Checkbox.types.ts +1 -0
  9. package/templates/components/DatePicker/DatePicker.component.vue +23 -3
  10. package/templates/components/DatePicker/DatePicker.types.ts +0 -1
  11. package/templates/components/DateRangePicker/DateRangePicker.component.vue +29 -4
  12. package/templates/components/DateRangePicker/DateRangePicker.types.ts +0 -1
  13. package/templates/components/FileUpload/FileUpload.component.vue +23 -13
  14. package/templates/components/Input/Input.component.vue +23 -13
  15. package/templates/components/Input/Input.types.ts +0 -1
  16. package/templates/components/Popover/Popover.component.vue +1 -1
  17. package/templates/components/Select/Select.component.vue +23 -14
  18. package/templates/components/Select/Select.types.ts +0 -1
  19. package/templates/components/Table/Table.component.vue +507 -0
  20. package/templates/components/Table/Table.styles.ts +140 -0
  21. package/templates/components/Table/Table.types.ts +98 -0
  22. package/templates/components/Table/index.ts +39 -0
  23. package/templates/components/Textarea/Textarea.component.vue +20 -14
  24. package/templates/components/Textarea/Textarea.types.ts +0 -1
  25. package/templates/components.json +6 -0
  26. package/templates/composables/usePosition.ts +6 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "v-uixy",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "private": false,
5
5
  "author": {
6
6
  "name": "Alan Haber",
@@ -22,6 +22,8 @@
22
22
  v-bind="alert"
23
23
  :key="alert.id"
24
24
  @close-alert="close(alert.id)"
25
+ @pause-timer="pauseTimer(alert.id)"
26
+ @resume-timer="resumeTimer(alert.id)"
25
27
  />
26
28
  </motion.li>
27
29
  </animate-presence>
@@ -41,9 +43,25 @@
41
43
 
42
44
  const alerts = ref<UixyAlertItemProps[]>([]);
43
45
 
44
- const timeouts = reactive<
45
- { id: string; timeout: ReturnType<typeof setTimeout> }[]
46
- >([]);
46
+ const ALERT_DURATION = 8000;
47
+
48
+ interface AlertTimeout {
49
+ id: string;
50
+ timeout: ReturnType<typeof setTimeout> | null;
51
+ remaining: number;
52
+ startedAt: number;
53
+ }
54
+
55
+ const timeouts = reactive<AlertTimeout[]>([]);
56
+
57
+ const startTimeout = (entry: AlertTimeout) => {
58
+ entry.startedAt = Date.now();
59
+ entry.timeout = setTimeout(() => {
60
+ alerts.value = alerts.value.filter((a) => a.id !== entry.id);
61
+ const index = timeouts.findIndex((t) => t.id === entry.id);
62
+ if (index !== -1) timeouts.splice(index, 1);
63
+ }, entry.remaining);
64
+ };
47
65
 
48
66
  const push = (alert: Omit<UixyAlertItemProps, "id">) => {
49
67
  const id = uuidv4();
@@ -56,20 +74,25 @@
56
74
  },
57
75
  ];
58
76
 
59
- timeouts.push({
77
+ const entry: AlertTimeout = {
60
78
  id,
61
- timeout: setTimeout(() => {
62
- alerts.value = alerts.value.filter((a) => a.id !== id);
63
- }, 8000),
64
- });
79
+ timeout: null,
80
+ remaining: ALERT_DURATION,
81
+ startedAt: Date.now(),
82
+ };
83
+
84
+ timeouts.push(entry);
85
+ startTimeout(entry);
65
86
  };
66
87
 
67
88
  const close = (id: string) => {
68
89
  alerts.value = alerts.value.filter((alert) => {
69
90
  if (alert.id === id) {
70
- const timeout = timeouts.find((t) => t.id === id);
71
- if (timeout) {
72
- clearTimeout(timeout.timeout);
91
+ const index = timeouts.findIndex((t) => t.id === id);
92
+ if (index !== -1) {
93
+ const timeout = timeouts[index];
94
+ if (timeout.timeout) clearTimeout(timeout.timeout);
95
+ timeouts.splice(index, 1);
73
96
  }
74
97
  return false;
75
98
  }
@@ -77,6 +100,25 @@
77
100
  });
78
101
  };
79
102
 
103
+ const pauseTimer = (id: string) => {
104
+ const entry = timeouts.find((t) => t.id === id);
105
+ if (!entry || !entry.timeout) return;
106
+
107
+ clearTimeout(entry.timeout);
108
+ entry.timeout = null;
109
+ entry.remaining = Math.max(
110
+ entry.remaining - (Date.now() - entry.startedAt),
111
+ 0,
112
+ );
113
+ };
114
+
115
+ const resumeTimer = (id: string) => {
116
+ const entry = timeouts.find((t) => t.id === id);
117
+ if (!entry || entry.timeout) return;
118
+
119
+ startTimeout(entry);
120
+ };
121
+
80
122
  provide("push-alert", push);
81
123
  provide("close-alert", close);
82
124
  </script>
@@ -15,4 +15,6 @@ export interface UixyAlertItemProps {
15
15
 
16
16
  export interface UixyAlertItemEmits {
17
17
  (event: "close-alert"): void;
18
+ (event: "pause-timer"): void;
19
+ (event: "resume-timer"): void;
18
20
  }
@@ -1,6 +1,8 @@
1
1
  <template>
2
2
  <uixy-card
3
3
  class="relative flex w-full items-center justify-between gap-8 overflow-hidden bg-gray-200 py-4 shadow-xs dark:bg-black"
4
+ @mouseenter="handleMouseEnter"
5
+ @mouseleave="handleMouseLeave"
4
6
  >
5
7
  <div class="flex gap-4">
6
8
  <uixy-icon v-if="props.icon" :name="props.icon" class="size-6 shrink-0" />
@@ -34,7 +36,12 @@
34
36
  </template>
35
37
 
36
38
  <script setup lang="ts">
37
- import { motion, useMotionValue, animate } from "motion-v";
39
+ import {
40
+ motion,
41
+ useMotionValue,
42
+ animate,
43
+ type AnimationPlaybackControls,
44
+ } from "motion-v";
38
45
  import { UixyButton, UixyIconButton, UixyIcon, UixyCard } from "..";
39
46
  import type { UixyAlertItemProps, UixyAlertItemEmits } from "./Alert.types";
40
47
 
@@ -44,10 +51,22 @@
44
51
 
45
52
  const width = useMotionValue("100%");
46
53
 
54
+ let controls: AnimationPlaybackControls | undefined;
55
+
47
56
  onMounted(() => {
48
- animate(width, "0%", {
57
+ controls = animate(width, "0%", {
49
58
  duration: 8,
50
59
  ease: "linear",
51
60
  });
52
61
  });
62
+
63
+ const handleMouseEnter = () => {
64
+ controls?.pause();
65
+ emit("pause-timer");
66
+ };
67
+
68
+ const handleMouseLeave = () => {
69
+ controls?.play();
70
+ emit("resume-timer");
71
+ };
53
72
  </script>
@@ -1,7 +1,7 @@
1
1
  import styles from "~/utils/styles";
2
2
 
3
3
  export const buttonStyles = styles(
4
- "relative cursor-pointer h-8 font-500 transition-all duration-200 ease-in-out hover:shadow-xs flex items-center justify-center gap-2 disabled:pointer-events-none border border-transparent",
4
+ "relative cursor-pointer font-500 transition-all duration-200 ease-in-out hover:shadow-xs flex items-center justify-center gap-2 disabled:pointer-events-none border border-transparent",
5
5
  {
6
6
  variant: {
7
7
  primary:
@@ -14,9 +14,9 @@ export const buttonStyles = styles(
14
14
  "dark:hover:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-800 disabled:text-gray-700",
15
15
  },
16
16
  size: {
17
- sm: "px-5 py-1 text-button-sm",
18
- md: "px-6 py-1 text-button-md",
19
- lg: "px-7 py-1 text-button-lg",
17
+ sm: "h-7 px-4 text-button-sm",
18
+ md: "h-8 px-6 text-button-md",
19
+ lg: "h-10 px-8 text-button-lg",
20
20
  },
21
21
  rounded: {
22
22
  true: "rounded-full",
@@ -2,14 +2,15 @@
2
2
  <label :class="checkboxStyles($attrs.class as string)">
3
3
  <div class="relative flex items-center justify-center">
4
4
  <input
5
+ ref="inputRef"
5
6
  type="checkbox"
6
7
  :disabled="props.disabled"
7
8
  v-model="checked"
8
- class="size-4 cursor-pointer appearance-none rounded-1 border border-black transition-all duration-150 ease-in-out checked:bg-black hover:bg-gray-300 checked:hover:bg-gray-800 disabled:pointer-events-none disabled:border-gray-500 disabled:bg-gray-300 checked:disabled:bg-gray-500 dark:border-gray-100 dark:checked:bg-gray-100 dark:hover:bg-gray-800 dark:checked:hover:bg-gray-200 dark:disabled:border-gray-600 dark:disabled:bg-gray-900 dark:checked:disabled:bg-gray-600"
9
+ :class="inputStyles({ indeterminate: !!props.indeterminate })"
9
10
  />
10
11
  <animate-presence>
11
12
  <motion.div
12
- v-if="checked"
13
+ v-if="checked || props.indeterminate"
13
14
  :initial="{ opacity: 0 }"
14
15
  :animate="{ opacity: 1 }"
15
16
  :exit="{ opacity: 0 }"
@@ -17,11 +18,11 @@
17
18
  class="pointer-events-none absolute"
18
19
  >
19
20
  <uixy-icon
20
- name="check"
21
+ :name="props.indeterminate ? 'minus' : 'check'"
21
22
  :class="
22
23
  iconStyles({
23
24
  disabled: !!props.disabled,
24
- checked: checked,
25
+ checked: checked || props.indeterminate,
25
26
  })
26
27
  "
27
28
  />
@@ -41,9 +42,22 @@
41
42
  import { motion, AnimatePresence } from "motion-v";
42
43
  import { UixyIcon } from "../Icon";
43
44
  import type { UixyCheckboxProps } from "./Checkbox.types";
44
- import { checkboxStyles, iconStyles, labelStyles } from "./Checkbox.styles";
45
+ import {
46
+ checkboxStyles,
47
+ inputStyles,
48
+ iconStyles,
49
+ labelStyles,
50
+ } from "./Checkbox.styles";
45
51
 
46
52
  const props = defineProps<UixyCheckboxProps>();
47
53
 
48
54
  const checked = defineModel<boolean>("checked");
55
+
56
+ const inputRef = ref<HTMLInputElement | null>(null);
57
+
58
+ watchEffect(() => {
59
+ if (inputRef.value) {
60
+ inputRef.value.indeterminate = !!props.indeterminate;
61
+ }
62
+ });
49
63
  </script>
@@ -2,6 +2,16 @@ import styles from "~/utils/styles";
2
2
 
3
3
  export const checkboxStyles = styles("flex items-center gap-2");
4
4
 
5
+ export const inputStyles = styles(
6
+ "size-4 cursor-pointer appearance-none rounded-1 border border-black transition-all duration-150 ease-in-out checked:bg-black hover:bg-gray-300 checked:hover:bg-gray-800 disabled:pointer-events-none disabled:border-gray-500 disabled:bg-gray-300 checked:disabled:bg-gray-500 dark:border-gray-100 dark:checked:bg-gray-100 dark:hover:bg-gray-800 dark:checked:hover:bg-gray-200 dark:disabled:border-gray-600 dark:disabled:bg-gray-900 dark:checked:disabled:bg-gray-600",
7
+ {
8
+ indeterminate: {
9
+ true: "bg-black hover:bg-gray-800 disabled:bg-gray-300 dark:bg-gray-100 dark:hover:bg-gray-200 dark:disabled:bg-gray-900",
10
+ false: "",
11
+ },
12
+ },
13
+ );
14
+
5
15
  export const iconStyles = styles("h-3 w-3", {
6
16
  disabled: {
7
17
  true: "text-gray-500",
@@ -1,3 +1,4 @@
1
1
  export interface UixyCheckboxProps {
2
2
  disabled?: boolean;
3
+ indeterminate?: boolean;
3
4
  }
@@ -33,9 +33,29 @@
33
33
  />
34
34
  </div>
35
35
 
36
- <div v-if="!props.hideHelper && helperText" class="h-4">
37
- <p :class="helperStyles({ status })">{{ helperText }}</p>
38
- </div>
36
+ <animate-presence mode="wait" :initial="false">
37
+ <motion.div
38
+ v-if="!!helperText"
39
+ class="overflow-hidden"
40
+ :initial="{ height: 0 }"
41
+ :animate="{ height: 'auto' }"
42
+ :exit="{ height: 0 }"
43
+ :transition="{ duration: 0.1, ease: 'easeInOut' }"
44
+ >
45
+ <animate-presence mode="wait" :initial="false">
46
+ <motion.p
47
+ :key="status + helperText"
48
+ :class="helperStyles({ status })"
49
+ :initial="{ opacity: 0, y: -4 }"
50
+ :animate="{ opacity: 1, y: 0 }"
51
+ :exit="{ opacity: 0, y: 4 }"
52
+ :transition="{ duration: 0.12, ease: 'easeInOut', delay: 0.1 }"
53
+ >
54
+ {{ helperText }}
55
+ </motion.p>
56
+ </animate-presence>
57
+ </motion.div>
58
+ </animate-presence>
39
59
 
40
60
  <teleport to="body">
41
61
  <animate-presence>
@@ -13,6 +13,5 @@ export interface UixyDatePickerProps {
13
13
  startOfWeek?: "Sunday" | "Monday";
14
14
  isDateDisabled?: (date: Date) => boolean;
15
15
  direction?: "top" | "bottom";
16
- hideHelper?: boolean;
17
16
  closeOnSelect?: boolean;
18
17
  }
@@ -19,7 +19,12 @@
19
19
  :class="triggerIconStyles({ disabled: props.disabled })"
20
20
  />
21
21
  <span
22
- :class="triggerValueStyles({ placeholder: !hasValue, disabled: props.disabled })"
22
+ :class="
23
+ triggerValueStyles({
24
+ placeholder: !hasValue,
25
+ disabled: props.disabled,
26
+ })
27
+ "
23
28
  >
24
29
  {{ displayValue }}
25
30
  </span>
@@ -31,9 +36,29 @@
31
36
  />
32
37
  </div>
33
38
 
34
- <div v-if="!props.hideHelper && helperText" class="h-4">
35
- <p :class="helperStyles({ status })">{{ helperText }}</p>
36
- </div>
39
+ <animate-presence mode="wait" :initial="false">
40
+ <motion.div
41
+ v-if="!!helperText"
42
+ class="overflow-hidden"
43
+ :initial="{ height: 0 }"
44
+ :animate="{ height: 'auto' }"
45
+ :exit="{ height: 0 }"
46
+ :transition="{ duration: 0.1, ease: 'easeInOut' }"
47
+ >
48
+ <animate-presence mode="wait" :initial="false">
49
+ <motion.p
50
+ :key="status + helperText"
51
+ :class="helperStyles({ status })"
52
+ :initial="{ opacity: 0, y: -4 }"
53
+ :animate="{ opacity: 1, y: 0 }"
54
+ :exit="{ opacity: 0, y: 4 }"
55
+ :transition="{ duration: 0.12, ease: 'easeInOut', delay: 0.1 }"
56
+ >
57
+ {{ helperText }}
58
+ </motion.p>
59
+ </animate-presence>
60
+ </motion.div>
61
+ </animate-presence>
37
62
 
38
63
  <teleport to="body">
39
64
  <animate-presence>
@@ -18,6 +18,5 @@ export interface UixyDateRangePickerProps {
18
18
  startOfWeek?: "Sunday" | "Monday";
19
19
  isDateDisabled?: (date: Date) => boolean;
20
20
  direction?: "top" | "bottom";
21
- hideHelper?: boolean;
22
21
  closeOnSelect?: boolean;
23
22
  }
@@ -78,19 +78,29 @@
78
78
  </div>
79
79
  </div>
80
80
 
81
- <div v-if="text" class="h-4">
82
- <animate-presence mode="wait" :initial="false">
83
- <motion.div
84
- :key="status"
85
- :initial="{ opacity: 0, y: '-4px' }"
86
- :animate="{ opacity: 1, y: 0 }"
87
- :exit="{ opacity: 0, y: '-4px' }"
88
- :transition="{ duration: 0.125 }"
89
- >
90
- <p :class="helperStyles({ status })">{{ text }}</p>
91
- </motion.div>
92
- </animate-presence>
93
- </div>
81
+ <animate-presence mode="wait" :initial="false">
82
+ <motion.div
83
+ v-if="!!text"
84
+ class="overflow-hidden"
85
+ :initial="{ height: 0 }"
86
+ :animate="{ height: 'auto' }"
87
+ :exit="{ height: 0 }"
88
+ :transition="{ duration: 0.1, ease: 'easeInOut' }"
89
+ >
90
+ <animate-presence mode="wait" :initial="false">
91
+ <motion.p
92
+ :key="status + text"
93
+ :class="helperStyles({ status })"
94
+ :initial="{ opacity: 0, y: -4 }"
95
+ :animate="{ opacity: 1, y: 0 }"
96
+ :exit="{ opacity: 0, y: 4 }"
97
+ :transition="{ duration: 0.12, ease: 'easeInOut', delay: 0.1 }"
98
+ >
99
+ {{ text }}
100
+ </motion.p>
101
+ </animate-presence>
102
+ </motion.div>
103
+ </animate-presence>
94
104
  </div>
95
105
  </template>
96
106
 
@@ -27,19 +27,29 @@
27
27
  {{ props.label }}
28
28
  </label>
29
29
  </div>
30
- <div v-if="!props.hideHelper" class="h-4">
31
- <animate-presence mode="wait" :initial="false">
32
- <motion.div
33
- :initial="{ opacity: 0, y: '-4px' }"
34
- :animate="{ opacity: 1, y: 0 }"
35
- :exit="{ opacity: 0, y: '-4px' }"
36
- :transition="{ duration: 0.125 }"
37
- :key="status"
38
- >
39
- <p :class="helperStyles({ status })">{{ text }}</p>
40
- </motion.div>
41
- </animate-presence>
42
- </div>
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
 
@@ -11,7 +11,6 @@ export interface UixyInputProps {
11
11
  helperText?: string;
12
12
  errorText?: string;
13
13
  autoFocus?: boolean;
14
- hideHelper?: boolean;
15
14
  }
16
15
 
17
16
  export interface UixyInputEmits {
@@ -17,7 +17,7 @@
17
17
  :exit="{ opacity: 0, scale: 0.8 }"
18
18
  :transition="{ duration: 0.15, ease: 'easeInOut' }"
19
19
  >
20
- <slot />
20
+ <slot :close="handleLeave" />
21
21
  </motion.div>
22
22
  </div>
23
23
  </motion.div>
@@ -63,7 +63,6 @@
63
63
  icon="search"
64
64
  iconPositon="right"
65
65
  placeholder="Search..."
66
- hide-helper
67
66
  />
68
67
  </div>
69
68
 
@@ -140,19 +139,29 @@
140
139
  {{ props.label }}
141
140
  </div>
142
141
  </div>
143
- <div v-if="!props.hideHelper" class="h-4">
144
- <animate-presence mode="wait" :initial="false">
145
- <motion.div
146
- :initial="{ opacity: 0, y: '-4px' }"
147
- :animate="{ opacity: 1, y: 0 }"
148
- :exit="{ opacity: 0, y: '-4px' }"
149
- :transition="{ duration: 0.125 }"
150
- :key="status"
151
- >
152
- <p :class="helperStyles({ status })">{{ text }}</p>
153
- </motion.div>
154
- </animate-presence>
155
- </div>
142
+ <animate-presence mode="wait" :initial="false">
143
+ <motion.div
144
+ v-if="!!text"
145
+ class="overflow-hidden"
146
+ :initial="{ height: 0 }"
147
+ :animate="{ height: 'auto' }"
148
+ :exit="{ height: 0 }"
149
+ :transition="{ duration: 0.1, ease: 'easeInOut' }"
150
+ >
151
+ <animate-presence mode="wait" :initial="false">
152
+ <motion.p
153
+ :key="status + text"
154
+ :class="helperStyles({ status })"
155
+ :initial="{ opacity: 0, y: -4 }"
156
+ :animate="{ opacity: 1, y: 0 }"
157
+ :exit="{ opacity: 0, y: 4 }"
158
+ :transition="{ duration: 0.12, ease: 'easeInOut', delay: 0.1 }"
159
+ >
160
+ {{ text }}
161
+ </motion.p>
162
+ </animate-presence>
163
+ </motion.div>
164
+ </animate-presence>
156
165
  </div>
157
166
  </template>
158
167
 
@@ -34,7 +34,6 @@ export type UixySelectProps = {
34
34
  leafOnly?: boolean;
35
35
  autoOpen?: boolean;
36
36
  delay?: number;
37
- hideHelper?: boolean;
38
37
  deselectable?: boolean;
39
38
  allowCustom?: boolean;
40
39
  clearSearchOnSelect?: boolean;