2
0
mirror of https://github.com/frappe/books.git synced 2025-01-09 09:50:27 +00:00

refactor: simplify Toast with Teleport

This commit is contained in:
18alantom 2023-03-24 13:42:03 +05:30 committed by Alan
parent 903ee3e158
commit 4fa3169ce4
4 changed files with 81 additions and 121 deletions

View File

@ -30,10 +30,8 @@
<div <div
id="toast-container" id="toast-container"
class="absolute bottom-0 flex flex-col items-end mb-3 pe-6" class="absolute bottom-0 flex flex-col items-end mb-3 pe-6"
style="width: 100%" style="width: 100%; pointer-events: none"
> ></div>
<div id="toast-target" />
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -1,6 +1,10 @@
<template> <template>
<Teleport to="#toast-container">
<Transition>
<div <div
v-if="open"
class=" class="
inner
text-gray-900 text-gray-900
shadow-lg shadow-lg
px-3 px-3
@ -8,16 +12,18 @@
flex flex
items-center items-center
mb-3 mb-3
w-96 w-toast
z-30 z-30
bg-white bg-white
rounded-lg rounded-lg
" "
style="transition: opacity 150ms ease-in" style="pointer-events: auto"
:style="{ opacity }"
v-if="show"
> >
<feather-icon :name="iconName" class="w-6 h-6 me-3" :class="iconColor" /> <feather-icon
:name="iconName"
class="w-6 h-6 me-3"
:class="iconColor"
/>
<div @click="actionClicked" :class="actionText ? 'cursor-pointer' : ''"> <div @click="actionClicked" :class="actionText ? 'cursor-pointer' : ''">
<p class="text-base">{{ message }}</p> <p class="text-base">{{ message }}</p>
<button <button
@ -29,35 +35,34 @@
</div> </div>
<feather-icon <feather-icon
name="x" name="x"
class="w-4 h-4 ms-auto text-gray-600 cursor-pointer hover:text-gray-800" class="
w-4
h-4
ms-auto
text-gray-600
cursor-pointer
hover:text-gray-800
"
@click="closeToast" @click="closeToast"
/> />
</div> </div>
</Transition>
</Teleport>
</template> </template>
<script lang="ts"> <script lang="ts">
import { getColorClass } from 'src/utils/colors'; import { getColorClass } from 'src/utils/colors';
import { ToastDuration, ToastType } from 'src/utils/types'; import { ToastDuration, ToastType } from 'src/utils/types';
import { toastDurationMap } from 'src/utils/ui'; import { toastDurationMap } from 'src/utils/ui';
import { defineComponent, PropType } from 'vue'; import { defineComponent, nextTick, PropType } from 'vue';
import FeatherIcon from './FeatherIcon.vue'; import FeatherIcon from './FeatherIcon.vue';
type TimeoutId = ReturnType<typeof setTimeout>;
export default defineComponent({ export default defineComponent({
components: { components: {
FeatherIcon, FeatherIcon,
}, },
data() { data() {
return { return {
opacity: 0, open: false,
show: true,
opacityTimeoutId: null,
cleanupTimeoutId: null,
} as {
opacity: number;
show: boolean;
opacityTimeoutId: null | TimeoutId;
cleanupTimeoutId: null | TimeoutId;
}; };
}, },
props: { props: {
@ -90,20 +95,10 @@ export default defineComponent({
return getColorClass(this.color ?? 'gray', 'text', 400); return getColorClass(this.color ?? 'gray', 'text', 400);
}, },
}, },
mounted() { async mounted() {
const duration = toastDurationMap[this.duration]; const duration = toastDurationMap[this.duration];
setTimeout(() => { await nextTick(() => (this.open = true));
this.opacity = 1; setTimeout(this.closeToast, duration);
}, 50);
this.opacityTimeoutId = setTimeout(() => {
this.opacity = 0;
}, duration);
this.cleanupTimeoutId = setTimeout(() => {
this.show = false;
this.cleanup();
}, duration + 300);
}, },
methods: { methods: {
actionClicked() { actionClicked() {
@ -111,30 +106,24 @@ export default defineComponent({
this.closeToast(); this.closeToast();
}, },
closeToast() { closeToast() {
if (this.opacityTimeoutId != null) { this.open = false;
clearTimeout(this.opacityTimeoutId);
}
if (this.cleanupTimeoutId != null) {
clearTimeout(this.cleanupTimeoutId);
}
this.opacity = 0;
setTimeout(() => {
this.show = false;
this.cleanup();
}, 300);
},
cleanup() {
const element = this.$el;
if (!(element instanceof Element)) {
return;
}
Array.from(element.parentElement?.children ?? [])
.filter((el) => !el.innerHTML)
.splice(1)
.forEach((el) => el.remove());
}, },
}, },
}); });
</script> </script>
<style scoped>
.v-enter-active,
.v-leave-active {
transition: all 150ms ease-out;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-enter-to,
.v-leave-from {
opacity: 1;
}
</style>

View File

@ -91,7 +91,11 @@ input[type='number']::-webkit-inner-spin-button {
} }
.w-dialog { .w-dialog {
width: 300px; width: 24rem;
}
.w-toast {
width: 24rem;
} }
.w-quick-edit { .w-quick-edit {

View File

@ -34,11 +34,7 @@ export async function showDialog<DO extends DialogOptions>(options: DO) {
}, },
}); });
const fragment = document.createDocumentFragment(); fragmentMountComponent(dialogApp);
// @ts-ignore
dialogApp.mount(fragment);
document.body.append(fragment);
}) as DialogReturn<DO>; }) as DialogReturn<DO>;
} }
@ -49,40 +45,13 @@ export async function showToast(options: ToastOptions) {
}, },
}); });
replaceAndAppendMount(toastApp, 'toast-target'); fragmentMountComponent(toastApp);
} }
function replaceAndAppendMount(app: App<Element>, replaceId: string) { function fragmentMountComponent(app: App<Element>) {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
const target = document.getElementById(replaceId);
if (target === null) {
return;
}
const parent = target.parentElement;
const clone = target.cloneNode();
// @ts-ignore // @ts-ignore
app.mount(fragment); app.mount(fragment);
target.replaceWith(fragment); document.body.append(fragment);
parent!.append(clone);
} }
// @ts-ignore
window.st = () => showToast({ message: 'peace' });
// @ts-ignore
window.sd = async function () {
const options = {
title: 'Do This?',
description: 'Give me confirmation, should I do this?',
buttons: [
{ label: 'Yes', handler: () => 'do it', isPrimary: true },
{ label: 'No', handler: () => 'dont do it' },
],
};
const ret = await showDialog(options);
console.log(ret);
return ret;
};