react-admin-crud-manager 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +1322 -207
  2. package/dist/index.cjs.js +20 -55
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.es.js +4092 -2529
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/types/components/CrudPage.d.ts +2 -211
  7. package/dist/types/components/Details/Details.d.ts +11 -0
  8. package/dist/types/components/Details/components/CardGroup.d.ts +9 -1
  9. package/dist/types/components/Details/components/DetailRow.d.ts +9 -1
  10. package/dist/types/components/Details/components/GroupRow.d.ts +9 -1
  11. package/dist/types/components/Filter/FilterDrawer.d.ts +2 -2
  12. package/dist/types/components/Form/components/AudioPicker.d.ts +2 -1
  13. package/dist/types/components/Form/components/Checkbox.d.ts +2 -1
  14. package/dist/types/components/Form/components/ImagePicker.d.ts +2 -1
  15. package/dist/types/components/Form/components/Input.d.ts +1 -0
  16. package/dist/types/components/Form/components/MultiImagePicker.d.ts +21 -0
  17. package/dist/types/components/Form/components/PhoneInput.d.ts +2 -1
  18. package/dist/types/components/Form/components/Radio.d.ts +2 -1
  19. package/dist/types/components/Form/components/RenderFields.d.ts +3 -2
  20. package/dist/types/components/Form/components/Select.d.ts +5 -2
  21. package/dist/types/components/Form/components/Switch.d.ts +1 -0
  22. package/dist/types/components/Form/components/TextArea.d.ts +2 -0
  23. package/dist/types/components/Form/components/TinyEditor.d.ts +3 -1
  24. package/dist/types/components/Form/components/VideoPicker.d.ts +2 -1
  25. package/dist/types/components/Modal/Modal.d.ts +11 -10
  26. package/dist/types/index.d.ts +2 -5
  27. package/dist/types/lib/crudClasses.d.ts +94 -0
  28. package/dist/types/types/crudtypes.d.ts +268 -0
  29. package/package.json +10 -8
  30. package/dist/tailwind.css +0 -2364
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  A reusable React CRUD admin template with modular components for rapid admin dashboard development.
4
4
 
5
5
  ## Features
6
+
6
7
  - Plug-and-play CRUD page component
7
8
  - Modular, customizable UI components (Table, Modal, Form, etc.)
8
9
  - Built with React 18+ and TypeScript
@@ -11,7 +12,6 @@ A reusable React CRUD admin template with modular components for rapid admin das
11
12
  - Sorting, filtering, searching, and pagination support
12
13
  - Rich form fields including text, select, image upload, rich text editor, and more
13
14
 
14
-
15
15
  ## Installation
16
16
 
17
17
  ```sh
@@ -20,15 +20,7 @@ npm install react-admin-crud-manager
20
20
 
21
21
  ## Usage
22
22
 
23
- ### 1. Import the built CSS for Tailwind styles
24
-
25
- In your main entry file (e.g., `src/main.jsx` or `src/index.js`):
26
-
27
- ```js
28
- import 'react-admin-crud-manager/dist/tailwind.css';
29
- ```
30
-
31
- ### 2. Use the component
23
+ ### 1. Use the component
32
24
 
33
25
  ```
34
26
  import Crud from 'react-admin-crud-manager';
