API layer for sandbox app 434 (three workflows: Order Booking, Store, Daily Reports) — ZinoClient, per-workflow client singletons, config ids, types, and the source API docs. Component library (Tailwind v4 + design tokens): buttons, reusable (Card/Badge/Input/Select/Pagination/Modal/Spinner/EmptyState), generic + wired record views (rv) and detail views (dv). Screens + routing (react-router v7): login gate, auth-guarded console shell with tab nav, and deep-linkable record/detail routes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
import { cn } from '../../lib/cn';
|
|
|
|
export type ButtonVariant = 'primary' | 'navy' | 'secondary' | 'ghost' | 'danger';
|
|
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
|
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
/** Visual style. @default "primary" */
|
|
variant?: ButtonVariant;
|
|
/** @default "md" */
|
|
size?: ButtonSize;
|
|
iconLeft?: ReactNode;
|
|
iconRight?: ReactNode;
|
|
/** Stretch to container width. @default false */
|
|
full?: boolean;
|
|
}
|
|
|
|
const SIZES: Record<ButtonSize, string> = {
|
|
sm: 'h-[34px] px-3.5 text-[13px] rounded-sm',
|
|
md: 'h-[42px] px-[18px] text-base rounded-md',
|
|
lg: 'h-[50px] px-6 text-md rounded-md',
|
|
};
|
|
|
|
const VARIANTS: Record<ButtonVariant, string> = {
|
|
primary: 'bg-sunrise text-white border border-transparent shadow-sunrise',
|
|
navy: 'bg-navy-900 text-on-navy border border-transparent shadow-sm',
|
|
secondary: 'bg-card text-strong border border-border-default shadow-xs',
|
|
ghost: 'bg-transparent text-body border border-transparent',
|
|
danger: 'bg-ruby-600 text-white border border-transparent shadow-sm',
|
|
};
|
|
|
|
/**
|
|
* Primary action button. Sunrise gradient for primary, solid navy for
|
|
* secondary emphasis, quiet ghost for tertiary.
|
|
*/
|
|
export function Button({
|
|
children,
|
|
variant = 'primary',
|
|
size = 'md',
|
|
iconLeft,
|
|
iconRight,
|
|
full = false,
|
|
className,
|
|
...rest
|
|
}: ButtonProps) {
|
|
return (
|
|
<button
|
|
className={cn(
|
|
'inline-flex items-center justify-center gap-2 font-sans font-semibold leading-none',
|
|
'cursor-pointer transition-[transform,filter,box-shadow] duration-150',
|
|
'hover:brightness-[0.98] active:scale-[0.97]',
|
|
'disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100',
|
|
SIZES[size],
|
|
VARIANTS[variant],
|
|
full ? 'w-full' : 'w-auto',
|
|
className,
|
|
)}
|
|
{...rest}
|
|
>
|
|
{iconLeft}
|
|
{children}
|
|
{iconRight}
|
|
</button>
|
|
);
|
|
}
|