anima-ds-nucleus 1.0.10 → 1.0.12

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.10",
3
+ "version": "1.0.12",
4
4
  "description": "Anima Design System - A comprehensive React component library",
5
5
  "author": "Nucleus Labs <ipvasallo@nucleus.com.ar>",
6
6
  "license": "UNLICENSED",
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useEffect, useRef, 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';
@@ -6,15 +6,65 @@ import { SidebarCoreMobile } from '../Sidebar/SidebarCore';
6
6
 
7
7
  export const HeaderCore = ({
8
8
  searchPlaceholder = 'Buscar empleados, reportes, configuraciones...',
9
+ mobileSearchPlaceholder = 'Busca lo que necesites',
9
10
  userName,
10
11
  userAvatar,
11
12
  notificationCount = 0,
12
13
  onSearch,
13
14
  onNotificationClick,
14
15
  onUserClick,
16
+ // Borde inferior (subrayado)
17
+ showBottomBorder = false,
18
+ bottomBorderClassName = 'border-b border-gray-200',
19
+ // Sidebar mobile (animación/cierre controlado)
20
+ mobileSidebarTransitionDurationMs = 300,
21
+ mobileSidebarTransitionEasing = 'ease-in-out',
22
+ // Mobile background
23
+ mobileBackgroundColor = '#F3F3F3',
24
+ mobileHeaderClassName = '',
25
+ mobileHeaderStyle,
26
+ // Overrides de estilo/clases para poder ajustar layout desde el proyecto consumidor
27
+ desktopLayoutClassName = '',
28
+ desktopLayoutStyle,
29
+ desktopSearchContainerClassName = '',
30
+ desktopSearchContainerStyle,
31
+ desktopRightContainerClassName = '',
32
+ desktopRightContainerStyle,
33
+ notificationButtonClassName = '',
34
+ notificationButtonStyle,
35
+ notificationIconClassName = '',
36
+ notificationIconStyle,
37
+ notificationBadgeClassName = '',
38
+ notificationBadgeStyle,
39
+ // Overrides específicos de medidas/posiciones (Desktop / Mobile)
40
+ notificationDesktopButtonStyle,
41
+ notificationDesktopIconWrapperClassName = '',
42
+ notificationDesktopIconWrapperStyle,
43
+ notificationDesktopIconSize = 18,
44
+ notificationDesktopIconStrokeWidth = 1.5,
45
+ notificationDesktopIconStyle,
46
+ notificationDesktopBadgeStyle,
47
+ notificationMobileButtonClassName = '',
48
+ notificationMobileButtonStyle,
49
+ notificationMobileIconWrapperClassName = '',
50
+ notificationMobileIconWrapperStyle,
51
+ notificationMobileIconSize = 24,
52
+ notificationMobileIconStrokeWidth = 1.5,
53
+ notificationMobileIconStyle,
54
+ showNotificationBadgeOnMobile = false,
55
+ notificationMobileBadgeStyle,
56
+ // Desktop avatar / nombre (overrides)
57
+ desktopUserAvatarClassName = '',
58
+ desktopUserAvatarStyle,
59
+ desktopUserNameStyle,
60
+ // Mobile avatar (overrides)
61
+ mobileUserAvatarClassName = '',
62
+ mobileUserAvatarStyle,
15
63
  sidebarSections,
16
64
  sidebarActiveItem,
17
65
  onSidebarItemClick,
66
+ // Mobile: permitir persistir el item activo aunque el consumidor no controle sidebarActiveItem
67
+ enableInternalSidebarActiveItem = true,
18
68
  sidebarItemBadges,
19
69
  sidebarCompanyName,
20
70
  sidebarCompanyLogo,
@@ -27,6 +77,48 @@ export const HeaderCore = ({
27
77
  }) => {
28
78
  const [searchValue, setSearchValue] = useState('');
29
79
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
80
+ const [shouldRenderSidebarMobile, setShouldRenderSidebarMobile] = useState(false);
81
+ const [internalSidebarActiveItem, setInternalSidebarActiveItem] = useState(undefined);
82
+ const closeTimeoutRef = useRef(null);
83
+
84
+ const openSidebarMobile = () => {
85
+ if (closeTimeoutRef.current) {
86
+ clearTimeout(closeTimeoutRef.current);
87
+ closeTimeoutRef.current = null;
88
+ }
89
+ // Para que se vea la animación de apertura:
90
+ // 1) montamos el sidebar en estado cerrado
91
+ // 2) lo abrimos en el siguiente frame (después de pintar)
92
+ setShouldRenderSidebarMobile(true);
93
+ setIsSidebarOpen(false);
94
+ requestAnimationFrame(() => setIsSidebarOpen(true));
95
+ };
96
+
97
+ const closeSidebarMobile = () => {
98
+ setIsSidebarOpen(false);
99
+ };
100
+
101
+ const resolvedSidebarActiveItem =
102
+ sidebarActiveItem !== undefined && sidebarActiveItem !== null
103
+ ? sidebarActiveItem
104
+ : enableInternalSidebarActiveItem
105
+ ? internalSidebarActiveItem
106
+ : sidebarActiveItem;
107
+
108
+ useEffect(() => {
109
+ if (!isSidebarOpen && shouldRenderSidebarMobile) {
110
+ closeTimeoutRef.current = setTimeout(() => {
111
+ setShouldRenderSidebarMobile(false);
112
+ closeTimeoutRef.current = null;
113
+ }, mobileSidebarTransitionDurationMs);
114
+ }
115
+ return () => {
116
+ if (closeTimeoutRef.current) {
117
+ clearTimeout(closeTimeoutRef.current);
118
+ closeTimeoutRef.current = null;
119
+ }
120
+ };
121
+ }, [isSidebarOpen, shouldRenderSidebarMobile, mobileSidebarTransitionDurationMs]);
30
122
 
31
123
  const handleSearchChange = (e) => {
32
124
  const value = e.target.value;
@@ -45,7 +137,7 @@ export const HeaderCore = ({
45
137
 
46
138
  return (
47
139
  <header
48
- className={`bg-white border-b border-gray-200 ${className}`}
140
+ className={`bg-white ${showBottomBorder ? bottomBorderClassName : ''} ${className}`}
49
141
  {...props}
50
142
  >
51
143
  <style>{`
@@ -103,9 +195,15 @@ export const HeaderCore = ({
103
195
 
104
196
  <div className="w-full" style={{ paddingLeft: '4px', paddingRight: '4px' }}>
105
197
  {/* Layout Desktop (más de 768px) */}
106
- <div className="header-desktop-layout flex items-center justify-between h-16">
198
+ <div
199
+ className={`header-desktop-layout flex items-center justify-between h-16 ${desktopLayoutClassName}`}
200
+ style={desktopLayoutStyle}
201
+ >
107
202
  {/* Barra de búsqueda con icono a la derecha */}
108
- <div className="flex-1 mr-2 md:mr-4" style={{ maxWidth: 'none', minWidth: '0' }}>
203
+ <div
204
+ className={`flex-1 mr-2 md:mr-4 ${desktopSearchContainerClassName}`}
205
+ style={{ maxWidth: 'none', minWidth: '0', ...(desktopSearchContainerStyle || {}) }}
206
+ >
109
207
  <form onSubmit={handleSearchSubmit} className="header-search-form flex items-center w-full">
110
208
  {/* Input de búsqueda */}
111
209
  <input
@@ -151,28 +249,71 @@ export const HeaderCore = ({
151
249
  </div>
152
250
 
153
251
  {/* Notificaciones y Perfil */}
154
- <div className="flex items-center space-x-3 md:space-x-6 flex-shrink-0" style={{ marginLeft: 'auto', paddingRight: '0px' }}>
252
+ <div
253
+ className={`flex items-center space-x-3 md:space-x-6 flex-shrink-0 ${desktopRightContainerClassName}`}
254
+ style={{ marginLeft: 'auto', paddingRight: '0px', ...(desktopRightContainerStyle || {}) }}
255
+ >
155
256
  {/* Icono de notificaciones */}
156
257
  {notificationCount !== undefined && (
157
258
  <button
158
259
  onClick={onNotificationClick}
159
- className="relative p-2 hover:bg-gray-100 rounded-lg transition-colors"
260
+ className={`ml-3 p-2 hover:bg-gray-100 rounded-lg transition-colors flex items-center gap-2 ${notificationButtonClassName}`}
160
261
  aria-label="Notificaciones"
262
+ style={{
263
+ // Nota: en desktop el contenedor derecho está anclado a la derecha (marginLeft: auto).
264
+ // Un ml-* en el primer hijo no “mueve” visualmente, porque ensancha el contenedor hacia la izquierda.
265
+ // Por eso usamos translateX por defecto (overrideable por props).
266
+ transform: 'translateX(12px)',
267
+ ...(notificationButtonStyle || {}),
268
+ ...(notificationDesktopButtonStyle || {}),
269
+ }}
161
270
  >
162
- <Icon
163
- name="BellIcon"
271
+ {/* Contenedor 24x24 + icono posicionado (Figma) */}
272
+ <span
273
+ className={`relative inline-block ${notificationDesktopIconWrapperClassName}`}
274
+ style={{
275
+ width: '24px',
276
+ height: '24px',
277
+ opacity: 1,
278
+ ...(notificationDesktopIconWrapperStyle || {}),
279
+ }}
280
+ >
281
+ <Icon
282
+ name="BellIcon"
164
283
  variant="24-outline"
165
- size={24}
166
- className="color-gray-600"
284
+ size={notificationDesktopIconSize}
285
+ strokeWidth={notificationDesktopIconStrokeWidth}
286
+ className={`color-gray-600 ${notificationIconClassName}`}
287
+ style={{
288
+ width: '17.75112533569336px',
289
+ height: '18px',
290
+ position: 'absolute',
291
+ top: '3px',
292
+ left: '3.12px',
293
+ opacity: 1,
294
+ transform: 'rotate(0deg)',
295
+ ...(notificationIconStyle || {}),
296
+ ...(notificationDesktopIconStyle || {}),
297
+ }}
167
298
  />
299
+ </span>
168
300
  {notificationCount > 0 && (
169
301
  <span
170
- className="absolute -top-1 -right-1 px-1.5 py-0.5 min-w-[20px] h-5
171
- text-white rounded-full flex items-center justify-center
172
- text-body-sm font-medium"
302
+ className={`h-5 text-white flex items-center justify-center text-body-sm font-medium ${notificationBadgeClassName}`}
173
303
  style={{
174
304
  backgroundColor: '#6D3856',
175
- borderRadius: '12px'
305
+ minWidth: '24px',
306
+ width: 'fit-content',
307
+ height: '20px',
308
+ paddingTop: '2px',
309
+ paddingRight: '8px',
310
+ paddingBottom: '2px',
311
+ paddingLeft: '8px',
312
+ borderRadius: '8px',
313
+ opacity: 1,
314
+ transform: 'rotate(0deg)',
315
+ ...(notificationBadgeStyle || {}),
316
+ ...(notificationDesktopBadgeStyle || {}),
176
317
  }}
177
318
  >
178
319
  {notificationCount > 9 ? '9+' : notificationCount}
@@ -194,6 +335,15 @@ export const HeaderCore = ({
194
335
  name={userName}
195
336
  size="medium"
196
337
  variant="circle"
338
+ className={desktopUserAvatarClassName}
339
+ style={{
340
+ width: '36px',
341
+ height: '36px',
342
+ opacity: 1,
343
+ borderRadius: '16777200px',
344
+ transform: 'rotate(0deg)',
345
+ ...(desktopUserAvatarStyle || {}),
346
+ }}
197
347
  />
198
348
  <Typography
199
349
  variant="body-lg"
@@ -206,6 +356,7 @@ export const HeaderCore = ({
206
356
  lineHeight: '24px',
207
357
  letterSpacing: '0%',
208
358
  verticalAlign: 'middle'
359
+ ,...(desktopUserNameStyle || {})
209
360
  }}
210
361
  >
211
362
  {userName}
@@ -222,14 +373,17 @@ export const HeaderCore = ({
222
373
  </div>
223
374
 
224
375
  {/* Layout Mobile (768px o menos) */}
225
- <div className="header-mobile-layout">
376
+ <div
377
+ className={`header-mobile-layout ${mobileHeaderClassName}`}
378
+ style={{ background: mobileBackgroundColor, ...(mobileHeaderStyle || {}) }}
379
+ >
226
380
  {/* Primera fila: Menú, Core, Notificaciones y Avatar */}
227
381
  <div className="header-mobile-top-row">
228
382
  {/* Lado izquierdo: Menú hamburguesa y Core */}
229
383
  <div className="flex items-center space-x-2">
230
384
  {/* Botón menú hamburguesa */}
231
385
  <button
232
- onClick={() => setIsSidebarOpen(true)}
386
+ onClick={openSidebarMobile}
233
387
  className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
234
388
  aria-label="Menú"
235
389
  >
@@ -263,23 +417,66 @@ export const HeaderCore = ({
263
417
  {notificationCount !== undefined && (
264
418
  <button
265
419
  onClick={onNotificationClick}
266
- className="relative p-2 hover:bg-gray-100 rounded-lg transition-colors"
420
+ className={`ml-2 hover:bg-gray-100 transition-colors flex items-center justify-center ${notificationButtonClassName} ${notificationMobileButtonClassName}`}
267
421
  aria-label="Notificaciones"
422
+ style={{
423
+ width: '48px',
424
+ height: '48px',
425
+ paddingRight: '0px',
426
+ borderRadius: '8px',
427
+ opacity: 1,
428
+ transform: 'rotate(0deg)',
429
+ ...(notificationButtonStyle || {}),
430
+ ...(notificationMobileButtonStyle || {}),
431
+ }}
268
432
  >
269
- <Icon
270
- name="BellIcon"
433
+ {/* Contenedor 32x32 implícito dentro del botón 48x48 + icono posicionado (Figma) */}
434
+ <span
435
+ className={`relative inline-block ${notificationMobileIconWrapperClassName}`}
436
+ style={{
437
+ width: '32px',
438
+ height: '32px',
439
+ opacity: 1,
440
+ ...(notificationMobileIconWrapperStyle || {}),
441
+ }}
442
+ >
443
+ <Icon
444
+ name="BellIcon"
271
445
  variant="24-outline"
272
- size={24}
273
- className="color-gray-600"
274
- />
275
- {notificationCount > 0 && (
446
+ size={notificationMobileIconSize}
447
+ strokeWidth={notificationMobileIconStrokeWidth}
448
+ className={`color-gray-600 ${notificationIconClassName}`}
449
+ style={{
450
+ width: '22.163536071777344px',
451
+ height: '24px',
452
+ position: 'absolute',
453
+ top: '4px',
454
+ left: '4.92px',
455
+ opacity: 1,
456
+ transform: 'rotate(0deg)',
457
+ ...(notificationIconStyle || {}),
458
+ ...(notificationMobileIconStyle || {}),
459
+ }}
460
+ />
461
+ </span>
462
+ {showNotificationBadgeOnMobile && notificationCount > 0 && (
276
463
  <span
277
- className="absolute -top-1 -right-1 px-1.5 py-0.5 min-w-[20px] h-5
278
- text-white rounded-full flex items-center justify-center
279
- text-body-sm font-medium"
464
+ className={`h-5 text-white flex items-center justify-center text-body-sm font-medium ${notificationBadgeClassName}`}
280
465
  style={{
281
466
  backgroundColor: '#6D3856',
282
- borderRadius: '12px'
467
+ minWidth: '24px',
468
+ width: 'fit-content',
469
+ height: '20px',
470
+ paddingTop: '2px',
471
+ paddingRight: '8px',
472
+ paddingBottom: '2px',
473
+ paddingLeft: '8px',
474
+ borderRadius: '8px',
475
+ marginLeft: '6px',
476
+ opacity: 1,
477
+ transform: 'rotate(0deg)',
478
+ ...(notificationBadgeStyle || {}),
479
+ ...(notificationMobileBadgeStyle || {}),
283
480
  }}
284
481
  >
285
482
  {notificationCount > 9 ? '9+' : notificationCount}
@@ -300,6 +497,8 @@ export const HeaderCore = ({
300
497
  name={userName || ''}
301
498
  size="medium"
302
499
  variant="circle"
500
+ className={mobileUserAvatarClassName}
501
+ style={mobileUserAvatarStyle}
303
502
  />
304
503
  </button>
305
504
  )}
@@ -314,7 +513,7 @@ export const HeaderCore = ({
314
513
  type="text"
315
514
  value={searchValue}
316
515
  onChange={handleSearchChange}
317
- placeholder={searchPlaceholder}
516
+ placeholder={mobileSearchPlaceholder}
318
517
  className="header-search-input flex-1 pl-3 pr-3 py-2 bg-white border border-gray-400 rounded-l-lg
319
518
  focus:outline-none text-sm"
320
519
  style={{
@@ -354,17 +553,21 @@ export const HeaderCore = ({
354
553
  </div>
355
554
  </div>
356
555
 
357
- {/* SidebarCoreMobile - Solo visible en móvil cuando está abierto */}
358
- {isSidebarOpen && (
556
+ {/* SidebarCoreMobile - mantener montado para animación de cierre */}
557
+ {shouldRenderSidebarMobile && (
359
558
  <div className="header-core-mobile-sidebar-wrapper">
360
559
  <SidebarCoreMobile
361
560
  sections={sidebarSections}
362
- activeItem={sidebarActiveItem}
561
+ activeItem={resolvedSidebarActiveItem}
363
562
  onItemClick={(itemId) => {
563
+ if (enableInternalSidebarActiveItem) {
564
+ setInternalSidebarActiveItem(itemId);
565
+ }
364
566
  if (onSidebarItemClick) {
365
567
  onSidebarItemClick(itemId);
366
568
  }
367
- setIsSidebarOpen(false);
569
+ // permitir que el item se "pinte" y luego cerrar con animación
570
+ requestAnimationFrame(() => closeSidebarMobile());
368
571
  }}
369
572
  itemBadges={sidebarItemBadges}
370
573
  companyName={sidebarCompanyName}
@@ -374,7 +577,9 @@ export const HeaderCore = ({
374
577
  coreContainerStyle={sidebarCoreContainerStyle}
375
578
  coreTextStyle={sidebarCoreTextStyle}
376
579
  isOpen={isSidebarOpen}
377
- onClose={() => setIsSidebarOpen(false)}
580
+ onClose={closeSidebarMobile}
581
+ transitionDurationMs={mobileSidebarTransitionDurationMs}
582
+ transitionEasing={mobileSidebarTransitionEasing}
378
583
  />
379
584
  </div>
380
585
  )}
@@ -0,0 +1,262 @@
1
+ import { Typography } from '../../Atoms/Typography/Typography';
2
+ import { Icon } from '../../Atoms/Icon/Icon';
3
+
4
+ const defaultDateOptions = {
5
+ weekday: 'long',
6
+ day: '2-digit',
7
+ month: 'long',
8
+ year: 'numeric',
9
+ };
10
+
11
+ const coerceDate = (value) => {
12
+ if (!value) return null;
13
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
14
+ const d = new Date(value);
15
+ return Number.isNaN(d.getTime()) ? null : d;
16
+ };
17
+
18
+ const capitalizeFirst = (value) => {
19
+ if (!value) return value;
20
+ return value.charAt(0).toUpperCase() + value.slice(1);
21
+ };
22
+
23
+ /**
24
+ * SaludoConFechaDashboard
25
+ *
26
+ * Encabezado de dashboard con saludo y fecha formateada (locale-aware).
27
+ * Todas las medidas/estilos se pueden overridear por props para que el proyecto consumidor ajuste sin tocar la librería.
28
+ */
29
+ export const SaludoConFechaDashboard = ({
30
+ // Content
31
+ userName,
32
+ saludo = 'Hola',
33
+ date = new Date(),
34
+ showDate = true,
35
+
36
+ // Breadcrumb / inicio (arriba del saludo)
37
+ showInicio = true,
38
+ inicioText = 'Inicio',
39
+ showInicioIcon = true,
40
+ inicioIconName = 'ChevronRightIcon',
41
+ inicioIconVariant = '20-solid', // heroicons "mini"
42
+ inicioIconSize = 16,
43
+ // Responsive behavior (<= breakpoint): centrar saludo y ocultar el resto
44
+ responsiveBreakpointPx = 768,
45
+ responsiveHideInicio = true,
46
+ responsiveHideDate = true,
47
+ responsiveCenterGreeting = true,
48
+
49
+ // Date formatting
50
+ locale = 'es-AR',
51
+ dateOptions = defaultDateOptions,
52
+ formatDate, // (date, { locale, dateOptions }) => string
53
+ capitalizeDate = true,
54
+
55
+ // Layout / styles (overrideable)
56
+ className = '',
57
+ style,
58
+ leftClassName = '',
59
+ leftStyle,
60
+ rightClassName = '',
61
+ rightStyle,
62
+ saludoFechaContainerClassName = '',
63
+ saludoFechaContainerStyle,
64
+ inicioContainerClassName = '',
65
+ inicioContainerStyle,
66
+ inicioTextClassName = '',
67
+ inicioTextStyle,
68
+ inicioIconClassName = '',
69
+ inicioIconStyle,
70
+ greetingClassName = '',
71
+ greetingStyle,
72
+ greetingPrefix = '¡',
73
+ greetingSuffix = '!',
74
+ nameClassName = '',
75
+ nameStyle,
76
+ dateTextClassName = '',
77
+ dateTextStyle,
78
+ ...props
79
+ }) => {
80
+ const resolvedDate = showDate ? coerceDate(date) : null;
81
+
82
+ let formattedDate = '';
83
+ if (resolvedDate) {
84
+ if (typeof formatDate === 'function') {
85
+ formattedDate = formatDate(resolvedDate, { locale, dateOptions });
86
+ } else {
87
+ formattedDate = new Intl.DateTimeFormat(locale, dateOptions).format(resolvedDate);
88
+ }
89
+ if (capitalizeDate) {
90
+ formattedDate = capitalizeFirst(formattedDate);
91
+ }
92
+ }
93
+
94
+ return (
95
+ <div
96
+ className={`w-full ${className}`}
97
+ style={style}
98
+ {...props}
99
+ >
100
+ <style>{`
101
+ @media (max-width: ${responsiveBreakpointPx}px) {
102
+ .scfd-inicio {
103
+ display: ${responsiveHideInicio ? 'none' : 'flex'};
104
+ }
105
+ .scfd-date {
106
+ display: ${responsiveHideDate ? 'none' : 'flex'};
107
+ }
108
+ .scfd-saludo-fecha {
109
+ justify-content: ${responsiveCenterGreeting ? 'center' : 'space-between'};
110
+ }
111
+ .scfd-greeting-wrapper {
112
+ width: 100%;
113
+ }
114
+ .scfd-greeting {
115
+ text-align: ${responsiveCenterGreeting ? 'center' : 'left'};
116
+ }
117
+ }
118
+ `}</style>
119
+ {/* Grid de 2 filas:
120
+ - Fila 1: "Inicio >" (solo izquierda)
121
+ - Fila 2: "Hola, Nombre" (izquierda) + Fecha (derecha) alineados a la misma altura */}
122
+ <div className="grid grid-cols-[minmax(0,1fr)_auto] grid-rows-[auto_auto] gap-x-4 gap-y-1 items-center">
123
+ {showInicio && (
124
+ <div
125
+ className={`scfd-inicio col-start-1 row-start-1 flex items-center gap-1 ${inicioContainerClassName}`}
126
+ style={{
127
+ // Mantener "Inicio" alineado al mismo borde que "Hola" (padding-left del contenedor inferior)
128
+ paddingLeft: '8px',
129
+ ...(inicioContainerStyle || {}),
130
+ }}
131
+ >
132
+ <Typography
133
+ variant="body-md"
134
+ className={inicioTextClassName}
135
+ style={{
136
+ color: '#223B40',
137
+ fontFamily: 'IBM Plex Sans',
138
+ fontWeight: 700,
139
+ fontStyle: 'normal',
140
+ fontSize: '14px',
141
+ lineHeight: '20px',
142
+ letterSpacing: '0%',
143
+ verticalAlign: 'middle',
144
+ ...(inicioTextStyle || {}),
145
+ }}
146
+ >
147
+ {inicioText}
148
+ </Typography>
149
+ {showInicioIcon && inicioIconName ? (
150
+ <Icon
151
+ name={inicioIconName}
152
+ variant={inicioIconVariant}
153
+ size={inicioIconSize}
154
+ className={inicioIconClassName}
155
+ style={{
156
+ color: '#223B40',
157
+ opacity: 1,
158
+ transform: 'rotate(0deg)',
159
+ ...(inicioIconStyle || {}),
160
+ }}
161
+ />
162
+ ) : null}
163
+ </div>
164
+ )}
165
+
166
+ {/* Contenedor padre de la fila "Hola + Fecha" (Figma) */}
167
+ <div
168
+ className={`scfd-saludo-fecha col-start-1 col-end-3 row-start-2 flex items-center justify-between ${saludoFechaContainerClassName}`}
169
+ style={{
170
+ // Responsive: ocupar el ancho disponible sin generar scroll horizontal,
171
+ // manteniendo el ancho Figma como máximo.
172
+ width: '100%',
173
+ maxWidth: '1250px',
174
+ minHeight: '74px',
175
+ gap: '24px',
176
+ opacity: 1,
177
+ transform: 'rotate(0deg)',
178
+ paddingTop: '16px',
179
+ paddingBottom: '16px',
180
+ paddingLeft: '8px',
181
+ boxSizing: 'border-box',
182
+ ...(saludoFechaContainerStyle || {}),
183
+ }}
184
+ >
185
+ <div className={`scfd-greeting-wrapper min-w-0 ${leftClassName}`} style={leftStyle}>
186
+ <Typography
187
+ variant="h5"
188
+ className={`scfd-greeting ${greetingClassName}`}
189
+ style={{
190
+ color: '#223B40',
191
+ fontFamily: 'IBM Plex Sans',
192
+ fontWeight: 400,
193
+ fontStyle: 'normal',
194
+ fontSize: '28px',
195
+ lineHeight: '42px',
196
+ letterSpacing: '0%',
197
+ verticalAlign: 'middle',
198
+ ...(greetingStyle || {}),
199
+ }}
200
+ >
201
+ <span
202
+ style={{
203
+ display: 'inline-block',
204
+ verticalAlign: 'middle',
205
+ fontFamily: 'IBM Plex Sans',
206
+ fontWeight: 400,
207
+ fontStyle: 'normal',
208
+ fontSize: '28px',
209
+ lineHeight: '42px',
210
+ letterSpacing: '0%',
211
+ color: '#223B40',
212
+ }}
213
+ >
214
+ {greetingPrefix}
215
+ {saludo}
216
+ </span>
217
+ {userName ? (
218
+ <>
219
+ <span style={{ display: 'inline-block', verticalAlign: 'middle' }}>{',\u00A0'}</span>
220
+ <span
221
+ className={nameClassName}
222
+ style={{
223
+ display: 'inline-block',
224
+ verticalAlign: 'middle',
225
+ color: '#223B40',
226
+ fontFamily: 'IBM Plex Sans',
227
+ fontWeight: 400,
228
+ fontStyle: 'normal',
229
+ fontSize: '28px',
230
+ lineHeight: '42px',
231
+ letterSpacing: '0%',
232
+ ...(nameStyle || {}),
233
+ }}
234
+ >
235
+ {userName}
236
+ </span>
237
+ </>
238
+ ) : null}
239
+ <span style={{ display: 'inline-block', verticalAlign: 'middle' }}>{greetingSuffix}</span>
240
+ </Typography>
241
+ </div>
242
+
243
+ {showDate && formattedDate ? (
244
+ <div className={`scfd-date flex items-center flex-shrink-0 ${rightClassName}`} style={rightStyle}>
245
+ <Typography
246
+ variant="body-lg"
247
+ className={`color-gray-600 ${dateTextClassName}`}
248
+ style={dateTextStyle}
249
+ >
250
+ {formattedDate}
251
+ </Typography>
252
+ </div>
253
+ ) : null}
254
+ </div>
255
+ </div>
256
+ </div>
257
+ );
258
+ };
259
+
260
+ export default SaludoConFechaDashboard;
261
+
262
+