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