Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 267x 267x 21652x 21652x 113x 21539x 267x | import { forwardRef, type ButtonHTMLAttributes, type HTMLAttributes } from "react";
import { Slot, Slottable, type SlotProps } 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 justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary-500 text-foreground-inverse hover:bg-primary-600 active:bg-primary-700",
destructive:
"bg-error text-foreground-inverse hover:bg-error active:bg-error",
outline:
"border border-border-default bg-background-elevated text-foreground-default hover:bg-background-muted hover:border-border-strong",
secondary:
"bg-secondary-500 text-foreground-inverse hover:bg-secondary-600 active:bg-secondary-700",
ghost:
"text-foreground-default hover:bg-background-muted hover:text-foreground-default",
link: "text-primary-600 underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3 text-xs",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
isLoading?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
className,
variant,
size,
asChild = false,
isLoading,
children,
disabled,
...props
},
ref,
) => {
const loadingSpinner = isLoading ? (
<svg
className="h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
aria-hidden="true"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
) : null;
if (asChild) {
// Pass children to Slot as an array (not a Fragment): Slot uses
// React.Children.toArray + find(isSlottable), which only flattens
// arrays. A Fragment wrapper would be treated as a single element
// and the className would end up merged onto the Fragment (where
// it's silently dropped) instead of the Link/anchor inside.
return (
<Slot
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...(props as HTMLAttributes<HTMLElement>)}
>
{loadingSpinner}
<Slottable>{children as SlotProps["children"]}</Slottable>
</Slot>
);
}
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
disabled={disabled || isLoading}
{...props}
>
{loadingSpinner}
{children}
</button>
);
},
);
Button.displayName = "Button";
export { Button };
|