{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "family-drawer", "type": "registry:ui", "description": "A multi-view drawer component with smooth animations, view navigation, and customizable content views", "dependencies": [ "motion", "react-use-measure", "vaul", "@radix-ui/react-slot" ], "files": [ { "path": "registry/default/ui/family-drawer.tsx", "content": "\"use client\"\n\nimport {\n createContext,\n useContext,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport clsx from \"clsx\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport useMeasure from \"react-use-measure\"\nimport { Drawer } from \"vaul\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype ViewComponent = React.ComponentType>\n\ninterface ViewsRegistry {\n [viewName: string]: ViewComponent\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface FamilyDrawerContextValue {\n isOpen: boolean\n view: string\n setView: (view: string) => void\n opacityDuration: number\n elementRef: ReturnType[0]\n bounds: ReturnType[1]\n views: ViewsRegistry | undefined\n}\n\nconst FamilyDrawerContext = createContext(\n undefined\n)\n\nfunction useFamilyDrawer() {\n const context = useContext(FamilyDrawerContext)\n if (!context) {\n throw new Error(\n \"FamilyDrawer components must be used within FamilyDrawerRoot\"\n )\n }\n return context\n}\n\n// ============================================================================\n// Root Component\n// ============================================================================\n\ninterface FamilyDrawerRootProps {\n children: ReactNode\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n defaultView?: string\n onViewChange?: (view: string) => void\n views?: ViewsRegistry\n}\n\nfunction FamilyDrawerRoot({\n children,\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n defaultView = \"default\",\n onViewChange,\n views: customViews,\n}: FamilyDrawerRootProps) {\n const [internalOpen, setInternalOpen] = useState(defaultOpen)\n const [view, setView] = useState(defaultView)\n const [elementRef, bounds] = useMeasure()\n const previousHeightRef = useRef(0)\n\n const isOpen = controlledOpen !== undefined ? controlledOpen : internalOpen\n const setIsOpen = onOpenChange || setInternalOpen\n\n const opacityDuration = useMemo(() => {\n const currentHeight = bounds.height\n const previousHeight = previousHeightRef.current\n\n const MIN_DURATION = 0.15\n const MAX_DURATION = 0.27\n\n if (!previousHeightRef.current) {\n previousHeightRef.current = currentHeight\n return MIN_DURATION\n }\n\n const heightDifference = Math.abs(currentHeight - previousHeight)\n previousHeightRef.current = currentHeight\n\n const duration = Math.min(\n Math.max(heightDifference / 500, MIN_DURATION),\n MAX_DURATION\n )\n\n return duration\n }, [bounds.height])\n\n const handleViewChange = (newView: string) => {\n setView(newView)\n onViewChange?.(newView)\n }\n\n // Use custom views if provided, otherwise pass undefined\n const views =\n customViews && Object.keys(customViews).length > 0 ? customViews : undefined\n\n const contextValue: FamilyDrawerContextValue = {\n isOpen,\n view,\n setView: handleViewChange,\n opacityDuration,\n elementRef,\n bounds,\n views,\n }\n\n return (\n \n \n {children}\n \n \n )\n}\n\n// ============================================================================\n// Trigger Component\n// ============================================================================\n\ninterface FamilyDrawerTriggerProps {\n children: ReactNode\n asChild?: boolean\n className?: string\n}\n\nfunction FamilyDrawerTrigger({\n children,\n asChild = false,\n className,\n}: FamilyDrawerTriggerProps) {\n if (asChild) {\n return (\n \n {children}\n \n )\n }\n\n return (\n \n \n {children}\n \n \n )\n}\n\n// ============================================================================\n// Portal Component\n// ============================================================================\n\nfunction FamilyDrawerPortal({ children }: { children: ReactNode }) {\n return {children}\n}\n\n// ============================================================================\n// Overlay Component\n// ============================================================================\n\ninterface FamilyDrawerOverlayProps {\n className?: string\n onClick?: () => void\n}\n\nfunction FamilyDrawerOverlay({ className, onClick }: FamilyDrawerOverlayProps) {\n const { setView } = useFamilyDrawer()\n\n return (\n setView(\"default\"))}\n />\n )\n}\n\n// ============================================================================\n// Content Component\n// ============================================================================\n\ninterface FamilyDrawerContentProps {\n children: ReactNode\n className?: string\n asChild?: boolean\n}\n\nfunction FamilyDrawerContent({\n children,\n className,\n asChild = false,\n}: FamilyDrawerContentProps) {\n const { bounds } = useFamilyDrawer()\n\n const content = (\n \n {children}\n \n )\n\n if (asChild) {\n return (\n \n {content}\n \n )\n }\n\n return (\n \n {content}\n \n )\n}\n\n// ============================================================================\n// Animated Wrapper Component\n// ============================================================================\n\ninterface FamilyDrawerAnimatedWrapperProps {\n children: ReactNode\n className?: string\n}\n\nfunction FamilyDrawerAnimatedWrapper({\n children,\n className,\n}: FamilyDrawerAnimatedWrapperProps) {\n const { elementRef } = useFamilyDrawer()\n\n return (\n \n {children}\n \n )\n}\n\n// ============================================================================\n// Animated Content Component\n// ============================================================================\n\ninterface FamilyDrawerAnimatedContentProps {\n children: ReactNode\n}\n\nfunction FamilyDrawerAnimatedContent({\n children,\n}: FamilyDrawerAnimatedContentProps) {\n const { view, opacityDuration } = useFamilyDrawer()\n\n return (\n \n \n {children}\n \n \n )\n}\n\n// ============================================================================\n// Close Component\n// ============================================================================\n\ninterface FamilyDrawerCloseProps {\n children?: ReactNode\n asChild?: boolean\n className?: string\n}\n\nfunction FamilyDrawerClose({\n children,\n asChild = false,\n className,\n}: FamilyDrawerCloseProps) {\n const defaultClose = (\n \n {children || }\n \n )\n\n if (asChild) {\n return (\n \n {defaultClose}\n \n )\n }\n\n return {defaultClose}\n}\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\ninterface FamilyDrawerHeaderProps {\n icon: ReactNode\n title: string\n description: string\n className?: string\n}\n\nfunction FamilyDrawerHeader({\n icon,\n title,\n description,\n className,\n}: FamilyDrawerHeaderProps) {\n return (\n
\n {icon}\n

\n {title}\n

\n

\n {description}\n

\n
\n )\n}\n\ninterface FamilyDrawerButtonProps {\n children: ReactNode\n onClick: () => void\n className?: string\n asChild?: boolean\n}\n\nfunction FamilyDrawerButton({\n children,\n onClick,\n className,\n asChild = false,\n}: FamilyDrawerButtonProps) {\n const button = (\n \n {children}\n \n )\n\n if (asChild) {\n return {button}\n }\n\n return button\n}\n\ninterface FamilyDrawerSecondaryButtonProps {\n children: ReactNode\n onClick: () => void\n className: string\n asChild?: boolean\n}\n\nfunction FamilyDrawerSecondaryButton({\n children,\n onClick,\n className,\n asChild = false,\n}: FamilyDrawerSecondaryButtonProps) {\n const button = (\n \n {children}\n \n )\n\n if (asChild) {\n return {button}\n }\n\n return button\n}\n\n// ============================================================================\n// View Content Renderer\n// ============================================================================\n\ninterface FamilyDrawerViewContentProps {\n views?: ViewsRegistry\n}\n\nfunction FamilyDrawerViewContent(\n {\n views: propViews,\n }: FamilyDrawerViewContentProps = {} as FamilyDrawerViewContentProps\n) {\n const { view, views: contextViews } = useFamilyDrawer()\n\n // Use prop views first, then context views\n const views = propViews || contextViews\n\n if (!views) {\n throw new Error(\n \"FamilyDrawerViewContent requires views to be provided via props or FamilyDrawerRoot\"\n )\n }\n\n const ViewComponent = views[view]\n\n if (!ViewComponent) {\n // Fallback to default view if view not found\n const DefaultComponent = views.default\n return DefaultComponent ? : null\n }\n\n return \n}\n\n// ============================================================================\n// Icons\n// ============================================================================\n\nfunction CloseIcon() {\n return (\n \n Close Icon\n \n \n \n )\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport {\n FamilyDrawerRoot,\n FamilyDrawerTrigger,\n FamilyDrawerPortal,\n FamilyDrawerOverlay,\n FamilyDrawerContent,\n FamilyDrawerAnimatedWrapper,\n FamilyDrawerAnimatedContent,\n FamilyDrawerClose,\n FamilyDrawerHeader,\n FamilyDrawerButton,\n FamilyDrawerSecondaryButton,\n FamilyDrawerViewContent,\n useFamilyDrawer,\n type ViewsRegistry,\n type ViewComponent,\n}\n", "type": "registry:ui" } ] }