{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "board",
  "title": "Board — compositional kanban / sortable-list primitive",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/board/board.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport {\n  BoardCardContext,\n  BoardContext,\n  BoardLaneContext,\n  useBoardContext,\n  useBoardLaneContext,\n  type BoardAcceptsFn,\n  type BoardContextValue,\n  type BoardDensity,\n  type BoardOrientation,\n} from './context'\nimport { boardCardVariants, boardLaneVariants } from './board.variants'\n\n/* ------------------------------------------------------------------ */\n/* Board (Root)                                                        */\n/* ------------------------------------------------------------------ */\n\nconst ALWAYS_ACCEPT: BoardAcceptsFn = () => true\nconst NOOP = () => {}\n\nexport interface BoardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {\n  orientation?: BoardOrientation\n  density?: BoardDensity\n  /** Animation preset class applied by BoardLaneBody. Defaults to `motion-list`. */\n  motion?: string\n  /** Predicate run per drop. Defaults to always-accept. */\n  accepts?: BoardAcceptsFn\n  /** Imperative move from a parent's useBoard hook. */\n  moveItem?: (itemId: string | string[], toLaneId: string, toIndex?: number) => void\n  /** External state — pass `state.draggingId` etc. from useBoard. */\n  draggingId?: string | null\n  draggingIds?: readonly string[]\n  dragOverLaneId?: string | null\n  justMovedId?: string | null\n  selectedIds?: ReadonlySet<string>\n  /** Optional toggle/clearSelection from useBoard so the primitive can\n   *  expose selection mutations through context (consumed by BoardCard\n   *  click handler). Both default to no-ops, so Board still works with\n   *  consumers that don't wire selection. */\n  toggleSelection?: (itemId: string, additive?: boolean) => void\n  clearSelection?: () => void\n  registerAllowedLanes?: (cardId: string, lanes: readonly string[] | undefined) => void\n  unregisterAllowedLanes?: (cardId: string) => void\n  registerLaneDisabled?: (laneId: string, disabled: boolean) => void\n  unregisterLaneDisabled?: (laneId: string) => void\n  isLaneAcceptingFor?: (laneId: string) => boolean\n  children?: React.ReactNode\n}\n\nconst EMPTY_IDS: readonly string[] = []\nconst EMPTY_SELECTION: ReadonlySet<string> = new Set<string>()\n\nconst Board = React.forwardRef<HTMLDivElement, BoardProps>(\n  (\n    {\n      className,\n      orientation = 'horizontal',\n      density = 'default',\n      motion = 'motion-list',\n      accepts = ALWAYS_ACCEPT,\n      moveItem = NOOP,\n      draggingId = null,\n      draggingIds = EMPTY_IDS,\n      dragOverLaneId = null,\n      justMovedId = null,\n      selectedIds = EMPTY_SELECTION,\n      toggleSelection = NOOP,\n      clearSelection = NOOP,\n      registerAllowedLanes = NOOP,\n      unregisterAllowedLanes = NOOP,\n      registerLaneDisabled = NOOP,\n      unregisterLaneDisabled = NOOP,\n      isLaneAcceptingFor = () => true,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const ctx = React.useMemo<BoardContextValue>(\n      () => ({\n        orientation,\n        density,\n        motion,\n        draggingId,\n        draggingIds,\n        dragOverLaneId,\n        justMovedId,\n        selectedIds,\n        accepts,\n        moveItem,\n        toggleSelection,\n        clearSelection,\n        registerAllowedLanes,\n        unregisterAllowedLanes,\n        registerLaneDisabled,\n        unregisterLaneDisabled,\n        isLaneAcceptingFor,\n      }),\n      [\n        orientation,\n        density,\n        motion,\n        draggingId,\n        draggingIds,\n        dragOverLaneId,\n        justMovedId,\n        selectedIds,\n        accepts,\n        moveItem,\n        toggleSelection,\n        clearSelection,\n        registerAllowedLanes,\n        unregisterAllowedLanes,\n        registerLaneDisabled,\n        unregisterLaneDisabled,\n        isLaneAcceptingFor,\n      ],\n    )\n\n    return (\n      <BoardContext.Provider value={ctx}>\n        <div\n          ref={ref}\n          data-uipkge=\"\"\n          data-slot=\"board\"\n          data-orientation={orientation}\n          className={cn('w-full', className)}\n          {...props}\n        >\n          {children}\n        </div>\n      </BoardContext.Provider>\n    )\n  },\n)\nBoard.displayName = 'Board'\n\n/* ------------------------------------------------------------------ */\n/* BoardLane                                                           */\n/* ------------------------------------------------------------------ */\n\nexport interface BoardLaneRenderProps {\n  isDragOver: boolean\n  isAccepting: boolean\n  disabled: boolean\n}\n\nexport interface BoardLaneProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'id'> {\n  id: string\n  tone?: 'default' | 'plain'\n  /** Disable drops on this lane. Cards inside still render and stay\n   *  draggable; only the drop target is inert + visually dimmed. */\n  disabled?: boolean\n  onLaneDragOver?: (e: React.DragEvent<HTMLDivElement>) => void\n  onLaneDrop?: (e: React.DragEvent<HTMLDivElement>) => void\n  onLaneDragLeave?: () => void\n  children?: React.ReactNode | ((props: BoardLaneRenderProps) => React.ReactNode)\n}\n\nconst BoardLane = React.forwardRef<HTMLDivElement, BoardLaneProps>(\n  (\n    {\n      id,\n      className,\n      tone = 'default',\n      disabled = false,\n      onLaneDragOver,\n      onLaneDrop,\n      onLaneDragLeave,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const board = useBoardContext()\n\n    // Register the lane's disabled flag; keep it in sync when id/disabled change.\n    React.useEffect(() => {\n      board.registerLaneDisabled(id, disabled)\n      return () => board.unregisterLaneDisabled(id)\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [id, disabled])\n\n    const isDragOver = board.dragOverLaneId === id\n    const isAccepting = (() => {\n      if (!board.draggingId) return false\n      if (disabled) return false\n      return board.isLaneAcceptingFor(id)\n    })()\n\n    const laneCtx = React.useMemo(\n      () => ({ laneId: id, isDragOver, isAccepting, disabled }),\n      [id, isDragOver, isAccepting, disabled],\n    )\n\n    const state: 'idle' | 'over' | 'rejecting' = !isDragOver ? 'idle' : isAccepting ? 'over' : 'rejecting'\n\n    // Emit unconditionally so the hook can set dragOverLaneId\n    // (drives the rejecting-ring visual). The hook's isAllowed\n    // checks the disabled-lane registry and refuses preventDefault when\n    // it should — the browser's own refuse-to-drop semantics + our\n    // rejecting visual cover the rest.\n    function handleDragOver(e: React.DragEvent<HTMLDivElement>) {\n      onLaneDragOver?.(e)\n    }\n    function handleDrop(e: React.DragEvent<HTMLDivElement>) {\n      if (disabled) {\n        e.preventDefault()\n        return\n      }\n      onLaneDrop?.(e)\n    }\n    function handleDragLeave() {\n      onLaneDragLeave?.()\n    }\n\n    return (\n      <BoardLaneContext.Provider value={laneCtx}>\n        <div\n          ref={ref}\n          data-uipkge=\"\"\n          data-slot=\"board-lane\"\n          data-board-lane=\"\"\n          data-lane-id={id}\n          data-state={state}\n          data-disabled={disabled || undefined}\n          aria-disabled={disabled || undefined}\n          className={cn(\n            boardLaneVariants({ tone, state }),\n            // Disabled lane dims only the lane chrome (border, background,\n            // header). Cards inside stay legible — the rejection signal is\n            // carried by the no-drop cursor + the missing accept-ring.\n            disabled && 'opacity-80 [&>[data-slot=board-lane-header]]:opacity-60',\n            className,\n          )}\n          onDragOver={handleDragOver}\n          onDrop={handleDrop}\n          onDragLeave={handleDragLeave}\n          {...props}\n        >\n          {typeof children === 'function' ? children({ isDragOver, isAccepting, disabled }) : children}\n        </div>\n      </BoardLaneContext.Provider>\n    )\n  },\n)\nBoardLane.displayName = 'BoardLane'\n\n/* ------------------------------------------------------------------ */\n/* BoardLaneHeader                                                     */\n/* ------------------------------------------------------------------ */\n\nconst BoardLaneHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      data-uipkge=\"\"\n      data-slot=\"board-lane-header\"\n      className={cn('flex items-center justify-between gap-2', className)}\n      {...props}\n    />\n  ),\n)\nBoardLaneHeader.displayName = 'BoardLaneHeader'\n\n/* ------------------------------------------------------------------ */\n/* BoardLaneBody                                                       */\n/* ------------------------------------------------------------------ */\n\nexport interface BoardLaneBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Override the animation preset class. Defaults to the board-level motion preset. */\n  motion?: string\n}\n\nconst BoardLaneBody = React.forwardRef<HTMLDivElement, BoardLaneBodyProps>(\n  ({ className, motion, children, ...props }, ref) => {\n    const board = React.useContext(BoardContext)\n    const motionName = motion ?? board?.motion ?? 'motion-list'\n    return (\n      // Inner padding (py-1 / px-0.5) reserves breathing room for the\n      // per-card hover-lift (-translate-y-0.5), the focus / drag / moved\n      // rings (ring-2 + ring-offset-1 ≈ 3px outward), and the hover\n      // shadow halo. Without it, the first / last cards' hover state\n      // crops against the overflow-y-auto edge. pr-1 still wins on the\n      // right so the thin scrollbar has a gutter.\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"board-lane-body\"\n        className={cn(\n          'flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto px-0.5 py-1 pr-1 [scrollbar-width:thin]',\n          className,\n        )}\n        {...props}\n      >\n        {/* The Vue source wraps cards in a TransitionGroup for enter/leave/move\n            animations (the `motion-list` preset). React has no built-in\n            equivalent — the per-card transitions live on the card variants\n            (transform/box-shadow/opacity). The motion preset class is still\n            applied so consumers can target it with their own keyframes. */}\n        <div className={cn('relative flex flex-col gap-2', motionName)}>{children}</div>\n      </div>\n    )\n  },\n)\nBoardLaneBody.displayName = 'BoardLaneBody'\n\n/* ------------------------------------------------------------------ */\n/* BoardLaneEmpty                                                      */\n/* ------------------------------------------------------------------ */\n\nexport interface BoardLaneEmptyProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Show only when this is true (consumer wires from `lane.length === 0`). */\n  when?: boolean\n}\n\nconst BoardLaneEmpty = React.forwardRef<HTMLDivElement, BoardLaneEmptyProps>(\n  ({ className, when, children, ...props }, ref) => {\n    if (when === false) return null\n    return (\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"board-lane-empty\"\n        className={cn(\n          'text-muted-foreground/70 border-border/60 rounded-lg border border-dashed py-6 text-center text-xs',\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    )\n  },\n)\nBoardLaneEmpty.displayName = 'BoardLaneEmpty'\n\n/* ------------------------------------------------------------------ */\n/* BoardCard                                                           */\n/* ------------------------------------------------------------------ */\n\nexport interface BoardCardProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, 'id' | 'onDragStart' | 'onDragEnd' | 'onClick'> {\n  id: string\n  /** Disable keyboard grab (e.g. for read-only boards). Default: enabled. */\n  keyboardDraggable?: boolean\n  /** Disable the card entirely — non-draggable, non-clickable, dimmed.\n   *  Use for cards locked by a workflow rule or a server policy. */\n  disabled?: boolean\n  /** Per-card allow-list. When set, the card can only be dropped into\n   *  these lane ids; useBoard rejects any other target silently. Omit\n   *  to allow every lane the global `accepts` predicate permits. */\n  allowedLanes?: readonly string[]\n  /** Show the click-to-select chrome (ring + cmd/shift-click multi-select).\n   *  Default true. Turn off for read-only or single-tap-to-open boards. */\n  selectable?: boolean\n  onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void\n  onDragEnd?: () => void\n  onClick?: (e: React.MouseEvent<HTMLDivElement>) => void\n}\n\nconst BoardCard = React.forwardRef<HTMLDivElement, BoardCardProps>(\n  (\n    {\n      id,\n      className,\n      keyboardDraggable = true,\n      disabled = false,\n      allowedLanes,\n      selectable = true,\n      onDragStart,\n      onDragEnd,\n      onClick,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const board = useBoardContext()\n    const lane = useBoardLaneContext()\n\n    const [isKeyboardGrabbed, setIsKeyboardGrabbed] = React.useState(false)\n\n    const cardElRef = React.useRef<HTMLDivElement | null>(null)\n    const setRefs = React.useCallback(\n      (node: HTMLDivElement | null) => {\n        cardElRef.current = node\n        if (typeof ref === 'function') ref(node)\n        else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n      },\n      [ref],\n    )\n\n    // Either source of truth wins — `draggingIds` is the multi-select-aware\n    // list, `draggingId` is the single-anchor (backward-compat for consumers\n    // that don't pass the multi state through). Keyboard-grabbed adds the\n    // same visual without touching parent state.\n    const isDragging = board.draggingIds.includes(id) || board.draggingId === id || isKeyboardGrabbed\n    const isJustMoved = board.justMovedId === id\n    const isSelected = board.selectedIds.has(id)\n\n    const cardCtx = React.useMemo(\n      () => ({ cardId: id, laneId: lane.laneId, isDragging, isJustMoved, isSelected, disabled }),\n      [id, lane.laneId, isDragging, isJustMoved, isSelected, disabled],\n    )\n\n    // Per-card allowed-lanes registry. Re-run if the id OR the list changes.\n    React.useEffect(() => {\n      board.registerAllowedLanes(id, allowedLanes)\n      return () => board.unregisterAllowedLanes(id)\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [id, allowedLanes])\n\n    const state: 'idle' | 'dragging' | 'moved' = isDragging ? 'dragging' : isJustMoved ? 'moved' : 'idle'\n\n    function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {\n      if (disabled) return\n      // Enter activates the card (default action) — emits onClick for the\n      // consumer to open a detail panel / navigate / etc. Native <button>\n      // gets this for free; we re-emit because the card root is a\n      // <div role=\"button\"> (chosen so consumers can nest <button>/links\n      // inside the card without violating \"no interactive content in a\n      // button\").\n      if (e.key === 'Enter') {\n        e.preventDefault()\n        onClick?.(\n          new MouseEvent('click', { bubbles: true, cancelable: true }) as unknown as React.MouseEvent<HTMLDivElement>,\n        )\n        return\n      }\n      if (!keyboardDraggable) return\n      if (e.key === ' ') {\n        e.preventDefault()\n        setIsKeyboardGrabbed((v) => !v)\n        return\n      }\n      if (e.key === 'Escape' && isKeyboardGrabbed) {\n        e.preventDefault()\n        setIsKeyboardGrabbed(false)\n        return\n      }\n      if (!isKeyboardGrabbed) return\n      if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n        e.preventDefault()\n        const allLanes = Array.from(document.querySelectorAll<HTMLElement>('[data-board-lane]')).filter(\n          (el) => !el.hasAttribute('data-disabled'),\n        )\n        const currentIdx = allLanes.findIndex((el) => el.dataset.laneId === lane.laneId)\n        if (currentIdx === -1 || allLanes.length === 0) return\n        const delta = e.key === 'ArrowLeft' ? -1 : 1\n        const nextLane = allLanes[(currentIdx + delta + allLanes.length) % allLanes.length]\n        if (nextLane?.dataset.laneId) {\n          board.moveItem(id, nextLane.dataset.laneId)\n          requestAnimationFrame(() => {\n            const moved = document.querySelector<HTMLElement>(`[data-board-card-id=\"${id}\"]`)\n            moved?.focus()\n          })\n        }\n        return\n      }\n      if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n        e.preventDefault()\n        const laneEl = cardElRef.current?.closest('[data-board-lane]')\n        if (!laneEl) return\n        const cards = Array.from(laneEl.querySelectorAll<HTMLElement>('[data-board-card]'))\n        const currentIdx = cards.findIndex((el) => el.dataset.boardCardId === id)\n        if (currentIdx === -1) return\n        const delta = e.key === 'ArrowUp' ? -1 : 1\n        const targetIdx = Math.max(0, Math.min(cards.length - 1, currentIdx + delta))\n        if (targetIdx !== currentIdx) board.moveItem(id, lane.laneId, targetIdx)\n      }\n    }\n\n    function handleDragStart(e: React.DragEvent<HTMLDivElement>) {\n      if (disabled) {\n        e.preventDefault()\n        return\n      }\n      onDragStart?.(e)\n    }\n    function handleDragEnd() {\n      onDragEnd?.()\n    }\n    function handleClick(e: React.MouseEvent<HTMLDivElement>) {\n      if (disabled) {\n        e.preventDefault()\n        return\n      }\n      // Cmd/Ctrl/Shift+click toggles the selection (multi-select for drag);\n      // plain click clears any selection and just emits to the consumer\n      // (typically opens a detail Sheet).\n      if (selectable && (e.metaKey || e.ctrlKey || e.shiftKey)) {\n        e.preventDefault()\n        board.toggleSelection(id, true)\n        return\n      }\n      if (board.draggingIds.includes(id)) return\n      if (board.selectedIds.size > 0) board.clearSelection()\n      onClick?.(e)\n    }\n\n    return (\n      // Root is `<div role=\"button\">` rather than `<button type=\"button\">`\n      // so consumers can nest interactive content (buttons, links, menus)\n      // inside cards. The HTML5 spec forbids interactive content inside a\n      // <button>; browsers silently de-nest the inner element which\n      // breaks its events. Enter is wired manually in handleKeyDown to match\n      // the native button default-action behaviour.\n      <BoardCardContext.Provider value={cardCtx}>\n        <div\n          ref={setRefs}\n          role=\"button\"\n          data-uipkge=\"\"\n          data-slot=\"board-card\"\n          data-board-card=\"\"\n          data-board-card-id={id}\n          data-state={state}\n          data-selected={isSelected || undefined}\n          data-disabled={disabled || undefined}\n          aria-disabled={disabled || undefined}\n          aria-pressed={selectable ? isSelected : undefined}\n          className={cn(\n            boardCardVariants({ state }),\n            isSelected && 'ring-primary/60 ring-offset-background ring-2 ring-offset-1',\n            disabled && 'pointer-events-none cursor-not-allowed opacity-50 shadow-none grayscale hover:translate-y-0',\n            className,\n          )}\n          draggable={disabled ? false : true}\n          tabIndex={disabled ? -1 : 0}\n          aria-roledescription={keyboardDraggable && !disabled ? 'draggable card' : undefined}\n          onClick={handleClick}\n          onKeyDown={handleKeyDown}\n          onDragStart={handleDragStart}\n          onDragEnd={handleDragEnd}\n          {...props}\n        >\n          {children}\n        </div>\n      </BoardCardContext.Provider>\n    )\n  },\n)\nBoardCard.displayName = 'BoardCard'\n\nexport { Board, BoardLane, BoardLaneHeader, BoardLaneBody, BoardLaneEmpty, BoardCard }\n",
      "type": "registry:ui",
      "target": "~/components/ui/board/board.tsx"
    },
    {
      "path": "packages/registry-react/components/board/board.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file so consuming SFCs avoid the\n * circular-dep trap when importing from `index.ts`. Same pattern as\n * `timeline.variants.ts` / `card.variants.ts`.\n */\n\nexport const boardLaneVariants = cva(\n  'flex min-h-0 flex-col gap-3 rounded-xl border p-3 motion-safe:transition-[background-color,border-color,box-shadow] motion-safe:duration-200 motion-safe:ease-[cubic-bezier(0.16,1,0.3,1)]',\n  {\n    variants: {\n      tone: {\n        default: 'bg-muted/30',\n        plain: 'bg-transparent',\n      },\n      // Visual weight of `over` and `rejecting` deliberately matches so\n      // the eye reads them as the same kind of signal (drop intent),\n      // differing only in tone (primary = OK, destructive = NO).\n      state: {\n        idle: '',\n        over: 'border-primary/60 bg-primary/5 ring-2 ring-primary/30 ring-offset-1 ring-offset-background',\n        rejecting:\n          'border-destructive/40 bg-destructive/5 ring-2 ring-destructive/30 ring-offset-1 ring-offset-background',\n      },\n    },\n    defaultVariants: { tone: 'default', state: 'idle' },\n  },\n)\n\nexport const boardCardVariants = cva(\n  'bg-card hover:border-primary/40 group block w-full cursor-grab rounded-md border p-2 text-left outline-none motion-safe:transition-[transform,box-shadow,opacity,border-color] motion-safe:duration-200 motion-safe:ease-[cubic-bezier(0.16,1,0.3,1)] focus-visible:border-primary/60 focus-visible:ring-2 focus-visible:ring-primary/30 active:cursor-grabbing',\n  {\n    variants: {\n      state: {\n        idle: 'shadow-sm motion-safe:hover:-translate-y-0.5 hover:shadow-md',\n        dragging: 'scale-95 opacity-40 shadow-none',\n        moved: 'shadow-sm ring-2 ring-primary/50 ring-offset-1 ring-offset-background',\n      },\n    },\n    defaultVariants: { state: 'idle' },\n  },\n)\n\nexport type BoardLaneVariantsProps = VariantProps<typeof boardLaneVariants>\nexport type BoardCardVariantsProps = VariantProps<typeof boardCardVariants>\n",
      "type": "registry:ui",
      "target": "~/components/ui/board/board.variants.ts"
    },
    {
      "path": "packages/registry-react/components/board/context.ts",
      "content": "import * as React from 'react'\n\nexport type BoardOrientation = 'horizontal' | 'vertical'\nexport type BoardDensity = 'compact' | 'default' | 'comfortable'\n\n/** Drop event emitted by useBoard handlers. */\nexport interface BoardDropEvent {\n  /** First (anchor) item moved. For multi-item drops, see `itemIds`. */\n  itemId: string\n  /** All items moved in this drop, in their final visual order. */\n  itemIds: string[]\n  from: string\n  to: string\n  /** Insertion index of the first item in the target lane. */\n  index: number\n}\n\n/** Predicate consumers pass to control which items each lane accepts. */\nexport type BoardAcceptsFn = (itemId: string, fromLaneId: string, toLaneId: string) => boolean\n\nexport interface BoardContextValue {\n  orientation: BoardOrientation\n  density: BoardDensity\n  /** Animation preset class name. Defaults to `motion-list` (the @uipkge motion preset). */\n  motion: string\n  /** Currently-grabbed primary card id (pointer drag OR keyboard grab). */\n  draggingId: string | null\n  /** All cards being dragged this turn — usually `[draggingId]`, but\n   *  expands to the full selection when the user grabs one of a multi-\n   *  selected set. Lanes read this to compute drop math (the dragged\n   *  cards are excluded from the insertion-index walk). */\n  draggingIds: readonly string[]\n  /** Lane currently under the pointer/keyboard cursor. */\n  dragOverLaneId: string | null\n  /** Card that just landed — used by consumers for a momentary highlight. */\n  justMovedId: string | null\n  /** Multi-select set. Click-and-drag any selected card moves the whole\n   *  set; click a non-selected card to drag that one alone. */\n  selectedIds: ReadonlySet<string>\n  /** Predicate run by lanes; defaults to always-accept. */\n  accepts: BoardAcceptsFn\n  /** Imperative move — used by keyboard handlers + external callers. */\n  moveItem: (itemId: string | string[], toLaneId: string, toIndex?: number) => void\n  /** Toggle one item in the selection set (or replace it if `additive` is false). */\n  toggleSelection: (itemId: string, additive?: boolean) => void\n  /** Drop the entire selection. */\n  clearSelection: () => void\n  /** Per-card allow-list registry. BoardCard registers its own `allowedLanes`\n   *  on mount; lanes consult this to short-circuit rejected drops without\n   *  the consumer having to encode the rule inside `accepts`. */\n  registerAllowedLanes: (cardId: string, lanes: readonly string[] | undefined) => void\n  unregisterAllowedLanes: (cardId: string) => void\n  /** Whether the lane's drops are accepted for the dragging item, given\n   *  the current `accepts` + per-card allowedLanes + lane disabled state. */\n  isLaneAcceptingFor: (laneId: string) => boolean\n  /** Lane disabled registry — lanes call register/unregister; useBoard +\n   *  isLaneAcceptingFor read from it. */\n  registerLaneDisabled: (laneId: string, disabled: boolean) => void\n  unregisterLaneDisabled: (laneId: string) => void\n}\n\nexport const BoardContext = React.createContext<BoardContextValue | null>(null)\n\nexport function useBoardContext(): BoardContextValue {\n  const ctx = React.useContext(BoardContext)\n  if (!ctx) throw new Error('<BoardCard> / <BoardLane> must be a descendant of <Board>.')\n  return ctx\n}\n\nexport interface BoardLaneContextValue {\n  laneId: string\n  isDragOver: boolean\n  isAccepting: boolean\n  disabled: boolean\n}\n\nexport const BoardLaneContext = React.createContext<BoardLaneContextValue | null>(null)\n\nexport function useBoardLaneContext(): BoardLaneContextValue {\n  const ctx = React.useContext(BoardLaneContext)\n  if (!ctx) throw new Error('<BoardCard> must be a descendant of <BoardLane>.')\n  return ctx\n}\n\nexport interface BoardCardContextValue {\n  cardId: string\n  laneId: string\n  isDragging: boolean\n  isJustMoved: boolean\n  isSelected: boolean\n  disabled: boolean\n}\n\nexport const BoardCardContext = React.createContext<BoardCardContextValue | null>(null)\n",
      "type": "registry:ui",
      "target": "~/components/ui/board/context.ts"
    },
    {
      "path": "packages/registry-react/components/board/index.ts",
      "content": "export {\n  Board,\n  BoardLane,\n  BoardLaneHeader,\n  BoardLaneBody,\n  BoardLaneEmpty,\n  BoardCard,\n  type BoardProps,\n  type BoardLaneProps,\n  type BoardLaneRenderProps,\n  type BoardLaneBodyProps,\n  type BoardLaneEmptyProps,\n  type BoardCardProps,\n} from './board'\n\nexport {\n  BoardContext,\n  BoardLaneContext,\n  BoardCardContext,\n  useBoardContext,\n  useBoardLaneContext,\n  type BoardAcceptsFn,\n  type BoardContextValue,\n  type BoardCardContextValue,\n  type BoardDensity,\n  type BoardDropEvent,\n  type BoardLaneContextValue,\n  type BoardOrientation,\n} from './context'\n\nexport {\n  boardCardVariants,\n  boardLaneVariants,\n  type BoardCardVariantsProps,\n  type BoardLaneVariantsProps,\n} from './board.variants'\n",
      "type": "registry:ui",
      "target": "~/components/ui/board/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Six small composable components for any board / kanban / sortable-list surface — opinionated about drop targeting and animation, agnostic about layout, data, and chrome. Drop `<Board>` around a grid of `<BoardLane>` columns; nest `<BoardLaneHeader>`, `<BoardLaneBody>`, `<BoardLaneEmpty>`, and `<BoardCard>` items inside each lane. State lives in a sibling `useBoard()` hook (insertion-index drop math, keyboard a11y, accept predicate). Cards use native HTML5 drag-and-drop (draggable + onDragStart / onLaneDragOver / onLaneDrop) — the lane components emit drag events up to the consumer, which threads them through useBoard and feeds the resulting state back in as props. Three-level React context (board → lane → card) mirrors the Timeline / Card sub-component pattern.",
  "categories": [
    "data",
    "layout"
  ]
}