UNPKG

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
# ๐Ÿ“– 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 แƒกแƒขแƒ˜แƒšแƒ”แƒ‘แƒ˜แƒก แƒจแƒ”แƒกแƒแƒ›แƒแƒฌแƒ›แƒ”แƒ‘แƒšแƒแƒ“ --- **๐ŸŽ‰ แƒ”แƒก แƒแƒ แƒ˜แƒก แƒกแƒ แƒฃแƒšแƒ˜ แƒ’แƒ–แƒแƒ›แƒ™แƒ•แƒšแƒ”แƒ•แƒ˜! แƒ’แƒแƒ›แƒแƒ˜แƒงแƒ”แƒœแƒ”แƒ— แƒ“แƒ แƒจแƒ”แƒฅแƒ›แƒ”แƒœแƒ˜แƒ— แƒจแƒ”แƒกแƒแƒœแƒ˜แƒจแƒœแƒแƒ•แƒ˜ แƒคแƒแƒ แƒ›แƒ”แƒ‘แƒ˜! ๐Ÿš€**