FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
import { type InputHTMLAttributes, forwardRef } from "react";
|
|
import clsx from "clsx";
|
|
|
|
interface FormFieldProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
label: string;
|
|
error?: string;
|
|
hint?: string;
|
|
}
|
|
|
|
export const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
|
|
({ label, error, hint, id, className, ...props }, ref) => {
|
|
const fieldId = id ?? props.name;
|
|
return (
|
|
<div>
|
|
<label htmlFor={fieldId} className="block text-sm font-medium text-slate-700">
|
|
{label}
|
|
</label>
|
|
<input
|
|
ref={ref}
|
|
id={fieldId}
|
|
className={clsx(
|
|
"focus-ring mt-1 block w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm",
|
|
error && "border-red-400",
|
|
className,
|
|
)}
|
|
aria-invalid={error ? "true" : "false"}
|
|
aria-describedby={error ? `${fieldId}-error` : hint ? `${fieldId}-hint` : undefined}
|
|
{...props}
|
|
/>
|
|
{hint && !error && (
|
|
<p id={`${fieldId}-hint`} className="mt-1 text-xs text-slate-500">
|
|
{hint}
|
|
</p>
|
|
)}
|
|
{error && (
|
|
<p id={`${fieldId}-error`} className="mt-1 text-xs text-red-600" role="alert">
|
|
{error}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
FormField.displayName = "FormField";
|