{
  "$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 { AnimatePresence, motion } from \"motion/react\"\nimport useMeasure from \"react-use-measure\"\nimport { Drawer } from \"vaul\"\n\nimport { cn } from \"@/lib/utils\"\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype ViewComponent = React.ComponentType<Record<string, unknown>>\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<typeof useMeasure>[0]\n  bounds: ReturnType<typeof useMeasure>[1]\n  views: ViewsRegistry | undefined\n}\n\nconst FamilyDrawerContext = createContext<FamilyDrawerContextValue | undefined>(\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<number>(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    <FamilyDrawerContext.Provider value={contextValue}>\n      <Drawer.Root open={isOpen} onOpenChange={setIsOpen}>\n        {children}\n      </Drawer.Root>\n    </FamilyDrawerContext.Provider>\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      <Drawer.Trigger asChild>\n        <Slot>{children}</Slot>\n      </Drawer.Trigger>\n    )\n  }\n\n  return (\n    <Drawer.Trigger asChild>\n      <button\n        className={cn(\n          \"bg-background text-foreground hover:bg-accent focus-visible:shadow-focus-ring-button fixed top-1/2 left-1/2 h-[44px] -translate-x-1/2 -translate-y-1/2 cursor-pointer rounded-full border px-4 py-2 font-medium antialiased transition-colors md:font-medium\",\n          className\n        )}\n        type=\"button\"\n      >\n        {children}\n      </button>\n    </Drawer.Trigger>\n  )\n}\n\n// ============================================================================\n// Portal Component\n// ============================================================================\n\nfunction FamilyDrawerPortal({ children }: { children: ReactNode }) {\n  return <Drawer.Portal>{children}</Drawer.Portal>\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    <Drawer.Overlay\n      className={cn(\"fixed inset-0 z-50 bg-black/30\", className)}\n      onClick={onClick || (() => 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    <motion.div\n      animate={{\n        height: bounds.height,\n        transition: {\n          duration: 0.27,\n          ease: [0.25, 1, 0.5, 1],\n        },\n      }}\n    >\n      {children}\n    </motion.div>\n  )\n\n  if (asChild) {\n    return (\n      <Drawer.Content\n        asChild\n        className={cn(\n          \"bg-background fixed inset-x-4 bottom-4 z-50 mx-auto max-w-[361px] overflow-hidden rounded-[36px] outline-none md:mx-auto md:w-full\",\n          className\n        )}\n      >\n        <Slot>{content}</Slot>\n      </Drawer.Content>\n    )\n  }\n\n  return (\n    <Drawer.Content\n      asChild\n      className={cn(\n        \"bg-background fixed inset-x-4 bottom-4 z-50 mx-auto max-w-[361px] overflow-hidden rounded-[36px] outline-none md:mx-auto md:w-full\",\n        className\n      )}\n    >\n      {content}\n    </Drawer.Content>\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    <div\n      ref={elementRef}\n      className={cn(\"px-6 pt-2.5 pb-6 antialiased\", className)}\n    >\n      {children}\n    </div>\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    <AnimatePresence initial={false} mode=\"popLayout\" custom={view}>\n      <motion.div\n        initial={{ opacity: 0, scale: 0.96 }}\n        animate={{ opacity: 1, scale: 1, y: 0 }}\n        exit={{ opacity: 0, scale: 0.96 }}\n        key={view}\n        transition={{\n          duration: opacityDuration,\n          ease: [0.26, 0.08, 0.25, 1],\n        }}\n      >\n        {children}\n      </motion.div>\n    </AnimatePresence>\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    <button\n      data-vaul-no-drag=\"\"\n      className={cn(\n        \"bg-muted text-muted-foreground focus-visible:shadow-focus-ring-button absolute top-7 right-8 z-10 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full transition-transform focus:scale-95 active:scale-75\",\n        className\n      )}\n      type=\"button\"\n    >\n      {children || <CloseIcon />}\n    </button>\n  )\n\n  if (asChild) {\n    return (\n      <Drawer.Close asChild>\n        <Slot>{defaultClose}</Slot>\n      </Drawer.Close>\n    )\n  }\n\n  return <Drawer.Close asChild>{defaultClose}</Drawer.Close>\n}\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\ntype FamilyDrawerTitleProps = React.ComponentProps<typeof Drawer.Title>\n\n/**\n * Accessible title for the drawer. Every view must render one (directly or via\n * `FamilyDrawerHeader`) so screen readers can announce the dialog.\n */\nfunction FamilyDrawerTitle({ className, ...props }: FamilyDrawerTitleProps) {\n  return (\n    <Drawer.Title\n      data-slot=\"family-drawer-title\"\n      className={cn(\n        \"text-foreground text-[22px] font-semibold md:font-medium\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\ntype FamilyDrawerDescriptionProps = React.ComponentProps<\n  typeof Drawer.Description\n>\n\nfunction FamilyDrawerDescription({\n  className,\n  ...props\n}: FamilyDrawerDescriptionProps) {\n  return (\n    <Drawer.Description\n      data-slot=\"family-drawer-description\"\n      className={cn(\n        \"text-muted-foreground text-[17px] leading-[24px] font-medium md:font-normal\",\n        className\n      )}\n      {...props}\n    />\n  )\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    <header className={cn(\"mt-[21px]\", className)}>\n      {icon}\n      <FamilyDrawerTitle className=\"mt-2.5\">{title}</FamilyDrawerTitle>\n      <FamilyDrawerDescription className=\"mt-3\">\n        {description}\n      </FamilyDrawerDescription>\n    </header>\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    <button\n      data-vaul-no-drag=\"\"\n      className={cn(\n        \"bg-muted text-foreground focus-visible:shadow-focus-ring-button flex h-12 w-full cursor-pointer items-center gap-[15px] rounded-[16px] px-4 text-[17px] font-semibold transition-transform focus:scale-95 active:scale-95 md:font-medium\",\n        className\n      )}\n      onClick={onClick}\n      type=\"button\"\n    >\n      {children}\n    </button>\n  )\n\n  if (asChild) {\n    return <Slot>{button}</Slot>\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    <button\n      data-vaul-no-drag=\"\"\n      type=\"button\"\n      className={cn(\n        \"focus-visible:shadow-focus-ring-button flex h-12 w-full cursor-pointer items-center justify-center gap-[15px] rounded-full text-center text-[19px] font-semibold transition-transform focus:scale-95 active:scale-95 md:font-medium\",\n        className\n      )}\n      onClick={onClick}\n    >\n      {children}\n    </button>\n  )\n\n  if (asChild) {\n    return <Slot>{button}</Slot>\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 ? <DefaultComponent /> : null\n  }\n\n  return <ViewComponent />\n}\n\n// ============================================================================\n// Icons\n// ============================================================================\n\nfunction CloseIcon() {\n  return (\n    <svg\n      width=\"12\"\n      height=\"12\"\n      viewBox=\"0 0 12 12\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <title>Close Icon</title>\n      <path\n        d=\"M10.4854 1.99998L2.00007 10.4853\"\n        stroke=\"#999999\"\n        strokeWidth=\"3\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        d=\"M10.4854 10.4844L2.00007 1.99908\"\n        stroke=\"#999999\"\n        strokeWidth=\"3\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\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  FamilyDrawerTitle,\n  FamilyDrawerDescription,\n  FamilyDrawerButton,\n  FamilyDrawerSecondaryButton,\n  FamilyDrawerViewContent,\n  useFamilyDrawer,\n  type ViewsRegistry,\n  type ViewComponent,\n}\n",
      "type": "registry:ui"
    }
  ]
}