Add animations for button icons
This commit is contained in:
parent
379fd1fc27
commit
65465d813d
|
@ -1,4 +1,9 @@
|
||||||
import { ComponentType, CSSProperties } from "react";
|
import {
|
||||||
|
AnimationEventHandler,
|
||||||
|
ComponentType,
|
||||||
|
CSSProperties,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import "./buttonIcon.css";
|
import "./buttonIcon.css";
|
||||||
import { attributes } from "../utils/attributes";
|
import { attributes } from "../utils/attributes";
|
||||||
|
|
||||||
|
@ -9,7 +14,10 @@ interface CustomStyleCSS extends CSSProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
Icon: ComponentType;
|
Icon: ComponentType<{
|
||||||
|
className?: string;
|
||||||
|
onAnimationEnd?: AnimationEventHandler | undefined;
|
||||||
|
}>;
|
||||||
|
|
||||||
variant?: "big" | "small";
|
variant?: "big" | "small";
|
||||||
|
|
||||||
|
@ -33,6 +41,11 @@ type Props = {
|
||||||
* Apply custom classname.
|
* Apply custom classname.
|
||||||
*/
|
*/
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an animation on click
|
||||||
|
*/
|
||||||
|
animation?: "buzz" | "bounce";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ButtonIcon({
|
export function ButtonIcon({
|
||||||
|
@ -41,19 +54,30 @@ export function ButtonIcon({
|
||||||
style,
|
style,
|
||||||
onMouseEnter,
|
onMouseEnter,
|
||||||
onMouseLeave,
|
onMouseLeave,
|
||||||
|
className = "",
|
||||||
|
animation,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
variant = "big",
|
variant = "big",
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const [animationClassName, setAnimationClassName] = useState("");
|
||||||
|
|
||||||
|
const onInternalClick = () => {
|
||||||
|
setAnimationClassName("buttonIcon--" + animation);
|
||||||
|
onClick?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAnimationEnd = () => setAnimationClassName("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`buttonIcon buttonIcon--${variant}`}
|
className={`buttonIcon buttonIcon--${variant} ${className}`}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
onClick={onClick}
|
onClick={onInternalClick}
|
||||||
style={style}
|
style={style}
|
||||||
{...attributes({ disabled: disabled, "aria-disabled": disabled })}
|
{...attributes({ disabled: disabled, "aria-disabled": disabled })}
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon className={animationClassName} onAnimationEnd={onAnimationEnd} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,3 +35,69 @@
|
||||||
color: var(--codex-color-disabled);
|
color: var(--codex-color-disabled);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttonIcon--buzz {
|
||||||
|
-webkit-animation-name: buzz;
|
||||||
|
animation-name: buzz;
|
||||||
|
-webkit-animation-duration: 0.45s;
|
||||||
|
animation-duration: 0.45s;
|
||||||
|
-webkit-animation-timing-function: linear;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
-webkit-animation-iteration-count: 5;
|
||||||
|
animation-iteration-count: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes buzz {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translateX(0) rotate(0deg);
|
||||||
|
transform: translateX(0px) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
-webkit-transform: translateX(-3px) rotate(-15deg);
|
||||||
|
transform: translateX(-3px) rotate(-15deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translateX(0) rotate(0deg);
|
||||||
|
transform: translateX(0px) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
-webkit-transform: translateX(3px) rotate(15deg);
|
||||||
|
transform: translateX(3px) rotate(15deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
-webkit-transform: translateX(0) rotate(0deg);
|
||||||
|
transform: translateX(0px) rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonIcon--bounce {
|
||||||
|
-webkit-animation-name: bounce;
|
||||||
|
animation-name: bounce;
|
||||||
|
-webkit-animation-duration: 0.6s;
|
||||||
|
animation-duration: 0.6s;
|
||||||
|
-webkit-animation-timing-function: linear;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
-webkit-animation-iteration-count: 1;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translateY(0) scale(1);
|
||||||
|
transform: translateY(0px) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
-webkit-transform: translateY(-10px) scale(1);
|
||||||
|
transform: translateY(-10px) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
-webkit-transform: translateY(0) scale(1);
|
||||||
|
transform: translateY(0px) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { fn } from "@storybook/test";
|
import { fn } from "@storybook/test";
|
||||||
import { Plus } from "lucide-react";
|
import { Copy, Download, Plus } from "lucide-react";
|
||||||
import { ButtonIcon } from "../src/components/ButtonIcon/ButtonIcon";
|
import { ButtonIcon } from "../src/components/ButtonIcon/ButtonIcon";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
@ -42,6 +42,20 @@ export const Disabled: Story = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BuzzAnimation: Story = {
|
||||||
|
args: {
|
||||||
|
Icon: Copy,
|
||||||
|
animation: "buzz"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BounceAnimation: Story = {
|
||||||
|
args: {
|
||||||
|
Icon: Download,
|
||||||
|
animation: "bounce"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const CustomStyle: Story = {
|
export const CustomStyle: Story = {
|
||||||
args: {
|
args: {
|
||||||
Icon: Plus,
|
Icon: Plus,
|
||||||
|
|
Loading…
Reference in New Issue