{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "feedback-demo",
  "title": "Feedback Demo",
  "description": "component for the Feedback Demo",
  "dependencies": [
    "framer-motion",
    "lucide-react",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "app/registry/feedback/feedbackdemo.tsx",
      "content": "\"use client\";\n\nimport { Angry, Check, Frown, Laugh, Loader2, Smile } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { twMerge } from \"tailwind-merge\";\nimport { cn } from \"@/lib/utils\";\n\nconst feedback = [\n  { happiness: 4, emoji: Laugh, color: \"text-green-600\" },\n  { happiness: 3, emoji: Smile, color: \"text-green-400\" },\n  { happiness: 2, emoji: Frown, color: \"text-yellow-400\" },\n  { happiness: 1, emoji: Angry, color: \"text-red-600\" },\n];\n\nexport const Feedback = () => {\n  const textRef = useRef<HTMLTextAreaElement>(null);\n  const [happiness, setHappiness] = useState<null | number>(null);\n\n  const [isSubmitted, setSubmissionState] = useState(false);\n  const { submitFeedback, isLoading, isSent } = useSubmitFeedback();\n\n  useEffect(() => {\n    if (!happiness) {\n      //cleaning up textarea\n      if (textRef.current) textRef.current!.value = \"\";\n    }\n  }, [happiness]);\n\n  useEffect(() => {\n    let timeout = null;\n    let submissionStateTimeout = null;\n\n    if (isSent) {\n      setSubmissionState(true);\n\n      //cleaning up textarea and customer happiness state\n      timeout = setTimeout(() => {\n        setHappiness(null);\n        if (textRef.current) textRef.current!.value = \"\";\n      }, 2000);\n\n      //cleaning up successful submission text 100ms later\n      submissionStateTimeout = setTimeout(() => {\n        setSubmissionState(false);\n      }, 2200);\n    }\n\n    return () => {\n      if (timeout) {\n        clearTimeout(timeout);\n      }\n      if (submissionStateTimeout) {\n        clearTimeout(submissionStateTimeout);\n      }\n    };\n  }, [isSent]);\n\n  return (\n    <motion.div\n      layout\n      initial={{ borderRadius: \"2rem\" }}\n      animate={\n        happiness ? { borderRadius: \"0.5rem\" } : { borderRadius: \"2rem\" }\n      }\n      className={twMerge(\n        \"w-fit overflow-hidden border py-2 shadow-xs dark:border-neutral-800 dark:bg-neutral-950\",\n      )}\n    >\n      <span className=\"flex items-center justify-center gap-3 pl-4 pr-2\">\n        <div className=\"text-sm text-black dark:text-neutral-400\">\n          Like our service?\n        </div>\n        <div className=\"flex items-center text-neutral-400\">\n          {feedback.map((e) => {\n            const EmojiIcon = e.emoji;\n            return (\n              <button\n                onClick={() =>\n                  setHappiness((prev) =>\n                    e.happiness === prev ? null : e.happiness,\n                  )\n                }\n                className={twMerge(\n                  \"flex h-9 w-9 items-center justify-center rounded-full transition-all\",\n                  happiness === e.happiness\n                    ? e.color\n                    : \"text-neutral-500 dark:text-neutral-400\",\n                )}\n                key={e.happiness}\n              >\n                <EmojiIcon size={18} />\n              </button>\n            );\n          })}\n        </div>\n      </span>\n      <motion.div\n        aria-hidden={happiness ? false : true}\n        initial={{ height: 0, y: 15 }}\n        className=\"px-2\"\n        transition={{ ease: \"easeInOut\", duration: 0.3 }}\n        animate={happiness ? { height: \"195px\", width: \"330px\" } : {}}\n      >\n        <AnimatePresence>\n          {!isSubmitted ? (\n            <motion.span exit={{ opacity: 0 }} initial={{ opacity: 1 }}>\n              <textarea\n                ref={textRef}\n                placeholder=\"Your app is awesoooome\"\n                className=\"min-h-32 w-full resize-none rounded-md border bg-transparent p-2 text-sm placeholder-neutral-400 focus:border-neutral-400 focus:outline-0 dark:border-neutral-800 dark:focus:border-white\"\n              />\n              <div className=\"flex h-fit w-full justify-end\">\n                <button\n                  onClick={() =>\n                    submitFeedback(happiness!, textRef.current!.value || \"\")\n                  }\n                  className={cn(\n                    \"mt-1 flex h-9 items-center justify-center rounded-md border bg-neutral-950 px-2 text-sm text-white dark:bg-white dark:text-neutral-950\",\n                    {\n                      \"bg-neutral-500 dark:bg-white dark:text-neutral-500\":\n                        isLoading,\n                    },\n                  )}\n                >\n                  {isLoading ? (\n                    <>\n                      <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                      Loading\n                    </>\n                  ) : (\n                    \"Submit\"\n                  )}\n                </button>\n              </div>\n            </motion.span>\n          ) : (\n            <motion.div\n              variants={container}\n              initial=\"hidden\"\n              animate=\"show\"\n              className=\"flex h-full w-full flex-col items-center justify-start gap-2 pt-9 text-sm font-normal\"\n            >\n              <motion.div\n                variants={item}\n                className=\"flex h-8 min-h-8 w-8 min-w-8 items-center justify-center rounded-full bg-blue-500 dark:bg-sky-500\"\n              >\n                <Check strokeWidth={2.5} size={16} className=\"stroke-white\" />\n              </motion.div>\n              <motion.div variants={item}>\n                Your feedback has been received!\n              </motion.div>\n              <motion.div variants={item}>Thank you for your help.</motion.div>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </motion.div>\n    </motion.div>\n  );\n};\n\nconst container = {\n  hidden: { opacity: 0, y: 20 },\n  show: {\n    opacity: 1,\n    y: 0,\n    transition: {\n      duration: 0.2,\n      staggerChildren: 0.04,\n    },\n  },\n};\n\nconst item = {\n  hidden: { y: 10 },\n  show: { y: 0 },\n};\n\nconst useSubmitFeedback = () => {\n  const [feedback, setFeedback] = useState<{\n    happiness: number;\n    feedback: string;\n  } | null>(null);\n  const [isLoading, setLoadingState] = useState(false);\n  //error never happens in case of this mockup btw\n  const [error, setError] = useState<{ error: any } | null>(null);\n  const [isSent, setRequestState] = useState(false);\n\n  //fake api call\n  const submitFeedback = (feedback: { happiness: number; feedback: string }) =>\n    new Promise((res) => setTimeout(() => res(feedback), 1000));\n\n  useEffect(() => {\n    if (feedback) {\n      setLoadingState(true);\n      setRequestState(false);\n\n      submitFeedback(feedback)\n        .then(() => {\n          setRequestState(true);\n          setError(null);\n        })\n        .catch(() => {\n          setRequestState(false);\n          setError({ error: \"some error\" });\n        })\n        .finally(() => setLoadingState(false));\n    }\n  }, [feedback]);\n\n  return {\n    submitFeedback: (happiness: number, feedback: string) =>\n      setFeedback({ happiness, feedback }),\n    isLoading,\n    error,\n    isSent,\n  };\n};\n",
      "type": "registry:component",
      "target": "components/spectrumui/feedback-demo.tsx"
    }
  ],
  "type": "registry:component"
}
