anima-ds-nucleus 1.0.15 → 1.0.17

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.
@@ -4,6 +4,30 @@ import { Avatar } from '../../Atoms/Avatar/Avatar';
4
4
  import { Typography } from '../../Atoms/Typography/Typography';
5
5
  import { SidebarPointMobile } from '../Sidebar/SidebarPoint';
6
6
 
7
+ // Constantes de diseño reutilizables
8
+ const HEADER_HEIGHT = '80px';
9
+ const SEARCH_INPUT_HEIGHT = '40px';
10
+ const SEARCH_ICON_SIZE = '40px';
11
+ const NOTIFICATION_BADGE_COLOR = '#6D3856';
12
+ const TEXT_COLOR_GRAY_700 = '#374151';
13
+ const BORDER_COLOR_GRAY_400 = '#9ca3af';
14
+ const FOCUS_BORDER_COLOR = '#3b82f6';
15
+
16
+ // Constantes de tipografía
17
+ const FONT_FAMILY_IBM_PLEX = 'IBM Plex Sans';
18
+ const FONT_WEIGHT_REGULAR = 400;
19
+ const FONT_WEIGHT_SEMIBOLD = 600;
20
+ const FONT_SIZE_BASE = '14px';
21
+ const LINE_HEIGHT_BASE = '20px';
22
+
23
+ // Constantes de breakpoints
24
+ const BREAKPOINT_MOBILE_MAX = '480px';
25
+ const BREAKPOINT_DESKTOP_MIN = '481px';
26
+ const BREAKPOINT_TABLET_MAX = '700px';
27
+
28
+ // Constantes de valores por defecto
29
+ const DEFAULT_MOBILE_BACKGROUND = 'transparent';
30
+
7
31
  export const HeaderPoint = ({
8
32
  searchPlaceholder = 'Buscar empleados, reportes, configuraciones...',
9
33
  userName,
@@ -18,6 +42,7 @@ export const HeaderPoint = ({
18
42
  sidebarItemBadges,
19
43
  sidebarCompanyName,
20
44
  sidebarCompanyLogo,
45
+ mobileBackgroundColor = DEFAULT_MOBILE_BACKGROUND,
21
46
  className = '',
22
47
  ...props
23
48
  }) => {
@@ -39,34 +64,91 @@ export const HeaderPoint = ({
39
64
  }
40
65
  };
41
66
 
67
+ const handleSidebarItemClick = (itemId) => {
68
+ if (onSidebarItemClick) {
69
+ onSidebarItemClick(itemId);
70
+ }
71
+ setIsSidebarOpen(false);
72
+ };
73
+
74
+ const handleCloseSidebar = () => {
75
+ setIsSidebarOpen(false);
76
+ };
77
+
78
+ const formatNotificationCount = (count) => {
79
+ return count > 9 ? '9+' : count;
80
+ };
81
+
42
82
  return (
43
- <header
44
- className={`bg-white header-point-mobile ${className}`}
45
- style={{
46
- ...(props.style || {}),
47
- }}
48
- {...props}
49
- >
83
+ <>
50
84
  <style>{`
85
+ .header-point-container {
86
+ height: ${HEADER_HEIGHT};
87
+ }
51
88
  .header-search-input {
52
- border-color: #9ca3af;
89
+ border-color: ${BORDER_COLOR_GRAY_400};
90
+ height: ${SEARCH_INPUT_HEIGHT};
91
+ color: ${TEXT_COLOR_GRAY_700};
92
+ font-family: ${FONT_FAMILY_IBM_PLEX};
93
+ font-weight: ${FONT_WEIGHT_REGULAR};
94
+ font-style: italic;
95
+ font-size: ${FONT_SIZE_BASE};
96
+ line-height: ${LINE_HEIGHT_BASE};
97
+ letter-spacing: 0px;
53
98
  }
54
99
  .header-search-input::placeholder {
55
- color: #9ca3af !important;
100
+ color: ${BORDER_COLOR_GRAY_400} !important;
56
101
  opacity: 1 !important;
57
102
  }
58
103
  .header-search-icon-container {
59
- border-color: #9ca3af;
104
+ border-color: ${BORDER_COLOR_GRAY_400};
105
+ width: ${SEARCH_ICON_SIZE};
106
+ height: ${SEARCH_ICON_SIZE};
60
107
  }
61
108
  .header-search-form:focus-within .header-search-input {
62
- border-color: #3b82f6 !important;
63
- box-shadow: 0 0 0 1px #3b82f6;
109
+ border-color: ${FOCUS_BORDER_COLOR} !important;
110
+ box-shadow: 0 0 0 1px ${FOCUS_BORDER_COLOR};
64
111
  }
65
112
  .header-search-form:focus-within .header-search-icon-container {
66
- border-color: #3b82f6 !important;
67
- box-shadow: 0 0 0 1px #3b82f6;
113
+ border-color: ${FOCUS_BORDER_COLOR} !important;
114
+ box-shadow: 0 0 0 1px ${FOCUS_BORDER_COLOR};
115
+ }
116
+ .header-notification-badge {
117
+ background-color: ${NOTIFICATION_BADGE_COLOR};
118
+ border-radius: 12px;
119
+ }
120
+ .header-desktop-layout {
121
+ height: ${HEADER_HEIGHT};
122
+ }
123
+ .header-search-wrapper {
124
+ flex: 1 1 0%;
125
+ min-width: 0;
126
+ }
127
+ .header-actions-wrapper {
128
+ flex-shrink: 0;
129
+ }
130
+ /* Estilos para rango intermedio (481px - 700px) */
131
+ @media (min-width: ${BREAKPOINT_DESKTOP_MIN}) and (max-width: ${BREAKPOINT_TABLET_MAX}) {
132
+ .header-desktop-layout {
133
+ gap: 0.5rem;
134
+ padding-left: 0.25rem;
135
+ padding-right: 0.25rem;
136
+ }
137
+ .header-actions-wrapper {
138
+ gap: 0.5rem;
139
+ flex-shrink: 0;
140
+ }
141
+ .header-actions-wrapper button {
142
+ padding: 0.375rem;
143
+ }
144
+ .header-user-name {
145
+ display: none !important;
146
+ }
147
+ .header-user-chevron {
148
+ display: none !important;
149
+ }
68
150
  }
69
- @media (max-width: 768px) {
151
+ @media (max-width: ${BREAKPOINT_MOBILE_MAX}) {
70
152
  header.header-point-mobile,
71
153
  .header-point-mobile,
72
154
  header.bg-white.header-point-mobile {
@@ -128,10 +210,10 @@ export const HeaderPoint = ({
128
210
  right: 0 !important;
129
211
  width: 100% !important;
130
212
  height: calc(64px + 12px + 20px) !important;
131
- background-color: var(--color-main) !important;
132
- background: var(--color-main) !important;
213
+ background-color: ${mobileBackgroundColor} !important;
214
+ background: ${mobileBackgroundColor} !important;
133
215
  z-index: 0 !important;
134
- border-bottom: 1px solid var(--color-main-800) !important;
216
+ border-bottom: ${mobileBackgroundColor !== DEFAULT_MOBILE_BACKGROUND ? '1px solid var(--color-main-800)' : 'none'} !important;
135
217
  pointer-events: none !important;
136
218
  display: block !important;
137
219
  visibility: visible !important;
@@ -158,6 +240,7 @@ export const HeaderPoint = ({
158
240
  font-size: 12px !important;
159
241
  padding-left: 8px !important;
160
242
  padding-right: 8px !important;
243
+ height: 44px !important;
161
244
  }
162
245
  .header-mobile-layout .header-search-icon-container {
163
246
  width: 44px !important;
@@ -172,8 +255,19 @@ export const HeaderPoint = ({
172
255
  .header-mobile-point-text {
173
256
  color: var(--color-neutrals-white) !important;
174
257
  }
258
+ /* Cambiar color de iconos cuando los botones tienen hover/active/focus */
259
+ .header-mobile-menu-button:hover .header-mobile-menu-icon,
260
+ .header-mobile-menu-button:active .header-mobile-menu-icon,
261
+ .header-mobile-menu-button:focus .header-mobile-menu-icon {
262
+ color: ${TEXT_COLOR_GRAY_700} !important;
263
+ }
264
+ .header-mobile-notification-button:hover .header-mobile-notification-icon,
265
+ .header-mobile-notification-button:active .header-mobile-notification-icon,
266
+ .header-mobile-notification-button:focus .header-mobile-notification-icon {
267
+ color: ${TEXT_COLOR_GRAY_700} !important;
268
+ }
175
269
  }
176
- @media (min-width: 769px) {
270
+ @media (min-width: ${BREAKPOINT_DESKTOP_MIN}) {
177
271
  .header-mobile-layout {
178
272
  display: none;
179
273
  }
@@ -183,275 +277,181 @@ export const HeaderPoint = ({
183
277
  }
184
278
  `}</style>
185
279
 
186
- <div className="w-full" style={{ paddingLeft: '4px', paddingRight: '4px' }}>
187
- {/* Layout Desktop (más de 768px) */}
188
- <div className="header-desktop-layout flex items-center justify-between h-16">
189
- {/* Barra de búsqueda con icono a la derecha */}
190
- <div className="flex-1 mr-2 md:mr-4" style={{ maxWidth: 'none', minWidth: '0' }}>
191
- <form onSubmit={handleSearchSubmit} className="header-search-form flex items-center w-full">
192
- {/* Input de búsqueda */}
193
- <input
194
- type="text"
195
- value={searchValue}
196
- onChange={handleSearchChange}
197
- placeholder={searchPlaceholder}
198
- className="header-search-input flex-1 pl-2 md:pl-4 pr-2 md:pr-4 py-2 md:py-2.5 bg-white border border-gray-400 rounded-l-lg
199
- focus:outline-none text-sm md:text-base"
200
- style={{
201
- height: '40px',
202
- borderRight: 'none',
203
- borderTopRightRadius: '0',
204
- borderBottomRightRadius: '0',
205
- color: '#374151',
206
- fontFamily: 'IBM Plex Sans',
207
- fontWeight: 400,
208
- fontStyle: 'italic',
209
- fontSize: '14px',
210
- lineHeight: '20px',
211
- letterSpacing: '0px'
212
- }}
213
- />
214
-
215
- {/* Recuadro con icono de buscar a la derecha */}
216
- <div
217
- className="header-search-icon-container flex items-center justify-center border border-gray-400 rounded-r-lg bg-white"
218
- style={{
219
- width: '40px',
220
- height: '40px',
221
- borderTopLeftRadius: '0',
222
- borderBottomLeftRadius: '0'
223
- }}
224
- >
225
- <Icon
226
- name="MagnifyingGlassIcon"
227
- variant="24-outline"
228
- size={20}
229
- className="color-gray-600"
230
- />
231
- </div>
232
- </form>
233
- </div>
234
-
235
- {/* Notificaciones y Perfil */}
236
- <div className="flex items-center space-x-3 md:space-x-6 flex-shrink-0" style={{ marginLeft: 'auto', paddingRight: '0px' }}>
237
- {/* Icono de notificaciones */}
238
- {notificationCount !== undefined && (
239
- <button
240
- onClick={onNotificationClick}
241
- className="relative p-2 hover:bg-gray-100 rounded-lg transition-colors"
242
- aria-label="Notificaciones"
243
- >
244
- <Icon
245
- name="BellIcon"
246
- variant="24-outline"
247
- size={24}
248
- className="color-gray-600"
280
+ <header
281
+ className={`bg-white header-point-mobile rounded-12 header-point-container ${className}`}
282
+ {...props}
283
+ >
284
+ <div className="w-full px-1">
285
+ {/* Layout Desktop (más de 480px) */}
286
+ <div className="header-desktop-layout flex items-center justify-between gap-2 md:gap-4">
287
+ {/* Barra de búsqueda con icono a la derecha */}
288
+ <div className="header-search-wrapper">
289
+ <form onSubmit={handleSearchSubmit} className="header-search-form flex items-center w-full">
290
+ <input
291
+ type="text"
292
+ value={searchValue}
293
+ onChange={handleSearchChange}
294
+ placeholder={searchPlaceholder}
295
+ className="header-search-input flex-1 pl-2 md:pl-4 pr-2 md:pr-4 py-2 md:py-2.5 bg-white border border-gray-400 rounded-l-lg focus:outline-none text-sm md:text-base border-r-0 rounded-r-none"
249
296
  />
250
- {notificationCount > 0 && (
251
- <span
252
- className="absolute -top-1 -right-1 px-1.5 py-0.5 min-w-[20px] h-5
253
- text-white rounded-full flex items-center justify-center
254
- text-body-sm font-medium"
255
- style={{
256
- backgroundColor: '#6D3856',
257
- borderRadius: '12px'
258
- }}
259
- >
260
- {notificationCount > 9 ? '9+' : notificationCount}
261
- </span>
262
- )}
263
- </button>
264
- )}
265
-
266
- {/* Perfil de usuario */}
267
- {userName && (
268
- <button
269
- onClick={onUserClick}
270
- className="flex items-center space-x-3 hover:bg-gray-50
271
- rounded-lg px-2 py-1.5 transition-colors"
272
- aria-label="Perfil de usuario"
273
- >
274
- <Avatar
275
- src={userAvatar}
276
- name={userName}
277
- size="medium"
278
- variant="circle"
279
- />
280
- <Typography
281
- variant="body-lg"
282
- className="color-gray-700 hidden md:block"
283
- style={{
284
- fontFamily: 'IBM Plex Sans',
285
- fontWeight: 400,
286
- fontStyle: 'normal',
287
- fontSize: '16px',
288
- lineHeight: '24px',
289
- letterSpacing: '0%',
290
- verticalAlign: 'middle'
291
- }}
292
- >
293
- {userName}
294
- </Typography>
295
- <Icon
296
- name="ChevronDownIcon"
297
- variant="24-outline"
298
- size={20}
299
- className="color-gray-500"
300
- />
301
- </button>
302
- )}
303
- </div>
304
- </div>
305
-
306
- {/* Layout Mobile (768px o menos) */}
307
- <div className="header-mobile-layout">
308
- {/* Primera fila: Menú, Point (centrado), Notificaciones */}
309
- <div className="header-mobile-top-row" style={{ position: 'relative' }}>
310
- {/* Lado izquierdo: Menú hamburguesa */}
311
- <div className="flex items-center">
312
- <button
313
- onClick={() => setIsSidebarOpen(true)}
314
- className="p-2 hover:bg-gray-100 rounded-lg transition-colors header-mobile-menu-button"
315
- style={{ cursor: 'pointer', padding: '8px' }}
316
- aria-label="Menú"
317
- >
318
- <Icon
319
- name="Bars3Icon"
320
- variant="24-outline"
321
- size={24}
322
- className="header-mobile-menu-icon"
323
- />
324
- </button>
325
- </div>
326
-
327
- {/* Centro: Texto "Point" */}
328
- <div
329
- style={{
330
- position: 'absolute',
331
- left: '50%',
332
- top: '54%',
333
- transform: 'translate(-50%, -50%)',
334
- display: 'flex',
335
- alignItems: 'center'
336
- }}
337
- >
338
- <Typography
339
- variant="h6"
340
- className="header-mobile-point-text"
341
- style={{
342
- fontSize: '20px',
343
- fontFamily: 'IBM Plex Sans',
344
- fontWeight: 600,
345
- lineHeight: '1.2',
346
- margin: 0
347
- }}
348
- >
349
- Point
350
- </Typography>
297
+
298
+ <div className="header-search-icon-container flex items-center justify-center border border-gray-400 rounded-r-lg bg-white border-l-0">
299
+ <Icon
300
+ name="MagnifyingGlassIcon"
301
+ variant="24-outline"
302
+ size={20}
303
+ className="color-gray-600"
304
+ />
305
+ </div>
306
+ </form>
351
307
  </div>
352
308
 
353
- {/* Lado derecho: Notificaciones */}
354
- <div className="flex items-center ml-auto">
309
+ {/* Notificaciones y Perfil */}
310
+ <div className="header-actions-wrapper flex items-center gap-3 md:gap-6">
355
311
  {notificationCount !== undefined && (
356
312
  <button
357
313
  onClick={onNotificationClick}
358
- className="relative p-2 hover:bg-gray-100 rounded-lg transition-colors header-mobile-notification-button"
359
- style={{ cursor: 'pointer', padding: '8px' }}
314
+ className="relative p-2 hover:bg-gray-100 rounded-lg transition-colors"
360
315
  aria-label="Notificaciones"
361
316
  >
362
317
  <Icon
363
318
  name="BellIcon"
364
319
  variant="24-outline"
365
320
  size={24}
366
- className="header-mobile-notification-icon"
321
+ className="color-gray-600"
367
322
  />
368
323
  {notificationCount > 0 && (
369
- <span
370
- className="absolute -top-1 -right-1 px-1.5 py-0.5 min-w-[20px] h-5
371
- text-white rounded-full flex items-center justify-center
372
- text-body-sm font-medium"
373
- style={{
374
- backgroundColor: '#6D3856',
375
- borderRadius: '12px'
376
- }}
377
- >
378
- {notificationCount > 9 ? '9+' : notificationCount}
324
+ <span className="absolute -top-1 -right-1 px-1.5 py-0.5 min-w-[20px] h-5 text-white rounded-full flex items-center justify-center text-body-sm font-medium header-notification-badge">
325
+ {formatNotificationCount(notificationCount)}
379
326
  </span>
380
327
  )}
381
328
  </button>
382
329
  )}
330
+
331
+ {userName && (
332
+ <button
333
+ onClick={onUserClick}
334
+ className="flex items-center gap-3 hover:bg-gray-50 rounded-lg px-2 py-1.5 transition-colors"
335
+ aria-label="Perfil de usuario"
336
+ >
337
+ <Avatar
338
+ src={userAvatar}
339
+ name={userName}
340
+ size="medium"
341
+ variant="circle"
342
+ />
343
+ <Typography
344
+ variant="body-lg"
345
+ className="color-gray-700 hidden md:block header-user-name"
346
+ >
347
+ {userName}
348
+ </Typography>
349
+ <Icon
350
+ name="ChevronDownIcon"
351
+ variant="24-outline"
352
+ size={20}
353
+ className="color-gray-500 header-user-chevron hidden md:block"
354
+ />
355
+ </button>
356
+ )}
383
357
  </div>
384
358
  </div>
385
359
 
386
- {/* Segunda fila: Barra de búsqueda */}
387
- <div className="header-mobile-search-row">
388
- <form onSubmit={handleSearchSubmit} className="header-search-form flex items-center w-full">
389
- {/* Input de búsqueda */}
390
- <input
391
- type="text"
392
- value={searchValue}
393
- onChange={handleSearchChange}
394
- placeholder={searchPlaceholder}
395
- className="header-search-input flex-1 pl-4 pr-4 py-2 bg-white border border-gray-400 rounded-l-lg
396
- focus:outline-none text-sm"
397
- style={{
398
- height: '44px',
399
- borderRight: 'none',
400
- borderTopRightRadius: '0',
401
- borderBottomRightRadius: '0',
402
- color: '#374151',
403
- fontFamily: 'IBM Plex Sans',
404
- fontWeight: 400,
405
- fontStyle: 'italic',
406
- fontSize: '14px',
407
- lineHeight: '20px',
408
- letterSpacing: '0px'
409
- }}
410
- />
411
-
412
- {/* Recuadro con icono de buscar a la derecha */}
413
- <div
414
- className="header-search-icon-container flex items-center justify-center border border-gray-400 rounded-r-lg bg-white"
415
- style={{
416
- width: '40px',
417
- height: '40px',
418
- borderTopLeftRadius: '0',
419
- borderBottomLeftRadius: '0'
420
- }}
421
- >
422
- <Icon
423
- name="MagnifyingGlassIcon"
424
- variant="24-outline"
425
- size={20}
426
- className="color-gray-600"
427
- />
360
+ {/* Layout Mobile (480px o menos) */}
361
+ <div className="header-mobile-layout">
362
+ {/* Primera fila: Menú, Point (centrado), Notificaciones */}
363
+ <div className="header-mobile-top-row relative">
364
+ {/* Lado izquierdo: Menú hamburguesa */}
365
+ <div className="flex items-center">
366
+ <button
367
+ onClick={() => setIsSidebarOpen(true)}
368
+ className="p-2 hover:bg-gray-100 rounded-lg transition-colors header-mobile-menu-button cursor-pointer"
369
+ aria-label="Menú"
370
+ >
371
+ <Icon
372
+ name="Bars3Icon"
373
+ variant="24-outline"
374
+ size={24}
375
+ className="header-mobile-menu-icon"
376
+ />
377
+ </button>
378
+ </div>
379
+
380
+ {/* Centro: Texto "Point" */}
381
+ <div className="absolute left-1/2 -translate-x-1/2 flex items-center h-full">
382
+ <Typography
383
+ variant="h6"
384
+ className="header-mobile-point-text"
385
+ >
386
+ Point
387
+ </Typography>
388
+ </div>
389
+
390
+ {/* Lado derecho: Notificaciones */}
391
+ <div className="flex items-center ml-auto">
392
+ {notificationCount !== undefined && (
393
+ <button
394
+ onClick={onNotificationClick}
395
+ className="relative p-2 hover:bg-gray-100 rounded-lg transition-colors header-mobile-notification-button cursor-pointer"
396
+ aria-label="Notificaciones"
397
+ >
398
+ <Icon
399
+ name="BellIcon"
400
+ variant="24-outline"
401
+ size={24}
402
+ className="header-mobile-notification-icon"
403
+ />
404
+ {notificationCount > 0 && (
405
+ <span className="absolute -top-1 -right-1 px-1.5 py-0.5 min-w-[20px] h-5 text-white rounded-full flex items-center justify-center text-body-sm font-medium header-notification-badge">
406
+ {formatNotificationCount(notificationCount)}
407
+ </span>
408
+ )}
409
+ </button>
410
+ )}
428
411
  </div>
429
- </form>
412
+ </div>
413
+
414
+ {/* Segunda fila: Barra de búsqueda */}
415
+ <div className="header-mobile-search-row">
416
+ <form onSubmit={handleSearchSubmit} className="header-search-form flex items-center w-full">
417
+ <input
418
+ type="text"
419
+ value={searchValue}
420
+ onChange={handleSearchChange}
421
+ placeholder={searchPlaceholder}
422
+ className="header-search-input flex-1 pl-4 pr-4 py-2 bg-white border border-gray-400 rounded-l-lg focus:outline-none text-sm border-r-0 rounded-r-none"
423
+ />
424
+
425
+ <div className="header-search-icon-container flex items-center justify-center border border-gray-400 rounded-r-lg bg-white border-l-0">
426
+ <Icon
427
+ name="MagnifyingGlassIcon"
428
+ variant="24-outline"
429
+ size={20}
430
+ className="color-gray-600"
431
+ />
432
+ </div>
433
+ </form>
434
+ </div>
430
435
  </div>
431
436
  </div>
432
- </div>
433
437
 
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
- )}
454
- </header>
438
+ {/* SidebarPointMobile - Solo visible en móvil cuando está abierto */}
439
+ {isSidebarOpen && (
440
+ <div className="header-mobile-sidebar-wrapper">
441
+ <SidebarPointMobile
442
+ sections={sidebarSections}
443
+ activeItem={sidebarActiveItem}
444
+ onItemClick={handleSidebarItemClick}
445
+ itemBadges={sidebarItemBadges}
446
+ companyName={sidebarCompanyName}
447
+ companyLogo={sidebarCompanyLogo}
448
+ isOpen={isSidebarOpen}
449
+ onClose={handleCloseSidebar}
450
+ />
451
+ </div>
452
+ )}
453
+ </header>
454
+ </>
455
455
  );
456
456
  };
457
457