77 lines
1.9 KiB
TypeScript
77 lines
1.9 KiB
TypeScript
import {
|
|
PropsWithChildren,
|
|
Suspense,
|
|
useCallback,
|
|
useEffect,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
import Drawer from "./drawer";
|
|
import Modal from "./modal";
|
|
import { ScrollArea, Flex, Loader } from "@mantine/core";
|
|
|
|
interface SheetProps extends PropsWithChildren {
|
|
title?: string;
|
|
opened: boolean;
|
|
onChange: (next: boolean) => void;
|
|
}
|
|
|
|
const DRAWER_EXIT_MS = 500;
|
|
const MODAL_EXIT_MS = 200;
|
|
const EXIT_BUFFER_MS = 100;
|
|
|
|
const Sheet: React.FC<SheetProps> = ({ title, children, opened, onChange }) => {
|
|
const isMobile = useIsMobile();
|
|
const handleClose = useCallback(() => onChange(false), [onChange]);
|
|
|
|
const [mounted, setMounted] = useState(opened);
|
|
const openedRef = useRef(opened);
|
|
openedRef.current = opened;
|
|
const handleExited = useCallback(() => {
|
|
if (!openedRef.current) setMounted(false);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (opened) {
|
|
setMounted(true);
|
|
return;
|
|
}
|
|
const exitDuration =
|
|
(isMobile ? DRAWER_EXIT_MS : MODAL_EXIT_MS) + EXIT_BUFFER_MS;
|
|
const timer = window.setTimeout(() => setMounted(false), exitDuration);
|
|
return () => window.clearTimeout(timer);
|
|
}, [opened, isMobile]);
|
|
|
|
const SheetComponent = isMobile ? Drawer : Modal;
|
|
|
|
if (!opened && !mounted) return null;
|
|
|
|
return (
|
|
<SheetComponent
|
|
title={title}
|
|
opened={opened}
|
|
onChange={onChange}
|
|
onClose={handleClose}
|
|
onExited={handleExited}
|
|
>
|
|
<Suspense fallback={
|
|
<Flex justify='center' align='center' w='100%' style={{ minHeight: '25vh' }}>
|
|
<Loader size='lg' />
|
|
</Flex>
|
|
}>
|
|
<ScrollArea.Autosize
|
|
style={{ flex: 1, maxHeight: '75dvh' }}
|
|
scrollbarSize={8}
|
|
scrollbars="y"
|
|
type="scroll"
|
|
>
|
|
{children}
|
|
</ScrollArea.Autosize>
|
|
</Suspense>
|
|
</SheetComponent>
|
|
);
|
|
};
|
|
|
|
export default Sheet;
|