{
  "name": "side-dialog",
  "type": "registry:ui",
  "dependencies": [
    "lucide-react",
    "react",
    "react-dom"
  ],
  "files": [
    {
      "path": "./items/components/side-dialog/component.tsx",
      "type": "registry:ui",
      "target": "./components/ui/shuip/side-dialog.tsx",
      "content": "'use client';\n\nimport { XIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { cn } from '@/lib/utils';\n\ntype SideDialogPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'left' | 'right';\ntype SideDialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport interface SideDialogProps {\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  position?: SideDialogPosition;\n  size?: SideDialogSize;\n  children?: React.ReactNode;\n}\n\nexport interface SideDialogTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  asChild?: boolean;\n  children: React.ReactNode;\n}\n\nexport interface SideDialogContentProps extends React.HTMLAttributes<HTMLDivElement> {\n  showCloseButton?: boolean;\n  children: React.ReactNode;\n}\n\nexport interface SideDialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n  children: React.ReactNode;\n}\n\nexport interface SideDialogTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {\n  children: React.ReactNode;\n}\n\nexport interface SideDialogDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {\n  children: React.ReactNode;\n}\n\nexport interface SideDialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n  children: React.ReactNode;\n}\n\nexport interface SideDialogCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  asChild?: boolean;\n  children?: React.ReactNode;\n}\n\nexport interface SideDialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n  children: React.ReactNode;\n}\n\n// Context for managing dialog state\nconst SideDialogContext = React.createContext<{\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  position: SideDialogPosition;\n  size: SideDialogSize;\n}>({\n  open: false,\n  onOpenChange: () => {},\n  position: 'bottom-right',\n  size: 'sm',\n});\n\nexport function SideDialog({\n  open: controlledOpen,\n  onOpenChange: controlledOnOpenChange,\n  position = 'bottom-right',\n  size = 'sm',\n  children,\n}: SideDialogProps) {\n  // Internal state for uncontrolled mode\n  const [internalOpen, setInternalOpen] = React.useState(false);\n\n  // Use controlled props if provided, otherwise use internal state\n  const open = controlledOpen !== undefined ? controlledOpen : internalOpen;\n  const onOpenChange = controlledOnOpenChange !== undefined ? controlledOnOpenChange : setInternalOpen;\n\n  const contextValue = React.useMemo(\n    () => ({\n      open,\n      onOpenChange,\n      position,\n      size,\n    }),\n    [open, onOpenChange, position, size],\n  );\n\n  return <SideDialogContext.Provider value={contextValue}>{children}</SideDialogContext.Provider>;\n}\n\nexport function SideDialogTrigger({ asChild, children, onClick, ...props }: SideDialogTriggerProps) {\n  const { onOpenChange } = React.useContext(SideDialogContext);\n\n  const handleClick = React.useCallback(\n    (e: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(e);\n      onOpenChange(true);\n    },\n    [onClick, onOpenChange],\n  );\n\n  if (asChild) {\n    return React.cloneElement(children as React.ReactElement, {\n      onClick: handleClick,\n      ...(props as React.ButtonHTMLAttributes<HTMLButtonElement>),\n    });\n  }\n\n  return (\n    <button data-slot='side-dialog-trigger' onClick={handleClick} {...props}>\n      {children}\n    </button>\n  );\n}\n\nexport function SideDialogContent({ showCloseButton = true, className, children, ...props }: SideDialogContentProps) {\n  const { open, onOpenChange, position, size } = React.useContext(SideDialogContext);\n  const [mounted, setMounted] = React.useState(false);\n\n  React.useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  React.useEffect(() => {\n    const handleEscape = (e: KeyboardEvent) => {\n      if (e.key === 'Escape' && open) {\n        onOpenChange(false);\n      }\n    };\n\n    if (open) {\n      document.addEventListener('keydown', handleEscape);\n      document.body.style.overflow = 'hidden';\n    }\n\n    return () => {\n      document.removeEventListener('keydown', handleEscape);\n      document.body.style.overflow = '';\n    };\n  }, [open, onOpenChange]);\n\n  const handleOverlayClick = React.useCallback(\n    (e: React.MouseEvent) => {\n      if (e.target === e.currentTarget) {\n        onOpenChange(false);\n      }\n    },\n    [onOpenChange],\n  );\n\n  const getSizeClasses = (size: SideDialogSize) => {\n    const predefinedSizes = {\n      xs: 'w-full max-w-[min(20rem,calc(100vw-2rem))]',\n      sm: 'w-full max-w-[min(24rem,calc(100vw-2rem))]',\n      md: 'w-full max-w-[min(28rem,calc(100vw-2rem))]',\n      lg: 'w-full max-w-[min(32rem,calc(100vw-2rem))]',\n      xl: 'w-full max-w-[min(36rem,calc(100vw-2rem))]',\n      '2xl': 'w-full max-w-[min(42rem,calc(100vw-2rem))]',\n    };\n\n    return predefinedSizes[size as keyof typeof predefinedSizes] || 'w-full max-w-[min(24rem,calc(100vw-2rem))]';\n  };\n\n  const getPositionClasses = (pos: SideDialogPosition, size: SideDialogSize) => {\n    const baseClasses = 'fixed z-[100] bg-background border rounded-lg shadow-lg';\n    const sizeClasses = getSizeClasses(size);\n\n    switch (pos) {\n      case 'top-left':\n        return cn(\n          baseClasses,\n          sizeClasses,\n          'top-4 left-4',\n          'data-[state=open]:animate-in data-[state=closed]:animate-out',\n          'data-[state=closed]:slide-out-to-top-52 data-[state=closed]:slide-out-to-left-52',\n          'data-[state=open]:slide-in-from-top-52 data-[state=open]:slide-in-from-left-52',\n        );\n      case 'top-right':\n        return cn(\n          baseClasses,\n          sizeClasses,\n          'top-4 right-4',\n          'data-[state=open]:animate-in data-[state=closed]:animate-out',\n          'data-[state=closed]:slide-out-to-top-52 data-[state=closed]:slide-out-to-right-52',\n          'data-[state=open]:slide-in-from-top-52 data-[state=open]:slide-in-from-right-52',\n        );\n      case 'bottom-left':\n        return cn(\n          baseClasses,\n          sizeClasses,\n          'bottom-4 left-4',\n          'data-[state=open]:animate-in data-[state=closed]:animate-out',\n          'data-[state=closed]:slide-out-to-bottom-52 data-[state=closed]:slide-out-to-left-52',\n          'data-[state=open]:slide-in-from-bottom-52 data-[state=open]:slide-in-from-left-52',\n        );\n      case 'bottom-right':\n        return cn(\n          baseClasses,\n          sizeClasses,\n          'bottom-4 right-4',\n          'data-[state=open]:animate-in data-[state=closed]:animate-out',\n          'data-[state=closed]:slide-out-to-bottom-52 data-[state=closed]:slide-out-to-right-52',\n          'data-[state=open]:slide-in-from-bottom-52 data-[state=open]:slide-in-from-right-52',\n        );\n      case 'left':\n        return cn(\n          baseClasses,\n          sizeClasses,\n          'left-4 top-[50%] translate-y-[-50%]',\n          'data-[state=open]:animate-in data-[state=closed]:animate-out',\n          'data-[state=closed]:slide-out-to-left-52',\n          'data-[state=open]:slide-in-from-left-52',\n        );\n      case 'right':\n        return cn(\n          baseClasses,\n          sizeClasses,\n          'right-4 top-[50%] translate-y-[-50%]',\n          'data-[state=open]:animate-in data-[state=closed]:animate-out',\n          'data-[state=closed]:slide-out-to-right-52',\n          'data-[state=open]:slide-in-from-right-52',\n        );\n      default:\n        return cn(baseClasses, sizeClasses);\n    }\n  };\n\n  if (!mounted) return null;\n  if (!open) return null;\n\n  return createPortal(\n    <div\n      data-slot='side-dialog-overlay'\n      className='fixed inset-0 z-40 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0'\n      data-state={open ? 'open' : 'closed'}\n      onClick={handleOverlayClick}\n    >\n      <div\n        data-slot='side-dialog-content'\n        data-state={open ? 'open' : 'closed'}\n        className={cn(\n          getPositionClasses(position, size),\n          'flex flex-col max-h-[calc(100dvh-2rem)] p-6 duration-200',\n          className,\n        )}\n        {...props}\n        onClick={(e) => {\n          e.stopPropagation();\n          props.onClick?.(e);\n        }}\n      >\n        {children}\n        {showCloseButton && (\n          <SideDialogClose className='absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden'>\n            <XIcon className='size-4' />\n            <span className='sr-only'>Close</span>\n          </SideDialogClose>\n        )}\n      </div>\n    </div>,\n    document.body,\n  );\n}\n\nexport function SideDialogHeader({ className, children, ...props }: SideDialogHeaderProps) {\n  return (\n    <div data-slot='side-dialog-header' className={cn('shrink-0 flex flex-col gap-2 mb-4', className)} {...props}>\n      {children}\n    </div>\n  );\n}\n\nexport function SideDialogTitle({ className, children, ...props }: SideDialogTitleProps) {\n  return (\n    <h2 data-slot='side-dialog-title' className={cn('text-lg font-semibold leading-none', className)} {...props}>\n      {children}\n    </h2>\n  );\n}\n\nexport function SideDialogDescription({ className, children, ...props }: SideDialogDescriptionProps) {\n  return (\n    <p data-slot='side-dialog-description' className={cn('text-muted-foreground text-sm', className)} {...props}>\n      {children}\n    </p>\n  );\n}\n\nexport function SideDialogFooter({ className, children, ...props }: SideDialogFooterProps) {\n  return (\n    <div\n      data-slot='side-dialog-footer'\n      className={cn('shrink-0 flex flex-col-reverse gap-2 mt-4 sm:flex-row sm:justify-end', className)}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport function SideDialogBody({ className, children, ...props }: SideDialogBodyProps) {\n  return (\n    <div data-slot='side-dialog-body' className={cn('flex-1 min-h-0 overflow-y-auto', className)} {...props}>\n      {children}\n    </div>\n  );\n}\n\nexport function SideDialogClose({ asChild, children, onClick, ...props }: SideDialogCloseProps) {\n  const { onOpenChange } = React.useContext(SideDialogContext);\n\n  const handleClick = React.useCallback(\n    (e: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(e);\n      onOpenChange(false);\n    },\n    [onClick, onOpenChange],\n  );\n\n  if (asChild && children) {\n    return React.cloneElement(children as React.ReactElement, {\n      onClick: handleClick,\n      ...(props as React.HTMLAttributes<HTMLButtonElement>),\n    });\n  }\n\n  return (\n    <button data-slot='side-dialog-close' onClick={handleClick} {...props}>\n      {children}\n    </button>\n  );\n}\n"
    }
  ],
  "$schema": "https://ui.shadcn.com/schema/registry-item.json"
}
