"use client"; import * as React from "react"; import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer"; import { cn } from "@/lib/utils"; import { useAsRef } from "@/hooks/use-as-ref"; import { useIsomorphicLayoutEffect } from "@/hooks/use-isomorphic-layout-effect"; import { useLazyRef } from "@/hooks/use-lazy-ref"; import { useIsMobile } from "@/hooks/use-mobile"; const ROOT_NAME = "ResponsiveDialog"; interface StoreState { open: boolean; isMobile: boolean; } interface Store { subscribe: (callback: () => void) => () => void; getState: () => StoreState; setState: (key: K, value: StoreState[K]) => void; notify: () => void; } const StoreContext = React.createContext(null); function useStore( selector: (state: StoreState) => T, ogStore?: Store | null, ): T { const contextStore = React.useContext(StoreContext); const store = ogStore ?? contextStore; if (!store) { throw new Error(`\`useStore\` must be used within \`${ROOT_NAME}\``); } const getSnapshot = React.useCallback( () => selector(store.getState()), [store, selector], ); return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot); } interface ResponsiveDialogProps extends React.ComponentProps { breakpoint?: number; } function ResponsiveDialog({ breakpoint = 768, open: openProp, defaultOpen = false, onOpenChange: onOpenChangeProp, ...props }: ResponsiveDialogProps) { const isMobile = useIsMobile(breakpoint); const listenersRef = useLazyRef(() => new Set<() => void>()); const stateRef = useLazyRef(() => ({ open: openProp ?? defaultOpen, isMobile, })); const onOpenChangeRef = useAsRef(onOpenChangeProp); const store = React.useMemo(() => { return { subscribe: (cb) => { listenersRef.current.add(cb); return () => listenersRef.current.delete(cb); }, getState: () => stateRef.current, setState: (key, value) => { if (Object.is(stateRef.current[key], value)) return; if (key === "open" && typeof value === "boolean") { stateRef.current.open = value; onOpenChangeRef.current?.(value); } else { stateRef.current[key] = value; } store.notify(); }, notify: () => { for (const cb of listenersRef.current) { cb(); } }, }; }, [listenersRef, stateRef, onOpenChangeRef]); const open = useStore((state) => state.open, store); useIsomorphicLayoutEffect(() => { if (openProp !== undefined) { store.setState("open", openProp); } }, [openProp]); useIsomorphicLayoutEffect(() => { store.setState("isMobile", isMobile); }, [isMobile]); const onOpenChange = React.useCallback( (value: boolean) => { store.setState("open", value); }, [store], ); if (isMobile) { return ( ); } return ( ); } function ResponsiveDialogTrigger({ ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ; } return ; } function ResponsiveDialogClose({ ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ; } return ; } function ResponsiveDialogPortal({ ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ; } return ; } function ResponsiveDialogOverlay({ ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ; } return ; } function ResponsiveDialogContent({ className, ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ( ); } return ( ); } function ResponsiveDialogHeader({ ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ; } return ; } function ResponsiveDialogFooter({ ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ; } return ; } function ResponsiveDialogTitle({ ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ; } return ; } function ResponsiveDialogDescription({ ...props }: React.ComponentProps) { const isMobile = useStore((state) => state.isMobile); if (isMobile) { return ; } return ; } export { ResponsiveDialog, ResponsiveDialogClose, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogFooter, ResponsiveDialogHeader, ResponsiveDialogOverlay, ResponsiveDialogPortal, ResponsiveDialogTitle, ResponsiveDialogTrigger, };