Basic Information Block
BasicInformation is a controlled address and contact form block built on MUI.
Installationâ
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-basic-information-block
yarn add @nodeblocks/frontend-basic-information-block
pnpm add @nodeblocks/frontend-basic-information-block
bun add @nodeblocks/frontend-basic-information-block
What You Needâ
| Item | Why it matters |
|---|---|
data | Single source of truth for form state (NameInput, FuriganaInput, PostalCodeInput, PrefectureSelect, etc.) |
onDataChange | Receives updated state plus metadata for validation, analytics, or side effects |
errors (optional) | Field errors keyed by field path |
labels (optional) | Root copy for title, subtitle, field labels, search button text, and submit text |
placeholders (optional) | Root placeholder copy for text fields and the select |
selectOptions (optional) | Overrides the prefecture select options |
onSearchAddressClick (optional) | Handles the postal code search button |
blockSpacingBefore (optional) | Adds custom spacing before named blocks in the default or override layout |
BasicInformation does not own form state. Keep state in your app and pass it through data. The default shape includes NameInput, FuriganaInput, PostalCodeInput, PrefectureSelect, CityInput, AddressInput, and PhoneInput.
Code Examplesâ
- Quick Start
- Labels and Copy
- Form Errors
- Compound Components
- Block Override
function Example() { const defaultData = { NameInput: '', FuriganaInput: '', PostalCodeInput: '', PrefectureSelect: '', CityInput: '', AddressInput: '', PhoneInput: '', }; const [data, setData] = React.useState(defaultData); return ( <BasicInformation data={data} onDataChange={setData} onSearchAddressClick={() => { setData((current) => ({ ...current })); }} onSubmit={(event) => { event.preventDefault(); setData((current) => ({ ...current })); }} /> ); }
function Example() { const defaultData = { NameInput: '', FuriganaInput: '', PostalCodeInput: '', PrefectureSelect: '', CityInput: '', AddressInput: '', PhoneInput: '', }; const [data, setData] = React.useState(defaultData); const labels = { title: 'Basic information', subtitle: 'Fields marked * are required.', nameInput: 'Full name', furiganaInput: 'Furigana', postalCodeInput: 'Postal code', searchAddressButton: 'Find address', prefectureSelect: 'Prefecture', cityInput: 'City', addressInput: 'Address', phoneInput: 'Phone number', submitButton: 'Continue', }; const placeholders = { nameInput: 'Enter your full name', furiganaInput: 'Enter furigana', postalCodeInput: 'Enter postal code', prefectureSelect: 'Select a prefecture', cityInput: 'Enter city', addressInput: 'Enter street address', phoneInput: 'Enter phone number', }; return ( <BasicInformation data={data} onDataChange={setData} labels={labels} placeholders={placeholders} onSearchAddressClick={() => { setData((current) => ({ ...current })); }} onSubmit={(event) => { event.preventDefault(); setData((current) => ({ ...current })); }} /> ); }
Pass copy on the root when the same wording should apply to the whole form. Override individual subcomponents only when one field needs different text. This block has no URL props; the only action callback is onSearchAddressClick for the postal-code row.
function Example() { const defaultData = { NameInput: '', FuriganaInput: '', PostalCodeInput: '', PrefectureSelect: '', CityInput: '', AddressInput: '', PhoneInput: '', }; const defaultErrors = {}; const [data, setData] = React.useState(defaultData); const [errors, setErrors] = React.useState(defaultErrors); const handleDataChange = (nextData, meta) => { const { [meta.name]: _removed, ...restErrors } = errors; const nextErrors = { ...restErrors }; // Validate required fields on blur (same pattern as storybook) if (meta.cause === 'blur') { const value = nextData[meta.name]; if (value === '' || (typeof value === 'string' && !value.trim())) { nextErrors[meta.name] = 'This field is required.'; } } setErrors(nextErrors); setData(nextData); }; return ( <BasicInformation data={data} errors={Object.keys(errors).length ? errors : undefined} onDataChange={handleDataChange} onSearchAddressClick={() => { setData((current) => ({ ...current })); }} onSubmit={(event) => { event.preventDefault(); setData((current) => ({ ...current })); }} /> ); }
function Example() { const defaultData = { NameInput: '', FuriganaInput: '', PostalCodeInput: '', PrefectureSelect: '', CityInput: '', AddressInput: '', PhoneInput: '', }; const [data, setData] = React.useState(defaultData); return ( <BasicInformation data={data} onDataChange={setData} labels={{ searchAddressButton: 'My Search Address' }} onSearchAddressClick={() => { setData((current) => ({ ...current })); }} onSubmit={(event) => { event.preventDefault(); setData((current) => ({ ...current })); }} > <BasicInformation.Title>Basic Information</BasicInformation.Title> <div style={{ display: 'grid', gap: 24 }}> <BasicInformation.Subtitle>Please fill in your basic information</BasicInformation.Subtitle> <BasicInformation.NameInput required={false} /> <BasicInformation.FuriganaInput required={false} /> <BasicInformation.PostalCodeInput required={false} labels={{ postalCodeInput: 'My Postal Code' }} /> <BasicInformation.PrefectureSelect required={false} /> <BasicInformation.CityInput required={false} /> <BasicInformation.AddressInput required={false} /> <BasicInformation.PhoneInput required={false} /> </div> <BasicInformation.SubmitButton>Submit</BasicInformation.SubmitButton> </BasicInformation> ); }
function Example() { const defaultData = { NameInput: '', FuriganaInput: '', PostalCodeInput: '', PrefectureSelect: '', CityInput: '', AddressInput: '', PhoneInput: '', }; const [data, setData] = React.useState(defaultData); const [errors, setErrors] = React.useState({}); const handleDataChange = (nextData, meta) => { const { [meta.name]: _removed, ...restErrors } = errors; const nextErrors = { ...restErrors }; // Validate required fields on blur (same pattern as storybook) if (meta.cause === 'blur') { const value = nextData[meta.name]; if (value === '' || (typeof value === 'string' && !value.trim())) { nextErrors[meta.name] = 'This field is required.'; } } setErrors(nextErrors); setData(nextData); }; return ( <BasicInformation data={data} errors={Object.keys(errors).length ? errors : undefined} onDataChange={handleDataChange} onSearchAddressClick={() => { setData((current) => ({ ...current })); }} blockSpacingBefore={{ customBlock: 2, nameInput: { xs: 4, sm: 3 }, submitButton: 4 }} onSubmit={(event) => { event.preventDefault(); setData((current) => ({ ...current })); }} > {({ defaultBlocks, defaultBlockOrder }) => ({ blocks: { ...defaultBlocks, nameInput: <BasicInformation.NameInput label="Full name" placeholder="Enter your full name" />, customBlock: ( <div style={{ width: '100%', border: '1px solid #cddcff', borderRadius: 8, padding: 12, background: '#eef4ff', fontSize: 14, }} > Custom notification or help text </div> ), }, blockOrder: ['customBlock', ...defaultBlockOrder], })} </BasicInformation> ); }
When to use block overrides
Use overrides when you need to change order, replace default UI blocks, or inject custom content while preserving shared state handling. Default block order is title, subtitle, nameInput, furiganaInput, postalCodeInput, prefectureSelect, cityInput, addressInput, phoneInput, submitButton, input. blockSpacingBefore adds spacing before named blocks in the default or override layout.
Important Propsâ
Core Propsâ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data | BasicInformationFormData ({ NameInput, FuriganaInput, PostalCodeInput, PrefectureSelect, CityInput, AddressInput, PhoneInput } or extended Record<string, unknown>) | Yes | - | Controlled form data object |
onDataChange | (data, meta) => void | Yes | - | Called on updates; meta includes name, value, cause (input, change, blur, clear, reset, programmatic), and optional event |
errors | { [fieldName: string]: string | string[] } | No | undefined | Field errors keyed by field path; common keys are NameInput, FuriganaInput, PostalCodeInput, PrefectureSelect, CityInput, AddressInput, and PhoneInput |
Content Propsâ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
labels | { title?, subtitle?, nameInput?, furiganaInput?, postalCodeInput?, searchAddressButton?, prefectureSelect?, cityInput?, addressInput?, phoneInput?, submitButton? } | No | title: 'ćșæŹæ
ć ±', subtitle: '* ăŻćż
é äșé
ă§ă', nameInput: 'æ°ć', furiganaInput: 'ăăȘăŹă', postalCodeInput: 'é”äŸżçȘć·', searchAddressButton: 'äœæăæ€çŽą', prefectureSelect: 'éœéćșç', cityInput: 'ćžćșçșæ', addressInput: 'çșćă»çȘć°ă»ć»șç©', phoneInput: 'éŁç”Ąć
é»è©±çȘć·', submitButton: 'æŹĄăž' | Root copy for the title, helper text, field labels, search button, and submit button |
placeholders | { nameInput?, furiganaInput?, postalCodeInput?, prefectureSelect?, cityInput?, addressInput?, phoneInput? } | No | nameInput: 'æ°ćăć
„ć', furiganaInput: 'ăăȘăŹăăć
„ć', postalCodeInput: 'é”äŸżçȘć·ăć
„ć', prefectureSelect: 'éœéćșçăéžæ', cityInput: 'ćžćșçșæăć
„ć', addressInput: 'çșćă»çȘć°ă»ć»șç©ăć
„ć', phoneInput: 'é»è©±çȘć·ăć
„ćïŒăă€ăăłăȘăă§ć
„ćïŒ' | Root placeholder copy for the text inputs and select |
selectOptions | { prefectureSelect?: { value: string; label: string }[] } | No | PREFECTURES | Overrides the options shown in the prefecture select |
onSearchAddressClick | MouseEventHandler<HTMLButtonElement> | No | undefined | Enables the postal-code search button and runs when the user clicks it |
Layout and Composition Propsâ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
component | StackProps<'form'>['component'] | No | 'form' | Root container element |
children | ReactNode or block override function | No | undefined | Pass JSX children for compound components, or a function to override blocks |
blockSpacingBefore | Partial<Record<string, number | { xs?: number; sm?: number; md?: number; lg?: number; xl?: number }>> | No | { subtitle: 4, nameInput: { xs: 4, sm: 3 }, submitButton: 4 } | Extra spacing before selected blocks |
className | string | No | undefined | Additional class name on the root container |
sx | StackProps<'form'>['sx'] | No | undefined | MUI sx styling for the root container |
Inherits StackProps<'form'> props except children. When using block overrides, the default block order is title, subtitle, nameInput, furiganaInput, postalCodeInput, prefectureSelect, cityInput, addressInput, phoneInput, submitButton, input.
Default UI Blocksâ
| Block | MUI base component(s) | Notes |
|---|---|---|
BasicInformation | Stack | Root form container; defaults to component="form" with built-in padding and vertical spacing |
Title | Typography | Default copy is ćșæŹæ
ć ± |
Subtitle | Typography | Default copy is * ăŻćż
é äșé
ă§ă |
NameInput | TextField | name="NameInput", default label æ°ć, default placeholder æ°ćăć
„ć |
FuriganaInput | TextField | name="FuriganaInput", default label ăăȘăŹă, default placeholder ăăȘăŹăăć
„ć |
PostalCodeInput | TextField + Button | name="PostalCodeInput", default label é”äŸżçȘć·, default placeholder é”äŸżçȘć·ăć
„ć, inputMode="numeric", search button text defaults to äœæăæ€çŽą when onSearchAddressClick is present |
PrefectureSelect | FormControl + InputLabel + Select + MenuItem | name="PrefectureSelect", default label éœéćșç, default placeholder éœéćșçăéžæ, options default to PREFECTURES |
CityInput | TextField | name="CityInput", default label ćžćșçșæ, default placeholder ćžćșçșæăć
„ć |
AddressInput | TextField | name="AddressInput", default label çșćă»çȘć°ă»ć»șç©, default placeholder çșćă»çȘć°ă»ć»șç©ăć
„ć |
PhoneInput | TextField | name="PhoneInput", default label éŁç”Ąć
é»è©±çȘć·, default placeholder é»è©±çȘć·ăć
„ćïŒăă€ăăłăȘăă§ć
„ćïŒ, inputMode="numeric" |
SubmitButton | Button | Default copy is æŹĄăž |
Extra field primitivesâ
| Primitive | MUI base component(s) | Notes |
|---|---|---|
Input | TextField | Internal field primitive exposed as BasicInformation.Input for custom flows and overrides |
TypeScriptâ
export type BasicInformationFormData =
| {
NameInput: string;
FuriganaInput: string;
PostalCodeInput: string;
PrefectureSelect: string;
CityInput: string;
AddressInput: string;
PhoneInput: string;
}
| Record<string, unknown>;