@witchcraft/ui 0.3.19 → 0.3.20
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/dist/module.json +1 -1
- package/dist/runtime/assets/animations.css +1 -1
- package/dist/runtime/components/LibDatePicker/LibRangeDatePicker.vue +8 -4
- package/dist/runtime/components/LibNotifications/LibNotification.vue +2 -2
- package/dist/runtime/components/LibNotifications/LibNotifications.d.vue.ts +1 -0
- package/dist/runtime/components/LibNotifications/LibNotifications.vue +34 -17
- package/dist/runtime/components/LibNotifications/LibNotifications.vue.d.ts +1 -0
- package/dist/runtime/components/LibNotifications/calculateNotificationProgress.d.ts +2 -0
- package/dist/runtime/components/LibNotifications/calculateNotificationProgress.js +4 -0
- package/dist/runtime/composables/useTimeConditionally.d.ts +16 -0
- package/dist/runtime/composables/useTimeConditionally.js +27 -0
- package/package.json +1 -1
- package/src/runtime/assets/animations.css +50 -3
- package/src/runtime/components/LibDatePicker/LibRangeDatePicker.vue +8 -4
- package/src/runtime/components/LibNotifications/LibNotification.vue +2 -2
- package/src/runtime/components/LibNotifications/LibNotifications.vue +35 -18
- package/src/runtime/components/LibNotifications/calculateNotificationProgress.ts +8 -0
- package/src/runtime/composables/useTimeConditionally.ts +51 -0
package/dist/module.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@theme{--animate-blinkInf:blink 1s linear-infinite;@keyframes blink{0%{opacity:0}25%{opacity:1}75%{opacity:1}to{opacity:0}}--animate-slideBgInf:slide-bg 10s linear-infinite;@keyframes slide{0%{background-position:0}to{background-position:100%}}--animate-hide:hide 500ms ease-in;@keyframes hide{0%{opacity:1}to{opacity:0}}--animate-
|
|
1
|
+
@utility animate-from-*{--animate-from:--value([length,percentage]);--animate-from:calc(var(--spacing) * --value(integer))}@utility -animate-from-*{--animate-from:--value([length,percentage]);--animate-from:calc(var(--spacing) * -1 * --value(integer))}@theme{--animate-blinkInf:blink 1s linear-infinite;@keyframes blink{0%{opacity:0}25%{opacity:1}75%{opacity:1}to{opacity:0}}--animate-slideBgInf:slide-bg 10s linear-infinite;@keyframes slide{0%{background-position:0}to{background-position:100%}}--animate-hide:hide 500ms ease-in;@keyframes hide{0%{opacity:1}to{opacity:0}}--animate-slideInLeft:slideInLeft 500ms cubic-bezier(0.16,1,0.3,1);@keyframes slideInLeft{0%{opacity:0;transform:translateX(var(--animate-from,100%))}to{opacity:1;transform:translateX(0)}}--animate-slideInRight:slideInLeft 500ms cubic-bezier(0.16,1,0.3,1);@keyframes slideInRight{0%{opacity:0;transform:translateX(var(--animate-from,-100%))}to{opacity:1;transform:translateX(0)}}--animate-slideInUp:slideInUp 500ms cubic-bezier(0.16,1,0.3,1);@keyframes slideInUp{0%{opacity:0;transform:translateY(var(--animate-from,100%))}to{opacity:1;transform:translateY(0)}}--animate-slideInDown:slideInDown 500ms cubic-bezier(0.16,1,0.3,1);@keyframes slideInDown{0%{opacity:0;transform:translateY(var(--animate-from,-100%))}to{opacity:1;transform:translateY(0)}}--animate-overlayShow:overlayShow 500ms cubic-bezier(0.16,1,0.3,1);@keyframes overlayShow{0%{opacity:0}to{opacity:1}}--animate-contentShow:contentShow 500ms cubic-bezier(0.16,1,0.3,1);@keyframes contentShow{0%{opacity:0;transform:translateY(-10%) scale(.96)}to{opacity:1;transform:scale(1)}}}
|
|
@@ -209,10 +209,14 @@ const locale = useInjectedLocale().timeLocale;
|
|
|
209
209
|
dark:border-neutral-700
|
|
210
210
|
shadow-lg
|
|
211
211
|
will-change-[transform,opacity]
|
|
212
|
-
data-[
|
|
213
|
-
data-[
|
|
214
|
-
data-[
|
|
215
|
-
data-[
|
|
212
|
+
data-[side=top]:animate-from-3
|
|
213
|
+
data-[side=left]:animate-from-3
|
|
214
|
+
data-[side=right]:-animate-from-3
|
|
215
|
+
data-[side=bottom]:-animate-from-3
|
|
216
|
+
data-[state=open]:data-[side=top]:animate-slideInUp
|
|
217
|
+
data-[state=open]:data-[side=right]:animate-slideInRight
|
|
218
|
+
data-[state=open]:data-[side=bottom]:animate-slideInDown
|
|
219
|
+
data-[state=open]:data-[side=left]:animate-slideInLeft
|
|
216
220
|
text-fg
|
|
217
221
|
dark:text-neutral-200
|
|
218
222
|
"
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
:class="twMerge(
|
|
5
5
|
`
|
|
6
6
|
notification
|
|
7
|
-
max-w-700px
|
|
8
7
|
bg-neutral-50
|
|
9
8
|
dark:bg-neutral-900
|
|
10
9
|
text-fg
|
|
@@ -65,7 +64,7 @@
|
|
|
65
64
|
</div>
|
|
66
65
|
</slot>
|
|
67
66
|
<div class="notification--spacer flex-1"/>
|
|
68
|
-
<div class="actions flex">
|
|
67
|
+
<div class="notification--actions flex">
|
|
69
68
|
<LibButton
|
|
70
69
|
:border="false"
|
|
71
70
|
class="
|
|
@@ -203,6 +202,7 @@ onMounted(() => {
|
|
|
203
202
|
if (props.notification.isPaused) return;
|
|
204
203
|
emit("pause", props.notification);
|
|
205
204
|
} else {
|
|
205
|
+
if (!props.notification.isPaused) return;
|
|
206
206
|
emit("resume", props.notification);
|
|
207
207
|
}
|
|
208
208
|
}, { signal: mousedownAbortController.signal });
|
|
@@ -4,6 +4,7 @@ import type { HTMLAttributes } from "vue";
|
|
|
4
4
|
type RealProps = LinkableByIdProps & {
|
|
5
5
|
/** If not provided, uses the global handler (this requires useNotificationHandler be called and configured). */
|
|
6
6
|
handler?: NotificationHandler;
|
|
7
|
+
progressUpdateInterval?: number;
|
|
7
8
|
};
|
|
8
9
|
interface Props extends
|
|
9
10
|
/** @vue-ignore */
|
|
@@ -5,17 +5,16 @@
|
|
|
5
5
|
tag="div"
|
|
6
6
|
:class="twMerge(`
|
|
7
7
|
notifications
|
|
8
|
-
[--notification-width:
|
|
8
|
+
[--notification-width:calc(100dvw-var(--spacing)*4)]
|
|
9
|
+
sm:[--notification-width:300px]
|
|
9
10
|
fixed
|
|
10
11
|
top-0
|
|
11
12
|
z-50
|
|
12
13
|
right-[calc(var(--notification-width)*-1)]
|
|
13
14
|
w-[calc(var(--spacing)*2+var(--notification-width)*2)]
|
|
14
|
-
[&_.notification]:w-[var(--notification-width)]
|
|
15
15
|
max-h-[100dvh]
|
|
16
16
|
flex
|
|
17
17
|
flex-col
|
|
18
|
-
[&_.notification]:shrink-0
|
|
19
18
|
gap-1
|
|
20
19
|
list-none
|
|
21
20
|
outline-none
|
|
@@ -29,7 +28,16 @@
|
|
|
29
28
|
:handler="handler"
|
|
30
29
|
tabindex="0"
|
|
31
30
|
:notification="notification"
|
|
32
|
-
class="
|
|
31
|
+
class="
|
|
32
|
+
overflow-hidden
|
|
33
|
+
my-2
|
|
34
|
+
max-h-[300px]
|
|
35
|
+
w-[var(--notification-width)]
|
|
36
|
+
shrink-0
|
|
37
|
+
max-sm:[&_.notification--button]:p-2
|
|
38
|
+
max-sm:[&_.notification--button]:py-1
|
|
39
|
+
max-sm:[&_.notification--header]:text-lg
|
|
40
|
+
"
|
|
33
41
|
v-for="notification of notifications"
|
|
34
42
|
:key="notification.id"
|
|
35
43
|
@pause="handler.pause(notification)"
|
|
@@ -46,7 +54,7 @@
|
|
|
46
54
|
-mx-[calc(var(--spacing)*2+2px)]
|
|
47
55
|
rounded-none
|
|
48
56
|
"
|
|
49
|
-
:progress="
|
|
57
|
+
:progress="calculateNotificationProgress(notification, time)"
|
|
50
58
|
/>
|
|
51
59
|
</template>
|
|
52
60
|
</lib-notification>
|
|
@@ -67,19 +75,27 @@
|
|
|
67
75
|
<AlertDialogContent
|
|
68
76
|
class="
|
|
69
77
|
data-[state=open]:animate-contentShow
|
|
78
|
+
max-sm:data-[state=open]:animate-slideInUp
|
|
70
79
|
fixed
|
|
71
80
|
flex
|
|
81
|
+
max-h-[80dvh]
|
|
72
82
|
top-[50%]
|
|
73
83
|
left-[50%]
|
|
74
|
-
translate-x-[-50%]
|
|
75
|
-
translate-y-[-50%]
|
|
76
|
-
max-h-[80dvh]
|
|
84
|
+
sm:translate-x-[-50%]
|
|
85
|
+
sm:translate-y-[-50%]
|
|
77
86
|
max-w-[700px]
|
|
87
|
+
max-sm:bottom-2
|
|
88
|
+
max-sm:top-[unset]
|
|
89
|
+
max-sm:left-2
|
|
90
|
+
max-sm:right-2
|
|
91
|
+
max-sm:w-[calc(100%-var(--spacing)*4)]
|
|
78
92
|
z-100
|
|
79
93
|
"
|
|
80
94
|
>
|
|
81
95
|
<lib-notification
|
|
82
96
|
class="
|
|
97
|
+
w-full
|
|
98
|
+
sm:max-w-[700px]
|
|
83
99
|
max-w-full
|
|
84
100
|
max-h-full
|
|
85
101
|
top-notification
|
|
@@ -120,9 +136,11 @@ import {
|
|
|
120
136
|
AlertDialogRoot,
|
|
121
137
|
AlertDialogTitle
|
|
122
138
|
} from "reka-ui";
|
|
123
|
-
import { computed
|
|
139
|
+
import { computed } from "vue";
|
|
140
|
+
import { calculateNotificationProgress } from "./calculateNotificationProgress.js";
|
|
124
141
|
import LibNotification from "./LibNotification.vue";
|
|
125
142
|
import { useNotificationHandler } from "../../composables/useNotificationHandler.js";
|
|
143
|
+
import { useTimeConditionally } from "../../composables/useTimeConditionally.js";
|
|
126
144
|
import { NotificationHandler } from "../../helpers/NotificationHandler.js";
|
|
127
145
|
import { twMerge } from "../../utils/twMerge.js";
|
|
128
146
|
import LibProgressBar from "../LibProgressBar/LibProgressBar.vue";
|
|
@@ -132,17 +150,16 @@ defineOptions({
|
|
|
132
150
|
});
|
|
133
151
|
const props = defineProps({
|
|
134
152
|
id: { type: String, required: false },
|
|
135
|
-
handler: { type: Object, required: false }
|
|
153
|
+
handler: { type: Object, required: false },
|
|
154
|
+
progressUpdateInterval: { type: Number, required: false }
|
|
136
155
|
});
|
|
156
|
+
const handler = props.handler ?? useNotificationHandler();
|
|
137
157
|
const topNotifications = computed(() => handler.queue.filter((entry) => entry.requiresAction).reverse());
|
|
138
158
|
const notifications = computed(() => handler.queue.filter((entry) => !entry.requiresAction));
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
});
|
|
144
|
-
}, 50);
|
|
145
|
-
const handler = props.handler ?? useNotificationHandler();
|
|
159
|
+
const fetchTime = computed(() => {
|
|
160
|
+
return notifications.value.filter((entry) => entry.timeout !== void 0 && !entry.isPaused).length > 0;
|
|
161
|
+
});
|
|
162
|
+
const { time } = useTimeConditionally(fetchTime, { refreshInterval: props.progressUpdateInterval });
|
|
146
163
|
</script>
|
|
147
164
|
|
|
148
165
|
<script>
|
|
@@ -4,6 +4,7 @@ import type { HTMLAttributes } from "vue";
|
|
|
4
4
|
type RealProps = LinkableByIdProps & {
|
|
5
5
|
/** If not provided, uses the global handler (this requires useNotificationHandler be called and configured). */
|
|
6
6
|
handler?: NotificationHandler;
|
|
7
|
+
progressUpdateInterval?: number;
|
|
7
8
|
};
|
|
8
9
|
interface Props extends
|
|
9
10
|
/** @vue-ignore */
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export function calculateNotificationProgress(notification, time) {
|
|
2
|
+
if (notification.timeout === void 0) return 0;
|
|
3
|
+
return 100 - (notification.isPaused ? notification._timer.elapsedBeforePause : notification._timer.elapsedBeforePause + (time - notification.startTime)) / notification.timeout * 100;
|
|
4
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Ref } from "vue";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a ref with the current time if the given value ref is true or it's an array with 1 or more items.. It will update the time every 50ms (configurable) if so (within a requestAnimationFrame).
|
|
4
|
+
*
|
|
5
|
+
* When the value is anything else it will clear the interval and set the time to undefined.
|
|
6
|
+
*
|
|
7
|
+
* Useful for use with a progress bar.
|
|
8
|
+
*
|
|
9
|
+
* Updating the time all the time is expensive and not idea. This way we only set the interval if we really need it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useTimeConditionally(val: Ref<any[] | boolean | any>, { refreshInterval }?: {
|
|
12
|
+
refreshInterval?: number;
|
|
13
|
+
}): {
|
|
14
|
+
time: Ref<undefined | number>;
|
|
15
|
+
refresh: () => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ref, watch } from "vue";
|
|
2
|
+
export function useTimeConditionally(val, {
|
|
3
|
+
refreshInterval = 50
|
|
4
|
+
} = {}) {
|
|
5
|
+
const time = ref(void 0);
|
|
6
|
+
let interval;
|
|
7
|
+
function refresh(v = val.value) {
|
|
8
|
+
if (v === true || Array.isArray(v) && v.length > 0) {
|
|
9
|
+
if (interval !== void 0) return;
|
|
10
|
+
time.value = Date.now();
|
|
11
|
+
interval = setInterval(() => {
|
|
12
|
+
requestAnimationFrame(() => {
|
|
13
|
+
time.value = Date.now();
|
|
14
|
+
});
|
|
15
|
+
}, refreshInterval);
|
|
16
|
+
} else {
|
|
17
|
+
if (interval === void 0) return;
|
|
18
|
+
clearInterval(interval);
|
|
19
|
+
interval = void 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
watch(val, (val2) => {
|
|
23
|
+
refresh(val2);
|
|
24
|
+
});
|
|
25
|
+
refresh();
|
|
26
|
+
return { time, refresh };
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
@utility animate-from-* {
|
|
2
|
+
--animate-from: --value([length, percentage]);
|
|
3
|
+
--animate-from: calc(var(--spacing) * --value(integer));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
@utility -animate-from-* {
|
|
7
|
+
--animate-from: --value([length, percentage]);
|
|
8
|
+
--animate-from: calc(var(--spacing) * -1 * --value(integer));
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
@theme {
|
|
2
12
|
--animate-blinkInf: blink 1s linear-infinite;
|
|
3
13
|
@keyframes blink {
|
|
@@ -38,10 +48,10 @@
|
|
|
38
48
|
}
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
--animate-
|
|
42
|
-
@keyframes
|
|
51
|
+
--animate-slideInLeft: slideInLeft 500ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
52
|
+
@keyframes slideInLeft {
|
|
43
53
|
from {
|
|
44
|
-
transform: translateX(100%);
|
|
54
|
+
transform: translateX(var(--animate-from, 100%));
|
|
45
55
|
opacity: 0;
|
|
46
56
|
}
|
|
47
57
|
to {
|
|
@@ -50,6 +60,43 @@
|
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
|
|
63
|
+
--animate-slideInRight: slideInLeft 500ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
64
|
+
@keyframes slideInRight {
|
|
65
|
+
from {
|
|
66
|
+
transform: translateX(var(--animate-from, -100%));
|
|
67
|
+
opacity: 0;
|
|
68
|
+
}
|
|
69
|
+
to {
|
|
70
|
+
transform: translateX(0);
|
|
71
|
+
opacity: 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
--animate-slideInUp: slideInUp 500ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
76
|
+
@keyframes slideInUp {
|
|
77
|
+
from {
|
|
78
|
+
transform: translateY(var(--animate-from, 100%));
|
|
79
|
+
opacity: 0;
|
|
80
|
+
}
|
|
81
|
+
to {
|
|
82
|
+
transform: translateY(0);
|
|
83
|
+
opacity: 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
--animate-slideInDown: slideInDown 500ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
88
|
+
@keyframes slideInDown {
|
|
89
|
+
from {
|
|
90
|
+
transform: translateY(var(--animate-from, -100%));
|
|
91
|
+
opacity: 0;
|
|
92
|
+
}
|
|
93
|
+
to {
|
|
94
|
+
transform: translateY(0);
|
|
95
|
+
opacity: 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
53
100
|
--animate-overlayShow: overlayShow 500ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
54
101
|
@keyframes overlayShow {
|
|
55
102
|
from {
|
|
@@ -237,10 +237,14 @@ const locale = useInjectedLocale().timeLocale
|
|
|
237
237
|
dark:border-neutral-700
|
|
238
238
|
shadow-lg
|
|
239
239
|
will-change-[transform,opacity]
|
|
240
|
-
data-[
|
|
241
|
-
data-[
|
|
242
|
-
data-[
|
|
243
|
-
data-[
|
|
240
|
+
data-[side=top]:animate-from-3
|
|
241
|
+
data-[side=left]:animate-from-3
|
|
242
|
+
data-[side=right]:-animate-from-3
|
|
243
|
+
data-[side=bottom]:-animate-from-3
|
|
244
|
+
data-[state=open]:data-[side=top]:animate-slideInUp
|
|
245
|
+
data-[state=open]:data-[side=right]:animate-slideInRight
|
|
246
|
+
data-[state=open]:data-[side=bottom]:animate-slideInDown
|
|
247
|
+
data-[state=open]:data-[side=left]:animate-slideInLeft
|
|
244
248
|
text-fg
|
|
245
249
|
dark:text-neutral-200
|
|
246
250
|
"
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
v-if="notification"
|
|
4
4
|
:class="twMerge(`
|
|
5
5
|
notification
|
|
6
|
-
max-w-700px
|
|
7
6
|
bg-neutral-50
|
|
8
7
|
dark:bg-neutral-900
|
|
9
8
|
text-fg
|
|
@@ -63,7 +62,7 @@
|
|
|
63
62
|
</div>
|
|
64
63
|
</slot>
|
|
65
64
|
<div class="notification--spacer flex-1"/>
|
|
66
|
-
<div class="actions flex">
|
|
65
|
+
<div class="notification--actions flex">
|
|
67
66
|
<LibButton
|
|
68
67
|
:border="false"
|
|
69
68
|
class="
|
|
@@ -221,6 +220,7 @@ onMounted(() => {
|
|
|
221
220
|
if (props.notification.isPaused) return
|
|
222
221
|
emit("pause", props.notification)
|
|
223
222
|
} else {
|
|
223
|
+
if (!props.notification.isPaused) return
|
|
224
224
|
emit("resume", props.notification)
|
|
225
225
|
}
|
|
226
226
|
}, { signal: mousedownAbortController.signal })
|
|
@@ -5,17 +5,16 @@
|
|
|
5
5
|
tag="div"
|
|
6
6
|
:class="twMerge(`
|
|
7
7
|
notifications
|
|
8
|
-
[--notification-width:
|
|
8
|
+
[--notification-width:calc(100dvw-var(--spacing)*4)]
|
|
9
|
+
sm:[--notification-width:300px]
|
|
9
10
|
fixed
|
|
10
11
|
top-0
|
|
11
12
|
z-50
|
|
12
13
|
right-[calc(var(--notification-width)*-1)]
|
|
13
14
|
w-[calc(var(--spacing)*2+var(--notification-width)*2)]
|
|
14
|
-
[&_.notification]:w-[var(--notification-width)]
|
|
15
15
|
max-h-[100dvh]
|
|
16
16
|
flex
|
|
17
17
|
flex-col
|
|
18
|
-
[&_.notification]:shrink-0
|
|
19
18
|
gap-1
|
|
20
19
|
list-none
|
|
21
20
|
outline-none
|
|
@@ -29,7 +28,16 @@
|
|
|
29
28
|
:handler="handler"
|
|
30
29
|
tabindex="0"
|
|
31
30
|
:notification="notification"
|
|
32
|
-
class="
|
|
31
|
+
class="
|
|
32
|
+
overflow-hidden
|
|
33
|
+
my-2
|
|
34
|
+
max-h-[300px]
|
|
35
|
+
w-[var(--notification-width)]
|
|
36
|
+
shrink-0
|
|
37
|
+
max-sm:[&_.notification--button]:p-2
|
|
38
|
+
max-sm:[&_.notification--button]:py-1
|
|
39
|
+
max-sm:[&_.notification--header]:text-lg
|
|
40
|
+
"
|
|
33
41
|
v-for="notification of notifications"
|
|
34
42
|
:key="notification.id"
|
|
35
43
|
@pause="handler.pause(notification)"
|
|
@@ -46,7 +54,7 @@
|
|
|
46
54
|
-mx-[calc(var(--spacing)*2+2px)]
|
|
47
55
|
rounded-none
|
|
48
56
|
"
|
|
49
|
-
:progress="
|
|
57
|
+
:progress="calculateNotificationProgress(notification, time!)"
|
|
50
58
|
/>
|
|
51
59
|
</template>
|
|
52
60
|
</lib-notification>
|
|
@@ -67,19 +75,27 @@
|
|
|
67
75
|
<AlertDialogContent
|
|
68
76
|
class="
|
|
69
77
|
data-[state=open]:animate-contentShow
|
|
78
|
+
max-sm:data-[state=open]:animate-slideInUp
|
|
70
79
|
fixed
|
|
71
80
|
flex
|
|
81
|
+
max-h-[80dvh]
|
|
72
82
|
top-[50%]
|
|
73
83
|
left-[50%]
|
|
74
|
-
translate-x-[-50%]
|
|
75
|
-
translate-y-[-50%]
|
|
76
|
-
max-h-[80dvh]
|
|
84
|
+
sm:translate-x-[-50%]
|
|
85
|
+
sm:translate-y-[-50%]
|
|
77
86
|
max-w-[700px]
|
|
87
|
+
max-sm:bottom-2
|
|
88
|
+
max-sm:top-[unset]
|
|
89
|
+
max-sm:left-2
|
|
90
|
+
max-sm:right-2
|
|
91
|
+
max-sm:w-[calc(100%-var(--spacing)*4)]
|
|
78
92
|
z-100
|
|
79
93
|
"
|
|
80
94
|
>
|
|
81
95
|
<lib-notification
|
|
82
96
|
class="
|
|
97
|
+
w-full
|
|
98
|
+
sm:max-w-[700px]
|
|
83
99
|
max-w-full
|
|
84
100
|
max-h-full
|
|
85
101
|
top-notification
|
|
@@ -120,11 +136,13 @@ import {
|
|
|
120
136
|
AlertDialogRoot,
|
|
121
137
|
AlertDialogTitle
|
|
122
138
|
} from "reka-ui"
|
|
123
|
-
import { computed
|
|
139
|
+
import { computed } from "vue"
|
|
124
140
|
|
|
141
|
+
import { calculateNotificationProgress } from "./calculateNotificationProgress.js"
|
|
125
142
|
import LibNotification from "./LibNotification.vue"
|
|
126
143
|
|
|
127
144
|
import { useNotificationHandler } from "../../composables/useNotificationHandler.js"
|
|
145
|
+
import { useTimeConditionally } from "../../composables/useTimeConditionally.js"
|
|
128
146
|
import { NotificationHandler } from "../../helpers/NotificationHandler.js"
|
|
129
147
|
import { twMerge } from "../../utils/twMerge.js"
|
|
130
148
|
import LibProgressBar from "../LibProgressBar/LibProgressBar.vue"
|
|
@@ -137,18 +155,16 @@ defineOptions({
|
|
|
137
155
|
|
|
138
156
|
const props = defineProps<Props>()
|
|
139
157
|
|
|
158
|
+
const handler = props.handler ?? useNotificationHandler()
|
|
159
|
+
|
|
140
160
|
const topNotifications = computed(() => handler.queue.filter(entry => entry.requiresAction).reverse())
|
|
141
161
|
const notifications = computed(() => handler.queue.filter(entry => !entry.requiresAction))
|
|
142
162
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
time.value = Date.now()
|
|
147
|
-
})
|
|
148
|
-
}, 50)
|
|
149
|
-
|
|
163
|
+
const fetchTime = computed(() => {
|
|
164
|
+
return notifications.value.filter(entry => entry.timeout !== undefined && !entry.isPaused).length > 0
|
|
165
|
+
})
|
|
150
166
|
|
|
151
|
-
const
|
|
167
|
+
const { time } = useTimeConditionally(fetchTime, { refreshInterval: props.progressUpdateInterval })
|
|
152
168
|
</script>
|
|
153
169
|
|
|
154
170
|
<script lang="ts">
|
|
@@ -157,8 +173,9 @@ import type { HTMLAttributes } from "vue"
|
|
|
157
173
|
type RealProps
|
|
158
174
|
= & LinkableByIdProps
|
|
159
175
|
& {
|
|
160
|
-
|
|
176
|
+
/** If not provided, uses the global handler (this requires useNotificationHandler be called and configured). */
|
|
161
177
|
handler?: NotificationHandler
|
|
178
|
+
progressUpdateInterval?: number
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
interface Props
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NotificationEntry } from "../../helpers/NotificationHandler.js"
|
|
2
|
+
|
|
3
|
+
export function calculateNotificationProgress(notification: NotificationEntry, time: number) {
|
|
4
|
+
if (notification.timeout === undefined) return 0
|
|
5
|
+
|
|
6
|
+
return 100 - (((notification.isPaused ? (notification._timer.elapsedBeforePause) : (notification._timer.elapsedBeforePause + (time - notification.startTime))) / notification.timeout) * 100)
|
|
7
|
+
}
|
|
8
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type Ref, ref, watch } from "vue"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a ref with the current time if the given value ref is true or it's an array with 1 or more items.. It will update the time every 50ms (configurable) if so (within a requestAnimationFrame).
|
|
5
|
+
*
|
|
6
|
+
* When the value is anything else it will clear the interval and set the time to undefined.
|
|
7
|
+
*
|
|
8
|
+
* Useful for use with a progress bar.
|
|
9
|
+
*
|
|
10
|
+
* Updating the time all the time is expensive and not idea. This way we only set the interval if we really need it.
|
|
11
|
+
*/
|
|
12
|
+
export function useTimeConditionally(
|
|
13
|
+
val: Ref<any[] | boolean | any>,
|
|
14
|
+
{
|
|
15
|
+
refreshInterval = 50
|
|
16
|
+
}: {
|
|
17
|
+
refreshInterval?: number
|
|
18
|
+
} = {}
|
|
19
|
+
): {
|
|
20
|
+
time: Ref<undefined | number>
|
|
21
|
+
refresh: () => void
|
|
22
|
+
} {
|
|
23
|
+
const time = ref<undefined | number>(undefined)
|
|
24
|
+
|
|
25
|
+
let interval: ReturnType<typeof setInterval> | undefined
|
|
26
|
+
function refresh(
|
|
27
|
+
/** The value to use for the check. Do not pass unless you know what you're doing. */
|
|
28
|
+
v = val.value
|
|
29
|
+
) {
|
|
30
|
+
if (v === true || (Array.isArray(v) && v.length > 0)) {
|
|
31
|
+
if (interval !== undefined) return
|
|
32
|
+
time.value = Date.now()
|
|
33
|
+
interval = setInterval(() => {
|
|
34
|
+
requestAnimationFrame(() => {
|
|
35
|
+
time.value = Date.now()
|
|
36
|
+
})
|
|
37
|
+
}, refreshInterval)
|
|
38
|
+
} else {
|
|
39
|
+
if (interval === undefined) return
|
|
40
|
+
clearInterval(interval)
|
|
41
|
+
interval = undefined
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
watch(val, val => {
|
|
45
|
+
refresh(val)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
refresh()
|
|
49
|
+
|
|
50
|
+
return { time, refresh }
|
|
51
|
+
}
|