v-uixy 1.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "v-uixy",
3
- "version": "1.6.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>
@@ -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 {
@@ -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;
@@ -21,23 +21,29 @@
21
21
  {{ props.label }}
22
22
  </label>
23
23
  </div>
24
- <div v-if="!props.hideHelper" class="h-4">
25
- <animate-presence mode="wait" :initial="false">
26
- <motion.div
27
- :initial="{ opacity: 0, y: '-4px' }"
28
- :animate="{ opacity: 1, y: 0 }"
29
- :exit="{ opacity: 0, y: '-4px' }"
30
- :transition="{ duration: 0.125 }"
31
- :key="status"
32
- >
33
- <p
24
+ <animate-presence mode="wait" :initial="false">
25
+ <motion.div
26
+ v-if="!!text"
27
+ class="overflow-hidden"
28
+ :initial="{ height: 0 }"
29
+ :animate="{ height: 'auto' }"
30
+ :exit="{ height: 0 }"
31
+ :transition="{ duration: 0.1, ease: 'easeInOut' }"
32
+ >
33
+ <animate-presence mode="wait" :initial="false">
34
+ <motion.p
35
+ :key="status + text"
34
36
  :class="helperStyles({ status, align: props.alignText ?? 'left' })"
37
+ :initial="{ opacity: 0, y: -4 }"
38
+ :animate="{ opacity: 1, y: 0 }"
39
+ :exit="{ opacity: 0, y: 4 }"
40
+ :transition="{ duration: 0.12, ease: 'easeInOut', delay: 0.1 }"
35
41
  >
36
42
  {{ text }}
37
- </p>
38
- </motion.div>
39
- </animate-presence>
40
- </div>
43
+ </motion.p>
44
+ </animate-presence>
45
+ </motion.div>
46
+ </animate-presence>
41
47
  </div>
42
48
  </template>
43
49
 
@@ -10,5 +10,4 @@ export interface UixyTextareaProps {
10
10
  noResize?: boolean;
11
11
  rows?: number;
12
12
  maxLength?: number;
13
- hideHelper?: boolean;
14
13
  }