mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-30 05:34:21 +03:00
feat(core): keep the latest toast showing when multiple call (#4961)
This commit is contained in:
parent
f09c717413
commit
9bab1b5dff
@ -30,15 +30,15 @@ const htmlToElement = <T extends ChildNode>(html: string | TemplateResult) => {
|
|||||||
const createToastContainer = (portal?: HTMLElement) => {
|
const createToastContainer = (portal?: HTMLElement) => {
|
||||||
portal = portal || document.body;
|
portal = portal || document.body;
|
||||||
const styles = css`
|
const styles = css`
|
||||||
position: absolute;
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
top: 16px;
|
|
||||||
left: 16px;
|
|
||||||
right: 16px;
|
|
||||||
bottom: 78px;
|
bottom: 78px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
const template = html`<div
|
const template = html`<div
|
||||||
@ -55,6 +55,65 @@ export type ToastOptions = {
|
|||||||
portal?: HTMLElement;
|
portal?: HTMLElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const animateToastOut = (toastElement: HTMLDivElement) => {
|
||||||
|
toastElement.style.opacity = '0';
|
||||||
|
setTimeout(() => toastElement.remove(), 300); // Match transition duration
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAndShowNewToast = (
|
||||||
|
message: string,
|
||||||
|
duration: number,
|
||||||
|
portal?: HTMLElement
|
||||||
|
) => {
|
||||||
|
if (!ToastContainer || (portal && !portal.contains(ToastContainer))) {
|
||||||
|
ToastContainer = createToastContainer(portal);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastStyles = css`
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
max-width: 480px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--affine-font-family);
|
||||||
|
font-size: var(--affine-font-sm);
|
||||||
|
padding: 10px 16px;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--affine-white);
|
||||||
|
background: var(--affine-tooltip);
|
||||||
|
box-shadow: var(--affine-float-button-shadow);
|
||||||
|
border-radius: 8px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition:
|
||||||
|
transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1),
|
||||||
|
opacity 0.3s ease;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const toastTemplate = html`<div
|
||||||
|
style="${toastStyles}"
|
||||||
|
data-testid="affine-toast"
|
||||||
|
>
|
||||||
|
${message}
|
||||||
|
</div>`;
|
||||||
|
const toastElement = htmlToElement<HTMLDivElement>(toastTemplate);
|
||||||
|
// message is not trusted
|
||||||
|
toastElement.textContent = message;
|
||||||
|
ToastContainer.appendChild(toastElement);
|
||||||
|
logger.debug(`toast with message: "${message}"`);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('affine-toast:emit', { detail: message })
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
toastElement.style.opacity = '1';
|
||||||
|
toastElement.style.transform = 'translateY(0)';
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
animateToastOut(toastElement);
|
||||||
|
}, duration);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
@ -63,80 +122,21 @@ export type ToastOptions = {
|
|||||||
*/
|
*/
|
||||||
export const toast = (
|
export const toast = (
|
||||||
message: string,
|
message: string,
|
||||||
{ duration = 2500, portal }: ToastOptions = {
|
{ duration = 3000, portal }: ToastOptions = {}
|
||||||
duration: 2500,
|
|
||||||
}
|
|
||||||
) => {
|
) => {
|
||||||
if (!ToastContainer || (portal && !portal.contains(ToastContainer))) {
|
if (ToastContainer && ToastContainer.children.length >= 2) {
|
||||||
ToastContainer = createToastContainer(portal);
|
// If there are already two toasts, remove the oldest one immediately
|
||||||
|
const oldestToast = ToastContainer.children[0] as HTMLDivElement;
|
||||||
|
oldestToast.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = css`
|
// If there is one toast already, start its disappearing animation
|
||||||
max-width: 480px;
|
if (ToastContainer && ToastContainer.children.length === 1) {
|
||||||
text-align: center;
|
const currentToast = ToastContainer.children[0] as HTMLDivElement;
|
||||||
font-family: var(--affine-font-family);
|
animateToastOut(currentToast);
|
||||||
font-size: var(--affine-font-sm);
|
}
|
||||||
padding: 6px 12px;
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
color: var(--affine-white);
|
|
||||||
background: var(--affine-tooltip);
|
|
||||||
box-shadow: var(--affine-float-button-shadow);
|
|
||||||
border-radius: 10px;
|
|
||||||
transition: all 230ms cubic-bezier(0.21, 1.02, 0.73, 1);
|
|
||||||
opacity: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const template = html`<div
|
createAndShowNewToast(message, duration, portal);
|
||||||
style="${styles}"
|
|
||||||
data-testid="affine-toast"
|
|
||||||
></div>`;
|
|
||||||
const element = htmlToElement<HTMLDivElement>(template);
|
|
||||||
// message is not trusted
|
|
||||||
element.textContent = message;
|
|
||||||
ToastContainer.appendChild(element);
|
|
||||||
|
|
||||||
logger.debug(`toast with message: "${message}"`);
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent('affine-toast:emit', { detail: message })
|
|
||||||
);
|
|
||||||
|
|
||||||
const fadeIn = [
|
|
||||||
{
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
{ opacity: 1 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
duration: 230,
|
|
||||||
easing: 'cubic-bezier(0.21, 1.02, 0.73, 1)',
|
|
||||||
fill: 'forwards' as const,
|
|
||||||
} satisfies KeyframeAnimationOptions;
|
|
||||||
|
|
||||||
element.animate(fadeIn, options);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const animation = element.animate(
|
|
||||||
// fade out
|
|
||||||
fadeIn.reverse(),
|
|
||||||
options
|
|
||||||
);
|
|
||||||
animation.finished
|
|
||||||
.then(() => {
|
|
||||||
element.style.maxHeight = '0';
|
|
||||||
element.style.margin = '0';
|
|
||||||
element.style.padding = '0';
|
|
||||||
// wait for transition
|
|
||||||
// ToastContainer = null;
|
|
||||||
element.addEventListener('transitionend', () => {
|
|
||||||
element.remove();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}, duration);
|
|
||||||
return element;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default toast;
|
export default toast;
|
||||||
|
Loading…
Reference in New Issue
Block a user