Button
Clickable elements
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
cn(
//* base *//
"relative flex min-h-[44px] min-w-[44px] items-center justify-center",
"text-[17px] leading-[22px] font-medium",
"rounded-md ring-offset-white *:pointer-events-none",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"[font-feature-settings:'liga'_off,_'clig'_off]",
//* icon *//
"[&_[data-slot='icon']]:stroke-[2.25px]",
"[&_[data-slot='icon']]:transition-opacity [&_[data-slot='icon']]:duration-300",
"[&_[data-slot='icon']]:disabled:text-[color-mix(in_sRGB,white_10%,#5E5E5E_45%)]",
//* disabled *//
"disabled:pointer-events-none disabled:text-[color-mix(in_sRGB,white_10%,#5E5E5E_45%)]",
//* before *//
"before:[filter:blur(0.25px)] before:absolute before:inset-0 before:z-0 before:rounded-md",
"before:disabled:[linear-gradient(0deg,rgba(94,94,94,0.07)_0%,rgba(94,94,94,0.07)_100%),rgba(255,255,255,0.04)]",
"before:[transform:translateX(var(--btn-bg-translate-x))_translateY(var(--btn-bg-translate-y))]",
"before:[background-blend-mode:color-dodge,lighten] before:transition-opacity before:duration-300",
"before:[background:linear-gradient(0deg,rgba(94,94,94,0.24)_0%,rgba(94,94,94,0.24)_100%),rgba(255,255,255,0.12)]",
"before:opacity-0"
),
{
variants: {
/**
* @default "default"
*/
variant: {
default: cn(
"text-foreground/90",
//* icon *//
"[&_[data-slot='icon']]:text-foreground",
"[&_[data-slot='icon']]:opacity-[0.96]",
//* before *//
"before:opacity-75 before:hover:opacity-[0.96]"
),
secondary: cn(
"text-foreground/50 hover:text-foreground/90 transition-colors",
//* icon *//
"[&_[data-slot='icon']]:text-foreground",
"[&_[data-slot='icon']]:opacity-60",
"[&_[data-slot='icon']]:hover:opacity-[0.96]",
//* before *//
"before:hover:opacity-50 before:opacity-0",
"before:hover:[background:linear-gradient(0deg,rgba(94,94,94,0.24)_0%,rgba(94,94,94,0.24)_100%),rgba(255,255,255,0.12)]",
//* before[data-active]
"[&[data-active=true]]:before:opacity-40",
"[&[data-active=true]]:before:hover:[background:linear-gradient(0deg,rgba(94,94,94,0.24)_0%,rgba(94,94,94,0.24)_100%),rgba(255,255,255,0.12)]"
),
/* bg-destructive text-destructive-foreground */
destructive: cn(
"before:bg-destructive text-destructive-foreground/90",
//* icon *//
"[&_[data-slot='icon']]:text-destructive",
"[&_[data-slot='icon']]:opacity-70",
"[&_[data-slot='icon']]:hover:opacity-[0.96]",
//* before *//
"before:hover:opacity-50 before:opacity-0",
"before:hover:[background:linear-gradient(0deg,rgba(94,94,94,0.24)_0%,rgba(94,94,94,0.24)_100%),rgba(255,255,255,0.12)]"
),
selected: cn(
"text-background/[0.96]",
//* icon *//
"[&_[data-slot='icon']]:text-background [&_[data-slot='icon']]:z-[1]",
"before:[background:hsla(var(--foreground)/0.96)] before:text-background/90",
"before:hover:opacity-100 before:opacity-100"
),
link: cn(
"text-[#5ac8f5]",
//* icon *//
"[&_[data-slot='icon']]:text-[#5ac8f5]",
"[&_[data-slot='icon']]:opacity-70",
"[&_[data-slot='icon']]:hover:opacity-[0.96]",
//* before *//
"before:hover:opacity-50 before:opacity-0",
"before:hover:[background:linear-gradient(0deg,rgba(94,94,94,0.24)_0%,rgba(94,94,94,0.24)_100%),rgba(255,255,255,0.12)]"
),
},
/**
* @default "default"
*/
size: {
default: "h-[2.75rem] px-[20px]",
list: "h-[60px] px-[20px]",
icon: "h-[2.75rem] w-[2.75rem]",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
type ButtonVariant = VariantProps<typeof buttonVariants>;
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, ButtonVariant {
asChild?: boolean;
}
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
function ButtonGroup({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn(
"flex items-center justify-center gap-2 p-3",
"*:rounded-full [&_button:before]:rounded-full",
className
)}
{...props}
>
{children}
</div>
);
}
export { Button, buttonVariants, ButtonGroup };
export type { ButtonVariant, ButtonProps };The Button Component is a custom implementation of a button using React and Framer Motion. It provides a dynamic button that responds to user interactions and changes appearance based on the elements it hovers over.
Examples
import { Ellipsis, PenBox, Share } from "lucide-react";
import { Button, ButtonGroup } from "@/components/core/button";
import { Window } from "@/components/core/window";
export const ButtonExample = () => {
return (
<div className="flex flex-col items-center justify-center gap-4">
<ButtonGroup>
<Button>Action</Button>
<Button disabled>Action</Button>
<Button variant="secondary">Action</Button>
</ButtonGroup>
<Window thickness="thinner">
<ButtonGroup>
<Button variant="selected" className="rounded-full" size="icon">
<Share data-slot="icon" />
</Button>
<Button className="rounded-full" size="icon">
<Ellipsis data-slot="icon" />
</Button>
<Button className="rounded-full" size="icon" disabled>
<PenBox data-slot="icon" />
</Button>
</ButtonGroup>
</Window>
</div>
);
};API
| Prop | Type | Default |
|---|---|---|
variant? | "default" | "secondary" | "destructive" | "selected" | "link" | null | "default" |
size? | "default" | "list" | "icon" | null | "default" |
Edit on GitHub
Last updated on