@@ -44,31 +36,35 @@ function App() {
44
36
  ```
45
37
 
46
38
  ## Components
39
+
47
40
  - `Crud`: Main CRUD page component
48
41
  - `Table`, `Modal`, `Form`, etc.: Available for advanced use
49
42
 
50
43
  ## Props
44
+
51
45
  Below is a complete reference of the public props accepted by this package (types, accepted values and defaults). The library exposes a single primary component (`Crud`) that receives a single `config` prop — most configuration lives inside that object.
52
46
 
53
47
  ---
54
48
 
55
49
  ### Crud (default export)
50
+
56
51
  `<Crud config={config} />`
52
+
57
53
  - `config` (object) — required. Top-level configuration object used by the CRUD page.
58
54
 
59
55
  #### Key properties of `config`
60
56
 
61
- | Property | Type | Required | Description |
62
- |----------|------|----------|-------------|
63
- | `title` | string | Yes | Title of the CRUD page |
64
- | `description` | string | No | Optional description text |
65
- | `buttonText` | string | No | Custom text for action buttons |
66
- | `fetchData` | function | Yes | Async function to fetch data. Signature: `async ({ search, rows_per_page, current_page, sort_by, sort_order, ...filters }) => Promise<{ data: Array, pagination?: { current_page: number, rows_per_page: number, total_pages: number, total_records: number } }>`. Component expects `resp.data` (array) and optional `resp.pagination` for server-side pagination. |
67
- | `fetchRowDetails` | function | No | Async function to fetch additional details for a row. Signature: `async (item) => Promise<any>`. Used for view modal or details. |
68
- | `isStaticData` | boolean | No | Default: `false`. If true, add/edit/delete apply client-side instead of re-fetching |
69
- | `tableConfig` | object | Yes | Table configuration — [see tableConfig](#tableconfig-configuration) |
70
- | `modalConfig` | object | No | Modal definitions — [see modalConfig](#modalconfig-definitions) |
71
- | `filterConfig` | object | No | Filter drawer configuration — [see Form Field Schema](#form-field-schema) |
57
+ | Property | Type | Required | Description |
58
+ | ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
+ | `title` | string | Yes | Title of the CRUD page |
60
+ | `description` | string | No | Optional description text |
61
+ | `buttonText` | string | No | Custom text for action buttons |
62
+ | `fetchData` | function | Yes | Async function to fetch data. Signature: `async ({ search, rows_per_page, current_page, sort_by, sort_order, ...filters }) => Promise<{ data: Array, pagination?: { current_page: number, rows_per_page: number, total_pages: number, total_records: number } }>`. Component expects `resp.data` (array) and optional `resp.pagination` for server-side pagination. |
63
+ | `fetchRowDetails` | function | No | Async function to fetch additional details for a row. Signature: `async (item) => Promise<any>`. Used for view modal or details. |
64
+ | `isStaticData` | boolean | No | Default: `false`. If true, add/edit/delete apply client-side instead of re-fetching |
65
+ | `tableConfig` | object | Yes | Table configuration — [see tableConfig](#tableconfig-configuration) |
66
+ | `modalConfig` | object | No | Modal definitions — [see modalConfig](#modalconfig-definitions) |
67
+ | `filterConfig` | object | No | Filter drawer configuration — [see Form Field Schema](#form-field-schema) |
72
68
 
73
69
  ---
74
70
 
@@ -76,39 +72,41 @@ Below is a complete reference of the public props accepted by this package (type
76
72
 
77
73
  #### Table Configuration Keys
78
74
 
79
- | Key | Type | Required | Description |
80
- |-----|------|----------|-------------|
81
- | `table_head` | array of column objects | Yes | Column definitions [see Table column object](#table-column-object-table_head) |
82
- | `data` | array | No | Rows shown in the table |
83
- | `loading` | boolean | No | Show skeleton loader when true |
84
- | `search` | object | No | `{ enabled?: boolean, placeholder?: string, useServerSideSearch?: boolean, searchKeys?: string[] }` |
85
- | `filter` | object | No | `{ enabled?: boolean, useServerSideFilters?: boolean }` |
86
- | `pagination` | object | No | `{ enabled?: boolean, rows_per_page?: number, useServerSidePagination?: boolean, current_page?: number, total_pages?: number, total_records?: number }` |
87
- | `sort` | object | No | `{ enabled?: boolean, useServerSideSorting?: boolean, options?: Array<{ value: string, label: string, key: string, order: string, type?: string }>, fields?: string[], defaultValue?: string, autoGenerate?: boolean, clearLabel?: string, onChange?: (payload) => void }` |
88
- | `emptyMessage` | string | No | Message shown when table has no rows |
89
- | `onMenuAction` | function | No | Callback: `(actionType: string, item: object)` |
90
- | `setServerSidePaginationData` | function | No | Used to update server-side pagination/search state |
91
- | `onFilterApply` | function | No | Callback: `(filters: object)` |
92
- | `filterConfig` | object | No | Fields array rendered in the FilterDrawer |
75
+ | Key | Type | Description | Accepted Values / Example |
76
+ | ----------------------------- | ----------------------- | ------------------------------- | ------- |
77
+ | `table_head` | array of column objects | Column definitions | Array of table column objects (see [Table column object](#table-column-object-table_head)) |
78
+ | `data` | array | Rows displayed in the table | Array of objects (e.g., `[{ id: 1, name: "John" }, ...]`) |
79
+ | `loading` | boolean | Show skeleton loader when true | `true`, `false`; default: `false` |
80
+ | `search` | object | Search functionality config | `{ enabled: true, placeholder: "Search...", useServerSideSearch?: false, searchKeys?: ["name", "email"] }` |
81
+ | `filter` | object | Filter drawer config | `{ enabled: true, useServerSideFilters?: false }` |
82
+ | `pagination` | object | Pagination controls | `{ enabled: true, rows_per_page: 10, useServerSidePagination?: false, current_page?: 1, total_pages?: 5, total_records?: 50 }` |
83
+ | `sort` | object | Sorting configuration | `{ enabled: true, useServerSideSorting?: false, autoGenerate: true, onChange?: (payload) => void, options?: [...], clearLabel?: "Clear Sort" }` |
84
+ | `exportCSV` | object | Export data as CSV | `{ enabled: true, fileName: "users.csv", fields: [{ label: "Name", key: "name" }, ...] }` |
85
+ | `emptyMessage` | string | Message when no rows exist | `"No data available"`, `"No records found"`, etc. |
86
+ | `onMenuAction` | function | Callback for menu actions (edit/view/delete) | `(actionType: string, item: object) => void`; actionType: `"edit"`, `"view"`, `"delete"` |
87
+ | `setServerSidePaginationData` | function | Update pagination/search state | `(data: { search, rows_per_page, current_page, sort_by, sort_order, ...filters }) => void` |
88
+ | `onFilterApply` | function | Callback when filter applied | `(filters: object) => void` |
89
+ | `filterConfig` | object | Filter drawer form fields | `{ fields: [{ key: "status", type: "select", options: [...] }, ...] }`; uses [Form Field Schema](#form-field-schema) |
90
+ | `rowClick` | function | Callback on table row click | `(row: object, rowIndex: number) => void` |
93
91
 
94
92
  #### Table Column Object (`table_head[]`)
95
93
 
96
- | Key | Type | Required | Description |
97
- |-----|------|----------|-------------|
98
- | `key` | string | Yes | Property name in row objects |
99
- | `title` | string | No | Column header text |
100
- | `type` | string | No | Column type: `plain` (default), `index`, `group`, `chip`, `date`, `avatar`, `menu_actions` |
101
- | `imageKey` | string | No | Used with `group`/`avatar` types |
102
- | `titleKey` | string | No | Used with `group`/`avatar` types |
103
- | `subtitleKey` | string | No | Used with `group`/`avatar` types |
104
- | `onClickDetails` | boolean | No | If true, clicking cell triggers view action |
105
- | `variant` | string | No | For chips: `contained`, `outline`, `soft` |
106
- | `chipOptions` | array | No | Array of `{ value: string|number|boolean, label: string, color?: string }` |
107
- | `defaultColor` | string | No | Default chip color key (e.g., `green`) |
108
- | `className` | string | No | Custom CSS class for cell |
109
- | `format` | string | No | Date format (e.g., `DD MMM YYYY`) |
110
- | `menuList` | array | No | Menu action objects: `{ title, type, variant?, icon? }` |
111
- | `render` | function | No | Custom renderer: `(row, rowIndex) => ReactNode`. Overrides built-in renderers |
94
+ | Key | Type | Description | Accepted Values / Example |
95
+ | ---------------- | -------- | ---------------------------------------------------------------- | ------------------------------------- |
96
+ | `key` | string | Property name in row objects | `"id"`, `"name"`, `"email"` (must exist in data objects)|
97
+ | `title` | string | Column header text | `"User ID"`, `"Full Name"`, `"Email Address"` |
98
+ | `type` | string | Column renderer type | `"plain"` (default), `"index"` (row number), `"group"` (avatar+text), `"chip"` (badge), `"date"`, `"avatar"`, `"menu_actions"` |
99
+ | `imageKey` | string | Image property for avatar/group types | `"profileImage"`, `"avatarUrl"` (path to image in data object) |
100
+ | `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` (property key in data object) |
101
+ | `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` (property key in data object) |
102
+ | `onClickDetails` | boolean | Clicking cell opens view details modal | `true`, `false` (default: `false`) |
103
+ | `variant` | string | Chip styling variant | `"contained"`, `"outline"`, `"soft"` (used with `type: "chip"`) |
104
+ | `chipOptions` | array | Map values to chip labels and colors; array of `{ value: string\|number\|boolean, label: string, color?: string }` | `[{ value: "active", label: "Active", color: "green" }, { value: "inactive", label: "Inactive", color: "red" }]` |
105
+ | `defaultColor` | string | Default color for chips (if no match in chipOptions) | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"`, etc. |
106
+ | `className` | string | Custom CSS class for cell content | Tailwind classes: `"font-bold text-sm text-gray-600"` |
107
+ | `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` (uses date-fns patterns) |
108
+ | `render` | function | Custom cell renderer (overrides built-in logic) | `(row: object, rowIndex: number) => ReactNode`; e.g., `(row) => <span>{row.name.toUpperCase()}</span>` |
109
+ | `menuList` | array | Action menu items; array of `{ title: string, type: string, variant?: string, icon?: ReactNode }` | `[{ title: "Edit", type: "edit", icon: <EditIcon /> }, { title: "Delete", type: "delete", icon: <TrashIcon /> }]` |
112
110
 
113
111
  ---
114
112
 
@@ -116,224 +114,1341 @@ Below is a complete reference of the public props accepted by this package (type
116
114
 
117
115
  #### Add & Edit Modal (`addModal`, `editModal`)
118
116
 
119
- | Property | Type | Required | Description |
120
- |----------|------|----------|-------------|
121
- | `title` | string | Yes | Modal title |
122
- | `size` | string | No | Modal size: `sm`, `md` (default), `lg`, `xl`, `full` |
123
- | `formClass` | string | No | Custom CSS class for form |
124
- | `formFields` | array | Yes | Form field objects [see Form Field Schema](#form-field-schema) |
125
- | `handleSubmit` | function | Yes | Async callback: `(formData) => Promise<{ newObject, targetObject? }>`. For add: return `{ newObject }`. For edit: return `{ newObject, targetObject }` |
126
- | `actionButtons` | array | No | Action button objects: `[{ type, label, color, variant, onClick, disabled, className }]` |
117
+ | Property | Type | Required | Description | Accepted Values / Example |
118
+ | --------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ |
119
+ | `title` | string | Yes | Modal title | `"Add New User"`, `"Edit User Profile"` |
120
+ | `icon` | ReactNode | No | Icon element displayed in modal header | `<PlusIcon />`, `<EditIcon />` |
121
+ | `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
122
+ | `formClass` | string | No | Custom CSS class for form wrapper | Tailwind classes: `"grid grid-cols-12 gap-4"` |
123
+ | `formFields` | array | Yes | Form field objects | Array of form field objects (see [Form Field Schema](#form-field-schema)) |
124
+ | `handleSubmit` | function | Yes | Async callback on form submission | `async (formData) => Promise<{ newObject, message?: string }>` (add), `async (formData, item) => Promise<{ newObject, targetObject, message?: string }>` (edit) |
125
+ | `actionButtons` | array | No | Custom action buttons | `[{ type: "submit", label: "Save", color: "primary", variant: "contained", onClick?: (e, item) => void, disabled?: boolean }]` |
127
126
 
128
127
  #### Delete Modal (`deleteModal`)
129
128
 
130
- | Property | Type | Required | Description |
131
- |----------|------|----------|-------------|
132
- | `title` | string | No | Modal title |
133
- | `size` | string | No | Modal size: `sm`, `md` (default), `lg`, `xl`, `full` |
134
- | `confirmText` | string | No | Confirmation message text |
135
- | `referenceKey` | string | No | Property key of selectedItem to display as reference |
136
- | `action` | function | Yes | Async callback: `(selectedItem) => Promise<{ targetObject }>`. Should return the deleted row |
137
- | `actionButtons` | array | No | Action button objects: `[{ type, label, color, variant, onClick, disabled, className }]` |
129
+ | Property | Type | Required | Description | Accepted Values / Example |
130
+ | --------------- | -------- | -------- | -------------------------------- | ---------------------- |
131
+ | `title` | string | No | Modal title | `"Delete User"`, `"Confirm Delete"` |
132
+ | `icon` | ReactNode | No | Icon element displayed in modal header | `<TrashIcon />`, `<WarningIcon />` |
133
+ | `size` | string | No | Modal width | `"sm"` (default), `"md"`, `"lg"`, `"xl"`, `"full"` |
134
+ | `confirmText` | string | No | Confirmation message text | `"Are you sure you want to delete this user?"`, `"This action cannot be undone."` |
135
+ | `referenceKey` | string | No | Property key to display as confirmation reference | `"name"`, `"email"` (shows the value from selected item) |
136
+ | `action` | function | Yes | Async callback to delete item | `async (selectedItem) => Promise<{ targetObject }>` (returns the deleted row) |
137
+ | `actionButtons` | array | No | Custom action buttons | `[{ type: "submit", label: "Delete", color: "error", variant: "contained" }]` |
138
138
 
139
139
  #### View Modal (`viewModal`)
140
140
 
141
- | Property | Type | Required | Description |
142
- |----------|------|----------|-------------|
143
- | `title` | string | Yes | Modal title |
144
- | `size` | string | No | Modal size: `sm`, `md` (default), `lg`, `xl`, `full` |
145
- | `component` | React component | No | Custom component to render. Receives `data` prop with row data |
146
- | `fields` | array | No | View field objects for displaying data |
147
- | `footer` | object | No | `{ cancelButton?: boolean, cancelText?: string }` |
141
+ | Property | Type | Required | Description | Accepted Values / Example |
142
+ | ----------- | --------------- | -------- | -------------------- | -------------------- |
143
+ | `title` | string | Yes | Modal title | `"User Details"`, `"View Profile"` |
144
+ | `icon` | ReactNode | No | Icon element displayed in modal header | `<EyeIcon />`, `<InfoIcon />` |
145
+ | `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
146
+ | `component` | React component | No | Custom component to render (receives `data` prop) | `(props) => <CustomViewComponent data={props.data} />` |
147
+ | `variant` | string | No | View layout style | `"default"`, `"card"`, `"split"` |
148
+ | `fields` | array | No | View field objects (if not using custom component) | Array of field objects (see [View Field Schema](#view-field-schema)) |
149
+ | `styles` | object | No | Custom CSS classes for various view elements | `{ containerClass: "...", rowClass: "...", labelClass: "...", valueClass: "...", ... }` |
150
+ | `modalClassNames` | object | No | Custom classes for modal structure | `{ overlay: "...", container: "...", header: "...", body: "...", footer: "...", closeButton: "..." }` |
151
+ | `footer` | object | No | Footer configuration | `{ cancelButton: true, cancelText: "Close" }` |
148
152
 
149
153
  ---
150
154
 
151
155
  ### Form Field Schema
152
156
 
153
- Used by `modalConfig.*.formFields` and `filterConfig.fields`. Form fields follow the `formFieldType` shape used throughout the UI.
157
+ Used by `modalConfig.*.formFields`, `filterConfig.fields`, and `viewModal.fields`. All form fields follow the `FormField` shape.
158
+
159
+ #### Common Field Properties (All Types)
160
+
161
+ | Key | Type | Required | Description | Accepted Values / Example |
162
+ | ---------------- | -------- | --- | ----------------------- | -------------------- |
163
+ | `key` | string | Yes | Property name/identifier for form data | `"username"`, `"email"`, `"birth_date"` |
164
+ | `label` | string | No | Human-readable label for the field | `"User Name"`, `"Email Address"`, `"Date of Birth"` |
165
+ | `type` | string | Yes | Field type determining the input/renderer | `"text"`, `"number"`, `"email"`, `"password"`, `"select"`, `"checkbox"`, `"radio"`, `"switch"`, `"phone"`, `"textarea"`, `"image"`, `"video"`, `"audio"`, `"tinyEditor"`, `"group"`, `"cardGroup"` |
166
+ | `required` | boolean | No | Field must have a value for form submission | `true`, `false` (default: `false`) |
167
+ | `minLength` | number | No | Minimum character length (for text fields) | `5`, `10`, `50` |
168
+ | `placeholder` | string | No | Placeholder text shown in empty field | `"Enter your name"`, `"user@example.com"` |
169
+ | `disabled` | boolean | No | Field is disabled and read-only | `true`, `false` (default: `false`) |
170
+ | `parentClass` | string | No | Custom CSS classes for field wrapper (grid) | Tailwind classes: `"col-span-6"`, `"col-span-12"` |
171
+ | `defaultValue` | any | No | Initial value for the field | Depends on field type; e.g., `"John"`, `25`, `true`, `["option1", "option2"]` |
172
+ | `renderCondition` | function | No | Show field based on form data | `(formData: Record<string, any>) => boolean`; e.g., `(data) => data.userType === 'admin'` |
173
+ | `customValidation`| function | No | Custom validation logic | `(value: any) => boolean \| string`; return `false` for invalid, error message string, or `true` for valid |
174
+ | `pattern` | string | No | Regex pattern for validation (HTML5) | Regex: `"^[a-zA-Z0-9]*$"`, `"^[0-9]{10}$"` |
175
+ | `className` | string | No | Custom CSS class for input element | Tailwind classes: `"bg-gray-100 rounded-lg"` |
176
+
177
+ #### Type-Specific Properties
178
+
179
+ ##### `"text"` Field
180
+
181
+ | Property | Type | Description | Example |
182
+ | ---- | --- | -------------------- | --- |
183
+ | `minLength` | number | Minimum character length | `5` |
184
+ | `placeholder` | string | Placeholder text | `"Enter your name"` |
185
+ | `pattern` | string | Regex validation pattern | `"^[a-zA-Z ]*$"` (letters and spaces only) |
186
+ | `mask` | string | Input mask pattern | `"(99) 99999-9999"` (format: 9=digit, A=letter, X=alphanumeric, *=any char) |
187
+ | `maskApplyOnValue` | boolean | Apply mask to initial value | `true`, `false` (default: `true`) |
154
188
 
155
- #### Common Field Keys (All Types)
189
+ ##### `"number"` Field
156
190
 
157
- | Key | Type | Required | Description |
158
- |-----|------|----------|-------------|
159
- | `key` | string | Yes | Property name for form data |
160
- | `label` | string | No | Human-readable label |
161
- | `type` | string | Yes | Field type: `text`, `number`, `email`, `password`, `select`, `checkbox`, `radio`, `switch`, `phone`, `textarea`, `image`, `video`, `audio`, `tinyEditor`, `group`, `cardGroup` |
162
- | `required` | boolean | No | Field is required for form submission |
163
- | `minLength` | number | No | Minimum character length |
164
- | `parentClass` | string | No | Tailwind grid class (e.g., `col-span-6`) |
165
- | `placeholder` | string | No | Placeholder text |
166
- | `disabled` | boolean | No | Field is disabled |
191
+ | Property | Type | Description | Example |
192
+ | --- | --- | ------------- | --- |
193
+ | `negativeNumberAllow` | boolean | Allow negative numbers | `true`, `false` (default: `false`) |
167
194
 
168
- #### Type-Specific Keys
195
+ ##### `"email"` Field
169
196
 
170
- ##### Select Field
197
+ | Property | Type | Description | Example |
198
+ | ------ | ------- | -------------------- | --- |
199
+ | `placeholder` | string | Placeholder text | `"user@example.com"` |
171
200
 
172
- | Key | Type | Description |
173
- |-----|------|-------------|
174
- | `options` | array | `{ value: string|number|boolean, label: string }[]` |
175
- | `multiple` | boolean | Allow multiple selections |
176
- | `search` | boolean | Show search input inside dropdown |
177
- | `dropdownMaxHeight` | string | CSS height value (e.g., `300px`) |
201
+ ##### `"password"` Field
178
202
 
179
- ##### Checkbox Field
203
+ | Property | Type | Description | Example |
204
+ | --- | --- | -- | -- |
205
+ | `minLength` | number | Minimum character length | `8` |
180
206
 
181
- | Key | Type | Description |
182
- |-----|------|-------------|
183
- | `options` | array | `{ value, label }[]` |
184
- | `multiple` | boolean | Allow selecting multiple values (component prop: `multiSelect`) |
207
+ ##### `"select"` Field
185
208
 
186
- ##### Switch Field
209
+ | Property | Type | Description | Accepted Values / Example |
210
+ | ----- | --- | -------------------- | -- |
211
+ | `options` | array | Select options; `{ value, label, color? }[]` | `[{ value: "active", label: "Active", color: "green" }, { value: "inactive", label: "Inactive", color: "red" }]` |
212
+ | `multiple` | boolean | Allow selecting multiple options | `true`, `false` (default: `false`) |
213
+ | `search` | boolean | Enable searchable dropdown | `true`, `false` (default: `false`) |
214
+ | `dropdownMaxHeight` | number | Max height of dropdown in pixels | `300`, `400` |
187
215
 
188
- | Key | Type | Description |
189
- |-----|------|-------------|
190
- | `options` | array | Optional radio-like options: `[{ label, value }]` |
191
- | `text` | string | Description text shown next to switch |
216
+ ##### `"checkbox"` Field
192
217
 
193
- ##### Phone Field
218
+ | Property | Type | Description | Accepted Values / Example |
219
+ | -- | --- | -------------------- | -- |
220
+ | `options` | array | Checkbox options; `{ value, label }[]` | `[{ value: "read", label: "Can Read" }, { value: "write", label: "Can Write" }]` |
221
+ | `multiple` | boolean | Allow selecting multiple values | `true` (default: `true`); affects how data is stored |
194
222
 
195
- | Key | Type | Description |
196
- |-----|------|-------------|
197
- | `countriesList` | boolean | Show country selector |
198
- | `defaultCountry` | string | ISO country code (e.g., `US`) |
199
- | `search` | boolean | Enable searching countries |
200
- | `placeholder` | string | Placeholder text |
223
+ ##### `"radio"` Field
201
224
 
202
- ##### Textarea Field
225
+ | Property | Type | Description | Accepted Values / Example |
226
+ | -- | -- | -------------------- | -- |
227
+ | `options` | array | Radio button options; `{ value, label }[]` | `[{ value: "male", label: "Male" }, { value: "female", label: "Female" }, { value: "other", label: "Other" }]` |
203
228
 
204
- | Key | Type | Description |
205
- |-----|------|-------------|
206
- | `rows` | number | Number of visible rows |
229
+ ##### `"switch"` Field
207
230
 
208
- ##### Image Field
231
+ | Property | Type | Description | Accepted Values / Example |
232
+ | -- | -- | --------- | -- |
233
+ | `text` | string | Description/label shown next to switch | `"Enable notifications"`, `"Is admin"` |
234
+ | `options` | array | Optional radio-like options (radio fallback) | `[{ value: "yes", label: "Yes" }, { value: "no", label: "No" }]` |
209
235
 
210
- | Key | Type | Description |
211
- |-----|------|-------------|
212
- | `accept` | string | MIME type filter (default: `image/*`) |
213
- | `dragDrop` | boolean | Enable drag-and-drop upload |
236
+ ##### `"phone"` Field
214
237
 
215
- ##### TinyEditor Field
238
+ | Property | Type | Description | Accepted Values / Example |
239
+ | ---- | -- | --------- | - |
240
+ | `countriesList` | boolean | Show country selector dropdown | `true`, `false` (default: `false`) |
241
+ | `defaultCountry` | string | Default country ISO code | `"US"`, `"GB"`, `"IN"`, `"CA"`, `"AU"` |
242
+ | `search` | boolean | Enable searching countries in selector | `true`, `false` (default: `false`) |
243
+ | `placeholder` | string | Placeholder text | `"+1 (555) 000-0000"` |
216
244
 
217
- | Key | Type | Description |
218
- |-----|------|-------------|
219
- | `editorKey` | string | TinyMCE API key |
220
- | `fontFamily` | string | Default font family |
221
- | `height` | number | Editor height in pixels |
222
- | `imageUploadHandler` | function | `(blobInfo) => Promise<string>`. Returns image URL |
245
+ ##### `"textarea"` Field
223
246
 
224
- ##### Radio Field
247
+ | Property | Type | Description | Accepted Values / Example |
248
+ | - | -- | -- | - |
249
+ | `rows` | number | Number of visible rows | `3`, `5`, `10` |
225
250
 
226
- | Key | Type | Description |
227
- |-----|------|-------------|
228
- | `options` | array | `{ value, label }[]` |
251
+ ##### `"image"` Field
229
252
 
230
- ##### Video Field
253
+ | Property | Type | Description | Accepted Values / Example |
254
+ | - | -- | -- | - |
255
+ | `accept` | string | MIME type filter | `"image/*"` (default), `"image/jpeg,image/png"` |
256
+ | `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
257
+ | `cropImage` | boolean | Enable image cropping modal | `true`, `false` (default: `false`) |
258
+ | `aspectRatio` | number | Crop aspect ratio (width:height) | `1` (1:1 square), `16/9`, `4/3`, `1.5` |
231
259
 
232
- | Key | Type | Description |
233
- |-----|------|-------------|
234
- | `accept` | string | MIME type filter (default: `video/*`) |
235
- | `dragDrop` | boolean | Enable drag-and-drop upload |
236
- | `maxSize` | number | Maximum file size in bytes |
260
+ ##### `"video"` Field
237
261
 
238
- ##### Group Field
262
+ | Property | Type | Description | Accepted Values / Example |
263
+ | - | -- | -- | - |
264
+ | `accept` | string | MIME type filter | `"video/*"` (default), `"video/mp4,video/webm"` |
265
+ | `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
266
+ | `maxSize` | number | Maximum file size in bytes | `5242880` (5MB), `10485760` (10MB) |
239
267
 
240
- | Key | Type | Description |
241
- |-----|------|-------------|
242
- | `renderType` | string | Rendering type for group fields |
268
+ ##### `"audio"` Field
243
269
 
244
- ##### CardGroup Field
270
+ | Property | Type | Description | Accepted Values / Example |
271
+ | - | -- | -- | - |
272
+ | `accept` | string | MIME type filter | `"audio/*"` (default), `"audio/mpeg,audio/wav"` |
273
+ | `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
274
+ | `maxSize` | number | Maximum file size in bytes | `5242880` (5MB), `10485760` (10MB) |
245
275
 
246
- | Key | Type | Description |
247
- |-----|------|-------------|
248
- | `renderType` | string | Rendering type for card group fields |
276
+ ##### `"tinyEditor"` Field
249
277
 
250
- #### Return Values / onSubmit Handler
278
+ | Property | Type | Description | Accepted Values / Example |
279
+ | --------- | ------ | ------------ | - |
280
+ | `editorKey` | string | TinyMCE API key (required) | Get from `import.meta.env.VITE_EDITOR_KEY` or pass directly |
281
+ | `fontFamily` | string | Default font family in editor | `"Arial"`, `"Georgia"`, `"Courier New"` |
282
+ | `height` | number | Editor height in pixels | `300`, `500` |
283
+ | `imageUploadHandler` | function | Async image upload handler | `async (blobInfo) => Promise<string>` (returns image URL) |
251
284
 
252
- `onSubmit(formData)` receives an object keyed by `field.key` values.
285
+ ##### `"group"` & `"cardGroup"` Fields
286
+
287
+ | Property | Type | Description | Accepted Values / Example |
288
+ | -- | -- | -- | - |
289
+ | `renderType` | string | Rendering type for grouped fields | `"default"`, `"card"`, or custom layout type |
290
+ | (nested fields) | array | Child form fields within group | Array of FormField objects |
253
291
 
254
292
  ---
255
293
 
256
- ### Small/UI components (props summary)
257
-
258
- | Component | Key Props | Purpose |
259
- |-----------|-----------|----------|
260
- | **Button** | `variant`, `color`, `size`, `fullWidth`, `className`, `onClick`, `type`, `disabled` | Reusable button with multiple style variants (`contained`, `outlined`, `text`) and colors |
261
- | **Chip** | `label`, `variant`, `color` | Display small labeled badges with `contained`, `outline`, or `soft` styles |
262
- | **Modal** | `isOpen`, `onClose`, `icon`, `title`, `size`, `actionButtons`, `loadingBtn` | Modal dialog for forms, confirmations, and views with flexible sizing |
263
- | **FilterDrawer** | `isOpen`, `onClose`, `config`, `onApply` | Side panel for applying filters with customizable form fields |
264
-
265
- #### Button Props
266
-
267
- | Prop | Type | Default | Description |
268
- |------|------|---------|-------------|
269
- | `variant` | string | `contained` | Style variant: `contained`, `outlined`, `text` |
270
- | `color` | string | `primary` | Color: `primary`, `success`, `error`, `default` |
271
- | `size` | string | `default` | Size: `sm`, `md`, `lg`, `xl`, `default` |
272
- | `fullWidth` | boolean | `false` | Stretch button to full width |
273
- | `className` | string | — | Custom CSS class |
274
- | `onClick` | function | | Click event handler |
275
- | `type` | string | `button` | HTML button type: `button`, `submit`, `reset` |
276
- | `disabled` | boolean | `false` | Disable button interaction |
277
-
278
- #### Chip Props
279
-
280
- | Prop | Type | Default | Description |
281
- |------|------|---------|-------------|
282
- | `label` | string | Required | Badge text |
283
- | `variant` | string | `contained` | Style variant: `contained`, `outline`, `soft` |
284
- | `color` | string | `blue` | Color: `blue`, `teal`, `purple`, `yellow`, `green`, `red`, `gray` |
285
-
286
- #### Modal Props (Direct Usage)
287
-
288
- | Prop | Type | Description |
289
- |------|------|-------------|
290
- | `isOpen` | boolean | Show/hide modal |
291
- | `onClose` | function | Callback when modal closes |
292
- | `icon` | React node | Icon element displayed in modal header |
293
- | `title` | string | Modal title |
294
- | `size` | string | Size: `sm`, `md`, `lg`, `xl`, `full` |
295
- | `actionButtons` | array | `[{ type, label, color, variant, onClick, disabled }]` |
296
- | `loadingBtn` | boolean | Show loading state on action button |
297
-
298
- #### FilterDrawer Props
299
-
300
- | Prop | Type | Description |
301
- |------|------|-------------|
302
- | `isOpen` | boolean | Show/hide filter drawer |
303
- | `onClose` | function | Callback when drawer closes |
304
- | `config` | object | Fields array using `formFieldType` schema |
305
- | `onApply` | function | Callback: `(filters: object) => void` |
294
+ ### View Field Schema
295
+
296
+ Used by `viewModal.fields` to display data in view/details modals.
297
+
298
+ | Key | Type | Description | Accepted Values / Example |
299
+ | ----- | -- | - | - |
300
+ | `key` | string | Property name to display | `"username"`, `"email"`, `"birth_date"` |
301
+ | `label` | string | Display label for the field | `"User Name"`, `"Email Address"` |
302
+ | `type` | string | Display type (similar to form field types) | `"text"`, `"date"`, `"chip"`, `"image"`, `"avatar"`, `"group"`, `"cardGroup"` |
303
+ | `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` |
304
+ | `imageKey` | string | Image property for avatar types | `"profileImage"`, `"avatarUrl"` |
305
+ | `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` |
306
+ | `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` |
307
+ | `variant` | string | Chip display variant | `"contained"`, `"outline"`, `"soft"` |
308
+ | `chipOptions` | array | Map values to chip labels and colors | `[{ value: "active", label: "Active", color: "green" }]` |
309
+ | `defaultColor` | string | Default chip color | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"` |
310
+ | `className` | string | Custom CSS class for display element | Tailwind classes |
311
+ | `blockClass` | string | Custom CSS class for field block/wrapper | Tailwind classes |
312
+ | `icon` | ReactNode | Icon element to display with field | `<UserIcon />`, `<MailIcon />` |
313
+ | `renderCondition` | function | Show field based on data | `(data: Record<string, any>) => boolean` |
306
314
 
307
315
  ---
308
316
 
317
+ ### Standalone UI Components
318
+
319
+ In addition to the main CRUD component, the library exports reusable UI components that can be used independently:
320
+
321
+ #### Button Component
322
+
323
+ ```jsx
324
+ import { Button } from 'react-admin-crud-manager';
325
+
326
+ // Basic button
327
+ <Button onClick={() => console.log('Clicked')}>Click Me</Button>
328
+
329
+ // Variants
330
+ <Button variant="contained" color="primary">Primary</Button>
331
+ <Button variant="outlined" color="success">Success</Button>
332
+ <Button variant="text" color="error">Error</Button>
333
+
334
+ // Sizes
335
+ <Button size="sm">Small</Button>
336
+ <Button size="md">Medium</Button>
337
+ <Button size="lg">Large</Button>
338
+
339
+ // States
340
+ <Button disabled>Disabled</Button>
341
+ <Button fullWidth>Full Width</Button>
342
+
343
+ // Submit button
344
+ <Button type="submit">Submit Form</Button>
345
+ ```
346
+
347
+ | Prop | Type | Default | Description | Accepted Values |
348
+ | ----------- | -------- | ----------- | ----------------------------------------------- | ----------- |
349
+ | `variant` | string | `contained` | Style variant | `"contained"`, `"outlined"`, `"text"` |
350
+ | `color` | string | `primary` | Button color theme | `"primary"`, `"success"`, `"error"`, `"warning"`, `"default"` |
351
+ | `size` | string | `md` | Button size | `"sm"`, `"md"`, `"lg"`, `"xl"` |
352
+ | `fullWidth` | boolean | `false` | Stretch to full width | `true`, `false` |
353
+ | `className` | string | — | Additional CSS classes | Tailwind classes |
354
+ | `onClick` | function | — | Click handler (can be async) | `(e) => void \| Promise<any>` |
355
+ | `type` | string | `button` | HTML button type | `"button"`, `"submit"`, `"reset"` |
356
+ | `disabled` | boolean | `false` | Disable interaction | `true`, `false` |
357
+
358
+ #### Chip Component
359
+
360
+ ```jsx
361
+ import { Chip } from 'react-admin-crud-manager';
362
+
363
+ // Basic chip
364
+ <Chip label="Active" />
365
+
366
+ // Variants with colors
367
+ <Chip label="Active" color="green" variant="contained" />
368
+ <Chip label="Pending" color="yellow" variant="outline" />
369
+ <Chip label="Inactive" color="red" variant="soft" />
370
+
371
+ // All available colors
372
+ <Chip label="Blue" color="blue" />
373
+ <Chip label="Teal" color="teal" />
374
+ <Chip label="Purple" color="purple" />
375
+ <Chip label="Green" color="green" />
376
+ <Chip label="Red" color="red" />
377
+ <Chip label="Yellow" color="yellow" />
378
+ <Chip label="Gray" color="gray" />
379
+ ```
380
+
381
+ | Prop | Type | Default | Description | Accepted Values |
382
+ | --------- | ------ | ----------- | ------------------------------------- | --------- |
383
+ | `label` | string | Required | Badge text content | Any string |
384
+ | `variant` | string | `contained` | Display style | `"contained"`, `"outline"`, `"soft"` |
385
+ | `color` | string | `blue` | Color scheme | `"blue"`, `"teal"`, `"purple"`, `"green"`, `"red"`, `"yellow"`, `"gray"` |
386
+
387
+ #### Modal Component
388
+
389
+ ```jsx
390
+ import { Modal } from 'react-admin-crud-manager';
391
+ import { AlertIcon } from 'lucide-react';
392
+
393
+ // Basic modal
394
+ <Modal
395
+ isOpen={isOpen}
396
+ onClose={() => setIsOpen(false)}
397
+ title="Confirm Action"
398
+ icon={<AlertIcon />}
399
+ actionButtons={[
400
+ { type: "submit", label: "Confirm", color: "primary" },
401
+ { type: "cancel", label: "Cancel", color: "default" }
402
+ ]}
403
+ >
404
+ <p>Are you sure you want to continue?</p>
405
+ </Modal>
406
+
407
+ // Modal sizes
408
+ <Modal isOpen={true} size="sm" title="Small Modal" />
409
+ <Modal isOpen={true} size="md" title="Medium Modal" />
410
+ <Modal isOpen={true} size="lg" title="Large Modal" />
411
+ <Modal isOpen={true} size="xl" title="Extra Large Modal" />
412
+ <Modal isOpen={true} size="full" title="Full Screen Modal" />
413
+ ```
414
+
415
+ | Prop | Type | Description | Accepted Values |
416
+ | --------------- | ---------- | -------------------------------------------- | --------- --– |
417
+ | `isOpen` | boolean | Show/hide modal | `true`, `false` |
418
+ | `onClose` | function | Callback when modal closes | `() => void` |
419
+ | `icon` | ReactNode | Icon displayed in header | Any React component or JSX element |
420
+ | `title` | string | Modal title text | Any string |
421
+ | `size` | string | Modal width | `"sm"`, `"md"`, `"lg"`, `"xl"`, `"full"` |
422
+ | `actionButtons` | array | Footer action buttons | Array of `{ type, label, color, variant, onClick, disabled }` |
423
+ | `loadingBtn` | boolean | Show loading spinner on action button | `true`, `false` |
424
+ | `children` | ReactNode | Modal content | Any React components |
425
+
426
+ #### FilterDrawer Component
427
+
428
+ ```jsx
429
+ import { FilterDrawer } from 'react-admin-crud-manager';
430
+
431
+ <FilterDrawer
432
+ isOpen={isOpen}
433
+ onClose={() => setIsOpen(false)}
434
+ config={{
435
+ fields: [
436
+ {
437
+ key: "status",
438
+ label: "Status",
439
+ type: "select",
440
+ options: [
441
+ { value: "active", label: "Active" },
442
+ { value: "inactive", label: "Inactive" }
443
+ ]
444
+ },
445
+ {
446
+ key: "dateFrom",
447
+ label: "From Date",
448
+ type: "date"
449
+ }
450
+ ]
451
+ }}
452
+ onApply={(filters) => {
453
+ console.log("Applied filters:", filters);
454
+ setIsOpen(false);
455
+ }}
456
+ />
457
+ ```
458
+
459
+ | Prop | Type | Description | Accepted Values |
460
+ | --------- | -------- | ---------------- | --|
461
+ | `isOpen` | boolean | Show/hide filter drawer | `true`, `false` |
462
+ | `onClose` | function | Callback when drawer closes | `() => void` |
463
+ | `config` | object | Filter field configuration | `{ fields: [...] }` (uses Form Field Schema) |
464
+ | `onApply` | function | Callback with applied filter values | `(filters: object) => void` |
465
+
466
+ ---
467
+
468
+ ## Advanced Features
469
+
470
+ ### 1. Export CSV
471
+
472
+ Export table data as a CSV file with custom field selection:
473
+
474
+ ```js
475
+ const config = {
476
+ // ... other config
477
+ tableConfig: {
478
+ table_head: [...],
479
+ data: [...]
480
+ exportCSV: {
481
+ enabled: true,
482
+ fileName: "users_export.csv",
483
+ fields: [
484
+ { label: "ID", key: "id" },
485
+ { label: "Name", key: "name" },
486
+ { label: "Email", key: "email" },
487
+ { label: "Status", key: "status" }
488
+ ]
489
+ }
490
+ }
491
+ };
492
+ ```
493
+
494
+ ### 2. Conditional Field Rendering
495
+
496
+ Show/hide form fields based on other field values using `renderCondition`:
497
+
498
+ ```js
499
+ const formFields = [
500
+ {
501
+ key: "userType",
502
+ label: "User Type",
503
+ type: "select",
504
+ options: [
505
+ { value: "admin", label: "Administrator" },
506
+ { value: "user", label: "Regular User" }
507
+ ]
508
+ },
509
+ {
510
+ key: "adminLevel",
511
+ label: "Admin Level",
512
+ type: "select",
513
+ renderCondition: (formData) => formData.userType === "admin", // Only show if userType is admin
514
+ options: [
515
+ { value: "superadmin", label: "Super Admin" },
516
+ { value: "moderator", label: "Moderator" }
517
+ ]
518
+ },
519
+ {
520
+ key: "department",
521
+ label: "Department",
522
+ type: "text",
523
+ renderCondition: (formData) => formData.userType === "user", // Only show if userType is user
524
+ }
525
+ ];
526
+ ```
527
+
528
+ ### 3. Custom Field Validation
529
+
530
+ Add custom validation logic to form fields:
531
+
532
+ ```js
533
+ const formFields = [
534
+ {
535
+ key: "email",
536
+ label: "Email",
537
+ type: "email",
538
+ customValidation: (value) => {
539
+ // Return true/false or custom error message
540
+ if (!value.includes("@company.com")) {
541
+ return "Email must be a company email"; // Error message
542
+ }
543
+ return true; // Valid
544
+ }
545
+ },
546
+ {
547
+ key: "age",
548
+ label: "Age",
549
+ type: "number",
550
+ customValidation: (value) => {
551
+ if (value < 18) return "Must be 18 or older";
552
+ if (value > 120) return "Please enter a valid age";
553
+ return true;
554
+ }
555
+ }
556
+ ];
557
+ ```
558
+
559
+ ### 4. Custom Table Cell Rendering
560
+
561
+ Use the `render` function for complex cell display:
562
+
563
+ ```js
564
+ const tableConfig = {
565
+ table_head: [
566
+ { key: "id", title: "ID", type: "index" },
567
+ {
568
+ key: "name",
569
+ title: "Name",
570
+ render: (row, rowIndex) => (
571
+ <div className="font-bold text-blue-600">{row.name.toUpperCase()}</div>
572
+ )
573
+ },
574
+ {
575
+ key: "price",
576
+ title: "Price",
577
+ render: (row) => (
578
+ <span className="text-green-600 font-semibold">
579
+ ${row.price.toFixed(2)}
580
+ </span>
581
+ )
582
+ },
583
+ {
584
+ key: "actions",
585
+ title: "Actions",
586
+ render: (row) => (
587
+ <button onClick={() => console.log(row)}>
588
+ Custom Action
589
+ </button>
590
+ )
591
+ }
592
+ ],
593
+ data: [...]
594
+ };
595
+ ```
596
+
597
+ ### 5. View Modal Variants
598
+
599
+ Display details in different layouts with view modal variants:
600
+
601
+ ```js
602
+ // Default variant - standard grid layout
603
+ const config = {
604
+ modalConfig: {
605
+ viewModal: {
606
+ title: "User Details",
607
+ variant: "default", // Standard grid layout
608
+ fields: [
609
+ { key: "name", label: "Full Name", type: "text" },
610
+ { key: "email", label: "Email", type: "text" }
611
+ ]
612
+ }
613
+ }
614
+ };
615
+
616
+ // Card variant - each field in an elevated card
617
+ const cardConfig = {
618
+ modalConfig: {
619
+ viewModal: {
620
+ title: "User Details",
621
+ variant: "card", // Each field is a separate card
622
+ styles: {
623
+ containerClass: "grid grid-cols-12 gap-4",
624
+ cardGroupClass: "col-span-6 bg-white rounded-lg shadow p-4"
625
+ },
626
+ fields: [...]
627
+ }
628
+ }
629
+ };
630
+
631
+ // Split variant - property sheet style with dividing lines
632
+ const splitConfig = {
633
+ modalConfig: {
634
+ viewModal: {
635
+ title: "User Details",
636
+ variant: "split", // Clean property-sheet layout
637
+ fields: [...]
638
+ }
639
+ }
640
+ };
641
+ ```
642
+
643
+ ### 6. Custom View Component
644
+
645
+ Use a fully custom component for the view modal:
646
+
647
+ ```js
648
+ const CustomUserView = ({ data }) => (
649
+ <div className="space-y-4">
650
+ <div className="flex items-center gap-4">
651
+ <img
652
+ src={data.avatarUrl}
653
+ alt={data.name}
654
+ className="w-16 h-16 rounded-full"
655
+ />
656
+ <div>
657
+ <h2 className="text-xl font-bold">{data.name}</h2>
658
+ <p className="text-gray-600">{data.email}</p>
659
+ </div>
660
+ </div>
661
+ <div className="grid grid-cols-2 gap-4">
662
+ <div>
663
+ <label className="text-sm font-semibold">Phone</label>
664
+ <p>{data.phone}</p>
665
+ </div>
666
+ <div>
667
+ <label className="text-sm font-semibold">Status</label>
668
+ <p>{data.status}</p>
669
+ </div>
670
+ </div>
671
+ </div>
672
+ );
673
+
674
+ const config = {
675
+ modalConfig: {
676
+ viewModal: {
677
+ title: "User Details",
678
+ component: CustomUserView // Use custom component
679
+ }
680
+ }
681
+ };
682
+ ```
683
+
684
+ ### 7. Row Click Handler
685
+
686
+ Handle clicks on table rows:
687
+
688
+ ```js
689
+ const config = {
690
+ tableConfig: {
691
+ table_head: [...],
692
+ data: [...],
693
+ rowClick: (row, rowIndex) => {
694
+ console.log(`Row ${rowIndex} clicked:`, row);
695
+ // Open custom drawer, navigate, or perform any action
696
+ }
697
+ }
698
+ };
699
+ ```
700
+
701
+ ### 8. Server-Side Filtering
702
+
703
+ Implement server-side filtering with custom filter fields:
704
+
705
+ ```js
706
+ const config = {
707
+ tableConfig: {
708
+ filter: {
709
+ enabled: true,
710
+ useServerSideFilters: true // Enable server-side filtering
711
+ },
712
+ filterConfig: {
713
+ fields: [
714
+ {
715
+ key: "status",
716
+ label: "Status",
717
+ type: "select",
718
+ options: [
719
+ { value: "active", label: "Active" },
720
+ { value: "inactive", label: "Inactive" }
721
+ ]
722
+ },
723
+ {
724
+ key: "createdFrom",
725
+ label: "Created From",
726
+ type: "date"
727
+ },
728
+ {
729
+ key: "createdTo",
730
+ label: "Created To",
731
+ type: "date"
732
+ }
733
+ ]
734
+ },
735
+ onFilterApply: (filters) => {
736
+ console.log("Filters applied:", filters);
737
+ // Filters will be passed to fetchData as additional params
738
+ }
739
+ },
740
+ fetchData: async ({
741
+ search,
742
+ rows_per_page,
743
+ current_page,
744
+ sort_by,
745
+ sort_order,
746
+ ...filters // Additional filter params passed here
747
+ }) => {
748
+ const resp = await api.get("/users", {
749
+ params: {
750
+ q: search,
751
+ limit: rows_per_page,
752
+ page: current_page,
753
+ sort_by,
754
+ sort_order,
755
+ ...filters // Include filters in API call
756
+ }
757
+ });
758
+ return { data: resp.items, pagination: resp.pagination };
759
+ }
760
+ };
761
+ ```
762
+
763
+ ### 9. Input Masking
764
+
765
+ Format input with masks using pattern syntax:
766
+
767
+ ```js
768
+ const formFields = [
769
+ {
770
+ key: "phone",
771
+ label: "Phone Number",
772
+ type: "text",
773
+ mask: "(99) 99999-9999", // Mask pattern
774
+ maskApplyOnValue: true, // Apply mask to default value
775
+ placeholder: "(00) 00000-0000"
776
+ },
777
+ {
778
+ key: "zipCode",
779
+ label: "ZIP Code",
780
+ type: "text",
781
+ mask: "99999-999", // Pattern: 9 = digit, A = letter, X = alphanumeric, * = any
782
+ placeholder: "00000-000"
783
+ },
784
+ {
785
+ key: "creditCard",
786
+ label: "Credit Card",
787
+ type: "text",
788
+ mask: "9999 9999 9999 9999",
789
+ placeholder: "0000 0000 0000 0000"
790
+ }
791
+ ];
792
+ ```
793
+
794
+ **Mask Pattern Reference:**
795
+ - `9` — Digit (0-9)
796
+ - `A` — Letter (a-zA-Z)
797
+ - `X` — Alphanumeric (a-zA-Z0-9)
798
+ - `*` — Any character
799
+ - Any other character is literal (e.g., `-`, `/`, ` `)
800
+
801
+ ### 10. Image Cropping
802
+
803
+ Enable image cropping in image picker:
804
+
805
+ ```js
806
+ const formFields = [
807
+ {
808
+ key: "profileImage",
809
+ label: "Profile Picture",
810
+ type: "image",
811
+ cropImage: true, // Enable cropping modal
812
+ aspectRatio: 1, // 1:1 square ratio
813
+ dragDrop: true
814
+ },
815
+ {
816
+ key: "bannerImage",
817
+ label: "Banner Image",
818
+ type: "image",
819
+ cropImage: true,
820
+ aspectRatio: 16 / 9, // 16:9 widescreen ratio
821
+ dragDrop: true
822
+ }
823
+ ];
824
+ ```
825
+
826
+ ### 11. Notifications/Snackbar
827
+
828
+ Display success/error messages automatically:
829
+
830
+ Notifications are handled internally by the library using the `notistack` library. Success and error messages are shown automatically:
831
+
832
+ - **Success messages**: Shown when add/edit/delete operations complete successfully
833
+ - **Error messages**: Shown when operations fail
834
+ - **Custom messages**: Handlers can return `{ newObject, message: "Custom message" }`
835
+
836
+ ```js
837
+ const config = {
838
+ modalConfig: {
839
+ addModal: {
840
+ title: "Add User",
841
+ formFields: [...],
842
+ handleSubmit: async (formData) => {
843
+ const resp = await api.post("/users", formData);
844
+ return {
845
+ newObject: resp.user,
846
+ message: "User created successfully!" // Custom success message
847
+ };
848
+ }
849
+ }
850
+ }
851
+ };
852
+ ```
853
+
854
+ ### 12. Sortable Columns
855
+
856
+ Configure sorting with custom options and auto-generation:
857
+
858
+ ```js
859
+ const config = {
860
+ tableConfig: {
861
+ sort: {
862
+ enabled: true,
863
+ useServerSideSorting: false, // Client-side sorting
864
+ autoGenerate: true, // Auto-generate sort options from table headers
865
+ fields: ["name", "email", "createdAt"], // Fields to make sortable
866
+ defaultValue: "name", // Default sort field
867
+ clearLabel: "Clear Sort",
868
+ onChange: (payload) => {
869
+ console.log("Sort changed:", payload);
870
+ // payload contains: { value, option, key, order, type }
871
+ }
872
+ },
873
+ table_head: [
874
+ { key: "name", title: "Name" },
875
+ { key: "email", title: "Email" },
876
+ { key: "createdAt", title: "Created Date" }
877
+ ]
878
+ }
879
+ };
880
+ ```
881
+
309
882
  ### Examples
310
- Minimal CRUD config (client-side):
883
+
884
+ #### Example 1: Minimal Client-Side CRUD
885
+
886
+ ```js
887
+ import Crud from 'react-admin-crud-manager';
888
+
889
+ const users = [
890
+ { id: 1, name: "John Doe", email: "john@example.com", status: "active" },
891
+ { id: 2, name: "Jane Smith", email: "jane@example.com", status: "inactive" }
892
+ ];
893
+
894
+ function App() {
895
+ const config = {
896
+ title: "Users",
897
+ fetchData: async () => ({ data: users, pagination: null }),
898
+ isStaticData: true,
899
+ tableConfig: {
900
+ table_head: [
901
+ { key: "id", title: "ID", type: "index" },
902
+ { key: "name", title: "Name" },
903
+ { key: "email", title: "Email" },
904
+ { key: "status", title: "Status", type: "chip", chipOptions: [
905
+ { value: "active", label: "Active", color: "green" },
906
+ { value: "inactive", label: "Inactive", color: "red" }
907
+ ]}
908
+ ],
909
+ search: { enabled: true },
910
+ pagination: { enabled: true, rows_per_page: 10 }
911
+ },
912
+ modalConfig: {
913
+ addModal: {
914
+ title: "Add User",
915
+ formFields: [
916
+ { key: "name", label: "Name", type: "text", required: true },
917
+ { key: "email", label: "Email", type: "email", required: true },
918
+ {
919
+ key: "status",
920
+ label: "Status",
921
+ type: "select",
922
+ options: [
923
+ { value: "active", label: "Active" },
924
+ { value: "inactive", label: "Inactive" }
925
+ ]
926
+ }
927
+ ],
928
+ handleSubmit: async (formData) => ({
929
+ newObject: { ...formData, id: Math.max(...users.map(u => u.id)) + 1 }
930
+ })
931
+ }
932
+ }
933
+ };
934
+
935
+ return <Crud config={config} />;
936
+ }
937
+
938
+ export default App;
939
+ ```
940
+
941
+ #### Example 2: Server-Side CRUD with Advanced Features
942
+
943
+ ```js
944
+ import Crud from 'react-admin-crud-manager';
945
+ import axios from 'axios';
946
+ import { PlusIcon, EditIcon, TrashIcon } from 'lucide-react';
947
+
948
+ function App() {
949
+ const api = axios.create({ baseURL: 'https://api.example.com' });
950
+
951
+ const config = {
952
+ title: "Products",
953
+ description: "Manage your products inventory",
954
+ buttonText: "Add Product",
955
+ fetchData: async ({
956
+ search,
957
+ rows_per_page,
958
+ current_page,
959
+ sort_by,
960
+ sort_order,
961
+ category,
962
+ minPrice,
963
+ maxPrice
964
+ }) => {
965
+ const resp = await api.get("/products", {
966
+ params: {
967
+ q: search,
968
+ limit: rows_per_page,
969
+ page: current_page,
970
+ sort_by,
971
+ sort_order,
972
+ category,
973
+ minPrice,
974
+ maxPrice
975
+ }
976
+ });
977
+ return {
978
+ data: resp.data.items,
979
+ pagination: {
980
+ current_page: resp.data.page,
981
+ rows_per_page: resp.data.limit,
982
+ total_pages: resp.data.totalPages,
983
+ total_records: resp.data.total
984
+ }
985
+ };
986
+ },
987
+ tableConfig: {
988
+ table_head: [
989
+ { key: "id", title: "ID", type: "index" },
990
+ { key: "image", title: "Image", type: "avatar", imageKey: "image", titleKey: "name" },
991
+ { key: "name", title: "Product Name" },
992
+ { key: "price", title: "Price", render: (row) => `$${row.price.toFixed(2)}` },
993
+ {
994
+ key: "category",
995
+ title: "Category",
996
+ type: "chip",
997
+ variant: "soft",
998
+ chipOptions: [
999
+ { value: "electronics", label: "Electronics", color: "blue" },
1000
+ { value: "clothing", label: "Clothing", color: "purple" }
1001
+ ]
1002
+ }
1003
+ ],
1004
+ search: { enabled: true, useServerSideSearch: true, searchKeys: ["name", "sku"] },
1005
+ filter: { enabled: true, useServerSideFilters: true },
1006
+ pagination: { enabled: true, useServerSidePagination: true },
1007
+ sort: { enabled: true, useServerSideSorting: true, autoGenerate: true },
1008
+ exportCSV: {
1009
+ enabled: true,
1010
+ fileName: "products.csv",
1011
+ fields: [
1012
+ { label: "ID", key: "id" },
1013
+ { label: "Name", key: "name" },
1014
+ { label: "Price", key: "price" }
1015
+ ]
1016
+ },
1017
+ filterConfig: {
1018
+ fields: [
1019
+ {
1020
+ key: "category",
1021
+ label: "Category",
1022
+ type: "select",
1023
+ options: [
1024
+ { value: "electronics", label: "Electronics" },
1025
+ { value: "clothing", label: "Clothing" }
1026
+ ]
1027
+ },
1028
+ { key: "minPrice", label: "Min Price", type: "number" },
1029
+ { key: "maxPrice", label: "Max Price", type: "number" }
1030
+ ]
1031
+ }
1032
+ },
1033
+ modalConfig: {
1034
+ addModal: {
1035
+ title: "Add Product",
1036
+ size: "lg",
1037
+ icon: <PlusIcon />,
1038
+ formFields: [
1039
+ { key: "name", label: "Product Name", type: "text", required: true, parentClass: "col-span-12" },
1040
+ { key: "price", label: "Price", type: "number", required: true, parentClass: "col-span-6" },
1041
+ {
1042
+ key: "category",
1043
+ label: "Category",
1044
+ type: "select",
1045
+ required: true,
1046
+ parentClass: "col-span-6",
1047
+ options: [
1048
+ { value: "electronics", label: "Electronics" },
1049
+ { value: "clothing", label: "Clothing" }
1050
+ ]
1051
+ },
1052
+ { key: "stock", label: "Stock", type: "number", required: true, parentClass: "col-span-6" },
1053
+ { key: "description", label: "Description", type: "textarea", rows: 4, parentClass: "col-span-12" },
1054
+ {
1055
+ key: "image",
1056
+ label: "Product Image",
1057
+ type: "image",
1058
+ cropImage: true,
1059
+ aspectRatio: 1,
1060
+ dragDrop: true,
1061
+ parentClass: "col-span-12"
1062
+ }
1063
+ ],
1064
+ handleSubmit: async (formData) => {
1065
+ const resp = await api.post("/products", formData);
1066
+ return { newObject: resp.data, message: "Product added successfully!" };
1067
+ }
1068
+ },
1069
+ editModal: {
1070
+ title: "Edit Product",
1071
+ size: "lg",
1072
+ icon: <EditIcon />,
1073
+ formFields: [
1074
+ { key: "name", label: "Product Name", type: "text", required: true, parentClass: "col-span-12" },
1075
+ { key: "price", label: "Price", type: "number", required: true, parentClass: "col-span-6" },
1076
+ { key: "stock", label: "Stock", type: "number", required: true, parentClass: "col-span-6" }
1077
+ ],
1078
+ handleSubmit: async (formData, item) => {
1079
+ const resp = await api.put(`/products/${item.id}`, formData);
1080
+ return { newObject: resp.data, targetObject: item };
1081
+ }
1082
+ },
1083
+ deleteModal: {
1084
+ title: "Delete Product",
1085
+ icon: <TrashIcon />,
1086
+ confirmText: "Are you sure you want to delete this product?",
1087
+ referenceKey: "name",
1088
+ action: async (item) => {
1089
+ await api.delete(`/products/${item.id}`);
1090
+ return { targetObject: item };
1091
+ }
1092
+ },
1093
+ viewModal: {
1094
+ title: "Product Details",
1095
+ variant: "card",
1096
+ fields: [
1097
+ { key: "id", label: "ID" },
1098
+ { key: "name", label: "Product Name" },
1099
+ { key: "price", label: "Price" },
1100
+ { key: "stock", label: "Stock" },
1101
+ { key: "category", label: "Category", type: "chip" }
1102
+ ]
1103
+ }
1104
+ }
1105
+ };
1106
+
1107
+ return <Crud config={config} />;
1108
+ }
1109
+
1110
+ export default App;
1111
+ ```
1112
+
1113
+ #### Example 3: Complex Form with Conditional Fields and Validation
1114
+
1115
+ ```js
1116
+ const config = {
1117
+ title: "Advanced User Registration",
1118
+ tableConfig: {
1119
+ table_head: [
1120
+ { key: "id", title: "ID", type: "index" },
1121
+ { key: "name", title: "Full Name" },
1122
+ { key: "email", title: "Email" },
1123
+ { key: "userType", title: "Type", type: "chip" }
1124
+ ]
1125
+ },
1126
+ modalConfig: {
1127
+ addModal: {
1128
+ title: "Register New User",
1129
+ formFields: [
1130
+ {
1131
+ key: "name",
1132
+ label: "Full Name",
1133
+ type: "text",
1134
+ required: true,
1135
+ parentClass: "col-span-12",
1136
+ customValidation: (value) => {
1137
+ if (value.trim().split(" ").length < 2) {
1138
+ return "Please enter your full name";
1139
+ }
1140
+ return true;
1141
+ }
1142
+ },
1143
+ {
1144
+ key: "email",
1145
+ label: "Email Address",
1146
+ type: "email",
1147
+ required: true,
1148
+ parentClass: "col-span-12",
1149
+ customValidation: (value) => {
1150
+ if (!value.includes("@company.com")) {
1151
+ return "Must use company email";
1152
+ }
1153
+ return true;
1154
+ }
1155
+ },
1156
+ {
1157
+ key: "phone",
1158
+ label: "Phone",
1159
+ type: "phone",
1160
+ countriesList: true,
1161
+ mask: "(99) 99999-9999",
1162
+ parentClass: "col-span-12"
1163
+ },
1164
+ {
1165
+ key: "userType",
1166
+ label: "User Type",
1167
+ type: "select",
1168
+ required: true,
1169
+ parentClass: "col-span-6",
1170
+ options: [
1171
+ { value: "admin", label: "Administrator" },
1172
+ { value: "user", label: "Regular User" }
1173
+ ]
1174
+ },
1175
+ {
1176
+ key: "adminLevel",
1177
+ label: "Admin Level",
1178
+ type: "select",
1179
+ parentClass: "col-span-6",
1180
+ renderCondition: (data) => data.userType === "admin",
1181
+ options: [
1182
+ { value: "superadmin", label: "Super Admin" },
1183
+ { value: "moderator", label: "Moderator" }
1184
+ ]
1185
+ },
1186
+ {
1187
+ key: "permissions",
1188
+ label: "Permissions",
1189
+ type: "checkbox",
1190
+ parentClass: "col-span-12",
1191
+ multiple: true,
1192
+ renderCondition: (data) => data.userType === "admin",
1193
+ options: [
1194
+ { value: "read", label: "Can Read" },
1195
+ { value: "write", label: "Can Write" },
1196
+ { value: "delete", label: "Can Delete" }
1197
+ ]
1198
+ }
1199
+ ],
1200
+ handleSubmit: async (formData) => {
1201
+ const resp = await api.post("/users", formData);
1202
+ return {
1203
+ newObject: resp.data,
1204
+ message: `User ${formData.name} created successfully!`
1205
+ };
1206
+ }
1207
+ }
1208
+ }
1209
+ };
1210
+ ```
1211
+
1212
+ ---
1213
+
1214
+ ## Best Practices & Common Patterns
1215
+
1216
+ ### 1. State Management with Static Data
1217
+
1218
+ For client-side CRUD operations, use `isStaticData: true` and manage data updates through modal handlers:
311
1219
 
312
1220
  ```js
1221
+ const [users, setUsers] = useState(initialUsers);
1222
+
313
1223
  const config = {
314
- title: 'Users',
315
1224
  fetchData: async () => ({ data: users, pagination: null }),
316
1225
  isStaticData: true,
317
- tableConfig: {
318
- table_head: [ { key: 'id', title: 'ID', type: 'index' }, { key: 'name', title: 'Name' } ],
319
- pagination: { enabled: true }
1226
+ modalConfig: {
1227
+ addModal: {
1228
+ handleSubmit: async (formData) => {
1229
+ const newUser = { ...formData, id: Date.now() };
1230
+ setUsers([...users, newUser]);
1231
+ return { newObject: newUser };
1232
+ }
1233
+ },
1234
+ editModal: {
1235
+ handleSubmit: async (formData, item) => {
1236
+ const updated = { ...item, ...formData };
1237
+ setUsers(users.map(u => u.id === item.id ? updated : u));
1238
+ return { newObject: updated, targetObject: item };
1239
+ }
1240
+ }
1241
+ }
1242
+ };
1243
+ ```
1244
+
1245
+ ### 2. Server-Side Operations Best Practices
1246
+
1247
+ ```js
1248
+ // Always provide pagination info back to the component
1249
+ const fetchData = async (params) => {
1250
+ const resp = await api.get("/items", { params });
1251
+ return {
1252
+ data: resp.data.items || [],
1253
+ pagination: {
1254
+ current_page: resp.data.meta.currentPage,
1255
+ rows_per_page: resp.data.meta.perPage,
1256
+ total_pages: resp.data.meta.totalPages,
1257
+ total_records: resp.data.meta.total
1258
+ }
1259
+ };
1260
+ };
1261
+ ```
1262
+
1263
+ ### 3. Error Handling in Modal Submissions
1264
+
1265
+ ```js
1266
+ const handleSubmit = async (formData) => {
1267
+ try {
1268
+ const resp = await api.post("/items", formData);
1269
+ return {
1270
+ newObject: resp.data,
1271
+ message: "Item created successfully!" // Optional success message
1272
+ };
1273
+ } catch (error) {
1274
+ // Throw error to trigger snackbar error notification
1275
+ throw new Error(error.response?.data?.message || "Failed to create item");
1276
+ }
1277
+ };
1278
+ ```
1279
+
1280
+ ### 4. Dynamic Form Fields Based on Data
1281
+
1282
+ Combine `renderCondition` and `defaultValue` for dynamic forms:
1283
+
1284
+ ```js
1285
+ const formFields = [
1286
+ {
1287
+ key: "type",
1288
+ type: "select",
1289
+ options: [
1290
+ { value: "personal", label: "Personal" },
1291
+ { value: "business", label: "Business" }
1292
+ ]
1293
+ },
1294
+ {
1295
+ key: "companyName",
1296
+ label: "Company Name",
1297
+ type: "text",
1298
+ renderCondition: (data) => data.type === "business",
1299
+ required: (data) => data.type === "business"
1300
+ },
1301
+ {
1302
+ key: "phone",
1303
+ type: "phone",
1304
+ renderCondition: (data) => data.type === "personal"
1305
+ }
1306
+ ];
1307
+ ```
1308
+
1309
+ ### 5. Custom Table Rendering for Complex Data
1310
+
1311
+ ```js
1312
+ const tableConfig = {
1313
+ table_head: [
1314
+ {
1315
+ key: "user",
1316
+ title: "User",
1317
+ render: (row) => (
1318
+ <div className="flex items-center gap-3">
1319
+ <img src={row.avatar} className="w-8 h-8 rounded-full" />
1320
+ <div>
1321
+ <p className="font-semibold">{row.name}</p>
1322
+ <p className="text-xs text-gray-500">{row.email}</p>
1323
+ </div>
1324
+ </div>
1325
+ )
1326
+ },
1327
+ {
1328
+ key: "metrics",
1329
+ title: "Performance",
1330
+ render: (row) => (
1331
+ <div className="flex gap-2">
1332
+ <span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">
1333
+ {row.completed}%
1334
+ </span>
1335
+ </div>
1336
+ )
1337
+ }
1338
+ ]
1339
+ };
1340
+ ```
1341
+
1342
+ ### 6. Validation Patterns
1343
+
1344
+ ```js
1345
+ const formFields = [
1346
+ {
1347
+ key: "email",
1348
+ type: "email",
1349
+ customValidation: async (value) => {
1350
+ // Async validation example
1351
+ const exists = await checkEmailExists(value);
1352
+ if (exists) return "Email already in use";
1353
+ return true;
1354
+ }
320
1355
  },
1356
+ {
1357
+ key: "password",
1358
+ type: "password",
1359
+ customValidation: (value) => {
1360
+ if (value.length < 8) return "Password must be at least 8 characters";
1361
+ if (!/[A-Z]/.test(value)) return "Must contain uppercase letter";
1362
+ if (!/[0-9]/.test(value)) return "Must contain a number";
1363
+ return true;
1364
+ }
1365
+ }
1366
+ ];
1367
+ ```
1368
+
1369
+ ### 7. Handling File Uploads
1370
+
1371
+ ```js
1372
+ const handleImageUpload = async (file) => {
1373
+ const formData = new FormData();
1374
+ formData.append("file", file);
1375
+ const resp = await api.post("/upload", formData);
1376
+ return resp.data.url;
1377
+ };
1378
+
1379
+ const config = {
321
1380
  modalConfig: {
322
- addModal: { title: 'Add user', handleSubmit: async (formData)=>({ newObject: formData }), formFields: [ { key: 'name', label: 'Name', type: 'text', required: true } ] }
1381
+ addModal: {
1382
+ formFields: [
1383
+ {
1384
+ key: "image",
1385
+ type: "image",
1386
+ dragDrop: true,
1387
+ cropImage: true,
1388
+ aspectRatio: 1
1389
+ }
1390
+ ],
1391
+ handleSubmit: async (formData) => {
1392
+ // Image data includes base64 or file data
1393
+ const imageUrl = await handleImageUpload(formData.image);
1394
+ const resp = await api.post("/items", {
1395
+ ...formData,
1396
+ image: imageUrl
1397
+ });
1398
+ return { newObject: resp.data };
1399
+ }
1400
+ }
323
1401
  }
324
1402
  };
325
1403
  ```
326
1404
 
327
- Server-side listing (fetchData must return { data, pagination }):
1405
+ ### 8. Conditional Rendering with Row Data
328
1406
 
329
1407
  ```js
330
- const fetchData = async ({ search, rows_per_page, current_page, sort_by, sort_order }) => {
331
- const resp = await api.get('/users', { params: { q: search, limit: rows_per_page, page: current_page, sort_by, sort_order } });
332
- return { data: resp.items, pagination: { current_page: resp.page, rows_per_page: resp.limit, total_pages: resp.totalPages, total_records: resp.total } };
1408
+ const viewModal = {
1409
+ fields: [
1410
+ {
1411
+ key: "status",
1412
+ label: "Status",
1413
+ type: "chip",
1414
+ renderCondition: (data) => data.status !== null,
1415
+ chipOptions: [
1416
+ { value: "active", label: "Active", color: "green" },
1417
+ { value: "suspended", label: "Suspended", color: "red" }
1418
+ ]
1419
+ },
1420
+ {
1421
+ key: "suspendReason",
1422
+ label: "Suspension Reason",
1423
+ renderCondition: (data) => data.status === "suspended" // Only show if suspended
1424
+ }
1425
+ ]
1426
+ };
1427
+ ```
1428
+
1429
+ ### 9. Multiple Sort Options (Server-Side)
1430
+
1431
+ ```js
1432
+ const tableConfig = {
1433
+ sort: {
1434
+ enabled: true,
1435
+ useServerSideSorting: true,
1436
+ options: [
1437
+ { value: "recent", label: "Most Recent", key: "createdAt", order: "desc" },
1438
+ { value: "oldest", label: "Oldest", key: "createdAt", order: "asc" },
1439
+ { value: "name-asc", label: "Name (A-Z)", key: "name", order: "asc" },
1440
+ { value: "name-desc", label: "Name (Z-A)", key: "name", order: "desc" },
1441
+ { value: "popular", label: "Most Popular", key: "views", order: "desc" }
1442
+ ],
1443
+ onChange: (payload) => {
1444
+ console.log(`Sorted by: ${payload.option?.label}`);
1445
+ }
1446
+ }
333
1447
  };
334
1448
  ```
335
1449
 
336
1450
  ---
337
1451
 
338
1452
  ## License
1453
+
339
1454
  MIT