import type { ReactNode } from 'react' // SHARED FORM PRIMITIVES. // // These existed as copied class strings on every screen that had a form, which is how a product ends up with // four slightly different inputs. They are here so the surfaces built on top of them cannot drift, and so a // change to the focus ring is one edit rather than a search. // // Tokens only, no literal colours. The palette is defined once in index.css and a hard coded hex here would // survive a rebrand and look wrong. export const fieldCls = 'w-full rounded-md border border-line/70 bg-navy-950/60 px-3 py-1.5 text-sm text-ink outline-none placeholder:text-faint focus:border-blue/60 disabled:opacity-50' export const btnPrimary = 'inline-flex items-center gap-1.5 rounded-md bg-blue/20 px-3 py-1.5 text-sm font-semibold text-blue-soft hover:bg-blue/30 disabled:opacity-50' export const btnGhost = 'inline-flex items-center gap-1.5 rounded-md border border-line/70 px-3 py-1.5 text-xs font-semibold text-muted hover:text-ink disabled:opacity-40' export const btnDanger = 'inline-flex items-center gap-1.5 rounded-md border border-line/70 px-3 py-1.5 text-xs font-semibold text-muted hover:border-risk/60 hover:text-risk disabled:opacity-40' // Labeled wraps a control in the uppercase caption every existing form uses. export function Labeled({ label, hint, children, className = '', }: { label: string hint?: string children: ReactNode className?: string }) { return ( ) } // SettingRow is the settings layout: what the setting is, what it does in plain words, and the control. // // The consequence line is not decoration. "Managers and above" means nothing on its own; what an owner is // actually choosing is who can infer the margin from a bill rate, and the row is where that gets said. export function SettingRow({ label, hint, children, }: { label: string hint: string children: ReactNode }) { return (
{label}
{hint}
{children}
) } export function Select({ value, onChange, choices, disabled, className = '', }: { value: T onChange: (v: T) => void choices: { value: T; label: string }[] disabled?: boolean className?: string }) { return ( ) } // Toggle is a switch, not a checkbox, because these are settings that take effect on click rather than on a // later save. role and aria-checked are set so it is a switch to a screen reader too. export function Toggle({ value, onChange, disabled, label, }: { value: boolean onChange: (v: boolean) => void disabled?: boolean label: string }) { return ( ) } // Note is the one place a screen says something went wrong, went right, or needs care. One component so the // three tones cannot be spelled three ways, and so nothing invents a fourth. export function Note({ tone, children }: { tone: 'warn' | 'risk' | 'ok' | 'muted'; children: ReactNode }) { const cls = { warn: 'border-warn/40 bg-warn/10 text-warn', risk: 'border-risk/40 bg-risk/10 text-risk', ok: 'border-ok/40 bg-ok/10 text-ok', muted: 'border-line/60 bg-navy-950/40 text-muted', }[tone] return (
{children}
) } // Empty is the state a list is in most of the time on a new account. It takes a reason rather than only a // title, because "no candidates" and "no candidates YOU can see" are different facts and the second one is // the one somebody would otherwise file a support ticket about. export function Empty({ title, reason, action }: { title: string; reason?: string; action?: ReactNode }) { return (
{title}
{reason &&
{reason}
} {action &&
{action}
}
) }