Files
flxn-app/src/components/sheet/sheet.tsx
T

82 lines
2.1 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 { Box, 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>
}>
{/* Native overflow, not ScrollArea: vaul's drag-vs-scroll detection
only recognizes natively scrollable elements. */}
<Box
style={{
flex: 1,
maxHeight: "75dvh",
overflowY: "auto",
overscrollBehavior: "contain",
WebkitOverflowScrolling: "touch",
}}
>
{children}
</Box>
</Suspense>
</SheetComponent>
);
};
export default Sheet;