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/dist/anima-ds-nucleus.css +1 -1
- package/dist/anima-ds.cjs.js +80 -66
- package/dist/anima-ds.esm.js +2885 -2375
- package/package.json +1 -1
- package/src/components/Layout/Header/HeaderCore.jsx +238 -33
- package/src/components/Layout/SaludoConFechaDashboard/SaludoConFechaDashboard.jsx +262 -0
- package/src/components/Layout/SaludoConFechaDashboard/SaludoConFechaDashboard.stories.jsx +64 -0
- package/src/components/Layout/Sidebar/SidebarCore.jsx +144 -39
- package/src/index.js +1 -0
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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=
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
|
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={
|
|
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=
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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=
|
|
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
|
-
|
|
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={
|
|
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 -
|
|
358
|
-
{
|
|
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={
|
|
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
|
-
|
|
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={
|
|
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
|
+
|