Files
robot-shanghai/src/components/ui/overlay/Overlay.tsx
T
2026-09-08 14:19:44 +08:00

99 lines
2.1 KiB
TypeScript

import type { CSSProperties, MouseEvent } from "react";
import { useEffect } from "react";
import { Portal } from "@/components/ui/portal";
import { cn } from "@/lib/cn";
import styles from "./Overlay.module.css";
import type { OverlayProps } from "./Overlay.types";
export function Overlay({
open,
children,
variant = "dim",
backgroundImage,
backgroundClassName,
closeOnBackgroundClick = true,
closeOnEscape = true,
lockScroll = true,
onClose,
container,
disabledPortal = false,
className,
contentClassName,
fixed,
...props
}: OverlayProps) {
const isFixed = fixed ?? container == null;
useEffect(() => {
if (!open || !closeOnEscape) {
return;
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onClose?.();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [closeOnEscape, onClose, open]);
useEffect(() => {
if (!open || !lockScroll || typeof document === "undefined") {
return;
}
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = previousOverflow;
};
}, [lockScroll, open]);
if (!open) {
return null;
}
const backgroundImageStyle: CSSProperties | undefined = backgroundImage
? { backgroundImage }
: undefined;
const handleBackgroundClick = (event: MouseEvent<HTMLButtonElement>) => {
event.currentTarget.blur();
onClose?.();
};
return (
<Portal container={container} disabled={disabledPortal}>
<div
className={cn(
styles.overlay,
isFixed ? styles.fixed : styles.absolute,
className,
)}
{...props}
>
<button
type="button"
aria-label="关闭弹层背景"
className={cn(
styles.background,
styles[variant],
backgroundClassName,
)}
disabled={!closeOnBackgroundClick}
style={backgroundImageStyle}
onClick={handleBackgroundClick}
/>
<div className={cn(styles.content, contentClassName)}>
<div className={styles.contentInner}>{children}</div>
</div>
</div>
</Portal>
);
}