"use client";
import {
cloneElement,
forwardRef,
useId,
useState,
type InputHTMLAttributes,
type TextareaHTMLAttributes,
type SelectHTMLAttributes,
type ReactElement,
type ReactNode,
} from "react";
type FieldLabel = { label: string; hint?: ReactNode; error?: string };
export function FormField({
label,
hint,
error,
children,
className = "",
}: FieldLabel & {
children: ReactElement<{
id?: string;
"aria-describedby"?: string;
"aria-invalid"?: boolean;
}>;
className?: string;
}) {
const generated = useId(),
id = children.props.id ?? generated;
const helpId = `${id}-help`,
errorId = `${id}-error`;
const described = [
children.props["aria-describedby"],
hint && helpId,
error && errorId,
]
.filter(Boolean)
.join(" ");
return (
{cloneElement(children, {
id,
"aria-describedby": described || undefined,
"aria-invalid": error ? true : children.props["aria-invalid"],
})}
{hint && (
{hint}
)}
{error && (
{error}
)}
);
}
export type TextFieldProps = InputHTMLAttributes & FieldLabel;
export const TextField = forwardRef(
function TextField({ label, hint, error, className = "", ...props }, ref) {
return (
);
},
);
export const TextArea = forwardRef<
HTMLTextAreaElement,
TextareaHTMLAttributes & FieldLabel
>(function TextArea(
{ label, hint, error, className = "", rows = 4, ...props },
ref,
) {
return (
);
});
export const SelectField = forwardRef<
HTMLSelectElement,
SelectHTMLAttributes & FieldLabel
>(function SelectField(
{ label, hint, error, className = "", children, ...props },
ref,
) {
return (
);
});
export const Checkbox = forwardRef<
HTMLInputElement,
Omit, "type"> & {
label: ReactNode;
hint?: string;
}
>(function Checkbox({ label, hint, className = "", ...props }, ref) {
const id = useId();
return (
);
});
export const Switch = forwardRef<
HTMLInputElement,
Omit, "type"> & { label: string }
>(function Switch({ label, className = "", ...props }, ref) {
return (
);
});
export function SearchField({
label = "Search",
value,
onValueChange,
className = "",
...props
}: Omit & {
label?: string;
value: string;
onValueChange: (value: string) => void;
}) {
return (
onValueChange(e.target.value)}
{...props}
/>
{value && (
)}
);
}
export const PasswordField = forwardRef<
HTMLInputElement,
Omit
>(function PasswordField({ label, hint, error, ...props }, ref) {
const [visible, setVisible] = useState(false);
return (
);
});