groovinads-ui 1.9.97 → 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 +655 -13
- package/dist/index.es.js +14 -1
- package/dist/index.js +14 -1
- package/index.d.ts +141 -0
- package/package.json +18 -20
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
|
|
|
@@ -46,6 +53,7 @@ The library includes the following components:
|
|
|
46
53
|
- [ModalComponent](#modalcomponent): For displaying modals.
|
|
47
54
|
|
|
48
55
|
- [Navigation](#navigation):
|
|
56
|
+
- [Accordion](#accordion): For collapsible content sections.
|
|
49
57
|
- [Aside](#aside): For displaying aside panels.
|
|
50
58
|
- [Navbar](#navbar): For top navigation bars.
|
|
51
59
|
- [Pagination](#pagination): For pagination of tables.
|
|
@@ -127,7 +135,7 @@ This library requires several peer dependencies. Install them first:
|
|
|
127
135
|
|
|
128
136
|
```bash
|
|
129
137
|
# For npm users
|
|
130
|
-
npm install @awesome.me/kit-
|
|
138
|
+
npm install @awesome.me/kit-YOUR_CODE_HERE@^1.0.3 \
|
|
131
139
|
@fortawesome/fontawesome-svg-core@^7.1.0 \
|
|
132
140
|
@fortawesome/free-brands-svg-icons@^7.1.0 \
|
|
133
141
|
@fortawesome/duotone-regular-svg-icons@^7.1.0 \
|
|
@@ -140,7 +148,7 @@ npm install @awesome.me/kit-9889deefc5@^1.0.3 \
|
|
|
140
148
|
react-responsive@^10.0.0
|
|
141
149
|
|
|
142
150
|
# For Yarn users
|
|
143
|
-
yarn add @awesome.me/kit-
|
|
151
|
+
yarn add @awesome.me/kit-YOUR_CODE_HERE@^1.0.3 \
|
|
144
152
|
@fortawesome/fontawesome-svg-core@^7.1.0 \
|
|
145
153
|
@fortawesome/free-brands-svg-icons@^7.1.0 \
|
|
146
154
|
@fortawesome/duotone-regular-svg-icons@^7.1.0 \
|
|
@@ -165,6 +173,150 @@ yarn add groovinads-ui
|
|
|
165
173
|
|
|
166
174
|
Here are examples of how to use the components included in the Groovinads UI library:
|
|
167
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
|
+
|
|
168
320
|
## Buttons
|
|
169
321
|
|
|
170
322
|
### Button
|
|
@@ -828,6 +980,199 @@ export default ExampleInputChip;
|
|
|
828
980
|
| `textError` | String | No | n/a | 'You must enter a valid email address' | Allows adding custom error text when entering an invalid email address. |
|
|
829
981
|
| `titleList` | String | No | n/a | 'Added emails' | Allows adding a custom text that will be shown as the title of the email list. |
|
|
830
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
|
+
|
|
831
1176
|
### Radio
|
|
832
1177
|
|
|
833
1178
|
```jsx
|
|
@@ -1306,6 +1651,145 @@ import { ModalComponent } from 'groovinads-ui';
|
|
|
1306
1651
|
|
|
1307
1652
|
## Navigation
|
|
1308
1653
|
|
|
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
|
+
|
|
1658
|
+
```jsx
|
|
1659
|
+
import React from 'react';
|
|
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>
|
|
1673
|
+
</AccordionComponent>
|
|
1674
|
+
```
|
|
1675
|
+
|
|
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.
|
|
1792
|
+
|
|
1309
1793
|
### Aside
|
|
1310
1794
|
```jsx
|
|
1311
1795
|
import React from 'react';
|
|
@@ -1335,15 +1819,39 @@ import { Navbar } from 'groovinads-ui';
|
|
|
1335
1819
|
const NavbarComponent = () => {
|
|
1336
1820
|
const [show, setShow] = useState(false);
|
|
1337
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
|
+
|
|
1338
1843
|
return (
|
|
1339
1844
|
<div>
|
|
1340
1845
|
<Button onClick={() => setShow((prev) => !prev)}>Toggle Sidebar</Button>
|
|
1341
1846
|
<Navbar
|
|
1342
|
-
logoUrl='https://ui.groovinads.com/assets/groovinads-logo.svg'
|
|
1847
|
+
logoUrl='https://ui.groovinads.com/assets/groovinads-logo.svg'
|
|
1343
1848
|
showDeckMenu={true}
|
|
1344
1849
|
showUserMenu={true}
|
|
1345
1850
|
show={show}
|
|
1346
1851
|
setShow={setShow}
|
|
1852
|
+
// Nuevas props: pasar datos para evitar fetches internos
|
|
1853
|
+
applications={applications}
|
|
1854
|
+
userData={userData}
|
|
1347
1855
|
>
|
|
1348
1856
|
<div>Custom Content</div>
|
|
1349
1857
|
</Navbar>
|
|
@@ -1354,15 +1862,72 @@ const NavbarComponent = () => {
|
|
|
1354
1862
|
export default NavbarComponent;
|
|
1355
1863
|
```
|
|
1356
1864
|
|
|
1357
|
-
| Property
|
|
1358
|
-
|
|
|
1359
|
-
| `
|
|
1360
|
-
| `
|
|
1361
|
-
| `
|
|
1362
|
-
| `
|
|
1363
|
-
| `
|
|
1364
|
-
| `
|
|
1365
|
-
| `
|
|
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).
|
|
1366
1931
|
|
|
1367
1932
|
### Pagination
|
|
1368
1933
|
|
|
@@ -1865,6 +2430,83 @@ export default MyToastExamples;
|
|
|
1865
2430
|
| `status` | String | No | `in-progress` `error` | `in-progress` | Define the current state of the task. If `in-progress`, shows the ongoing progress, reflected in the progress bar. If `error`, indicates that the upload or processing has failed. |
|
|
1866
2431
|
| `variant` | String | Yes | `upload` `processing` | n/a | Define the type of process being performed. If `upload`, displays a progress bar and a loading status indicator. If `upload`, displays a progress bar and a loading status indicator. |
|
|
1867
2432
|
|
|
2433
|
+
# Testing
|
|
2434
|
+
|
|
2435
|
+
This library includes an automated testing system that validates all components using **Vitest** and **Playwright**.
|
|
2436
|
+
|
|
2437
|
+
## Test System Overview
|
|
2438
|
+
|
|
2439
|
+
The testing infrastructure is built on:
|
|
2440
|
+
- **Vitest**: Fast unit test framework powered by Vite
|
|
2441
|
+
- **Playwright**: Browser automation for real component testing
|
|
2442
|
+
- **Storybook Integration**: All Storybook stories are automatically converted to tests
|
|
2443
|
+
|
|
2444
|
+
## Available Test Commands
|
|
2445
|
+
|
|
2446
|
+
```bash
|
|
2447
|
+
# Run all tests once (for CI/CD)
|
|
2448
|
+
yarn test
|
|
2449
|
+
|
|
2450
|
+
# Run tests in watch mode (for development)
|
|
2451
|
+
yarn test:watch
|
|
2452
|
+
|
|
2453
|
+
# Open Vitest UI (visual test interface)
|
|
2454
|
+
yarn test:ui
|
|
2455
|
+
|
|
2456
|
+
# Run tests with coverage report
|
|
2457
|
+
yarn test:coverage
|
|
2458
|
+
```
|
|
2459
|
+
|
|
2460
|
+
## What Gets Tested
|
|
2461
|
+
|
|
2462
|
+
The test system automatically validates:
|
|
2463
|
+
- ✅ **Component Rendering**: All 36+ components render without errors
|
|
2464
|
+
- ✅ **Props Validation**: Each component handles props correctly
|
|
2465
|
+
- ✅ **Variants**: All component variants (sizes, colors, states) work properly
|
|
2466
|
+
- ✅ **Interactive Elements**: Buttons, dropdowns, inputs respond to user actions
|
|
2467
|
+
- ✅ **Storybook Stories**: All 322+ stories are tested automatically
|
|
2468
|
+
|
|
2469
|
+
## Test Results
|
|
2470
|
+
|
|
2471
|
+
Current test coverage:
|
|
2472
|
+
- **36 test files** (one per component category)
|
|
2473
|
+
- **322 individual tests** (one per Storybook story)
|
|
2474
|
+
- **100% pass rate** on component rendering
|
|
2475
|
+
|
|
2476
|
+
## Running Tests Locally
|
|
2477
|
+
|
|
2478
|
+
### Prerequisites
|
|
2479
|
+
- Node.js v20 (use `nvm use 20`)
|
|
2480
|
+
- All dependencies installed (`yarn install`)
|
|
2481
|
+
- Playwright browsers installed (runs automatically on first test)
|
|
2482
|
+
|
|
2483
|
+
### Example
|
|
2484
|
+
|
|
2485
|
+
```bash
|
|
2486
|
+
# Activate Node 20
|
|
2487
|
+
nvm use 20
|
|
2488
|
+
|
|
2489
|
+
# Run tests
|
|
2490
|
+
yarn test
|
|
2491
|
+
```
|
|
2492
|
+
|
|
2493
|
+
### Test Output Example
|
|
2494
|
+
|
|
2495
|
+
```
|
|
2496
|
+
Test Files 36 passed (36)
|
|
2497
|
+
Tests 322 passed (322)
|
|
2498
|
+
Duration 7.57s
|
|
2499
|
+
```
|
|
2500
|
+
|
|
2501
|
+
## Continuous Integration
|
|
2502
|
+
|
|
2503
|
+
Tests run automatically on:
|
|
2504
|
+
- Every commit
|
|
2505
|
+
- Pull requests
|
|
2506
|
+
- Before releases
|
|
2507
|
+
|
|
2508
|
+
This ensures component library stability and prevents regressions.
|
|
2509
|
+
|
|
1868
2510
|
# Customization
|
|
1869
2511
|
|
|
1870
2512
|
Currently, the components are not customizable.
|