Button Effects
March 2025
"use client";
import {cn} from "@/app/_lib/cn";
import React, {useState} from "react";
// Styles in tailwind
// --animate-swipe-right: swipe-right 0.6s cubic-bezier(0.83, 0, 0.17, 1) forwards;
// @keyframes swipe-right {
// 0% {
// transform: translate3d(0, 0, 0);
// }
// 100% {
// transform: translate3d(150%, 0, 0);
// }
// }
export default function ButtonEffects() {
return (
<article className="relative grid min-h-96 place-items-center rounded-xl border border-white/10 bg-white/5 p-3 md:aspect-[3/2] md:min-h-0">
<div className="grid w-full sm:grid-cols-2 content-center items-center justify-center justify-items-center gap-5 sm:gap-y-10 gap-y-7">
<InvertButton />
<ArrowSwipeButton />
<ScaleButton />
<HoldAndReleaseButton />
</div>
</article>
);
}
const InvertButton = () => {
return (
<button
className={cn(
// Base styles
"font-inter group relative w-fit cursor-pointer overflow-hidden rounded-full border border-current px-6 py-2 text-lg font-medium tracking-wide text-white",
// Effect styles
"transition-transform duration-700 ease-out will-change-transform hover:scale-x-[1.02] hover:ease-[cubic-bezier(.34,5.56,.64,1)]"
)}>
<span
data-text={"Invert Button"}
className={cn(
"relative block overflow-hidden",
// After child
"after:absolute after:left-0 after:z-1 after:translate-y-full after:transform after:text-black after:duration-700 after:ease-[cubic-bezier(.4,0,0,1)] after:will-change-transform after:content-[attr(data-text)] group-hover:after:translate-y-0"
)}>
<span className="inline-block duration-700 ease-[cubic-bezier(.4,0,0,1)] group-hover:-translate-y-full">
Invert Button
</span>
</span>
<span className="absolute inset-0 translate-y-full rounded-[50%_50%_0_0] bg-white transition-all duration-500 ease-[cubic-bezier(.4,0,0,1)] group-hover:translate-y-0 group-hover:rounded-none"></span>
</button>
);
};
const ScaleButton = () => {
return (
<button
className={cn(
// Base styles
"group relative w-fit cursor-pointer px-6 py-2 text-lg font-medium tracking-wide text-white"
)}>
<div className="absolute inset-0 overflow-hidden rounded-full border border-current transition duration-600 ease-[cubic-bezier(0.1,0,0.3,1)] will-change-transform group-hover:scale-[1.1]">
<span className="absolute -inset-[10%] scale-0 rounded-full bg-current ease-[cubic-bezier(0.1,0,0.3,1)] will-change-transform group-hover:scale-100 group-hover:transition group-hover:duration-600"></span>
<span className="group absolute -inset-[10%] rounded-full bg-current opacity-0 transition-opacity duration-600 ease-[cubic-bezier(0.1,0,0.3,1)] group-hover:opacity-100 group-hover:delay-400 group-hover:duration-[0.01s]"></span>
</div>
<span className="inline-block mix-blend-difference">
Scale Button
</span>
</button>
);
};
const ArrowSwipeButton = () => {
return (
<button
className={cn(
// Base styles
"font-inter group relative inline-flex w-fit cursor-pointer items-center gap-x-2 overflow-hidden rounded-full border border-current py-1 pr-1 pl-6 text-lg font-medium tracking-wide text-white"
)}>
<span>Arrow Swipe</span>
<span className="relative grid size-9 place-items-center overflow-hidden">
<span className="absolute inset-0 scale-0 transform rounded-full bg-white duration-600 ease-[cubic-bezier(0.83,0,0.17,1)] group-hover:scale-100"></span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="group-hover:animate-swipe-right size-5 mix-blend-difference">
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
/>
</svg>
<span className="absolute top-1/2 -translate-x-[150%] -translate-y-1/2 mix-blend-difference">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="group-hover:animate-swipe-right size-5 mix-blend-difference">
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
/>
</svg>
</span>
</span>
</button>
);
};
const HoldAndReleaseButton = () => {
const [isMouseDown, setIsMouseDown] = useState(false);
const handleMouseDown = () => {
setIsMouseDown(true);
};
const handleMouseUp = () => {
setIsMouseDown(false);
};
return (
<button
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onTouchStart={handleMouseDown}
onTouchEnd={handleMouseUp}
className={cn(
// Base styles
"font-inter group relative w-fit cursor-pointer overflow-hidden rounded-full p-0.5 px-6 py-2 text-lg font-medium tracking-wide text-white transition-transform will-change-transform",
isMouseDown ? "scale-90 duration-1500" : "duration-300"
)}>
<span
className={cn(
"absolute inset-0 rounded-full border border-current"
)}></span>
<span
className={cn(
"absolute inset-0 origin-left border border-current bg-white transition-transform",
isMouseDown
? "scale-x-100 duration-1500"
: "scale-x-0 duration-300"
)}></span>
<span className="inline-flex items-center gap-x-3 mix-blend-difference">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="size-5">
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
Hold and Release
</span>
</button>
);
};