{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "offcanvas",
  "title": "Offcanvas",
  "description": "Slide-in panel from any side with optional body-scroll mode.",
  "dependencies": ["@radix-ui/react-dialog", "lucide-react"],
  "registryDependencies": [
    "https://indiacn.in/r/theme.json",
    "https://indiacn.in/r/typography.json"
  ],
  "files": [
    {
      "path": "components/ui/offcanvas.tsx",
      "content": "'use client';\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\nimport { X } from 'lucide-react';\nimport { ComponentProps } from 'react';\n\nimport { Headline5, Label2 } from '@/components/ui/typography';\nimport { cn } from '@/lib/utils';\n\n/*\n * UX4G offcanvas: width 400px (left/right), height 30vh (top/bottom),\n * padding 1rem, border 1px solid neutral-200, transition 0.3s ease-in-out,\n * backdrop opacity 0.5.\n */\n\ntype TOffcanvasSide = 'left' | 'right' | 'top' | 'bottom';\n\n/** Offcanvas dialog root component. */\nfunction Offcanvas(props: ComponentProps<typeof DialogPrimitive.Root>) {\n  return <DialogPrimitive.Root {...props} />;\n}\n\n/** Offcanvas trigger button. */\nfunction OffcanvasTrigger(props: ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return <DialogPrimitive.Trigger {...props} />;\n}\n\n/** Offcanvas close button. */\nfunction OffcanvasClose(props: ComponentProps<typeof DialogPrimitive.Close>) {\n  return <DialogPrimitive.Close {...props} />;\n}\n\n/** Semi-transparent backdrop overlay. */\nfunction OffcanvasOverlay({ className, ...props }: ComponentProps<typeof DialogPrimitive.Overlay>) {\n  return (\n    <DialogPrimitive.Overlay\n      className={cn(\n        'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ninterface IOffcanvasContentProps extends ComponentProps<typeof DialogPrimitive.Content> {\n  side?: TOffcanvasSide;\n  bodyScroll?: boolean;\n}\n\nconst OFFCANVAS_SIDE_CLASSES: Record<TOffcanvasSide, string> = {\n  left: 'inset-y-0 left-0 h-full w-[400px] max-w-full border-r border-neutral-200 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left',\n  right:\n    'inset-y-0 right-0 h-full w-[400px] max-w-full border-l border-neutral-200 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',\n  top: 'inset-x-0 top-0 w-full h-[30vh] max-h-full border-b border-neutral-200 data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',\n  bottom:\n    'inset-x-0 bottom-0 w-full h-[30vh] max-h-full border-t border-neutral-200 data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',\n};\n\n/**\n * Sliding panel for off-canvas navigation or content.\n * Sides: left, right, top, bottom. bodyScroll=true allows page scroll while open.\n */\nfunction OffcanvasContent({\n  className,\n  side = 'right',\n  bodyScroll = false,\n  children,\n  ...props\n}: IOffcanvasContentProps) {\n  return (\n    <DialogPrimitive.Portal>\n      {!bodyScroll && <OffcanvasOverlay />}\n      <DialogPrimitive.Content\n        onInteractOutside={bodyScroll ? e => e.preventDefault() : undefined}\n        className={cn(\n          'bg-neutral-0 fixed z-50 flex flex-col shadow-lg transition duration-300 ease-in-out',\n          'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-300',\n          OFFCANVAS_SIDE_CLASSES[side],\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </DialogPrimitive.Content>\n    </DialogPrimitive.Portal>\n  );\n}\n\n/** Offcanvas header. UX4G: padding 1rem. */\nfunction OffcanvasHeader({ className, children, ...props }: ComponentProps<'div'>) {\n  return (\n    <div className={cn('flex items-center justify-between p-4', className)} {...props}>\n      {children}\n      <DialogPrimitive.Close className='hover:bg-primary/8 focus:shadow-focus-primary active:bg-primary/16 rounded-lg p-2 opacity-70 transition-all hover:opacity-100 focus:outline-none'>\n        <X className='size-6' />\n        <Label2 className='sr-only'>Close</Label2>\n      </DialogPrimitive.Close>\n    </div>\n  );\n}\n\n/** Offcanvas title. */\nfunction OffcanvasTitle({\n  className,\n  children,\n  ...props\n}: ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title asChild {...props}>\n      <Headline5 className={cn('leading-normal', className)}>{children}</Headline5>\n    </DialogPrimitive.Title>\n  );\n}\n\n/** Offcanvas body. UX4G: padding 1rem, flex-grow 1, overflow-y auto. */\nfunction OffcanvasBody({ className, ...props }: ComponentProps<'div'>) {\n  return <div className={cn('flex-1 overflow-y-auto p-4', className)} {...props} />;\n}\n\nexport {\n  Offcanvas,\n  OffcanvasTrigger,\n  OffcanvasClose,\n  OffcanvasContent,\n  OffcanvasHeader,\n  OffcanvasTitle,\n  OffcanvasBody,\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
