{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"kanban","type":"registry:ui","title":"Kanban","description":"","dependencies":["@base-ui/react","@dnd-kit/core","@dnd-kit/sortable","@dnd-kit/utilities","cn"],"registryDependencies":[],"files":[{"path":"kanban.tsx","type":"registry:ui","content":"/* eslint-disable @typescript-eslint/no-explicit-any */\n\"use client\"\n\nimport * as React from \"react\"\nimport type { CSSProperties, ReactNode } from \"react\"\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n} from \"react\"\nimport { mergeProps } from \"@base-ui/react/merge-props\"\nimport { useRender } from \"@base-ui/react/use-render\"\nimport type {\n  CollisionDetection,\n  DragCancelEvent,\n  DragEndEvent,\n  DragOverEvent,\n  DragStartEvent,\n  DropAnimation,\n  Modifiers,\n  UniqueIdentifier,\n} from \"@dnd-kit/core\"\nimport {\n  closestCenter,\n  defaultDropAnimationSideEffects,\n  DndContext,\n  DragOverlay,\n  getFirstCollision,\n  KeyboardSensor,\n  MeasuringStrategy,\n  MouseSensor,\n  pointerWithin,\n  rectIntersection,\n  TouchSensor,\n  useSensor,\n  useSensors,\n  type DraggableAttributes,\n  type DraggableSyntheticListeners,\n} from \"@dnd-kit/core\"\nimport {\n  arrayMove,\n  defaultAnimateLayoutChanges,\n  rectSortingStrategy,\n  SortableContext,\n  sortableKeyboardCoordinates,\n  useSortable,\n  verticalListSortingStrategy,\n  type AnimateLayoutChanges,\n} from \"@dnd-kit/sortable\"\nimport { CSS } from \"@dnd-kit/utilities\"\nimport { createPortal } from \"react-dom\"\n\nimport { cn } from \"cn\"\n\ninterface KanbanContextProps<T> {\n  columns: Record<string, T[]>\n  setColumns: (columns: Record<string, T[]>) => void\n  getItemId: (item: T) => string\n  columnIds: string[]\n  activeId: UniqueIdentifier | null\n  setActiveId: (id: UniqueIdentifier | null) => void\n  findContainer: (id: UniqueIdentifier) => string | undefined\n  isColumn: (id: UniqueIdentifier) => boolean\n  modifiers?: Modifiers\n}\n\nconst KanbanContext = createContext<KanbanContextProps<any>>({\n  columns: {},\n  setColumns: () => {},\n  getItemId: () => \"\",\n  columnIds: [],\n  activeId: null,\n  setActiveId: () => {},\n  findContainer: () => undefined,\n  isColumn: () => false,\n  modifiers: undefined,\n})\n\nconst ColumnContext = createContext<{\n  attributes: DraggableAttributes\n  listeners: DraggableSyntheticListeners | undefined\n  isDragging?: boolean\n  disabled?: boolean\n}>({\n  attributes: {} as DraggableAttributes,\n  listeners: undefined,\n  isDragging: false,\n  disabled: false,\n})\n\nconst ItemContext = createContext<{\n  listeners: DraggableSyntheticListeners | undefined\n  isDragging?: boolean\n  disabled?: boolean\n}>({\n  listeners: undefined,\n  isDragging: false,\n  disabled: false,\n})\n\nconst IsOverlayContext = createContext(false)\n\nconst animateLayoutChanges: AnimateLayoutChanges = (args) =>\n  defaultAnimateLayoutChanges({ ...args, wasDragging: true })\n\nconst dropAnimationConfig: DropAnimation = {\n  sideEffects: defaultDropAnimationSideEffects({\n    styles: {\n      active: {\n        opacity: \"0.4\",\n      },\n    },\n  }),\n}\n\n/**\n * Client-mount gate for the `createPortal` call in KanbanOverlay, which needs\n * `document.body` and so must not run on the server or during hydration.\n *\n * A never-notifying subscription makes `useSyncExternalStore` return the server\n * snapshot (`false`) while rendering on the server and while hydrating, then the\n * client snapshot (`true`) once mounted - the same gate the previous\n * `useLayoutEffect(() => setMounted(true), [])` provided, minus the extra render\n * pass that `react-hooks/set-state-in-effect` flags. All three functions are\n * module-scoped so their identities stay stable; an inline `getSnapshot` is the\n * classic cause of an infinite re-subscribe loop.\n */\nconst subscribeToNothing = () => () => {}\nconst getIsMounted = () => true\nconst getIsMountedOnServer = () => false\n\nconst MOUSE_SENSOR_OPTIONS = { activationConstraint: { distance: 10 } }\nconst TOUCH_SENSOR_OPTIONS = {\n  activationConstraint: { delay: 250, tolerance: 5 },\n}\nconst KEYBOARD_SENSOR_OPTIONS = {\n  coordinateGetter: sortableKeyboardCoordinates,\n}\nconst MEASURING_CONFIG = {\n  droppable: { strategy: MeasuringStrategy.Always },\n}\n\nexport interface KanbanMoveEvent {\n  event: DragEndEvent\n  activeContainer: string\n  activeIndex: number\n  overContainer: string\n  overIndex: number\n}\n\nexport interface KanbanCommitMeta<T> {\n  kind: \"item\" | \"column\"\n  event: DragEndEvent\n  activeContainer: string\n  activeIndex: number\n  overContainer: string\n  overIndex: number\n  previousValue: Record<string, T[]>\n}\n\nexport interface KanbanRootProps<T> extends Omit<\n  useRender.ComponentProps<\"div\">,\n  \"children\" | \"onDragStart\" | \"onDragEnd\"\n> {\n  value: Record<string, T[]>\n  onValueChange: (value: Record<string, T[]>) => void\n  getItemValue: (item: T) => string\n  children: ReactNode\n  onMove?: (event: KanbanMoveEvent) => void\n  onValueCommit?: (\n    value: Record<string, T[]>,\n    meta: KanbanCommitMeta<T>\n  ) => void\n  restoreOnCancel?: boolean\n  onDragStart?: (event: DragStartEvent) => void\n  onDragEnd?: (event: DragEndEvent) => void\n  onDragCancel?: (event: DragCancelEvent) => void\n  accessibility?: React.ComponentProps<typeof DndContext>[\"accessibility\"]\n  modifiers?: Modifiers\n}\n\nfunction Kanban<T>({\n  value,\n  onValueChange,\n  getItemValue,\n  children,\n  className,\n  render,\n  onMove,\n  onValueCommit,\n  restoreOnCancel = false,\n  onDragStart,\n  onDragEnd,\n  onDragCancel,\n  accessibility,\n  modifiers,\n  ...props\n}: KanbanRootProps<T>) {\n  const columns = value\n  const setColumns = onValueChange\n  const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null)\n\n  // Always-current mirrors so the drag handlers can read fresh values without\n  // widening their dependency arrays (keeps handler identity stable). The\n  // handlers only fire after commit, so syncing the mirrors in an effect is\n  // safe. Assigning to a ref during render breaks under concurrent rendering.\n  const valueRef = useRef(value)\n  const getItemValueRef = useRef(getItemValue)\n  useLayoutEffect(() => {\n    valueRef.current = value\n    getItemValueRef.current = getItemValue\n  })\n  const dragOriginRef = useRef<{\n    value: Record<string, T[]>\n    container: string | undefined\n    index: number\n  } | null>(null)\n\n  const sensors = useSensors(\n    useSensor(MouseSensor, MOUSE_SENSOR_OPTIONS),\n    useSensor(TouchSensor, TOUCH_SENSOR_OPTIONS),\n    useSensor(KeyboardSensor, KEYBOARD_SENSOR_OPTIONS)\n  )\n\n  const columnIds = useMemo(() => {\n    const keys = Object.keys(columns)\n    if (process.env.NODE_ENV !== \"production\") {\n      const seen = new Set<string>()\n      for (const key of keys) {\n        for (const item of columns[key]) {\n          const itemId = getItemValue(item)\n          if (seen.has(itemId)) {\n            console.warn(\n              `[Kanban] Duplicate item id \"${itemId}\". Item ids must be unique across all columns, or drag and drop will misbehave.`\n            )\n            break\n          }\n          seen.add(itemId)\n        }\n      }\n    }\n    return keys\n  }, [columns, getItemValue])\n\n  const isColumn = useCallback(\n    (id: UniqueIdentifier) => columnIds.includes(id as string),\n    [columnIds]\n  )\n\n  const findContainer = useCallback(\n    (id: UniqueIdentifier) => {\n      if (isColumn(id)) return id as string\n      return columnIds.find((key) =>\n        columns[key].some((item) => getItemValue(item) === id)\n      )\n    },\n    [columns, columnIds, getItemValue, isColumn]\n  )\n\n  // The droppable under the pointer, not the one the dragged rect overlaps\n  // most: in the gap between columns that overlap flips with every live-preview\n  // move, and each flip re-runs dragOver until React bails out.\n  const lastOverIdRef = useRef<UniqueIdentifier | null>(null)\n  const collisionDetection = useCallback<CollisionDetection>(\n    (args) => {\n      if (isColumn(args.active.id)) {\n        return closestCenter({\n          ...args,\n          droppableContainers: args.droppableContainers.filter((container) =>\n            isColumn(container.id)\n          ),\n        })\n      }\n\n      // Keyboard drags carry no pointer.\n      if (!args.pointerCoordinates) return rectIntersection(args)\n\n      let overId = getFirstCollision(pointerWithin(args), \"id\")\n      if (overId != null) {\n        // Over a column's empty space: resolve to its closest item, if any.\n        if (isColumn(overId)) {\n          const itemIds = new Set(columns[overId as string].map(getItemValue))\n          overId =\n            closestCenter({\n              ...args,\n              droppableContainers: args.droppableContainers.filter(\n                (container) => itemIds.has(container.id as string)\n              ),\n            })[0]?.id ?? overId\n        }\n        lastOverIdRef.current = overId\n        return [{ id: overId }]\n      }\n\n      // Between droppables: hold the last target so the preview stays put.\n      return lastOverIdRef.current != null\n        ? [{ id: lastOverIdRef.current }]\n        : rectIntersection(args)\n    },\n    [columns, getItemValue, isColumn]\n  )\n\n  const commitChange = useCallback(\n    (\n      finalValue: Record<string, T[]>,\n      event: DragEndEvent,\n      kind: \"item\" | \"column\"\n    ) => {\n      if (!onValueCommit) return\n      const origin = dragOriginRef.current\n      if (!origin) return\n\n      const id = event.active.id\n\n      if (kind === \"column\") {\n        const keys = Object.keys(finalValue)\n        const overIndex = keys.indexOf(id as string)\n        if (overIndex === -1 || overIndex === origin.index) return\n        onValueCommit(finalValue, {\n          kind: \"column\",\n          event,\n          activeContainer: id as string,\n          activeIndex: origin.index,\n          overContainer: String(event.over?.id ?? id),\n          overIndex,\n          previousValue: origin.value,\n        })\n        return\n      }\n\n      const getId = getItemValueRef.current\n      let overContainer: string | undefined\n      let overIndex = -1\n      for (const key of Object.keys(finalValue)) {\n        const found = finalValue[key].findIndex((item) => getId(item) === id)\n        if (found !== -1) {\n          overContainer = key\n          overIndex = found\n          break\n        }\n      }\n      if (overContainer === undefined) return\n      if (overContainer === origin.container && overIndex === origin.index) {\n        return\n      }\n      onValueCommit(finalValue, {\n        kind: \"item\",\n        event,\n        activeContainer: origin.container ?? overContainer,\n        activeIndex: origin.index,\n        overContainer,\n        overIndex,\n        previousValue: origin.value,\n      })\n    },\n    [onValueCommit]\n  )\n\n  const handleDragStart = useCallback(\n    (event: DragStartEvent) => {\n      lastOverIdRef.current = null\n      setActiveId(event.active.id)\n      onDragStart?.(event)\n\n      if (onValueCommit || restoreOnCancel) {\n        const snapshot = valueRef.current\n        const id = event.active.id\n        const keys = Object.keys(snapshot)\n        if (keys.includes(id as string)) {\n          dragOriginRef.current = {\n            value: snapshot,\n            container: id as string,\n            index: keys.indexOf(id as string),\n          }\n        } else {\n          const getId = getItemValueRef.current\n          let container: string | undefined\n          let index = -1\n          for (const key of keys) {\n            const found = snapshot[key].findIndex((item) => getId(item) === id)\n            if (found !== -1) {\n              container = key\n              index = found\n              break\n            }\n          }\n          dragOriginRef.current = { value: snapshot, container, index }\n        }\n      }\n    },\n    [onDragStart, onValueCommit, restoreOnCancel]\n  )\n\n  const handleDragOver = useCallback(\n    (event: DragOverEvent) => {\n      if (onMove) {\n        return\n      }\n\n      const { active, over } = event\n      if (!over) return\n\n      if (isColumn(active.id)) return\n\n      const activeContainer = findContainer(active.id)\n      const overContainer = findContainer(over.id)\n\n      if (!activeContainer || !overContainer) {\n        return\n      }\n\n      if (activeContainer !== overContainer) {\n        const activeItems = columns[activeContainer]\n        const overItems = columns[overContainer]\n\n        const activeIndex = activeItems.findIndex(\n          (item: T) => getItemValue(item) === active.id\n        )\n        let overIndex = overItems.findIndex(\n          (item: T) => getItemValue(item) === over.id\n        )\n\n        // If dropping on the column itself, not an item\n        if (isColumn(over.id)) {\n          overIndex = overItems.length\n        }\n\n        const newActiveItems = [...activeItems]\n        const newOverItems = [...overItems]\n        const [movedItem] = newActiveItems.splice(activeIndex, 1)\n        newOverItems.splice(overIndex, 0, movedItem)\n\n        setColumns({\n          ...columns,\n          [activeContainer]: newActiveItems,\n          [overContainer]: newOverItems,\n        })\n      } else {\n        const container = activeContainer\n        const activeIndex = columns[container].findIndex(\n          (item: T) => getItemValue(item) === active.id\n        )\n        const overIndex = columns[container].findIndex(\n          (item: T) => getItemValue(item) === over.id\n        )\n\n        if (activeIndex !== overIndex) {\n          setColumns({\n            ...columns,\n            [container]: arrayMove(columns[container], activeIndex, overIndex),\n          })\n        }\n      }\n    },\n    [findContainer, getItemValue, isColumn, setColumns, columns, onMove]\n  )\n\n  const handleDragCancel = useCallback(\n    (event: DragCancelEvent) => {\n      const origin = dragOriginRef.current\n\n      if (restoreOnCancel && origin && !onMove) {\n        // Escape/cancel: undo the live-preview reshuffle applied during dragOver.\n        setColumns(origin.value)\n      } else if (onValueCommit && origin && !onMove) {\n        // No restore requested: the live preview stays visible, so commit it.\n        commitChange(valueRef.current, event, \"item\")\n      }\n\n      dragOriginRef.current = null\n      lastOverIdRef.current = null\n      setActiveId(null)\n      onDragCancel?.(event)\n    },\n    [\n      restoreOnCancel,\n      onMove,\n      onValueCommit,\n      setColumns,\n      onDragCancel,\n      commitChange,\n    ]\n  )\n\n  const handleDragEnd = useCallback(\n    (event: DragEndEvent) => {\n      const { active, over } = event\n      lastOverIdRef.current = null\n      setActiveId(null)\n      onDragEnd?.(event)\n\n      if (!over) {\n        // Released over nothing. In default mode the live preview during\n        // dragOver may have already moved the item, so commit the current value.\n        commitChange(valueRef.current, event, \"item\")\n        dragOriginRef.current = null\n        return\n      }\n\n      // Handle item move callback\n      if (onMove && !isColumn(active.id)) {\n        const activeContainer = findContainer(active.id)\n        const overContainer = findContainer(over.id)\n\n        if (activeContainer && overContainer) {\n          const activeIndex = columns[activeContainer].findIndex(\n            (item: T) => getItemValue(item) === active.id\n          )\n          const overIndex = isColumn(over.id)\n            ? columns[overContainer].length\n            : columns[overContainer].findIndex(\n                (item: T) => getItemValue(item) === over.id\n              )\n\n          onMove({\n            event,\n            activeContainer,\n            activeIndex,\n            overContainer,\n            overIndex,\n          })\n        }\n        // In onMove mode the consumer owns applying the item move, so do not\n        // fire onValueCommit for item moves; column reorders still commit below.\n        dragOriginRef.current = null\n        return\n      }\n\n      // Handle column reordering\n      if (isColumn(active.id) && isColumn(over.id)) {\n        const activeIndex = columnIds.indexOf(active.id as string)\n        const overIndex = columnIds.indexOf(over.id as string)\n        if (activeIndex !== overIndex) {\n          const newOrder = arrayMove(\n            Object.keys(columns),\n            activeIndex,\n            overIndex\n          )\n          const newColumns: Record<string, T[]> = {}\n          newOrder.forEach((key) => {\n            newColumns[key] = columns[key]\n          })\n          setColumns(newColumns)\n          commitChange(newColumns, event, \"column\")\n        }\n        dragOriginRef.current = null\n        return\n      }\n\n      // A column drag that ends over a non-column droppable is not an item move.\n      if (isColumn(active.id)) {\n        dragOriginRef.current = null\n        return\n      }\n\n      const activeContainer = findContainer(active.id)\n      const overContainer = findContainer(over.id)\n\n      // Handle item reordering within the same column\n      if (\n        activeContainer &&\n        overContainer &&\n        activeContainer === overContainer\n      ) {\n        const container = activeContainer\n        const activeIndex = columns[container].findIndex(\n          (item: T) => getItemValue(item) === active.id\n        )\n        const overIndex = columns[container].findIndex(\n          (item: T) => getItemValue(item) === over.id\n        )\n\n        if (activeIndex !== overIndex) {\n          const newColumns = {\n            ...columns,\n            [container]: arrayMove(columns[container], activeIndex, overIndex),\n          }\n          setColumns(newColumns)\n          commitChange(newColumns, event, \"item\")\n        } else {\n          // Cross-column moves are applied during dragOver, so the current\n          // value is already final.\n          commitChange(columns, event, \"item\")\n        }\n      } else {\n        commitChange(columns, event, \"item\")\n      }\n      dragOriginRef.current = null\n    },\n    [\n      columnIds,\n      columns,\n      findContainer,\n      getItemValue,\n      isColumn,\n      setColumns,\n      onMove,\n      onDragEnd,\n      commitChange,\n    ]\n  )\n\n  const contextValue = useMemo(\n    () => ({\n      columns,\n      setColumns,\n      getItemId: getItemValue,\n      columnIds,\n      activeId,\n      setActiveId,\n      findContainer,\n      isColumn,\n      modifiers,\n    }),\n    [\n      columns,\n      setColumns,\n      getItemValue,\n      columnIds,\n      activeId,\n      findContainer,\n      isColumn,\n      modifiers,\n    ]\n  )\n\n  const defaultProps = {\n    \"data-slot\": \"kanban\",\n    \"data-dragging\": activeId !== null,\n    className: cn(activeId !== null && \"cursor-grabbing!\", className),\n    children,\n  }\n\n  return (\n    <KanbanContext.Provider value={contextValue}>\n      <DndContext\n        sensors={sensors}\n        modifiers={modifiers}\n        accessibility={accessibility}\n        measuring={MEASURING_CONFIG}\n        collisionDetection={collisionDetection}\n        onDragStart={handleDragStart}\n        onDragOver={handleDragOver}\n        onDragEnd={handleDragEnd}\n        onDragCancel={handleDragCancel}\n      >\n        {useRender({\n          defaultTagName: \"div\",\n          render,\n          props: mergeProps<\"div\">(defaultProps, props),\n        })}\n      </DndContext>\n    </KanbanContext.Provider>\n  )\n}\n\nexport type KanbanBoardProps = useRender.ComponentProps<\"div\">\n\nfunction KanbanBoard({ className, render, ...props }: KanbanBoardProps) {\n  const { columnIds } = useContext(KanbanContext)\n\n  const defaultProps = {\n    \"data-slot\": \"kanban-board\",\n    className: cn(\"grid auto-rows-fr gap-4 sm:grid-cols-3\", className),\n    children: props.children,\n  }\n\n  return (\n    <SortableContext items={columnIds} strategy={rectSortingStrategy}>\n      {useRender({\n        defaultTagName: \"div\",\n        render,\n        props: mergeProps<\"div\">(defaultProps, props),\n      })}\n    </SortableContext>\n  )\n}\n\nexport interface KanbanColumnProps extends useRender.ComponentProps<\"div\"> {\n  value: string\n  disabled?: boolean\n}\n\nfunction KanbanColumn({\n  value,\n  className,\n  render,\n  disabled,\n  ...props\n}: KanbanColumnProps) {\n  const isOverlay = useContext(IsOverlayContext)\n\n  const {\n    setNodeRef,\n    transform,\n    transition,\n    attributes,\n    listeners,\n    isDragging: isSortableDragging,\n  } = useSortable({\n    id: value,\n    disabled: disabled || isOverlay,\n    animateLayoutChanges,\n  })\n\n  // Hooks must run unconditionally; the derived value below is used only in the non-overlay branch.\n  const { activeId, isColumn } = useContext(KanbanContext)\n  const isColumnDragging = activeId ? isColumn(activeId) : false\n\n  const style = {\n    transition,\n    transform: CSS.Transform.toString(transform),\n  } as CSSProperties\n\n  const defaultProps = isOverlay\n    ? {\n        \"data-slot\": \"kanban-column\",\n        \"data-value\": value,\n        \"data-dragging\": true,\n        className: cn(\"group/kanban-column flex flex-col\", className),\n        children: props.children,\n      }\n    : {\n        \"data-slot\": \"kanban-column\",\n        \"data-value\": value,\n        \"data-dragging\": isSortableDragging,\n        \"data-disabled\": disabled,\n        ref: setNodeRef,\n        style,\n        className: cn(\n          \"group/kanban-column flex flex-col\",\n          isSortableDragging && \"opacity-50 z-50\",\n          disabled && \"opacity-50\",\n          className\n        ),\n        children: props.children,\n      }\n\n  return (\n    <ColumnContext.Provider\n      value={\n        isOverlay\n          ? {\n              attributes: {} as DraggableAttributes,\n              listeners: undefined,\n              isDragging: true,\n              disabled: false,\n            }\n          : { attributes, listeners, isDragging: isColumnDragging, disabled }\n      }\n    >\n      {useRender({\n        defaultTagName: \"div\",\n        render,\n        props: mergeProps<\"div\">(defaultProps, props),\n      })}\n    </ColumnContext.Provider>\n  )\n}\n\nexport interface KanbanColumnHandleProps extends useRender.ComponentProps<\"div\"> {\n  cursor?: boolean\n}\n\nfunction KanbanColumnHandle({\n  className,\n  render,\n  cursor = true,\n  ...props\n}: KanbanColumnHandleProps) {\n  const { attributes, listeners, isDragging, disabled } =\n    useContext(ColumnContext)\n\n  const defaultProps = {\n    \"data-slot\": \"kanban-column-handle\",\n    \"data-dragging\": isDragging,\n    \"data-disabled\": disabled,\n    ...attributes,\n    ...listeners,\n    className: cn(\n      /* Keyboard focus shows it too. focus-within would also match the focus\n         a mousedown gives the handle, so it would stay shown after a drag. */\n      \"opacity-0 transition-opacity group-hover/kanban-column:opacity-100 focus-visible:opacity-100 has-focus-visible:opacity-100\",\n      cursor && (isDragging ? \"cursor-grabbing!\" : \"cursor-grab!\"),\n      className\n    ),\n    children: props.children,\n  }\n\n  return useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  })\n}\n\nexport interface KanbanItemProps extends useRender.ComponentProps<\"div\"> {\n  value: string\n  disabled?: boolean\n}\n\nfunction KanbanItem({\n  value,\n  className,\n  render,\n  disabled,\n  ...props\n}: KanbanItemProps) {\n  const isOverlay = useContext(IsOverlayContext)\n\n  const {\n    setNodeRef,\n    transform,\n    transition,\n    attributes,\n    listeners,\n    isDragging: isSortableDragging,\n  } = useSortable({\n    id: value,\n    disabled: disabled || isOverlay,\n    animateLayoutChanges,\n  })\n\n  // Hooks must run unconditionally; the derived value below is used only in the non-overlay branch.\n  const { activeId, isColumn } = useContext(KanbanContext)\n  const isItemDragging = activeId ? !isColumn(activeId) : false\n\n  const style = {\n    transition,\n    transform: CSS.Transform.toString(transform),\n  } as CSSProperties\n\n  const defaultProps = isOverlay\n    ? {\n        \"data-slot\": \"kanban-item\",\n        \"data-value\": value,\n        \"data-dragging\": true,\n        className: cn(className),\n        children: props.children,\n      }\n    : {\n        \"data-slot\": \"kanban-item\",\n        \"data-value\": value,\n        \"data-dragging\": isSortableDragging,\n        \"data-disabled\": disabled,\n        ref: setNodeRef,\n        style,\n        ...attributes,\n        className: cn(\n          isSortableDragging && \"opacity-50 z-50\",\n          disabled && \"opacity-50\",\n          className\n        ),\n        children: props.children,\n      }\n\n  return (\n    <ItemContext.Provider\n      value={\n        isOverlay\n          ? { listeners: undefined, isDragging: true, disabled: false }\n          : { listeners, isDragging: isItemDragging, disabled }\n      }\n    >\n      {useRender({\n        defaultTagName: \"div\",\n        render,\n        props: mergeProps<\"div\">(defaultProps, props),\n      })}\n    </ItemContext.Provider>\n  )\n}\n\nexport interface KanbanItemHandleProps extends useRender.ComponentProps<\"div\"> {\n  cursor?: boolean\n}\n\nfunction KanbanItemHandle({\n  className,\n  render,\n  cursor = true,\n  ...props\n}: KanbanItemHandleProps) {\n  const { listeners, isDragging, disabled } = useContext(ItemContext)\n\n  const defaultProps = {\n    \"data-slot\": \"kanban-item-handle\",\n    \"data-dragging\": isDragging,\n    \"data-disabled\": disabled,\n    ...listeners,\n    className: cn(\n      cursor && (isDragging ? \"cursor-grabbing!\" : \"cursor-grab!\"),\n      className\n    ),\n    children: props.children,\n  }\n\n  return useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  })\n}\n\nexport interface KanbanColumnContentProps extends useRender.ComponentProps<\"div\"> {\n  value: string\n}\n\nfunction KanbanColumnContent({\n  value,\n  className,\n  render,\n  ...props\n}: KanbanColumnContentProps) {\n  const { columns, getItemId } = useContext(KanbanContext)\n\n  const itemIds = useMemo(() => {\n    const items = columns[value]\n    if (!items) {\n      throw new Error(\n        `KanbanColumnContent: column \"${value}\" was not found in the Kanban value. ` +\n          `Available columns: ${Object.keys(columns).join(\", \") || \"(none)\"}.`\n      )\n    }\n    return items.map(getItemId)\n  }, [columns, getItemId, value])\n\n  const defaultProps = {\n    \"data-slot\": \"kanban-column-content\",\n    className: cn(\"flex flex-col gap-2\", className),\n    children: props.children,\n  }\n\n  return (\n    <SortableContext items={itemIds} strategy={verticalListSortingStrategy}>\n      {useRender({\n        defaultTagName: \"div\",\n        render,\n        props: mergeProps<\"div\">(defaultProps, props),\n      })}\n    </SortableContext>\n  )\n}\n\nexport interface KanbanOverlayProps extends Omit<\n  React.ComponentProps<typeof DragOverlay>,\n  \"children\"\n> {\n  children?:\n    | ReactNode\n    | ((params: {\n        value: UniqueIdentifier\n        variant: \"column\" | \"item\"\n      }) => ReactNode)\n}\n\nfunction KanbanOverlay({ children, className, ...props }: KanbanOverlayProps) {\n  const { activeId, isColumn, modifiers } = useContext(KanbanContext)\n  const mounted = useSyncExternalStore(\n    subscribeToNothing,\n    getIsMounted,\n    getIsMountedOnServer\n  )\n\n  const variant = activeId ? (isColumn(activeId) ? \"column\" : \"item\") : \"item\"\n\n  const content =\n    activeId && children\n      ? typeof children === \"function\"\n        ? children({ value: activeId, variant })\n        : children\n      : null\n\n  if (!mounted) return null\n\n  return createPortal(\n    <DragOverlay\n      dropAnimation={dropAnimationConfig}\n      modifiers={modifiers}\n      className={cn(\"z-50\", activeId && \"cursor-grabbing\", className)}\n      {...props}\n    >\n      <IsOverlayContext.Provider value={true}>\n        {content}\n      </IsOverlayContext.Provider>\n    </DragOverlay>,\n    document.body\n  )\n}\n\nexport {\n  Kanban,\n  KanbanBoard,\n  KanbanColumn,\n  KanbanColumnHandle,\n  KanbanItem,\n  KanbanItemHandle,\n  KanbanColumnContent,\n  KanbanOverlay,\n}","target":"components/reui/kanban.tsx"}]}