54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
import { Button, ButtonProps } from '@mantine/core';
|
|
import { forwardRef, useMemo, useEffect } from 'react';
|
|
|
|
type AnimatedButtonProps = ButtonProps & {
|
|
animated?: [string, string];
|
|
animationDurationMs?: number;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
export const AnimatedButton = forwardRef<HTMLButtonElement, AnimatedButtonProps>(
|
|
({ animated, animationDurationMs = 2000, style, onClick, disabled, ...rest }, ref) => {
|
|
const animationName = useMemo(() => {
|
|
if (!animated) return undefined;
|
|
const safe = (s: string) => s.replace(/[^a-zA-Z0-9]/g, '');
|
|
return `abg_${safe(animated[0])}_${safe(animated[1])}`;
|
|
}, [animated]);
|
|
|
|
useEffect(() => {
|
|
if (!animated || !animationName) return;
|
|
const id = `__${animationName}`;
|
|
let styleEl = document.getElementById(id) as HTMLStyleElement | null;
|
|
if (!styleEl) {
|
|
styleEl = document.createElement('style');
|
|
styleEl.id = id;
|
|
document.head.appendChild(styleEl);
|
|
}
|
|
styleEl.textContent = `@keyframes ${animationName}{0%{background-position:-200% 0;}100%{background-position:200% 0;}}`;
|
|
}, [animated, animationName]);
|
|
|
|
return (
|
|
<Button
|
|
ref={ref}
|
|
{...rest}
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
style={
|
|
animated && animationName && !disabled
|
|
? {
|
|
background: animated[0],
|
|
backgroundImage: `linear-gradient(90deg, transparent, ${animated[1]}, transparent)`,
|
|
backgroundSize: '50% 100%',
|
|
backgroundRepeat: 'no-repeat',
|
|
animation: `${animationName} ${animationDurationMs}ms linear infinite`,
|
|
willChange: 'background-position',
|
|
position: 'relative',
|
|
overflow: 'hidden',
|
|
...style,
|
|
}
|
|
: style
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
); |