Back to home

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.

Current search condition
{}

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-form via register / 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 through useTranslations("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}
/>
PropTypeRequiredDescription
fieldPropertyNamestringYesField name (label key and input name)
type"text""password""email"
pageTranslate(key) => stringNoLabel translation function
validateTranslate(key) => stringNoValidation message translation function
errorMessagestringNoValidation error text (usually errors.xxx?.message)
isSubmittedbooleanNoWhether submitted; shows a green check on valid fields
readonlybooleanNoRead-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}
/>
PropTypeRequiredDescription
fieldPropertyNamestringYesField name
dictionaryKeyValue[]YesOptions dictionary
setValue(name, value) => voidYesreact-hook-form setValue
defaultValueanyNoInitial value (dictionary key)
pageTranslate(key) => stringNoLabel translation function
classNamestringNoTrigger 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}
/>
PropTypeRequiredDescription
fieldPropertyNamestringYesField name
setValue(name, value) => voidYesWrite back on date change
defaultValuestringNoInitial date string, defaults to today
formatstringNodayjs format, default YYYY-MM-DD
pageTranslate(key) => stringNoLabel translation function
readonlybooleanNoRead-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}
/>
PropTypeRequiredDescription
fieldPropertyNamestringYesField name
pageTranslate(key) => stringNoLabel translation function
errorMessagestringNoValidation error text
isSubmittedbooleanNoWhether submitted
readonlybooleanNoRead-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}
/>
PropTypeRequiredDescription
valuestringNoControlled value
propertyNamestringYesField name
pageTranslate(key) => stringYesPlaceholder translation function
setSearchConditionDispatch<SetStateAction<T>>YesWrite 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}
/>
PropTypeRequiredDescription
propertyNamestringYesField name
setSearchConditionDispatch<SetStateAction<T>>YesWrite back the search condition state
dictionaryKeyValue[]NoOptions dictionary
pageTranslate(key) => stringYesPlaceholder translation function

Integration notes

  1. register components (ValidatableInput / ValidatableTextarea): spread {...register("field")}, pass errorMessage={errors.field?.message}.
  2. setValue components (ValidatableSelect / ValidatableDate): not registered; pass setValue and defaultValue directly.
  3. Dictionary value is first translated via KVS.<field>.<value> (falls back to raw text), so add a KVS namespace to your messages.