icone animate

This commit is contained in:
Marco Pedone 2026-05-12 16:12:40 +02:00
parent d303c189a9
commit 74f3e10fae

View file

@ -0,0 +1,138 @@
"use client";
import {
domAnimation,
type Easing,
LazyMotion,
m,
type SVGMotionProps,
} from "framer-motion";
import { useState } from "react";
// https://www.chamaac.com/components/animated-icons
interface IconProps extends SVGMotionProps<SVGSVGElement> {
size?: number;
duration?: number;
strokeWidth?: number;
isHovered?: boolean;
repeatDelay?: number;
ease?: Easing;
}
export const ExternalLinkIcon = (props: IconProps) => {
const {
size = 28,
duration = 1.5,
strokeWidth = 2,
isHovered = false,
repeatDelay = 1,
ease = "easeInOut",
className,
...restProps
} = props;
const [isHoveredInternal, setIsHoveredInternal] = useState(false);
const shouldAnimate = isHovered ? isHoveredInternal : true;
const transition = {
duration: duration,
ease: ease,
repeat: isHovered ? 0 : Infinity,
repeatDelay: repeatDelay,
};
return (
<LazyMotion features={domAnimation}>
<m.svg
{...restProps}
className={className}
fill="none"
height={size}
onMouseEnter={() => isHovered && setIsHoveredInternal(true)}
onMouseLeave={() => isHovered && setIsHoveredInternal(false)}
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={strokeWidth}
viewBox="0 0 24 24"
width={size}
xmlns="http://www.w3.org/2000/svg"
>
<path d="M0 0h24v24H0z" fill="none" stroke="none" />
<path d="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6" />
<m.path
animate={
shouldAnimate ? { x: [0, 2, 0], y: [0, -2, 0] } : { x: 0, y: 0 }
}
d="M11 13l9 -9"
transition={transition}
/>
<m.path
animate={
shouldAnimate ? { x: [0, 2, 0], y: [0, -2, 0] } : { x: 0, y: 0 }
}
d="M15 4h5v5"
transition={transition}
/>
</m.svg>
</LazyMotion>
);
};
export const SearchIcon = (props: IconProps) => {
const {
size = 28, // Icon size in pixels
duration = 1.2, // Animation duration in seconds
strokeWidth = 2, // SVG stroke width
isHovered = false, // When true, animate only on hover
repeatDelay = 1, // Delay between animation loops (seconds)
ease = "easeInOut", // Animation easing function
className,
...restProps
} = props;
const [isHoveredInternal, setIsHoveredInternal] = useState(false);
const shouldAnimate = isHovered ? isHoveredInternal : true;
const groupAnimationProps = {
animate: shouldAnimate ? { rotate: [0, -25, 15, 0] } : { rotate: 0 },
transition: {
duration: duration,
ease: ease,
repeat: isHovered ? 0 : Infinity,
repeatDelay: repeatDelay,
},
};
return (
<LazyMotion features={domAnimation}>
<m.svg
{...restProps}
className={className}
fill="none"
height={size}
onMouseEnter={() => isHovered && setIsHoveredInternal(true)}
onMouseLeave={() => isHovered && setIsHoveredInternal(false)}
overflow="visible"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={strokeWidth}
viewBox="0 0 24 24"
width={size}
xmlns="http://www.w3.org/2000/svg"
>
<path d="M0 0h24v24H0z" fill="none" stroke="none" />
<m.g
{...groupAnimationProps}
style={{ originX: "21px", originY: "21px" }}
>
<m.circle cx="10" cy="10" r="7" />
<m.path d="M21 21l-6 -6" />
</m.g>
</m.svg>
</LazyMotion>
);
};