Creator Card
Make Creator Card and showcase creativity.
Code
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client"
import type React from "react"
import { useState, useEffect } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { Video, Phone, MessageSquare, MessageCircle, Settings, Star, Heart, Sun, Moon } from "lucide-react"
import Image from "next/image"
import { cn } from "@/lib/utils"
import { useAnimationControls } from "@/hooks/use-animation-controls"
import { useTheme } from "next-themes"
// Types
interface Creator {
id: string
name: string
isVerified?: boolean
title: string
avatar: string
rating: number
ratingCount: number
isFavorite?: boolean
}
// Color palette
const colors = {
primary: {
light: "bg-amber-500",
dark: "bg-amber-600",
text: "text-amber-500",
fill: "fill-amber-500",
},
secondary: {
light: "bg-orange-400",
dark: "bg-orange-500",
text: "text-orange-400",
fill: "fill-orange-400",
},
accent: {
light: "bg-red-500",
dark: "bg-red-600",
text: "text-red-500",
fill: "fill-red-500",
},
background: {
light: "bg-white",
dark: "bg-gray-900",
},
card: {
light: "bg-amber-50",
dark: "bg-gray-800",
},
text: {
primary: {
light: "text-gray-900",
dark: "text-white",
},
secondary: {
light: "text-gray-600",
dark: "text-gray-300",
},
},
button: {
active: {
light: "bg-amber-500",
dark: "bg-amber-600",
},
inactive: {
light: "bg-amber-100",
dark: "bg-gray-700",
},
},
}
// Theme toggle component
function ThemeToggle() {
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) return null
return (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="absolute right-4 top-4 z-10 p-2 rounded-full bg-amber-100 dark:bg-gray-700"
aria-label="Toggle theme"
>
<AnimatePresence mode="wait">
<motion.div
key={theme === "dark" ? "dark" : "light"}
initial={{ scale: 0.8, opacity: 0, rotate: -180 }}
animate={{ scale: 1, opacity: 1, rotate: 0 }}
exit={{ scale: 0.8, opacity: 0, rotate: 180 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
{theme === "dark" ? <Sun className="w-5 h-5 text-amber-500" /> : <Moon className="w-5 h-5 text-amber-600" />}
</motion.div>
</AnimatePresence>
</motion.button>
)
}
// Sub-components
function ActionButton({
icon,
label,
isActive = false,
onClick,
}: {
icon: React.ReactNode
label: string
isActive?: boolean
onClick?: () => void
}) {
return (
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={cn(
"flex items-center justify-center w-12 h-12 rounded-full",
isActive
? "bg-amber-500 dark:bg-amber-600 text-white"
: "bg-amber-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200",
)}
onClick={onClick}
aria-label={label}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
}}
>
{icon}
</motion.button>
)
}
function AvatarWithStatus({
src,
alt,
size = 64,
}: {
src: string
alt: string
size?: number
}) {
return (
<motion.div
className="relative"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
delay: 0.1,
}}
>
<div className="rounded-2xl overflow-hidden bg-amber-100 dark:bg-amber-200 shadow-md">
<Image src={src || "/placeholder.svg"} alt={alt} width={size} height={size} className="object-cover" />
</div>
</motion.div>
)
}
function Rating({
value,
count,
}: {
value: number
count: number
}) {
return (
<motion.div
className="flex items-center gap-1 text-sm"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 }}
>
<Star className="w-5 h-5 fill-amber-500 text-amber-500 dark:fill-amber-400 dark:text-amber-400" />
<span className="text-gray-800 dark:text-white">
{value} ({count})
</span>
</motion.div>
)
}
function FavoriteButton({
initialState = false,
onToggle,
}: {
initialState?: boolean
onToggle?: (state: boolean) => void
}) {
const [isFavorite, setIsFavorite] = useState(initialState)
const handleToggle = () => {
const newState = !isFavorite
setIsFavorite(newState)
if (onToggle) onToggle(newState)
}
return (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={handleToggle}
className="absolute right-4 top-4"
aria-label={isFavorite ? "Remove from favorites" : "Add to favorites"}
>
<AnimatePresence mode="wait">
<motion.div
key={isFavorite ? "filled" : "outline"}
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
{isFavorite ? (
<Heart className="w-6 h-6 fill-red-500 text-red-500 dark:fill-red-400 dark:text-red-400" />
) : (
<Heart className="w-6 h-6 text-red-500 dark:text-red-400" />
)}
</motion.div>
</AnimatePresence>
</motion.button>
)
}
// Main component
export function CreatorCard({ creator }: { creator: Creator }) {
const [activeAction, setActiveAction] = useState<string>("video")
const { scope, handleMouseEnter, handleMouseLeave } = useAnimationControls()
const { resolvedTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
const actions = [
{ id: "video", icon: <Video className="w-5 h-5" />, label: "Video call" },
{ id: "call", icon: <Phone className="w-5 h-5" />, label: "Call" },
{ id: "message", icon: <MessageSquare className="w-5 h-5" />, label: "Message" },
{ id: "chat", icon: <MessageCircle className="w-5 h-5" />, label: "Chat" },
{ id: "settings", icon: <Settings className="w-5 h-5" />, label: "Settings" },
]
const handleActionClick = (id: string) => {
setActiveAction(id)
}
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.05,
delayChildren: 0.3,
},
},
}
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 20 } },
}
// Don't render until mounted to avoid hydration mismatch
if (!mounted) {
return null
}
return (
<motion.div
ref={scope}
className="relative max-w-xs mx-auto"
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<motion.div
className="rounded-3xl overflow-hidden shadow-xl bg-gradient-to-br from-amber-50 to-orange-50 dark:from-gray-800 dark:to-gray-900"
layoutId={`creator-card-${creator.id}`}
>
<div className="p-4">
<div className="flex items-center gap-3">
<AvatarWithStatus src={creator.avatar} alt={creator.name} />
<div className="flex-1">
<motion.div
className="flex items-center gap-1"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<h3 className="text-gray-900 dark:text-white text-lg font-medium">{creator.name}</h3>
{creator.isVerified && <span className="text-orange-500 dark:text-orange-400 text-lg">🎮</span>}
</motion.div>
<motion.p
className="text-gray-600 dark:text-gray-300 text-sm"
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.25 }}
>
{creator.title}
</motion.p>
<Rating value={creator.rating} count={creator.ratingCount} />
</div>
<FavoriteButton initialState={creator.isFavorite} />
</div>
</div>
<motion.div
className="flex justify-between px-4 py-3 gap-2 bg-amber-50/80 dark:bg-gray-800/80 backdrop-blur-sm"
variants={container}
initial="hidden"
animate="show"
>
{actions.map((action) => (
<motion.div key={action.id} variants={item}>
<ActionButton
icon={action.icon}
label={action.label}
isActive={activeAction === action.id}
onClick={() => handleActionClick(action.id)}
/>
</motion.div>
))}
</motion.div>
</motion.div>
</motion.div>
)
}
// Demo page component
export function CreatorCardDemo() {
const creator = {
id: "1",
name: "Karan",
isVerified: true,
title: "Founder SwiftUI⚡",
avatar: "/favicon-2.png?height=64&width=64",
rating: 5,
ratingCount: 35,
isFavorite: false,
}
return (
<div className="w-full max-w-md">
<ThemeToggle />
<CreatorCard creator={creator} />
</div>
)
}