anima-ds-nucleus 1.0.8 → 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anima-ds-nucleus",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Anima Design System - A comprehensive React component library",
5
5
  "author": "Nucleus Labs <ipvasallo@nucleus.com.ar>",
6
6
  "license": "UNLICENSED",
@@ -2,6 +2,7 @@ import { useState } from 'react';
2
2
  import { Icon } from '../../Atoms/Icon/Icon';
3
3
  import { Avatar } from '../../Atoms/Avatar/Avatar';
4
4
  import { Typography } from '../../Atoms/Typography/Typography';
5
+ import { SidebarCoreMobile } from '../Sidebar/SidebarCore';
5
6
 
6
7
  export const HeaderCore = ({
7
8
  searchPlaceholder = 'Buscar empleados, reportes, configuraciones...',
@@ -11,10 +12,21 @@ export const HeaderCore = ({
11
12
  onSearch,
12
13
  onNotificationClick,
13
14
  onUserClick,
15
+ sidebarSections,
16
+ sidebarActiveItem,
17
+ onSidebarItemClick,
18
+ sidebarItemBadges,
19
+ sidebarCompanyName,
20
+ sidebarCompanyLogo,
21
+ sidebarFooterText,
22
+ sidebarFooterCollapsedContent,
23
+ sidebarCoreContainerStyle,
24
+ sidebarCoreTextStyle,
14
25
  className = '',
15
26
  ...props
16
27
  }) => {
17
28
  const [searchValue, setSearchValue] = useState('');
29
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
18
30
 
19
31
  const handleSearchChange = (e) => {
20
32
  const value = e.target.value;
@@ -217,6 +229,7 @@ export const HeaderCore = ({
217
229
  <div className="flex items-center space-x-2">
218
230
  {/* Botón menú hamburguesa */}
219
231
  <button
232
+ onClick={() => setIsSidebarOpen(true)}
220
233
  className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
221
234
  aria-label="Menú"
222
235
  >
@@ -340,6 +353,31 @@ export const HeaderCore = ({
340
353
  </div>
341
354
  </div>
342
355
  </div>
356
+
357
+ {/* SidebarCoreMobile - Solo visible en móvil cuando está abierto */}
358
+ {isSidebarOpen && (
359
+ <div className="header-core-mobile-sidebar-wrapper">
360
+ <SidebarCoreMobile
361
+ sections={sidebarSections}
362
+ activeItem={sidebarActiveItem}
363
+ onItemClick={(itemId) => {
364
+ if (onSidebarItemClick) {
365
+ onSidebarItemClick(itemId);
366
+ }
367
+ setIsSidebarOpen(false);
368
+ }}
369
+ itemBadges={sidebarItemBadges}
370
+ companyName={sidebarCompanyName}
371
+ companyLogo={sidebarCompanyLogo}
372
+ footerText={sidebarFooterText}
373
+ footerCollapsedContent={sidebarFooterCollapsedContent}
374
+ coreContainerStyle={sidebarCoreContainerStyle}
375
+ coreTextStyle={sidebarCoreTextStyle}
376
+ isOpen={isSidebarOpen}
377
+ onClose={() => setIsSidebarOpen(false)}
378
+ />
379
+ </div>
380
+ )}
343
381
  </header>
344
382
  );
345
383
  };
@@ -2,6 +2,7 @@ import { useState } from 'react';
2
2
  import { Icon } from '../../Atoms/Icon/Icon';
3
3
  import { Avatar } from '../../Atoms/Avatar/Avatar';
4
4
  import { Typography } from '../../Atoms/Typography/Typography';
5
+ import { SidebarPointMobile } from '../Sidebar/SidebarPoint';
5
6
 
6
7
  export const HeaderPoint = ({
7
8
  searchPlaceholder = 'Buscar empleados, reportes, configuraciones...',
@@ -11,10 +12,17 @@ export const HeaderPoint = ({
11
12
  onSearch,
12
13
  onNotificationClick,
13
14
  onUserClick,
15
+ sidebarSections,
16
+ sidebarActiveItem,
17
+ onSidebarItemClick,
18
+ sidebarItemBadges,
19
+ sidebarCompanyName,
20
+ sidebarCompanyLogo,
14
21
  className = '',
15
22
  ...props
16
23
  }) => {
17
24
  const [searchValue, setSearchValue] = useState('');
25
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
18
26
 
19
27
  const handleSearchChange = (e) => {
20
28
  const value = e.target.value;
@@ -65,6 +73,12 @@ export const HeaderPoint = ({
65
73
  background-color: transparent !important;
66
74
  background: transparent !important;
67
75
  border-bottom: none !important;
76
+ width: 100% !important;
77
+ max-width: 100% !important;
78
+ margin: 0 !important;
79
+ padding: 0 !important;
80
+ left: 0 !important;
81
+ right: 0 !important;
68
82
  }
69
83
  header.header-point-mobile[style*="background"],
70
84
  .header-point-mobile[style*="background"],
@@ -72,9 +86,18 @@ export const HeaderPoint = ({
72
86
  background-color: transparent !important;
73
87
  background: transparent !important;
74
88
  }
89
+ header.header-point-mobile > div {
90
+ width: 100% !important;
91
+ max-width: 100% !important;
92
+ padding-left: 0 !important;
93
+ padding-right: 0 !important;
94
+ margin: 0 !important;
95
+ }
75
96
  .header-mobile-layout {
76
97
  display: block !important;
77
98
  position: relative !important;
99
+ width: 100% !important;
100
+ max-width: 100% !important;
78
101
  }
79
102
  .header-mobile-top-row {
80
103
  display: flex !important;
@@ -83,6 +106,9 @@ export const HeaderPoint = ({
83
106
  padding: 24px 24px 12px 24px !important;
84
107
  min-height: 64px !important;
85
108
  height: 64px !important;
109
+ width: 100% !important;
110
+ max-width: 100% !important;
111
+ box-sizing: border-box !important;
86
112
  }
87
113
  .header-mobile-menu-button,
88
114
  .header-mobile-notification-button {
@@ -118,11 +144,12 @@ export const HeaderPoint = ({
118
144
  .header-mobile-search-row {
119
145
  position: relative;
120
146
  z-index: 2 !important;
121
- }
122
- .header-mobile-search-row {
123
147
  display: block;
124
148
  padding: 12px 24px 0 24px;
125
149
  margin-top: -8px;
150
+ width: 100% !important;
151
+ max-width: 100% !important;
152
+ box-sizing: border-box !important;
126
153
  }
127
154
  .header-desktop-layout {
128
155
  display: none !important;
@@ -283,6 +310,7 @@ export const HeaderPoint = ({
283
310
  {/* Lado izquierdo: Menú hamburguesa */}
284
311
  <div className="flex items-center">
285
312
  <button
313
+ onClick={() => setIsSidebarOpen(true)}
286
314
  className="p-2 hover:bg-gray-100 rounded-lg transition-colors header-mobile-menu-button"
287
315
  style={{ cursor: 'pointer', padding: '8px' }}
288
316
  aria-label="Menú"
@@ -402,6 +430,27 @@ export const HeaderPoint = ({
402
430
  </div>
403
431
  </div>
404
432
  </div>
433
+
434
+ {/* SidebarPointMobile - Solo visible en móvil cuando está abierto */}
435
+ {isSidebarOpen && (
436
+ <div className="header-mobile-sidebar-wrapper">
437
+ <SidebarPointMobile
438
+ sections={sidebarSections}
439
+ activeItem={sidebarActiveItem}
440
+ onItemClick={(itemId) => {
441
+ if (onSidebarItemClick) {
442
+ onSidebarItemClick(itemId);
443
+ }
444
+ setIsSidebarOpen(false);
445
+ }}
446
+ itemBadges={sidebarItemBadges}
447
+ companyName={sidebarCompanyName}
448
+ companyLogo={sidebarCompanyLogo}
449
+ isOpen={isSidebarOpen}
450
+ onClose={() => setIsSidebarOpen(false)}
451
+ />
452
+ </div>
453
+ )}
405
454
  </header>
406
455
  );
407
456
  };
@@ -34,13 +34,23 @@ const SidebarCoreMobile = ({
34
34
  footerCollapsedContent, // Contenido a mostrar en el footer cuando está colapsado (puede ser string, ReactNode, o objeto con icon y text)
35
35
  coreContainerStyle, // Estilos personalizados para el contenedor de LogoHexa y "Core"
36
36
  coreTextStyle, // Estilos personalizados para el texto "Core"
37
+ isOpen: controlledIsOpen,
38
+ onClose: controlledOnClose,
37
39
  className = '',
38
40
  ...props
39
41
  }) => {
40
- const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
42
+ const [internalIsOpen, setInternalIsOpen] = useState(false);
43
+
44
+ // Si se proporciona control externo, usarlo; si no, usar estado interno
45
+ const isMobileMenuOpen = controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;
46
+ const setIsMobileMenuOpen = controlledOnClose ? controlledOnClose : setInternalIsOpen;
41
47
 
42
48
  const toggleMobileMenu = () => {
43
- setIsMobileMenuOpen(!isMobileMenuOpen);
49
+ if (controlledOnClose) {
50
+ controlledOnClose();
51
+ } else {
52
+ setInternalIsOpen(!internalIsOpen);
53
+ }
44
54
  };
45
55
 
46
56
  // Generar footerCollapsedContent automáticamente si no se proporciona pero sí hay footerText
@@ -79,8 +89,8 @@ const SidebarCoreMobile = ({
79
89
  transition: transform 0.3s ease-in-out;
80
90
  }
81
91
  `}</style>
82
- {/* Botón hamburguesa - solo visible cuando el menú está cerrado */}
83
- {!isMobileMenuOpen && (
92
+ {/* Botón hamburguesa - solo visible cuando el menú está cerrado y no hay control externo */}
93
+ {!isMobileMenuOpen && controlledIsOpen === undefined && (
84
94
  <button
85
95
  onClick={toggleMobileMenu}
86
96
  className="fixed top-4 left-4 z-50 p-2 bg-white border border-gray-200 rounded-lg shadow-md
@@ -96,12 +106,21 @@ const SidebarCoreMobile = ({
96
106
  </button>
97
107
  )}
98
108
  {/* Overlay para cerrar el menú */}
99
- {isMobileMenuOpen && (
100
- <div
101
- className="fixed inset-0 bg-black bg-opacity-50 z-40"
102
- onClick={toggleMobileMenu}
103
- />
104
- )}
109
+ <div
110
+ className="fixed inset-0 bg-black bg-opacity-50 z-40"
111
+ style={{
112
+ opacity: isMobileMenuOpen ? 1 : 0,
113
+ pointerEvents: isMobileMenuOpen ? 'auto' : 'none',
114
+ transition: 'opacity 0.3s ease-in-out'
115
+ }}
116
+ onClick={() => {
117
+ if (controlledOnClose) {
118
+ controlledOnClose();
119
+ } else {
120
+ toggleMobileMenu();
121
+ }
122
+ }}
123
+ />
105
124
  <aside
106
125
  className={`bg-white border-r border-gray-200 sidebar-core-mobile ${className}`}
107
126
  {...props}
@@ -145,7 +164,13 @@ const SidebarCoreMobile = ({
145
164
 
146
165
  {/* Lado derecho: Botón X */}
147
166
  <button
148
- onClick={toggleMobileMenu}
167
+ onClick={() => {
168
+ if (controlledOnClose) {
169
+ controlledOnClose();
170
+ } else {
171
+ toggleMobileMenu();
172
+ }
173
+ }}
149
174
  className="p-1 hover:bg-gray-100 rounded transition-colors flex-shrink-0"
150
175
  aria-label="Cerrar menú"
151
176
  >
@@ -164,7 +189,11 @@ const SidebarCoreMobile = ({
164
189
  <button
165
190
  onClick={() => {
166
191
  onItemClick && onItemClick('inicio');
167
- toggleMobileMenu();
192
+ if (controlledOnClose) {
193
+ controlledOnClose();
194
+ } else {
195
+ toggleMobileMenu();
196
+ }
168
197
  }}
169
198
  className={`w-full flex items-center cursor-pointer px-4 justify-between py-2.5 rounded-lg transition-all duration-200 ${
170
199
  activeItem === 'inicio'
@@ -232,7 +261,11 @@ const SidebarCoreMobile = ({
232
261
  <button
233
262
  onClick={() => {
234
263
  onItemClick && onItemClick(item.id);
235
- toggleMobileMenu();
264
+ if (controlledOnClose) {
265
+ controlledOnClose();
266
+ } else {
267
+ toggleMobileMenu();
268
+ }
236
269
  }}
237
270
  className={`w-full flex items-center cursor-pointer px-4 justify-between py-2.5 rounded-lg transition-all duration-200 ${
238
271
  activeItem === item.id
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useState, useEffect } from 'react';
2
2
  import { Icon } from '../../Atoms/Icon/Icon';
3
3
  import { Typography } from '../../Atoms/Typography/Typography';
4
4
  import { LogoHexa } from '../../Atoms/LogoHexa/LogoHexa';
@@ -331,13 +331,44 @@ const SidebarPointMobile = ({
331
331
  companyLogo,
332
332
  onCompanyClick,
333
333
  itemBadges = {},
334
+ isOpen: controlledIsOpen,
335
+ onClose: controlledOnClose,
334
336
  className = '',
335
337
  ...props
336
338
  }) => {
337
- const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
339
+ const [internalIsOpen, setInternalIsOpen] = useState(false);
340
+ const [shouldAnimate, setShouldAnimate] = useState(false);
341
+
342
+ // Si se proporciona control externo, usarlo; si no, usar estado interno
343
+ const isMobileMenuOpen = controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;
344
+ const setIsMobileMenuOpen = controlledOnClose ? controlledOnClose : setInternalIsOpen;
345
+
346
+ // Efecto para manejar la animación cuando se controla externamente
347
+ useEffect(() => {
348
+ if (controlledIsOpen !== undefined) {
349
+ if (controlledIsOpen) {
350
+ // Cuando se abre, primero montamos sin animación, luego activamos la animación
351
+ setShouldAnimate(false);
352
+ // Pequeño delay para asegurar que el DOM esté listo antes de animar
353
+ const timer = setTimeout(() => {
354
+ setShouldAnimate(true);
355
+ }, 10);
356
+ return () => clearTimeout(timer);
357
+ } else {
358
+ // Cuando se cierra, desactivamos la animación inmediatamente
359
+ setShouldAnimate(false);
360
+ }
361
+ } else {
362
+ setShouldAnimate(internalIsOpen);
363
+ }
364
+ }, [controlledIsOpen, internalIsOpen]);
338
365
 
339
366
  const toggleMobileMenu = () => {
340
- setIsMobileMenuOpen(!isMobileMenuOpen);
367
+ if (controlledOnClose) {
368
+ controlledOnClose();
369
+ } else {
370
+ setInternalIsOpen(!internalIsOpen);
371
+ }
341
372
  };
342
373
 
343
374
  // Formatear fecha de hoy
@@ -360,11 +391,20 @@ const SidebarPointMobile = ({
360
391
  top: 0;
361
392
  height: 100vh;
362
393
  z-index: 50;
363
- transition: transform 0.3s ease-in-out;
394
+ transition: ${shouldAnimate ? 'transform 0.3s ease-in-out' : 'none'};
395
+ }
396
+ .sidebar-point-mobile-overlay {
397
+ position: fixed;
398
+ inset: 0;
399
+ background-color: rgba(0, 0, 0, 0.5);
400
+ z-index: 40;
401
+ transition: opacity 0.3s ease-in-out;
402
+ opacity: ${isMobileMenuOpen ? '1' : '0'};
403
+ pointer-events: ${isMobileMenuOpen ? 'auto' : 'none'};
364
404
  }
365
405
  `}</style>
366
- {/* Botón hamburguesa - solo visible cuando el menú está cerrado */}
367
- {!isMobileMenuOpen && (
406
+ {/* Botón hamburguesa - solo visible cuando el menú está cerrado y no hay control externo */}
407
+ {!isMobileMenuOpen && controlledIsOpen === undefined && (
368
408
  <button
369
409
  onClick={toggleMobileMenu}
370
410
  className="fixed top-4 left-4 z-50 p-2 bg-white border border-gray-200 rounded-lg shadow-md
@@ -380,19 +420,23 @@ const SidebarPointMobile = ({
380
420
  </button>
381
421
  )}
382
422
  {/* Overlay para cerrar el menú */}
383
- {isMobileMenuOpen && (
384
- <div
385
- className="fixed inset-0 bg-black bg-opacity-50 z-40"
386
- onClick={toggleMobileMenu}
387
- />
388
- )}
423
+ <div
424
+ className="sidebar-point-mobile-overlay"
425
+ onClick={() => {
426
+ if (controlledOnClose) {
427
+ controlledOnClose();
428
+ } else {
429
+ toggleMobileMenu();
430
+ }
431
+ }}
432
+ />
389
433
  <aside
390
434
  className={`bg-gray-100 border-r border-gray-200 sidebar-point-mobile ${className}`}
391
435
  {...props}
392
436
  >
393
437
  <nav className="h-full flex flex-col">
394
- {/* Header verde con "Point" y botón X */}
395
- <div className="bg-brand px-4 py-4 flex items-center justify-between" style={{ backgroundColor: '#56B676' }}>
438
+ {/* Header MAIN con "Point" y botón X */}
439
+ <div className="px-4 py-4 flex items-center justify-between" style={{ backgroundColor: 'var(--color-main)' }}>
396
440
  <Typography
397
441
  variant="h5"
398
442
  className="color-white font-bold"
@@ -400,8 +444,15 @@ const SidebarPointMobile = ({
400
444
  Point
401
445
  </Typography>
402
446
  <button
403
- onClick={toggleMobileMenu}
447
+ onClick={() => {
448
+ if (controlledOnClose) {
449
+ controlledOnClose();
450
+ } else {
451
+ toggleMobileMenu();
452
+ }
453
+ }}
404
454
  className="p-1 hover:bg-opacity-80 rounded transition-colors"
455
+ style={{ cursor: 'pointer' }}
405
456
  aria-label="Cerrar menú"
406
457
  >
407
458
  <Icon
@@ -421,7 +472,7 @@ const SidebarPointMobile = ({
421
472
  {capitalizedDate}
422
473
  </Typography>
423
474
  </div>
424
- {/* Logo de empresa (si se proporciona) */}
475
+ {/* Logo de empresa (solo si se proporciona) */}
425
476
  {companyLogo && (
426
477
  <div className="bg-gray-100 px-4 py-6 flex justify-center border-b border-gray-200">
427
478
  <img