"use client"; import React, { useEffect, useRef, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { X } from "lucide-react"; import { useAtom } from "jotai"; import { cn } from "@/utils/cn"; import { isMobileSheetOpenAtom } from "@/atoms/sheets"; interface MobileSheetProps { isOpen: boolean; onClose: () => void; title?: React.ReactNode; showCloseButton?: boolean; position?: "top" | "bottom"; spacing?: "sm" | "md" | "lg"; // sm=16px, md=20px, lg=24px from all edges children: React.ReactNode; className?: string; contentHeight?: "auto" | "fill" | "full"; // 'auto' for small content, 'fill' for lists, 'full' for nearly fullscreen contentPadding?: boolean; // Whether to add default padding to content area (default: true) closeOnOverlayClick?: boolean; // Whether clicking the overlay should close the sheet (default: true) } // Hook to auto-close mobile sheets when transitioning to desktop function useAutoCloseOnDesktop(isOpen: boolean, onClose: () => void) { useEffect(() => { if (!isOpen) return; const handleResize = () => { // Close immediately if screen becomes larger than mobile breakpoint (768px for lg:) if (window.innerWidth >= 1024) { onClose(); } }; window.addEventListener("resize", handleResize); // Check immediately in case we're already on desktop handleResize(); return () => { window.removeEventListener("resize", handleResize); }; }, [isOpen, onClose]); } export function MobileSheet({ isOpen, onClose, title, showCloseButton = false, position = "bottom", spacing = "sm", children, className, contentHeight = "auto", contentPadding = true, closeOnOverlayClick = true, }: MobileSheetProps) { const [, setIsMobileSheetOpen] = useAtom(isMobileSheetOpenAtom); const scrollRef = useRef(null); const [showTopGradient, setShowTopGradient] = useState(false); const [showBottomGradient, setShowBottomGradient] = useState(false); // Handle global mobile sheet state changes (always reflect actual open state) useEffect(() => { setIsMobileSheetOpen(isOpen); return () => setIsMobileSheetOpen(false); }, [isOpen, setIsMobileSheetOpen]); // Lock background scroll when sheet is open, restore when closed useEffect(() => { if (typeof document === "undefined") return; if (isOpen) { const previousOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = previousOverflow; }; } else { document.body.style.overflow = ""; } }, [isOpen]); // Auto-close when transitioning to desktop useAutoCloseOnDesktop(isOpen, onClose); // Check initial scroll state when sheet opens useEffect(() => { if (isOpen && scrollRef.current) { // Small delay to ensure content is rendered setTimeout(() => { const target = scrollRef.current; if (target) { const isAtTop = target.scrollTop <= 5; const isAtBottom = target.scrollHeight - target.scrollTop - target.clientHeight <= 5; const hasScroll = target.scrollHeight > target.clientHeight; setShowTopGradient(hasScroll && !isAtTop); setShowBottomGradient(hasScroll && !isAtBottom); } }, 50); } }, [isOpen]); // Enhanced close handler const handleClose = () => { // Re-enable page scroll after closing sheet document.body.style.overflow = ""; onClose(); }; // Define spacing values const getSpacingClass = () => { const horizontalSpacing = spacing === "sm" ? "left-16 right-16" : spacing === "md" ? "left-20 right-20" : "left-24 right-24"; if (contentHeight === "full") { if (spacing === "sm") return `${horizontalSpacing} top-16 bottom-16`; if (spacing === "md") return `${horizontalSpacing} top-20 bottom-20`; return `${horizontalSpacing} top-24 bottom-24`; } const verticalSpacing = position === "top" ? spacing === "sm" ? "top-16" : spacing === "md" ? "top-20" : "top-24" : spacing === "sm" ? "bottom-16" : spacing === "md" ? "bottom-20" : "bottom-24"; return `${horizontalSpacing} ${verticalSpacing}`; }; return ( {isOpen && (
{ if (closeOnOverlayClick) { handleClose(); } }} > {/* Sheet content - positioned from top or bottom */} e.stopPropagation()} >
{/* Header */} {(title || showCloseButton) && (
{title && (

{title}

)} {showCloseButton && ( )}
)} {/* Content with gradient overlays */}
{ const target = e.currentTarget; const isAtTop = target.scrollTop <= 5; const isAtBottom = target.scrollHeight - target.scrollTop - target.clientHeight <= 5; const hasScroll = target.scrollHeight > target.clientHeight; // Update gradient visibility states setShowTopGradient(hasScroll && !isAtTop); setShowBottomGradient(hasScroll && !isAtBottom); }} > {children}
{/* Animated fade gradients */}
)}
); }