"use client"; import { AnimatePresence, motion } from "framer-motion"; import { X } from "lucide-react"; import { createContext, type ReactNode, useContext, useEffect, useState, } from "react"; // Context interface ExpandableScreenContextValue { isExpanded: boolean; expand: () => void; collapse: () => void; layoutId: string; triggerRadius: string; contentRadius: string; animationDuration: number; } const ExpandableScreenContext = createContext(null); function useExpandableScreen() { const context = useContext(ExpandableScreenContext); if (!context) { throw new Error( "useExpandableScreen must be used within an ExpandableScreen", ); } return context; } // Root Component interface ExpandableScreenProps { children: ReactNode; defaultExpanded?: boolean; onExpandChange?: (expanded: boolean) => void; layoutId?: string; triggerRadius?: string; contentRadius?: string; animationDuration?: number; lockScroll?: boolean; } /** * @link https://www.cult-ui.com/docs/components/expandable-screen * */ export function ExpandableScreen({ children, defaultExpanded = false, onExpandChange, layoutId = "expandable-card", triggerRadius = "100px", contentRadius = "24px", animationDuration = 0.3, lockScroll = true, }: ExpandableScreenProps) { const [isExpanded, setIsExpanded] = useState(defaultExpanded); const expand = () => { setIsExpanded(true); onExpandChange?.(true); }; const collapse = () => { setIsExpanded(false); onExpandChange?.(false); }; useEffect(() => { if (lockScroll) { if (isExpanded) { document.body.style.overflow = "hidden"; } else { document.body.style.overflow = "unset"; } } }, [isExpanded, lockScroll]); return ( {children} ); } // Trigger Component interface ExpandableScreenTriggerProps { children: ReactNode; className?: string; } export function ExpandableScreenTrigger({ children, className = "", }: ExpandableScreenTriggerProps) { const { isExpanded, expand, layoutId, triggerRadius } = useExpandableScreen(); return ( {!isExpanded && ( {/* Background layer with shared layoutId for morphing */} {/* Content layer that fades out on expand */} {children} )} ); } // Content Component interface ExpandableScreenContentProps { children: ReactNode; className?: string; showCloseButton?: boolean; closeButtonClassName?: string; } export function ExpandableScreenContent({ children, className = "", showCloseButton = true, closeButtonClassName = "", }: ExpandableScreenContentProps) { const { isExpanded, collapse, layoutId, contentRadius, animationDuration } = useExpandableScreen(); return ( {isExpanded && (
{/* Morphing background with shared layoutId */} {children} {showCloseButton && ( )}
)}
); } // Background Component (optional) interface ExpandableScreenBackgroundProps { trigger?: ReactNode; content?: ReactNode; className?: string; } export function ExpandableScreenBackground({ trigger, content, className = "", }: ExpandableScreenBackgroundProps) { const { isExpanded } = useExpandableScreen(); if (isExpanded && content) { return
{content}
; } if (!isExpanded && trigger) { return
{trigger}
; } return null; } export { useExpandableScreen };