anima-ds-nucleus 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/anima-ds-nucleus.css +1 -1
  2. package/dist/anima-ds.cjs.js +124 -57
  3. package/dist/anima-ds.esm.js +6591 -5067
  4. package/package.json +10 -2
  5. package/src/assets/nucleus-logo.svg +3 -0
  6. package/src/components/Atoms/LogoHexa/LogoHexa.jsx +34 -0
  7. package/src/components/Atoms/LogoHexa/LogoHexa.stories.jsx +36 -0
  8. package/src/components/Atoms/Typography/Typography.jsx +4 -0
  9. package/src/components/Atoms/Typography/Typography.stories.jsx +37 -1
  10. package/src/components/DataDisplay/Card/Card.jsx +117 -24
  11. package/src/components/DataDisplay/Card/Card.stories.jsx +119 -35
  12. package/src/components/DataDisplay/Card/CardError.jsx +142 -0
  13. package/src/components/DataDisplay/Card/CardSkeleton.jsx +96 -0
  14. package/src/components/DataDisplay/Card/CardTituloCorto.jsx +62 -0
  15. package/src/components/DataDisplay/Card/CardTituloCortoMasEstado.jsx +80 -0
  16. package/src/components/DataDisplay/Card/CardTituloLargo.jsx +62 -0
  17. package/src/components/DataDisplay/Card/CardTituloLargoMasEstado.jsx +77 -0
  18. package/src/components/DataDisplay/Card/CardVacia.jsx +111 -0
  19. package/src/components/Layout/Header/HeaderConBuscador.stories.jsx +8 -0
  20. package/src/components/Layout/Header/HeaderCore.jsx +314 -85
  21. package/src/components/Layout/Header/HeaderCore.stories.jsx +25 -21
  22. package/src/components/Layout/Header/HeaderGeneral.jsx +92 -0
  23. package/src/components/Layout/Header/HeaderGeneral.stories.jsx +64 -0
  24. package/src/components/Layout/Header/HeaderPoint.jsx +120 -0
  25. package/src/components/Layout/Header/HeaderPoint.stories.jsx +110 -0
  26. package/src/components/Layout/NavPoint/NavPoint.jsx +64 -0
  27. package/src/components/Layout/NavPoint/NavPoint.stories.jsx +52 -0
  28. package/src/components/Layout/Sidebar/SidebarCore.jsx +524 -91
  29. package/src/components/Layout/Sidebar/SidebarCore.stories.jsx +76 -1
  30. package/src/components/Layout/Sidebar/SidebarPoint.jsx +264 -59
  31. package/src/components/Layout/Sidebar/SidebarPoint.stories.jsx +69 -0
  32. package/src/index.js +12 -3
  33. package/src/style.css +25 -0
  34. package/src/components/Layout/Header/Header.jsx +0 -50
  35. package/src/components/Layout/Header/Header.stories.jsx +0 -36
  36. package/src/components/Layout/Sidebar/Sidebar.jsx +0 -230
  37. package/src/components/Layout/Sidebar/Sidebar.stories.jsx +0 -90
@@ -1,6 +1,7 @@
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
+ import { LogoHexa } from '../../Atoms/LogoHexa/LogoHexa';
4
5
 
5
6
  // Mock data con 2 títulos y 2 secciones
6
7
  const MOCK_SECTIONS = [
@@ -20,6 +21,295 @@ const MOCK_SECTIONS = [
20
21
  },
21
22
  ];
22
23
 
