{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dnd-list",
  "type": "registry:ui",
  "title": "Sortable List",
  "description": "Accessible pointer and keyboard sortable list with reorder callbacks.",
  "dependencies": [
    "@dnd-kit/core@^6.3.1",
    "@dnd-kit/sortable@^10.0.0",
    "@dnd-kit/utilities@^3.2.2",
    "@mantine/core@^9",
    "@mantine/hooks@^9",
    "clsx@^2.1.1"
  ],
  "docs": "Adapted from Mantine UI DndList at ffbf61c559f374a7ea28fcf00355e84dcbe9a908; hardcoded periodic-table data was replaced with a reusable item contract.",
  "registryDependencies": [
    "@house/mantine-ui-license"
  ],
  "files": [
    {
      "path": "registry/mantine-ui/dnd-list/dnd-list.tsx",
      "type": "registry:ui",
      "content": "/**\n * Adapted from Mantine UI's DndList at\n * ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see\n * LICENSES/MANTINE-UI.txt after installation.\n */\n\nimport {\n  closestCenter,\n  DndContext,\n  type DragEndEvent,\n  KeyboardSensor,\n  PointerSensor,\n  useSensor,\n  useSensors,\n} from \"@dnd-kit/core\";\nimport {\n  arrayMove,\n  SortableContext,\n  sortableKeyboardCoordinates,\n  useSortable,\n  verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n  type Factory,\n  factory,\n  type StylesApiProps,\n  Text,\n  useProps,\n  useStyles,\n} from \"@mantine/core\";\nimport { useListState } from \"@mantine/hooks\";\nimport cx from \"clsx\";\nimport type { ReactNode } from \"react\";\n\nimport classes from \"./dnd-list.module.css\";\n\nexport interface DndListItem {\n  id: string;\n  label: ReactNode;\n  description?: ReactNode;\n  leading?: ReactNode;\n}\n\n/**\n * DndList renders no wrapper element of its own — `DndContext`/`SortableContext`\n * are context providers, not DOM nodes, and the component's top-level output is\n * the bare list of per-item `<div>`s. There is therefore no `root` selector:\n * every selector here names a part of the repeated item row instead.\n */\nexport type DndListStylesNames = \"item\" | \"itemSection\" | \"itemLabel\" | \"itemDescription\";\n\nexport interface DndListProps extends StylesApiProps<DndListFactory> {\n  initialItems: DndListItem[];\n  onChange?: (items: DndListItem[]) => void;\n}\n\nexport type DndListFactory = Factory<{\n  props: DndListProps;\n  stylesNames: DndListStylesNames;\n}>;\n\ninterface SortableItemProps {\n  item: DndListItem;\n  getStyles: ReturnType<typeof useStyles<DndListFactory>>;\n}\n\nfunction SortableItem({ item, getStyles }: SortableItemProps) {\n  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n    id: item.id,\n  });\n  const itemStyles = getStyles(\"item\");\n\n  return (\n    <div\n      ref={setNodeRef}\n      {...itemStyles}\n      className={cx(itemStyles.className, { [classes.itemDragging]: isDragging })}\n      // dnd-kit's transform/transition are mandatory for drag positioning and must\n      // always win; they're applied last so a consumer's `styles={{ item: {...} }}`\n      // can still set any other CSS property on this element.\n      style={{\n        ...itemStyles.style,\n        transform: CSS.Transform.toString(transform),\n        transition,\n      }}\n      {...attributes}\n      {...listeners}\n    >\n      {item.leading && <div {...getStyles(\"itemSection\")}>{item.leading}</div>}\n      <div>\n        <Text {...getStyles(\"itemLabel\")}>{item.label}</Text>\n        {item.description && (\n          <Text {...getStyles(\"itemDescription\")} size=\"sm\">\n            {item.description}\n          </Text>\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport const DndList = factory<DndListFactory>((_props) => {\n  const props = useProps(\"DndList\", null, _props);\n  const { classNames, styles, unstyled, vars, attributes, initialItems, onChange } = props;\n\n  const getStyles = useStyles<DndListFactory>({\n    name: \"DndList\",\n    classes,\n    props,\n    classNames,\n    styles,\n    unstyled,\n    attributes,\n    vars,\n  });\n\n  const [items, handlers] = useListState(initialItems);\n  const sensors = useSensors(\n    useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),\n  );\n\n  const handleDragEnd = ({ active, over }: DragEndEvent) => {\n    if (!over || active.id === over.id) return;\n\n    const previousIndex = items.findIndex((item) => item.id === active.id);\n    const nextIndex = items.findIndex((item) => item.id === over.id);\n    if (previousIndex === -1 || nextIndex === -1) return;\n\n    const reordered = arrayMove(items, previousIndex, nextIndex);\n    handlers.setState(reordered);\n    onChange?.(reordered);\n  };\n\n  return (\n    <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>\n      <SortableContext items={items.map((item) => item.id)} strategy={verticalListSortingStrategy}>\n        {items.map((item) => (\n          <SortableItem key={item.id} item={item} getStyles={getStyles} />\n        ))}\n      </SortableContext>\n    </DndContext>\n  );\n});\n\nDndList.classes = classes;\nDndList.displayName = \"DndList\";\n\nexport namespace DndList {\n  export type Props = DndListProps;\n  export type StylesNames = DndListStylesNames;\n  export type Factory = DndListFactory;\n}\n"
    },
    {
      "path": "registry/mantine-ui/dnd-list/dnd-list.module.css",
      "type": "registry:file",
      "target": "@ui/dnd-list.module.css",
      "content": "/*\n * Adapted from Mantine UI's DndList at\n * ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see\n * LICENSES/MANTINE-UI.txt after installation.\n */\n.item {\n  display: flex;\n  align-items: center;\n  margin-bottom: var(--mantine-spacing-sm);\n  padding: var(--mantine-spacing-sm) var(--mantine-spacing-lg);\n  cursor: grab;\n  background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-5));\n  border: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));\n  border-radius: var(--mantine-radius-md);\n  touch-action: none;\n}\n\n.item:active {\n  cursor: grabbing;\n}\n\n.itemDragging {\n  box-shadow: var(--mantine-shadow-sm);\n}\n\n.itemSection {\n  display: grid;\n  min-width: 60px;\n  font-size: 30px;\n  font-weight: 700;\n  place-items: center start;\n}\n\n/*\n * itemLabel is a purely structural selector: the label had no default styling\n * before this selector existed either, so it intentionally has no rule here\n * (see the customization test's carve-out comment for the same reason). Adding\n * an empty rule just to satisfy tooling would trip Biome's noEmptyBlock lint.\n */\n\n.itemDescription {\n  color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-gray-4));\n}\n"
    }
  ],
  "meta": {
    "mantine": {
      "requires": ">=9",
      "provider": "MantineProvider",
      "stylesApi": {
        "DndList": [
          "item",
          "itemSection",
          "itemLabel",
          "itemDescription"
        ]
      },
      "props": {
        "DndList": [
          {
            "name": "initialItems",
            "type": "DndListItem[]",
            "required": true,
            "description": "Seeds the internal list state that the sortable items render from and reorder within."
          },
          {
            "name": "onChange",
            "type": "(items: DndListItem[]) => void",
            "required": false,
            "description": "Called with the full reordered item array whenever a drag ends on a different position."
          }
        ]
      },
      "usage": {
        "path": "registry/mantine-ui/dnd-list/dnd-list.usage.tsx",
        "content": "import { DndList } from \"@/components/ui/dnd-list\";\n\nexport function ProjectPriorityList() {\n  return (\n    <DndList\n      initialItems={[\n        {\n          id: \"1\",\n          label: \"Ship onboarding redesign\",\n          description: \"Due this Friday\",\n          leading: \"1\",\n        },\n        {\n          id: \"2\",\n          label: \"Fix billing webhook retries\",\n          description: \"Blocked on infra\",\n          leading: \"2\",\n        },\n        {\n          id: \"3\",\n          label: \"Write Q3 roadmap draft\",\n          description: \"Needs stakeholder review\",\n          leading: \"3\",\n        },\n        { id: \"4\", label: \"Migrate docs to new theme\", description: \"Nice to have\", leading: \"4\" },\n      ]}\n      onChange={(items) =>\n        console.log(\n          \"reordered\",\n          items.map((item) => item.id),\n        )\n      }\n    />\n  );\n}\n"
      }
    }
  }
}
