{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sortable-list-demo",
  "type": "registry:component",
  "registryDependencies": [
    "sortable-list"
  ],
  "files": [
    {
      "path": "registry/default/example/sortable-list-demo.tsx",
      "content": "\"use client\"\n\nimport { useCallback, useState } from \"react\"\nimport { Plus, RepeatIcon, Settings2Icon, XIcon } from \"lucide-react\"\nimport { AnimatePresence, LayoutGroup, motion } from \"motion/react\"\nimport { toast } from \"sonner\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Slider } from \"@/components/ui/slider\"\nimport { DirectionAwareTabs } from \"@/registry/default/ui/direction-aware-tabs\"\n\nimport SortableList, { Item, SortableListItem } from \"../ui/sortable-list\"\n\nconst initialState = [\n  {\n    text: \"Gather Data\",\n    checked: false,\n    id: 1,\n    description:\n      \"Collect relevant marketing copy from the user's website and competitor sites to understand the current market positioning and identify potential areas for improvement.\",\n  },\n  {\n    text: \"Analyze Copy\",\n    checked: false,\n    id: 2,\n    description:\n      \"As an AI language model, analyze the collected marketing copy for clarity, persuasiveness, and alignment with the user's brand voice and target audience. Identify strengths, weaknesses, and opportunities for optimization.\",\n  },\n  {\n    text: \"Create Suggestions\",\n    checked: false,\n    id: 3,\n    description:\n      \"Using natural language generation techniques, create alternative versions of the marketing copy that address the identified weaknesses and leverage the opportunities for improvement. Ensure the generated copy is compelling, on-brand, and optimized for the target audience.\",\n  },\n  {\n    text: \"Recommendations\",\n    checked: false,\n    id: 5,\n    description:\n      \"Present the AI-generated marketing copy suggestions to the user, along with insights on why these changes were recommended. Provide a user-friendly interface for the user to review, edit, and implement the optimized copy on their website.\",\n  },\n]\n\nfunction SortableListDemo() {\n  const [items, setItems] = useState<Item[]>(initialState)\n  const [openItemId, setOpenItemId] = useState<number | null>(null)\n  const [tabChangeRerender, setTabChangeRerender] = useState<number>(1)\n  const [topP, setTopP] = useState([10])\n  const [temp, setTemp] = useState([10])\n  const [tokens, setTokens] = useState([10])\n\n  const handleCompleteItem = (id: number) => {\n    setItems((prevItems) =>\n      prevItems.map((item) =>\n        item.id === id ? { ...item, checked: !item.checked } : item\n      )\n    )\n  }\n\n  const handleAddItem = () => {\n    setItems((prevItems) => [\n      ...prevItems,\n      {\n        text: `Item ${prevItems.length + 1}`,\n        checked: false,\n        id: Date.now(),\n        description: \"\",\n      },\n    ])\n  }\n\n  const handleResetItems = () => {\n    setItems(initialState)\n  }\n\n  const handleCloseOnDrag = useCallback(() => {\n    setItems((prevItems) => {\n      const updatedItems = prevItems.map((item) =>\n        item.checked ? { ...item, checked: false } : item\n      )\n      return updatedItems.some(\n        (item, index) => item.checked !== prevItems[index].checked\n      )\n        ? updatedItems\n        : prevItems\n    })\n  }, [])\n\n  const renderListItem = (\n    item: Item,\n    order: number,\n    onCompleteItem: (id: number) => void,\n    onRemoveItem: (id: number) => void\n  ) => {\n    const isOpen = item.id === openItemId\n\n    const tabs = [\n      {\n        id: 0,\n        label: \"Title\",\n        content: (\n          <div className=\"flex w-full flex-col py-2 pr-2\">\n            <motion.div\n              initial={{ opacity: 0, filter: \"blur(4px)\" }}\n              animate={{ opacity: 1, filter: \"blur(0px)\" }}\n              transition={{\n                type: \"spring\",\n                bounce: 0.2,\n                duration: 0.75,\n                delay: 0.15,\n              }}\n            >\n              <label className=\"text-xs text-neutral-400\">\n                Short title for your agent task\n              </label>\n              <motion.input\n                type=\"text\"\n                value={item.text}\n                className=\"w-full rounded-lg border border-black/10 bg-neutral-800 px-1 py-[6px] text-xl font-semibold text-white placeholder:text-white/30 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#13EEE3]/80 md:text-3xl dark:border-white/10\"\n                onChange={(e) => {\n                  const text = e.target.value\n                  setItems((prevItems) =>\n                    prevItems.map((i) =>\n                      i.id === item.id ? { ...i, text } : i\n                    )\n                  )\n                }}\n              />\n            </motion.div>\n          </div>\n        ),\n      },\n      {\n        id: 1,\n        label: \"Prompt\",\n        content: (\n          <div className=\"flex flex-col pr-2\">\n            <motion.div\n              initial={{ opacity: 0, filter: \"blur(4px)\" }}\n              animate={{ opacity: 1, filter: \"blur(0px)\" }}\n              transition={{\n                type: \"spring\",\n                bounce: 0.2,\n                duration: 0.75,\n                delay: 0.15,\n              }}\n            >\n              <label className=\"text-xs text-neutral-400\" htmlFor=\"prompt\">\n                Prompt{\" \"}\n                <span className=\"lowercase\">\n                  instructing your agent how to {item.text.slice(0, 20)}\n                </span>\n              </label>\n              <textarea\n                id=\"prompt\"\n                className=\"h-[100px] w-full resize-none rounded-[6px] bg-neutral-800 px-2 py-[2px] text-sm text-white placeholder:text-white/30 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#13EEE3]/80\"\n                value={item.description}\n                placeholder=\"update agent prompt\"\n                onChange={(e) => {\n                  const description = e.target.value\n                  setItems((prevItems) =>\n                    prevItems.map((i) =>\n                      i.id === item.id ? { ...i, description } : i\n                    )\n                  )\n                }}\n              />\n            </motion.div>\n          </div>\n        ),\n      },\n      {\n        id: 2,\n        label: \"Settings\",\n        content: (\n          <div className=\"flex flex-col px-1 py-2\">\n            <motion.div\n              initial={{ opacity: 0, filter: \"blur(4px)\" }}\n              animate={{ opacity: 1, filter: \"blur(0px)\" }}\n              transition={{\n                type: \"spring\",\n                bounce: 0.2,\n                duration: 0.75,\n                delay: 0.15,\n              }}\n              className=\"space-y-3\"\n            >\n              <p className=\"text-xs text-neutral-400\">\n                AI settings for the{\" \"}\n                <span className=\"lowercase\">\n                  {item.text.slice(0, 20)} stage\n                </span>\n              </p>\n              <div className=\"grid gap-4\">\n                <div className=\"flex items-center justify-between\">\n                  <label className=\"text-xs text-neutral-400\" htmlFor=\"top-p\">\n                    Top P\n                  </label>\n                  <div className=\"flex w-1/2 items-center gap-3\">\n                    <span className=\"text-muted-foreground w-12 rounded-md bg-black/20 px-2 py-0.5 text-right text-sm\">\n                      {topP}\n                    </span>\n                    <Slider\n                      id=\"temperature\"\n                      max={1}\n                      defaultValue={topP}\n                      step={0.1}\n                      onValueChange={setTopP}\n                      className=\"[&_[role=slider]]:h-8 [&_[role=slider]]:w-5 [&_[role=slider]]:rounded-md [&_[role=slider]]:border-neutral-100/10 [&_[role=slider]]:bg-neutral-900 [&_[role=slider]]:hover:border-[#13EEE3]/70\"\n                      aria-label=\"Top P\"\n                    />\n                  </div>\n                </div>\n              </div>\n              <div className=\"grid gap-4\">\n                <div className=\"flex items-center justify-between\">\n                  <label className=\"text-xs text-neutral-400\" htmlFor=\"top-p\">\n                    Temperature\n                  </label>\n                  <div className=\"flex w-1/2 items-center gap-3\">\n                    <span className=\"text-muted-foreground w-12 rounded-md bg-black/20 px-2 py-0.5 text-right text-sm\">\n                      {temp}\n                    </span>\n                    <Slider\n                      id=\"top-p\"\n                      max={1}\n                      defaultValue={temp}\n                      step={0.1}\n                      onValueChange={setTemp}\n                      className=\"[&_[role=slider]]:h-8 [&_[role=slider]]:w-5 [&_[role=slider]]:rounded-md [&_[role=slider]]:border-neutral-100/10 [&_[role=slider]]:bg-neutral-900 [&_[role=slider]]:hover:border-[#13EEE3]/70\"\n                      aria-label=\"Temperature\"\n                    />\n                  </div>\n                </div>\n              </div>\n              <div className=\"grid gap-4\">\n                <div className=\"flex items-center justify-between\">\n                  <label className=\"text-xs text-neutral-400\" htmlFor=\"top-p\">\n                    Max Tokens\n                  </label>\n                  <div className=\"flex w-1/2 items-center gap-3\">\n                    <span className=\"text-muted-foreground w-12 rounded-md bg-black/20 px-2 py-0.5 text-right text-sm\">\n                      {tokens}\n                    </span>\n                    <Slider\n                      id=\"max_tokens\"\n                      max={1}\n                      defaultValue={tokens}\n                      step={0.1}\n                      onValueChange={setTokens}\n                      className=\"[&_[role=slider]]:h-8 [&_[role=slider]]:w-5 [&_[role=slider]]:rounded-md [&_[role=slider]]:border-neutral-100/10 [&_[role=slider]]:bg-neutral-900 [&_[role=slider]]:hover:border-[#13EEE3]/70\"\n                      aria-label=\"Tokens\"\n                    />\n                  </div>\n                </div>\n              </div>\n            </motion.div>\n          </div>\n        ),\n      },\n    ]\n\n    return (\n      <SortableListItem\n        item={item}\n        order={order}\n        key={item.id}\n        isExpanded={isOpen}\n        onCompleteItem={onCompleteItem}\n        onRemoveItem={onRemoveItem}\n        handleDrag={handleCloseOnDrag}\n        className=\"my-2\"\n        renderExtra={(item) => (\n          <div\n            key={`${isOpen}`}\n            className={cn(\n              \"flex h-full w-full flex-col items-center justify-center gap-2\",\n              isOpen ? \"px-1 py-1\" : \"py-3\"\n            )}\n          >\n            <motion.button\n              layout\n              onClick={() => setOpenItemId(!isOpen ? item.id : null)}\n              key=\"collapse\"\n              className={cn(\n                isOpen\n                  ? \"absolute top-3 right-3 z-10\"\n                  : \"relative z-10 mr-3 ml-auto\"\n              )}\n            >\n              {isOpen ? (\n                <motion.span\n                  initial={{ opacity: 0, filter: \"blur(4px)\" }}\n                  animate={{ opacity: 1, filter: \"blur(0px)\" }}\n                  exit={{ opacity: 1, filter: \"blur(0px)\" }}\n                  transition={{\n                    type: \"spring\",\n                    duration: 1.95,\n                  }}\n                >\n                  <XIcon className=\"h-5 w-5 text-neutral-500\" />\n                </motion.span>\n              ) : (\n                <motion.span\n                  initial={{ opacity: 0, filter: \"blur(4px)\" }}\n                  animate={{ opacity: 1, filter: \"blur(0px)\" }}\n                  exit={{ opacity: 1, filter: \"blur(0px)\" }}\n                  transition={{\n                    type: \"spring\",\n                    duration: 0.95,\n                  }}\n                >\n                  <Settings2Icon className=\"h-5 w-5 stroke-1 text-white/80 hover:stroke-[#13EEE3]/70\" />\n                </motion.span>\n              )}\n            </motion.button>\n\n            <LayoutGroup id={`${item.id}`}>\n              <AnimatePresence mode=\"popLayout\">\n                {isOpen ? (\n                  <motion.div className=\"flex w-full flex-col\">\n                    <div className=\"w-full\">\n                      <motion.div\n                        initial={{\n                          y: 0,\n                          opacity: 0,\n                          filter: \"blur(4px)\",\n                        }}\n                        animate={{\n                          y: 0,\n                          opacity: 1,\n                          filter: \"blur(0px)\",\n                        }}\n                        transition={{\n                          type: \"spring\",\n                          duration: 0.15,\n                        }}\n                        layout\n                        className=\"w-full\"\n                      >\n                        <DirectionAwareTabs\n                          className=\"mr-auto bg-transparent pr-2\"\n                          rounded=\"rounded-lg\"\n                          roundedInner=\"rounded-[5px]\"\n                          tabs={tabs}\n                          onChange={() =>\n                            setTabChangeRerender(tabChangeRerender + 1)\n                          }\n                        />\n                      </motion.div>\n                    </div>\n\n                    <motion.div\n                      key={`re-render-${tabChangeRerender}`} //  re-animates the button section on tab change\n                      className=\"mb-2 flex w-full items-center justify-between pl-2\"\n                      initial={{ opacity: 0, filter: \"blur(4px)\" }}\n                      animate={{ opacity: 1, filter: \"blur(0px)\" }}\n                      transition={{\n                        type: \"spring\",\n                        bounce: 0,\n                        duration: 0.55,\n                      }}\n                    >\n                      <motion.div className=\"flex items-center gap-2 pt-3\">\n                        <div className=\"h-1.5 w-1.5 rounded-full bg-[#13EEE3]\" />\n                        <span className=\"text-xs text-neutral-300/80\">\n                          Changes\n                        </span>\n                      </motion.div>\n                      <motion.div layout className=\"mr-1 ml-auto pt-2\">\n                        <Button\n                          size=\"sm\"\n                          variant=\"ghost\"\n                          onClick={() => {\n                            setOpenItemId(null)\n                            toast.info(\"Changes saved\")\n                          }}\n                          className=\"h-7 rounded-lg bg-[#13EEE3]/80 text-black hover:bg-[#13EEE3] hover:text-black\"\n                        >\n                          Apply Changes\n                        </Button>\n                      </motion.div>\n                    </motion.div>\n                  </motion.div>\n                ) : null}\n              </AnimatePresence>\n            </LayoutGroup>\n          </div>\n        )}\n      />\n    )\n  }\n\n  return (\n    <div className=\"w-full max-w-xl md:px-4\">\n      <div className=\"mb-9 rounded-2xl bg-black p-2 shadow-sm md:p-6\">\n        <div className=\"overflow-auto p-1 md:p-4\">\n          <div className=\"flex flex-col space-y-2\">\n            <div className=\"\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"256\"\n                height=\"260\"\n                preserveAspectRatio=\"xMidYMid\"\n                viewBox=\"0 0 256 260\"\n                className=\"h-6 w-6 fill-neutral-500\"\n              >\n                <path d=\"M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483Zm-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87 51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601Zm-104.466-44.61a48.345 48.345 0 0 1-5.781-32.589l1.534.921 51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803ZM23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405v.256Zm179.466 41.695-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213Zm21.742-32.69-1.535-.922-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391v.205ZM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367l-.051 72.697Zm11.868-25.58 28.138-16.217 28.188 16.218v32.434l-28.086 16.218-28.188-16.218-.052-32.434Z\" />\n              </svg>\n              <h3 className=\"text-neutral-200\">Agent workflow</h3>\n              <a\n                className=\"text-xs text-white/80\"\n                href=\"https://www.uilabs.dev/\"\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n              >\n                Inspired by <span className=\"text-[#13EEE3]\"> @mrncst</span>\n              </a>\n            </div>\n            <div className=\"flex items-center justify-between gap-4 py-2\">\n              <button disabled={items?.length > 5} onClick={handleAddItem}>\n                <Plus className=\"dark:text-netural-100 h-5 w-5 text-neutral-500/80 hover:text-white/80\" />\n              </button>\n              <div data-tip=\"Reset task list\">\n                <button onClick={handleResetItems}>\n                  <RepeatIcon className=\"dark:text-netural-100 h-4 w-4 text-neutral-500/80 hover:text-white/80\" />\n                </button>\n              </div>\n            </div>\n            <SortableList\n              items={items}\n              setItems={setItems}\n              onCompleteItem={handleCompleteItem}\n              renderItem={renderListItem}\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default SortableListDemo\n",
      "type": "registry:component"
    }
  ]
}