{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gooey-ai",
  "description": "A gooey AI prompt component with an expandable dialog, controlled textarea, and customizable trigger.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "registry/new-york/gooey-ai/GooeyAI.tsx",
      "content": "\"use client\";\r\n\r\nimport { cn } from \"@/lib/utils\";\r\nimport { ArrowUp, PlusIcon, Square } from \"lucide-react\";\r\nimport { easeOut, motion } from \"motion/react\";\r\nimport {\r\n  createContext,\r\n  ReactNode,\r\n  useContext,\r\n  useEffect,\r\n  useRef,\r\n  useState,\r\n} from \"react\";\r\n\r\ntype GooeyAIContextType = {\r\n  value: string;\r\n  setValue: React.Dispatch<React.SetStateAction<string>>;\r\n  isLoading: boolean;\r\n  onSubmit: () => void;\r\n\r\n  isExpanded: boolean;\r\n  setIsExpanded: React.Dispatch<React.SetStateAction<boolean>>;\r\n\r\n  rootRef: React.RefObject<HTMLDivElement | null>;\r\n};\r\n\r\nconst GooeyAIContext = createContext<GooeyAIContextType | null>(null);\r\nfunction useGooeyAI() {\r\n  const context = useContext(GooeyAIContext);\r\n\r\n  if (!context) {\r\n    throw new Error(\"GooeyAI components must be inside <GooeyAI>\");\r\n  }\r\n\r\n  return context;\r\n}\r\n\r\ntype GooeyAIProps = {\r\n  children: ReactNode;\r\n  value: string;\r\n  setValue: React.Dispatch<React.SetStateAction<string>>;\r\n  isLoading: boolean;\r\n  onSubmit: () => void;\r\n};\r\n\r\nexport function GooeyAI({\r\n  children,\r\n  value,\r\n  setValue,\r\n  isLoading,\r\n  onSubmit,\r\n}: GooeyAIProps) {\r\n  const [isExpanded, setIsExpanded] = useState(false);\r\n\r\n  const rootRef = useRef<HTMLDivElement>(null);\r\n\r\n  useEffect(() => {\r\n    if (!isExpanded) return;\r\n\r\n    function handleOutsideClick(e: MouseEvent) {\r\n      if (rootRef.current?.contains(e.target as Node)) return;\r\n      setIsExpanded(false);\r\n    }\r\n\r\n    document.addEventListener(\"pointerdown\", handleOutsideClick);\r\n\r\n    return () =>\r\n      document.removeEventListener(\"pointerdown\", handleOutsideClick);\r\n  }, [isExpanded]);\r\n\r\n  return (\r\n    <GooeyAIContext.Provider\r\n      value={{\r\n        value,\r\n        setValue,\r\n        isLoading,\r\n        onSubmit,\r\n        isExpanded,\r\n        setIsExpanded,\r\n        rootRef,\r\n      }}\r\n    >\r\n      <div\r\n        style={{\r\n          filter: \"url(#gooey-AI)\",\r\n        }}\r\n        className=\"flex flex-col justify-center space-y-2\"\r\n      >\r\n        <GooeyFilter />\r\n\r\n        <div\r\n          ref={rootRef}\r\n          className=\"relative w-14 h-14 flex items-center justify-center\"\r\n        >\r\n          {children}\r\n        </div>\r\n      </div>\r\n    </GooeyAIContext.Provider>\r\n  );\r\n}\r\n\r\nexport function GooeyAIButton({ className }: { className?: string }) {\r\n  const { isExpanded, setIsExpanded } = useGooeyAI();\r\n\r\n  return (\r\n    <button\r\n      onClick={() => setIsExpanded(!isExpanded)}\r\n      className={cn(\r\n        \"size-14 flex items-center justify-center rounded-full bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-200\",\r\n        className,\r\n      )}\r\n    >\r\n      AI\r\n    </button>\r\n  );\r\n}\r\n\r\nexport function GooeyAIDialog({\r\n  children,\r\n  className,\r\n}: {\r\n  children: ReactNode;\r\n  className?: string;\r\n}) {\r\n  const { isExpanded } = useGooeyAI();\r\n\r\n  const variants = {\r\n    closed: {\r\n      y: -20,\r\n      width: 0,\r\n      opacity: 0,\r\n      scale: 0,\r\n      transition: {\r\n        y: { duration: 0.5 },\r\n        opacity: { duration: 0.5 },\r\n        ease: easeOut,\r\n      },\r\n    },\r\n    open: {\r\n      y: -62,\r\n      width: 386,\r\n      height: \"auto\",\r\n      opacity: 1,\r\n      scale: 1,\r\n      transition: {\r\n        y: { duration: 0.5 },\r\n        width: {\r\n          duration: 0.31,\r\n          delay: 0.05,\r\n        },\r\n        height: {\r\n          duration: 0.3,\r\n          delay: 0.03,\r\n        },\r\n        opacity: {\r\n          duration: 0.12,\r\n        },\r\n      },\r\n    },\r\n  };\r\n\r\n  return (\r\n    <motion.div\r\n      initial=\"closed\"\r\n      animate={isExpanded ? \"open\" : \"closed\"}\r\n      variants={variants}\r\n      style={{\r\n        transformOrigin: \"left bottom\",\r\n      }}\r\n      className={cn(\r\n        \"absolute -left-1 bottom-0 rounded-3xl bg-neutral-200 dark:bg-neutral-800 p-3 py-4\",\r\n        className,\r\n      )}\r\n    >\r\n      {children}\r\n    </motion.div>\r\n  );\r\n}\r\n\r\nexport function GooeyAITextArea({\r\n  className,\r\n  placeholder,\r\n}: {\r\n  className?: string;\r\n  placeholder?: string;\r\n}) {\r\n  const { value, setValue, isLoading, onSubmit } = useGooeyAI();\r\n\r\n  const textareaRef = useRef<HTMLTextAreaElement>(null);\r\n\r\n  function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {\r\n    const textarea = textareaRef.current!;\r\n\r\n    textarea.style.height = \"0\";\r\n    textarea.style.height = textarea.scrollHeight + \"px\";\r\n\r\n    setValue(e.target.value);\r\n  }\r\n  const canSubmit = value.trim().length > 0 && !isLoading;\r\n  return (\r\n    <>\r\n      <textarea\r\n        ref={textareaRef}\r\n        value={value}\r\n        placeholder={placeholder}\r\n        onChange={handleChange}\r\n        onKeyDown={(e) => {\r\n          if (e.key === \"Enter\" && !e.shiftKey) {\r\n            e.preventDefault();\r\n\r\n            if (!value.trim() || isLoading) return;\r\n\r\n            onSubmit();\r\n\r\n            setValue(\"\");\r\n\r\n            textareaRef.current!.style.height = \"40px\";\r\n          }\r\n        }}\r\n        className={cn(\r\n          \"w-70 md:w-86 h-10 max-h-40 resize-none overflow-y-auto [scrollbar-width:none] px-2 py-3 focus:outline-none text-black dark:text-neutral-100 placeholder:text-neutral-500 dark:placeholder:text-neutral-500 leading-6 mask-b-from-70% mask-b-to-100%\",\r\n          className,\r\n        )}\r\n      />\r\n\r\n      <div className=\"flex items-center justify-between\">\r\n        <button>\r\n          <PlusIcon className=\"size-9 rounded-full p-2 text-neutral-600 hover:bg-neutral-300 dark:text-neutral-300 dark:hover:bg-neutral-700\" />\r\n        </button>\r\n\r\n        {isLoading ? (\r\n          <button\r\n            type=\"button\"\r\n            disabled\r\n            className=\"size-9 flex items-center justify-center rounded-full bg-neutral-800 dark:bg-neutral-200 cursor-not-allowed\"\r\n          >\r\n            <Square className=\"size-4 md:size-5 fill-white text-white dark:fill-neutral-800 dark:text-neutral-800\" />\r\n          </button>\r\n        ) : (\r\n          <button\r\n            type=\"button\"\r\n            disabled={!canSubmit}\r\n            onClick={onSubmit}\r\n            className={cn(\r\n              \"size-9 flex items-center justify-center rounded-full transition-colors\",\r\n              canSubmit\r\n                ? \"bg-neutral-800 hover:bg-neutral-700 cursor-pointer dark:bg-neutral-200 text-neutral-100 dark:text-neutral-800\"\r\n                : \"bg-neutral-300 text-neutral-500 cursor-not-allowed dark:bg-neutral-700 dark:text-neutral-400\",\r\n            )}\r\n          >\r\n            <ArrowUp className=\"size-4 md:size-5\" />\r\n          </button>\r\n        )}\r\n      </div>\r\n    </>\r\n  );\r\n}\r\n\r\nfunction GooeyFilter() {\r\n  return (\r\n    <svg aria-hidden=\"true\">\r\n      <defs>\r\n        <filter id=\"gooey-AI\">\r\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"4\" result=\"blur\" />\r\n          <feColorMatrix\r\n            in=\"blur\"\r\n            type=\"matrix\"\r\n            values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -15\"\r\n            result=\"goo\"\r\n          />\r\n          <feComposite in=\"SourceGraphic\" in2=\"goo\" operator=\"atop\" />\r\n        </filter>\r\n      </defs>\r\n    </svg>\r\n  );\r\n}\r\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:component"
}