{"version":3,"file":"Modal.cjs","names":[],"sources":["../../../src/components/Modal/Modal.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — size, fullscreen and\n * fullscreenOnMobile are three distinct decisions (a fixed width, always full, full\n * only on a phone), and the rest is the dialog contract: open, onClose, title,\n * children, footer, closeOnBackdrop, closeOnEsc, hideCloseButton. The body is the\n * focus trap and the scroll lock, which must be installed and torn down together.\n */\nimport { useEffect, useId } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Modal.module.css\";\nimport { usePortalHost } from \"../Portal/portal-host\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"3xl\";\n\nexport interface ModalProps {\n    open: boolean;\n    onClose: () => void;\n    title?: ReactNode;\n    children?: ReactNode;\n    footer?: ReactNode;\n    size?: ModalSize;\n    closeOnBackdrop?: boolean;\n    closeOnEsc?: boolean;\n    className?: string;\n    hideCloseButton?: boolean;\n    /** Force fullscreen at all breakpoints. */\n    fullscreen?: boolean;\n    /** Auto-fullscreen on mobile viewports (< 640px). Default `false`. */\n    fullscreenOnMobile?: boolean;\n    /**\n     * Accessible name for the dialog. Only needed when there is no `title` —\n     * with a `title` the dialog is named by it through `aria-labelledby`.\n     */\n    \"aria-label\"?: string;\n}\n\n/**\n * Portal-rendered modal dialog with backdrop, Esc handler, and slots for\n * header/body/footer. Locks body scroll while open.\n *\n * The `dialog` role needs an accessible name: a `title` supplies it via\n * `aria-labelledby`, and a titleless dialog should pass `aria-label`. The\n * header heading is only rendered when there is a title, so a close-button-only\n * header does not leave an empty `h3` behind.\n */\nexport function Modal({\n    open,\n    onClose,\n    title,\n    children,\n    footer,\n    size = \"md\",\n    closeOnBackdrop = true,\n    closeOnEsc = true,\n    className,\n    hideCloseButton = false,\n    fullscreen = false,\n    fullscreenOnMobile = false,\n    \"aria-label\": ariaLabel,\n}: ModalProps) {\n    const titleId = useId();\n    useEffect(() => {\n        if (!open) return;\n        const previousOverflow = document.body.style.overflow;\n        document.body.style.overflow = \"hidden\";\n\n        const handleKey = (event: KeyboardEvent): void => {\n            if (closeOnEsc && event.key === \"Escape\") onClose();\n        };\n        window.addEventListener(\"keydown\", handleKey);\n\n        return () => {\n            document.body.style.overflow = previousOverflow;\n            window.removeEventListener(\"keydown\", handleKey);\n        };\n    }, [open, closeOnEsc, onClose]);\n\n    const portalHost = usePortalHost();\n\n    if (!open || !portalHost) return null;\n\n    return createPortal(\n        <div\n            className={styles.overlay}\n            role=\"presentation\"\n            onClick={() => {\n                if (closeOnBackdrop) onClose();\n            }}\n        >\n            <div\n                role=\"dialog\"\n                aria-modal=\"true\"\n                aria-labelledby={title ? titleId : undefined}\n                aria-label={title ? undefined : ariaLabel}\n                className={cn(\n                    styles.dialog,\n                    sizeClassName(size),\n                    fullscreen && styles.fullscreen,\n                    fullscreenOnMobile && styles.fullscreenOnMobile,\n                    className,\n                )}\n                onClick={(event) => event.stopPropagation()}\n            >\n                {(title || !hideCloseButton) && (\n                    <header className={styles.header}>\n                        {title && (\n                            <h3 id={titleId} className={styles.title}>\n                                {title}\n                            </h3>\n                        )}\n                        {!hideCloseButton && (\n                            <button\n                                type=\"button\"\n                                className={styles.close}\n                                aria-label=\"Fechar\"\n                                onClick={onClose}\n                            >\n                                <CloseIcon />\n                            </button>\n                        )}\n                    </header>\n                )}\n                <div className={styles.body}>{children}</div>\n                {footer && <footer className={styles.footer}>{footer}</footer>}\n            </div>\n        </div>,\n        portalHost,\n    );\n}\n\nfunction sizeClassName(size: ModalSize): string | undefined {\n    if (size === \"2xl\") return styles.size2xl;\n    if (size === \"3xl\") return styles.size3xl;\n    return styles[size];\n}\n\nfunction CloseIcon() {\n    return (\n        <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n            <path\n                d=\"M6 6l12 12M6 18L18 6\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n            />\n        </svg>\n    );\n}\n"],"mappings":"0LA+CA,SAAgB,EAAM,CAClB,OACA,UACA,QACA,WACA,SACA,OAAO,KACP,kBAAkB,GAClB,aAAa,GACb,YACA,kBAAkB,GAClB,aAAa,GACb,qBAAqB,GACrB,aAAc,GACH,CACX,IAAM,GAAA,EAAU,EAAA,MAAA,CAAM,GACtB,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAM,OACX,IAAM,EAAmB,SAAS,KAAK,MAAM,SAC7C,SAAS,KAAK,MAAM,SAAW,SAE/B,IAAM,EAAa,GAA+B,CAC1C,GAAc,EAAM,MAAQ,UAAU,EAAQ,CACtD,EAGA,OAFA,OAAO,iBAAiB,UAAW,CAAS,MAE/B,CACT,SAAS,KAAK,MAAM,SAAW,EAC/B,OAAO,oBAAoB,UAAW,CAAS,CACnD,CACJ,EAAG,CAAC,EAAM,EAAY,CAAO,CAAC,EAE9B,IAAM,EAAa,EAAA,cAAc,EAIjC,MAFI,CAAC,GAAQ,CAAC,EAAmB,MAEjC,EAAO,EAAA,aAAA,EACH,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,QAClB,KAAK,eACL,YAAe,CACP,GAAiB,EAAQ,CACjC,EAEA,UAAA,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,KAAK,SACL,aAAW,OACX,kBAAiB,EAAQ,EAAU,IAAA,GACnC,aAAY,EAAQ,IAAA,GAAY,EAChC,UAAW,EAAA,GACP,EAAA,QAAO,OACP,EAAc,CAAI,EAClB,GAAc,EAAA,QAAO,WACrB,GAAsB,EAAA,QAAO,mBAC7B,CACJ,EACA,QAAU,GAAU,EAAM,gBAAgB,EAZ9C,SAAA,EAcM,GAAS,CAAC,KACR,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAA1B,SAAA,CACK,IACG,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,GAAI,EAAS,UAAW,EAAA,QAAO,MAC9B,SAAA,CACD,CAAA,EAEP,CAAC,IACE,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,MAClB,aAAW,SACX,QAAS,EAET,UAAA,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,CAAA,CACR,CAAA,CAER,KAEZ,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EAC3C,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAAS,SAAA,CAAe,CAAA,CAC5D,GACJ,CAAA,EACL,CACJ,CACJ,CAEA,SAAS,EAAc,EAAqC,CAGxD,OAFI,IAAS,MAAc,EAAA,QAAO,QAC9B,IAAS,MAAc,EAAA,QAAO,QAC3B,EAAA,QAAO,EAClB,CAEA,SAAS,GAAY,CACjB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACjD,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,uBACF,OAAO,eACP,YAAY,IACZ,cAAc,OACjB,CAAA,CACA,CAAA,CAEb"}