AdminGuides
SmartForm Guide
TL;DR (Minimal Usable Form)
import { SmartModalForm } from '@/components/admin/smart-form' const fieldsConfig = [{ key: 'name', title: 'Name', type: 'text', form: { required: true } }] <SmartModalForm title="Add" open={open} onOpenChange={setOpen} fieldsConfig={fieldsConfig} onFinish={async(v)=>{ await create(v); return true }} />
Detailed Guide to Universal Form Components
🎯 Overview
SmartForm series components are NextJS Base's form solution, automatically generating forms based on fieldsConfig configuration.
Component Types
| Component | Description | Use Case |
|---|---|---|
SmartForm | Base Form | Inline Form in Page |
SmartModalForm | Modal Form | Add/Edit Modal |
SmartDrawerForm | Drawer Form | Sidebar Form |
Core Features
- ✅ Configuration-driven, auto-generate form fields
- ✅ Automatic form validation handling
- ✅ Support multiple field types
- ✅ Support field linkage
- ✅ Support async data loading
📦 Component Types
SmartModalForm
Most commonly used form component, displayed as modal:
import { SmartModalForm } from '@/components/admin/smart-form'
<SmartModalForm
title="Add User"
open={modalOpen}
onOpenChange={setModalOpen}
fieldsConfig={fieldsConfig}
onFinish={async (values) => {
await createUser(values)
return true // Return true to close modal
}}
/>SmartDrawerForm
Form displayed as drawer:
import { SmartDrawerForm } from '@/components/admin/smart-form'
<SmartDrawerForm
title="Edit User"
open={drawerOpen}
onOpenChange={setDrawerOpen}
fieldsConfig={fieldsConfig}
initialValues={currentRecord}
onFinish={async (values) => {
await updateUser(values)
return true
}}
width={600}
/>SmartForm
Base form, embedded in page:
import { SmartForm } from '@/components/admin/smart-form'
<SmartForm
fieldsConfig={fieldsConfig}
initialValues={initialData}
onFinish={async (values) => {
await saveData(values)
}}
submitter={{
searchConfig: {
submitText: 'Save',
resetText: 'Reset',
},
}}
/>📋 Configuration Details
Basic Configuration
const fieldsConfig = [
{
key: 'name',
title: 'Username',
type: 'text',
form: {
required: true,
placeholder: 'Enter username',
rules: [
{ min: 2, message: 'At least 2 characters' },
{ max: 20, message: 'At most 20 characters' },
],
},
},
]Field Types and Form Components
text - Text Input
{
key: 'name',
title: 'Name',
type: 'text',
form: {
required: true,
placeholder: 'Enter name',
maxLength: 100,
showCount: true, // Show character count
},
}textarea - Multi-line Text
{
key: 'description',
title: 'Description',
type: 'textarea',
form: {
rows: 4,
maxLength: 500,
showCount: true,
placeholder: 'Enter description',
},
}number - Number Input
{
key: 'price',
title: 'Price',
type: 'number',
form: {
min: 0,
max: 99999,
precision: 2, // Decimal places
step: 0.01, // Step
prefix: '$', // Prefix
suffix: '', // Suffix
},
}select - Dropdown
{
key: 'status',
title: 'Status',
type: 'select',
options: [
{ label: 'Enabled', value: 'active' },
{ label: 'Disabled', value: 'inactive' },
],
form: {
required: true,
defaultValue: 'active',
allowClear: true,
showSearch: true, // Searchable
},
}
// Dynamic Options
{
key: 'categoryId',
title: 'Category',
type: 'select',
options: async () => {
const res = await getCategoryList()
return res.data.map(item => ({
label: item.name,
value: item.id,
}))
},
}switch - Switch
{
key: 'enable',
title: 'Enabled',
type: 'switch',
form: {
defaultValue: true,
checkedChildren: 'On',
unCheckedChildren: 'Off',
},
}date / datetime - Date Picker
// Date
{
key: 'birthday',
title: 'Birthday',
type: 'date',
form: {
format: 'YYYY-MM-DD',
},
}
// DateTime
{
key: 'publishTime',
title: 'Publish Time',
type: 'datetime',
form: {
format: 'YYYY-MM-DD HH:mm:ss',
showTime: true,
},
}tree-select - Tree Select
{
key: 'parentId',
title: 'Parent Category',
type: 'tree-select',
options: async () => {
const res = await getCategoryTree()
return res.data
},
form: {
allowClear: true,
showSearch: true,
treeDefaultExpandAll: true,
placeholder: 'Select parent category',
},
}image - Image Upload
{
key: 'avatar',
title: 'Avatar',
type: 'image',
form: {
maxCount: 1,
accept: 'image/*',
maxSize: 2, // MB
tip: 'Supports jpg, png format, max 2MB',
},
}images - Multiple Image Upload
{
key: 'gallery',
title: 'Gallery',
type: 'images',
form: {
maxCount: 9,
accept: 'image/*',
maxSize: 5,
},
}markdown - Markdown Editor
{
key: 'content',
title: 'Content',
type: 'markdown',
form: {
height: 400,
placeholder: 'Enter article content...',
},
}icon - Icon Picker
{
key: 'icon',
title: 'Icon',
type: 'icon',
form: {
placeholder: 'Select icon',
},
}json - JSON Editor
{
key: 'config',
title: 'Configuration',
type: 'json',
form: {
height: 200,
defaultValue: {},
},
}🎨 Advanced Usage
Field Linkage
const fieldsConfig = [
{
key: 'discountType',
title: 'Discount Type',
type: 'select',
options: [
{ label: 'Fixed Amount', value: 'fixed' },
{ label: 'Percentage', value: 'percent' },
],
},
{
key: 'discountValue',
title: 'Discount Value',
type: 'number',
form: {
// Dependent Field
dependencies: ['discountType'],
// Dynamic Configuration
fieldProps: (form) => {
const type = form.getFieldValue('discountType')
return {
suffix: type === 'percent' ? '%' : '',
max: type === 'percent' ? 100 : 99999,
}
},
},
},
]Conditional Display
{
key: 'reason',
title: 'Rejection Reason',
type: 'textarea',
form: {
// Only show when status is rejected
dependencies: ['status'],
visible: (form) => form.getFieldValue('status') === 'rejected',
required: (form) => form.getFieldValue('status') === 'rejected',
},
}Custom Validation
{
key: 'password',
title: 'Password',
type: 'text',
form: {
required: true,
rules: [
{ min: 8, message: 'Password at least 8 characters' },
{
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
message: 'Password must contain uppercase, lowercase letters and numbers',
},
{
validator: async (_, value) => {
if (value && value.includes(' ')) {
throw new Error('Password cannot contain spaces')
}
},
},
],
},
}Form Layout
<SmartModalForm
fieldsConfig={fieldsConfig}
// Form Layout
layout="horizontal"
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
// Or use grid layout
grid={true}
colProps={{ span: 12 }} // Each field takes half width
/>
// Control individual field width
{
key: 'description',
title: 'Description',
type: 'textarea',
form: {
colSpan: 24, // Full row width
},
}Form Grouping
const fieldsConfig = [
// Basic Info Group
{
key: 'basicInfo',
title: 'Basic Information',
type: 'group',
children: [
{ key: 'name', title: 'Name', type: 'text' },
{ key: 'code', title: 'Code', type: 'text' },
],
},
// Advanced Config Group
{
key: 'advancedConfig',
title: 'Advanced Configuration',
type: 'group',
children: [
{ key: 'enable', title: 'Enabled', type: 'switch' },
{ key: 'remark', title: 'Remarks', type: 'textarea' },
],
},
]📊 Props Reference
Common Props
| Property | Type | Required | Description |
|---|---|---|---|
fieldsConfig | FieldConfig[] | ✅ | Field configuration array |
initialValues | object | Initial values | |
onFinish | (values) => Promise<boolean> | ✅ | Submit callback |
onValuesChange | (changedValues, allValues) => void | Value change callback | |
layout | 'horizontal' | 'vertical' | 'inline' | Layout mode | |
disabled | boolean | Disable entire form | |
readonly | boolean | Read-only mode |
SmartModalForm Props
| Property | Type | Required | Description |
|---|---|---|---|
title | string | ✅ | Modal title |
open | boolean | ✅ | Whether to show |
onOpenChange | (open: boolean) => void | ✅ | Show state change |
width | number | Modal width, default 600 | |
destroyOnClose | boolean | Destroy on close, default true |
SmartDrawerForm Props
| Property | Type | Required | Description |
|---|---|---|---|
title | string | ✅ | Drawer title |
open | boolean | ✅ | Whether to show |
onOpenChange | (open: boolean) => void | ✅ | Show state change |
width | number | Drawer width, default 400 | |
placement | 'left' | 'right' | Position, default right |