{
  "name": "avatar-picker",
  "type": "registry:component",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "card",
    "button",
    "input"
  ],
  "files": [
    {
      "type": "registry:component",
      "content": "\"use client\";\n\n/**\n * @author: @dorianbaffier\n * @description: Avatar Picker\n * @version: 2.0.0\n * @date: 2026-02-22\n * @license: MIT\n * @website: https://kokonutui.com\n * @github: https://github.com/kokonut-labs/kokonutui\n */\n\nimport { Check, ChevronRight, User2 } from \"lucide-react\";\nimport type { Variants } from \"motion/react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport type { ReactNode } from \"react\";\nimport { useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { cn } from \"@/lib/utils\";\n\ninterface Avatar {\n  id: number;\n  svg: ReactNode;\n  alt: string;\n}\n\n// RGB values for the per-avatar color ring on the stage\nconst AVATAR_RGB: Record<number, string> = {\n  1: \"255, 0, 91\",\n  2: \"255, 125, 16\",\n  3: \"255, 0, 91\",\n  4: \"137, 252, 179\",\n};\n\nconst avatars: Avatar[] = [\n  {\n    id: 1,\n    svg: (\n      <svg\n        aria-label=\"Avatar 1\"\n        fill=\"none\"\n        height=\"40\"\n        role=\"img\"\n        viewBox=\"0 0 36 36\"\n        width=\"40\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <title>Avatar 1</title>\n        <mask\n          height=\"36\"\n          id=\":r111:\"\n          maskUnits=\"userSpaceOnUse\"\n          width=\"36\"\n          x=\"0\"\n          y=\"0\"\n        >\n          <rect fill=\"#FFFFFF\" height=\"36\" rx=\"72\" width=\"36\" />\n        </mask>\n        <g mask=\"url(#:r111:)\">\n          <rect fill=\"#ff005b\" height=\"36\" width=\"36\" />\n          <rect\n            fill=\"#ffb238\"\n            height=\"36\"\n            rx=\"6\"\n            transform=\"translate(9 -5) rotate(219 18 18) scale(1)\"\n            width=\"36\"\n            x=\"0\"\n            y=\"0\"\n          />\n          <g transform=\"translate(4.5 -4) rotate(9 18 18)\">\n            <path\n              d=\"M15 19c2 1 4 1 6 0\"\n              fill=\"none\"\n              stroke=\"#000000\"\n              strokeLinecap=\"round\"\n            />\n            <rect\n              fill=\"#000000\"\n              height=\"2\"\n              rx=\"1\"\n              stroke=\"none\"\n              width=\"1.5\"\n              x=\"10\"\n              y=\"14\"\n            />\n            <rect\n              fill=\"#000000\"\n              height=\"2\"\n              rx=\"1\"\n              stroke=\"none\"\n              width=\"1.5\"\n              x=\"24\"\n              y=\"14\"\n            />\n          </g>\n        </g>\n      </svg>\n    ),\n    alt: \"Avatar 1\",\n  },\n  {\n    id: 2,\n    svg: (\n      <svg\n        aria-label=\"Avatar 2\"\n        fill=\"none\"\n        height=\"40\"\n        role=\"img\"\n        viewBox=\"0 0 36 36\"\n        width=\"40\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <title>Avatar 2</title>\n        <mask\n          height=\"36\"\n          id=\":R4mrttb:\"\n          maskUnits=\"userSpaceOnUse\"\n          width=\"36\"\n          x=\"0\"\n          y=\"0\"\n        >\n          <rect fill=\"#FFFFFF\" height=\"36\" rx=\"72\" width=\"36\" />\n        </mask>\n        <g mask=\"url(#:R4mrttb:)\">\n          <rect fill=\"#ff7d10\" height=\"36\" width=\"36\" />\n          <rect\n            fill=\"#0a0310\"\n            height=\"36\"\n            rx=\"6\"\n            transform=\"translate(5 -1) rotate(55 18 18) scale(1.1)\"\n            width=\"36\"\n            x=\"0\"\n            y=\"0\"\n          />\n          <g transform=\"translate(7 -6) rotate(-5 18 18)\">\n            <path\n              d=\"M15 20c2 1 4 1 6 0\"\n              fill=\"none\"\n              stroke=\"#FFFFFF\"\n              strokeLinecap=\"round\"\n            />\n            <rect\n              fill=\"#FFFFFF\"\n              height=\"2\"\n              rx=\"1\"\n              stroke=\"none\"\n              width=\"1.5\"\n              x=\"14\"\n              y=\"14\"\n            />\n            <rect\n              fill=\"#FFFFFF\"\n              height=\"2\"\n              rx=\"1\"\n              stroke=\"none\"\n              width=\"1.5\"\n              x=\"20\"\n              y=\"14\"\n            />\n          </g>\n        </g>\n      </svg>\n    ),\n    alt: \"Avatar 2\",\n  },\n  {\n    id: 3,\n    svg: (\n      <svg\n        aria-label=\"Avatar 3\"\n        fill=\"none\"\n        height=\"40\"\n        role=\"img\"\n        viewBox=\"0 0 36 36\"\n        width=\"40\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <title>Avatar 3</title>\n        <mask\n          height=\"36\"\n          id=\":r11c:\"\n          maskUnits=\"userSpaceOnUse\"\n          width=\"36\"\n          x=\"0\"\n          y=\"0\"\n        >\n          <rect fill=\"#FFFFFF\" height=\"36\" rx=\"72\" width=\"36\" />\n        </mask>\n        <g mask=\"url(#:r11c:)\">\n          <rect fill=\"#0a0310\" height=\"36\" width=\"36\" />\n          <rect\n            fill=\"#ff005b\"\n            height=\"36\"\n            rx=\"36\"\n            transform=\"translate(-3 7) rotate(227 18 18) scale(1.2)\"\n            width=\"36\"\n            x=\"0\"\n            y=\"0\"\n          />\n          <g transform=\"translate(-3 3.5) rotate(7 18 18)\">\n            <path d=\"M13,21 a1,0.75 0 0,0 10,0\" fill=\"#FFFFFF\" />\n            <rect\n              fill=\"#FFFFFF\"\n              height=\"2\"\n              rx=\"1\"\n              stroke=\"none\"\n              width=\"1.5\"\n              x=\"12\"\n              y=\"14\"\n            />\n            <rect\n              fill=\"#FFFFFF\"\n              height=\"2\"\n              rx=\"1\"\n              stroke=\"none\"\n              width=\"1.5\"\n              x=\"22\"\n              y=\"14\"\n            />\n          </g>\n        </g>\n      </svg>\n    ),\n    alt: \"Avatar 3\",\n  },\n  {\n    id: 4,\n    svg: (\n      <svg\n        aria-label=\"Avatar 4\"\n        fill=\"none\"\n        height=\"40\"\n        role=\"img\"\n        viewBox=\"0 0 36 36\"\n        width=\"40\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <title>Avatar 4</title>\n        <mask\n          height=\"36\"\n          id=\":r1gg:\"\n          maskUnits=\"userSpaceOnUse\"\n          width=\"36\"\n          x=\"0\"\n          y=\"0\"\n        >\n          <rect fill=\"#FFFFFF\" height=\"36\" rx=\"72\" width=\"36\" />\n        </mask>\n        <g mask=\"url(#:r1gg:)\">\n          <rect fill=\"#d8fcb3\" height=\"36\" width=\"36\" />\n          <rect\n            fill=\"#89fcb3\"\n            height=\"36\"\n            rx=\"6\"\n            transform=\"translate(9 -5) rotate(219 18 18) scale(1)\"\n            width=\"36\"\n            x=\"0\"\n            y=\"0\"\n          />\n          <g transform=\"translate(4.5 -4) rotate(9 18 18)\">\n            <path\n              d=\"M15 19c2 1 4 1 6 0\"\n              fill=\"none\"\n              stroke=\"#000000\"\n              strokeLinecap=\"round\"\n            />\n            <rect\n              fill=\"#000000\"\n              height=\"2\"\n              rx=\"1\"\n              stroke=\"none\"\n              width=\"1.5\"\n              x=\"10\"\n              y=\"14\"\n            />\n            <rect\n              fill=\"#000000\"\n              height=\"2\"\n              rx=\"1\"\n              stroke=\"none\"\n              width=\"1.5\"\n              x=\"24\"\n              y=\"14\"\n            />\n          </g>\n        </g>\n      </svg>\n    ),\n    alt: \"Avatar 4\",\n  },\n];\n\ninterface ProfileSetupProps {\n  onComplete?: (data: { username: string; avatarId: number }) => void;\n  className?: string;\n}\n\nconst containerVariants: Variants = {\n  initial: { opacity: 0 },\n  animate: {\n    opacity: 1,\n    transition: { staggerChildren: 0.06, delayChildren: 0.05 },\n  },\n};\n\nconst thumbnailVariants: Variants = {\n  initial: { opacity: 0, y: 6 },\n  animate: {\n    opacity: 1,\n    y: 0,\n    transition: { duration: 0.28, ease: \"easeOut\" },\n  },\n};\n\nexport default function ProfileSetup({\n  onComplete,\n  className,\n}: ProfileSetupProps) {\n  const [selectedAvatar, setSelectedAvatar] = useState<Avatar>(avatars[0]);\n  const [username, setUsername] = useState(\"\");\n  const [isFocused, setIsFocused] = useState(false);\n  const shouldReduceMotion = useReducedMotion();\n\n  const handleAvatarSelect = (avatar: Avatar) => {\n    if (avatar.id === selectedAvatar.id) return;\n    setSelectedAvatar(avatar);\n  };\n\n  const handleSubmit = () => {\n    if (username.trim() && onComplete) {\n      onComplete({\n        username: username.trim(),\n        avatarId: selectedAvatar.id,\n      });\n    }\n  };\n\n  const isValid = username.trim().length >= 3;\n  const showError = username.trim().length > 0 && username.trim().length < 3;\n  const rgb = AVATAR_RGB[selectedAvatar.id];\n\n  return (\n    <Card\n      className={cn(\n        \"relative mx-auto w-full max-w-[400px] border-border bg-card\",\n        className\n      )}\n    >\n      <CardContent className=\"p-8\">\n        <div className=\"space-y-8\">\n          {/* Header */}\n          <div className=\"space-y-1 text-center\">\n            <h2 className=\"font-semibold text-xl tracking-tight\">\n              Pick Your Avatar\n            </h2>\n            <p className=\"text-muted-foreground text-sm\">\n              Choose one to get started\n            </p>\n          </div>\n\n          {/* Avatar Stage */}\n          <div className=\"flex flex-col items-center gap-4\">\n            {/*\n             * Two-div approach: outer div holds the animated color ring\n             * (no overflow-hidden so box-shadow renders cleanly),\n             * inner div clips the avatar SVG.\n             * scale-[4] fills the 160px circle with the avatar's background.\n             */}\n            <div className=\"relative h-40 w-40\">\n              {/* Animated per-avatar color ring */}\n              <motion.div\n                animate={{\n                  boxShadow: `0 0 0 2px rgba(${rgb}, 0.55), 0 6px 24px rgba(${rgb}, 0.18)`,\n                }}\n                aria-hidden=\"true\"\n                className=\"pointer-events-none absolute inset-0 rounded-full\"\n                transition={\n                  shouldReduceMotion\n                    ? { duration: 0 }\n                    : { duration: 0.45, ease: \"easeOut\" }\n                }\n              />\n\n              {/* Avatar circle — clips content */}\n              <div className=\"relative h-full w-full overflow-hidden rounded-full\">\n                <AnimatePresence mode=\"wait\">\n                  <motion.div\n                    animate={{ opacity: 1 }}\n                    className=\"absolute inset-0 flex items-center justify-center\"\n                    exit={{ opacity: 0 }}\n                    initial={{ opacity: 0 }}\n                    key={selectedAvatar.id}\n                    transition={\n                      shouldReduceMotion\n                        ? { duration: 0 }\n                        : { duration: 0.2, ease: \"easeOut\" }\n                    }\n                  >\n                    {/* scale-[4]: 40px SVG × 4 = 160px, fills the circle */}\n                    <div className=\"scale-[4] transform\">\n                      {selectedAvatar.svg}\n                    </div>\n                  </motion.div>\n                </AnimatePresence>\n              </div>\n            </div>\n\n            {/* Avatar name — fades with selection */}\n            <AnimatePresence mode=\"wait\">\n              <motion.span\n                animate={{ opacity: 1 }}\n                className=\"text-[11px] text-muted-foreground uppercase tracking-[0.12em]\"\n                exit={{ opacity: 0 }}\n                initial={{ opacity: 0 }}\n                key={selectedAvatar.id}\n                transition={\n                  shouldReduceMotion\n                    ? { duration: 0 }\n                    : { duration: 0.16, ease: \"easeOut\" }\n                }\n              >\n                {selectedAvatar.alt}\n              </motion.span>\n            </AnimatePresence>\n\n            {/* Thumbnail strip */}\n            <motion.div\n              animate=\"animate\"\n              className=\"flex gap-3\"\n              initial=\"initial\"\n              variants={containerVariants}\n            >\n              {avatars.map((avatar) => {\n                const isSelected = selectedAvatar.id === avatar.id;\n                return (\n                  <motion.button\n                    aria-label={`Select ${avatar.alt}`}\n                    aria-pressed={isSelected}\n                    className={cn(\n                      \"relative h-14 w-14 overflow-hidden rounded-xl border bg-muted transition-[opacity,box-shadow] duration-200 ease-out\",\n                      isSelected\n                        ? \"border-foreground/20 opacity-100 ring-2 ring-foreground/70 ring-offset-2 ring-offset-background\"\n                        : \"border-border opacity-50 hover:opacity-100\"\n                    )}\n                    key={avatar.id}\n                    onClick={() => handleAvatarSelect(avatar)}\n                    type=\"button\"\n                    variants={thumbnailVariants}\n                    whileHover={shouldReduceMotion ? {} : { scale: 1.06 }}\n                    whileTap={shouldReduceMotion ? {} : { scale: 0.94 }}\n                  >\n                    <div className=\"absolute inset-0 flex items-center justify-center\">\n                      <div className=\"scale-[2.3] transform\">{avatar.svg}</div>\n                    </div>\n                    {isSelected && (\n                      <div className=\"absolute -right-0.5 -bottom-0.5 flex h-5 w-5 items-center justify-center rounded-full bg-foreground\">\n                        <Check\n                          aria-hidden=\"true\"\n                          className=\"h-3 w-3 text-background\"\n                        />\n                      </div>\n                    )}\n                  </motion.button>\n                );\n              })}\n            </motion.div>\n          </div>\n\n          {/* Username field */}\n          <div className=\"space-y-4\">\n            <div className=\"space-y-2\">\n              <div className=\"flex items-center justify-between\">\n                <label className=\"font-medium text-sm\" htmlFor=\"username\">\n                  Username\n                </label>\n                <span\n                  className={cn(\n                    \"text-xs tabular-nums transition-colors duration-200 ease-out\",\n                    username.length >= 18\n                      ? \"text-amber-500 dark:text-amber-400\"\n                      : \"text-muted-foreground/50\"\n                  )}\n                >\n                  {username.length}/20\n                </span>\n              </div>\n\n              <div className=\"relative\">\n                <Input\n                  autoComplete=\"username\"\n                  className={cn(\n                    \"h-10 pl-9 text-sm\",\n                    showError &&\n                      \"border-destructive/50 focus-visible:ring-destructive\"\n                  )}\n                  id=\"username\"\n                  maxLength={20}\n                  name=\"username\"\n                  onBlur={() => setIsFocused(false)}\n                  onChange={(e) => setUsername(e.target.value)}\n                  onFocus={() => setIsFocused(true)}\n                  placeholder=\"your_username…\"\n                  spellCheck={false}\n                  type=\"text\"\n                  value={username}\n                />\n                <User2\n                  aria-hidden=\"true\"\n                  className={cn(\n                    \"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transition-colors duration-200 ease-out\",\n                    isFocused ? \"text-foreground\" : \"text-muted-foreground\"\n                  )}\n                />\n              </div>\n\n              <AnimatePresence>\n                {showError && (\n                  <motion.p\n                    animate={{ opacity: 1, y: 0 }}\n                    className=\"ml-0.5 text-destructive text-xs\"\n                    exit={{ opacity: 0, y: -4 }}\n                    initial={{ opacity: 0, y: -4 }}\n                    role=\"alert\"\n                    transition={{ duration: 0.15, ease: \"easeOut\" }}\n                  >\n                    Username must be at least 3 characters\n                  </motion.p>\n                )}\n              </AnimatePresence>\n            </div>\n\n            <Button\n              className=\"group h-10 w-full text-sm\"\n              disabled={!isValid}\n              onClick={handleSubmit}\n              type=\"button\"\n            >\n              Get Started\n              <ChevronRight\n                aria-hidden=\"true\"\n                className=\"ml-1 h-4 w-4 transition-transform duration-200 ease-out group-hover:translate-x-0.5\"\n              />\n            </Button>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "path": "/components/kokonutui/avatar-picker.tsx",
      "target": "components/kokonutui/avatar-picker.tsx"
    }
  ]
}