Files
pieced-portal/src/components/ui/button.tsx
admin f2a9637058
All checks were successful
Build and Push / build (push) Successful in 2m25s
mobile nav, locale-preserving navigation, accent button contrast
2026-05-29 22:12:51 +02:00

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}
/>
);
}
);