import { createContext, useContext, useEffect, useState, useCallback, useRef, } from "react"; import { createPortal } from "react-dom"; import { motion, AnimatePresence } from "framer-motion"; import { IoMdArrowRoundBack } from "react-icons/io"; import { cn } from "@/lib/utils"; import { isPWA } from "@/utils/isPWA"; import { Button } from "@/components/ui/button"; import { useTranslation } from "react-i18next"; import { useHistoryBack } from "@/hooks/use-history-back"; const MobilePageContext = createContext<{ open: boolean; onOpenChange: (open: boolean) => void; } | null>(null); type MobilePageProps = { children: React.ReactNode; open?: boolean; onOpenChange?: (open: boolean) => void; enableHistoryBack?: boolean; }; export function MobilePage({ children, open: controlledOpen, onOpenChange, enableHistoryBack = true, }: MobilePageProps) { const [uncontrolledOpen, setUncontrolledOpen] = useState(false); const open = controlledOpen ?? uncontrolledOpen; const setOpen = useCallback( (value: boolean) => { if (onOpenChange) { onOpenChange(value); } else { setUncontrolledOpen(value); } }, [onOpenChange, setUncontrolledOpen], ); // Handle browser back button to close mobile page useHistoryBack({ enabled: enableHistoryBack, open, onClose: () => setOpen(false), }); return ( {children} ); } type MobilePageTriggerProps = React.HTMLAttributes; export function MobilePageTrigger({ children, ...props }: MobilePageTriggerProps) { const context = useContext(MobilePageContext); if (!context) throw new Error("MobilePageTrigger must be used within MobilePage"); return (
context.onOpenChange(true)} {...props}> {children}
); } type MobilePagePortalProps = { children: React.ReactNode; container?: HTMLElement; }; export function MobilePagePortal({ children, container, }: MobilePagePortalProps) { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); return () => setMounted(false); }, []); if (!mounted) return null; return createPortal(children, container || document.body); } type MobilePageContentProps = { children: React.ReactNode; className?: string; scrollerRef?: React.RefObject; }; export function MobilePageContent({ children, className, scrollerRef, }: MobilePageContentProps) { const context = useContext(MobilePageContext); if (!context) throw new Error("MobilePageContent must be used within MobilePage"); const [isVisible, setIsVisible] = useState(context.open); const containerRef = useRef(null); useEffect(() => { if (context.open) { setIsVisible(true); } }, [context.open]); const handleAnimationComplete = () => { if (context.open) { // After opening animation completes, ensure scroller is at the top if (scrollerRef?.current) { scrollerRef.current.scrollTop = 0; } } else { setIsVisible(false); } }; useEffect(() => { if (context.open && scrollerRef?.current) { scrollerRef.current.scrollTop = 0; } }, [context.open, scrollerRef]); return ( {isVisible && ( {children} )} ); } interface MobilePageHeaderProps extends React.HTMLAttributes { onClose?: () => void; actions?: React.ReactNode; } export function MobilePageHeader({ children, className, onClose, actions, ...props }: MobilePageHeaderProps) { const { t } = useTranslation(["common"]); const context = useContext(MobilePageContext); if (!context) throw new Error("MobilePageHeader must be used within MobilePage"); const handleClose = () => { if (onClose) { onClose(); } else { context.onOpenChange(false); } }; return (
{children}
{actions && (
{actions}
)}
); } type MobilePageTitleProps = React.HTMLAttributes; export function MobilePageTitle({ className, ...props }: MobilePageTitleProps) { return

; } type MobilePageDescriptionProps = React.HTMLAttributes; export function MobilePageDescription({ className, ...props }: MobilePageDescriptionProps) { return (

); }