Skip to content

Commit 0674f74

Browse files
committed
feat: toast swipes
1 parent 0e6e5cb commit 0674f74

8 files changed

Lines changed: 427 additions & 163 deletions

File tree

Lines changed: 128 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,13 @@
11
<script setup lang="ts">
22
// This is so bad lol pls fix
3-
import { type Message, type ToastControllerProps, type ToastLayout, type ToastPostion, type ToastToDismiss } from '@/types/pinesTypes';
3+
import type { Message, ToastControllerProps, ToastLayout, ToastPostion, ToastToDismiss } from '@/types/pinesTypes';
44
5+
import { DEFAULT_GAP, MOBILE_VIEWPORT_OFFSET, SCALE_STEP, TOAST_WIDTH, VIEWPORT_OFFSET, VISIBLE_TOASTS_AMOUNT, Y_OFFSET_STEP, Z_STEP } from '@/service/toaster/constants';
56
import { nextTick, onMounted, ref, watch, watchEffect } from 'vue';
67
import { ToastState } from '@/service/toaster/toastService';
78
89
import ToastNotification from '@/components/pinesUI/ToastNotification.vue';
910
10-
// Visible toasts amount
11-
const VISIBLE_TOASTS_AMOUNT = 3;
12-
13-
// Viewport padding
14-
const VIEWPORT_OFFSET = '24px';
15-
const MOBILE_VIEWPORT_OFFSET = '16px';
16-
17-
// Default toast width
18-
const TOAST_WIDTH = 0;
19-
20-
// Default gap between toasts
21-
const DEFAULT_GAP = 16;
22-
2311
const props = withDefaults(defineProps<ToastControllerProps>(), {
2412
layout: 'default',
2513
position: 'bottom-right',
@@ -33,8 +21,8 @@ const props = withDefaults(defineProps<ToastControllerProps>(), {
3321
const container = ref<HTMLElement>();
3422
3523
const messages = ref<Message[]>([]);
36-
const layout = ref<ToastLayout>(props.layout);
3724
const position = ref<ToastPostion>(props.position);
25+
const layout = ref<ToastLayout>(props.layout);
3826
3927
const toastsHovered = ref(false);
4028
const expanded = ref(props.layout === 'expanded');
@@ -45,35 +33,53 @@ const burnTimeout = ref<null | number>(null);
4533
function deleteToastWithId(id: string) {
4634
messages.value = messages.value.filter((msg) => msg.id !== id);
4735
ToastState.dismiss(id);
48-
stackToasts();
36+
stackToasts('DELETE');
4937
}
5038
51-
function stackToasts() {
52-
positionToasts();
39+
function stackToasts(_?: any) {
40+
positionToasts(_);
5341
calculateHeightOfToastsContainer();
5442
5543
if (heightRecalculateTimeout.value) clearTimeout(heightRecalculateTimeout.value);
5644
45+
// This calculates container height after the toasts are positioned
46+
if (!expanded.value) return;
47+
5748
heightRecalculateTimeout.value = window.setTimeout(() => {
49+
// positionToasts(_);
5850
calculateHeightOfToastsContainer();
59-
}, 300);
51+
}, 100);
6052
}
6153
62-
function positionToasts() {
63-
if (messages.value.length == 0) return;
54+
function positionToasts(_?: any) {
55+
if (messages.value.length == 0) {
56+
return;
57+
}
6458
6559
try {
66-
let scaleBuffer = 1;
67-
let yBuffer = 0;
68-
let zBuffer = 200;
6960
const bottomFlag = position.value.includes('bottom');
70-
let totalHeight = 0;
7161
const toastElements = [];
62+
63+
let totalHeight = 0;
64+
65+
let scaleBuffer = 1 + SCALE_STEP;
66+
let yBuffer = -Y_OFFSET_STEP;
67+
let zBuffer = 200 + Z_STEP;
68+
7269
for (let i = 0; i < messages.value.length; i++) {
70+
const msg = messages.value[i];
7371
const toast = document.getElementById(`${messages.value[i].id}`);
7472
7573
if (!toast) return;
7674
75+
zBuffer -= Z_STEP;
76+
scaleBuffer -= SCALE_STEP;
77+
yBuffer += Y_OFFSET_STEP;
78+
79+
msg.scale = scaleBuffer;
80+
msg.offsetY = yBuffer;
81+
msg.zIndex = zBuffer;
82+
7783
toast.style.zIndex = `${zBuffer}`;
7884
toastElements.push(toast);
7985
@@ -87,22 +93,27 @@ function positionToasts() {
8793
8894
totalHeight += toast.offsetHeight;
8995
90-
toast.style.scale = `1`;
91-
toast.style.transform = `translateY(0px)`;
92-
} else if (i > 0) {
93-
toast.style.scale = `${scaleBuffer}`;
94-
95-
if (bottomFlag && i < props.maxVisibleToasts) {
96-
toast.style.transform = `translateY(-${yBuffer}px)`;
97-
} else {
98-
alignBottom(toastElements[0], toast);
99-
toast.style.transform = `translateY(${yBuffer}px)`;
100-
}
96+
// toast.style.scale = `1`;
97+
msg.scale = 1;
98+
99+
msg.offsetY = 0;
100+
101+
// toast.style.setProperty('--offset-y', '0px');
102+
continue;
101103
}
102104
103-
zBuffer -= 10;
104-
scaleBuffer -= 0.06;
105-
yBuffer += 16;
105+
msg.scale = scaleBuffer;
106+
// toast.style.scale = `${scaleBuffer}`;
107+
108+
if (bottomFlag) {
109+
msg.offsetY *= -1;
110+
// toast.style.setProperty('--offset-y', `-${yBuffer}px`);
111+
} else {
112+
alignBottom(toastElements[0], toast);
113+
// toast.style.setProperty('--offset-y', `${yBuffer}px`);
114+
}
115+
116+
Object.assign(messages.value[i], msg);
106117
}
107118
108119
handleToastsOverflow(toastElements);
@@ -123,12 +134,12 @@ function handleToastsOverflow(toastElements: HTMLElement[]) {
123134
return;
124135
}
125136
126-
burnToast.firstElementChild?.classList.remove('opacity-100');
137+
// burnToast.firstElementChild?.classList.remove('opacity-100');
127138
burnToast.firstElementChild?.classList.add('opacity-0');
128139
129-
if (burnTimeout.value) {
130-
clearTimeout(burnTimeout.value);
131-
}
140+
// if (burnTimeout.value) {
141+
// clearTimeout(burnTimeout.value);
142+
// }
132143
133144
// Burn 🔥 (remove) last toast
134145
burnTimeout.value = window.setTimeout(function () {
@@ -159,15 +170,17 @@ function calculateHeightOfToastsContainer() {
159170
160171
if (!firstToastRectangle || !lastToastRectangle) return;
161172
162-
if (toastsHovered.value) {
163-
if (position.value.includes('bottom')) {
164-
container.value.style.height = firstToastRectangle.top + firstToastRectangle.height - lastToastRectangle.top + 'px';
165-
} else {
166-
container.value.style.height = lastToastRectangle.top + lastToastRectangle.height - firstToastRectangle.top + 'px';
167-
}
168-
} else {
173+
if (!toastsHovered.value) {
169174
container.value.style.height = firstToastRectangle.height + 'px';
175+
return;
170176
}
177+
178+
if (position.value.includes('bottom')) {
179+
container.value.style.height = firstToastRectangle.top + firstToastRectangle.height - lastToastRectangle.top + 'px';
180+
return;
181+
}
182+
183+
container.value.style.height = lastToastRectangle.top + lastToastRectangle.height - firstToastRectangle.top + 'px';
171184
}
172185
173186
function alignBottom(element1: HTMLElement, element2: HTMLElement) {
@@ -204,36 +217,44 @@ function resetTop() {
204217
}
205218
206219
onMounted(() => {
207-
stackToasts();
220+
stackToasts('MOUNT');
208221
});
209222
210223
watch(
211224
() => toastsHovered.value,
212225
(value) => {
213-
if (layout.value == 'default') {
214-
if (position.value.includes('bottom')) {
215-
resetBottom();
216-
} else {
217-
resetTop();
218-
}
226+
if (layout.value !== 'default') return;
219227
220-
if (value) {
221-
// calculate the new positions
222-
expanded.value = true;
223-
if (layout.value == 'default') {
224-
stackToasts();
225-
}
226-
} else {
227-
if (layout.value == 'default') {
228-
expanded.value = false;
229-
//setTimeout(function(){
230-
stackToasts();
231-
//}, 10);
232-
window.setTimeout(function () {
233-
stackToasts();
234-
}, 10);
235-
}
236-
}
228+
if (position.value.includes('bottom')) {
229+
resetBottom();
230+
} else {
231+
resetTop();
232+
}
233+
234+
if (value) {
235+
// calculate the new positions
236+
expanded.value = true;
237+
stackToasts('HOVER');
238+
return;
239+
}
240+
241+
expanded.value = false;
242+
// setTimeout(function () {
243+
// This can get called a bunch of times but since the timer is so short, it will probably be called before the event happens again
244+
stackToasts('HOVER CLOSE');
245+
// }, 10);
246+
// window.setTimeout(function () {
247+
// stackToasts('POST HOVER CLOSE');
248+
// }, 10);
249+
},
250+
);
251+
252+
watch(
253+
() => messages.value,
254+
() => {
255+
// Ensure expanded is always false when no toasts are present / only one left
256+
if (messages.value.length <= 1) {
257+
expanded.value = false;
237258
}
238259
},
239260
);
@@ -256,22 +277,21 @@ watchEffect((onInvalidate) => {
256277
{ ...messages.value[indexOfExistingToast], ...newToast },
257278
...messages.value.slice(indexOfExistingToast + 1),
258279
];
259-
} else {
260-
const toast = newToast as Message;
261-
262-
if (toast.position) position.value = toast.position;
263-
264-
messages.value.unshift({
265-
...toast,
266-
id: toast.id,
267-
type: toast.type ?? 'default',
268-
position: toast.position ?? position.value,
269-
life: toast.life ?? props.defaultLife,
270-
title: toast.title,
271-
});
272280
273-
// messages.value = [toast as Message, ...messages.value];
281+
return;
274282
}
283+
const toast = newToast as Message;
284+
285+
if (toast.position) position.value = toast.position;
286+
287+
messages.value.unshift({
288+
...toast,
289+
id: toast.id,
290+
type: toast.type ?? 'default',
291+
position: toast.position ?? position.value,
292+
life: toast.life ?? props.defaultLife,
293+
title: toast.title,
294+
});
275295
});
276296
});
277297
@@ -281,31 +301,37 @@ watchEffect((onInvalidate) => {
281301

282302
<template>
283303
<teleport to="body">
284-
<ul
304+
<ol
285305
v-cloak
286306
ref="container"
287-
:tabindex="-1"
288-
:class="
289-
`fixed w-full ${TOAST_WIDTH ? `sm:w-[${TOAST_WIDTH}px]` : 'sm:max-w-sm'} group z-[500] [&>*]:px-4 [&>*]:px-[${mobileViewportOffset ?? viewportOffset}] [&>*]:sm:px-6 [&>*]:sm:px-[${viewportOffset}] my-4 sm:my-6 my-[${mobileViewportOffset ?? viewportOffset}] sm:my-[${viewportOffset}] ` +
290-
`${position == 'top-right' ? 'right-0 top-0' : ''} ` +
291-
`${position == 'top-left' ? 'left-0 top-0' : ''} ` +
292-
`${position == 'top-center' ? 'left-1/2 -translate-x-1/2 top-0' : ''} ` +
293-
`${position == 'bottom-right' ? 'right-0 bottom-0' : ''} ` +
294-
`${position == 'bottom-left' ? 'left-0 bottom-0' : ''} ` +
295-
`${position == 'bottom-center' ? 'left-1/2 -translate-x-1/2 bottom-0' : ''} `
296-
"
307+
:class="[
308+
`fixed w-full group z-[500] [&>*]:px-4 [&>*]:px-[${mobileViewportOffset ?? viewportOffset}] [&>*]:sm:px-6 [&>*]:sm:px-[${viewportOffset}] my-4 sm:my-6 my-[${mobileViewportOffset ?? viewportOffset}] sm:my-[${viewportOffset}]`,
309+
`${TOAST_WIDTH ? `sm:w-[${TOAST_WIDTH}px]` : 'sm:max-w-sm'}`,
310+
`${position == 'top-right' ? 'right-0 top-0' : ''}`,
311+
`${position == 'top-left' ? 'left-0 top-0' : ''}`,
312+
`${position == 'top-center' ? 'left-1/2 -translate-x-1/2 top-0' : ''}`,
313+
`${position == 'bottom-right' ? 'right-0 bottom-0' : ''}`,
314+
`${position == 'bottom-left' ? 'left-0 bottom-0' : ''}`,
315+
`${position == 'bottom-center' ? 'left-1/2 -translate-x-1/2 bottom-0' : ''}`,
316+
]"
297317
@mouseenter="toastsHovered = true"
298318
@mouseleave="toastsHovered = false"
299319
>
300320
<ToastNotification
301-
v-for="toast in messages"
321+
v-for="(toast, index) in messages"
302322
v-bind="toast"
303323
:key="toast.id"
304-
:stack="stackToasts"
324+
:stack="
325+
() => {
326+
stackToasts('TOAST');
327+
}
328+
"
305329
:toastCount="messages.length"
306330
:position="position"
331+
:expanded="expanded"
332+
:index="index"
307333
@close="deleteToastWithId"
308334
/>
309-
</ul>
335+
</ol>
310336
</teleport>
311337
</template>

0 commit comments

Comments
 (0)