Files
server-26/drb-frontend/components/ui/Button.tsx
T
Logan Cusano c6bc712b54
Build & Deploy / Build & push images (push) Successful in 4m10s
Build & Deploy / Deploy to VM (push) Successful in 1m57s
Frontend redesign chunk 1: design tokens and type
Replace hardcoded dark-palette Tailwind classes with semantic CSS custom
properties (page/surface/raised/line/ink/accent/sev-moderate/sev-major/
map-*) defined on :root (light) and .dark (dark), wired through
tailwind.config.ts theme.extend.colors. Add IBM Plex Sans/Mono via
next/font/google: sans for everything a person reads, mono reserved for
machine identifiers only. Drop font-mono from body and Button's base
classes. Existing !important light-mode overrides kept temporarily so
nothing goes unreadable mid-migration (removed in chunk 4).

Per UI_REDESIGN.md chunk 1.
2026-08-19 22:53:20 -04:00

77 lines
2.6 KiB
TypeScript

import Link from "next/link";
import type { ButtonHTMLAttributes, ReactNode } from "react";
type Variant = "primary" | "secondary" | "ghost" | "danger";
type Size = "sm" | "md" | "lg";
const VARIANT_CLASSES: Record<Variant, string> = {
primary:
"bg-indigo-600 hover:bg-indigo-500 active:bg-indigo-700 text-white shadow-card disabled:hover:bg-indigo-600",
secondary:
"bg-gray-800 hover:bg-gray-700 active:bg-gray-700 text-gray-100 border border-gray-700 disabled:hover:bg-gray-800",
ghost:
"bg-transparent hover:bg-gray-800 active:bg-gray-800 text-gray-300 hover:text-white disabled:hover:bg-transparent",
danger:
"bg-red-700 hover:bg-red-600 active:bg-red-700 text-white disabled:hover:bg-red-700",
};
const SIZE_CLASSES: Record<Size, string> = {
sm: "text-xs px-3 py-1.5 rounded-lg gap-1.5",
md: "text-sm px-4 py-2 rounded-lg gap-2",
lg: "text-sm px-5 py-2.5 rounded-xl gap-2",
};
const BASE =
"inline-flex items-center justify-center font-semibold transition-colors " +
"disabled:opacity-50 disabled:cursor-not-allowed " +
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-950";
interface CommonProps {
variant?: Variant;
size?: Size;
children: ReactNode;
className?: string;
fullWidth?: boolean;
}
type ButtonProps = CommonProps &
ButtonHTMLAttributes<HTMLButtonElement> & {
href?: undefined;
};
interface LinkButtonProps extends CommonProps {
href: string;
external?: boolean;
}
function classes(variant: Variant, size: Size, fullWidth: boolean | undefined, extra?: string) {
return [BASE, VARIANT_CLASSES[variant], SIZE_CLASSES[size], fullWidth ? "w-full" : "", extra ?? ""]
.filter(Boolean)
.join(" ");
}
/** Button — use for in-page actions. Pass `href` instead to render a Link (see LinkButton export). */
export function Button({ variant = "primary", size = "md", children, className, fullWidth, ...rest }: ButtonProps) {
return (
<button className={classes(variant, size, fullWidth, className)} {...rest}>
{children}
</button>
);
}
/** Same visual language as Button, but renders a Next.js Link — for navigation, not actions. */
export function LinkButton({ variant = "primary", size = "md", children, className, fullWidth, href, external }: LinkButtonProps) {
if (external) {
return (
<a href={href} target="_blank" rel="noopener noreferrer" className={classes(variant, size, fullWidth, className)}>
{children}
</a>
);
}
return (
<Link href={href} className={classes(variant, size, fullWidth, className)}>
{children}
</Link>
);
}