Modals: Add close animation.

This commit is contained in:
Paul 2021-07-07 22:02:18 +01:00
parent 41567413f2
commit 98ea3e7af7
5 changed files with 43 additions and 6 deletions

2
external/lang vendored

@ -1 +1 @@
Subproject commit e91ad23b9747d424e14566e858b21d59a61d0ee0 Subproject commit f28b9849a1765c45c67b173516f1776113828233

View file

@ -1,9 +1,10 @@
import styled, { css, keyframes } from "styled-components"; 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 { Children } from "../../types/Preact";
import Button, { ButtonProps } from "./Button"; import Button, { ButtonProps } from "./Button";
import { internalSubscribe } from "../../lib/eventEmitter";
const open = keyframes` const open = keyframes`
0% {opacity: 0;} 0% {opacity: 0;}
@ -11,12 +12,23 @@ const open = keyframes`
100% {opacity: 1;} 100% {opacity: 1;}
`; `;
const close = keyframes`
0% {opacity: 1;}
70% {opacity: 0;}
100% {opacity: 0;}
`;
const zoomIn = keyframes` const zoomIn = keyframes`
0% {transform: scale(0.5);} 0% {transform: scale(0.5);}
98% {transform: scale(1.01);} 98% {transform: scale(1.01);}
100% {transform: scale(1);} 100% {transform: scale(1);}
`; `;
const zoomOut = keyframes`
0% {transform: scale(1);}
100% {transform: scale(0.5);}
`;
const ModalBase = styled.div` const ModalBase = styled.div`
top: 0; top: 0;
left: 0; left: 0;
@ -36,6 +48,14 @@ const ModalBase = styled.div`
color: var(--foreground); color: var(--foreground);
background: rgba(0, 0, 0, 0.8); background: rgba(0, 0, 0, 0.8);
&.closing {
animation-name: ${close};
}
&.closing > div {
animation-name: ${zoomOut};
}
`; `;
const ModalContainer = styled.div` const ModalContainer = styled.div`
@ -120,6 +140,8 @@ interface Props {
visible: boolean; visible: boolean;
} }
export let isModalClosing = false;
export default function Modal(props: Props) { export default function Modal(props: Props) {
if (!props.visible) return null; if (!props.visible) return null;
@ -138,12 +160,21 @@ export default function Modal(props: Props) {
return content; return content;
} }
const [animateClose, setAnimateClose] = useState(false);
isModalClosing = animateClose;
function onClose() {
setAnimateClose(true);
setTimeout(() => props.onClose(), 2e2);
}
useEffect(() => internalSubscribe('Modal', 'close', onClose), []);
useEffect(() => { useEffect(() => {
if (props.disallowClosing) return; if (props.disallowClosing) return;
function keyDown(e: KeyboardEvent) { function keyDown(e: KeyboardEvent) {
if (e.key === "Escape") { if (e.key === "Escape") {
props.onClose(); onClose();
} }
} }
@ -154,6 +185,7 @@ export default function Modal(props: Props) {
let confirmationAction = props.actions?.find( let confirmationAction = props.actions?.find(
(action) => action.confirmation, (action) => action.confirmation,
); );
useEffect(() => { useEffect(() => {
if (!confirmationAction) return; if (!confirmationAction) return;
@ -171,7 +203,7 @@ export default function Modal(props: Props) {
}, [confirmationAction]); }, [confirmationAction]);
return createPortal( return createPortal(
<ModalBase <ModalBase className={animateClose ? 'closing' : undefined}
onClick={(!props.disallowClosing && props.onClose) || undefined}> onClick={(!props.disallowClosing && props.onClose) || undefined}>
<ModalContainer onClick={(e) => (e.cancelBubble = true)}> <ModalContainer onClick={(e) => (e.cancelBubble = true)}>
{content} {content}

View file

@ -1,3 +1,5 @@
import { isModalClosing } from "../../components/ui/Modal";
import { internalEmit } from "../../lib/eventEmitter";
import { Screen } from "./Intermediate"; import { Screen } from "./Intermediate";
import { ClipboardModal } from "./modals/Clipboard"; import { ClipboardModal } from "./modals/Clipboard";
import { ErrorModal } from "./modals/Error"; import { ErrorModal } from "./modals/Error";
@ -12,7 +14,7 @@ export interface Props {
} }
export default function Modals({ screen, openScreen }: 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) { switch (screen.id) {
case "_prompt": case "_prompt":

View file

@ -1,4 +1,6 @@
import { useContext } from "preact/hooks"; import { useContext } from "preact/hooks";
import { isModalClosing } from "../../components/ui/Modal";
import { internalEmit } from "../../lib/eventEmitter";
import { IntermediateContext, useIntermediate } from "./Intermediate"; import { IntermediateContext, useIntermediate } from "./Intermediate";
import { SpecialInputModal } from "./modals/Input"; import { SpecialInputModal } from "./modals/Input";
@ -14,7 +16,7 @@ export default function Popovers() {
const { screen } = useContext(IntermediateContext); const { screen } = useContext(IntermediateContext);
const { openScreen } = useIntermediate(); const { openScreen } = useIntermediate();
const onClose = () => openScreen({ id: "none" }); const onClose = () => isModalClosing ? openScreen({ id: "none" }) : internalEmit('Modal', 'close');
switch (screen.id) { switch (screen.id) {
case "profile": case "profile":

View file

@ -26,4 +26,5 @@ export function internalEmit(ns: string, event: string, ...args: any[]) {
// - MessageBox/append // - MessageBox/append
// - TextArea/focus // - TextArea/focus
// - ReplyBar/add // - ReplyBar/add
// - Modal/close
// - PWA/update // - PWA/update