"use client" import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( "inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-primary-foreground hover:bg-destructive/90", cool: "dark:inset-shadow-2xs dark:inset-shadow-white/10 bg-linear-to-t border border-b-2 border-zinc-950/40 from-primary to-primary/85 shadow-md shadow-primary/20 ring-1 ring-inset ring-white/25 transition-[filter] duration-200 hover:brightness-110 active:brightness-90 dark:border-x-0 text-primary-foreground dark:text-primary-foreground dark:border-t-0 dark:border-primary/50 dark:ring-white/5", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2", sm: "h-8 rounded-md px-3 text-xs", lg: "h-10 rounded-md px-8", icon: "h-9 w-9", }, }, defaultVariants: { variant: "default", size: "default", }, } ) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( ) } ) Button.displayName = "Button" export { Button, buttonVariants, liquidbuttonVariants, LiquidButton } const liquidbuttonVariants = cva( "inline-flex items-center transition-colors justify-center cursor-pointer gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-transparent hover:scale-105 duration-300 transition text-primary", destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 text-xs gap-1.5 px-4 has-[>svg]:px-4", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", xl: "h-12 rounded-md px-8 has-[>svg]:px-6", xxl: "h-14 rounded-md px-10 has-[>svg]:px-8", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "xxl", }, } ) function LiquidButton({ className, variant, size, asChild = false, children, ...props }: React.ComponentProps<"button"> & VariantProps & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <>
{children}
) } function GlassFilter() { return ( {/* Generate turbulent noise for distortion */} {/* Blur the turbulence pattern slightly */} {/* Displace the source graphic with the noise */} {/* Apply overall blur on the final result */} {/* Output the result */} ); } type ColorVariant = | "default" | "primary" | "success" | "error" | "gold" | "bronze"; interface MetalButtonProps extends React.ButtonHTMLAttributes { variant?: ColorVariant; } const colorVariants: Record< ColorVariant, { outer: string; inner: string; button: string; textColor: string; textShadow: string; } > = { default: { outer: "bg-gradient-to-b from-[#000] to-[#A0A0A0]", inner: "bg-gradient-to-b from-[#FAFAFA] via-[#3E3E3E] to-[#E5E5E5]", button: "bg-gradient-to-b from-[#B9B9B9] to-[#969696]", textColor: "text-white", textShadow: "[text-shadow:_0_-1px_0_rgb(80_80_80_/_100%)]", }, primary: { outer: "bg-gradient-to-b from-[#000] to-[#A0A0A0]", inner: "bg-gradient-to-b from-primary via-secondary to-muted", button: "bg-gradient-to-b from-primary to-primary/40", textColor: "text-white", textShadow: "[text-shadow:_0_-1px_0_rgb(30_58_138_/_100%)]", }, success: { outer: "bg-gradient-to-b from-[#005A43] to-[#7CCB9B]", inner: "bg-gradient-to-b from-[#E5F8F0] via-[#00352F] to-[#D1F0E6]", button: "bg-gradient-to-b from-[#9ADBC8] to-[#3E8F7C]", textColor: "text-[#FFF7F0]", textShadow: "[text-shadow:_0_-1px_0_rgb(6_78_59_/_100%)]", }, error: { outer: "bg-gradient-to-b from-[#5A0000] to-[#FFAEB0]", inner: "bg-gradient-to-b from-[#FFDEDE] via-[#680002] to-[#FFE9E9]", button: "bg-gradient-to-b from-[#F08D8F] to-[#A45253]", textColor: "text-[#FFF7F0]", textShadow: "[text-shadow:_0_-1px_0_rgb(146_64_14_/_100%)]", }, gold: { outer: "bg-gradient-to-b from-[#917100] to-[#EAD98F]", inner: "bg-gradient-to-b from-[#FFFDDD] via-[#856807] to-[#FFF1B3]", button: "bg-gradient-to-b from-[#FFEBA1] to-[#9B873F]", textColor: "text-[#FFFDE5]", textShadow: "[text-shadow:_0_-1px_0_rgb(178_140_2_/_100%)]", }, bronze: { outer: "bg-gradient-to-b from-[#864813] to-[#E9B486]", inner: "bg-gradient-to-b from-[#EDC5A1] via-[#5F2D01] to-[#FFDEC1]", button: "bg-gradient-to-b from-[#FFE3C9] to-[#A36F3D]", textColor: "text-[#FFF7F0]", textShadow: "[text-shadow:_0_-1px_0_rgb(124_45_18_/_100%)]", }, }; const metalButtonVariants = ( variant: ColorVariant = "default", isPressed: boolean, isHovered: boolean, isTouchDevice: boolean, ) => { const colors = colorVariants[variant]; const transitionStyle = "all 250ms cubic-bezier(0.1, 0.4, 0.2, 1)"; return { wrapper: cn( "relative inline-flex transform-gpu rounded-md p-[1.25px] will-change-transform", colors.outer, ), wrapperStyle: { transform: isPressed ? "translateY(2.5px) scale(0.99)" : "translateY(0) scale(1)", boxShadow: isPressed ? "0 1px 2px rgba(0, 0, 0, 0.15)" : isHovered && !isTouchDevice ? "0 4px 12px rgba(0, 0, 0, 0.12)" : "0 3px 8px rgba(0, 0, 0, 0.08)", transition: transitionStyle, transformOrigin: "center center", }, inner: cn( "absolute inset-[1px] transform-gpu rounded-lg will-change-transform", colors.inner, ), innerStyle: { transition: transitionStyle, transformOrigin: "center center", filter: isHovered && !isPressed && !isTouchDevice ? "brightness(1.05)" : "none", }, button: cn( "relative z-10 m-[1px] rounded-md inline-flex h-11 transform-gpu cursor-pointer items-center justify-center overflow-hidden rounded-md px-6 py-2 text-sm leading-none font-semibold will-change-transform outline-none", colors.button, colors.textColor, colors.textShadow, ), buttonStyle: { transform: isPressed ? "scale(0.97)" : "scale(1)", transition: transitionStyle, transformOrigin: "center center", filter: isHovered && !isPressed && !isTouchDevice ? "brightness(1.02)" : "none", }, }; }; const ShineEffect = ({ isPressed }: { isPressed: boolean }) => { return (
); }; export const MetalButton = React.forwardRef< HTMLButtonElement, MetalButtonProps >(({ children, className, variant = "default", ...props }, ref) => { const [isPressed, setIsPressed] = React.useState(false); const [isHovered, setIsHovered] = React.useState(false); const [isTouchDevice, setIsTouchDevice] = React.useState(false); React.useEffect(() => { setIsTouchDevice("ontouchstart" in window || navigator.maxTouchPoints > 0); }, []); const buttonText = children || "Button"; const variants = metalButtonVariants( variant, isPressed, isHovered, isTouchDevice, ); const handleInternalMouseDown = () => { setIsPressed(true); }; const handleInternalMouseUp = () => { setIsPressed(false); }; const handleInternalMouseLeave = () => { setIsPressed(false); setIsHovered(false); }; const handleInternalMouseEnter = () => { if (!isTouchDevice) { setIsHovered(true); } }; const handleInternalTouchStart = () => { setIsPressed(true); }; const handleInternalTouchEnd = () => { setIsPressed(false); }; const handleInternalTouchCancel = () => { setIsPressed(false); }; return (
); }); MetalButton.displayName = "MetalButton";