Halves component source
components/halves/Field.tsx
import type { InputHTMLAttributes, SelectHTMLAttributes } from "react";
type FieldProps = {
label: string;
hint?: string;
error?: string;
} & InputHTMLAttributes<HTMLInputElement>;
export function Field({ label, hint, error, id, className = "", ...props }: FieldProps) {
const fieldId = id ?? `field-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
return (
<label className={`field ${error ? "has-error" : ""} ${className}`.trim()} htmlFor={fieldId}>
<span className="field__label">{label}</span>
<input id={fieldId} className="field__control" aria-invalid={Boolean(error)} {...props} />
{error ? <span className="field__error">{error}</span> : hint ? <span className="field__hint">{hint}</span> : null}
</label>
);
}
type SelectFieldProps = {
label: string;
options: Array<{ label: string; value: string }>;
} & SelectHTMLAttributes<HTMLSelectElement>;
export function SelectField({ label, options, id, ...props }: SelectFieldProps) {
const fieldId = id ?? `select-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
return (
<label className="field" htmlFor={fieldId}>
<span className="field__label">{label}</span>
<select id={fieldId} className="field__control field__select" {...props}>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</label>
);
}