/** * The modal dialog (milestone 23, ruling 4). * * React Aria owns the focus trap, the Escape handler and the `aria-modal` * wiring that the hand-rolled overlay only approximated. State is controlled by * the caller because the trigger is a table row button, not a `DialogTrigger`. */ import type { ReactNode } from "react"; import * as stylex from "@stylexjs/stylex"; import { Dialog as AriaDialog, Modal, ModalOverlay } from "react-aria-components"; import { colors } from "./tokens.stylex"; interface Props { /** The dialog's accessible name. */ label: string; isOpen: boolean; onClose: () => void; children: ReactNode; } const styles = stylex.create({ overlay: { position: "fixed", inset: 0, zIndex: 50, display: "flex", alignItems: "center", justifyContent: "center", padding: "1rem", backgroundColor: "rgba(0, 0, 0, 0.4)", }, panel: { width: "100%", maxWidth: "28rem", borderRadius: "0.5rem", borderWidth: 1, borderStyle: "solid", borderColor: colors.border, backgroundColor: colors.surfaceRaised, color: colors.text, padding: "1.5rem", boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)", }, /** The panel already draws the boundary; the dialog's own ring would double it. */ body: { outlineStyle: "none", }, }); export default function Dialog({ label, isOpen, onClose, children }: Props) { return ( { if (!open) onClose(); }} isDismissable className={() => stylex.props(styles.overlay).className ?? ""} > stylex.props(styles.panel).className ?? ""}> {children} ); }