Try hover/click the icons
animated-icons-001.tsx
"use client";
import { motion, useReducedMotion } from "framer-motion";
import { ChevronRight } from "lucide-react";
import { useState } from "react";
import { cn } from "@/lib/utils";
const SPRING = { type: "spring", stiffness: 420, damping: 28, mass: 0.7 } as const;
const tileClassName =
"grid size-[88px] place-items-center rounded-3xl bg-white/[0.045] text-ink/70 transition-colors duration-200 hover:bg-white/[0.07] hover:text-ink";
function ArrowIcon({ className }: { className?: string }) {
return (
<div
className={cn(
"group flex size-full cursor-pointer items-center justify-center",
className,
)}
>
<div className="relative grid cursor-pointer items-center justify-center">
<ChevronRight className="transition-all duration-500 ease-out group-hover:translate-x-0.5" />
<div className="absolute right-[9px] h-[2px] w-3 origin-right scale-x-0 rounded-[1px] bg-current transition-all duration-300 ease-out group-hover:right-[7px] group-hover:scale-x-100" />
</div>
</div>
);
}
function MenuIcon({ className }: { className?: string }) {
const reduce = useReducedMotion();
const [toggle, setToggle] = useState(false);
return (
<div
onClick={() => setToggle((x) => !x)}
className={cn(
"group flex size-full cursor-pointer items-center justify-center",
className,
)}
>
<div className="relative grid size-4 cursor-pointer items-center justify-center">
<motion.div
initial={false}
animate={{ y: toggle ? 0 : -5, rotate: toggle ? 45 : 0 }}
transition={reduce ? { duration: 0 } : SPRING}
className="absolute h-0.5 w-full rounded-full bg-current"
/>
<motion.div
initial={false}
animate={{ opacity: toggle ? 0 : 1, scaleX: toggle ? 0.4 : 1 }}
transition={
reduce ? { duration: 0 } : { ...SPRING, stiffness: 550, damping: 32 }
}
className="absolute h-0.5 w-full rounded-full bg-current"
/>
<motion.div
initial={false}
animate={{ y: toggle ? 0 : 5, rotate: toggle ? -45 : 0 }}
transition={reduce ? { duration: 0 } : SPRING}
className="absolute h-0.5 w-full rounded-full bg-current"
/>
</div>
</div>
);
}
function VolumeIcon({ className }: { className?: string }) {
const reduce = useReducedMotion();
const [isMuted, setIsMuted] = useState(false);
return (
<div
onClick={() => setIsMuted((x) => !x)}
className={cn(
"group flex size-full cursor-pointer items-center justify-center",
className,
)}
>
<motion.div
initial={false}
className="relative flex size-5 items-center justify-center"
animate={
reduce
? {}
: { rotate: isMuted ? [0, -14, 6, -3, 0] : 0 }
}
transition={
reduce ? { duration: 0 } : { duration: 0.45, ease: [0.22, 1, 0.36, 1] }
}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className={cn("size-5", className)}
>
<path
fill="currentColor"
stroke="none"
d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
/>
<motion.g
initial={false}
animate={
reduce ? {} : { opacity: isMuted ? 0 : 1, scale: isMuted ? 0.8 : 1 }
}
transition={reduce ? { duration: 0 } : SPRING}
style={{ transformOrigin: "12px 12px" }}
>
<path
fill="none"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
d="M16 9a5 5 0 0 1 0 6"
/>
<path
fill="none"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
d="M19.364 18.364a9 9 0 0 0 0-12.728"
/>
</motion.g>
</svg>
<div className="absolute inset-0 flex items-center justify-center">
<div className="rotate-[-40deg] overflow-hidden">
<motion.div
initial={false}
animate={{ scaleY: isMuted ? 1 : 0 }}
transition={
reduce
? { duration: 0 }
: {
type: "spring",
stiffness: 500,
damping: 30,
mass: 0.6,
}
}
style={{ transformOrigin: "50% 0%" }}
className="h-[18px] w-fit rounded-full"
>
<div className="flex h-full w-[3.5px] items-center justify-center rounded-full bg-black">
<div className="h-full w-[1.5px] rounded-full bg-white" />
</div>
</motion.div>
</div>
</div>
</motion.div>
</div>
);
}
export function AnimatedIcons({
className,
}: {
className?: string;
}) {
return (
<div className={cn("flex flex-wrap items-center justify-center gap-2", className)}>
<div className={tileClassName}>
<ArrowIcon />
</div>
<div className={tileClassName}>
<MenuIcon />
</div>
<div className={tileClassName}>
<VolumeIcon />
</div>
</div>
);
}
export { ArrowIcon, MenuIcon, VolumeIcon };