import styled, { css, keyframes } from "styled-components"; 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;} 70% {opacity: 0;} 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; width: 100%; height: 100%; z-index: 9999; position: fixed; max-height: 100%; user-select: none; animation-name: ${open}; animation-duration: 0.2s; display: grid; overflow-y: auto; place-items: center; color: var(--foreground); background: rgba(0, 0, 0, 0.8); &.closing { animation-name: ${close}; } &.closing > div { animation-name: ${zoomOut}; } `; const ModalContainer = styled.div` overflow: hidden; max-width: calc(100vw - 20px); border-radius: var(--border-radius); animation-name: ${zoomIn}; animation-duration: 0.25s; animation-timing-function: cubic-bezier(0.3, 0.3, 0.18, 1.1); `; const ModalContent = styled.div< { [key in "attachment" | "noBackground" | "border" | "padding"]?: boolean } >` text-overflow: ellipsis; border-radius: var(--border-radius); h3 { margin-top: 0; } form { display: flex; flex-direction: column; } ${(props) => !props.noBackground && css` background: var(--secondary-header); `} ${(props) => props.padding && css` padding: 1.5em; `} ${(props) => props.attachment && css` border-radius: var(--border-radius) var(--border-radius) 0 0; `} ${(props) => props.border && css` border-radius: var(--border-radius); border: 2px solid var(--secondary-background); `} `; const ModalActions = styled.div` gap: 8px; display: flex; flex-direction: row-reverse; padding: 1em 1.5em; border-radius: 0 0 8px 8px; background: var(--secondary-background); `; export type Action = Omit & { confirmation?: boolean; onClick: () => void; }; interface Props { children?: Children; title?: Children; disallowClosing?: boolean; noBackground?: boolean; dontModal?: boolean; padding?: boolean; onClose: () => void; actions?: Action[]; disabled?: boolean; border?: boolean; visible: boolean; } export let isModalClosing = false; export default function Modal(props: Props) { if (!props.visible) return null; let content = ( {props.title &&

{props.title}

} {props.children}
); if (props.dontModal) { 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") { onClose(); } } document.body.addEventListener("keydown", keyDown); return () => document.body.removeEventListener("keydown", keyDown); }, [props.disallowClosing, props.onClose]); let confirmationAction = props.actions?.find( (action) => action.confirmation, ); useEffect(() => { if (!confirmationAction) return; // ! FIXME: this may be done better if we // ! can focus the button although that // ! doesn't seem to work... function keyDown(e: KeyboardEvent) { if (e.key === "Enter") { confirmationAction!.onClick(); } } document.body.addEventListener("keydown", keyDown); return () => document.body.removeEventListener("keydown", keyDown); }, [confirmationAction]); return createPortal( (e.cancelBubble = true)}> {content} {props.actions && ( {props.actions.map((x) => (