{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sortable-list",
  "type": "registry:ui",
  "description": "Drag-and-drop sortable list component with smooth animations and reordering",
  "dependencies": [
    "motion",
    "react-use-measure",
    "lucide-react"
  ],
  "registryDependencies": [
    "checkbox"
  ],
  "files": [
    {
      "path": "registry/default/ui/sortable-list.tsx",
      "content": "\"use client\"\n\n// npx shadcn-ui@latest add checkbox\n// npm  i react-use-measure\nimport { Dispatch, ReactNode, SetStateAction, useState } from \"react\"\nimport { Trash } from \"lucide-react\"\nimport {\n  AnimatePresence,\n  LayoutGroup,\n  motion,\n  Reorder,\n  useDragControls,\n} from \"motion/react\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\n\nexport type Item = {\n  text: string\n  checked: boolean\n  id: number\n  description: string\n}\n\ninterface SortableListItemProps {\n  item: Item\n  order: number\n  onCompleteItem: (id: number) => void\n  onRemoveItem: (id: number) => void\n  renderExtra?: (item: Item) => React.ReactNode\n  isExpanded?: boolean\n  className?: string\n  handleDrag: () => void\n}\n\nfunction SortableListItem({\n  item,\n  order,\n  onCompleteItem,\n  onRemoveItem,\n  renderExtra,\n  handleDrag,\n  isExpanded,\n  className,\n}: SortableListItemProps) {\n  let [ref, bounds] = useMeasure()\n  const [isDragging, setIsDragging] = useState(false)\n  // const [isDraggable, setIsDraggable] = useState(true)\n  const dragControls = useDragControls()\n\n  const handleDragStart = (event: any) => {\n    setIsDragging(true)\n    dragControls.start(event, { snapToCursor: true })\n    handleDrag()\n  }\n\n  const handleDragEnd = () => {\n    setIsDragging(false)\n  }\n\n  return (\n    <motion.div className={cn(\"\", className)} key={item.id}>\n      <div className=\"flex w-full items-center\">\n        <Reorder.Item\n          value={item}\n          className={cn(\n            \"relative z-auto grow\",\n            \"bg-primary dark:bg-primary-foreground h-full rounded-xl\",\n            \"shadow-[0px_1px_0px_0px_hsla(0,0%,100%,.03)_inset,0px_0px_0px_1px_hsla(0,0%,100%,.03)_inset,0px_0px_0px_1px_rgba(0,0,0,.1),0px_2px_2px_0px_rgba(0,0,0,.1),0px_4px_4px_0px_rgba(0,0,0,.1),0px_8px_8px_0px_rgba(0,0,0,.1)]\",\n            item.checked ? \"cursor-not-allowed\" : \"cursor-grab\",\n            item.checked && !isDragging ? \"w-7/10\" : \"w-full\"\n          )}\n          key={item.id}\n          initial={{ opacity: 0 }}\n          animate={{\n            opacity: 1,\n            height: bounds.height > 0 ? bounds.height : undefined,\n            transition: {\n              type: \"spring\",\n              bounce: 0,\n              duration: 0.4,\n            },\n          }}\n          exit={{\n            opacity: 0,\n            transition: {\n              duration: 0.05,\n              type: \"spring\",\n              bounce: 0.1,\n            },\n          }}\n          layout\n          layoutId={`item-${item.id}`}\n          dragListener={!item.checked}\n          dragControls={dragControls}\n          onDragEnd={handleDragEnd}\n          style={\n            isExpanded\n              ? {\n                  zIndex: 9999,\n                  marginTop: 10,\n                  marginBottom: 10,\n                  position: \"relative\",\n                  overflow: \"hidden\",\n                }\n              : {\n                  position: \"relative\",\n                  overflow: \"hidden\",\n                }\n          }\n          whileDrag={{ zIndex: 9999 }}\n        >\n          <div ref={ref} className={cn(isExpanded ? \"\" : \"\", \"z-20\")}>\n            <motion.div\n              layout=\"position\"\n              className=\"flex items-center justify-center\"\n            >\n              <AnimatePresence>\n                {!isExpanded ? (\n                  <motion.div\n                    initial={{ opacity: 0, filter: \"blur(4px)\" }}\n                    animate={{ opacity: 1, filter: \"blur(0px)\" }}\n                    exit={{ opacity: 0, filter: \"blur(4px)\" }}\n                    transition={{ duration: 0.001 }}\n                    className=\"flex items-center space-x-2\"\n                  >\n                    {/* List Remove Actions */}\n                    <div className=\"pt-1 pl-3\">\n                      <Checkbox\n                        checked={item.checked}\n                        id={`checkbox-${item.id}`}\n                        aria-label=\"Mark to delete\"\n                        onCheckedChange={() => onCompleteItem(item.id)}\n                        className=\"h-5 w-5 rounded-md border-white/20 bg-black/30 data-[state=checked]:bg-black data-[state=checked]:text-red-200\"\n                      />\n                    </div>\n                    {/* List Order */}\n                    <p className=\"pl-1 font-mono text-xs text-white/50\">\n                      {order + 1}\n                    </p>\n\n                    {/* List Title */}\n                    <motion.div\n                      key={`${item.checked}`}\n                      className=\"min-w-[150px] px-1\"\n                      initial={{\n                        opacity: 0,\n                        filter: \"blur(4px)\",\n                      }}\n                      animate={{ opacity: 1, filter: \"blur(0px)\" }}\n                      transition={{\n                        bounce: 0.2,\n                        delay: item.checked ? 0.2 : 0,\n                        type: \"spring\",\n                      }}\n                    >\n                      <h4\n                        className={cn(\n                          \"text-base tracking-tighter md:text-lg\",\n                          item.checked ? \"text-red-400\" : \"text-white/70\"\n                        )}\n                      >\n                        {item.checked ? \"Delete\" : ` ${item.text}`}\n                      </h4>\n                    </motion.div>\n                  </motion.div>\n                ) : null}\n              </AnimatePresence>\n\n              {/* List Item Children */}\n              {renderExtra && renderExtra(item)}\n            </motion.div>\n          </div>\n          <div\n            onPointerDown={handleDragStart}\n            style={{ touchAction: \"none\" }}\n          />\n        </Reorder.Item>\n        {/* List Delete Action Animation */}\n        <AnimatePresence mode=\"popLayout\">\n          {item.checked ? (\n            <motion.div\n              layout\n              initial={{ opacity: 0, x: -10 }}\n              animate={{\n                opacity: 1,\n                x: 0,\n                transition: {\n                  delay: 0.17,\n                  duration: 0.17,\n                  type: \"spring\",\n                  bounce: 0.6,\n                },\n                zIndex: 5,\n              }}\n              exit={{\n                opacity: 0,\n                x: -5,\n                transition: {\n                  delay: 0,\n                  duration: 0.0,\n                  type: \"spring\",\n                  bounce: 0,\n                },\n              }}\n              className=\"-ml-[1px] h-[1.5rem] w-3 rounded-l-none rounded-r-none border-y border-y-white/5 border-r-white/10 bg-[#161716]\"\n            />\n          ) : null}\n        </AnimatePresence>\n        <AnimatePresence mode=\"popLayout\">\n          {item.checked ? (\n            <motion.div\n              layout\n              initial={{ opacity: 0, x: -5, filter: \"blur(4px)\" }}\n              animate={{\n                opacity: 1,\n                x: 0,\n                filter: \"blur(0px)\",\n                transition: {\n                  delay: 0.3,\n                  duration: 0.15,\n                  type: \"spring\",\n                  bounce: 0.9,\n                },\n              }}\n              exit={{\n                opacity: 0,\n                filter: \"blur(4px)\",\n                x: -10,\n                transition: { delay: 0, duration: 0.12 },\n              }}\n              className=\"inset-0 z-0 border-spacing-1 rounded-l-sm rounded-r-xl border-r-2 border-r-red-300/60 bg-[#161716]/80 shadow-[0_1px_0_0_rgba(255,255,255,0.03)_inset,0_0_0_1px_rgba(255,255,255,0.03)_inset,0_0_0_1px_rgba(0,0,0,0.1),0_2px_2px_0_rgba(0,0,0,0.1),0_4px_4px_0_rgba(0,0,0,0.1),0_8px_8px_0_rgba(0,0,0,0.1)] dark:bg-[#161716]/50\"\n            >\n              <button\n                type=\"button\"\n                className=\"focus-visible:ring-ring inline-flex h-10 items-center justify-center rounded-md px-3 text-sm font-medium whitespace-nowrap transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50\"\n                onClick={() => onRemoveItem(item.id)}\n              >\n                <Trash className=\"h-4 w-4 fill-red-400/60 text-red-400 transition-colors duration-150\" />\n              </button>\n            </motion.div>\n          ) : null}\n        </AnimatePresence>\n      </div>\n    </motion.div>\n  )\n}\n\nSortableListItem.displayName = \"SortableListItem\"\n\ninterface SortableListProps {\n  items: Item[]\n  setItems: Dispatch<SetStateAction<Item[]>>\n  onCompleteItem: (id: number) => void\n  renderItem: (\n    item: Item,\n    order: number,\n    onCompleteItem: (id: number) => void,\n    onRemoveItem: (id: number) => void\n  ) => ReactNode\n}\n\nfunction SortableList({\n  items,\n  setItems,\n  onCompleteItem,\n  renderItem,\n}: SortableListProps) {\n  if (items) {\n    return (\n      <LayoutGroup>\n        <Reorder.Group\n          axis=\"y\"\n          values={items}\n          onReorder={setItems}\n          className=\"flex flex-col\"\n        >\n          <AnimatePresence>\n            {items?.map((item, index) =>\n              renderItem(item, index, onCompleteItem, (id: number) =>\n                setItems((items) => items.filter((item) => item.id !== id))\n              )\n            )}\n          </AnimatePresence>\n        </Reorder.Group>\n      </LayoutGroup>\n    )\n  }\n  return null\n}\n\nSortableList.displayName = \"SortableList\"\n\nexport { SortableList, SortableListItem }\nexport default SortableList\n",
      "type": "registry:ui"
    }
  ]
}