{ "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 = {\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 \n Avatar 1\n \n \n \n \n \n \n \n \n \n \n \n \n \n ),\n alt: \"Avatar 1\",\n },\n {\n id: 2,\n svg: (\n \n Avatar 2\n \n \n \n \n \n \n \n \n \n \n \n \n \n ),\n alt: \"Avatar 2\",\n },\n {\n id: 3,\n svg: (\n \n Avatar 3\n \n \n \n \n \n \n \n \n \n \n \n \n \n ),\n alt: \"Avatar 3\",\n },\n {\n id: 4,\n svg: (\n \n Avatar 4\n \n \n \n \n \n \n \n \n \n \n \n \n \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(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 \n \n
\n {/* Header */}\n
\n

\n Pick Your Avatar\n

\n

\n Choose one to get started\n

\n
\n\n {/* Avatar Stage */}\n
\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
\n {/* Animated per-avatar color ring */}\n \n\n {/* Avatar circle — clips content */}\n
\n \n \n {/* scale-[4]: 40px SVG × 4 = 160px, fills the circle */}\n
\n {selectedAvatar.svg}\n
\n \n
\n
\n
\n\n {/* Avatar name — fades with selection */}\n \n \n {selectedAvatar.alt}\n \n \n\n {/* Thumbnail strip */}\n \n {avatars.map((avatar) => {\n const isSelected = selectedAvatar.id === avatar.id;\n return (\n handleAvatarSelect(avatar)}\n type=\"button\"\n variants={thumbnailVariants}\n whileHover={shouldReduceMotion ? {} : { scale: 1.06 }}\n whileTap={shouldReduceMotion ? {} : { scale: 0.94 }}\n >\n
\n
{avatar.svg}
\n
\n {isSelected && (\n
\n \n
\n )}\n \n );\n })}\n \n
\n\n {/* Username field */}\n
\n
\n
\n \n = 18\n ? \"text-amber-500 dark:text-amber-400\"\n : \"text-muted-foreground/50\"\n )}\n >\n {username.length}/20\n \n
\n\n
\n 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 \n
\n\n \n {showError && (\n \n Username must be at least 3 characters\n \n )}\n \n
\n\n \n Get Started\n \n \n
\n
\n
\n \n );\n}\n", "path": "/components/kokonutui/avatar-picker.tsx", "target": "components/kokonutui/avatar-picker.tsx" } ] }