/* Copyright 2023, 2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ import { FC, ReactNode, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Root as DialogRoot, Portal as DialogPortal, Overlay as DialogOverlay, Content as DialogContent, Title as DialogTitle, Close as DialogClose, } from "@radix-ui/react-dialog"; import { Drawer } from "vaul"; import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import classNames from "classnames"; import { Heading, Glass } from "@vector-im/compound-web"; import styles from "./Modal.module.css"; import overlayStyles from "./Overlay.module.css"; import { useMediaQuery } from "./useMediaQuery"; export interface Props { title: string; children: ReactNode; className?: string; /** * The controlled open state of the modal. */ // An option to leave the open state uncontrolled is intentionally not // provided, since modals are always opened due to external triggers, and it // is the author's belief that controlled components lead to more obvious code. open: boolean; /** * Callback for when the user dismisses the modal. If undefined, the modal * will be non-dismissable. */ onDismiss?: () => void; /** * Whether the modal content has tabs. */ // TODO: Better tabs support tabbed?: boolean; } /** * A modal, taking the form of a drawer / bottom sheet on touchscreen devices, * and a dialog box on desktop. */ export const Modal: FC = ({ title, children, className, open, onDismiss, tabbed, ...rest }) => { const { t } = useTranslation(); // Empirically, Chrome on Android can end up not matching (hover: none), but // still matching (pointer: coarse) :/ const touchscreen = useMediaQuery("(hover: none) or (pointer: coarse)"); const onOpenChange = useCallback( (open: boolean) => { if (!open) onDismiss?.(); }, [onDismiss], ); if (touchscreen) { return (
{title}
{children}
); } else { return ( {/* Suppress the warning about there being no description; the modal has an accessible title */}
{title} {onDismiss !== undefined && ( )}
{children}
); } };