Skip to Content

Form

Organism

Form é uma superfície de coleta e revisão antes de uma ação rastreável.

Formulários no Pulso precisam ser explícitos sobre obrigatoriedade, erro, consequência e estado de envio. Placeholder não substitui label, e mensagens críticas não devem ficar abaixo da dobra sem sinalização.

Exemplos copiáveis

React

import * as React from "react" type CrisisFormValues = { title: string source: string risk: "stable" | "monitor" | "attention" | "high" | "critical" summary: string } type PulsoCrisisFormProps = { initialValues?: Partial<CrisisFormValues> onSubmit: (values: CrisisFormValues) => Promise<void> | void } const defaultValues: CrisisFormValues = { title: "", source: "", risk: "monitor", summary: "" } export function PulsoCrisisForm({ initialValues, onSubmit }: PulsoCrisisFormProps) { const [values, setValues] = React.useState<CrisisFormValues>({ ...defaultValues, ...initialValues }) const [errors, setErrors] = React.useState<Partial<Record<keyof CrisisFormValues, string>>>({}) const [isSubmitting, setIsSubmitting] = React.useState(false) function updateValue<Key extends keyof CrisisFormValues>(key: Key, value: CrisisFormValues[Key]) { setValues((current) => ({ ...current, [key]: value })) setErrors((current) => ({ ...current, [key]: undefined })) } function validate(nextValues: CrisisFormValues) { const nextErrors: Partial<Record<keyof CrisisFormValues, string>> = {} if (!nextValues.title.trim()) nextErrors.title = "Nomeie o caso antes de continuar." if (!nextValues.source.trim()) nextErrors.source = "Informe a fonte para manter rastreabilidade." if (nextValues.summary.trim().length < 24) nextErrors.summary = "Descreva contexto suficiente para revisão humana." return nextErrors } async function handleSubmit(event: React.FormEvent<HTMLFormElement>) { event.preventDefault() const nextErrors = validate(values) setErrors(nextErrors) if (Object.keys(nextErrors).length > 0) return setIsSubmitting(true) try { await onSubmit(values) } finally { setIsSubmitting(false) } } return ( <form onSubmit={handleSubmit} noValidate className="grid gap-5"> <div className="grid gap-1.5"> <label htmlFor="case-title" className="font-mono text-xs uppercase tracking-[var(--tracking-kicker)] text-[var(--fg-muted)]">Nome do caso</label> <input id="case-title" value={values.title} onChange={(event) => updateValue("title", event.target.value)} aria-invalid={Boolean(errors.title)} aria-describedby={errors.title ? "case-title-error" : undefined} className="min-h-10 rounded-[var(--radius-md)] border border-[var(--border)] bg-[var(--card)] px-3 text-sm" /> {errors.title ? <p id="case-title-error" className="text-sm text-[var(--risk-critical-text)]">{errors.title}</p> : null} </div> <div className="grid gap-1.5"> <label htmlFor="case-risk" className="font-mono text-xs uppercase tracking-[var(--tracking-kicker)] text-[var(--fg-muted)]">Risco operacional</label> <select id="case-risk" value={values.risk} onChange={(event) => updateValue("risk", event.target.value as CrisisFormValues["risk"])} className="min-h-10 rounded-[var(--radius-md)] border border-[var(--border)] bg-[var(--card)] px-3 text-sm"> <option value="stable">stable</option><option value="monitor">monitor</option><option value="attention">attention</option><option value="high">high</option><option value="critical">critical</option> </select> </div> <div className="grid gap-1.5"> <label htmlFor="case-summary" className="font-mono text-xs uppercase tracking-[var(--tracking-kicker)] text-[var(--fg-muted)]">Contexto para decisão</label> <textarea id="case-summary" value={values.summary} onChange={(event) => updateValue("summary", event.target.value)} aria-invalid={Boolean(errors.summary)} aria-describedby={errors.summary ? "case-summary-error" : undefined} rows={4} className="rounded-[var(--radius-md)] border border-[var(--border)] bg-[var(--card)] px-3 py-2 text-sm" /> {errors.summary ? <p id="case-summary-error" className="text-sm text-[var(--risk-critical-text)]">{errors.summary}</p> : null} </div> <footer className="flex justify-end gap-2 border-t border-[var(--border)] pt-4"> <button type="button" className="rounded-[var(--radius-md)] border border-[var(--border)] px-4 py-2 text-sm">Cancelar</button> <button type="submit" disabled={isSubmitting} className="rounded-[var(--radius-md)] bg-[var(--primary)] px-4 py-2 text-sm font-semibold text-[var(--primary-fg)]">{isSubmitting ? "Salvando…" : "Salvar caso"}</button> </footer> </form> ) }

Regras

  1. Todo campo precisa de label programático.
  2. Erros devem ser próximos, específicos e conectados por aria-describedby.
  3. Risco usa stable / monitor / attention / high / critical.
  4. Estado de envio precisa bloquear duplicidade e manter feedback claro.
  5. Ações destrutivas exigem confirmação fora do form comum.
Last updated on