groovinads-ui 1.9.98 → 1.9.99
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.
- package/README.md +569 -37
- package/dist/index.es.js +14 -1
- package/dist/index.js +14 -1
- package/index.d.ts +132 -3
- package/package.json +12 -19
package/README.md
CHANGED
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
The library includes the following components:
|
|
15
15
|
|
|
16
|
+
- [Context Providers](#context-providers):
|
|
17
|
+
- [ThemeProvider](#themeprovider): For managing application theme (light, dark, system).
|
|
18
|
+
|
|
16
19
|
- [Buttons](#buttons):
|
|
17
20
|
- [Button](#button): For user actions.
|
|
18
21
|
|
|
@@ -25,20 +28,24 @@ The library includes the following components:
|
|
|
25
28
|
|
|
26
29
|
- [Inputs](#inputs):
|
|
27
30
|
- [Checkbox](#checkbox): For multiple option selections.
|
|
31
|
+
- [Dropzone](#dropzone): For drag-and-drop file uploads with progress tracking.
|
|
28
32
|
- [Input](#input): For user data entry.
|
|
29
33
|
- [InputChip](#inputChip): For dynamically managing and displaying keywords.
|
|
30
|
-
- [InputEmail](#inputEmail): For managing email lists
|
|
34
|
+
- [InputEmail](#inputEmail): For managing email lists.
|
|
31
35
|
- [Radio](#radio): For exclusive selections.
|
|
36
|
+
- [Slider](#slider): For selecting values using a slider control.
|
|
32
37
|
- [Switch](#switch): For toggle states.
|
|
33
38
|
- [Textarea](#textarea): For multiline text input.
|
|
34
39
|
|
|
35
40
|
- [Labels](#labels):
|
|
36
41
|
- [Alert](#alert): For displaying alerts.
|
|
42
|
+
- [BlockIcon](#blockicon): For displaying icons with block containers.
|
|
37
43
|
- [Card](#card): For containing and grouping related content.
|
|
38
44
|
- [EditableContent](#editablecontent): For inline editing of text content.
|
|
39
45
|
- [Icon](#icon): For displaying icons.
|
|
40
46
|
- [LoginSource](#loginsource): For show icons of login sources.
|
|
41
47
|
- [PillComponent](#pillcomponent): For displaying information.
|
|
48
|
+
- [ProgressBar](#progressbar): For displaying progress indicators.
|
|
42
49
|
- [Spinner](#spinner): For loading animations.
|
|
43
50
|
- [StatusIcon](#statusicon): For displaying status icons.
|
|
44
51
|
|
|
@@ -166,6 +173,150 @@ yarn add groovinads-ui
|
|
|
166
173
|
|
|
167
174
|
Here are examples of how to use the components included in the Groovinads UI library:
|
|
168
175
|
|
|
176
|
+
## Context Providers
|
|
177
|
+
|
|
178
|
+
### ThemeProvider
|
|
179
|
+
|
|
180
|
+
The `ThemeProvider` manages the application's theme system, supporting three modes: `'light'`, `'dark'`, and `'default'` (system preference).
|
|
181
|
+
|
|
182
|
+
#### Features
|
|
183
|
+
|
|
184
|
+
- **Automatic theme persistence**: Theme preference is saved to browser cookies
|
|
185
|
+
- **Cross-subdomain sharing**: Optional `rootDomain` prop enables theme sharing across subdomains
|
|
186
|
+
- **System integration**: `'default'` mode respects the operating system theme
|
|
187
|
+
- **SSR-safe**: Works correctly with Server-Side Rendering
|
|
188
|
+
- **Auto-sync with DOM**: Automatically sets `data-theme` attribute on `document.documentElement`
|
|
189
|
+
|
|
190
|
+
#### Basic Usage
|
|
191
|
+
|
|
192
|
+
Wrap your application root with the `ThemeProvider`:
|
|
193
|
+
|
|
194
|
+
```jsx
|
|
195
|
+
import React from 'react';
|
|
196
|
+
import ReactDOM from 'react-dom/client';
|
|
197
|
+
import { ThemeProvider } from 'groovinads-ui';
|
|
198
|
+
import App from './App';
|
|
199
|
+
|
|
200
|
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
201
|
+
<React.StrictMode>
|
|
202
|
+
<ThemeProvider>
|
|
203
|
+
<App />
|
|
204
|
+
</ThemeProvider>
|
|
205
|
+
</React.StrictMode>
|
|
206
|
+
);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Cross-Subdomain Theme Sharing
|
|
210
|
+
|
|
211
|
+
By default, theme preferences are automatically shared across all Groovinads subdomains (`.groovinads.com`):
|
|
212
|
+
|
|
213
|
+
```jsx
|
|
214
|
+
import React from 'react';
|
|
215
|
+
import ReactDOM from 'react-dom/client';
|
|
216
|
+
import { ThemeProvider } from 'groovinads-ui';
|
|
217
|
+
import App from './App';
|
|
218
|
+
|
|
219
|
+
// Default behavior - shares theme across all *.groovinads.com subdomains
|
|
220
|
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
221
|
+
<React.StrictMode>
|
|
222
|
+
<ThemeProvider>
|
|
223
|
+
<App />
|
|
224
|
+
</ThemeProvider>
|
|
225
|
+
</React.StrictMode>
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Custom Domain or Disable Cross-Subdomain:**
|
|
230
|
+
|
|
231
|
+
```jsx
|
|
232
|
+
// Custom domain
|
|
233
|
+
<ThemeProvider rootDomain=".example.com">
|
|
234
|
+
<App />
|
|
235
|
+
</ThemeProvider>
|
|
236
|
+
|
|
237
|
+
// Disable cross-subdomain (current domain only)
|
|
238
|
+
<ThemeProvider rootDomain={null}>
|
|
239
|
+
<App />
|
|
240
|
+
</ThemeProvider>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Important Notes:**
|
|
244
|
+
- **Default**: `.groovinads.com` - theme is shared across all subdomains
|
|
245
|
+
- The `rootDomain` should start with a dot (`.`) to work across all subdomains
|
|
246
|
+
- On localhost, the domain attribute is automatically omitted for local development
|
|
247
|
+
- Cookie is stored with a 1-year expiration and `SameSite=Lax` for security
|
|
248
|
+
|
|
249
|
+
#### Using the useTheme Hook
|
|
250
|
+
|
|
251
|
+
Access and control the theme from any component:
|
|
252
|
+
|
|
253
|
+
```jsx
|
|
254
|
+
import React from 'react';
|
|
255
|
+
import { useTheme } from 'groovinads-ui';
|
|
256
|
+
|
|
257
|
+
function ThemeSelector() {
|
|
258
|
+
const { theme, setTheme, isLight, isDark, isDefault } = useTheme();
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div>
|
|
262
|
+
<p>Current theme: {theme}</p>
|
|
263
|
+
<button onClick={() => setTheme('light')}>Light</button>
|
|
264
|
+
<button onClick={() => setTheme('dark')}>Dark</button>
|
|
265
|
+
<button onClick={() => setTheme('default')}>System</button>
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### Props
|
|
272
|
+
|
|
273
|
+
| Prop | Type | Default | Description |
|
|
274
|
+
|------|------|---------|-------------|
|
|
275
|
+
| `children` | `ReactNode` | **required** | Application content to wrap with theme context |
|
|
276
|
+
| `initialTheme` | `'light' \| 'dark' \| 'default'` | `'default'` | Initial theme value (overrides cookie on mount) |
|
|
277
|
+
| `rootDomain` | `string` | `'.groovinads.com'` | Root domain for cross-subdomain cookies. Theme is automatically shared across all subdomains. Set to `null` to disable cross-subdomain sharing. |
|
|
278
|
+
|
|
279
|
+
#### useTheme Return Value
|
|
280
|
+
|
|
281
|
+
| Property | Type | Description |
|
|
282
|
+
|----------|------|-------------|
|
|
283
|
+
| `theme` | `'light' \| 'dark' \| 'default'` | Current active theme |
|
|
284
|
+
| `setTheme` | `(theme: ThemeMode) => void` | Function to change theme |
|
|
285
|
+
| `isLight` | `boolean` | `true` if theme is `'light'` |
|
|
286
|
+
| `isDark` | `boolean` | `true` if theme is `'dark'` |
|
|
287
|
+
| `isDefault` | `boolean` | `true` if theme is `'default'` |
|
|
288
|
+
|
|
289
|
+
#### Implementation Notes
|
|
290
|
+
|
|
291
|
+
- **Cookie Storage**: Theme preference is stored in a cookie named `theme_pref`
|
|
292
|
+
- When theme is `'default'`: The `data-theme` attribute is removed from the HTML element and the cookie is deleted
|
|
293
|
+
- When theme is `'light'` or `'dark'`: The `data-theme` attribute is set on `document.documentElement` and the preference is saved to a cookie with 1-year expiration
|
|
294
|
+
- **Cross-Subdomain**: When `rootDomain` is provided, the cookie is set with the `domain` attribute to share across subdomains
|
|
295
|
+
- **Localhost Handling**: On localhost, the domain attribute is automatically omitted to work in local development
|
|
296
|
+
- **SSR-Safe**: The component checks for `window` existence before accessing `document.cookie` or `document`
|
|
297
|
+
- **Security**: Cookies are set with `SameSite=Lax` and `path=/` for security and accessibility
|
|
298
|
+
|
|
299
|
+
#### Integration with Navbar
|
|
300
|
+
|
|
301
|
+
The `Navbar` component with `showUserMenu={true}` automatically includes a theme selector in the user dropdown menu when wrapped with `ThemeProvider`.
|
|
302
|
+
|
|
303
|
+
```jsx
|
|
304
|
+
import React from 'react';
|
|
305
|
+
import { ThemeProvider, Navbar } from 'groovinads-ui';
|
|
306
|
+
|
|
307
|
+
function App() {
|
|
308
|
+
return (
|
|
309
|
+
<ThemeProvider>
|
|
310
|
+
<Navbar
|
|
311
|
+
showUserMenu={true}
|
|
312
|
+
userData={userData}
|
|
313
|
+
/>
|
|
314
|
+
{/* Rest of your app */}
|
|
315
|
+
</ThemeProvider>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
169
320
|
## Buttons
|
|
170
321
|
|
|
171
322
|
### Button
|
|
@@ -829,6 +980,199 @@ export default ExampleInputChip;
|
|
|
829
980
|
| `textError` | String | No | n/a | 'You must enter a valid email address' | Allows adding custom error text when entering an invalid email address. |
|
|
830
981
|
| `titleList` | String | No | n/a | 'Added emails' | Allows adding a custom text that will be shown as the title of the email list. |
|
|
831
982
|
|
|
983
|
+
### Dropzone
|
|
984
|
+
|
|
985
|
+
A generic drag-and-drop file upload component with progress tracking, validation, and full control via props. Perfect for image, video, and document uploads.
|
|
986
|
+
|
|
987
|
+
```jsx
|
|
988
|
+
import React, { useState } from 'react';
|
|
989
|
+
import { Dropzone } from 'groovinads-ui';
|
|
990
|
+
|
|
991
|
+
const ExampleDropzone = () => {
|
|
992
|
+
const [files, setFiles] = useState([]);
|
|
993
|
+
const [showError, setShowError] = useState(false);
|
|
994
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
995
|
+
|
|
996
|
+
// Your upload function that returns a Promise
|
|
997
|
+
const handleUpload = async (file) => {
|
|
998
|
+
const formData = new FormData();
|
|
999
|
+
formData.append('file', file);
|
|
1000
|
+
|
|
1001
|
+
const response = await fetch('/api/upload', {
|
|
1002
|
+
method: 'POST',
|
|
1003
|
+
body: formData,
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
const result = await response.json();
|
|
1007
|
+
|
|
1008
|
+
// Return file metadata
|
|
1009
|
+
return {
|
|
1010
|
+
id: result.id,
|
|
1011
|
+
url: result.url,
|
|
1012
|
+
thumbnail: result.thumbnail,
|
|
1013
|
+
name: file.name,
|
|
1014
|
+
size: file.size,
|
|
1015
|
+
type: file.type,
|
|
1016
|
+
width: result.width,
|
|
1017
|
+
height: result.height,
|
|
1018
|
+
};
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
const handleSuccess = (result) => {
|
|
1022
|
+
// Add to files list
|
|
1023
|
+
setFiles((prev) => [...prev, result]);
|
|
1024
|
+
console.log('File uploaded:', result);
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
const handleRemove = (file) => {
|
|
1028
|
+
// Remove from files list
|
|
1029
|
+
setFiles((prev) => prev.filter((f) => f.id !== file.id));
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
const handleError = (error, file) => {
|
|
1033
|
+
console.error('Upload error:', error);
|
|
1034
|
+
setShowError(true);
|
|
1035
|
+
setErrorMessage('Upload failed. Please try again.');
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
const handleValidate = () => {
|
|
1039
|
+
if (files.length === 0) {
|
|
1040
|
+
setShowError(true);
|
|
1041
|
+
setErrorMessage('You must upload at least one file');
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
return (
|
|
1046
|
+
<Dropzone
|
|
1047
|
+
// Data
|
|
1048
|
+
files={files}
|
|
1049
|
+
|
|
1050
|
+
// Events
|
|
1051
|
+
onUpload={handleUpload}
|
|
1052
|
+
onSuccess={handleSuccess}
|
|
1053
|
+
onRemove={handleRemove}
|
|
1054
|
+
onError={handleError}
|
|
1055
|
+
|
|
1056
|
+
// Configuration
|
|
1057
|
+
accept={{ 'image/*': ['.png', '.jpg', '.jpeg'] }}
|
|
1058
|
+
maxSize={20 * 1024 * 1024} // 20MB
|
|
1059
|
+
multiple={true}
|
|
1060
|
+
|
|
1061
|
+
// Validation
|
|
1062
|
+
showError={showError}
|
|
1063
|
+
requiredText={errorMessage}
|
|
1064
|
+
|
|
1065
|
+
// UI Texts
|
|
1066
|
+
title="Drag and drop your image here"
|
|
1067
|
+
subtitle="PNG, JPG up to 20MB"
|
|
1068
|
+
buttonText="Browse file…"
|
|
1069
|
+
/>
|
|
1070
|
+
);
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
export default ExampleDropzone;
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
#### Props
|
|
1077
|
+
|
|
1078
|
+
| Property | Type | Required | Default | Description |
|
|
1079
|
+
| --------------------- | -------- | -------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
|
1080
|
+
| **Data** | | | | |
|
|
1081
|
+
| `files` | Array | No | `[]` | Array of uploaded files. Each file should have `id`, `url`, `name`, `size`, etc. |
|
|
1082
|
+
| **Events** | | | | |
|
|
1083
|
+
| `onError` | Function | No | n/a | Called on upload or validation errors. Receives `(error, file)`. |
|
|
1084
|
+
| `onRemove` | Function | No | n/a | Called when user clicks remove button. Receives the file object. |
|
|
1085
|
+
| `onSuccess` | Function | No | n/a | Called when upload succeeds. Receives the result from `onUpload`. |
|
|
1086
|
+
| `onUpload` | Function | **Yes** | n/a | Async function that handles file upload. Must return a Promise with file metadata `{ id, url, name, size, ... }`. |
|
|
1087
|
+
| **Configuration** | | | | |
|
|
1088
|
+
| `accept` | Object | No | n/a | Accepted file types in react-dropzone format: `{ 'image/*': ['.png', '.jpg'] }`. |
|
|
1089
|
+
| `disabled` | Boolean | No | `false` | Disable the dropzone. |
|
|
1090
|
+
| `maxSize` | Number | No | `20971520` (20MB) | Maximum file size in bytes. |
|
|
1091
|
+
| `multiple` | Boolean | No | `true` | Allow multiple file uploads. |
|
|
1092
|
+
| `validator` | Function | No | n/a | Custom file validator. Return `{ code, message }` for errors, `null` for valid files. |
|
|
1093
|
+
| **Validation** | | | | |
|
|
1094
|
+
| `requiredText` | String | No | n/a | Error message displayed when `showError` is `true`. Used via the `data-error` attribute. |
|
|
1095
|
+
| `showError` | Boolean | No | `false` | If `true`, displays the dropzone in error state with `not-validated` class. Controlled externally for validation. |
|
|
1096
|
+
| **UI Texts** | | | | |
|
|
1097
|
+
| `buttonText` | String | No | `'Browse file…'` | Browse button text. |
|
|
1098
|
+
| `orLabel` | String | No | `'or'` | Text for the separator between dropzone and browse button. |
|
|
1099
|
+
| `subtitle` | String | No | n/a | Subtitle text (e.g., file size limits). |
|
|
1100
|
+
| `title` | String | No | `'Drag and drop your files here'` | Main dropzone title. |
|
|
1101
|
+
| `uploadingText` | String | No | `'Uploading…'` | Text shown during upload. |
|
|
1102
|
+
| **UI Customization** | | | | |
|
|
1103
|
+
| `className` | String | No | `''` | Additional CSS classes. |
|
|
1104
|
+
| `icon` | String | No | `'cloud-arrow-up'` | FontAwesome icon name. |
|
|
1105
|
+
| `iconScale` | Number | No | `2` | Icon size scale. Options: `0.7`, `1`, `2`, `3`, `4`. |
|
|
1106
|
+
| **Format Validation** | | | | |
|
|
1107
|
+
| `formatLabel` | Function | No | n/a | Function to generate format label for display. Receives file, returns string. |
|
|
1108
|
+
| `formatValidator` | Function | No | n/a | Function to validate file format. Receives file, returns boolean. |
|
|
1109
|
+
|
|
1110
|
+
#### File Object Structure
|
|
1111
|
+
|
|
1112
|
+
The component normalizes file objects to ensure consistency. Your `onUpload` function should return:
|
|
1113
|
+
|
|
1114
|
+
```javascript
|
|
1115
|
+
{
|
|
1116
|
+
id: string | number, // Unique identifier
|
|
1117
|
+
url: string, // File URL (required)
|
|
1118
|
+
thumbnail?: string, // Thumbnail URL (optional)
|
|
1119
|
+
name: string, // File name
|
|
1120
|
+
size: number, // File size in bytes
|
|
1121
|
+
type?: string, // MIME type
|
|
1122
|
+
width?: number, // Image/video width
|
|
1123
|
+
height?: number, // Image/video height
|
|
1124
|
+
duration?: number, // Video duration
|
|
1125
|
+
}
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
The component also supports legacy formats with fields like `media`, `file_description`, `file_name`, `file_size`, etc.
|
|
1129
|
+
|
|
1130
|
+
#### Examples
|
|
1131
|
+
|
|
1132
|
+
**Image Upload**
|
|
1133
|
+
```jsx
|
|
1134
|
+
<Dropzone
|
|
1135
|
+
files={images}
|
|
1136
|
+
onUpload={uploadImage}
|
|
1137
|
+
onSuccess={(result) => setImages([...images, result])}
|
|
1138
|
+
onRemove={(file) => setImages(images.filter(f => f.id !== file.id))}
|
|
1139
|
+
accept={{ 'image/*': ['.png', '.jpg', '.jpeg'] }}
|
|
1140
|
+
maxSize={20 * 1024 * 1024}
|
|
1141
|
+
title="Upload your creative"
|
|
1142
|
+
subtitle="PNG, JPG up to 20MB"
|
|
1143
|
+
/>
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
**Video Upload (Single File)**
|
|
1147
|
+
```jsx
|
|
1148
|
+
<Dropzone
|
|
1149
|
+
files={video ? [video] : []}
|
|
1150
|
+
onUpload={uploadVideo}
|
|
1151
|
+
onSuccess={setVideo}
|
|
1152
|
+
onRemove={() => setVideo(null)}
|
|
1153
|
+
accept={{ 'video/*': ['.mp4', '.mov'] }}
|
|
1154
|
+
maxSize={100 * 1024 * 1024}
|
|
1155
|
+
multiple={false}
|
|
1156
|
+
icon="video"
|
|
1157
|
+
title="Upload your video"
|
|
1158
|
+
subtitle="MP4, MOV up to 100MB"
|
|
1159
|
+
/>
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
**Document Upload with Format Validation**
|
|
1163
|
+
```jsx
|
|
1164
|
+
<Dropzone
|
|
1165
|
+
files={documents}
|
|
1166
|
+
onUpload={uploadDocument}
|
|
1167
|
+
onSuccess={(result) => setDocuments([...documents, result])}
|
|
1168
|
+
accept={{ 'application/pdf': ['.pdf'] }}
|
|
1169
|
+
formatValidator={(file) => file.size < 10 * 1024 * 1024}
|
|
1170
|
+
formatLabel={(file) => `${(file.size / 1024).toFixed(0)} KB`}
|
|
1171
|
+
title="Upload PDF document"
|
|
1172
|
+
subtitle="PDF only, max 10MB"
|
|
1173
|
+
/>
|
|
1174
|
+
```
|
|
1175
|
+
|
|
832
1176
|
### Radio
|
|
833
1177
|
|
|
834
1178
|
```jsx
|
|
@@ -1307,37 +1651,144 @@ import { ModalComponent } from 'groovinads-ui';
|
|
|
1307
1651
|
|
|
1308
1652
|
## Navigation
|
|
1309
1653
|
|
|
1310
|
-
###
|
|
1654
|
+
### AccordionComponent
|
|
1655
|
+
|
|
1656
|
+
A flexible accordion component built on top of react-bootstrap Accordion. Headers render as `<div>` elements (not buttons) to allow nesting interactive elements like buttons without HTML validation conflicts.
|
|
1657
|
+
|
|
1311
1658
|
```jsx
|
|
1312
1659
|
import React from 'react';
|
|
1313
|
-
import {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
<AccordionComponent defaultActiveKey="0">
|
|
1317
|
-
<
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
</
|
|
1323
|
-
<
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
This is the second item's accordion body.
|
|
1327
|
-
</Accordion.Body>
|
|
1328
|
-
</Accordion.Item>
|
|
1660
|
+
import { AccordionComponent, AccordionItem } from 'groovinads-ui';
|
|
1661
|
+
|
|
1662
|
+
// Basic usage
|
|
1663
|
+
<AccordionComponent defaultActiveKey="0" iconPosition="start">
|
|
1664
|
+
<AccordionItem eventKey="0" header="Section 1">
|
|
1665
|
+
This is the first item's accordion body.
|
|
1666
|
+
</AccordionItem>
|
|
1667
|
+
<AccordionItem eventKey="1" header="Section 2">
|
|
1668
|
+
This is the second item's accordion body.
|
|
1669
|
+
</AccordionItem>
|
|
1670
|
+
<AccordionItem eventKey="2" header="Section 3">
|
|
1671
|
+
This is the third item's accordion body.
|
|
1672
|
+
</AccordionItem>
|
|
1329
1673
|
</AccordionComponent>
|
|
1330
1674
|
```
|
|
1331
1675
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1676
|
+
**Advanced usage with complex headers:**
|
|
1677
|
+
|
|
1678
|
+
```jsx
|
|
1679
|
+
import React from 'react';
|
|
1680
|
+
import { AccordionComponent, AccordionItem } from 'groovinads-ui';
|
|
1681
|
+
import { Button } from 'groovinads-ui';
|
|
1682
|
+
|
|
1683
|
+
const AdvancedAccordion = () => {
|
|
1684
|
+
const handleEdit = (e, id) => {
|
|
1685
|
+
e.stopPropagation(); // Prevent accordion toggle
|
|
1686
|
+
console.log('Edit clicked for:', id);
|
|
1687
|
+
};
|
|
1688
|
+
|
|
1689
|
+
return (
|
|
1690
|
+
<AccordionComponent defaultActiveKey="0" iconPosition="start">
|
|
1691
|
+
<AccordionItem
|
|
1692
|
+
eventKey="0"
|
|
1693
|
+
header={
|
|
1694
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
|
1695
|
+
<div>
|
|
1696
|
+
<strong>Campaign Name</strong>
|
|
1697
|
+
<div style={{ fontSize: '12px', color: '#888' }}>
|
|
1698
|
+
Budget: $50,000 | Status: Active
|
|
1699
|
+
</div>
|
|
1700
|
+
</div>
|
|
1701
|
+
<Button size="sm" onClick={(e) => handleEdit(e, 1)}>
|
|
1702
|
+
Edit
|
|
1703
|
+
</Button>
|
|
1704
|
+
</div>
|
|
1705
|
+
}
|
|
1706
|
+
>
|
|
1707
|
+
<p>Campaign details go here...</p>
|
|
1708
|
+
</AccordionItem>
|
|
1709
|
+
<AccordionItem
|
|
1710
|
+
eventKey="1"
|
|
1711
|
+
header={
|
|
1712
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
|
1713
|
+
<div>
|
|
1714
|
+
<strong>Another Campaign</strong>
|
|
1715
|
+
<div style={{ fontSize: '12px', color: '#888' }}>
|
|
1716
|
+
Budget: $35,000 | Status: Scheduled
|
|
1717
|
+
</div>
|
|
1718
|
+
</div>
|
|
1719
|
+
<Button size="sm" onClick={(e) => handleEdit(e, 2)}>
|
|
1720
|
+
Edit
|
|
1721
|
+
</Button>
|
|
1722
|
+
</div>
|
|
1723
|
+
}
|
|
1724
|
+
>
|
|
1725
|
+
<p>More campaign details...</p>
|
|
1726
|
+
</AccordionItem>
|
|
1727
|
+
</AccordionComponent>
|
|
1728
|
+
);
|
|
1729
|
+
};
|
|
1730
|
+
```
|
|
1731
|
+
|
|
1732
|
+
**Props for AccordionComponent:**
|
|
1733
|
+
|
|
1734
|
+
| Property | Type | Required | Options | Default | Description |
|
|
1735
|
+
| ----------------- | ----------------- | -------- | ------------- | ------- | ---------------------------------------------------------------------------------------------------------- |
|
|
1736
|
+
| `activeKey` | String / String[] | No | n/a | n/a | Currently active key(s) for controlled mode. Use with state to control which items are open. String for single item, array for multiple when alwaysOpen is true. |
|
|
1737
|
+
| `alwaysOpen` | Boolean | No | `true` `false`| `false` | If true, allows multiple accordion items to stay open at the same time. If false, opening one closes others. |
|
|
1738
|
+
| `children` | Node | Yes | n/a | n/a | AccordionItem components that define the accordion sections. |
|
|
1739
|
+
| `className` | String | No | n/a | `''` | Additional CSS classes to apply to the accordion container. |
|
|
1740
|
+
| `defaultActiveKey`| String / String[] | No | n/a | n/a | Default active key(s) for uncontrolled mode. Use string for single item, array for multiple when alwaysOpen is true. |
|
|
1741
|
+
| `iconPosition` | String | No | `start` `end` | `start` | Position of the caret icon. `start` places it before the content (left), `end` places it after (right). |
|
|
1742
|
+
| `onSelect` | Function | No | n/a | n/a | Callback fired when the active item changes. Receives `(eventKey: string \| string[] \| null) => void`. Use with `activeKey` for controlled mode. |
|
|
1743
|
+
| `size` | String | No | `sm` `md` `lg`| `md` | Size variant for the accordion. `sm` for compact, `md` for default, `lg` for larger spacing. |
|
|
1744
|
+
|
|
1745
|
+
**Props for AccordionItem:**
|
|
1746
|
+
|
|
1747
|
+
| Property | Type | Required | Description |
|
|
1748
|
+
| ---------- | ------ | -------- | ---------------------------------------------------------------------------- |
|
|
1749
|
+
| `eventKey` | String | Yes | Unique identifier for this accordion item. Used to control which item is open. |
|
|
1750
|
+
| `children` | Node | Yes | Content to display in the accordion body when the item is expanded. |
|
|
1751
|
+
| `header` | Node | Yes | Content to display in the accordion header. Can be text, JSX, or complex components. |
|
|
1752
|
+
|
|
1753
|
+
**Controlled Mode Example:**
|
|
1754
|
+
|
|
1755
|
+
```jsx
|
|
1756
|
+
import React, { useState } from 'react';
|
|
1757
|
+
import { AccordionComponent, AccordionItem } from 'groovinads-ui';
|
|
1758
|
+
|
|
1759
|
+
const ControlledAccordion = () => {
|
|
1760
|
+
const [activeKey, setActiveKey] = useState('0');
|
|
1761
|
+
|
|
1762
|
+
const handleSelect = (eventKey) => {
|
|
1763
|
+
console.log('Selected:', eventKey);
|
|
1764
|
+
setActiveKey(eventKey);
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
return (
|
|
1768
|
+
<AccordionComponent activeKey={activeKey} onSelect={handleSelect}>
|
|
1769
|
+
<AccordionItem eventKey="0" header="Section 1">
|
|
1770
|
+
Content 1
|
|
1771
|
+
</AccordionItem>
|
|
1772
|
+
<AccordionItem eventKey="1" header="Section 2">
|
|
1773
|
+
Content 2
|
|
1774
|
+
</AccordionItem>
|
|
1775
|
+
<AccordionItem eventKey="2" header="Section 3">
|
|
1776
|
+
Content 3
|
|
1777
|
+
</AccordionItem>
|
|
1778
|
+
</AccordionComponent>
|
|
1779
|
+
);
|
|
1780
|
+
};
|
|
1781
|
+
```
|
|
1782
|
+
|
|
1783
|
+
**Key Features:**
|
|
1784
|
+
|
|
1785
|
+
- **Headers as divs**: Headers render as `<div>` elements instead of `<button>` elements, allowing you to nest interactive elements (buttons, links, inputs) inside headers without HTML validation errors or click event conflicts.
|
|
1786
|
+
- **Flexible headers**: Headers accept any React node, so you can create complex, multi-line headers with custom layouts, icons, badges, and buttons.
|
|
1787
|
+
- **Icon positioning**: Caret icon can be positioned at the start (left) or end (right) of the header.
|
|
1788
|
+
- **Multiple open items**: Use `alwaysOpen={true}` to allow multiple sections to be expanded simultaneously.
|
|
1789
|
+
- **Controlled/Uncontrolled**: Works in both controlled (with `activeKey` and `onSelect` props) and uncontrolled (`defaultActiveKey`) modes. In controlled mode, use `activeKey` with `onSelect` to manage state externally.
|
|
1790
|
+
|
|
1791
|
+
**Note:** When nesting buttons or other interactive elements in headers, make sure to call `e.stopPropagation()` in their click handlers to prevent the accordion from toggling when clicking the nested elements.
|
|
1341
1792
|
|
|
1342
1793
|
### Aside
|
|
1343
1794
|
```jsx
|
|
@@ -1368,15 +1819,39 @@ import { Navbar } from 'groovinads-ui';
|
|
|
1368
1819
|
const NavbarComponent = () => {
|
|
1369
1820
|
const [show, setShow] = useState(false);
|
|
1370
1821
|
|
|
1822
|
+
// Mock data - en una app real, esto vendría de tu AuthContext o similar
|
|
1823
|
+
const applications = [
|
|
1824
|
+
{
|
|
1825
|
+
id_application: 1,
|
|
1826
|
+
application_name: 'Dashboard',
|
|
1827
|
+
application_url: '/dashboard',
|
|
1828
|
+
application_icon: 'grid-2',
|
|
1829
|
+
favorite: true,
|
|
1830
|
+
},
|
|
1831
|
+
// ... más aplicaciones
|
|
1832
|
+
];
|
|
1833
|
+
|
|
1834
|
+
const userData = {
|
|
1835
|
+
auth_data: {
|
|
1836
|
+
oauth_username: 'John Doe',
|
|
1837
|
+
oauth_picture: 'https://example.com/avatar.jpg',
|
|
1838
|
+
email: 'john.doe@example.com',
|
|
1839
|
+
oauth_name: 'Google',
|
|
1840
|
+
},
|
|
1841
|
+
};
|
|
1842
|
+
|
|
1371
1843
|
return (
|
|
1372
1844
|
<div>
|
|
1373
1845
|
<Button onClick={() => setShow((prev) => !prev)}>Toggle Sidebar</Button>
|
|
1374
1846
|
<Navbar
|
|
1375
|
-
logoUrl='https://ui.groovinads.com/assets/groovinads-logo.svg'
|
|
1847
|
+
logoUrl='https://ui.groovinads.com/assets/groovinads-logo.svg'
|
|
1376
1848
|
showDeckMenu={true}
|
|
1377
1849
|
showUserMenu={true}
|
|
1378
1850
|
show={show}
|
|
1379
1851
|
setShow={setShow}
|
|
1852
|
+
// Nuevas props: pasar datos para evitar fetches internos
|
|
1853
|
+
applications={applications}
|
|
1854
|
+
userData={userData}
|
|
1380
1855
|
>
|
|
1381
1856
|
<div>Custom Content</div>
|
|
1382
1857
|
</Navbar>
|
|
@@ -1387,15 +1862,72 @@ const NavbarComponent = () => {
|
|
|
1387
1862
|
export default NavbarComponent;
|
|
1388
1863
|
```
|
|
1389
1864
|
|
|
1390
|
-
| Property
|
|
1391
|
-
|
|
|
1392
|
-
| `
|
|
1393
|
-
| `
|
|
1394
|
-
| `
|
|
1395
|
-
| `
|
|
1396
|
-
| `
|
|
1397
|
-
| `
|
|
1398
|
-
| `
|
|
1865
|
+
| Property | Type | Required | Options | Default | Description |
|
|
1866
|
+
| -------------------- | -------- | -------- | -------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1867
|
+
| `applications` | Array | No | n/a | n/a | Array of application objects for the deck menu. When provided, prevents internal API calls. Each object should have: `id_application`, `application_name`, `application_url`, `application_icon`, `favorite`. The component automatically maps `application_name` → `app_name` and `application_icon` → `app_icon_sm` internally. |
|
|
1868
|
+
| `applicationsLoading`| Boolean | No | `true` `false` | `false` | Loading state for applications data. Shows skeleton loader when `true`. |
|
|
1869
|
+
| `children` | Node | No | n/a | n/a | Allows inserting custom content within the Navbar. |
|
|
1870
|
+
| `favoritesTitle` | String | No | n/a | `'Favorites'` | Title for the favorites section in the deck menu. Allows customization of the favorites label. |
|
|
1871
|
+
| `logoUrl` | String | No | n/a | Groovinads logo | Accepts a URL to customize the logo image. If empty, show the Groovinads logo. |
|
|
1872
|
+
| `logoUrlMobile` | String | No | n/a | Groovinads logo | Accepts a URL for the mobile version of the logo. If not provided, uses `logoUrl` on mobile devices. |
|
|
1873
|
+
| `onApplicationClick` | Function | No | n/a | n/a | Callback fired when user clicks an application. Receives the application object. If not provided, opens application in new tab (default behavior). |
|
|
1874
|
+
| `onFavoriteToggle` | Function | No | n/a | n/a | Callback fired when user toggles favorite status. Receives `(applicationId, isFavorite)`. Must return a Promise. If not provided, makes internal API call (default behavior). |
|
|
1875
|
+
| `setShow` | Function | No | n/a | n/a | Function to toggle the visibility state of the sidebar. When provided (along with `show`), the sidebar toggle button is rendered. When omitted, the button is hidden. |
|
|
1876
|
+
| `show` | Boolean | No | `true` `false` | n/a | Controls the visibility of the sidebar. When provided (along with `setShow`), the sidebar toggle button is rendered. When omitted, the button is hidden. |
|
|
1877
|
+
| `showDeckMenu` | Boolean | No | `true` `false` | `false` | Controls the visibility of the deck menu in the navbar. If `true`, it is displayed; if `false`, it is hidden. Applications open in a new tab when clicked. |
|
|
1878
|
+
| `showUserMenu` | Boolean | No | `true` `false` | `false` | Controls the visibility of the user menu. If `true`, the user menu is shown; if `false`, it is hidden. |
|
|
1879
|
+
| `userData` | Object | No | n/a | n/a | User data object for the user menu. When provided, prevents internal API calls. Should contain `auth_data` with: `oauth_username`, `oauth_picture`, `email`, `oauth_name`. |
|
|
1880
|
+
| `userDataLoading` | Boolean | No | `true` `false` | `false` | Loading state for user data. Shows skeleton loader when `true`. |
|
|
1881
|
+
|
|
1882
|
+
**⚠️ Important Note on Data Props:**
|
|
1883
|
+
- When `showDeckMenu={true}` is used **without** providing `applications` prop, the component will make an internal API call to `/v2/applications`
|
|
1884
|
+
- When `showUserMenu={true}` is used **without** providing `userData` prop, the component will make an internal API call to `/v2/auth/status`
|
|
1885
|
+
- To avoid duplicate API calls in your app, always provide `applications` and `userData` props from your app's state management (e.g., AuthContext, Redux, etc.)
|
|
1886
|
+
|
|
1887
|
+
**💡 Event Handlers for Custom Navigation:**
|
|
1888
|
+
|
|
1889
|
+
If you need to integrate the Navbar with your application's routing (e.g., react-router) or handle favorites with your own state management, use the event handler props:
|
|
1890
|
+
|
|
1891
|
+
```jsx
|
|
1892
|
+
import { useNavigate } from 'react-router-dom';
|
|
1893
|
+
|
|
1894
|
+
const NavbarComponent = () => {
|
|
1895
|
+
const navigate = useNavigate();
|
|
1896
|
+
const [applications, setApplications] = useState([...]);
|
|
1897
|
+
|
|
1898
|
+
// Custom navigation handler
|
|
1899
|
+
const handleApplicationClick = (app) => {
|
|
1900
|
+
// Navigate using react-router instead of opening new tab
|
|
1901
|
+
navigate(app.application_url);
|
|
1902
|
+
};
|
|
1903
|
+
|
|
1904
|
+
// Custom favorite toggle handler
|
|
1905
|
+
const handleFavoriteToggle = async (appId, isFavorite) => {
|
|
1906
|
+
// Update in your API
|
|
1907
|
+
await updateFavoriteAPI(appId, isFavorite);
|
|
1908
|
+
|
|
1909
|
+
// Update local state
|
|
1910
|
+
setApplications(prev =>
|
|
1911
|
+
prev.map(app =>
|
|
1912
|
+
app.id_application === appId
|
|
1913
|
+
? { ...app, favorite: isFavorite }
|
|
1914
|
+
: app
|
|
1915
|
+
)
|
|
1916
|
+
);
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1919
|
+
return (
|
|
1920
|
+
<Navbar
|
|
1921
|
+
showDeckMenu={true}
|
|
1922
|
+
applications={applications}
|
|
1923
|
+
onApplicationClick={handleApplicationClick}
|
|
1924
|
+
onFavoriteToggle={handleFavoriteToggle}
|
|
1925
|
+
/>
|
|
1926
|
+
);
|
|
1927
|
+
};
|
|
1928
|
+
```
|
|
1929
|
+
|
|
1930
|
+
**Note:** If you don't provide these handlers, the component uses default behavior (opens in new tab for clicks, makes internal API call for favorites).
|
|
1399
1931
|
|
|
1400
1932
|
### Pagination
|
|
1401
1933
|
|