import styled, { css } from "styled-components"; import { createPortal } from "react-dom"; import React, { useCallback, useEffect, useState } from "react"; import { Button, Props as ButtonProps } from "../inputs/Button"; import { H2 } from "../heading/H2"; import { H4 } from "../heading/H4"; import { animationFadeIn, animationFadeOut, animationZoomIn, animationZoomOut, } from "../../../common/animations"; export type Action = Omit, "as"> & Omit & { confirmation?: boolean; onClick: () => void | boolean | Promise; }; export interface Props { padding?: string; maxWidth?: string; maxHeight?: string; disabled?: boolean; transparent?: boolean; nonDismissable?: boolean; actions?: Action[]; onClose?: (force: boolean) => void; signal?: "close" | "confirm" | "force"; registerOnClose?: (fn: () => void) => () => void; registerOnConfirm?: (fn: () => void) => () => void; title?: React.ReactNode; description?: React.ReactNode; children?: React.ReactNode; } const Base = styled.div<{ closing?: boolean }>` top: 0; left: 0; width: 100%; height: 100%; z-index: 9999; position: fixed; max-height: 100%; user-select: none; animation-duration: 0.2s; animation-fill-mode: forwards; display: grid; overflow-y: auto; place-items: center; color: var(--foreground); background: rgba(0, 0, 0, 0.8); ${(props) => props.closing ? css` animation-name: ${animationFadeOut}; > div { animation-name: ${animationZoomOut}; } ` : css` animation-name: ${animationFadeIn}; `} `; const Container = styled.div< Pick & { actions: boolean } >` min-height: 200px; max-width: min(calc(100vw - 20px), ${(props) => props.maxWidth ?? "800px"}); max-height: min( calc(100vh - 20px), ${(props) => props.maxHeight ?? "1000px"} ); margin: 20px; display: flex; flex-direction: column; animation-name: ${animationZoomIn}; animation-duration: 0.25s; animation-timing-function: cubic-bezier(0.3, 0.3, 0.18, 1.1); ${(props) => !props.maxWidth && css` width: 100%; `} ${(props) => !props.transparent && css` overflow: hidden; background: var(--secondary-header); border-radius: var(--border-radius); `} `; const Title = styled.div` padding: 1rem; flex-shrink: 0; word-break: break-word; gap: 8px; display: flex; flex-direction: column; `; const Content = styled.div>` flex-grow: 1; padding-top: 0; padding: ${(props) => props.padding ?? "0 1rem 1rem"}; overflow-y: auto; font-size: 0.9375rem; display: flex; flex-direction: column; ${(props) => !props.transparent && css` background: var(--secondary-header); `} `; const Actions = styled.div` flex-shrink: 0; gap: 8px; display: flex; padding: 1rem; flex-direction: row-reverse; background: var(--secondary-background); border-radius: 0 0 var(--border-radius) var(--border-radius); `; export function LargeModal({ children, actions, disabled, onClose, title, description, nonDismissable, registerOnClose, registerOnConfirm, signal, ...props }: Props) { const [closing, setClosing] = useState(false); const closeModal = useCallback(() => { setClosing(true); if (!closing) setTimeout(() => onClose?.(true), 2e2); }, [closing, props]); const confirm = useCallback(async () => { if (await actions?.find((x) => x.confirmation)?.onClick?.()) { closeModal(); } }, [actions]); useEffect(() => registerOnClose?.(closeModal), [closeModal]); useEffect(() => registerOnConfirm?.(confirm), [confirm]); useEffect(() => { if (signal === "confirm") { confirm(); } else if (signal) { if (signal === "close" && nonDismissable) { return; } closeModal(); } }, [signal]); return createPortal( !nonDismissable && closeModal()}> 0 : false} onClick={(e) => e.stopPropagation()}> {(title || description) && ( {title && <H2>{title}</H2>} {description && <H4>{description}</H4>} )} {children} {actions && actions.length > 0 && ( {actions.map((x, index) => (