Form Components Example
Demonstrates the six components in src/common/components/forms: validated inputs and table search components.
Search components
SearchInput and SearchSelect write into a search-condition state.
{}Validated form components
ValidatableInput / ValidatableSelect / ValidatableDate / ValidatableTextarea, wired to react-hook-form.
Form Components (components/forms) Usage Guide
Overview
src/common/components/forms/ contains 6 reusable form components built on react-hook-form and shadcn/ui, in two groups:
- Validated form components (wired to
react-hook-formviaregister/setValue):ValidatableInput,ValidatableSelect,ValidatableDate,ValidatableTextarea - Table search components (write into a search-condition state):
SearchInput,SearchSelect
Both translate field labels via
pageTranslate; select components also translate dictionary values throughuseTranslations("KVS").
ValidatableInput — validated input
A controlled input wired via forwardRef and register("fieldName"); {...register(...)} forwards onChange / onBlur / name / ref to the inner <input>.
import {ValidatableInput} from "@/common/components/forms/validatable-input";
const {register, formState: {errors, isSubmitted}} = useForm();
<ValidatableInput
{...register("userName", {required: "Required"})}
type="text"
fieldPropertyName="userName"
pageTranslate={pageTranslate}
errorMessage={errors.userName?.message}
isSubmitted={isSubmitted}
/>
| Prop | Type | Required | Description |
|---|---|---|---|
| fieldPropertyName | string | Yes | Field name (label key and input name) |
| type | "text" | "password" | "email" |
| pageTranslate | (key) => string | No | Label translation function |
| validateTranslate | (key) => string | No | Validation message translation function |
| errorMessage | string | No | Validation error text (usually errors.xxx?.message) |
| isSubmitted | boolean | No | Whether submitted; shows a green check on valid fields |
| readonly | boolean | No | Read-only |
Other props inherit React.InputHTMLAttributes (including defaultValue, className).
ValidatableSelect — validated select
Uses setValue (not register) to sync its value; dictionary accepts KeyValue[]; falls back to the first item when the default is not matched.
import {ValidatableSelect} from "@/common/components/forms/validatable-select";
<ValidatableSelect
fieldPropertyName="gender"
dictionary={dictionary}
defaultValue={original.gender}
setValue={setValue}
pageTranslate={pageTranslate}
/>
| Prop | Type | Required | Description |
|---|---|---|---|
| fieldPropertyName | string | Yes | Field name |
| dictionary | KeyValue[] | Yes | Options dictionary |
| setValue | (name, value) => void | Yes | react-hook-form setValue |
| defaultValue | any | No | Initial value (dictionary key) |
| pageTranslate | (key) => string | No | Label translation function |
| className | string | No | Trigger class |
KeyValue is { key: string; value: string }: key is the submitted value; value is both the display text and the KVS translation key (KVS.<field>.<value>), so it must match your KVS keys.
ValidatableDate — validated date picker
A date picker built on Calendar + Popover, formatted with dayjs, syncing via setValue; writes the default value (today) in a useEffect on mount.
import {ValidatableDate} from "@/common/components/forms/validatable-date";
<ValidatableDate
fieldPropertyName="birthday"
defaultValue="1990-01-01"
format="YYYY-MM-DD"
setValue={setValue}
pageTranslate={pageTranslate}
/>
| Prop | Type | Required | Description |
|---|---|---|---|
| fieldPropertyName | string | Yes | Field name |
| setValue | (name, value) => void | Yes | Write back on date change |
| defaultValue | string | No | Initial date string, defaults to today |
| format | string | No | dayjs format, default YYYY-MM-DD |
| pageTranslate | (key) => string | No | Label translation function |
| readonly | boolean | No | Read-only |
ValidatableTextarea — validated textarea
Same usage as ValidatableInput, wired via register, rendering shadcn's Textarea.
import {ValidatableTextarea} from "@/common/components/forms/validatable-textarea";
<ValidatableTextarea
{...register("description")}
fieldPropertyName="description"
pageTranslate={pageTranslate}
errorMessage={errors.description?.message}
isSubmitted={isSubmitted}
/>
| Prop | Type | Required | Description |
|---|---|---|---|
| fieldPropertyName | string | Yes | Field name |
| pageTranslate | (key) => string | No | Label translation function |
| errorMessage | string | No | Validation error text |
| isSubmitted | boolean | No | Whether submitted |
| readonly | boolean | No | Read-only |
Other props inherit React.TextareaHTMLAttributes.
SearchInput — search input
For table search bars; writes input into the matching field of setSearchCondition.
import SearchInput from "@/common/components/forms/search-input";
<SearchInput
value={condition.userName || ""}
propertyName="userName"
pageTranslate={pageTranslate}
setSearchCondition={setCondition}
/>
| Prop | Type | Required | Description |
|---|---|---|---|
| value | string | No | Controlled value |
| propertyName | string | Yes | Field name |
| pageTranslate | (key) => string | Yes | Placeholder translation function |
| setSearchCondition | Dispatch<SetStateAction<T>> | Yes | Write back the search condition state |
SearchSelect — search select
For table search bars; appends an "all" option (value -1) on top of the dictionary.
import SearchSelect from "@/common/components/forms/search-select";
<SearchSelect
propertyName="status"
pageTranslate={pageTranslate}
setSearchCondition={setCondition}
dictionary={dictionary}
/>
| Prop | Type | Required | Description |
|---|---|---|---|
| propertyName | string | Yes | Field name |
| setSearchCondition | Dispatch<SetStateAction<T>> | Yes | Write back the search condition state |
| dictionary | KeyValue[] | No | Options dictionary |
| pageTranslate | (key) => string | Yes | Placeholder translation function |
Integration notes
- register components (
ValidatableInput/ValidatableTextarea): spread{...register("field")}, passerrorMessage={errors.field?.message}. - setValue components (
ValidatableSelect/ValidatableDate): not registered; passsetValueanddefaultValuedirectly. - Dictionary
valueis first translated viaKVS.<field>.<value>(falls back to raw text), so add aKVSnamespace to your messages.