From 98ea3e7af70318063b224b9c45b16bf5d5de4d6d Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 7 Jul 2021 22:02:18 +0100 Subject: [PATCH] Modals: Add close animation. --- external/lang | 2 +- src/components/ui/Modal.tsx | 38 ++++++++++++++++++++++++--- src/context/intermediate/Modals.tsx | 4 ++- src/context/intermediate/Popovers.tsx | 4 ++- src/lib/eventEmitter.ts | 1 + 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/external/lang b/external/lang index e91ad23b..f28b9849 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit e91ad23b9747d424e14566e858b21d59a61d0ee0 +Subproject commit f28b9849a1765c45c67b173516f1776113828233 diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index 67009c7c..d9c9a100 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -1,9 +1,10 @@ import styled, { css, keyframes } from "styled-components"; -import { createPortal, useEffect } from "preact/compat"; +import { createPortal, useEffect, useState } from "preact/compat"; import { Children } from "../../types/Preact"; import Button, { ButtonProps } from "./Button"; +import { internalSubscribe } from "../../lib/eventEmitter"; const open = keyframes` 0% {opacity: 0;} @@ -11,12 +12,23 @@ const open = keyframes` 100% {opacity: 1;} `; +const close = keyframes` + 0% {opacity: 1;} + 70% {opacity: 0;} + 100% {opacity: 0;} +`; + const zoomIn = keyframes` 0% {transform: scale(0.5);} 98% {transform: scale(1.01);} 100% {transform: scale(1);} `; +const zoomOut = keyframes` + 0% {transform: scale(1);} + 100% {transform: scale(0.5);} +`; + const ModalBase = styled.div` top: 0; left: 0; @@ -36,6 +48,14 @@ const ModalBase = styled.div` color: var(--foreground); background: rgba(0, 0, 0, 0.8); + + &.closing { + animation-name: ${close}; + } + + &.closing > div { + animation-name: ${zoomOut}; + } `; const ModalContainer = styled.div` @@ -120,6 +140,8 @@ interface Props { visible: boolean; } +export let isModalClosing = false; + export default function Modal(props: Props) { if (!props.visible) return null; @@ -138,12 +160,21 @@ export default function Modal(props: Props) { return content; } + const [animateClose, setAnimateClose] = useState(false); + isModalClosing = animateClose; + function onClose() { + setAnimateClose(true); + setTimeout(() => props.onClose(), 2e2); + } + + useEffect(() => internalSubscribe('Modal', 'close', onClose), []); + useEffect(() => { if (props.disallowClosing) return; function keyDown(e: KeyboardEvent) { if (e.key === "Escape") { - props.onClose(); + onClose(); } } @@ -154,6 +185,7 @@ export default function Modal(props: Props) { let confirmationAction = props.actions?.find( (action) => action.confirmation, ); + useEffect(() => { if (!confirmationAction) return; @@ -171,7 +203,7 @@ export default function Modal(props: Props) { }, [confirmationAction]); return createPortal( - (e.cancelBubble = true)}> {content} diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx index 4db686dd..9ed2438e 100644 --- a/src/context/intermediate/Modals.tsx +++ b/src/context/intermediate/Modals.tsx @@ -1,3 +1,5 @@ +import { isModalClosing } from "../../components/ui/Modal"; +import { internalEmit } from "../../lib/eventEmitter"; import { Screen } from "./Intermediate"; import { ClipboardModal } from "./modals/Clipboard"; import { ErrorModal } from "./modals/Error"; @@ -12,7 +14,7 @@ export interface Props { } export default function Modals({ screen, openScreen }: Props) { - const onClose = () => openScreen({ id: "none" }); + const onClose = () => isModalClosing ? openScreen({ id: "none" }) : internalEmit('Modal', 'close'); switch (screen.id) { case "_prompt": diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx index f2dc82cc..44c0d9ec 100644 --- a/src/context/intermediate/Popovers.tsx +++ b/src/context/intermediate/Popovers.tsx @@ -1,4 +1,6 @@ import { useContext } from "preact/hooks"; +import { isModalClosing } from "../../components/ui/Modal"; +import { internalEmit } from "../../lib/eventEmitter"; import { IntermediateContext, useIntermediate } from "./Intermediate"; import { SpecialInputModal } from "./modals/Input"; @@ -14,7 +16,7 @@ export default function Popovers() { const { screen } = useContext(IntermediateContext); const { openScreen } = useIntermediate(); - const onClose = () => openScreen({ id: "none" }); + const onClose = () => isModalClosing ? openScreen({ id: "none" }) : internalEmit('Modal', 'close'); switch (screen.id) { case "profile": diff --git a/src/lib/eventEmitter.ts b/src/lib/eventEmitter.ts index 192bae46..751f52d2 100644 --- a/src/lib/eventEmitter.ts +++ b/src/lib/eventEmitter.ts @@ -26,4 +26,5 @@ export function internalEmit(ns: string, event: string, ...args: any[]) { // - MessageBox/append // - TextArea/focus // - ReplyBar/add +// - Modal/close // - PWA/update