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 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, including adding, displaying, and deleting email addresses.
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-9889deefc5@^1.0.3 \
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-9889deefc5@^1.0.3 \
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' // custom url
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 | Type | Required | Options | Default | Description |
1358
- | ----------------- | -------- | -------- | -------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------ |
1359
- | `children` | Node | No | n/a | n/a | Allows inserting custom content within the Navbar. |
1360
- | `logoUrl` | String | No | n/a | Groovinads logo | Accepts a URL to customize the logo image. If empty, show the Groovinads logo. |
1361
- | `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. |
1362
- | `setShow` | Function | No | n/a | n/a | Function to toggle the visibility state between visible and hidden. |
1363
- | `show` | Boolean | No | `true` `false` | n/a | Controls the visibility of the sidebar. If `true`, the sidebar is displayed; if `false`, it is hidden. |
1364
- | `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. |
1365
- | `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. |
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.