59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import { forwardRef } from "react";
|
|
|
|
/**
|
|
* Shared button primitive.
|
|
*
|
|
* Why this exists
|
|
* ---------------
|
|
* The accent fill (#00d4aa) is bright; white text on it measures ~1.9:1,
|
|
* which fails WCAG even for large/UI text. Dark text (surface-0) on the
|
|
* same accent is ~10:1. The codebase had ~40 hand-rolled accent buttons,
|
|
* most using `text-white`. This component centralises the correct token
|
|
* (`text-surface-0` on accent) so the contrast can't drift again — reach
|
|
* for `<Button>` instead of re-deriving the class string.
|
|
*/
|
|
|
|
type Variant = "primary" | "secondary" | "ghost" | "danger";
|
|
type Size = "sm" | "md";
|
|
|
|
const BASE =
|
|
"inline-flex items-center justify-center gap-1.5 font-medium rounded-lg " +
|
|
"transition-colors cursor-pointer focus:outline-none focus-visible:ring-2 " +
|
|
"focus-visible:ring-accent/50 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
|
|
const VARIANTS: Record<Variant, string> = {
|
|
// surface-0 (dark) text — the contrast-correct pairing for the accent.
|
|
primary: "bg-accent text-surface-0 hover:bg-accent-dim shadow-sm shadow-accent/20",
|
|
secondary:
|
|
"bg-surface-2 text-text-primary border border-border hover:bg-surface-3 hover:border-border-active",
|
|
ghost: "text-text-secondary hover:text-text-primary hover:bg-surface-2",
|
|
danger: "bg-error text-surface-0 hover:opacity-90",
|
|
};
|
|
|
|
const SIZES: Record<Size, string> = {
|
|
sm: "text-xs px-3 py-1.5",
|
|
md: "text-sm px-4 py-2",
|
|
};
|
|
|
|
export interface ButtonProps
|
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
variant?: Variant;
|
|
size?: Size;
|
|
}
|
|
|
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
function Button(
|
|
{ variant = "primary", size = "md", className = "", type = "button", ...rest },
|
|
ref
|
|
) {
|
|
return (
|
|
<button
|
|
ref={ref}
|
|
type={type}
|
|
className={`${BASE} ${VARIANTS[variant]} ${SIZES[size]} ${className}`}
|
|
{...rest}
|
|
/>
|
|
);
|
|
}
|
|
);
|