react-admin-crud-manager 1.2.14 → 1.2.16

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 (2) hide show
  1. package/README.md +730 -1389
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,378 +1,577 @@
1
- # React Admin CRUD Manager
1
+ # react-admin-crud-manager
2
2
 
3
- A reusable React CRUD admin template with modular components for rapid admin dashboard development.
3
+ A plug-and-play React CRUD admin component with full support for tables, modals, forms, filters, sorting, pagination, and more — all driven by a single `config` object.
4
4
 
5
- ## Features
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Quick Start](#quick-start)
11
+ - [Config Reference](#config-reference)
12
+ - [Top-Level Config](#top-level-config)
13
+ - [tableConfig](#tableconfig)
14
+ - [Table Column Object](#table-column-object-table_head)
15
+ - [Sorting](#sorting-sort)
16
+ - [modalConfig](#modalconfig)
17
+ - [Add & Edit Modal](#add--edit-modal)
18
+ - [Delete Modal](#delete-modal)
19
+ - [View Modal](#view-modal)
20
+ - [Form Field Schema](#form-field-schema)
21
+ - [View Field Schema](#view-field-schema)
22
+ - [Features & Examples](#features--examples)
23
+ - [Client-Side CRUD](#1-client-side-crud-minimal-setup)
24
+ - [Server-Side CRUD](#2-server-side-crud-with-advanced-features)
25
+ - [Conditional Fields & Validation](#3-conditional-fields--custom-validation)
26
+ - [Export CSV](#4-export-csv)
27
+ - [Server-Side Filtering](#5-server-side-filtering)
28
+ - [Image Cropping](#6-image-cropping)
29
+ - [Input Masking](#7-input-masking)
30
+ - [Custom Table Cell Rendering](#8-custom-table-cell-rendering)
31
+ - [Custom Toolbar Buttons](#9-custom-toolbar-buttons)
32
+ - [Row Click Handler](#10-row-click-handler)
33
+ - [View Modal Variants](#11-view-modal-variants)
34
+ - [Custom View Component](#12-custom-view-component)
35
+ - [CSS Customization](#css-customization)
36
+ - [Primary Color Override](#primary-color-override)
37
+ - [CSS Class Reference](#css-class-reference)
38
+ - [License](#license)
6
39
 
7
- - Plug-and-play CRUD page component
8
- - Modular, customizable UI components (Table, Modal, Form, etc.)
9
- - Built with React 18+ and TypeScript
10
- - Tailwind CSS for styling
11
- - Server-side and client-side data handling
12
- - Sorting, filtering, searching, and pagination support
13
- - Rich form fields including text, select, image upload, file upload, rich text editor, and more
40
+ ---
14
41
 
15
42
  ## Installation
16
43
 
17
- ```sh
44
+ ```bash
18
45
  npm install react-admin-crud-manager
19
46
  ```
20
47
 
21
- ## Usage
48
+ ---
22
49
 
23
- ### 1. Use the component
50
+ ## Quick Start
24
51
 
25
- ```js
52
+ ```jsx
26
53
  import Crud from "react-admin-crud-manager";
27
54
 
28
55
  function App() {
29
56
  const config = {
30
57
  title: "Users",
31
- fetchData: async () => {
32
- /* fetch logic */
58
+ fetchData: async () => ({ data: [] }),
59
+ tableConfig: {
60
+ table_head: [
61
+ { key: "id", title: "ID", type: "index" },
62
+ { key: "name", title: "Name" },
63
+ { key: "email", title: "Email" },
64
+ ],
33
65
  },
34
- // ...other config options
35
66
  };
67
+
36
68
  return <Crud config={config} />;
37
69
  }
38
70
  ```
39
71
 
40
- ## Components
41
-
42
- - `Crud`: Main CRUD page component
43
-
44
- ## Props
45
-
46
- 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.
47
-
48
72
  ---
49
73
 
50
- ### Crud (default export)
74
+ ## Config Reference
51
75
 
52
- `<Crud config={config} />`
76
+ ### Top-Level Config
53
77
 
54
- - `config` (object) required. Top-level configuration object used by the CRUD page.
78
+ | Property | Type | Required | Description |
79
+ | ----------------- | -------- | :------: | ----------------------------------------------------------------------------- |
80
+ | `title` | string | ✅ | Page title |
81
+ | `description` | string | — | Optional subtitle / description |
82
+ | `buttonText` | string | — | Label for the Add button (default: `"Add"`) |
83
+ | `fetchData` | function | ✅ | Async function to load data — see signature below |
84
+ | `fetchRowDetails` | function | — | Async function to load extra details for a row (used by view modal) |
85
+ | `isStaticData` | boolean | — | `false` (default). Set `true` for client-side-only add / edit / delete |
86
+ | `tableConfig` | object | ✅ | Table configuration — [see tableConfig](#tableconfig) |
87
+ | `modalConfig` | object | — | Modal definitions — [see modalConfig](#modalconfig) |
88
+ | `filterConfig` | object | — | Filter drawer field definitions — [see Form Field Schema](#form-field-schema) |
55
89
 
56
- #### Key properties of `config`
90
+ **`fetchData` signature:**
57
91
 
58
- | Property | Type | Required | Description |
59
- | ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
60
- | `title` | string | Yes | Title of the CRUD page |
61
- | `description` | string | No | Optional description text |
62
- | `buttonText` | string | No | Custom text for add button |
63
- | `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. |
64
- | `fetchRowDetails` | function | No | Async function to fetch additional details for a row. Signature: `async (item) => Promise<any>`. Used for view modal or details. |
65
- | `isStaticData` | boolean | No | Default: `false`. If true, add/edit/delete apply client-side instead of re-fetching |
66
- | `tableConfig` | object | Yes | Table configuration — [see tableConfig](#tableconfig-configuration) |
67
- | `modalConfig` | object | No | Modal definitions — [see modalConfig](#modalconfig-definitions) |
68
- | `filterConfig` | object | No | Filter drawer configuration — [see Form Field Schema](#form-field-schema) |
69
-
70
- ---
71
-
72
- ### tableConfig Configuration
73
-
74
- #### Table Configuration Keys
75
-
76
- | Key | Type | Description | Accepted Values / Example |
77
- | ------------ | ----------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------- |
78
- | `table_head` | array of column objects | Column definitions | Array of table column objects (see [Table column object](#table-column-object-table_head)) |
79
- | `search` | object | Search functionality config | `{ enabled: true, useServerSideSearch?: false, searchKeys?: ["name", "email"] }` |
80
- | `filter` | object | Filter drawer config | `{ enabled: true, useServerSideFilters?: false }` |
81
- | `pagination` | object | Pagination controls | `{ enabled: true, useServerSidePagination?: false }` |
82
- | `sort` | object | Sorting configuration | Enables and controls sorting behavior for the data table. (see [Table Sorting Config](#table-sorting-config)) |
83
- | `exportCSV` | object | Export data as CSV | `{ enabled: true, fileName: "users.csv", fields: [{ label: "Name", key: "name" }, ...] }` |
84
- | `rowClick` | function or boolean | Callback on table row click | `(row: object, rowIndex: number) => void` or `true` (setting true will open details) |
85
-
86
- `customButtons` can also be passed in `tableConfig` to render extra header toolbar buttons with custom click handlers. See [Custom Toolbar Buttons (Header)](#12-custom-toolbar-buttons-header).
87
-
88
- #### Table Column Object (`table_head[]`)
89
-
90
- | Key | Type | Description | Accepted Values / Example |
91
- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
92
- | `key` | string | Property name in row objects | `"id"`, `"name"`, `"email"` (must exist in data objects) |
93
- | `title` | string | Column header text | `"User ID"`, `"Full Name"`, `"Email Address"` |
94
- | `type` | string | Column renderer type | `"plain"` (default), `"index"` (row number), `"group"` (avatar+text), `"chip"` (badge), `"date"`, `"avatar"`, `"menu_actions"` |
95
- | `imageKey` | string | Image property for avatar/group types | `"profileImage"`, `"avatarUrl"` (path to image in data object) |
96
- | `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` (property key in data object) |
97
- | `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` (property key in data object) |
98
- | `onClickDetails` | boolean | Clicking cell opens view details modal | `true`, `false` (default: `false`) |
99
- | `variant` | string | Chip styling variant | `"contained"`, `"outline"`, `"soft"` (used with `type: "chip"`) |
100
- | `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" }]` |
101
- | `defaultColor` | string | Default color for chips (if no match in chipOptions) | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"`, etc. |
102
- | `className` | string | Custom CSS class for cell content | Tailwind classes: `"font-bold text-sm text-gray-600"` |
103
- | `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` (uses date-fns patterns) |
104
- | `menuList` | array | Action menu items; array of `{ title: string, type: string, variant?: string, icon?: ReactNode, onClick?: Function }` | `[{ title: "Edit", type: "edit", icon: <EditIcon /> }, { title: "Open", type: "custom", onClick: (event, row) => setShowModal(true) }]` |
105
- | `render` | function | Custom cell renderer (overrides built-in logic) | `(row: object, rowIndex: number) => ReactNode`; e.g., `(row) => <span>{row.name.toUpperCase()}</span>` |
92
+ ```js
93
+ async ({
94
+ search,
95
+ rows_per_page,
96
+ current_page,
97
+ sort_by,
98
+ sort_order,
99
+ ...filters // any extra keys from filterConfig
100
+ }) => ({
101
+ data: [], // array of row objects
102
+ pagination: {
103
+ // omit for client-side pagination
104
+ current_page,
105
+ rows_per_page,
106
+ total_pages,
107
+ total_records,
108
+ },
109
+ });
110
+ ```
106
111
 
107
112
  ---
108
113
 
109
- ### Table Sorting Config
114
+ ### tableConfig
110
115
 
111
- The `sort` property enables and controls sorting behavior for the data table. It supports both client-side and server-side sorting, along with customizable sorting options.
112
-
113
- | Property | Type | Required | Description |
114
- | ---------------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------- |
115
- | `enabled` | boolean | No | Enables or disables sorting functionality. Default is `true`. |
116
- | `useServerSideSorting` | boolean | No | If set to `true`, sorting will be handled on the server instead of the client. |
117
- | `fields` | string[] | No | List of field keys that are sortable. Optional if `options` are provided. |
118
- | `autoGenerate` | boolean | No | Auto-generate sort options from table headers. |
119
- | `defaultValue` | string | No | Sets the default selected sorting option on initial load (e.g., `"createdAt_desc"`). |
120
- | `clearLabel` | string | No | Label displayed for the "clear sorting" option. |
121
- | `onChange` | function | No | Callback triggered when sorting changes. Receives an object containing `value`, `key`, `order`, and `type`. |
122
- | `options` | array | No | Custom sorting options. If not provided, options will be auto-generated from sortable table columns. |
116
+ | Key | Type | Description |
117
+ | ----------------- | ------------------ | -------------------------------------------------------------------------------------------- |
118
+ | `table_head` | array | Column definitions — [see Table Column Object](#table-column-object-table_head) |
119
+ | `search` | object | `{ enabled, useServerSideSearch?, searchKeys? }` |
120
+ | `filter` | object | `{ enabled, useServerSideFilters? }` |
121
+ | `pagination` | object | `{ enabled, useServerSidePagination? }` |
122
+ | `sort` | object | Sorting config [see Sorting](#sorting-sort) |
123
+ | `exportCSV` | object | `{ enabled, fileName, fields: [{ label, key }] }` |
124
+ | `rowClick` | function \| `true` | Called on row click `(row, rowIndex) => void`, or `true` to open the view details modal |
125
+ | `customButtons` | array | Extra buttons in the table toolbar — [see Custom Toolbar Buttons](#9-custom-toolbar-buttons) |
126
+ | `customMenuItems` | array | Items in the 3-dot overflow menu in the toolbar |
123
127
 
124
128
  ---
125
129
 
126
- ### modalConfig Definitions
127
-
128
- #### Add & Edit Modal (`addModal`, `editModal`)
129
-
130
- | Property | Type | Required | Description | Accepted Values / Example |
131
- | --------------- | --------- | -------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
132
- | `title` | string | Yes | Modal title | `"Add New User"`, `"Edit User Profile"` |
133
- | `icon` | ReactNode | No | Icon element displayed in modal header | `<PlusIcon />`, `<EditIcon />` |
134
- | `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
135
- | `formClass` | string | No | Custom CSS class for form wrapper | Tailwind classes: `"grid grid-cols-12 gap-4"` |
136
- | `formFields` | array | Yes | Form field objects | Array of form field objects (see [Form Field Schema](#form-field-schema)) |
137
- | `handleSubmit` | function | Yes | Async callback on form submission | `async (formData) => Promise<{ newObject, message?: string }>` (add), `async (formData, item) => Promise<{ newObject, targetObject, message?: string }>` (edit) |
138
- | `actionButtons` | array | No | Custom action buttons | `[{ type: "submit", label: "Save", color: "primary", variant: "contained", onClick?: (e, item) => void, disabled?: boolean }]` |
139
-
140
- #### Delete Modal (`deleteModal`)
141
-
142
- | Property | Type | Required | Description | Accepted Values / Example |
143
- | --------------- | --------- | -------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
144
- | `title` | string | No | Modal title | `"Delete User"`, `"Confirm Delete"` |
145
- | `icon` | ReactNode | No | Icon element displayed in modal header | `<TrashIcon />`, `<WarningIcon />` |
146
- | `size` | string | No | Modal width | `"sm"` (default), `"md"`, `"lg"`, `"xl"`, `"full"` |
147
- | `confirmText` | string | No | Confirmation message text | `"Are you sure you want to delete this user?"`, `"This action cannot be undone."` |
148
- | `referenceKey` | string | No | Property key to display as confirmation reference | `"name"`, `"email"` (shows the value from selected item) |
149
- | `actionButtons` | array | No | Custom action buttons | `[{ type: "button", label: "Delete", color: "error", variant: "contained", onClick: async (event , selectedItem) => Promise<{ targetObject }>},... ]` |
150
-
151
- #### View Modal (`viewModal`)
152
-
153
- | Property | Type | Required | Description | Accepted Values / Example |
154
- | ----------------- | --------------- | -------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
155
- | `title` | string | Yes | Modal title | `"User Details"`, `"View Profile"` |
156
- | `icon` | ReactNode | No | Icon element displayed in modal header | `<EyeIcon />`, `<InfoIcon />` |
157
- | `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
158
- | `component` | React component | No | Custom component to render (receives `data` prop) | `(props) => <CustomViewComponent data={props.data} />` |
159
- | `variant` | string | No | View layout style | `"default"`, `"card"`, `"split"` |
160
- | `fields` | array | No | View field objects (if not using custom component) | Array of field objects (see [View Field Schema](#view-field-schema)) |
161
- | `styles` | object | No | Custom CSS classes for various view elements | `{ containerClass: "...", rowClass: "...", labelClass: "...", valueClass: "...", ... }` |
162
- | `modalClassNames` | object | No | Custom classes for modal structure | `{ overlay: "...", container: "...", header: "...", body: "...", footer: "...", closeButton: "..." }` |
163
- | `footer` | object | No | Footer configuration | `{ cancelButton: true, cancelText: "Close" }` |
130
+ ### Table Column Object (`table_head[]`)
131
+
132
+ | Key | Type | Description |
133
+ | ---------------- | -------- | --------------------------------------------------------------------------------------- |
134
+ | `key` | string | Property name in the row data object |
135
+ | `title` | string | Column header text |
136
+ | `type` | string | Renderer: `plain` (default), `index`, `group`, `chip`, `date`, `avatar`, `menu_actions` |
137
+ | `imageKey` | string | Property path to image URL (for `avatar` / `group` types) |
138
+ | `titleKey` | string | Property path to title text (for `avatar` / `group` types) |
139
+ | `subtitleKey` | string | Property path to subtitle text (for `avatar` / `group` types) |
140
+ | `onClickDetails` | boolean | Opens view modal when cell is clicked |
141
+ | `variant` | string | Chip style: `contained`, `outline`, `soft` |
142
+ | `chipOptions` | array | `[{ value, label, color? }]` maps data values to chip labels/colors |
143
+ | `defaultColor` | string | Fallback chip color when no `chipOptions` match |
144
+ | `className` | string | Tailwind classes for the cell content |
145
+ | `format` | string | Date format pattern (e.g. `"DD MMM YYYY"`) — used with `type: "date"` |
146
+ | `menuList` | array | `[{ title, type, variant?, icon?, onClick? }]` — used with `type: "menu_actions"` |
147
+ | `render` | function | Custom cell renderer: `(row, rowIndex) => ReactNode` |
164
148
 
165
149
  ---
166
150
 
167
- ### Form Field Schema
168
-
169
- Used by `modalConfig.*.formFields`, `filterConfig.fields`, and `viewModal.fields`. All form fields follow the `FormField` shape.
170
-
171
- #### Common Field Properties (All Types)
172
-
173
- | Key | Type | Required | Description | Accepted Values / Example |
174
- | ------------------ | -------- | -------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
175
- | `key` | string | Yes | Property name/identifier for form data | `"username"`, `"email"`, `"birth_date"` |
176
- | `label` | string | No | Human-readable label for the field | `"User Name"`, `"Email Address"`, `"Date of Birth"` |
177
- | `type` | string | Yes | Field type determining the input/renderer | `"text"`, `"number"`, `"email"`, `"password"`, `"select"`, `"checkbox"`, `"radio"`, `"switch"`, `"phone"`, `"textarea"`, `"image"`, `"video"`, `"audio"`, `"file"`, `"tinyEditor"`, `"group"` |
178
- | `required` | boolean | No | Field must have a value for form submission | `true`, `false` (default: `false`) |
179
- | `minLength` | number | No | Minimum character length (for text fields) | `5`, `10`, `50` |
180
- | `placeholder` | string | No | Placeholder text shown in empty field | `"Enter your name"`, `"user@example.com"` |
181
- | `disabled` | boolean | No | Field is disabled and read-only | `true`, `false` (default: `false`) |
182
- | `parentClass` | string | No | Custom CSS classes for field wrapper (grid) | Tailwind classes: `"col-span-6"`, `"col-span-12"` |
183
- | `renderCondition` | function | No | Show field based on form data | `(formData: Record<string, any>) => boolean`; e.g., `(data) => data.userType === 'admin'` |
184
- | `customValidation` | function | No | Custom validation logic | `(value: any) => boolean \| string`; return `false` for invalid, error message string, or `true` for valid |
185
- | `className` | string | No | Custom CSS class for input element | Tailwind classes: `"bg-gray-100 rounded-lg"` |
186
-
187
- #### Type-Specific Properties
188
-
189
- ##### `"text"` Field
190
-
191
- | Property | Type | Description | Example |
192
- | ------------------ | ------- | ------------------------- | ---------------------------------------------------------------------------- |
193
- | `pattern` | string | Regex validation pattern | `"^[a-zA-Z ]*$"` (letters and spaces only) |
194
- | `mask` | string | Input mask pattern | `"(99) 99999-9999"` (format: 9=digit, A=letter, X=alphanumeric, \*=any char) |
195
- | `maskApplyOnValue` | boolean | Apply mask formData value | `true`, `false` (default: `true`) |
151
+ ### Sorting (`sort`)
196
152
 
197
- ##### `"number"` Field
153
+ | Property | Type | Description |
154
+ | ---------------------- | -------- | --------------------------------------------------------------- |
155
+ | `enabled` | boolean | Enable sorting (default: `true`) |
156
+ | `useServerSideSorting` | boolean | Pass sort params to `fetchData` instead of sorting client-side |
157
+ | `fields` | string[] | Sortable field keys |
158
+ | `autoGenerate` | boolean | Auto-generate sort options from table column headers |
159
+ | `defaultValue` | string | Default selected sort option (e.g. `"createdAt_desc"`) |
160
+ | `clearLabel` | string | Label for the "clear sort" option |
161
+ | `onChange` | function | `({ value, key, order, type }) => void` — called on sort change |
162
+ | `options` | array | `[{ value, label, key, order, type }]` — custom sort options |
198
163
 
199
- | Property | Type | Description | Example |
200
- | --------------------- | ------- | ---------------------- | ---------------------------------- |
201
- | `negativeNumberAllow` | boolean | Allow negative numbers | `true`, `false` (default: `false`) |
202
-
203
- ##### `"select"` Field
204
-
205
- | Property | Type | Description | Accepted Values / Example |
206
- | ------------------- | ------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
207
- | `options` | array | Select options; `{ value, label, color? }[]` | `[{ value: "active", label: "Active", color: "green" }, { value: "inactive", label: "Inactive", color: "red" }]` |
208
- | `countriesList` | boolean | Country selector dropdown | `true`, `false` (default: `false`) |
209
- | `multiple` | boolean | Allow selecting multiple options | `true`, `false` (default: `false`) |
210
- | `search` | boolean | Enable searchable dropdown | `true`, `false` (default: `false`) |
211
- | `dropdownMaxHeight` | number | Max height of dropdown in pixels | `300`, `400` |
212
- | `defaultValue` | any | Initial value for the field | `"John"`, `25`, `true`, `["option1", "option2"]` |
164
+ ---
213
165
 
214
- ##### `"checkbox"` Field
166
+ ### modalConfig
215
167
 
216
- | Property | Type | Description | Accepted Values / Example |
217
- | ---------- | ------- | -------------------------------------- | -------------------------------------------------------------------------------- |
218
- | `options` | array | Checkbox options; `{ value, label }[]` | `[{ value: "read", label: "Can Read" }, { value: "write", label: "Can Write" }]` |
219
- | `multiple` | boolean | Allow selecting multiple values | `true` (default: `true`); affects how data is stored |
168
+ Configure the add, edit, delete, and view modals under a single `modalConfig` object:
220
169
 
221
- ##### `"radio"` Field
170
+ ```js
171
+ modalConfig: {
172
+ addModal: { ... },
173
+ editModal: { ... },
174
+ deleteModal: { ... },
175
+ viewModal: { ... },
176
+ }
177
+ ```
222
178
 
223
- | Property | Type | Description | Accepted Values / Example |
224
- | --------- | ----- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
225
- | `options` | array | Radio button options; `{ value, label }[]` | `[{ value: "male", label: "Male" }, { value: "female", label: "Female" }, { value: "other", label: "Other" }]` |
179
+ ---
226
180
 
227
- ##### `"switch"` Field
181
+ ### Add & Edit Modal
228
182
 
229
- | Property | Type | Description | Accepted Values / Example |
230
- | --------- | ------ | -------------------------------------------- | ---------------------------------------------------------------- |
231
- | `text` | string | Description/label shown next to switch | `"Enable notifications"`, `"Is admin"` |
232
- | `options` | array | Optional radio-like options (radio fallback) | `[{ value: "yes", label: "Yes" }, { value: "no", label: "No" }]` |
183
+ | Property | Type | Description |
184
+ | --------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------- |
185
+ | `title` | string | Modal title |
186
+ | `icon` | ReactNode | Icon in the modal header |
187
+ | `size` | string | Width: `sm`, `md` (default), `lg`, `xl`, `full` |
188
+ | `formClass` | string | Tailwind classes for the form wrapper (e.g. `"grid grid-cols-12 gap-4"`) |
189
+ | `formFields` | array | Field definitions — [see Form Field Schema](#form-field-schema) |
190
+ | `handleSubmit` | function | **Add:** `async (formData) => { newObject, message? }` **Edit:** `async (formData, item) => { newObject, targetObject, message? }` |
191
+ | `actionButtons` | array | Custom footer buttons |
233
192
 
234
- ##### `"phone"` Field
193
+ ---
235
194
 
236
- | Property | Type | Description | Accepted Values / Example |
237
- | ---------------- | ------- | -------------------------------------- | -------------------------------------- |
238
- | `countriesList` | boolean | Show country selector dropdown | `true`, `false` (default: `false`) |
239
- | `defaultCountry` | string | Default country ISO code | `"US"`, `"GB"`, `"IN"`, `"CA"`, `"AU"` |
240
- | `search` | boolean | Enable searching countries in selector | `true`, `false` (default: `false`) |
241
- | `placeholder` | string | Placeholder text | `"+1 (555) 000-0000"` |
195
+ ### Delete Modal
242
196
 
243
- ##### `"textarea"` Field
197
+ | Property | Type | Description |
198
+ | --------------- | --------- | ------------------------------------------------------------------------------------- |
199
+ | `title` | string | Modal title |
200
+ | `icon` | ReactNode | Icon in the modal header |
201
+ | `size` | string | Width: `sm` (default), `md`, `lg`, `xl`, `full` |
202
+ | `confirmText` | string | Confirmation message shown to the user |
203
+ | `referenceKey` | string | Property from the selected row to display (e.g. `"name"`) |
204
+ | `actionButtons` | array | `[{ type, label, color, variant, onClick: async (event, item) => { targetObject } }]` |
244
205
 
245
- | Property | Type | Description | Accepted Values / Example |
246
- | -------- | ------ | ---------------------- | ------------------------- |
247
- | `rows` | number | Number of visible rows | `3`, `5`, `10` |
206
+ ---
248
207
 
249
- ##### `"image"` Field
208
+ ### View Modal
250
209
 
251
- | Property | Type | Description | Accepted Values / Example |
252
- | ------------- | ------- | -------------------------------- | ----------------------------------------------- |
253
- | `accept` | string | MIME type filter | `"image/*"` (default), `"image/jpeg,image/png"` |
254
- | `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
255
- | `cropImage` | boolean | Enable image cropping modal | `true`, `false` (default: `false`) |
256
- | `aspectRatio` | number | Crop aspect ratio (width:height) | `1` (1:1 square), `16/9`, `4/3`, `1.5` |
257
- | `multiple` | boolean | Allow selecting images | `true`, `false` (default: `false`) |
258
- | `maxImages` | number | Limit the multiple select images | `8`, `4` |
210
+ | Property | Type | Description |
211
+ | ----------------- | --------------- | ---------------------------------------------------------------------------------- |
212
+ | `title` | string | Modal title |
213
+ | `icon` | ReactNode | Icon in the modal header |
214
+ | `size` | string | Width: `sm`, `md` (default), `lg`, `xl`, `full` |
215
+ | `variant` | string | Layout style: `default`, `card`, `split` |
216
+ | `fields` | array | View field definitions [see View Field Schema](#view-field-schema) |
217
+ | `component` | React component | Fully custom component receives `{ data }` prop |
218
+ | `styles` | object | Custom classes: `containerClass`, `rowClass`, `labelClass`, `valueClass`, etc. |
219
+ | `modalClassNames` | object | Custom classes for modal parts: `overlay`, `container`, `header`, `body`, `footer` |
220
+ | `footer` | object | `{ cancelButton: true, cancelText: "Close" }` |
259
221
 
260
- ##### `"video"` Field
222
+ ---
261
223
 
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 MB | `5`, `10`, `25` |
224
+ ### Form Field Schema
267
225
 
268
- ##### `"audio"` Field
226
+ Used in `addModal.formFields`, `editModal.formFields`, and `filterConfig.fields`.
227
+
228
+ #### Common Properties (all field types)
229
+
230
+ | Property | Type | Description |
231
+ | ------------------ | -------- | --------------------------------------------------------------- |
232
+ | `key` | string | Data property name — **required** |
233
+ | `label` | string | Display label |
234
+ | `type` | string | Field type — see list below — **required** |
235
+ | `required` | boolean | Marks the field as required |
236
+ | `minLength` | number | Minimum character length |
237
+ | `placeholder` | string | Input placeholder text |
238
+ | `disabled` | boolean | Makes the field read-only |
239
+ | `parentClass` | string | Tailwind class on the wrapper div (e.g. `"col-span-6"`) |
240
+ | `className` | string | Tailwind class on the input element |
241
+ | `renderCondition` | function | `(formData) => boolean` — show/hide field based on other values |
242
+ | `customValidation` | function | `(value) => true \| "error message"` — custom validation logic |
243
+
244
+ #### Available Field Types
245
+
246
+ | Type | Extra Properties |
247
+ | ------------ | ------------------------------------------------------------------------------------- |
248
+ | `text` | `pattern` (regex), `mask` (e.g. `"(99) 99999-9999"`), `maskApplyOnValue` |
249
+ | `number` | `negativeNumberAllow` |
250
+ | `email` | — |
251
+ | `password` | — |
252
+ | `textarea` | `rows` |
253
+ | `select` | `options`, `multiple`, `search`, `countriesList`, `dropdownMaxHeight`, `defaultValue` |
254
+ | `checkbox` | `options`, `multiple` |
255
+ | `radio` | `options` |
256
+ | `switch` | `text`, `options` |
257
+ | `phone` | `countriesList`, `defaultCountry`, `search` |
258
+ | `image` | `accept`, `dragDrop`, `cropImage`, `aspectRatio`, `multiple`, `maxImages` |
259
+ | `video` | `accept`, `dragDrop`, `maxSize` (MB) |
260
+ | `audio` | `accept`, `dragDrop`, `maxSize` (MB) |
261
+ | `file` | `accept` (e.g. `".pdf,.docx"`), `dragDrop`, `maxSize` (MB) |
262
+ | `tinyEditor` | `editorKey`, `fontFamily`, `height` |
263
+ | `group` | — |
264
+
265
+ **Mask pattern characters:** `9` = digit, `A` = letter, `X` = alphanumeric, `*` = any character. All other characters are treated as literals.
269
266
 
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 MB | `5`, `10`, `25` |
267
+ ---
275
268
 
276
- ##### `"file"` Field
269
+ ### View Field Schema
277
270
 
278
- Upload any file type in add/edit forms, with client-side type restriction and file icon preview.
271
+ Used in `viewModal.fields`.
272
+
273
+ | Property | Type | Description |
274
+ | ----------------- | --------- | --------------------------------------------------------------- |
275
+ | `key` | string | Property name from row data |
276
+ | `label` | string | Display label |
277
+ | `type` | string | `text`, `date`, `chip`, `image`, `avatar`, `group`, `cardGroup` |
278
+ | `format` | string | Date format (e.g. `"DD MMM YYYY"`) |
279
+ | `imageKey` | string | Property path to image URL |
280
+ | `titleKey` | string | Property path to title text |
281
+ | `subtitleKey` | string | Property path to subtitle text |
282
+ | `variant` | string | Chip style: `contained`, `outline`, `soft` |
283
+ | `chipOptions` | array | `[{ value, label, color }]` |
284
+ | `defaultColor` | string | Fallback chip color |
285
+ | `className` | string | Tailwind class on the value element |
286
+ | `blockClass` | string | Tailwind class on the field wrapper |
287
+ | `icon` | ReactNode | Icon displayed beside the field |
288
+ | `renderCondition` | function | `(data) => boolean` — show/hide based on row data |
279
289
 
280
- | Property | Type | Description | Accepted Values / Example |
281
- | ---------- | ------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
282
- | `accept` | string | Allowed file types (same as native input accept) | `"*/*"` (default), `".pdf,.doc,.docx"`, `"application/pdf"`, `"image/*"` |
283
- | `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
284
- | `maxSize` | number | Maximum file size in MB | `5`, `10`, `25` |
290
+ ---
285
291
 
286
- Behavior:
292
+ ## Features & Examples
287
293
 
288
- - Validates selected/dropped file using `accept`
289
- - Shows file icon automatically based on extension (pdf/doc/xls/ppt/zip/txt/json, etc.)
290
- - Shows selected file name and allows replacing/removing the file
294
+ ### 1. Client-Side CRUD (Minimal Setup)
291
295
 
292
- Example:
296
+ ```jsx
297
+ import Crud from "react-admin-crud-manager";
293
298
 
294
- ```js
295
- const formFields = [
296
- {
297
- key: "attachment",
298
- label: "Attachment",
299
- type: "file",
300
- accept: ".pdf,.doc,.docx",
301
- maxSize: 10, // MB
302
- dragDrop: true,
303
- required: true,
304
- },
305
- {
306
- key: "contractFile",
307
- label: "Contract",
308
- type: "file",
309
- accept: "application/pdf",
310
- },
299
+ const users = [
300
+ { id: 1, name: "John Doe", email: "john@example.com", status: "active" },
301
+ { id: 2, name: "Jane Smith", email: "jane@example.com", status: "inactive" },
311
302
  ];
312
- ```
313
303
 
314
- ##### `"tinyEditor"` Field
304
+ function App() {
305
+ const config = {
306
+ title: "Users",
307
+ isStaticData: true,
308
+ fetchData: async () => ({ data: users }),
309
+ tableConfig: {
310
+ table_head: [
311
+ { key: "id", title: "ID", type: "index" },
312
+ { key: "name", title: "Name" },
313
+ { key: "email", title: "Email" },
314
+ {
315
+ key: "status",
316
+ title: "Status",
317
+ type: "chip",
318
+ chipOptions: [
319
+ { value: "active", label: "Active", color: "green" },
320
+ { value: "inactive", label: "Inactive", color: "red" },
321
+ ],
322
+ },
323
+ ],
324
+ search: { enabled: true, searchKeys: ["name", "email"] },
325
+ pagination: { enabled: true },
326
+ },
327
+ modalConfig: {
328
+ addModal: {
329
+ title: "Add User",
330
+ formFields: [
331
+ { key: "name", label: "Name", type: "text", required: true },
332
+ { key: "email", label: "Email", type: "email", required: true },
333
+ {
334
+ key: "status",
335
+ label: "Status",
336
+ type: "select",
337
+ options: [
338
+ { value: "active", label: "Active" },
339
+ { value: "inactive", label: "Inactive" },
340
+ ],
341
+ },
342
+ ],
343
+ handleSubmit: async (formData) => ({
344
+ newObject: { ...formData, id: Date.now() },
345
+ }),
346
+ },
347
+ },
348
+ };
315
349
 
316
- | Property | Type | Description | Accepted Values / Example |
317
- | ------------ | ------ | ----------------------------- | ----------------------------------------------------------- |
318
- | `editorKey` | string | TinyMCE API key (required) | Get from `import.meta.env.VITE_EDITOR_KEY` or pass directly |
319
- | `fontFamily` | string | Default font family in editor | `"Arial"`, `"Georgia"`, `"Courier New"` |
320
- | `height` | number | Editor height in pixels | `300`, `500` |
350
+ return <Crud config={config} />;
351
+ }
352
+ ```
321
353
 
322
354
  ---
323
355
 
324
- ### View Field Schema
325
-
326
- Used by `viewModal.fields` to display data in view/details modals.
327
-
328
- | Key | Type | Description | Accepted Values / Example |
329
- | ----------------- | --------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
330
- | `key` | string | Property name to display | `"username"`, `"email"`, `"birth_date"` |
331
- | `label` | string | Display label for the field | `"User Name"`, `"Email Address"` |
332
- | `type` | string | Display type (similar to form field types) | `"text"`, `"date"`, `"chip"`, `"image"`, `"avatar"`, `"group"`, `"cardGroup"` |
333
- | `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` |
334
- | `imageKey` | string | Image property for avatar types | `"profileImage"`, `"avatarUrl"` |
335
- | `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` |
336
- | `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` |
337
- | `variant` | string | Chip display variant | `"contained"`, `"outline"`, `"soft"` |
338
- | `chipOptions` | array | Map values to chip labels and colors | `[{ value: "active", label: "Active", color: "green" }]` |
339
- | `defaultColor` | string | Default chip color | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"` |
340
- | `className` | string | Custom CSS class for display element | Tailwind classes |
341
- | `blockClass` | string | Custom CSS class for field block/wrapper | Tailwind classes |
342
- | `icon` | ReactNode | Icon element to display with field | `<UserIcon />`, `<MailIcon />` |
343
- | `renderCondition` | function | Show field based on data | `(data: Record<string, any>) => boolean` |
356
+ ### 2. Server-Side CRUD with Advanced Features
344
357
 
345
- ---
358
+ ```jsx
359
+ import Crud from "react-admin-crud-manager";
360
+ import axios from "axios";
346
361
 
347
- ## Advanced Features
362
+ const api = axios.create({ baseURL: "https://api.example.com" });
348
363
 
349
- ### 1. Export CSV
364
+ const config = {
365
+ title: "Products",
366
+ description: "Manage your product inventory",
367
+ buttonText: "Add Product",
350
368
 
351
- Export table data as a CSV file with custom field selection:
369
+ fetchData: async ({
370
+ search,
371
+ rows_per_page,
372
+ current_page,
373
+ sort_by,
374
+ sort_order,
375
+ ...filters
376
+ }) => {
377
+ const resp = await api.get("/products", {
378
+ params: {
379
+ q: search,
380
+ limit: rows_per_page,
381
+ page: current_page,
382
+ sort_by,
383
+ sort_order,
384
+ ...filters,
385
+ },
386
+ });
387
+ return {
388
+ data: resp.data.items,
389
+ pagination: {
390
+ current_page: resp.data.page,
391
+ rows_per_page: resp.data.limit,
392
+ total_pages: resp.data.totalPages,
393
+ total_records: resp.data.total,
394
+ },
395
+ };
396
+ },
352
397
 
353
- ```js
354
- const config = {
355
- // ... other config
356
398
  tableConfig: {
357
- table_head: [...],
358
- data: [...]
399
+ table_head: [
400
+ { key: "id", title: "ID", type: "index" },
401
+ { key: "name", title: "Name" },
402
+ {
403
+ key: "price",
404
+ title: "Price",
405
+ render: (row) => `$${row.price.toFixed(2)}`,
406
+ },
407
+ {
408
+ key: "category",
409
+ title: "Category",
410
+ type: "chip",
411
+ variant: "soft",
412
+ chipOptions: [
413
+ { value: "electronics", label: "Electronics", color: "blue" },
414
+ { value: "clothing", label: "Clothing", color: "purple" },
415
+ ],
416
+ },
417
+ {
418
+ key: "actions",
419
+ title: "",
420
+ type: "menu_actions",
421
+ menuList: [
422
+ { title: "Edit", type: "edit" },
423
+ { title: "Delete", type: "delete" },
424
+ { title: "View", type: "view" },
425
+ ],
426
+ },
427
+ ],
428
+ search: { enabled: true, useServerSideSearch: true },
429
+ filter: { enabled: true, useServerSideFilters: true },
430
+ pagination: { enabled: true, useServerSidePagination: true },
431
+ sort: { enabled: true, useServerSideSorting: true, autoGenerate: true },
359
432
  exportCSV: {
360
433
  enabled: true,
361
- fileName: "users_export.csv",
434
+ fileName: "products.csv",
362
435
  fields: [
363
436
  { label: "ID", key: "id" },
364
437
  { label: "Name", key: "name" },
365
- { label: "Email", key: "email" },
366
- { label: "Status", key: "status" }
367
- ]
368
- }
369
- }
438
+ { label: "Price", key: "price" },
439
+ ],
440
+ },
441
+ filterConfig: {
442
+ fields: [
443
+ {
444
+ key: "category",
445
+ label: "Category",
446
+ type: "select",
447
+ options: [
448
+ { value: "electronics", label: "Electronics" },
449
+ { value: "clothing", label: "Clothing" },
450
+ ],
451
+ },
452
+ { key: "minPrice", label: "Min Price", type: "number" },
453
+ { key: "maxPrice", label: "Max Price", type: "number" },
454
+ ],
455
+ },
456
+ },
457
+
458
+ modalConfig: {
459
+ addModal: {
460
+ title: "Add Product",
461
+ size: "lg",
462
+ formFields: [
463
+ {
464
+ key: "name",
465
+ label: "Name",
466
+ type: "text",
467
+ required: true,
468
+ parentClass: "col-span-12",
469
+ },
470
+ {
471
+ key: "price",
472
+ label: "Price",
473
+ type: "number",
474
+ required: true,
475
+ parentClass: "col-span-6",
476
+ },
477
+ {
478
+ key: "stock",
479
+ label: "Stock",
480
+ type: "number",
481
+ required: true,
482
+ parentClass: "col-span-6",
483
+ },
484
+ {
485
+ key: "description",
486
+ label: "Description",
487
+ type: "textarea",
488
+ rows: 4,
489
+ parentClass: "col-span-12",
490
+ },
491
+ {
492
+ key: "image",
493
+ label: "Image",
494
+ type: "image",
495
+ dragDrop: true,
496
+ cropImage: true,
497
+ aspectRatio: 1,
498
+ parentClass: "col-span-12",
499
+ },
500
+ ],
501
+ handleSubmit: async (formData) => {
502
+ const resp = await api.post("/products", formData);
503
+ return { newObject: resp.data, message: "Product added!" };
504
+ },
505
+ },
506
+
507
+ editModal: {
508
+ title: "Edit Product",
509
+ size: "lg",
510
+ formFields: [
511
+ {
512
+ key: "name",
513
+ label: "Name",
514
+ type: "text",
515
+ required: true,
516
+ parentClass: "col-span-12",
517
+ },
518
+ {
519
+ key: "price",
520
+ label: "Price",
521
+ type: "number",
522
+ required: true,
523
+ parentClass: "col-span-6",
524
+ },
525
+ {
526
+ key: "stock",
527
+ label: "Stock",
528
+ type: "number",
529
+ required: true,
530
+ parentClass: "col-span-6",
531
+ },
532
+ ],
533
+ handleSubmit: async (formData, item) => {
534
+ const resp = await api.put(`/products/${item.id}`, formData);
535
+ return { newObject: resp.data, targetObject: item };
536
+ },
537
+ },
538
+
539
+ deleteModal: {
540
+ title: "Delete Product",
541
+ confirmText: "Are you sure you want to delete this product?",
542
+ referenceKey: "name",
543
+ actionButtons: [
544
+ {
545
+ type: "button",
546
+ label: "Delete",
547
+ color: "error",
548
+ variant: "contained",
549
+ onClick: async (event, item) => {
550
+ await api.delete(`/products/${item.id}`);
551
+ return { targetObject: item };
552
+ },
553
+ },
554
+ ],
555
+ },
556
+
557
+ viewModal: {
558
+ title: "Product Details",
559
+ variant: "card",
560
+ fields: [
561
+ { key: "id", label: "ID" },
562
+ { key: "name", label: "Name" },
563
+ { key: "price", label: "Price" },
564
+ { key: "stock", label: "Stock" },
565
+ { key: "category", label: "Category", type: "chip" },
566
+ ],
567
+ },
568
+ },
370
569
  };
371
570
  ```
372
571
 
373
- ### 2. Conditional Field Rendering
572
+ ---
374
573
 
375
- Show/hide form fields based on other field values using `renderCondition`:
574
+ ### 3. Conditional Fields & Custom Validation
376
575
 
377
576
  ```js
378
577
  const formFields = [
@@ -389,454 +588,254 @@ const formFields = [
389
588
  key: "adminLevel",
390
589
  label: "Admin Level",
391
590
  type: "select",
392
- renderCondition: (formData) => formData.userType === "admin", // Only show if userType is admin
591
+ renderCondition: (data) => data.userType === "admin", // only shown for admins
393
592
  options: [
394
593
  { value: "superadmin", label: "Super Admin" },
395
594
  { value: "moderator", label: "Moderator" },
396
595
  ],
397
596
  },
398
- {
399
- key: "department",
400
- label: "Department",
401
- type: "text",
402
- renderCondition: (formData) => formData.userType === "user", // Only show if userType is user
403
- },
404
- ];
405
- ```
406
-
407
- ### 3. Custom Field Validation
408
-
409
- Add custom validation logic to form fields:
410
-
411
- ```js
412
- const formFields = [
413
597
  {
414
598
  key: "email",
415
599
  label: "Email",
416
600
  type: "email",
601
+ required: true,
417
602
  customValidation: (value) => {
418
- // Return true/false or custom error message
419
- if (!value.includes("@company.com")) {
420
- return "Email must be a company email"; // Error message
421
- }
422
- return true; // Valid
603
+ if (!value.includes("@company.com")) return "Must be a company email";
604
+ return true;
423
605
  },
424
606
  },
425
607
  {
426
- key: "age",
427
- label: "Age",
428
- type: "number",
608
+ key: "password",
609
+ label: "Password",
610
+ type: "password",
429
611
  customValidation: (value) => {
430
- if (value < 18) return "Must be 18 or older";
431
- if (value > 120) return "Please enter a valid age";
612
+ if (value.length < 8) return "Minimum 8 characters";
613
+ if (!/[A-Z]/.test(value)) return "Must include an uppercase letter";
614
+ if (!/[0-9]/.test(value)) return "Must include a number";
432
615
  return true;
433
616
  },
434
617
  },
435
618
  ];
436
619
  ```
437
620
 
438
- ### 4. Custom Table Cell Rendering
621
+ ---
439
622
 
440
- Use the `render` function for complex cell display:
623
+ ### 4. Export CSV
441
624
 
442
625
  ```js
443
- const tableConfig = {
444
- table_head: [
445
- { key: "id", title: "ID", type: "index" },
446
- {
447
- key: "name",
448
- title: "Name",
449
- render: (row, rowIndex) => (
450
- <div className="font-bold text-blue-600">{row.name.toUpperCase()}</div>
451
- )
452
- },
453
- {
454
- key: "price",
455
- title: "Price",
456
- render: (row) => (
457
- <span className="text-green-600 font-semibold">
458
- ${row.price.toFixed(2)}
459
- </span>
460
- )
461
- },
462
- {
463
- key: "actions",
464
- title: "Actions",
465
- render: (row) => (
466
- <button onClick={() => console.log(row)}>
467
- Custom Action
468
- </button>
469
- )
470
- }
471
- ],
472
- data: [...]
473
- };
626
+ tableConfig: {
627
+ exportCSV: {
628
+ enabled: true,
629
+ fileName: "users_export.csv",
630
+ fields: [
631
+ { label: "ID", key: "id" },
632
+ { label: "Name", key: "name" },
633
+ { label: "Email", key: "email" },
634
+ ],
635
+ },
636
+ }
474
637
  ```
475
638
 
476
- ### 5. View Modal Variants
639
+ ---
477
640
 
478
- Display details in different layouts with view modal variants:
641
+ ### 5. Server-Side Filtering
479
642
 
480
643
  ```js
481
- // Default variant - standard grid layout
482
- const config = {
483
- modalConfig: {
484
- viewModal: {
485
- title: "User Details",
486
- variant: "default", // Standard grid layout
487
- fields: [
488
- { key: "name", label: "Full Name", type: "text" },
489
- { key: "email", label: "Email", type: "text" }
490
- ]
491
- }
492
- }
493
- };
494
-
495
- // Card variant - each field in an elevated card
496
- const cardConfig = {
497
- modalConfig: {
498
- viewModal: {
499
- title: "User Details",
500
- variant: "card", // Each field is a separate card
501
- styles: {
502
- containerClass: "grid grid-cols-12 gap-4",
503
- cardGroupClass: "col-span-6 bg-white rounded-lg shadow p-4"
644
+ tableConfig: {
645
+ filter: { enabled: true, useServerSideFilters: true },
646
+ filterConfig: {
647
+ fields: [
648
+ {
649
+ key: "status", label: "Status", type: "select",
650
+ options: [
651
+ { value: "active", label: "Active" },
652
+ { value: "inactive", label: "Inactive" },
653
+ ],
504
654
  },
505
- fields: [...]
506
- }
507
- }
508
- };
509
-
510
- // Split variant - property sheet style with dividing lines
511
- const splitConfig = {
512
- modalConfig: {
513
- viewModal: {
514
- title: "User Details",
515
- variant: "split", // Clean property-sheet layout
516
- fields: [...]
517
- }
518
- }
519
- };
520
- ```
521
-
522
- ### 6. Custom View Component
523
-
524
- Use a fully custom component for the view modal:
525
-
526
- ```js
527
- const CustomUserView = ({ data }) => (
528
- <div className="space-y-4">
529
- <div className="flex items-center gap-4">
530
- <img
531
- src={data.avatarUrl}
532
- alt={data.name}
533
- className="w-16 h-16 rounded-full"
534
- />
535
- <div>
536
- <h2 className="text-xl font-bold">{data.name}</h2>
537
- <p className="text-gray-600">{data.email}</p>
538
- </div>
539
- </div>
540
- <div className="grid grid-cols-2 gap-4">
541
- <div>
542
- <label className="text-sm font-semibold">Phone</label>
543
- <p>{data.phone}</p>
544
- </div>
545
- <div>
546
- <label className="text-sm font-semibold">Status</label>
547
- <p>{data.status}</p>
548
- </div>
549
- </div>
550
- </div>
551
- );
552
-
553
- const config = {
554
- modalConfig: {
555
- viewModal: {
556
- title: "User Details",
557
- component: CustomUserView, // Use custom component
558
- },
655
+ { key: "createdFrom", label: "From", type: "date" },
656
+ { key: "createdTo", label: "To", type: "date" },
657
+ ],
559
658
  },
560
- };
659
+ },
660
+ fetchData: async ({ search, rows_per_page, current_page, sort_by, sort_order, ...filters }) => {
661
+ const resp = await api.get("/users", {
662
+ params: { q: search, limit: rows_per_page, page: current_page, sort_by, sort_order, ...filters },
663
+ });
664
+ return { data: resp.data.items, pagination: resp.data.pagination };
665
+ },
561
666
  ```
562
667
 
563
- ### 7. Row Click Handler
668
+ ---
564
669
 
565
- Handle clicks on table rows:
670
+ ### 6. Image Cropping
566
671
 
567
672
  ```js
568
- // For a custom function
569
- const config = {
570
- tableConfig: {
571
- table_head: [...],
572
- data: [...],
573
- rowClick: (row, rowIndex) => {
574
- console.log(`Row ${rowIndex} clicked:`, row);
575
- // Open custom drawer, navigate, or perform any action
576
- }
577
- }
578
- };
579
-
580
- // For open details on row click
581
- const config = {
582
- tableConfig: {
583
- table_head: [...],
584
- data: [...],
585
- rowClick: true,
586
- }
587
- };
673
+ {
674
+ key: "profileImage", label: "Profile Picture", type: "image",
675
+ cropImage: true,
676
+ aspectRatio: 1, // 1:1 square
677
+ dragDrop: true,
678
+ }
588
679
 
680
+ {
681
+ key: "banner", label: "Banner", type: "image",
682
+ cropImage: true,
683
+ aspectRatio: 16 / 9,
684
+ dragDrop: true,
685
+ }
589
686
  ```
590
687
 
591
- ### 8. Server-Side Filtering
688
+ ---
592
689
 
593
- Implement server-side filtering with custom filter fields:
690
+ ### 7. Input Masking
594
691
 
595
692
  ```js
596
- const config = {
597
- tableConfig: {
598
- filter: {
599
- enabled: true,
600
- useServerSideFilters: true, // Enable server-side filtering
601
- },
602
- filterConfig: {
603
- fields: [
604
- {
605
- key: "status",
606
- label: "Status",
607
- type: "select",
608
- options: [
609
- { value: "active", label: "Active" },
610
- { value: "inactive", label: "Inactive" },
611
- ],
612
- },
613
- {
614
- key: "createdFrom",
615
- label: "Created From",
616
- type: "date",
617
- },
618
- {
619
- key: "createdTo",
620
- label: "Created To",
621
- type: "date",
622
- },
623
- ],
624
- },
625
- },
626
- fetchData: async ({
627
- search,
628
- rows_per_page,
629
- current_page,
630
- sort_by,
631
- sort_order,
632
- ...filters // Additional filter params passed here
633
- }) => {
634
- const resp = await api.get("/users", {
635
- params: {
636
- q: search,
637
- limit: rows_per_page,
638
- page: current_page,
639
- sort_by,
640
- sort_order,
641
- ...filters, // Include filters in API call
642
- },
643
- });
644
- return { data: resp.items, pagination: resp.pagination };
645
- },
646
- };
693
+ // Pattern: 9 = digit | A = letter | X = alphanumeric | * = any | other = literal
694
+
695
+ { key: "phone", type: "text", mask: "(99) 99999-9999" }
696
+ { key: "zip", type: "text", mask: "99999-999" }
697
+ { key: "creditCard", type: "text", mask: "9999 9999 9999 9999" }
647
698
  ```
648
699
 
649
- ### 9. Input Masking
700
+ ---
650
701
 
651
- Format input with masks using pattern syntax:
702
+ ### 8. Custom Table Cell Rendering
652
703
 
653
- ```js
654
- const formFields = [
704
+ ```jsx
705
+ table_head: [
655
706
  {
656
- key: "phone",
657
- label: "Phone Number",
658
- type: "text",
659
- mask: "(99) 99999-9999", // Mask pattern
660
- maskApplyOnValue: true, // Apply mask to default value
661
- placeholder: "(00) 00000-0000",
707
+ key: "price",
708
+ title: "Price",
709
+ render: (row) => (
710
+ <span className="text-green-600 font-semibold">
711
+ ${row.price.toFixed(2)}
712
+ </span>
713
+ ),
662
714
  },
663
715
  {
664
- key: "zipCode",
665
- label: "ZIP Code",
666
- type: "text",
667
- mask: "99999-999", // Pattern: 9 = digit, A = letter, X = alphanumeric, * = any
668
- placeholder: "00000-000",
669
- },
670
- {
671
- key: "creditCard",
672
- label: "Credit Card",
673
- type: "text",
674
- mask: "9999 9999 9999 9999",
675
- placeholder: "0000 0000 0000 0000",
716
+ key: "user",
717
+ title: "User",
718
+ render: (row) => (
719
+ <div className="flex items-center gap-3">
720
+ <img src={row.avatar} className="w-8 h-8 rounded-full" />
721
+ <div>
722
+ <p className="font-semibold">{row.name}</p>
723
+ <p className="text-xs text-gray-500">{row.email}</p>
724
+ </div>
725
+ </div>
726
+ ),
676
727
  },
677
728
  ];
678
729
  ```
679
730
 
680
- **Mask Pattern Reference:**
681
-
682
- - `9` — Digit (0-9)
683
- - `A` — Letter (a-zA-Z)
684
- - `X` — Alphanumeric (a-zA-Z0-9)
685
- - `*` — Any character
686
- - Any other character is literal (e.g., `-`, `/`, ` `)
731
+ ---
687
732
 
688
- ### 10. Image Cropping
733
+ ### 9. Custom Toolbar Buttons
689
734
 
690
- Enable image cropping in image picker:
735
+ ```jsx
736
+ import { Upload, RefreshCw } from "lucide-react";
691
737
 
692
- ```js
693
- const formFields = [
694
- {
695
- key: "profileImage",
696
- label: "Profile Picture",
697
- type: "image",
698
- cropImage: true, // Enable cropping modal
699
- aspectRatio: 1, // 1:1 square ratio
700
- dragDrop: true,
701
- },
702
- {
703
- key: "bannerImage",
704
- label: "Banner Image",
705
- type: "image",
706
- cropImage: true,
707
- aspectRatio: 16 / 9, // 16:9 widescreen ratio
708
- dragDrop: true,
709
- },
710
- ];
738
+ tableConfig: {
739
+ customButtons: [
740
+ {
741
+ key: "import", label: "Import",
742
+ icon: <Upload className="w-4 h-4" />,
743
+ color: "primary", variant: "contained",
744
+ onClick: (event, ctx) => {
745
+ // ctx: { data, filteredData, sortedData, paginatedData,
746
+ // searchTerm, appliedFilters, currentPage, pageSize, totalRecords }
747
+ console.log("Total records:", ctx.totalRecords);
748
+ },
749
+ },
750
+ {
751
+ key: "refresh", label: "Refresh",
752
+ icon: <RefreshCw className="w-4 h-4" />,
753
+ variant: "outlined",
754
+ onClick: async () => { /* refresh logic */ },
755
+ },
756
+ ],
757
+ customMenuItems: [
758
+ {
759
+ key: "bulk-delete", label: "Bulk Delete",
760
+ onClick: (event, ctx) => console.log(ctx.filteredData),
761
+ },
762
+ ],
763
+ }
711
764
  ```
712
765
 
713
- ### 11. Sortable Columns
766
+ **Button properties:** `key`, `label`, `icon`, `variant` (`contained` / `outlined` / `text`), `color` (`primary` / `success` / `error` / `default`), `className`, `disabled`, `show`, `onClick`
714
767
 
715
- Configure sorting with custom options and auto-generation:
768
+ **Menu item properties:** `key`, `label`, `icon`, `className`, `disabled`, `show`, `onClick`
769
+
770
+ ---
771
+
772
+ ### 10. Row Click Handler
716
773
 
717
774
  ```js
718
- const config = {
719
- tableConfig: {
720
- sort: {
721
- enabled: true,
722
- useServerSideSorting: false, // Client-side sorting
723
- autoGenerate: true, // Auto-generate sort options from table headers
724
- fields: ["name", "email", "createdAt"], // Fields to make sortable
725
- defaultValue: "name", // Default sort field
726
- clearLabel: "Clear Sort",
727
- onChange: (payload) => {
728
- console.log("Sort changed:", payload);
729
- // payload contains: { value, option, key, order, type }
730
- },
731
- options: [
732
- {
733
- value: "name_asc",
734
- label: "Name (A-Z)",
735
- key: "name",
736
- order: "asc",
737
- type: "string",
738
- },
739
- {
740
- value: "name_desc",
741
- label: "Name (Z-A)",
742
- key: "name",
743
- order: "desc",
744
- type: "string",
745
- },
746
- ],
747
- },
748
- table_head: [
749
- { key: "name", title: "Name" },
750
- { key: "email", title: "Email" },
751
- { key: "createdAt", title: "Created Date" },
752
- ],
775
+ // Custom handler
776
+ tableConfig: {
777
+ rowClick: (row, rowIndex) => {
778
+ console.log("Clicked row:", row);
753
779
  },
754
- };
780
+ }
781
+
782
+ // Open view modal on row click
783
+ tableConfig: {
784
+ rowClick: true,
785
+ }
755
786
  ```
756
787
 
757
- ### 12. Custom Toolbar Buttons (Header)
788
+ ---
758
789
 
759
- Add custom buttons in the table header area (same area as Add / Search / Filter / Sort / Export), with your own click handlers.
790
+ ### 11. View Modal Variants
760
791
 
761
792
  ```js
762
- import { Upload, RefreshCw } from "lucide-react";
793
+ // default standard grid layout
794
+ viewModal: { title: "Details", variant: "default", fields: [...] }
795
+
796
+ // card — each field in its own elevated card
797
+ viewModal: {
798
+ title: "Details", variant: "card",
799
+ styles: { containerClass: "grid grid-cols-12 gap-4" },
800
+ fields: [...],
801
+ }
763
802
 
764
- const config = {
765
- tableConfig: {
766
- table_head: [...],
767
- search: { enabled: true },
768
- filter: { enabled: true },
769
- sort: { enabled: true },
770
- customButtons: [
771
- {
772
- key: "import",
773
- label: "Import",
774
- icon: <Upload className="w-4 h-4" />,
775
- color: "primary",
776
- variant: "contained",
777
- onClick: (event, ctx) => {
778
- // ctx has: data, filteredData, sortedData, paginatedData,
779
- // searchTerm, appliedFilters, currentPage, pageSize, totalRecords
780
- console.log("Import clicked", ctx.totalRecords);
781
- },
782
- },
783
- {
784
- key: "refresh",
785
- label: "Refresh",
786
- icon: <RefreshCw className="w-4 h-4" />,
787
- variant: "outlined",
788
- onClick: async () => {
789
- // your custom logic
790
- },
791
- },
792
- ],
793
- customMenuItems: [
794
- {
795
- key: "bulk-actions",
796
- label: "Bulk Action",
797
- onClick: (event, ctx) => {
798
- console.log("bulk action", ctx.filteredData.length);
799
- },
800
- },
801
- {
802
- key: "archive-all",
803
- label: "Archive Filtered",
804
- onClick: async (event, ctx) => {
805
- // run async logic using current filtered records
806
- },
807
- },
808
- ],
809
- },
810
- };
803
+ // split clean property-sheet with dividing lines
804
+ viewModal: { title: "Details", variant: "split", fields: [...] }
811
805
  ```
812
806
 
813
- Supported button properties:
807
+ ---
808
+
809
+ ### 12. Custom View Component
814
810
 
815
- - `key` (optional): unique key
816
- - `label` (required): button text
817
- - `icon` (optional): React node shown before label
818
- - `variant` (optional): `contained`, `outlined`, `text`
819
- - `color` (optional): `primary`, `success`, `error`, `default`
820
- - `className` (optional): custom Tailwind/class string
821
- - `disabled` (optional): disable button
822
- - `show` (optional): set `false` to hide a button
823
- - `onClick` (optional): `(event, context) => void | Promise<void>`
811
+ ```jsx
812
+ const UserProfile = ({ data }) => (
813
+ <div className="space-y-4">
814
+ <div className="flex items-center gap-4">
815
+ <img src={data.avatarUrl} className="w-16 h-16 rounded-full" />
816
+ <div>
817
+ <h2 className="text-xl font-bold">{data.name}</h2>
818
+ <p className="text-gray-500">{data.email}</p>
819
+ </div>
820
+ </div>
821
+ </div>
822
+ );
824
823
 
825
- `customMenuItems` opens in a 3-dot menu button in the same header toolbar. Supported fields:
824
+ modalConfig: {
825
+ viewModal: {
826
+ title: "User Details",
827
+ component: UserProfile, // receives { data } prop
828
+ },
829
+ }
830
+ ```
826
831
 
827
- - `key` (optional): unique key
828
- - `label` (required): menu item text
829
- - `icon` (optional): React node before label
830
- - `className` (optional): custom class string
831
- - `disabled` (optional): disable item
832
- - `show` (optional): set `false` to hide item
833
- - `onClick` (optional): `(event, context) => void | Promise<void>`
832
+ ---
834
833
 
835
- ### Primary Color Customization
834
+ ## CSS Customization
836
835
 
837
- You can override the default primary color used across the UI by defining CSS variables in your global `:root`.
836
+ ### Primary Color Override
838
837
 
839
- Simply add the following variables to your main CSS file (e.g., `root.css`, `global.css`):
838
+ Add these CSS variables to your global stylesheet:
840
839
 
841
840
  ```css
842
841
  :root {
@@ -853,733 +852,75 @@ Simply add the following variables to your main CSS file (e.g., `root.css`, `glo
853
852
  }
854
853
  ```
855
854
 
856
- ### CSS Class Customization
857
-
858
- The following table lists all available CSS classes that can be overridden to customize the UI.
859
-
860
- ---
861
-
862
- #### Class Reference
863
-
864
- | Component | Key | Class Name | Description |
865
- | ------------------ | ------------------ | ------------------------------- | --------------------------- |
866
- | **Crud Page** | `root` | `crud_page` | Main container of CRUD page |
867
- | | `deleteContent` | `crud_page_delete_content` | Delete confirmation content |
868
- | **Button** | `root` | `crud_button` | Default button styling |
869
- | **Chip** | `root` | `crud_chip` | Chip/tag element |
870
- | **Spinner** | `root` | `crud_spinner` | Loading spinner |
871
- | **Modal** | `root` | `crud_modal` | Modal root |
872
- | | `overlay` | `crud_modal_overlay` | Modal overlay |
873
- | | `container` | `crud_modal_container` | Modal container |
874
- | | `header` | `crud_modal_header` | Modal header |
875
- | | `title` | `crud_modal_title` | Modal title |
876
- | | `closeButton` | `crud_modal_close_button` | Close button |
877
- | | `body` | `crud_modal_body` | Modal body |
878
- | | `footer` | `crud_modal_footer` | Modal footer |
879
- | | `actionButton` | `crud_modal_action_button` | Modal action button |
880
- | | `loadingIndicator` | `crud_modal_loading_indicator` | Modal loading state |
881
- | **Table** | `root` | `crud_table` | Table wrapper |
882
- | | `toolbar` | `crud_table_toolbar` | Table toolbar |
883
- | | `searchField` | `crud_table_search_field` | Search field wrapper |
884
- | | `searchInput` | `crud_table_search_input` | Search input |
885
- | | `container` | `crud_table_container` | Table container |
886
- | | `element` | `crud_table_element` | Table element |
887
- | | `head` | `crud_table_head` | Table head |
888
- | | `headRow` | `crud_table_head_row` | Header row |
889
- | | `headCell` | `crud_table_head_cell` | Header cell |
890
- | | `body` | `crud_table_body` | Table body |
891
- | | `row` | `crud_table_row` | Table row |
892
- | | `cell` | `crud_table_cell` | Table cell |
893
- | | `noData` | `crud_table_no_data` | No data state |
894
- | | `actionButton` | `crud_table_action_button` | Row action button |
895
- | | `menu` | `crud_table_menu` | Action menu |
896
- | | `menuItem` | `crud_table_menu_item` | Menu item |
897
- | | `pagination` | `crud_table_pagination` | Pagination wrapper |
898
- | **Table Skeleton** | `root` | `crud_table_skeleton` | Skeleton loader wrapper |
899
- | | `table` | `crud_table_skeleton_table` | Skeleton table |
900
- | **Sort Dropdown** | `root` | `crud_sort_dropdown` | Sort dropdown root |
901
- | | `trigger` | `crud_sort_dropdown_trigger` | Dropdown trigger |
902
- | | `menu` | `crud_sort_dropdown_menu` | Dropdown menu |
903
- | | `item` | `crud_sort_dropdown_item` | Dropdown item |
904
- | **Image Preview** | `root` | `crud_image_preview` | Image preview root |
905
- | | `container` | `crud_image_preview_container` | Image container |
906
- | | `image` | `crud_image_preview_image` | Image element |
907
- | **Filter Drawer** | `overlay` | `crud_filter_overlay` | Drawer overlay |
908
- | | `panel` | `crud_filter_panel` | Drawer panel |
909
- | | `header` | `crud_filter_header` | Drawer header |
910
- | | `body` | `crud_filter_body` | Drawer body |
911
- | | `footer` | `crud_filter_footer` | Drawer footer |
912
- | **Form** | `root` | `crud_form` | Form wrapper |
913
- | | `loading` | `crud_form_loading` | Form loading state |
914
- | **Field** | `wrapper` | `crud_field_wrapper` | Field wrapper |
915
- | | `label` | `crud_field_label` | Field label |
916
- | | `input` | `crud_field_input` | Input field |
917
- | | `error` | `crud_field_error` | Error message |
918
- | **Media Picker** | `image` | `crud_media_image_picker` | Image picker |
919
- | | `multiImage` | `crud_media_multi_image_picker` | Multi-image picker |
920
- | | `audio` | `crud_media_audio_picker` | Audio picker |
921
- | | `video` | `crud_media_video_picker` | Video picker |
922
- | | `dropzone` | `crud_media_dropzone` | File dropzone |
923
- | | `cropModal` | `crud_media_crop_modal` | Crop modal |
924
- | **Details** | `root` | `crud_details` | Details root |
925
- | | `container` | `crud_details_container` | Details container |
926
- | | `row` | `crud_details_row` | Details row |
927
-
928
855
  ---
929
856
 
930
- #### Notes
931
-
932
- - All class names are prefixed with `crud_` to prevent conflicts.
933
- - You can override these classes in your own stylesheet.
934
- - Works with CSS, SCSS, Tailwind (`@apply`), or CSS-in-JS.
935
- - No manual assignment required classes are already applied internally.
936
-
937
- ### CRUD Examples
938
-
939
- #### Example 1: Minimal Client-Side CRUD
940
-
941
- ```js
942
- import Crud from "react-admin-crud-manager";
943
-
944
- const users = [
945
- { id: 1, name: "John Doe", email: "john@example.com", status: "active" },
946
- { id: 2, name: "Jane Smith", email: "jane@example.com", status: "inactive" },
947
- ];
948
-
949
- function App() {
950
- const config = {
951
- title: "Users",
952
- fetchData: async () => ({ data: users, pagination: null }),
953
- isStaticData: true,
954
- tableConfig: {
955
- table_head: [
956
- { key: "id", title: "ID", type: "index" },
957
- { key: "name", title: "Name" },
958
- { key: "email", title: "Email" },
959
- {
960
- key: "status",
961
- title: "Status",
962
- type: "chip",
963
- chipOptions: [
964
- { value: "active", label: "Active", color: "green" },
965
- { value: "inactive", label: "Inactive", color: "red" },
966
- ],
967
- },
968
- ],
969
- search: { enabled: true, searchKeys: ["name", "email"] },
970
- pagination: { enabled: true },
971
- },
972
- modalConfig: {
973
- addModal: {
974
- title: "Add User",
975
- formFields: [
976
- { key: "name", label: "Name", type: "text", required: true },
977
- { key: "email", label: "Email", type: "email", required: true },
978
- {
979
- key: "status",
980
- label: "Status",
981
- type: "select",
982
- options: [
983
- { value: "active", label: "Active" },
984
- { value: "inactive", label: "Inactive" },
985
- ],
986
- },
987
- ],
988
- handleSubmit: async (formData) => ({
989
- newObject: {
990
- ...formData,
991
- id: Math.max(...users.map((u) => u.id)) + 1,
992
- },
993
- }),
994
- },
995
- },
996
- };
997
-
998
- return <Crud config={config} />;
999
- }
1000
-
1001
- export default App;
1002
- ```
1003
-
1004
- #### Example 2: Server-Side CRUD with Advanced Features
1005
-
1006
- ```js
1007
- import Crud from "react-admin-crud-manager";
1008
- import axios from "axios";
1009
- import { PlusIcon, EditIcon, TrashIcon } from "lucide-react";
1010
-
1011
- function App() {
1012
- const api = axios.create({ baseURL: "https://api.example.com" });
1013
-
1014
- const config = {
1015
- title: "Products",
1016
- description: "Manage your products inventory",
1017
- buttonText: "Add Product",
1018
- fetchData: async ({
1019
- search,
1020
- rows_per_page,
1021
- current_page,
1022
- sort_by,
1023
- sort_order,
1024
- category,
1025
- minPrice,
1026
- maxPrice,
1027
- }) => {
1028
- const resp = await api.get("/products", {
1029
- params: {
1030
- q: search,
1031
- limit: rows_per_page,
1032
- page: current_page,
1033
- sort_by,
1034
- sort_order,
1035
- category,
1036
- minPrice,
1037
- maxPrice,
1038
- },
1039
- });
1040
- return {
1041
- data: resp.data.items,
1042
- pagination: {
1043
- current_page: resp.data.page,
1044
- rows_per_page: resp.data.limit,
1045
- total_pages: resp.data.totalPages,
1046
- total_records: resp.data.total,
1047
- },
1048
- };
1049
- },
1050
- tableConfig: {
1051
- table_head: [
1052
- { key: "id", title: "ID", type: "index" },
1053
- {
1054
- key: "image",
1055
- title: "Image",
1056
- type: "avatar",
1057
- imageKey: "image",
1058
- titleKey: "name",
1059
- },
1060
- { key: "name", title: "Product Name" },
1061
- {
1062
- key: "price",
1063
- title: "Price",
1064
- render: (row) => `$${row.price.toFixed(2)}`,
1065
- },
1066
- {
1067
- key: "category",
1068
- title: "Category",
1069
- type: "chip",
1070
- variant: "soft",
1071
- chipOptions: [
1072
- { value: "electronics", label: "Electronics", color: "blue" },
1073
- { value: "clothing", label: "Clothing", color: "purple" },
1074
- ],
1075
- },
1076
- ],
1077
- search: {
1078
- enabled: true,
1079
- useServerSideSearch: true,
1080
- },
1081
- filter: { enabled: true, useServerSideFilters: true },
1082
- pagination: { enabled: true, useServerSidePagination: true },
1083
- sort: { enabled: true, useServerSideSorting: true, autoGenerate: true },
1084
- exportCSV: {
1085
- enabled: true,
1086
- fileName: "products.csv",
1087
- fields: [
1088
- { label: "ID", key: "id" },
1089
- { label: "Name", key: "name" },
1090
- { label: "Price", key: "price" },
1091
- ],
1092
- },
1093
- filterConfig: {
1094
- fields: [
1095
- {
1096
- key: "category",
1097
- label: "Category",
1098
- type: "select",
1099
- options: [
1100
- { value: "electronics", label: "Electronics" },
1101
- { value: "clothing", label: "Clothing" },
1102
- ],
1103
- },
1104
- { key: "minPrice", label: "Min Price", type: "number" },
1105
- { key: "maxPrice", label: "Max Price", type: "number" },
1106
- ],
1107
- },
1108
- },
1109
- modalConfig: {
1110
- addModal: {
1111
- title: "Add Product",
1112
- size: "lg",
1113
- icon: <PlusIcon />,
1114
- formFields: [
1115
- {
1116
- key: "name",
1117
- label: "Product Name",
1118
- type: "text",
1119
- required: true,
1120
- parentClass: "col-span-12",
1121
- },
1122
- {
1123
- key: "price",
1124
- label: "Price",
1125
- type: "number",
1126
- required: true,
1127
- parentClass: "col-span-6",
1128
- },
1129
- {
1130
- key: "category",
1131
- label: "Category",
1132
- type: "select",
1133
- required: true,
1134
- parentClass: "col-span-6",
1135
- options: [
1136
- { value: "electronics", label: "Electronics" },
1137
- { value: "clothing", label: "Clothing" },
1138
- ],
1139
- },
1140
- {
1141
- key: "stock",
1142
- label: "Stock",
1143
- type: "number",
1144
- required: true,
1145
- parentClass: "col-span-6",
1146
- },
1147
- {
1148
- key: "description",
1149
- label: "Description",
1150
- type: "textarea",
1151
- rows: 4,
1152
- parentClass: "col-span-12",
1153
- },
1154
- {
1155
- key: "image",
1156
- label: "Product Image",
1157
- type: "image",
1158
- cropImage: true,
1159
- aspectRatio: 1,
1160
- dragDrop: true,
1161
- parentClass: "col-span-12",
1162
- },
1163
- ],
1164
- handleSubmit: async (formData) => {
1165
- const resp = await api.post("/products", formData);
1166
- return {
1167
- newObject: resp.data,
1168
- message: "Product added successfully!",
1169
- };
1170
- },
1171
- },
1172
- editModal: {
1173
- title: "Edit Product",
1174
- size: "lg",
1175
- icon: <EditIcon />,
1176
- formFields: [
1177
- {
1178
- key: "name",
1179
- label: "Product Name",
1180
- type: "text",
1181
- required: true,
1182
- parentClass: "col-span-12",
1183
- },
1184
- {
1185
- key: "price",
1186
- label: "Price",
1187
- type: "number",
1188
- required: true,
1189
- parentClass: "col-span-6",
1190
- },
1191
- {
1192
- key: "stock",
1193
- label: "Stock",
1194
- type: "number",
1195
- required: true,
1196
- parentClass: "col-span-6",
1197
- },
1198
- ],
1199
- handleSubmit: async (formData, item) => {
1200
- const resp = await api.put(`/products/${item.id}`, formData);
1201
- return { newObject: resp.data, targetObject: item };
1202
- },
1203
- },
1204
- deleteModal: {
1205
- title: "Delete Product",
1206
- icon: <TrashIcon />,
1207
- confirmText: "Are you sure you want to delete this product?",
1208
- referenceKey: "name",
1209
- actionButtons: [
1210
- {
1211
- type: "button",
1212
- label: "Delete",
1213
- color: "error",
1214
- variant: "contained",
1215
- onClick: async (event, item) => {
1216
- event.preventDefault();
1217
- await api.delete(`/products/${item.id}`);
1218
- return { targetObject: item };
1219
- },
1220
- },
1221
- ],
1222
- },
1223
- viewModal: {
1224
- title: "Product Details",
1225
- variant: "card",
1226
- fields: [
1227
- { key: "id", label: "ID" },
1228
- { key: "name", label: "Product Name" },
1229
- { key: "price", label: "Price" },
1230
- { key: "stock", label: "Stock" },
1231
- { key: "category", label: "Category", type: "chip" },
1232
- ],
1233
- },
1234
- },
1235
- };
1236
-
1237
- return <Crud config={config} />;
1238
- }
1239
-
1240
- export default App;
1241
- ```
1242
-
1243
- #### Example 3: Complex Form with Conditional Fields and Validation
1244
-
1245
- ```js
1246
- const config = {
1247
- title: "Advanced User Registration",
1248
- tableConfig: {
1249
- table_head: [
1250
- { key: "id", title: "ID", type: "index" },
1251
- { key: "name", title: "Full Name" },
1252
- { key: "email", title: "Email" },
1253
- { key: "userType", title: "Type", type: "chip" },
1254
- ],
1255
- },
1256
- modalConfig: {
1257
- addModal: {
1258
- title: "Register New User",
1259
- formFields: [
1260
- {
1261
- key: "name",
1262
- label: "Full Name",
1263
- type: "text",
1264
- required: true,
1265
- parentClass: "col-span-12",
1266
- customValidation: (value) => {
1267
- if (value.trim().split(" ").length < 2) {
1268
- return "Please enter your full name";
1269
- }
1270
- return true;
1271
- },
1272
- },
1273
- {
1274
- key: "email",
1275
- label: "Email Address",
1276
- type: "email",
1277
- required: true,
1278
- parentClass: "col-span-12",
1279
- customValidation: (value) => {
1280
- if (!value.includes("@company.com")) {
1281
- return "Must use company email";
1282
- }
1283
- return true;
1284
- },
1285
- },
1286
- {
1287
- key: "phone",
1288
- label: "Phone",
1289
- type: "phone",
1290
- countriesList: true,
1291
- mask: "(99) 99999-9999",
1292
- parentClass: "col-span-12",
1293
- },
1294
- {
1295
- key: "userType",
1296
- label: "User Type",
1297
- type: "select",
1298
- required: true,
1299
- parentClass: "col-span-6",
1300
- options: [
1301
- { value: "admin", label: "Administrator" },
1302
- { value: "user", label: "Regular User" },
1303
- ],
1304
- },
1305
- {
1306
- key: "adminLevel",
1307
- label: "Admin Level",
1308
- type: "select",
1309
- parentClass: "col-span-6",
1310
- renderCondition: (data) => data.userType === "admin",
1311
- options: [
1312
- { value: "superadmin", label: "Super Admin" },
1313
- { value: "moderator", label: "Moderator" },
1314
- ],
1315
- },
1316
- {
1317
- key: "permissions",
1318
- label: "Permissions",
1319
- type: "checkbox",
1320
- parentClass: "col-span-12",
1321
- multiple: true,
1322
- renderCondition: (data) => data.userType === "admin",
1323
- options: [
1324
- { value: "read", label: "Can Read" },
1325
- { value: "write", label: "Can Write" },
1326
- { value: "delete", label: "Can Delete" },
1327
- ],
1328
- },
1329
- ],
1330
- handleSubmit: async (formData) => {
1331
- const resp = await api.post("/users", formData);
1332
- return {
1333
- newObject: resp.data,
1334
- message: `User ${formData.name} created successfully!`,
1335
- };
1336
- },
1337
- },
1338
- },
1339
- };
1340
- ```
1341
-
1342
- ---
1343
-
1344
- ## Best Practices & Common Patterns
1345
-
1346
- ### 1. State Management with Static Data
1347
-
1348
- For client-side CRUD operations, use `isStaticData: true` and manage data updates through modal handlers:
1349
-
1350
- ```js
1351
- const [users, setUsers] = useState(initialUsers);
1352
-
1353
- const config = {
1354
- fetchData: async () => ({ data: users, pagination: null }),
1355
- isStaticData: true,
1356
- modalConfig: {
1357
- addModal: {
1358
- handleSubmit: async (formData) => {
1359
- const newUser = { ...formData, id: Date.now() };
1360
- setUsers([...users, newUser]);
1361
- return { newObject: newUser };
1362
- },
1363
- },
1364
- editModal: {
1365
- handleSubmit: async (formData, item) => {
1366
- const updated = { ...item, ...formData };
1367
- setUsers(users.map((u) => (u.id === item.id ? updated : u)));
1368
- return { newObject: updated, targetObject: item };
1369
- },
1370
- },
1371
- },
1372
- };
1373
- ```
1374
-
1375
- ### 2. Server-Side Operations Best Practices
1376
-
1377
- ```js
1378
- // Always provide pagination info back to the component
1379
- const fetchData = async (params) => {
1380
- const resp = await api.get("/items", { params });
1381
- return {
1382
- data: resp.data.items || [],
1383
- pagination: {
1384
- current_page: resp.data.meta.currentPage,
1385
- rows_per_page: resp.data.meta.perPage,
1386
- total_pages: resp.data.meta.totalPages,
1387
- total_records: resp.data.meta.total,
1388
- },
1389
- };
1390
- };
1391
- ```
1392
-
1393
- ### 3. Error Handling in Modal Submissions
1394
-
1395
- ```js
1396
- const handleSubmit = async (formData) => {
1397
- try {
1398
- const resp = await api.post("/items", formData);
1399
- return {
1400
- newObject: resp.data,
1401
- message: "Item created successfully!", // Optional success message
1402
- };
1403
- } catch (error) {
1404
- // Throw error to trigger snackbar error notification
1405
- throw new Error(error.response?.data?.message || "Failed to create item");
1406
- }
1407
- };
1408
- ```
1409
-
1410
- ### 4. Dynamic Form Fields Based on Data
1411
-
1412
- Combine `renderCondition` and `defaultValue` for dynamic forms:
1413
-
1414
- ```js
1415
- const formFields = [
1416
- {
1417
- key: "type",
1418
- type: "select",
1419
- options: [
1420
- { value: "personal", label: "Personal" },
1421
- { value: "business", label: "Business" },
1422
- ],
1423
- },
1424
- {
1425
- key: "companyName",
1426
- label: "Company Name",
1427
- type: "text",
1428
- renderCondition: (data) => data.type === "business",
1429
- },
1430
- {
1431
- key: "phone",
1432
- type: "phone",
1433
- renderCondition: (data) => data.type === "personal",
1434
- },
1435
- ];
1436
- ```
1437
-
1438
- ### 5. Custom Table Rendering for Complex Data
1439
-
1440
- ```js
1441
- const tableConfig = {
1442
- table_head: [
1443
- {
1444
- key: "user",
1445
- title: "User",
1446
- render: (row) => (
1447
- <div className="flex items-center gap-3">
1448
- <img src={row.avatar} className="w-8 h-8 rounded-full" />
1449
- <div>
1450
- <p className="font-semibold">{row.name}</p>
1451
- <p className="text-xs text-gray-500">{row.email}</p>
1452
- </div>
1453
- </div>
1454
- ),
1455
- },
1456
- {
1457
- key: "metrics",
1458
- title: "Performance",
1459
- render: (row) => (
1460
- <div className="flex gap-2">
1461
- <span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">
1462
- {row.completed}%
1463
- </span>
1464
- </div>
1465
- ),
1466
- },
1467
- ],
1468
- };
1469
- ```
1470
-
1471
- ### 6. Validation Patterns
1472
-
1473
- ```js
1474
- const formFields = [
1475
- {
1476
- key: "email",
1477
- type: "email",
1478
- customValidation: (value) => {
1479
- // Async validation example
1480
- const exists = checkEmailExists(value);
1481
- if (exists) return "Email already in use";
1482
- return true;
1483
- },
1484
- },
1485
- {
1486
- key: "password",
1487
- type: "password",
1488
- customValidation: (value) => {
1489
- if (value.length < 8) return "Password must be at least 8 characters";
1490
- if (!/[A-Z]/.test(value)) return "Must contain uppercase letter";
1491
- if (!/[0-9]/.test(value)) return "Must contain a number";
1492
- return true;
1493
- },
1494
- },
1495
- ];
1496
- ```
1497
-
1498
- ### 7. Handling File Uploads
1499
-
1500
- ```js
1501
- const handleImageUpload = async (file) => {
1502
- const formData = new FormData();
1503
- formData.append("file", file);
1504
- const resp = await api.post("/upload", formData);
1505
- return resp.data.url;
1506
- };
1507
-
1508
- const config = {
1509
- modalConfig: {
1510
- addModal: {
1511
- formFields: [
1512
- {
1513
- key: "image",
1514
- type: "image",
1515
- dragDrop: true,
1516
- cropImage: true,
1517
- aspectRatio: 1,
1518
- },
1519
- ],
1520
- handleSubmit: async (formData) => {
1521
- // Image data includes base64 or file data
1522
- const imageUrl = await handleImageUpload(formData.image);
1523
- const resp = await api.post("/items", {
1524
- ...formData,
1525
- image: imageUrl,
1526
- });
1527
- return { newObject: resp.data };
1528
- },
1529
- },
1530
- },
1531
- };
1532
- ```
1533
-
1534
- ### 8. Conditional Rendering with Row Data
1535
-
1536
- ```js
1537
- const viewModal = {
1538
- fields: [
1539
- {
1540
- key: "status",
1541
- label: "Status",
1542
- type: "chip",
1543
- renderCondition: (data) => data.status !== null,
1544
- chipOptions: [
1545
- { value: "active", label: "Active", color: "green" },
1546
- { value: "suspended", label: "Suspended", color: "red" },
1547
- ],
1548
- },
1549
- {
1550
- key: "suspendReason",
1551
- label: "Suspension Reason",
1552
- renderCondition: (data) => data.status === "suspended", // Only show if suspended
1553
- },
1554
- ],
1555
- };
1556
- ```
1557
-
1558
- ### 9. Multiple Sort Options (Server-Side)
1559
-
1560
- ```js
1561
- const tableConfig = {
1562
- sort: {
1563
- enabled: true,
1564
- useServerSideSorting: true,
1565
- options: [
1566
- {
1567
- value: "recent",
1568
- label: "Most Recent",
1569
- key: "createdAt",
1570
- order: "desc",
1571
- },
1572
- { value: "oldest", label: "Oldest", key: "createdAt", order: "asc" },
1573
- { value: "name-asc", label: "Name (A-Z)", key: "name", order: "asc" },
1574
- { value: "name-desc", label: "Name (Z-A)", key: "name", order: "desc" },
1575
- { value: "popular", label: "Most Popular", key: "views", order: "desc" },
1576
- ],
1577
- onChange: (payload) => {
1578
- console.log(`Sorted by: ${payload.option?.label}`);
1579
- },
1580
- },
1581
- };
1582
- ```
857
+ ### CSS Class Reference
858
+
859
+ All classes are prefixed with `crud_` and can be overridden in any stylesheet (CSS, SCSS, Tailwind `@apply`, or CSS-in-JS).
860
+
861
+ | Component | Class Name | Description |
862
+ | ----------------- | ------------------------------- | ------------------------ |
863
+ | **Page** | `crud_page` | Main page wrapper |
864
+ | | `crud_page_delete_content` | Delete confirmation area |
865
+ | **Button** | `crud_button` | Button element |
866
+ | **Chip** | `crud_chip` | Chip / badge element |
867
+ | **Spinner** | `crud_spinner` | Loading spinner |
868
+ | **Modal** | `crud_modal` | Modal root |
869
+ | | `crud_modal_overlay` | Backdrop overlay |
870
+ | | `crud_modal_container` | Modal box |
871
+ | | `crud_modal_header` | Modal header |
872
+ | | `crud_modal_title` | Modal title text |
873
+ | | `crud_modal_close_button` | Close (×) button |
874
+ | | `crud_modal_body` | Modal body |
875
+ | | `crud_modal_footer` | Modal footer |
876
+ | | `crud_modal_action_button` | Footer action buttons |
877
+ | | `crud_modal_loading_indicator` | Loading state overlay |
878
+ | **Table** | `crud_table` | Table wrapper |
879
+ | | `crud_table_toolbar` | Toolbar (search, filter) |
880
+ | | `crud_table_search_field` | Search field wrapper |
881
+ | | `crud_table_search_input` | Search input |
882
+ | | `crud_table_container` | Table scroll container |
883
+ | | `crud_table_element` | `<table>` element |
884
+ | | `crud_table_head` | `<thead>` |
885
+ | | `crud_table_head_row` | Header `<tr>` |
886
+ | | `crud_table_head_cell` | Header `<th>` |
887
+ | | `crud_table_body` | `<tbody>` |
888
+ | | `crud_table_row` | Body `<tr>` |
889
+ | | `crud_table_cell` | Body `<td>` |
890
+ | | `crud_table_no_data` | Empty state |
891
+ | | `crud_table_action_button` | Row action button |
892
+ | | `crud_table_menu` | Action menu |
893
+ | | `crud_table_menu_item` | Menu item |
894
+ | | `crud_table_pagination` | Pagination controls |
895
+ | **Skeleton** | `crud_table_skeleton` | Skeleton wrapper |
896
+ | | `crud_table_skeleton_table` | Skeleton table |
897
+ | **Sort** | `crud_sort_dropdown` | Sort dropdown root |
898
+ | | `crud_sort_dropdown_trigger` | Dropdown trigger button |
899
+ | | `crud_sort_dropdown_menu` | Dropdown menu |
900
+ | | `crud_sort_dropdown_item` | Dropdown item |
901
+ | **Image Preview** | `crud_image_preview` | Preview root |
902
+ | | `crud_image_preview_container` | Preview container |
903
+ | | `crud_image_preview_image` | Preview `<img>` |
904
+ | **Filter Drawer** | `crud_filter_overlay` | Drawer backdrop |
905
+ | | `crud_filter_panel` | Drawer panel |
906
+ | | `crud_filter_header` | Drawer header |
907
+ | | `crud_filter_body` | Drawer body |
908
+ | | `crud_filter_footer` | Drawer footer |
909
+ | **Form** | `crud_form` | Form wrapper |
910
+ | | `crud_form_loading` | Form loading overlay |
911
+ | **Field** | `crud_field_wrapper` | Field wrapper |
912
+ | | `crud_field_label` | Field label |
913
+ | | `crud_field_input` | Input element |
914
+ | | `crud_field_error` | Error message |
915
+ | **Media Picker** | `crud_media_image_picker` | Single image picker |
916
+ | | `crud_media_multi_image_picker` | Multi-image picker |
917
+ | | `crud_media_audio_picker` | Audio file picker |
918
+ | | `crud_media_video_picker` | Video file picker |
919
+ | | `crud_media_dropzone` | Drag-and-drop zone |
920
+ | | `crud_media_crop_modal` | Image crop modal |
921
+ | **Details** | `crud_details` | Details root |
922
+ | | `crud_details_container` | Details container |
923
+ | | `crud_details_row` | Details row |
1583
924
 
1584
925
  ---
1585
926