Files

129 lines
5.5 KiB
TypeScript

import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "@radix-ui/react-slot"
import { cn } from "@/lib/utils"
/**
* Premium luxury button system.
* Gold primary (metallic gradient), sapphire secondary (navy gradient), shared hover/active.
* Light reflection (::after) on primary and secondary.
*/
const buttonVariants = cva(
[
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium",
"rounded-lg outline-none select-none",
"transition-all duration-[280ms] ease-out",
"hover:-translate-y-0.5 active:translate-y-0",
"disabled:pointer-events-none disabled:opacity-50 disabled:translate-y-0 disabled:hover:translate-y-0 disabled:active:translate-y-0",
"focus-visible:ring-[3px] focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
].join(" "),
{
variants: {
variant: {
primary: [
"relative overflow-hidden",
"bg-[linear-gradient(180deg,var(--btn-gold-from)_0%,var(--btn-gold-via)_50%,var(--btn-gold-to)_100%)]",
"text-[var(--btn-gold-text)]",
"shadow-[var(--btn-gold-shadow)]",
"hover:shadow-[var(--btn-gold-shadow-hover)] hover:shadow-[var(--btn-gold-glow-hover)]",
"active:shadow-[var(--btn-gold-shadow-active)]",
"focus-visible:ring-primary/30",
"after:content-[''] after:absolute after:inset-0 after:rounded-lg after:overflow-hidden after:pointer-events-none",
"after:bg-[linear-gradient(105deg,transparent_0%,rgba(255,255,255,0.38)_50%,transparent_100%)]",
"after:opacity-0 hover:after:opacity-100 hover:after:animate-[btn-shine_0.55s_ease-out]",
].join(" "),
secondary: [
"relative overflow-hidden",
"bg-[linear-gradient(180deg,var(--btn-sapphire-from)_0%,var(--btn-sapphire-to)_100%)]",
"text-secondary-foreground",
"shadow-[var(--btn-sapphire-shadow)]",
"hover:shadow-[var(--btn-sapphire-shadow-hover)] hover:shadow-[var(--btn-sapphire-glow-hover)]",
"active:shadow-[var(--btn-sapphire-shadow-active)]",
"focus-visible:ring-secondary/30",
"after:content-[''] after:absolute after:inset-0 after:rounded-lg after:overflow-hidden after:pointer-events-none",
"after:bg-[linear-gradient(105deg,transparent_0%,rgba(255,255,255,0.25)_50%,transparent_100%)]",
"after:opacity-0 hover:after:opacity-100 hover:after:animate-[btn-shine_0.55s_ease-out]",
].join(" "),
outline: [
"border-2 border-secondary bg-transparent text-secondary",
"shadow-sm",
"hover:bg-secondary/10 hover:shadow-md hover:shadow-secondary/20",
"active:bg-secondary/15 active:shadow-sm",
"focus-visible:ring-secondary/30",
].join(" "),
ghost: [
"bg-transparent",
"hover:bg-accent hover:text-accent-foreground",
"active:bg-accent/80",
"focus-visible:ring-accent/30",
].join(" "),
link: [
"text-primary underline-offset-4 shadow-none",
"hover:underline hover:opacity-90 hover:-translate-y-0.5 active:translate-y-0",
"focus-visible:ring-primary/20",
].join(" "),
destructive: [
"bg-destructive text-destructive-foreground shadow-sm",
"hover:bg-destructive/90 hover:shadow-md",
"active:bg-destructive/95 active:shadow-sm active:translate-y-0",
"focus-visible:ring-destructive/30",
].join(" "),
default: [
"relative overflow-hidden",
"bg-[linear-gradient(180deg,var(--btn-gold-from)_0%,var(--btn-gold-via)_50%,var(--btn-gold-to)_100%)]",
"text-[var(--btn-gold-text)]",
"shadow-[var(--btn-gold-shadow)]",
"hover:shadow-[var(--btn-gold-shadow-hover)] hover:shadow-[var(--btn-gold-glow-hover)]",
"active:shadow-[var(--btn-gold-shadow-active)]",
"focus-visible:ring-primary/30",
"after:content-[''] after:absolute after:inset-0 after:rounded-lg after:overflow-hidden after:pointer-events-none",
"after:bg-[linear-gradient(105deg,transparent_0%,rgba(255,255,255,0.38)_50%,transparent_100%)]",
"after:opacity-0 hover:after:opacity-100 hover:after:animate-[btn-shine_0.55s_ease-out]",
].join(" "),
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-lg px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }