{
  "name": "feedback-widget",
  "dependencies": [
    "motion",
    "react-markdown",
    "@radix-ui/react-toggle-group"
  ],
  "files": [
    {
      "path": "ui/feedback-widget.tsx",
      "content": "\"use client\";\n\nimport * as ToggleGroup from \"@radix-ui/react-toggle-group\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport { cn } from \"@/lib/utils\";\n\nconst EMOJIS = [\n  {\n    id: \"very-sad\",\n    label: \"Terrible\",\n    icon: (\n      <svg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <circle cx=\"12\" cy=\"12\" r=\"10\" />\n        <path d=\"M16 16s-1.5-2-4-2-4 2-4 2\" />\n        <path d=\"M9 9h.01\" />\n        <path d=\"M15 9h.01\" />\n        <path d=\"M9 13v2\" stroke=\"#3b82f6\" />\n        <path d=\"M15 13v2\" stroke=\"#3b82f6\" />\n      </svg>\n    ),\n  },\n  {\n    id: \"sad\",\n    label: \"Bad\",\n    icon: (\n      <svg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <circle cx=\"12\" cy=\"12\" r=\"10\" />\n        <path d=\"M16 16s-1.5-2-4-2-4 2-4 2\" />\n        <line x1=\"9\" y1=\"9\" x2=\"9.01\" y2=\"9\" />\n        <line x1=\"15\" y1=\"9\" x2=\"15.01\" y2=\"9\" />\n      </svg>\n    ),\n  },\n  {\n    id: \"neutral\",\n    label: \"Okay\",\n    icon: (\n      <svg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <circle cx=\"12\" cy=\"12\" r=\"10\" />\n        <path d=\"M8 13s1.5 2 4 2 4-2 4-2\" />\n        <line x1=\"9\" y1=\"9\" x2=\"9.01\" y2=\"9\" />\n        <line x1=\"15\" y1=\"9\" x2=\"15.01\" y2=\"9\" />\n      </svg>\n    ),\n  },\n  {\n    id: \"happy\",\n    label: \"Amazing\",\n    icon: (\n      <svg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <circle cx=\"12\" cy=\"12\" r=\"10\" />\n        <path d=\"M8 13s1.5 2 4 2 4-2 4-2\" />\n        <path\n          d=\"M9 9l.5 1.5l1.5 .5l-1.5 .5l-.5 1.5l-.5-1.5l-1.5-.5l1.5-.5z\"\n          fill=\"#f97316\"\n          stroke=\"none\"\n        />\n        <path\n          d=\"M15 9l.5 1.5l1.5 .5l-1.5 .5l-.5 1.5l-.5-1.5l-1.5-.5l1.5-.5z\"\n          fill=\"#f97316\"\n          stroke=\"none\"\n        />\n      </svg>\n    ),\n  },\n];\n\ninterface FeedbackWidgetProps {\n  onSubmit?: (data: { rating: string; feedback: string }) => void;\n  onClose?: () => void;\n  className?: string;\n  /** Text shown in the collapsed state */\n  label?: string;\n  /** Placeholder for the textarea */\n  placeholder?: string;\n}\n\nexport function FeedbackWidget({\n  onSubmit,\n  onClose,\n  className,\n  label = \"Was this helpful?\",\n  placeholder = \"Your feedback...\",\n}: FeedbackWidgetProps) {\n  const [value, setValue] = React.useState<string>(\"\");\n  const [feedback, setFeedback] = React.useState(\"\");\n  const [isPreview, setIsPreview] = React.useState(false);\n  const [isSubmitting, setIsSubmitting] = React.useState(false);\n  const isExpanded = value !== \"\";\n  const containerRef = React.useRef<HTMLDivElement>(null);\n\n  const springTransition = {\n    type: \"spring\",\n    stiffness: 300,\n    damping: 30,\n    mass: 1,\n  } as const;\n\n  const handleValueChange = (val: string) => {\n    if (val === \"\" || val === value) {\n      setValue(\"\");\n      setIsPreview(false);\n      onClose?.();\n    } else {\n      setValue(val);\n      // Auto-focus the textarea after expansion\n      setTimeout(() => {\n        containerRef.current?.querySelector(\"textarea\")?.focus();\n      }, 100);\n    }\n  };\n\n  const handleSend = async () => {\n    if (!feedback.trim()) return;\n\n    setIsSubmitting(true);\n    try {\n      await onSubmit?.({ rating: value, feedback });\n      setValue(\"\");\n      setFeedback(\"\");\n      setIsPreview(false);\n    } finally {\n      setIsSubmitting(false);\n    }\n  };\n\n  return (\n    <div className={cn(\"flex items-center justify-center p-4\", className)}>\n      <motion.div\n        ref={containerRef}\n        layout\n        transition={springTransition}\n        initial={false}\n        className={cn(\n          \"overflow-hidden border border-zinc-200 bg-white text-zinc-900 shadow-[0_32px_64px_-16px_rgba(0,0,0,0.15)] dark:border-white/10 dark:bg-zinc-950 dark:text-white dark:shadow-[0_32px_64px_-16px_rgba(0,0,0,0.8)]\",\n          isExpanded ? \"w-full max-w-[420px] rounded-[28px]\" : \"rounded-full\",\n        )}\n      >\n        <motion.div\n          layout=\"position\"\n          className=\"px-4 py-2 md:px-5 md:py-2.5\"\n          transition={springTransition}\n        >\n          <div className=\"flex items-center justify-between gap-6\">\n            <motion.span\n              layout=\"position\"\n              transition={springTransition}\n              className=\"ml-2 cursor-default select-none whitespace-nowrap font-medium text-[14px] text-zinc-600 dark:text-zinc-400\"\n            >\n              {label}\n            </motion.span>\n\n            <ToggleGroup.Root\n              type=\"single\"\n              value={value}\n              onValueChange={handleValueChange}\n              className=\"flex items-center gap-1.5\"\n            >\n              {EMOJIS.map((emoji) => (\n                <ToggleGroup.Item key={emoji.id} value={emoji.id} asChild>\n                  <button\n                    title={emoji.label}\n                    className={cn(\n                      \"relative rounded-full p-2 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-blue-500\",\n                      value === emoji.id\n                        ? \"text-white\"\n                        : \"text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600 dark:text-zinc-500 dark:hover:bg-white/5 dark:hover:text-zinc-300\",\n                    )}\n                  >\n                    <motion.div\n                      layout=\"position\"\n                      transition={springTransition}\n                      className=\"relative z-10 flex h-5 w-5 scale-110 items-center justify-center transition-transform active:scale-90\"\n                    >\n                      {emoji.icon}\n                    </motion.div>\n                    {value === emoji.id && (\n                      <motion.div\n                        layoutId=\"active-bg\"\n                        className=\"absolute inset-0 rounded-full bg-blue-600\"\n                        transition={springTransition}\n                      />\n                    )}\n                  </button>\n                </ToggleGroup.Item>\n              ))}\n            </ToggleGroup.Root>\n          </div>\n\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            {isExpanded && (\n              <motion.div\n                initial={{\n                  height: 0,\n                  opacity: 0,\n                  scale: 0.98,\n                  filter: \"blur(4px)\",\n                }}\n                animate={{\n                  height: \"auto\",\n                  opacity: 1,\n                  scale: 1,\n                  filter: \"blur(0px)\",\n                }}\n                exit={{\n                  height: 0,\n                  opacity: 0,\n                  scale: 0.98,\n                  filter: \"blur(4px)\",\n                  transition: {\n                    height: { duration: 0.3, ease: [0.32, 0, 0.67, 0] },\n                    opacity: { duration: 0.15 },\n                    scale: { duration: 0.2 },\n                    filter: { duration: 0.2 },\n                  },\n                }}\n                transition={{\n                  height: { ...springTransition, bounce: 0 },\n                  opacity: { duration: 0.25 },\n                  scale: { ...springTransition, damping: 25 },\n                  filter: { duration: 0.3 },\n                }}\n                className=\"overflow-hidden\"\n              >\n                <div className=\"px-1 pt-6 pb-2\">\n                  <div className=\"mb-2.5 flex items-center justify-between\">\n                    <span className=\"select-none font-bold text-[10px] text-zinc-500 uppercase tracking-[0.1em] dark:text-zinc-500\">\n                      {isPreview ? \"Preview\" : \"Feedback\"}\n                    </span>\n                    <button\n                      onClick={() => setIsPreview(!isPreview)}\n                      className=\"rounded-md bg-zinc-100 px-2 py-0.5 font-semibold text-[11px] text-zinc-600 transition-colors hover:bg-zinc-200 hover:text-zinc-900 dark:bg-white/5 dark:text-zinc-400 dark:hover:bg-white/10 dark:hover:text-white\"\n                    >\n                      {isPreview ? \"Edit\" : \"Preview\"}\n                    </button>\n                  </div>\n\n                  <div className=\"group/textarea relative\">\n                    <AnimatePresence mode=\"wait\">\n                      {isPreview ? (\n                        <motion.div\n                          key=\"preview\"\n                          initial={{ opacity: 0, y: 5 }}\n                          animate={{ opacity: 1, y: 0 }}\n                          exit={{ opacity: 0, y: -5 }}\n                          className=\"prose prose-sm scrollbar-none dark:prose-invert h-[140px] w-full max-w-none overflow-y-auto rounded-2xl border border-zinc-200 bg-zinc-50 p-4 text-[14px] text-zinc-700 leading-relaxed dark:border-white/5 dark:bg-zinc-900/50 dark:text-zinc-300\"\n                        >\n                          <ReactMarkdown>\n                            {feedback || \"*Nothing to preview...*\"}\n                          </ReactMarkdown>\n                        </motion.div>\n                      ) : (\n                        <motion.textarea\n                          key=\"editor\"\n                          initial={{ opacity: 0, y: 5 }}\n                          animate={{ opacity: 1, y: 0 }}\n                          exit={{ opacity: 0, y: -5 }}\n                          autoFocus\n                          placeholder={placeholder}\n                          value={feedback}\n                          onChange={(e) => setFeedback(e.target.value)}\n                          className=\"scrollbar-none h-[140px] w-full resize-none rounded-2xl border border-zinc-200 bg-zinc-50 p-4 text-[14px] text-zinc-800 leading-relaxed transition-all placeholder:text-zinc-400 focus:border-zinc-300 focus:outline-none dark:border-white/5 dark:bg-zinc-900/50 dark:text-zinc-200 dark:focus:border-white/20 dark:placeholder:text-zinc-600\"\n                        />\n                      )}\n                    </AnimatePresence>\n\n                    {!isPreview && (\n                      <div className=\"pointer-events-none absolute right-4 bottom-3 flex select-none items-center gap-1.5 opacity-40 transition-opacity group-focus-within/textarea:opacity-80\">\n                        <span className=\"font-bold text-[10px] text-zinc-400 tracking-tight dark:text-zinc-500\">\n                          M↓\n                        </span>\n                        <span className=\"font-bold text-[10px] text-zinc-400 tracking-tight dark:text-zinc-500\">\n                          supported\n                        </span>\n                      </div>\n                    )}\n                  </div>\n                </div>\n\n                <motion.div\n                  initial={{ y: 20, opacity: 0 }}\n                  animate={{ y: 0, opacity: 1 }}\n                  exit={{ y: 20, opacity: 0 }}\n                  transition={{ delay: 0.1, ...springTransition }}\n                  className=\"mt-3 flex items-center justify-between border-zinc-200 border-t pt-4 dark:border-white/5\"\n                >\n                  <p className=\"font-medium text-[11px] text-zinc-500 dark:text-zinc-500\">\n                    We appreciate your input.\n                  </p>\n                  <button\n                    onClick={handleSend}\n                    disabled={!feedback.trim() || isSubmitting}\n                    className=\"relative rounded-xl bg-zinc-900 px-6 py-2 font-bold text-[13px] text-white transition-all hover:bg-zinc-800 active:scale-95 disabled:pointer-events-none disabled:opacity-30 disabled:grayscale dark:bg-white dark:text-black dark:hover:bg-zinc-200\"\n                  >\n                    {isSubmitting ? (\n                      <motion.div\n                        animate={{ rotate: 360 }}\n                        transition={{\n                          repeat: Number.POSITIVE_INFINITY,\n                          duration: 1,\n                          ease: \"linear\",\n                        }}\n                        className=\"h-4 w-4 rounded-full border-2 border-white/20 border-t-white dark:border-black/20 dark:border-t-black\"\n                      />\n                    ) : (\n                      \"Send Feedback\"\n                    )}\n                  </button>\n                </motion.div>\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </motion.div>\n      </motion.div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}