{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "draggable",
  "title": "Draggable",
  "description": "Drag and drop components using @dnd-kit/react. Includes draggable, droppable, and sortable functionality with hooks.",
  "dependencies": [
    "@dnd-kit/core",
    "@dnd-kit/sortable",
    "@dnd-kit/utilities",
    "lucide-react",
    "@mdi/js"
  ],
  "registryDependencies": [
    "https://blok.sitecore.com/r/theme.json",
    "https://blok.sitecore.com/r/button.json"
  ],
  "files": [
    {
      "path": "src/components/ui/dnd-context.tsx",
      "content": "\"use client\";\n\nimport {\n  type CollisionDetection,\n  DndContext as DndKitContext,\n  type DragEndEvent,\n  type DragMoveEvent,\n  type DragOverEvent,\n  type DragStartEvent,\n  KeyboardSensor,\n  PointerSensor,\n  type UniqueIdentifier,\n  closestCenter,\n  useSensor,\n  useSensors,\n} from \"@dnd-kit/core\";\nimport {\n  SortableContext,\n  type SortingStrategy,\n  horizontalListSortingStrategy,\n  rectSortingStrategy,\n  sortableKeyboardCoordinates,\n  verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport * as React from \"react\";\n\n// Context to track if DndContext is mounted (client-side only)\nconst DndMountedContext = React.createContext(false);\n\nexport function useDndMounted() {\n  return React.useContext(DndMountedContext);\n}\n\nexport interface DndContextProps {\n  children: React.ReactNode;\n  onDragStart?: (event: DragStartEvent) => void;\n  onDragMove?: (event: DragMoveEvent) => void;\n  onDragOver?: (event: DragOverEvent) => void;\n  onDragEnd?: (event: DragEndEvent) => void;\n  onDragCancel?: () => void;\n  collisionDetection?: CollisionDetection;\n}\n\nexport function DndContext({\n  children,\n  onDragStart,\n  onDragMove,\n  onDragOver,\n  onDragEnd,\n  onDragCancel,\n  collisionDetection = closestCenter,\n}: DndContextProps) {\n  // Prevent hydration mismatch by only rendering DndKit on client\n  const [isMounted, setIsMounted] = React.useState(false);\n\n  React.useEffect(() => {\n    setIsMounted(true);\n  }, []);\n\n  const sensors = useSensors(\n    useSensor(PointerSensor, {\n      activationConstraint: {\n        distance: 8,\n      },\n    }),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates,\n    }),\n  );\n\n  // Render children with mounted context but without DndKit on server\n  if (!isMounted) {\n    return (\n      <DndMountedContext.Provider value={false}>\n        {children}\n      </DndMountedContext.Provider>\n    );\n  }\n\n  return (\n    <DndMountedContext.Provider value={true}>\n      <DndKitContext\n        sensors={sensors}\n        collisionDetection={collisionDetection}\n        onDragStart={onDragStart}\n        onDragMove={onDragMove}\n        onDragOver={onDragOver}\n        onDragEnd={onDragEnd}\n        onDragCancel={onDragCancel}\n      >\n        {children}\n      </DndKitContext>\n    </DndMountedContext.Provider>\n  );\n}\n\nexport interface SortableContainerProps {\n  children: React.ReactNode;\n  items: UniqueIdentifier[];\n  strategy?: \"vertical\" | \"horizontal\" | \"grid\";\n}\n\nexport function SortableContainer({\n  children,\n  items,\n  strategy = \"vertical\",\n}: SortableContainerProps) {\n  const isMounted = useDndMounted();\n\n  const sortingStrategy: SortingStrategy =\n    strategy === \"horizontal\"\n      ? horizontalListSortingStrategy\n      : strategy === \"grid\"\n        ? rectSortingStrategy\n        : verticalListSortingStrategy;\n\n  // Render children without SortableContext on server\n  if (!isMounted) {\n    return <>{children}</>;\n  }\n\n  return (\n    <SortableContext items={items} strategy={sortingStrategy}>\n      {children}\n    </SortableContext>\n  );\n}\n\n// Re-export utilities for convenience\nexport { arrayMove } from \"@dnd-kit/sortable\";\nexport {\n  closestCenter,\n  closestCorners,\n  rectIntersection,\n  pointerWithin,\n} from \"@dnd-kit/core\";\nexport type {\n  DragStartEvent,\n  DragEndEvent,\n  DragOverEvent,\n  DragMoveEvent,\n  UniqueIdentifier,\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/draggable.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { type UniqueIdentifier, useDraggable } from \"@dnd-kit/core\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport type * as React from \"react\";\nimport { useDndMounted } from \"./dnd-context\";\n\nexport interface DraggableProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"id\"> {\n  /** Unique identifier for this draggable item */\n  id: UniqueIdentifier;\n  /** Whether dragging is disabled */\n  disabled?: boolean;\n  /** Optional data to pass along with drag events */\n  data?: Record<string, unknown>;\n  /** Element type to render (default: div) */\n  as?: React.ElementType;\n  children: React.ReactNode;\n}\n\nfunction DraggableInner({\n  id,\n  disabled = false,\n  data,\n  children,\n  className,\n  as: Component = \"div\",\n  ...props\n}: DraggableProps) {\n  const { attributes, listeners, setNodeRef, transform, isDragging } =\n    useDraggable({\n      id,\n      disabled,\n      data,\n    });\n\n  const style: React.CSSProperties = {\n    transform: CSS.Translate.toString(transform),\n  };\n\n  return (\n    <Component\n      ref={setNodeRef}\n      style={style}\n      data-draggable-id={id}\n      data-dragging={isDragging}\n      className={cn(\n        isDragging && \"opacity-50 z-50\",\n        !disabled && \"cursor-grab\",\n        isDragging && \"cursor-grabbing\",\n        disabled && \"cursor-not-allowed opacity-60\",\n        className,\n      )}\n      {...listeners}\n      {...attributes}\n      {...props}\n    >\n      {children}\n    </Component>\n  );\n}\n\nexport function Draggable({\n  children,\n  className,\n  as: Component = \"div\",\n  id,\n  disabled,\n  data,\n  ...props\n}: DraggableProps) {\n  const isMounted = useDndMounted();\n\n  // Render static version on server\n  if (!isMounted) {\n    return (\n      <Component className={className} {...props}>\n        {children}\n      </Component>\n    );\n  }\n\n  return (\n    <DraggableInner\n      className={className}\n      as={Component}\n      id={id}\n      disabled={disabled}\n      data={data}\n      {...props}\n    >\n      {children}\n    </DraggableInner>\n  );\n}\n\nexport interface DraggableHandleProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  children: React.ReactNode;\n}\n\n/**\n * Use this component to create a drag handle within a Draggable.\n * Pass the listeners and attributes from useDraggable to this component.\n */\nexport function DraggableHandle({\n  children,\n  className,\n  ...props\n}: DraggableHandleProps) {\n  return (\n    <div\n      className={cn(\"cursor-grab active:cursor-grabbing\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\n// Re-export the hook for custom implementations\nexport { useDraggable } from \"@dnd-kit/core\";\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/droppable.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { type UniqueIdentifier, useDroppable } from \"@dnd-kit/core\";\nimport type * as React from \"react\";\nimport { useDndMounted } from \"./dnd-context\";\n\nexport interface DroppableProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"id\"> {\n  /** Unique identifier for this droppable area */\n  id: UniqueIdentifier;\n  /** Whether dropping is disabled */\n  disabled?: boolean;\n  /** Optional data to pass along with drop events */\n  data?: Record<string, unknown>;\n  /** Element type to render (default: div) */\n  as?: React.ElementType;\n  children: React.ReactNode;\n}\n\nfunction DroppableInner({\n  id,\n  disabled = false,\n  data,\n  children,\n  className,\n  as: Component = \"div\",\n  ...props\n}: DroppableProps) {\n  const { setNodeRef, isOver, active } = useDroppable({\n    id,\n    disabled,\n    data,\n  });\n\n  return (\n    <Component\n      ref={setNodeRef}\n      data-droppable-id={id}\n      data-drop-target={isOver}\n      data-has-active={!!active}\n      className={cn(\n        \"transition-all duration-200\",\n        isOver && \"ring-2 ring-primary ring-offset-2\",\n        disabled && \"opacity-60\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </Component>\n  );\n}\n\nexport function Droppable({\n  children,\n  className,\n  as: Component = \"div\",\n  id,\n  disabled,\n  data,\n  ...props\n}: DroppableProps) {\n  const isMounted = useDndMounted();\n\n  // Render static version on server\n  if (!isMounted) {\n    return (\n      <Component className={className} {...props}>\n        {children}\n      </Component>\n    );\n  }\n\n  return (\n    <DroppableInner\n      className={className}\n      as={Component}\n      id={id}\n      disabled={disabled}\n      data={data}\n      {...props}\n    >\n      {children}\n    </DroppableInner>\n  );\n}\n\n// Re-export the hook for custom implementations\nexport { useDroppable } from \"@dnd-kit/core\";\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/sortable.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { UniqueIdentifier } from \"@dnd-kit/core\";\nimport {\n  type AnimateLayoutChanges,\n  defaultAnimateLayoutChanges,\n  useSortable,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport * as React from \"react\";\nimport { useDndMounted } from \"./dnd-context\";\n\n// Context for passing sortable props to handles\ninterface SortableContextValue {\n  listeners: ReturnType<typeof useSortable>[\"listeners\"];\n  attributes: ReturnType<typeof useSortable>[\"attributes\"];\n  isDragging: boolean;\n  isMounted: boolean;\n}\n\n// Default context value for server-side rendering\nconst defaultContextValue: SortableContextValue = {\n  listeners: undefined,\n  attributes: {} as ReturnType<typeof useSortable>[\"attributes\"],\n  isDragging: false,\n  isMounted: false,\n};\n\nconst SortableItemContext =\n  React.createContext<SortableContextValue>(defaultContextValue);\n\nexport interface SortableItemProps\n  extends Omit<React.HTMLAttributes<HTMLElement>, \"id\"> {\n  /** Unique identifier for this sortable item */\n  id: UniqueIdentifier;\n  /** Whether sorting is disabled for this item */\n  disabled?: boolean;\n  /** Optional data to pass along with drag events */\n  data?: Record<string, unknown>;\n  /** Element type to render (default: div) */\n  as?: React.ElementType;\n  /** Whether to use a handle (if true, spreading listeners won't make the whole element draggable) */\n  withHandle?: boolean;\n  children: React.ReactNode;\n}\n\nconst animateLayoutChanges: AnimateLayoutChanges = (args) =>\n  defaultAnimateLayoutChanges({ ...args, wasDragging: true });\n\nfunction SortableItemInner({\n  id,\n  disabled = false,\n  data,\n  children,\n  className,\n  as: Component = \"div\",\n  withHandle = false,\n  ...props\n}: SortableItemProps) {\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    transform,\n    transition,\n    isDragging,\n    isOver,\n  } = useSortable({\n    id,\n    disabled,\n    data,\n    animateLayoutChanges,\n  });\n\n  const style: React.CSSProperties = {\n    transform: CSS.Transform.toString(transform),\n    transition,\n  };\n\n  // If using a handle, don't spread listeners on the container\n  const containerProps = withHandle ? {} : listeners;\n\n  // Context value for child handles\n  const contextValue = React.useMemo(\n    () => ({ listeners, attributes, isDragging, isMounted: true }),\n    [listeners, attributes, isDragging],\n  );\n\n  return (\n    <SortableItemContext.Provider value={contextValue}>\n      <Component\n        ref={setNodeRef}\n        style={style}\n        data-sortable-id={id}\n        data-dragging={isDragging}\n        data-over={isOver}\n        className={cn(\n          \"touch-none\",\n          isDragging && \"opacity-50 z-50\",\n          !disabled && !withHandle && \"cursor-grab\",\n          isDragging && \"cursor-grabbing\",\n          isOver && \"ring-2 ring-primary\",\n          disabled && \"cursor-not-allowed opacity-60\",\n          className,\n        )}\n        {...containerProps}\n        {...attributes}\n        {...props}\n      >\n        {children}\n      </Component>\n    </SortableItemContext.Provider>\n  );\n}\n\nexport function SortableItem({\n  children,\n  className,\n  as: Component = \"div\",\n  id,\n  disabled,\n  data,\n  withHandle,\n  ...props\n}: SortableItemProps) {\n  const isMounted = useDndMounted();\n\n  // Render static version on server with default context\n  if (!isMounted) {\n    return (\n      <SortableItemContext.Provider value={defaultContextValue}>\n        <Component className={className} {...props}>\n          {children}\n        </Component>\n      </SortableItemContext.Provider>\n    );\n  }\n\n  return (\n    <SortableItemInner\n      className={className}\n      as={Component}\n      id={id}\n      disabled={disabled}\n      data={data}\n      withHandle={withHandle}\n      {...props}\n    >\n      {children}\n    </SortableItemInner>\n  );\n}\n\n/**\n * Hook to get sortable listeners and attributes for a custom drag handle.\n * Must be used within a SortableItem with withHandle={true}.\n *\n * Example:\n * ```tsx\n * function MySortableCard({ id, content }) {\n *   return (\n *     <SortableItem id={id} withHandle>\n *       <Card>\n *         <SortableHandle>\n *           <GripVertical />\n *         </SortableHandle>\n *         <span>{content}</span>\n *       </Card>\n *     </SortableItem>\n *   );\n * }\n * ```\n */\nexport function useSortableItemContext() {\n  return React.useContext(SortableItemContext);\n}\n\nexport interface SortableHandleProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  children: React.ReactNode;\n  as?: React.ElementType;\n}\n\n/**\n * A component that serves as the drag handle within a SortableItem.\n * Must be used within a SortableItem with withHandle={true}.\n */\nexport function SortableHandle({\n  children,\n  className,\n  as: Component = \"div\",\n  ...props\n}: SortableHandleProps) {\n  const { listeners, attributes, isDragging, isMounted } =\n    useSortableItemContext();\n\n  // Render static version when not mounted\n  if (!isMounted) {\n    return (\n      <Component className={cn(\"cursor-grab touch-none\", className)} {...props}>\n        {children}\n      </Component>\n    );\n  }\n\n  return (\n    <Component\n      className={cn(\n        \"cursor-grab touch-none\",\n        isDragging && \"cursor-grabbing\",\n        className,\n      )}\n      {...listeners}\n      {...attributes}\n      {...props}\n    >\n      {children}\n    </Component>\n  );\n}\n\n// Re-export the hook and utilities for custom implementations\nexport { useSortable } from \"@dnd-kit/sortable\";\nexport { CSS } from \"@dnd-kit/utilities\";\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/drag-overlay.tsx",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  DragOverlay as DndKitDragOverlay,\n  type DragOverlayProps as DndKitDragOverlayProps,\n} from \"@dnd-kit/core\";\nimport { useDndMounted } from \"./dnd-context\";\n\nexport interface DragOverlayProps extends DndKitDragOverlayProps {\n  /** Additional class names for the overlay wrapper */\n  className?: string;\n}\n\n/**\n * DragOverlay renders a draggable element that follows the cursor.\n * It's removed from the normal document flow and positioned relative to the viewport.\n *\n * Use this when:\n * - Items need to move between containers\n * - You want a custom drag preview\n * - You need the dragged item to appear above all other elements\n *\n * Example:\n * ```tsx\n * const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);\n *\n * <DndContext onDragStart={({active}) => setActiveId(active.id)} onDragEnd={() => setActiveId(null)}>\n *   <Droppable id=\"container\">\n *     {items.map(item => <Draggable key={item.id} id={item.id}>{item.content}</Draggable>)}\n *   </Droppable>\n *   <DragOverlay>\n *     {activeId ? <div>{items.find(i => i.id === activeId)?.content}</div> : null}\n *   </DragOverlay>\n * </DndContext>\n * ```\n */\nexport function DragOverlay({\n  children,\n  className,\n  dropAnimation = {\n    duration: 250,\n    easing: \"cubic-bezier(0.18, 0.67, 0.6, 1.22)\",\n  },\n  ...props\n}: DragOverlayProps) {\n  const isMounted = useDndMounted();\n\n  // Don't render on server\n  if (!isMounted) {\n    return null;\n  }\n\n  return (\n    <DndKitDragOverlay dropAnimation={dropAnimation} {...props}>\n      {children ? (\n        <div className={cn(\"cursor-grabbing\", className)}>{children}</div>\n      ) : null}\n    </DndKitDragOverlay>\n  );\n}\n\n// Re-export for convenience\nexport { DragOverlay as DndKitDragOverlay } from \"@dnd-kit/core\";\n",
      "type": "registry:ui"
    },
    {
      "path": "src/lib/icon.tsx",
      "content": "import type { SVGProps } from \"react\";\n\ntype IconProps = SVGProps<SVGSVGElement> & {\n  path: string;\n  title?: string;\n  size?: number | string;\n  className?: string;\n  fill?: string;\n};\n\nexport function Icon({\n  path,\n  title,\n  size = 1,\n  className,\n  fill = \"currentColor\",\n  ...rest\n}: IconProps) {\n  return (\n    <svg\n      transform={`scale(${size})`}\n      viewBox=\"0 0 24 24\"\n      aria-label={title}\n      className={className}\n      {...rest}\n    >\n      {title ? <title>{title}</title> : null}\n      <path d={path} fill={fill} />\n    </svg>\n  );\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:ui"
}