import type { ChangeEvent, InputHTMLAttributes, ReactNode, SelectHTMLAttributes, TextareaHTMLAttributes, } from 'react'; /** * Canonical form atoms. * * Why these exist: roughly 40+ inputs/selects/textareas/checkboxes across the * admin shipped with subtly different focus rings, missing dark-mode pairs, * or rounded-lg instead of rounded-xl. New pages that import these atoms * inherit the design system automatically; existing pages can be migrated * page-at-a-time. * * Conventions enforced: * - Inputs/selects/textareas: rounded-xl, slate-200/slate-600 border, focus * ring brand-500/20 width-2, dark-mode background slate-800, placeholder * slate-400/slate-500. * - Checkboxes: h-4 w-4, rounded, brand-600 fill, focus ring brand-500/40. * - Labels: eyebrow text-xs uppercase + 'block' OR regular text-sm + font-medium. * - Hints: mt-1 text-xs slate-500/400. * - Errors: mt-1 text-xs red-600/red-400 + role="alert". * - All variants carry disabled:opacity-50 disabled:cursor-not-allowed. */ const INPUT_BASE = 'block w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 ' + 'placeholder:text-slate-400 ' + 'focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 ' + 'disabled:cursor-not-allowed disabled:opacity-50 ' + 'dark:border-slate-600 dark:bg-slate-800 dark:text-white dark:placeholder:text-slate-500'; const INPUT_ERROR = 'border-red-500 focus:border-red-500 focus:ring-red-500/30 ' + 'dark:border-red-500 dark:focus:border-red-400 dark:focus:ring-red-400/30'; const CHECKBOX_BASE = 'h-4 w-4 shrink-0 rounded border-slate-300 text-brand-600 ' + 'focus:outline-none focus:ring-2 focus:ring-brand-500/40 focus:ring-offset-1 ' + 'disabled:cursor-not-allowed disabled:opacity-50 ' + 'dark:border-slate-600 dark:bg-slate-700 dark:focus:ring-offset-slate-900'; const LABEL_EYEBROW = 'block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400'; const LABEL_REGULAR = 'block text-sm font-medium text-slate-700 dark:text-slate-300'; const HINT = 'mt-1 text-xs text-slate-500 dark:text-slate-400'; const ERROR_TEXT = 'mt-1 text-xs text-red-600 dark:text-red-400'; export type FormInputProps = InputHTMLAttributes & { invalid?: boolean; }; export function FormInput({ className = '', invalid, ...rest }: FormInputProps) { const cls = `${INPUT_BASE} ${invalid ? INPUT_ERROR : ''} ${className}`.trim(); return ( ); } export type FormSelectProps = SelectHTMLAttributes & { invalid?: boolean; }; export function FormSelect({ className = '', invalid, children, ...rest }: FormSelectProps) { const cls = `${INPUT_BASE} ${invalid ? INPUT_ERROR : ''} ${className}`.trim(); return ( ); } export type FormTextareaProps = TextareaHTMLAttributes & { invalid?: boolean; }; export function FormTextarea({ className = '', invalid, ...rest }: FormTextareaProps) { const cls = `${INPUT_BASE} resize-y min-h-[6rem] ${invalid ? INPUT_ERROR : ''} ${className}`.trim(); return (