"use client";
import type { LucideIcon } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
export type AnimatedIconVariant =
| "draw"
| "wiggle"
| "spin"
| "bounce"
| "pop"
| "pulse"
| "nudge";
export interface AnimatedIconProps {
/** Which hover animation to play. */
variant: AnimatedIconVariant;
/** Icon size in px. */
size?: number;
className?: string;
/** Lucide icon component to animate. Ignored by the `draw` variant. */
icon?: LucideIcon;
/** SVG path `d` strings the `draw` variant renders. Defaults to a checkmark. */
paths?: string[];
}
const DEFAULT_PATHS = ["M5 13l4 4L19 7"];
/**
* Seven hover-played icon micro-interactions behind a single `variant` prop:
* `draw` self-draws its stroke (pathLength 0 → 1) — ignores `icon`, uses `paths`;
* `wiggle` rocks side to side; `spin` rotates a full turn; `bounce` springs
* vertically; `pop` springs its scale up and back; `pulse` loops a gentle
* scale while hovered; `nudge` springs a small step sideways. Reduced-motion
* renders a static icon (or a static checkmark for `draw`) with no animation.
*/
export function AnimatedIcon({
variant,
size = 28,
className,
icon: Icon,
paths = DEFAULT_PATHS,
}: AnimatedIconProps) {
const reduce = useReducedMotion();
if (variant === "draw") {
if (reduce) {
return (
);
}
return (
{paths.map((d) => (
))}
);
}
if (!Icon) return null;
if (reduce) {
return ;
}
switch (variant) {
case "wiggle":
return (
);
case "spin":
return (
);
case "bounce":
return (
);
case "pop":
return (
);
case "pulse":
return (
);
case "nudge":
return (
);
default:
return null;
}
}