24
+ // Componente SidebarCore Mobile
25
+ const SidebarCoreMobile = ({
26
+ sections = MOCK_SECTIONS,
27
+ activeItem,
28
+ onItemClick,
29
+ companyName = 'HEXA Core',
30
+ companyLogo,
31
+ onCompanyClick,
32
+ itemBadges = {},
33
+ footerText, // Texto del footer: puede ser string "v1.0 | Powered by Nucleus" o objeto { version: "v1.0", poweredBy: "Powered by", brand: "Nucleus" }
34
+ footerCollapsedContent, // Contenido a mostrar en el footer cuando está colapsado (puede ser string, ReactNode, o objeto con icon y text)
35
+ className = '',
36
+ ...props
37
+ }) => {
38
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
39
+
40
+ const toggleMobileMenu = () => {
41
+ setIsMobileMenuOpen(!isMobileMenuOpen);
42
+ };
43
+
44
+ // Generar footerCollapsedContent automáticamente si no se proporciona pero sí hay footerText
45
+ const getFooterCollapsedContent = () => {
46
+ if (footerCollapsedContent) {
47
+ return footerCollapsedContent;
48
+ }
49
+
50
+ // Si hay footerText pero no footerCollapsedContent, generar uno automáticamente
51
+ if (footerText && !footerCollapsedContent) {
52
+ if (typeof footerText === 'string') {
53
+ // Extraer la versión (parte antes del |) o usar todo el texto si no hay |
54
+ const parts = footerText.split('|');
55
+ return parts[0].trim() || footerText.trim();
56
+ } else if (typeof footerText === 'object' && footerText.version) {
57
+ return footerText.version;
58
+ }
59
+ }
60
+
61
+ return null;
62
+ };
63
+
64
+ const effectiveFooterCollapsedContent = getFooterCollapsedContent();
65
+
66
+ return (
67
+ <>
68
+ <style>{`
69
+ .sidebar-core-mobile {
70
+ width: 100%;
71
+ transform: ${isMobileMenuOpen ? 'translateX(0)' : 'translateX(-100%)'};
72
+ position: fixed;
73
+ left: 0;
74
+ top: 0;
75
+ height: 100vh;
76
+ z-index: 50;
77
+ transition: transform 0.3s ease-in-out;
78
+ }
79
+ `}</style>
80
+ {/* Botón hamburguesa - solo visible cuando el menú está cerrado */}
81
+ {!isMobileMenuOpen && (
82
+ <button
83
+ onClick={toggleMobileMenu}
84
+ className="fixed top-4 left-4 z-50 p-2 bg-white border border-gray-200 rounded-lg shadow-md
85
+ hover:bg-gray-50 transition-colors"
86
+ aria-label="Toggle menu"
87
+ >
88
+ <Icon
89
+ name="Bars3Icon"
90
+ variant="24-outline"
91
+ size={24}
92
+ className="color-gray-700"
93
+ />
94
+ </button>
95
+ )}
96
+ {/* Overlay para cerrar el menú */}
97
+ {isMobileMenuOpen && (
98
+ <div
99
+ className="fixed inset-0 bg-black bg-opacity-50 z-40"
100
+ onClick={toggleMobileMenu}
101
+ />
102
+ )}
103
+ <aside
104
+ className={`bg-white border-r border-gray-200 sidebar-core-mobile ${className}`}
105
+ {...props}
106
+ >
107
+ <nav className="h-full flex flex-col">
108
+ {/* Header con "Core" y botón X */}
109
+ <div className="bg-white px-4 py-4 flex items-center justify-between border-b border-gray-200">
110
+ <Typography
111
+ variant="h5"
112
+ style={{ color: '#223B40', fontSize: '24px', fontWeight: 'normal' }}
113
+ >
114
+ Core
115
+ </Typography>
116
+ <button
117
+ onClick={toggleMobileMenu}
118
+ className="p-1 hover:bg-gray-100 rounded transition-colors"
119
+ aria-label="Cerrar menú"
120
+ >
121
+ <Icon
122
+ name="XMarkIcon"
123
+ variant="24-outline"
124
+ size={24}
125
+ className="color-gray-700"
126
+ />
127
+ </button>
128
+ </div>
129
+ {/* Contenido del sidebar mobile */}
130
+ <div className="flex-1 overflow-y-auto py-4">
131
+ {/* Item "Inicio" destacado */}
132
+ <div className="px-4 mb-4">
133
+ <button
134
+ onClick={() => {
135
+ onItemClick && onItemClick('inicio');
136
+ toggleMobileMenu();
137
+ }}
138
+ className={`w-full flex items-center cursor-pointer px-4 justify-between py-2.5 rounded-lg transition-all duration-200 ${
139
+ activeItem === 'inicio'
140
+ ? ''
141
+ : 'color-gray-700 hover:bg-gray-100'
142
+ }`}
143
+ style={
144
+ activeItem === 'inicio'
145
+ ? { backgroundColor: '#2D5C63' }
146
+ : {}
147
+ }
148
+ >
149
+ <div className="flex items-center">
150
+ <Icon
151
+ name="HomeIcon"
152
+ variant="24-outline"
153
+ size={20}
154
+ className={`mr-3 ${
155
+ activeItem === 'inicio' ? 'color-white' : 'color-gray-700'
156
+ }`}
157
+ />
158
+ <Typography
159
+ variant="body-md"
160
+ className={`font-medium ${
161
+ activeItem === 'inicio' ? 'color-white' : 'color-gray-700'
162
+ }`}
163
+ >
164
+ Inicio
165
+ </Typography>
166
+ </div>
167
+ {itemBadges['inicio'] !== undefined && itemBadges['inicio'] > 0 && (
168
+ <span
169
+ className="px-2 py-0.5 min-w-[20px] h-5
170
+ text-white rounded-full flex items-center justify-center
171
+ text-body-sm font-medium"
172
+ style={{
173
+ backgroundColor: '#6D3856',
174
+ borderRadius: '12px'
175
+ }}
176
+ >
177
+ {itemBadges['inicio'] > 9 ? '9+' : itemBadges['inicio']}
178
+ </span>
179
+ )}
180
+ </button>
181
+ </div>
182
+
183
+ {/* Secciones */}
184
+ {sections.map((section, sectionIndex) => (
185
+ <div key={sectionIndex} className="mb-6">
186
+ {/* Título de la sección */}
187
+ <div className="px-4 mb-2">
188
+ <Typography
189
+ variant="body-sm"
190
+ className="color-gray-500 uppercase font-medium tracking-wider"
191
+ >
192
+ {section.title}
193
+ </Typography>
194
+ </div>
195
+
196
+ {/* Items de la sección */}
197
+ <div className="space-y-1">
198
+ {section.items.map((item) => (
199
+ <div key={item.id} className="px-4">
200
+ <button
201
+ onClick={() => {
202
+ onItemClick && onItemClick(item.id);
203
+ toggleMobileMenu();
204
+ }}
205
+ className={`w-full flex items-center cursor-pointer px-4 justify-between py-2.5 rounded-lg transition-all duration-200 ${
206
+ activeItem === item.id
207
+ ? ''
208
+ : 'color-gray-700 hover:bg-gray-100'
209
+ }`}
210
+ style={
211
+ activeItem === item.id
212
+ ? { backgroundColor: '#2D5C63' }
213
+ : {}
214
+ }
215
+ >
216
+ <div className="flex items-center">
217
+ <Icon
218
+ name={item.icon}
219
+ variant="24-outline"
220
+ size={20}
221
+ className={`mr-3 ${
222
+ activeItem === item.id ? 'color-white' : 'color-gray-700'
223
+ }`}
224
+ />
225
+ <Typography
226
+ variant="body-md"
227
+ className={`font-medium ${
228
+ activeItem === item.id ? 'color-white' : 'color-gray-700'
229
+ }`}
230
+ >
231
+ {item.label}
232
+ </Typography>
233
+ </div>
234
+ {((itemBadges[item.id] !== undefined && itemBadges[item.id] > 0) || (item.id === 'empleados' && 2)) && (
235
+ <span
236
+ className="px-2 py-0.5 min-w-[20px] h-5
237
+ text-white rounded-full flex items-center justify-center
238
+ text-body-sm font-medium"
239
+ style={{
240
+ backgroundColor: '#6D3856',
241
+ borderRadius: '12px'
242
+ }}
243
+ >
244
+ {item.id === 'empleados' ? 2 : (itemBadges[item.id] > 9 ? '9+' : itemBadges[item.id])}
245
+ </span>
246
+ )}
247
+ </button>
248
+ </div>
249
+ ))}
250
+ </div>
251
+ </div>
252
+ ))}
253
+ </div>
254
+
255
+ {/* Footer con texto configurable */}
256
+ {footerText && (
257
+ <div className="px-4 pb-4 flex-shrink-0">
258
+ <div
259
+ className="bg-white border rounded-lg px-3 py-2 flex items-center justify-center"
260
+ style={{
261
+ borderColor: '#2D5C63',
262
+ borderRadius: '8px'
263
+ }}
264
+ >
265
+ {typeof footerText === 'string' ? (
266
+ <Typography
267
+ variant="body-sm"
268
+ style={{ color: '#2D5C63' }}
269
+ className="text-center"
270
+ >
271
+ {(() => {
272
+ const parts = footerText.split('|');
273
+ if (parts.length === 2) {
274
+ const leftPart = parts[0].trim();
275
+ const rightPart = parts[1].trim();
276
+ // Si contiene "Powered by", separar y poner la marca en bold
277
+ if (rightPart.includes('Powered by')) {
278
+ const poweredByMatch = rightPart.match(/Powered by (.+)/);
279
+ if (poweredByMatch) {
280
+ return (
281
+ <>
282
+ {leftPart} | Powered by <span className="font-bold">{poweredByMatch[1]}</span>
283
+ </>
284
+ );
285
+ }
286
+ }
287
+ return `${leftPart} | ${rightPart}`;
288
+ }
289
+ return footerText;
290
+ })()}
291
+ </Typography>
292
+ ) : (
293
+ <Typography
294
+ variant="body-sm"
295
+ style={{ color: '#2D5C63' }}
296
+ className="text-center"
297
+ >
298
+ {footerText.version && `${footerText.version}`}
299
+ {footerText.version && (footerText.poweredBy || footerText.brand) && ' | '}
300
+ {footerText.poweredBy && `${footerText.poweredBy} `}
301
+ {footerText.brand && <span className="font-bold">{footerText.brand}</span>}
302
+ </Typography>
303
+ )}
304
+ </div>
305
+ </div>
306
+ )}
307
+ </nav>
308
+ </aside>
309
+ </>
310
+ );
311
+ };
312
+
23
313
  export const SidebarCore = ({
24
314
  sections = MOCK_SECTIONS,
25
315
  activeItem,
@@ -32,33 +322,94 @@ export const SidebarCore = ({
32
322
  nucleusLogo,
33
323
  onNucleusClick,
34
324
  itemBadges = {}, // Objeto con { itemId: number } para los badges
325
+ footerText, // Texto del footer: puede ser string "v1.0 | Powered by Nucleus" o objeto { version: "v1.0", poweredBy: "Powered by", brand: "Nucleus" }
326
+ footerCollapsedContent, // Contenido a mostrar en el footer cuando está colapsado (puede ser string, ReactNode, o objeto con icon y text)
35
327
  className = '',
36
328
  ...props
37
329
  }) => {
38
330
  const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
331
+ const [isMobile, setIsMobile] = useState(false);
332
+
333
+ // Detectar tamaño de pantalla
334
+ useEffect(() => {
335
+ const checkMobile = () => {
336
+ setIsMobile(window.innerWidth <= 768);
337
+ };
338
+
339
+ // Verificar al montar
340
+ checkMobile();
341
+
342
+ // Escuchar cambios de tamaño
343
+ window.addEventListener('resize', checkMobile);
344
+
345
+ return () => {
346
+ window.removeEventListener('resize', checkMobile);
347
+ };
348
+ }, []);
39
349
 
40
350
  const toggleCollapse = () => {
41
351
  setIsCollapsed(!isCollapsed);
42
352
  };
43
353
 
354
+ // Generar footerCollapsedContent automáticamente si no se proporciona pero sí hay footerText
355
+ const getFooterCollapsedContent = () => {
356
+ if (footerCollapsedContent) {
357
+ return footerCollapsedContent;
358
+ }
359
+
360
+ // Si hay footerText pero no footerCollapsedContent, generar uno automáticamente
361
+ if (footerText && !footerCollapsedContent) {
362
+ if (typeof footerText === 'string') {
363
+ // Extraer la versión (parte antes del |) o usar todo el texto si no hay |
364
+ const parts = footerText.split('|');
365
+ return parts[0].trim() || footerText.trim();
366
+ } else if (typeof footerText === 'object' && footerText.version) {
367
+ return footerText.version;
368
+ }
369
+ }
370
+
371
+ return null;
372
+ };
373
+
374
+ const effectiveFooterCollapsedContent = getFooterCollapsedContent();
375
+
376
+ // Si es mobile, renderizar SidebarCoreMobile
377
+ if (isMobile) {
378
+ return (
379
+ <SidebarCoreMobile
380
+ sections={sections}
381
+ activeItem={activeItem}
382
+ onItemClick={onItemClick}
383
+ companyName={companyName}
384
+ companyLogo={companyLogo}
385
+ onCompanyClick={onCompanyClick}
386
+ itemBadges={itemBadges}
387
+ footerText={footerText}
388
+ footerCollapsedContent={footerCollapsedContent}
389
+ className={className}
390
+ {...props}
391
+ />
392
+ );
393
+ }
394
+
44
395
  return (
45
396
  <aside
46
- className={`bg-white border-r border-gray-200 transition-all duration-300 ease-in-out ${
397
+ className={`bg-white border-r border-gray-200 transition-all duration-300 ease-in-out h-full ${
47
398
  isCollapsed ? 'w-20' : 'w-64'
48
399
  } ${className}`}
49
400
  {...props}
50
401
  >
51
- <nav className="h-full flex flex-col overflow-hidden">
402
+ <nav className="h-full flex flex-col" style={{ overflow: 'hidden' }}>
52
403
  {/* Barra superior con HEXA Core */}
53
404
  {!isCollapsed && (
54
- <div className="p-4 border-b border-gray-200">
405
+ <div className="p-4 border-b border-gray-200 flex-shrink-0">
55
406
  <button
56
407
  onClick={onCompanyClick}
57
408
  className="w-full bg-white rounded-lg px-3 py-2.5
58
409
  flex items-center justify-between hover:bg-gray-50 transition-colors"
59
410
  >
60
411
  <div className="flex items-center space-x-3">
61
- {/* Icono cuadrado oscuro con H */}
412
+ {/* Logo hexagonal */}
62
413
  {companyLogo ? (
63
414
  <img
64
415
  src={companyLogo}
@@ -66,14 +417,7 @@ export const SidebarCore = ({
66
417
  className="w-8 h-8 rounded"
67
418
  />
68
419
  ) : (
69
- <div className="w-8 h-8 bg-gray-700 rounded flex items-center justify-center">
70
- <Typography
71
- variant="body-md"
72
- className="color-white font-bold"
73
- >
74
- H
75
- </Typography>
76
- </div>
420
+ <LogoHexa width={36} height={40} />
77
421
  )}
78
422
  {/* Nombre de la empresa */}
79
423
  <Typography
@@ -86,45 +430,10 @@ export const SidebarCore = ({
86
430
  </button>
87
431
  </div>
88
432
  )}
89
- {/* Botón Nucleus AR */}
90
- {!isCollapsed && (
91
- <div className="bg-gray-100 py-4 w-full">
92
- <div className="px-4">
93
- <button
94
- onClick={onNucleusClick}
95
- className="w-full flex items-center px-4 py-2.5 rounded-lg
96
- transition-all duration-200 color-gray-700 hover:bg-gray-200
97
- justify-between"
98
- >
99
- <div className="flex items-center">
100
- <Icon
101
- name="BuildingOfficeIcon"
102
- variant="24-outline"
103
- size={20}
104
- className="mr-3 color-teal"
105
- />
106
- <Typography
107
- variant="body-md"
108
- className="font-medium color-gray-700"
109
- >
110
- {nucleusName}
111
- </Typography>
112
- </div>
113
- {/* Chevron hacia abajo */}
114
- <Icon
115
- name="ChevronDownIcon"
116
- variant="24-outline"
117
- size={16}
118
- className="color-teal"
119
- />
120
- </button>
121
- </div>
122
- </div>
123
- )}
124
433
 
125
434
  {/* Botón de colapsar/expandir - Solo visible cuando está expandido */}
126
435
  {!isCollapsed && (
127
- <div className="px-4 pb-2 flex justify-end">
436
+ <div className="px-4 pt-2 pb-2 flex justify-end flex-shrink-0">
128
437
  <button
129
438
  onClick={toggleCollapse}
130
439
  className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors"
@@ -141,9 +450,9 @@ export const SidebarCore = ({
141
450
  </div>
142
451
  )}
143
452
 
144
- {/* Logo H cuando está colapsado */}
453
+ {/* Logo cuando está colapsado */}
145
454
  {isCollapsed && (
146
- <div className="p-4 border-b border-gray-200 flex justify-center">
455
+ <div className="p-4 border-b border-gray-200 flex justify-center flex-shrink-0">
147
456
  {companyLogo ? (
148
457
  <img
149
458
  src={companyLogo}
@@ -151,47 +460,14 @@ export const SidebarCore = ({
151
460
  className="w-8 h-8 rounded"
152
461
  />
153
462
  ) : (
154
- <div className="w-8 h-8 bg-gray-700 rounded flex items-center justify-center">
155
- <Typography
156
- variant="body-md"
157
- className="color-white font-bold"
158
- >
159
- H
160
- </Typography>
161
- </div>
463
+ <LogoHexa width={36} height={40} />
162
464
  )}
163
465
  </div>
164
466
  )}
165
467
 
166
- {/* Botón Nucleus AR cuando está colapsado */}
167
- {isCollapsed && (
168
- <div className="bg-gray-100 py-4 w-full">
169
- <div className="px-4 flex justify-center">
170
- <button
171
- onClick={onNucleusClick}
172
- className="flex items-center justify-center p-2.5 rounded-lg
173
- transition-all duration-200 color-gray-700 hover:bg-gray-200"
174
- >
175
- <Icon
176
- name="BuildingOfficeIcon"
177
- variant="24-outline"
178
- size={20}
179
- className="color-teal"
180
- />
181
- <Icon
182
- name="ChevronDownIcon"
183
- variant="24-outline"
184
- size={16}
185
- className="ml-1 color-teal"
186
- />
187
- </button>
188
- </div>
189
- </div>
190
- )}
191
-
192
468
  {/* Botón para expandir - Solo visible cuando está colapsado */}
193
469
  {isCollapsed && (
194
- <div className="px-4 pb-2 flex justify-center">
470
+ <div className="px-4 pt-2 pb-2 flex justify-center flex-shrink-0">
195
471
  <button
196
472
  onClick={toggleCollapse}
197
473
  className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors"
@@ -207,8 +483,9 @@ export const SidebarCore = ({
207
483
  </div>
208
484
  )}
209
485
 
210
- {/* Contenido del sidebar */}
211
- <div className="flex-1 overflow-y-auto py-4">
486
+ {/* Contenido del sidebar - Wrapper para controlar el espacio */}
487
+ <div className="flex-1 min-h-0 flex flex-col overflow-hidden">
488
+ <div className={`flex-1 overflow-y-auto ${isCollapsed ? 'py-2' : 'py-4'}`} style={{ overflowX: 'hidden' }}>
212
489
  {/* Item "Inicio" destacado */}
213
490
  <div className="px-4 mb-4">
214
491
  <button
@@ -226,7 +503,7 @@ export const SidebarCore = ({
226
503
  : {}
227
504
  }
228
505
  >
229
- <div className="flex items-center">
506
+ <div className={`flex items-center ${isCollapsed ? 'relative' : ''}`} style={isCollapsed && itemBadges['inicio'] !== undefined && itemBadges['inicio'] > 0 ? { paddingRight: '16px' } : {}}>
230
507
  <Icon
231
508
  name="HomeIcon"
232
509
  variant="24-outline"
@@ -245,6 +522,22 @@ export const SidebarCore = ({
245
522
  Inicio
246
523
  </Typography>
247
524
  )}
525
+ {isCollapsed && itemBadges['inicio'] !== undefined && itemBadges['inicio'] > 0 && (
526
+ <span
527
+ className="px-2 py-0.5 min-w-[20px] h-5
528
+ text-white rounded-full flex items-center justify-center
529
+ text-body-sm font-medium absolute"
530
+ style={{
531
+ backgroundColor: '#6D3856',
532
+ borderRadius: '12px',
533
+ top: '-4px',
534
+ right: '-4px',
535
+ zIndex: 10
536
+ }}
537
+ >
538
+ {itemBadges['inicio'] > 9 ? '9+' : itemBadges['inicio']}
539
+ </span>
540
+ )}
248
541
  </div>
249
542
  {!isCollapsed && itemBadges['inicio'] !== undefined && itemBadges['inicio'] > 0 && (
250
543
  <span
@@ -265,7 +558,14 @@ export const SidebarCore = ({
265
558
  {/* Secciones */}
266
559
  {sections.map((section, sectionIndex) => (
267
560
  <div key={sectionIndex} className="mb-6">
268
- {/* Título de la sección */}
561
+ {/* Línea separadora cuando está colapsado - antes de cada sección */}
562
+ {isCollapsed && (
563
+ <div className="px-4 mb-4">
564
+ <div className="border-t" style={{ borderColor: '#2D5C63' }}></div>
565
+ </div>
566
+ )}
567
+
568
+ {/* Título de la sección - solo visible cuando NO está colapsado */}
269
569
  {!isCollapsed && (
270
570
  <div className="px-4 mb-2">
271
571
  <Typography
@@ -297,7 +597,7 @@ export const SidebarCore = ({
297
597
  }
298
598
  title={isCollapsed ? item.label : ''}
299
599
  >
300
- <div className="flex items-center">
600
+ <div className={`flex items-center ${isCollapsed ? 'relative' : ''}`} style={isCollapsed && ((itemBadges[item.id] !== undefined && itemBadges[item.id] > 0) || (item.id === 'empleados' && 2)) ? { paddingRight: '16px' } : {}}>
301
601
  <Icon
302
602
  name={item.icon}
303
603
  variant="24-outline"
@@ -316,6 +616,22 @@ export const SidebarCore = ({
316
616
  {item.label}
317
617
  </Typography>
318
618
  )}
619
+ {isCollapsed && ((itemBadges[item.id] !== undefined && itemBadges[item.id] > 0) || (item.id === 'empleados' && 2)) && (
620
+ <span
621
+ className="px-2 py-0.5 min-w-[20px] h-5
622
+ text-white rounded-full flex items-center justify-center
623
+ text-body-sm font-medium absolute"
624
+ style={{
625
+ backgroundColor: '#6D3856',
626
+ borderRadius: '12px',
627
+ top: '-4px',
628
+ right: '-4px',
629
+ zIndex: 10
630
+ }}
631
+ >
632
+ {item.id === 'empleados' ? 2 : (itemBadges[item.id] > 9 ? '9+' : itemBadges[item.id])}
633
+ </span>
634
+ )}
319
635
  </div>
320
636
  {!isCollapsed && ((itemBadges[item.id] !== undefined && itemBadges[item.id] > 0) || (item.id === 'empleados' && 2)) && (
321
637
  <span
@@ -336,11 +652,128 @@ export const SidebarCore = ({
336
652
  </div>
337
653
  </div>
338
654
  ))}
655
+ </div>
339
656
  </div>
657
+
658
+ {/* Footer con texto configurable - cuando está expandido */}
659
+ {footerText && !isCollapsed && (
660
+ <div className="px-4 pb-4 flex-shrink-0">
661
+ <div
662
+ className="bg-white border rounded-lg px-3 py-2 flex items-center justify-center"
663
+ style={{
664
+ borderColor: '#2D5C63',
665
+ borderRadius: '8px'
666
+ }}
667
+ >
668
+ {typeof footerText === 'string' ? (
669
+ <Typography
670
+ variant="body-sm"
671
+ style={{ color: '#2D5C63' }}
672
+ className="text-center"
673
+ >
674
+ {(() => {
675
+ const parts = footerText.split('|');
676
+ if (parts.length === 2) {
677
+ const leftPart = parts[0].trim();
678
+ const rightPart = parts[1].trim();
679
+ // Si contiene "Powered by", separar y poner la marca en bold
680
+ if (rightPart.includes('Powered by')) {
681
+ const poweredByMatch = rightPart.match(/Powered by (.+)/);
682
+ if (poweredByMatch) {
683
+ return (
684
+ <>
685
+ {leftPart} | Powered by <span className="font-bold">{poweredByMatch[1]}</span>
686
+ </>
687
+ );
688
+ }
689
+ }
690
+ return `${leftPart} | ${rightPart}`;
691
+ }
692
+ return footerText;
693
+ })()}
694
+ </Typography>
695
+ ) : (
696
+ <Typography
697
+ variant="body-sm"
698
+ style={{ color: '#2D5C63' }}
699
+ className="text-center"
700
+ >
701
+ {footerText.version && `${footerText.version}`}
702
+ {footerText.version && (footerText.poweredBy || footerText.brand) && ' | '}
703
+ {footerText.poweredBy && `${footerText.poweredBy} `}
704
+ {footerText.brand && <span className="font-bold">{footerText.brand}</span>}
705
+ </Typography>
706
+ )}
707
+ </div>
708
+ </div>
709
+ )}
710
+
711
+ {/* Footer cuando está colapsado */}
712
+ {effectiveFooterCollapsedContent && isCollapsed && (
713
+ <div className="flex justify-center flex-shrink-0" style={{ zIndex: 10, paddingTop: '10px', paddingBottom: '10px' }}>
714
+ <div
715
+ className="bg-white border flex items-center justify-center"
716
+ style={{
717
+ width: '52px',
718
+ height: '40px',
719
+ borderRadius: '8px',
720
+ border: '1px solid #29474C',
721
+ paddingTop: '8px',
722
+ paddingRight: '12px',
723
+ paddingBottom: '8px',
724
+ paddingLeft: '12px'
725
+ }}
726
+ >
727
+ {typeof effectiveFooterCollapsedContent === 'string' ? (
728
+ <span
729
+ style={{
730
+ color: '#2D5C63',
731
+ fontSize: '12px',
732
+ lineHeight: '1.2',
733
+ fontWeight: '600',
734
+ textAlign: 'center',
735
+ whiteSpace: 'nowrap',
736
+ overflow: 'hidden',
737
+ textOverflow: 'ellipsis',
738
+ maxWidth: '100%'
739
+ }}
740
+ >
741
+ {effectiveFooterCollapsedContent}
742
+ </span>
743
+ ) : typeof effectiveFooterCollapsedContent === 'object' && effectiveFooterCollapsedContent.icon ? (
744
+ <div className="flex items-center justify-center">
745
+ {effectiveFooterCollapsedContent.icon && (
746
+ <Icon
747
+ name={effectiveFooterCollapsedContent.icon}
748
+ variant="24-outline"
749
+ size={14}
750
+ style={{ color: '#2D5C63' }}
751
+ />
752
+ )}
753
+ {effectiveFooterCollapsedContent.text && (
754
+ <span
755
+ style={{
756
+ color: '#2D5C63',
757
+ fontSize: '12px',
758
+ marginLeft: effectiveFooterCollapsedContent.icon ? '4px' : '0'
759
+ }}
760
+ >
761
+ {effectiveFooterCollapsedContent.text}
762
+ </span>
763
+ )}
764
+ </div>
765
+ ) : (
766
+ effectiveFooterCollapsedContent
767
+ )}
768
+ </div>
769
+ </div>
770
+ )}
340
771
  </nav>
341
772
  </aside>
342
773
  );
343
774
  };
344
775
 
776
+ // Exportar ambos componentes
777
+ export { SidebarCoreMobile };
345
778
  export default SidebarCore;
346
779