react-dynamic-form-builder-reactquery
Version:
A flexible and powerful dynamic form builder for React with Ant Design - 23 components, responsive layout, React Query integration, field dependencies, conditional fields, full TypeScript support
1,185 lines (1,013 loc) โข 27.9 kB
Markdown
# ๐ React Dynamic Form Builder - แกแ แฃแแ แแแแแแแแแแ
## ๐ฆ แแแแแขแแก แกแ แฃแแ แกแขแ แฃแฅแขแฃแ แ
```
@ladojibladze/react-dynamic-form-builder
โ
โโโ ๐ฏ DynamicForm # แแแแแแ แ แแแแแแแฃแ แ แคแแ แแแก แแแแแแแแแขแ
โ
โโโ ๐ Form Input Components (13)
โ โโโ MyInput # แขแแฅแกแขแฃแ แ แแแแ
โ โโโ MyTextArea # แแ แแแแแฎแแแแแแ แขแแฅแกแขแ
โ โโโ MySelect # แกแแแแฅแขแ (single/multiple)
โ โโโ MyCascader # แแแ แแ แฅแแฃแแ แกแแแแฅแขแ
โ โโโ MyDatePicker # แแแ แแฆแแก แแ แฉแแแ
โ โโโ MyRangePicker # แแแ แแฆแแแแก แแแแแแแแแ
โ โโโ MyTimePicker # แแ แแแก แแ แฉแแแ
โ โโโ MyTimeRangePicker # แแ แแแก แแแแแแแแแ
โ โโโ MyCheckbox # แฉแแแแแฅแกแแแ
โ โโโ MyRadio # แ แแแแ แฆแแแแแแแ
โ โโโ MySwitch # แแแแแแ แแแแแ
โ โโโ MySlider # แกแแแแแแ แ
โ โโโ MyRate # แจแแคแแกแแแ (แแแ แกแแแแแแแแ)
โ โโโ MyUpload # แคแแแแแแแก แแขแแแ แแแ
โ
โโโ ๐จ UI Components (8)
โ โโโ MyButton # แฆแแแแแ (gradient, shadow)
โ โโโ MyCard # แแแ แแแ (elevated, bordered)
โ โโโ MyModal # แแแแแแฃแ แ แคแแแฏแแ แ
โ โโโ MyDrawer # แแแแแกแแฌแแแ แแแแแแ
โ โโโ MyPopover # Popover
โ โโโ MyTable # แชแฎแ แแแ
โ โโโ MyCollapse # Collapse แแแแแแแแ
โ โโโ MyForm # แคแแ แแแก wrapper
โ โโโ MyFormItem # แคแแ แแแก แแแแแแแขแแก wrapper
โ
โโโ ๐ง Utilities (3)
โโโ useFieldDependencies # แแแแแแแก แแแแแแแแแแฃแแแแแแ
โโโ useFormDirty # แชแแแแแแแแแแก แแ แแฅแแแแ
โโโ useConditionalFields # แแแ แแแแแ แแแแแแ
**แกแฃแ: 22 แแแแแแแแแขแ + 3 utility hook**
```
## ๐ฏ DynamicForm - แ แแแแ แแฃแจแแแแก?
### ๐ แแแแคแแแฃแ แแชแแแก แกแขแ แฃแฅแขแฃแ แ
```typescript
import { DynamicForm, FormConfig } from '@ladojibladze/react-dynamic-form-builder';
const formConfig: FormConfig = {
// 1. แแแแแแแก แแแแกแแแฆแแ แ
fields: [
{
name: 'username',
type: 'input',
label: 'แกแแฎแแแ',
rules: [{ required: true }],
span: { xs: 24, md: 12 } // Responsive!
}
],
// 2. แแ แกแแฅแชแแแแแ แแแงแแคแ
sections: [
{
title: 'แแแ แแแ แแแคแแ แแแชแแ',
description: 'แจแแแแกแแ แแแ แแแ แแแแแชแแแแแ',
collapsible: true,
fields: [...]
}
],
// 3. แคแแ แแแก แแแ แแแแขแ แแแ
layout: 'vertical', // 'horizontal' | 'vertical' | 'inline'
columns: 2, // แกแแแขแแแแก แ แแแแแแแแ
submitText: 'แแแแแแแแ',
showReset: true
};
```
### ๐ CRUD Modes
```typescript
// CREATE MODE
<DynamicForm
mode="create"
config={formConfig}
onSubmit={handleCreate}
/>
// EDIT MODE
<DynamicForm
mode="edit"
initialValues={existingData}
config={formConfig}
onSubmit={handleUpdate}
/>
// VIEW MODE (Readonly)
<DynamicForm
mode="view"
initialValues={data}
config={formConfig}
/>
```
### ๐ isDirty Tracking
```typescript
const [isDirty, setIsDirty] = useState(false);
<DynamicForm
config={formConfig}
initialValues={data}
onDirtyChange={setIsDirty} // แแแแซแแแแแแ แชแแแแแแแแแ
onSubmit={handleSubmit}
/>
// แแฎแแ แจแแแแซแแแแ แแแแแแงแแแแ isDirty
{isDirty && <div>แแแฅแแ แจแแฃแแแฎแแแ แชแแแแแแแแแ!</div>}
```
## ๐ฑ Responsive Grid System (24-Column)
### Breakpoints
| Breakpoint | แแแแ | แแแฌแงแแแแแแแ | แแแแแงแแแแแ |
|-----------|------|------------|-----------|
| **xs** | <576px | Mobile | span: 24 (แกแ แฃแแ) |
| **sm** | โฅ576px | Tablet | span: 12 (แแแฎแแแแ แ) |
| **md** | โฅ768px | Tablet | span: 12, 8 |
| **lg** | โฅ992px | Desktop | span: 8, 6 |
| **xl** | โฅ1200px | Desktop+ | span: 6, 4 |
| **xxl** | โฅ1600px | 4K | span: 4, 3 |
### Mobile-First Example
```typescript
const config = {
fields: [
{
name: 'firstName',
type: 'input',
// Mobile: แกแ แฃแแ แกแแแแแ
// Tablet: แแแฎแแแแ แ
// Desktop: แแแกแแแแแ
span: { xs: 24, sm: 12, lg: 8 }
},
{
name: 'lastName',
type: 'input',
span: { xs: 24, sm: 12, lg: 8 }
},
{
name: 'email',
type: 'input',
span: { xs: 24, sm: 24, lg: 8 } // Tablet-แแ แกแ แฃแแ
}
]
}
```
### Advanced Layout
```typescript
{
name: 'field',
span: { xs: 24, lg: 12 }, // แกแแแแแ
offset: { lg: 6 }, // แชแแ แแแแ แแแแแแ แแแ แชแฎแแแแแ
push: { md: 2 }, // แแแแแแแแแแแแ แแแ แฏแแแแ
pull: { md: 2 }, // แแแแแแแแแแแแ แแแ แชแฎแแแ
style: { padding: '10px' }, // Custom style
formItemStyle: { marginBottom: '20px' }
}
```
## ๐ Field Dependencies (แแแแแแแแแแฃแแ แแแแแแ)
### Simple Dependency
```typescript
{
fields: [
{
name: 'country',
type: 'select',
options: [
{ label: 'แกแแฅแแ แแแแแ', value: 'ge' },
{ label: 'USA', value: 'us' }
]
},
{
name: 'city',
type: 'select',
dependsOn: 'country', // แแแแแแแแแแฃแแแ country-แแ
dependencies: [{
field: 'country',
getOptions: (countryValue) => {
if (countryValue === 'ge') {
return [
{ label: 'แแแแแแกแ', value: 'tbilisi' },
{ label: 'แแแแฃแแ', value: 'batumi' }
];
}
return [
{ label: 'New York', value: 'ny' },
{ label: 'LA', value: 'la' }
];
}
}]
}
]
}
```
### Multi-level Cascader (Backend Integration)
```typescript
{
name: 'location',
type: 'cascader',
cascaderOptions: await fetchLocations(),
dependencies: [{
loadData: async (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
// Backend-แแแ แแแแแชแแแแแแก แแแฆแแแ
const children = await api.getSubLocations(targetOption.value);
targetOption.children = children;
targetOption.loading = false;
}
}]
}
```
## ๐ญ Conditional Fields (แแแ แแแแแ แแแแแแ)
### Simple Condition
```typescript
{
name: 'hasVehicle',
type: 'radio',
options: [
{ label: 'แแแแฎ', value: 'yes' },
{ label: 'แแ แ', value: 'no' }
],
conditionalFields: [
{
when: 'hasVehicle', // แ แแแแกแแช hasVehicle
is: ['yes'], // แแ แแก 'yes'
fields: [ // แแแแแฉแแแแก แแก แแแแแแ
{
name: 'vehicleType',
type: 'select',
options: [
{ label: 'แแแแฅแแแ', value: 'car' },
{ label: 'แแแขแแชแแแแ', value: 'moto' }
]
}
]
}
]
}
```
### Nested Conditions
```typescript
{
name: 'employmentStatus',
type: 'select',
options: [
{ label: 'แแแกแแฅแแแแฃแแ', value: 'employed' },
{ label: 'แกแขแฃแแแแขแ', value: 'student' }
],
conditionalFields: [
{
when: 'employmentStatus',
is: 'employed',
fields: [
{
name: 'companyName',
type: 'input',
// Nested condition!
conditionalFields: [{
when: 'companyName',
condition: (value) => value?.length > 0,
fields: [
{ name: 'position', type: 'input' },
{ name: 'salary', type: 'number' }
]
}]
}
]
}
]
}
```
## โ
Validation System
### Built-in Rules
```typescript
{
name: 'email',
type: 'email',
rules: [
{ required: true, message: 'แแ-แคแแกแขแ แกแแแแแแแแฃแแแ' },
{ type: 'email', message: 'แแ แแกแฌแแ แ แคแแ แแแขแ' },
{
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'แแแฎแแแ แจแแแงแแแแแ แกแฌแแ แ แแ-แคแแกแขแ'
}
]
}
```
### Custom Validation
```typescript
{
name: 'password',
type: 'password',
rules: [
{
validator: async (_, value) => {
if (!value) {
return Promise.reject('แแแ แแแ แกแแแแแแแแฃแแแ');
}
if (value.length < 8) {
return Promise.reject('แแแแแแฃแ 8 แกแแแแแแ');
}
if (!/[A-Z]/.test(value)) {
return Promise.reject('แฃแแแ แจแแแชแแแแแก แแแ แแกแแก');
}
return Promise.resolve();
}
}
]
}
```
### Async Validation
```typescript
{
name: 'username',
type: 'input',
rules: [
{
validator: async (_, value) => {
const available = await api.checkUsername(value);
if (!available) {
return Promise.reject('แแก แกแแฎแแแ แฃแแแ แแแแแแแแฃแแแ');
}
return Promise.resolve();
}
}
]
}
```
## ๐จ CSS Customization - แ แแแแ แแแแแ แแแ แกแขแแแ?
### 1. CSS Classes
แงแแแแ แแแแแแแแแขแก แแฅแแก className prop แแ แกแแแชแแแแฃแ แ แแแแกแแแ:
```css
/* FormField tooltips */
.form-field-tooltip-icon {
color: #888;
font-size: 14px;
}
/* DynamicForm sections */
.dynamic-form-section {
margin-bottom: 24px;
}
.dynamic-form-section-title {
margin-bottom: 16px;
font-size: 18px;
font-weight: 600;
}
.dynamic-form-section-description {
color: #888;
margin-bottom: 16px;
}
.dynamic-form-section-collapse {
margin-bottom: 16px;
}
/* Form actions */
.dynamic-form-actions {
margin-top: 24px;
margin-bottom: 0;
}
```
### 2. SCSS Support
```scss
.my-form {
.dynamic-form-section {
&-title {
color: $primary-color;
border-bottom: 2px solid $border-color;
padding-bottom: 10px;
}
&-description {
font-style: italic;
color: lighten($text-color, 20%);
}
}
}
```
### 3. Tailwind CSS
```typescript
<DynamicForm
className="space-y-4 p-6 bg-white rounded-lg shadow-lg"
config={{
fields: [
{
name: 'name',
type: 'input',
className: 'mb-4',
formItemStyle: { marginBottom: 0 }
}
]
}}
/>
```
### 4. CSS-in-JS (Styled Components)
```typescript
import styled from 'styled-components';
const StyledForm = styled.div`
.dynamic-form-section {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.form-field-tooltip-icon {
color: ${props => props.theme.primary};
}
`;
<StyledForm>
<DynamicForm config={config} />
</StyledForm>
```
### 5. Per-Field Styling
```typescript
{
name: 'field',
type: 'input',
style: { // Col wrapper style
padding: '0 10px'
},
formItemStyle: { // Form.Item style
marginBottom: '30px',
'.ant-form-item-label': {
fontWeight: 'bold'
}
},
className: 'custom-field', // Custom class
componentProps: { // Input component style
style: {
borderRadius: '8px',
border: '2px solid #1890ff'
}
}
}
```
## ๐ Form Input Components - แแแขแแแฃแ แ แแแแแแแแแแ
### 1. MyInput
```typescript
import { MyInput } from '@ladojibladze/react-dynamic-form-builder';
<MyInput
value={value}
onChange={setValue}
type="text" // 'text' | 'password' | 'email' | 'number' | 'phone'
placeholder="แจแแแงแแแแแ แขแแฅแกแขแ"
disabled={false}
readonly={false}
maxLength={100}
prefix={<UserOutlined />}
suffix={<CheckCircleOutlined />}
addonBefore="https://"
addonAfter=".com"
allowClear
/>
```
**DynamicForm-แจแ:**
```typescript
{
name: 'website',
type: 'input',
componentProps: {
addonBefore: 'https://',
addonAfter: '.com',
placeholder: 'mysite'
}
}
```
### 2. MySelect
```typescript
<MySelect
value={value}
onChange={setValue}
options={[
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2', disabled: true }
]}
mode="multiple" // undefined | 'multiple' | 'tags'
showSearch
allowClear
placeholder="แแแ แฉแแแ..."
loading={false}
filterOption={(input, option) =>
option?.label.toLowerCase().includes(input.toLowerCase())
}
/>
```
### 3. MyCascader (Multi-Level)
```typescript
<MyCascader
value={value}
onChange={setValue}
options={[
{
label: 'แกแแฅแแ แแแแแ',
value: 'ge',
children: [
{
label: 'แแแแแแกแ',
value: 'tbilisi',
children: [
{ label: 'แแแแ', value: 'vake' },
{ label: 'แกแแแฃแ แแแแ', value: 'saburtalo' }
]
}
]
}
]}
multiple={false}
showSearch
changeOnSelect // แแแแแฉแแแแก แจแฃแแแแแฃแ แ แแ แฉแแแแแ
expandTrigger="hover" // 'click' | 'hover'
loadData={async (selectedOptions) => {
// Dynamic load from backend
}}
/>
```
### 4. MyDatePicker & MyRangePicker
```typescript
<MyDatePicker
value={date}
onChange={setDate}
format="YYYY-MM-DD"
showTime={{ format: 'HH:mm' }}
picker="date" // 'date' | 'week' | 'month' | 'quarter' | 'year'
disabledDate={(current) => current && current < dayjs()}
/>
<MyRangePicker
value={[startDate, endDate]}
onChange={setDates}
format="YYYY-MM-DD HH:mm"
showTime
/>
```
### 5. MyTimePicker
```typescript
<MyTimePicker
value={time}
onChange={setTime}
format="HH:mm:ss"
use12Hours={false}
showNow
hourStep={1}
minuteStep={15}
secondStep={30}
/>
```
### 6. MyCheckbox & MyRadio
```typescript
<MyCheckbox
value={['option1', 'option3']}
onChange={setValue}
options={[
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2', disabled: true },
{ label: 'Option 3', value: 'option3' }
]}
/>
<MyRadio
value="yes"
onChange={setValue}
options={[
{ label: 'แแแแฎ', value: 'yes' },
{ label: 'แแ แ', value: 'no' }
]}
optionType="button" // 'default' | 'button'
buttonStyle="solid" // 'outline' | 'solid'
/>
```
### 7. MySlider
```typescript
<MySlider
value={50}
onChange={setValue}
min={0}
max={100}
step={10}
marks={{
0: '0ยฐC',
25: '25ยฐC',
50: '50ยฐC',
75: '75ยฐC',
100: '100ยฐC'
}}
range // Range slider
vertical // Vertical orientation
tooltipVisible // Always show tooltip
/>
```
### 8. MyRate
```typescript
<MyRate
value={4.5}
onChange={setValue}
count={5}
allowHalf
allowClear
character={<HeartOutlined />} // Custom icon
tooltips={['terrible', 'bad', 'normal', 'good', 'wonderful']}
/>
```
### 9. MyUpload
```typescript
<MyUpload
value={fileList}
onChange={setFileList}
uploadType="dragger" // 'button' | 'dragger' | 'avatar'
maxCount={5}
accept="image/*"
listType="picture-card" // 'text' | 'picture' | 'picture-card'
beforeUpload={(file) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('แแฎแแแแ JPG/PNG แคแแแแแแ!');
}
return isJpgOrPng;
}}
customRequest={async ({ file, onSuccess, onError }) => {
try {
const url = await uploadToServer(file);
onSuccess({ url });
} catch (error) {
onError(error);
}
}}
/>
```
## ๐จ UI Components - แแแขแแแฃแ แ แแแแแแแแแแ
### 1. MyButton
```typescript
<MyButton
buttonVariant="primary" // 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info'
gradient // Gradient background
rounded // Rounded corners
shadow // Box shadow
type="primary" // Ant Design type
size="large" // 'small' | 'middle' | 'large'
loading={isLoading}
disabled={false}
icon={<SearchOutlined />}
onClick={handleClick}
>
แซแแแแ
</MyButton>
```
**Gradient แแแ แแแแขแแแ:**
- `primary`: Blue gradient
- `success`: Green gradient
- `danger`: Red gradient
- `warning`: Orange gradient
### 2. MyCard
```typescript
<MyCard
cardVariant="elevated" // 'default' | 'bordered' | 'elevated' | 'gradient'
shadow="lg" // 'none' | 'sm' | 'md' | 'lg' | 'xl'
title="แแแ แแแแก แกแแแแฃแ แ"
extra={<a href="#">More</a>}
hoverable // Hover effect
bordered={false}
style={{ width: 300 }}
>
<p>แแแ แแแแก แจแแแแแแกแ</p>
</MyCard>
```
### 3. MyModal
```typescript
const [open, setOpen] = useState(false);
<MyModal
open={open}
onOk={() => {
// Handle OK
setOpen(false);
}}
onCancel={() => setOpen(false)}
title="แแแแแแฃแ แ แคแแแฏแแ แ"
width={600}
centered
footer={[
<MyButton key="back" onClick={() => setOpen(false)}>
แแแฃแฅแแแแ
</MyButton>,
<MyButton key="submit" buttonVariant="primary" onClick={handleOk}>
แแแแแกแขแฃแ แแแ
</MyButton>
]}
>
<p>แจแแแแแแกแ</p>
</MyModal>
```
### 4. MyDrawer
```typescript
<MyDrawer
open={open}
onClose={() => setOpen(false)}
title="แแแแแกแแฌแแแ แแแแแแ"
placement="right" // 'left' | 'right' | 'top' | 'bottom'
width={400}
extra={
<Space>
<MyButton onClick={() => setOpen(false)}>แแแฎแฃแ แแ</MyButton>
<MyButton buttonVariant="primary">แจแแแแฎแแ</MyButton>
</Space>
}
>
<p>แแแแแแแก แจแแแแแแกแ</p>
</MyDrawer>
```
### 5. MyCollapse
```typescript
// Option 1: Using panels prop
<MyCollapse
panels={[
{
key: '1',
header: 'แแแแแแ 1',
children: <p>แจแแแแแแกแ 1</p>
},
{
key: '2',
header: 'แแแแแแ 2',
children: <p>แจแแแแแแกแ 2</p>,
disabled: true
}
]}
defaultActiveKey={['1']}
accordion // Only one panel open at a time
ghost // Borderless style
/>
// Option 2: Using children
<MyCollapse defaultActiveKey={['1']}>
<MyCollapse.Panel key="1" header="แแแแแแ 1">
<p>แจแแแแแแกแ 1</p>
</MyCollapse.Panel>
<MyCollapse.Panel key="2" header="แแแแแแ 2">
<p>แจแแแแแแกแ 2</p>
</MyCollapse.Panel>
</MyCollapse>
```
### 6. MyTable
```typescript
interface DataType {
key: string;
name: string;
age: number;
address: string;
}
<MyTable<DataType>
dataSource={data}
columns={[
{
title: 'แกแแฎแแแ',
dataIndex: 'name',
key: 'name',
sorter: (a, b) => a.name.localeCompare(b.name),
filters: [
{ text: 'Joe', value: 'Joe' },
{ text: 'Jim', value: 'Jim' }
],
onFilter: (value, record) => record.name.indexOf(value as string) === 0
},
{
title: 'แแกแแแ',
dataIndex: 'age',
key: 'age'
}
]}
tableVariant="striped" // 'default' | 'bordered' | 'striped' | 'compact'
pagination={{
pageSize: 10,
showSizeChanger: true,
showTotal: (total) => `แกแฃแ: ${total}`
}}
rowSelection={{
type: 'checkbox',
onChange: (selectedRowKeys, selectedRows) => {
console.log(selectedRows);
}
}}
/>
```
### 7. MyForm & MyFormItem
```typescript
import { MyForm, MyFormItem, MyInput, MyButton } from '@ladojibladze/react-dynamic-form-builder';
const [form] = MyForm.useForm();
<MyForm
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{ username: 'admin' }}
>
<MyFormItem
name="username"
label="แแแแฎแแแ แแแแแ"
rules={[{ required: true }]}
>
<MyInput placeholder="แจแแแงแแแแแ แกแแฎแแแ" />
</MyFormItem>
<MyFormItem
name="email"
label="แแ-แคแแกแขแ"
rules={[
{ required: true },
{ type: 'email' }
]}
>
<MyInput type="email" />
</MyFormItem>
<MyFormItem>
<MyButton
type="primary"
htmlType="submit"
buttonVariant="primary"
gradient
>
แแแแแแแแ
</MyButton>
</MyFormItem>
</MyForm>
```
## ๐ง Utility Hooks
### 1. useFormDirty
```typescript
import { useFormDirty } from '@ladojibladze/react-dynamic-form-builder';
const MyComponent = () => {
const [form] = Form.useForm();
const initialValues = { name: 'John' };
const { isDirty, checkDirtyState, resetDirtyState } = useFormDirty(
form,
initialValues,
(dirty) => {
console.log('Form dirty state changed:', dirty);
}
);
return (
<>
<Form form={form} initialValues={initialValues}>
{/* fields */}
</Form>
{isDirty && <Alert message="แแแฅแแ แจแแฃแแแฎแแแ แชแแแแแแแแแ" />}
</>
);
};
```
### 2. useFieldDependencies
```typescript
import { useFieldDependencies } from '@ladojibladze/react-dynamic-form-builder';
const { dependentOptions, loadingFields, handleDependencyChange } = useFieldDependencies(
form,
fields
);
// dependentOptions - แแแแแแแฃแ แแ แแแขแแแ แแฃแแ แแคแจแแแแแ
// loadingFields - loading state แแแแแแฃแแ แแแแแกแแแแก
// handleDependencyChange - แแแแฃแแแฃแ แ แแแแแแแแแแฃแแแแแก แแแ แแแ
```
### 3. useConditionalFields
```typescript
import { useConditionalFields } from '@ladojibladze/react-dynamic-form-builder';
const allFields = useConditionalFields(
baseFields, // แซแแ แแแแแ แแแแแแ
formValues, // แคแแ แแแก แแแแแแแแ แ แแแแจแแแแแแแแแ
5 // max depth (circular reference protection)
);
// allFields - แจแแแชแแแก แซแแ แแแแ แแ แแแ แแแแ แแแแแแก
```
## ๐ Advanced Use Cases
### 1. Multi-Step Form
```typescript
const [step, setStep] = useState(0);
const steps = [
{
title: 'แแแ แแแ แแแคแแ แแแชแแ',
fields: [
{ name: 'firstName', type: 'input' },
{ name: 'lastName', type: 'input' }
]
},
{
title: 'แกแแแแแขแแฅแขแ',
fields: [
{ name: 'email', type: 'email' },
{ name: 'phone', type: 'phone' }
]
}
];
<Steps current={step}>
{steps.map((s, i) => <Step key={i} title={s.title} />)}
</Steps>
<DynamicForm
config={{ fields: steps[step].fields }}
onSubmit={(values) => {
if (step < steps.length - 1) {
setStep(step + 1);
} else {
// Final submit
}
}}
/>
```
### 2. Form Builder UI
```typescript
const [fields, setFields] = useState<FieldConfig[]>([]);
const addField = (type: FieldType) => {
setFields([...fields, {
name: `field_${Date.now()}`,
type,
label: `New ${type} field`
}]);
};
<div>
<MyButton onClick={() => addField('input')}>Add Input</MyButton>
<MyButton onClick={() => addField('select')}>Add Select</MyButton>
<DynamicForm
config={{ fields }}
mode="create"
/>
</div>
```
### 3. Backend Integration
```typescript
const formConfig: FormConfig = {
fields: [
{
name: 'category',
type: 'select',
dependencies: [{
field: 'category',
getOptions: async (value) => {
const response = await api.getSubcategories(value);
return response.data;
}
}]
}
]
};
<DynamicForm
config={formConfig}
onSubmit={async (values) => {
try {
await api.createItem(values);
message.success('แจแแแแฎแฃแแแ!');
} catch (error) {
message.error('แจแแชแแแแ!');
}
}}
/>
```
## ๐ฆ Installation & Setup
```bash
npm install @ladojibladze/react-dynamic-form-builder
npm install react react-dom antd
```
```typescript
import '@ladojibladze/react-dynamic-form-builder/dist/style.css'; // แแฃ แแ แแก
import 'antd/dist/reset.css'; // Ant Design styles
import {
DynamicForm,
MyButton,
MyCard,
// ... other components
} from '@ladojibladze/react-dynamic-form-builder';
```
## ๐ฏ Best Practices
### 1. Performance
```typescript
// โ
แแแ แแ - useMemo for config
const formConfig = useMemo(() => ({
fields: [...]
}), [dependencies]);
// โ แชแฃแแ - config แฅแแแแก แงแแแแ render-แแ
const formConfig = {
fields: [...]
};
```
### 2. TypeScript
```typescript
// โ
แแแ แแ - Type safety
import { FormConfig, FieldConfig } from '@ladojibladze/react-dynamic-form-builder';
const config: FormConfig = {
fields: [
{ name: 'test', type: 'input' } as FieldConfig
]
};
```
### 3. Error Handling
```typescript
<DynamicForm
config={config}
onSubmit={handleSubmit}
onValidationFail={(errors) => {
console.error('Validation errors:', errors);
message.error('แแแฎแแแ แจแแแแกแแ แงแแแแ แกแแแแแแแแฃแแ แแแแ');
}}
/>
```
## ๐ Troubleshooting
### Circular Reference Warning
```
โ ๏ธ Warning: useConditionalFields: Maximum depth (5) reached
```
**แแแแแฌแงแแแขแ:**
- แจแแแแแฌแแแ conditionalFields-แแก แกแขแ แฃแฅแขแฃแ แ
- แแแแแ แแแ maxDepth แแฃ แกแแญแแ แแ
- แแแแแแแ แแแ แแแแ แแแแแก แกแแแฃแแแ แแแแแ แแแแแแแแแแฃแแแแ
### Styles Not Applied
**แแแแแฌแงแแแขแ:**
- แแแ แฌแแฃแแแแ แ แแ Ant Design CSS แแแแแ แขแแ แแแฃแแแ
- แจแแแแแฌแแแ className-แแแแก แกแฌแแ แแ แแแฌแแ แ
- แแแแแแงแแแแ browser DevTools แกแขแแแแแแก แจแแกแแแแฌแแแแแแ
**๐ แแก แแ แแก แกแ แฃแแ แแแแแแแแแแ! แแแแแแงแแแแ แแ แจแแฅแแแแแ แจแแกแแแแจแแแแ แคแแ แแแแ! ๐**