{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sortable-list",
  "type": "registry:ui",
  "title": "Sortable list",
  "description": "Drag to reorder a list in React with Motion Reorder, plus Arrow key reordering from the handle and polite announcements of every move for screen readers.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/sortable-list.tsx",
      "type": "registry:ui",
      "target": "components/ui/sortable-list.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { Reorder, useDragControls, useReducedMotion } from \"motion/react\"\nimport { GripVerticalIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * A drag to reorder list built on Motion's Reorder: grab the handle to\n * drag, or focus it and use Arrow Up and Arrow Down to move an item by\n * keyboard. Every move is announced, and the new order comes back through\n * onReorder.\n */\ntype Item = { id: string; label: string; detail?: string }\n\ntype SortableListProps = {\n  items?: Item[]\n  onReorder?: (items: Item[]) => void\n  className?: string\n}\n\nconst DEMO: Item[] = [\n  { id: \"a\", label: \"Design review\", detail: \"Tue 10:00\" },\n  { id: \"b\", label: \"Ship onboarding flow\", detail: \"Wed\" },\n  { id: \"c\", label: \"Pricing page copy\", detail: \"Thu\" },\n  { id: \"d\", label: \"Customer interviews\", detail: \"Fri\" },\n  { id: \"e\", label: \"Release notes\", detail: \"Fri 16:00\" },\n]\n\nfunction Row({ item, index, total, move }: { item: Item; index: number; total: number; move: (from: number, to: number) => void }) {\n  const controls = useDragControls()\n  const reduce = useReducedMotion()\n  return (\n    <Reorder.Item\n      value={item}\n      dragListener={false}\n      dragControls={controls}\n      layout={reduce ? undefined : true}\n      whileDrag={{ scale: 1.02, boxShadow: \"0 18px 40px -12px rgb(0 0 0 / 0.35)\", zIndex: 10 }}\n      className=\"relative flex items-center gap-3 rounded-xl border border-border bg-surface px-3 py-2.5 shadow-raised select-none\"\n    >\n      <button\n        type=\"button\"\n        aria-label={`Reorder ${item.label}, position ${index + 1} of ${total}`}\n        onPointerDown={(e) => controls.start(e)}\n        onKeyDown={(e) => {\n          if (e.key === \"ArrowUp\" && index > 0) {\n            e.preventDefault()\n            move(index, index - 1)\n          }\n          if (e.key === \"ArrowDown\" && index < total - 1) {\n            e.preventDefault()\n            move(index, index + 1)\n          }\n        }}\n        className=\"grid size-7 cursor-grab touch-none place-items-center rounded-md text-fg-subtle outline-none hover:bg-sunken hover:text-fg focus-visible:ring-2 focus-visible:ring-accent active:cursor-grabbing\"\n      >\n        <GripVerticalIcon className=\"size-4\" />\n      </button>\n      <span className=\"min-w-0 flex-1 truncate text-sm text-fg\">{item.label}</span>\n      {item.detail ? <span className=\"shrink-0 font-mono text-[11px] text-fg-subtle\">{item.detail}</span> : null}\n    </Reorder.Item>\n  )\n}\n\nfunction SortableList({ items = DEMO, onReorder, className }: SortableListProps) {\n  const [list, setList] = React.useState(items)\n  const [said, setSaid] = React.useState(\"\")\n  const update = (next: Item[]) => {\n    setList(next)\n    onReorder?.(next)\n  }\n  const move = (from: number, to: number) => {\n    const next = [...list]\n    const [it] = next.splice(from, 1)\n    next.splice(to, 0, it)\n    update(next)\n    setSaid(`${it.label} moved to position ${to + 1} of ${next.length}`)\n    // keep focus on the moved handle\n    requestAnimationFrame(() => (document.querySelectorAll<HTMLButtonElement>('[data-slot=\"sortable-list\"] button')[to])?.focus())\n  }\n  return (\n    <div data-slot=\"sortable-list\" className={cn(\"w-full max-w-sm\", className)}>\n      <Reorder.Group axis=\"y\" values={list} onReorder={update} className=\"flex flex-col gap-2\">\n        {list.map((it, i) => (\n          <Row key={it.id} item={it} index={i} total={list.length} move={move} />\n        ))}\n      </Reorder.Group>\n      <p aria-live=\"polite\" className=\"sr-only\">{said}</p>\n    </div>\n  )\n}\n\nexport { SortableList }\nexport type { SortableListProps }\n"
    }
  ]
}
