2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 03:19:01 +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
id="toast-container"
class="absolute bottom-0 flex flex-col items-end mb-3 pe-6"
style="width: 100%"
>
<div id="toast-target" />
</div>
style="width: 100%; pointer-events: none"
></div>
</div>
</template>
<script lang="ts">

View File

@ -1,63 +1,68 @@
<template>
<div
class="
text-gray-900
shadow-lg
px-3
py-2
flex
items-center
mb-3
w-96
z-30
bg-white
rounded-lg
"
style="transition: opacity 150ms ease-in"
:style="{ opacity }"
v-if="show"
>
<feather-icon :name="iconName" class="w-6 h-6 me-3" :class="iconColor" />
<div @click="actionClicked" :class="actionText ? 'cursor-pointer' : ''">
<p class="text-base">{{ message }}</p>
<button
v-if="actionText"
class="text-sm text-gray-700 hover:text-gray-800"
<Teleport to="#toast-container">
<Transition>
<div
v-if="open"
class="
inner
text-gray-900
shadow-lg
px-3
py-2
flex
items-center
mb-3
w-toast
z-30
bg-white
rounded-lg
"
style="pointer-events: auto"
>
{{ actionText }}
</button>
</div>
<feather-icon
name="x"
class="w-4 h-4 ms-auto text-gray-600 cursor-pointer hover:text-gray-800"
@click="closeToast"
/>
</div>
<feather-icon
:name="iconName"
class="w-6 h-6 me-3"
:class="iconColor"
/>
<div @click="actionClicked" :class="actionText ? 'cursor-pointer' : ''">
<p class="text-base">{{ message }}</p>
<button
v-if="actionText"
class="text-sm text-gray-700 hover:text-gray-800"
>
{{ actionText }}
</button>
</div>
<feather-icon
name="x"
class="
w-4
h-4
ms-auto
text-gray-600
cursor-pointer
hover:text-gray-800
"
@click="closeToast"
/>
</div>
</Transition>
</Teleport>
</template>
<script lang="ts">
import { getColorClass } from 'src/utils/colors';
import { ToastDuration, ToastType } from 'src/utils/types';
import { toastDurationMap } from 'src/utils/ui';
import { defineComponent, PropType } from 'vue';
import { defineComponent, nextTick, PropType } from 'vue';
import FeatherIcon from './FeatherIcon.vue';
type TimeoutId = ReturnType<typeof setTimeout>;
export default defineComponent({
components: {
FeatherIcon,
},
data() {
return {
opacity: 0,
show: true,
opacityTimeoutId: null,
cleanupTimeoutId: null,
} as {
opacity: number;
show: boolean;
opacityTimeoutId: null | TimeoutId;
cleanupTimeoutId: null | TimeoutId;
open: false,
};
},
props: {
@ -90,20 +95,10 @@ export default defineComponent({
return getColorClass(this.color ?? 'gray', 'text', 400);
},
},
mounted() {
async mounted() {
const duration = toastDurationMap[this.duration];
setTimeout(() => {
this.opacity = 1;
}, 50);
this.opacityTimeoutId = setTimeout(() => {
this.opacity = 0;
}, duration);
this.cleanupTimeoutId = setTimeout(() => {
this.show = false;
this.cleanup();
}, duration + 300);
await nextTick(() => (this.open = true));
setTimeout(this.closeToast, duration);
},
methods: {
actionClicked() {
@ -111,30 +106,24 @@ export default defineComponent({
this.closeToast();
},
closeToast() {
if (this.opacityTimeoutId != null) {
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());
this.open = false;
},
},
});
</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 {
width: 300px;
width: 24rem;
}
.w-toast {
width: 24rem;
}
.w-quick-edit {

View File

@ -34,11 +34,7 @@ export async function showDialog<DO extends DialogOptions>(options: DO) {
},
});
const fragment = document.createDocumentFragment();
// @ts-ignore
dialogApp.mount(fragment);
document.body.append(fragment);
fragmentMountComponent(dialogApp);
}) 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 target = document.getElementById(replaceId);
if (target === null) {
return;
}
const parent = target.parentElement;
const clone = target.cloneNode();
// @ts-ignore
app.mount(fragment);
target.replaceWith(fragment);
parent!.append(clone);
document.body.append(fragment);
}
// @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;
};