anima-ds-nucleus 1.0.17 → 1.0.18

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.
@@ -308,7 +308,7 @@ const SidebarCoreMobile = ({
308
308
  </div>
309
309
 
310
310
  {/* Items de la sección */}
311
- <div className="space-y-1">
311
+ <div>
312
312
  {section.items.map((item) => (
313
313
  <div key={item.id} className="px-4">
314
314
  <button
@@ -524,6 +524,7 @@ export const SidebarCore = ({
524
524
  footerCollapsedContent, // Contenido a mostrar en el footer cuando está colapsado (puede ser string, ReactNode, o objeto con icon y text)
525
525
  coreContainerStyle, // Estilos personalizados para el contenedor de LogoHexa y "Core" (solo mobile)
526
526
  coreTextStyle, // Estilos personalizados para el texto "Core" (solo mobile)
527
+ fluidWidth = false, // Si es true, el root respeta el ancho del contenedor (grid/flex) en lugar de usar anchos fijos
527
528
  className = '',
528
529
  ...props
529
530
  }) => {
@@ -609,15 +610,31 @@ export const SidebarCore = ({
609
610
  );
610
611
  }
611
612
 
613
+ // Determinar estilos del root según fluidWidth
614
+ const rootWidthStyles = fluidWidth
615
+ ? {
616
+ width: '100%',
617
+ maxWidth: '100%',
618
+ minWidth: 0,
619
+ boxSizing: 'border-box',
620
+ }
621
+ : isCollapsed
622
+ ? { width: desktopCollapsedWidth }
623
+ : {};
624
+
625
+ const rootClassName = fluidWidth
626
+ ? `bg-white transition-all duration-300 ease-in-out h-full ${className}`
627
+ : `bg-white transition-all duration-300 ease-in-out h-full ${
628
+ isCollapsed ? '' : 'w-64'
629
+ } ${className}`;
630
+
612
631
  return (
613
632
  <aside
614
- className={`bg-white transition-all duration-300 ease-in-out h-full ${
615
- isCollapsed ? '' : 'w-64'
616
- } ${className}`}
633
+ className={rootClassName}
617
634
  style={{
618
635
  border: '1px solid #C4C4C4',
619
636
  borderRadius: '16px',
620
- ...(isCollapsed ? { width: desktopCollapsedWidth } : {}),
637
+ ...rootWidthStyles,
621
638
  }}
622
639
  {...props}
623
640
  >
@@ -631,21 +648,21 @@ export const SidebarCore = ({
631
648
  flex items-center justify-between hover:bg-gray-50 transition-colors ${desktopCompanyButtonClassName}`}
632
649
  style={desktopCompanyButtonStyle}
633
650
  >
634
- <div className="flex items-center space-x-3">
651
+ <div className="flex items-center space-x-3 min-w-0 flex-1">
635
652
  {/* Logo hexagonal */}
636
653
  {companyLogo ? (
637
654
  <img
638
655
  src={companyLogo}
639
656
  alt={companyName}
640
- className="w-8 h-8 rounded"
657
+ className="w-8 h-8 rounded flex-shrink-0"
641
658
  />
642
659
  ) : (
643
- <LogoHexa width={36} height={40} />
660
+ <LogoHexa width={36} height={40} className="flex-shrink-0" />
644
661
  )}
645
662
  {/* Nombre de la empresa */}
646
663
  <Typography
647
664
  variant="body-md"
648
- className={desktopCompanyNameClassName}
665
+ className={`${desktopCompanyNameClassName} min-w-0`}
649
666
  style={{
650
667
  color: '#223B40',
651
668
  fontFamily: 'IBM Plex Sans',
@@ -656,6 +673,9 @@ export const SidebarCore = ({
656
673
  letterSpacing: '0px',
657
674
  opacity: 1,
658
675
  transform: 'rotate(0deg)',
676
+ overflow: 'hidden',
677
+ textOverflow: 'ellipsis',
678
+ whiteSpace: 'nowrap',
659
679
  ...(desktopCompanyNameStyle || {}),
660
680
  }}
661
681
  >
@@ -784,9 +804,9 @@ export const SidebarCore = ({
784
804
  >
785
805
  <button
786
806
  onClick={() => onItemClick && onItemClick('inicio')}
787
- className={`w-full flex items-center ${
788
- isCollapsed ? 'justify-center' : 'px-4 justify-between'
789
- } ${isCollapsed ? '' : 'py-2.5 rounded-lg'} transition-all duration-200 ${
807
+ className={`flex items-center ${
808
+ isCollapsed ? 'justify-center' : 'justify-between'
809
+ } ${isCollapsed ? '' : 'rounded-lg'} transition-all duration-200 ${
790
810
  activeItem === 'inicio'
791
811
  ? ''
792
812
  : 'color-gray-700 hover:bg-gray-100'
@@ -799,13 +819,22 @@ export const SidebarCore = ({
799
819
  backgroundColor: 'transparent',
800
820
  borderRadius: '8px',
801
821
  }
802
- : activeItem === 'inicio'
803
- ? { backgroundColor: '#2D5C63' }
804
- : {}
822
+ : {
823
+ width: '228px',
824
+ height: '40px',
825
+ paddingTop: '8px',
826
+ paddingRight: '12px',
827
+ paddingBottom: '8px',
828
+ paddingLeft: '12px',
829
+ gap: '12px',
830
+ borderRadius: '8px',
831
+ opacity: 1,
832
+ backgroundColor: activeItem === 'inicio' ? '#2D5C63' : 'transparent',
833
+ }
805
834
  }
806
835
  >
807
836
  <div
808
- className={`flex items-center ${isCollapsed ? '' : ''}`}
837
+ className={`flex items-center ${isCollapsed ? '' : 'min-w-0 flex-1'}`}
809
838
  style={
810
839
  isCollapsed
811
840
  ? {
@@ -813,7 +842,9 @@ export const SidebarCore = ({
813
842
  columnGap: `${desktopCollapsedOverlapColumnGapPx}px`,
814
843
  width: '100%',
815
844
  }
816
- : {}
845
+ : {
846
+ minWidth: 0,
847
+ }
817
848
  }
818
849
  >
819
850
  {isCollapsed ? (
@@ -883,8 +914,13 @@ export const SidebarCore = ({
883
914
  <Typography
884
915
  variant="body-lg"
885
916
  weight={activeItem === 'inicio' ? desktopItemLabelWeightActive : desktopItemLabelWeightInactive}
886
- className={activeItem === 'inicio' ? 'color-white' : 'color-gray-700'}
887
- style={{ fontSize: desktopItemLabelFontSize }}
917
+ className={`${activeItem === 'inicio' ? 'color-white' : 'color-gray-700'} min-w-0`}
918
+ style={{
919
+ fontSize: desktopItemLabelFontSize,
920
+ overflow: 'hidden',
921
+ textOverflow: 'ellipsis',
922
+ whiteSpace: 'nowrap',
923
+ }}
888
924
  >
889
925
  Inicio
890
926
  </Typography>
@@ -992,7 +1028,7 @@ export const SidebarCore = ({
992
1028
  )}
993
1029
 
994
1030
  {/* Items de la sección */}
995
- <div className="space-y-1">
1031
+ <div>
996
1032
  {section.items.map((item) => (
997
1033
  <div key={item.id} className="px-4">
998
1034
  <div
@@ -1011,9 +1047,9 @@ export const SidebarCore = ({
1011
1047
  >
1012
1048
  <button
1013
1049
  onClick={() => onItemClick && onItemClick(item.id)}
1014
- className={`w-full flex items-center ${
1015
- isCollapsed ? 'justify-center' : 'px-4 justify-between'
1016
- } ${isCollapsed ? '' : 'py-2.5 rounded-lg'} transition-all duration-200 ${
1050
+ className={`flex items-center ${
1051
+ isCollapsed ? 'justify-center' : 'justify-between'
1052
+ } ${isCollapsed ? '' : 'rounded-lg'} transition-all duration-200 ${
1017
1053
  activeItem === item.id
1018
1054
  ? ''
1019
1055
  : 'color-gray-700 hover:bg-gray-100'
@@ -1026,14 +1062,23 @@ export const SidebarCore = ({
1026
1062
  backgroundColor: 'transparent',
1027
1063
  borderRadius: '8px',
1028
1064
  }
1029
- : activeItem === item.id
1030
- ? { backgroundColor: '#2D5C63' }
1031
- : {}
1065
+ : {
1066
+ width: '228px',
1067
+ height: '40px',
1068
+ paddingTop: '8px',
1069
+ paddingRight: '12px',
1070
+ paddingBottom: '8px',
1071
+ paddingLeft: '12px',
1072
+ gap: '12px',
1073
+ borderRadius: '8px',
1074
+ opacity: 1,
1075
+ backgroundColor: activeItem === item.id ? '#2D5C63' : 'transparent',
1076
+ }
1032
1077
  }
1033
1078
  title={isCollapsed ? item.label : ''}
1034
1079
  >
1035
1080
  <div
1036
- className="flex items-center"
1081
+ className={`flex items-center ${isCollapsed ? '' : 'min-w-0 flex-1'}`}
1037
1082
  style={
1038
1083
  isCollapsed
1039
1084
  ? {
@@ -1041,7 +1086,9 @@ export const SidebarCore = ({
1041
1086
  columnGap: `${desktopCollapsedOverlapColumnGapPx}px`,
1042
1087
  width: '100%',
1043
1088
  }
1044
- : {}
1089
+ : {
1090
+ minWidth: 0,
1091
+ }
1045
1092
  }
1046
1093
  >
1047
1094
  {isCollapsed ? (
@@ -1111,8 +1158,13 @@ export const SidebarCore = ({
1111
1158
  <Typography
1112
1159
  variant="body-lg"
1113
1160
  weight={activeItem === item.id ? desktopItemLabelWeightActive : desktopItemLabelWeightInactive}
1114
- className={activeItem === item.id ? 'color-white' : 'color-gray-700'}
1115
- style={{ fontSize: desktopItemLabelFontSize }}
1161
+ className={`${activeItem === item.id ? 'color-white' : 'color-gray-700'} min-w-0`}
1162
+ style={{
1163
+ fontSize: desktopItemLabelFontSize,
1164
+ overflow: 'hidden',
1165
+ textOverflow: 'ellipsis',
1166
+ whiteSpace: 'nowrap',
1167
+ }}
1116
1168
  >
1117
1169
  {item.label}
1118
1170
  </Typography>
@@ -0,0 +1,341 @@
1
+ import { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button } from '../../Atoms/Button/Button';
4
+ import { Typography } from '../../Atoms/Typography/Typography';
5
+ import { Icon } from '../../Atoms/Icon/Icon';
6
+
7
+ export const ForgotPassword = ({
8
+ onBackToLogin,
9
+ onSubmit,
10
+ loading: externalLoading,
11
+ error: externalError,
12
+ className = '',
13
+ variant = 'default',
14
+ ...props
15
+ }) => {
16
+ const { t } = useTranslation();
17
+ const isHexaLogin = variant === 'hexa-login';
18
+
19
+ // Estados internos (si no se pasan como props)
20
+ const [email, setEmail] = useState('');
21
+ const [errors, setErrors] = useState({});
22
+ const [internalLoading, setInternalLoading] = useState(false);
23
+ const [resetLink, setResetLink] = useState(null);
24
+ const [internalError, setInternalError] = useState(null);
25
+
26
+ // Usar loading externo o interno
27
+ const loading = externalLoading !== undefined ? externalLoading : internalLoading;
28
+ // Usar error externo o interno
29
+ const error = externalError !== undefined ? externalError : internalError;
30
+
31
+ // Funciones de validación
32
+ const hasAtSymbol = (email) => {
33
+ return email && email.includes('@');
34
+ };
35
+
36
+ const isValidEmail = (email) => {
37
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
38
+ return emailRegex.test(email);
39
+ };
40
+
41
+ const validateForm = () => {
42
+ const newErrors = {};
43
+
44
+ if (!email) {
45
+ newErrors.email = t('errors.required') ?? 'Este campo es obligatorio';
46
+ } else if (!isValidEmail(email)) {
47
+ newErrors.email = t('errors.invalidEmail') ?? 'El mail ingresado no es válido';
48
+ }
49
+
50
+ setErrors(newErrors);
51
+ return Object.keys(newErrors).length === 0;
52
+ };
53
+
54
+ const handleEmailChange = (e) => {
55
+ const value = e.target.value;
56
+ setEmail(value);
57
+
58
+ // Limpiar errores al cambiar
59
+ if (errors.email) {
60
+ setErrors((prev) => ({ ...prev, email: null }));
61
+ }
62
+ // Si hay error interno, limpiarlo
63
+ if (internalError) {
64
+ setInternalError(null);
65
+ }
66
+ // Si hay error externo, no lo manejamos aquí (el componente padre debe manejarlo)
67
+ };
68
+
69
+ const handleSubmit = async (e) => {
70
+ e.preventDefault();
71
+
72
+ if (!validateForm()) {
73
+ return;
74
+ }
75
+
76
+ if (!onSubmit) {
77
+ console.warn('ForgotPassword: onSubmit prop is required');
78
+ return;
79
+ }
80
+
81
+ // Usar loading interno si no se pasa como prop
82
+ if (externalLoading === undefined) {
83
+ setInternalLoading(true);
84
+ }
85
+ setErrors({});
86
+ setInternalError(null);
87
+
88
+ try {
89
+ const result = await onSubmit({ email, countryCode: null });
90
+
91
+ if (result?.resetLink) {
92
+ setResetLink(result.resetLink);
93
+ } else {
94
+ // Si no hay resetLink pero no hay error, asumir éxito
95
+ setResetLink('success');
96
+ }
97
+ } catch (err) {
98
+ let errorMessage = t('forgotPassword.errors.sendFailed') ?? 'Error al enviar el link. Intenta nuevamente.';
99
+
100
+ if (err?.response?.status === 404) {
101
+ errorMessage = err?.response?.data?.message ||
102
+ err?.message ||
103
+ 'No se encontró una cuenta asociada a este correo electrónico. Por favor, verifica que el correo esté correcto.';
104
+ } else if (err?.response?.status === 400) {
105
+ errorMessage = err?.response?.data?.message ||
106
+ err?.message ||
107
+ 'El correo electrónico ingresado no es válido.';
108
+ } else if (err?.response?.status >= 500) {
109
+ errorMessage = 'Error del servidor. Por favor, intenta más tarde.';
110
+ } else if (err?.message) {
111
+ errorMessage = err.message;
112
+ }
113
+
114
+ if (externalError === undefined) {
115
+ setInternalError(errorMessage);
116
+ }
117
+ // Si el error viene como prop, el componente padre lo maneja
118
+ } finally {
119
+ if (externalLoading === undefined) {
120
+ setInternalLoading(false);
121
+ }
122
+ }
123
+ };
124
+
125
+ return (
126
+ <div
127
+ className={`min-h-screen flex flex-col ${className}`}
128
+ style={{ backgroundColor: '#f5f5f5' }}
129
+ {...props}
130
+ >
131
+ <div className="flex-1 flex items-center justify-center p-4">
132
+ <div className="w-full max-w-md">
133
+ {/* Título y subtítulo */}
134
+ <div className="mb-8 text-center">
135
+ <Typography
136
+ variant="h3"
137
+ className="mb-2 font-semibold"
138
+ style={{
139
+ color: '#2D5C63',
140
+ fontFamily: 'inherit',
141
+ fontWeight: 400,
142
+ fontStyle: 'normal',
143
+ fontSize: '32px',
144
+ lineHeight: '48px',
145
+ letterSpacing: '0px',
146
+ textAlign: 'center',
147
+ }}
148
+ >
149
+ {t('forgotPassword.title') ?? 'Recuperar contraseña'}
150
+ </Typography>
151
+ <Typography
152
+ variant="body2"
153
+ className="text-gray-600"
154
+ style={{
155
+ fontFamily: 'inherit',
156
+ fontWeight: 400,
157
+ fontStyle: 'normal',
158
+ fontSize: '16px',
159
+ lineHeight: '24px',
160
+ letterSpacing: '0px',
161
+ textAlign: 'center',
162
+ }}
163
+ >
164
+ {t('forgotPassword.subtitle') ?? 'Ingresa tu correo electrónico y te enviaremos un link para restablecer tu contraseña.'}
165
+ </Typography>
166
+ </div>
167
+
168
+ {/* Card blanca */}
169
+ <div className="bg-white border border-gray-200 rounded-lg p-6 shadow-md">
170
+ {/* Estado de éxito */}
171
+ {resetLink && !loading && (
172
+ <div className="text-center space-y-4">
173
+ <div
174
+ className="rounded-lg p-4 border"
175
+ style={{
176
+ backgroundColor: '#d1fae5',
177
+ borderColor: '#10b981',
178
+ }}
179
+ >
180
+ <Typography
181
+ variant="body2"
182
+ className="font-medium"
183
+ style={{ color: '#065f46' }}
184
+ >
185
+ {t('forgotPassword.success.title') ?? 'Link generado'}
186
+ </Typography>
187
+ <Typography
188
+ variant="body2"
189
+ className="mt-2"
190
+ style={{ color: '#047857', fontSize: '14px' }}
191
+ >
192
+ {t('forgotPassword.success.message') ?? 'Se ha generado un link de recuperación. Haz click en el enlace a continuación para restablecer tu contraseña.'}
193
+ </Typography>
194
+ </div>
195
+ {resetLink !== 'success' && (
196
+ <a
197
+ href={resetLink}
198
+ className="inline-block px-4 py-2 rounded-lg font-medium text-white hover:opacity-90 transition-opacity"
199
+ style={{ backgroundColor: '#2D5C63' }}
200
+ >
201
+ {t('forgotPassword.success.linkLabel') ?? 'Ir a restablecer contraseña'}
202
+ </a>
203
+ )}
204
+ </div>
205
+ )}
206
+
207
+ {/* Estado de carga */}
208
+ {loading && (
209
+ <div className="text-center space-y-4">
210
+ <div
211
+ className="rounded-lg p-4 border"
212
+ style={{
213
+ backgroundColor: '#dbeafe',
214
+ borderColor: '#3b82f6',
215
+ }}
216
+ >
217
+ <Typography
218
+ variant="body2"
219
+ className="font-medium"
220
+ style={{ color: '#1e40af' }}
221
+ >
222
+ {t('forgotPassword.sending') ?? 'Enviando...'}
223
+ </Typography>
224
+ <Typography
225
+ variant="body2"
226
+ className="mt-2"
227
+ style={{ color: '#1e3a8a', fontSize: '14px' }}
228
+ >
229
+ {t('forgotPassword.waitingMessage') ?? 'Espera, estamos procesando tu solicitud...'}
230
+ </Typography>
231
+ <Typography
232
+ variant="body2"
233
+ className="mt-2"
234
+ style={{ color: '#1e3a8a', fontSize: '13px' }}
235
+ >
236
+ {t('forgotPassword.waitingSubmessage') ?? 'Hemos enviado un link de recuperación a tu correo. Esto puede tardar unos momentos.'}
237
+ </Typography>
238
+ </div>
239
+ </div>
240
+ )}
241
+
242
+ {/* Estado de formulario */}
243
+ {!resetLink && !loading && (
244
+ <>
245
+ {/* Botón volver */}
246
+ {onBackToLogin && (
247
+ <button
248
+ type="button"
249
+ onClick={onBackToLogin}
250
+ className="flex items-center gap-2 mb-6 text-body-md hover:underline font-medium cursor-pointer"
251
+ style={{ color: '#2D5C63' }}
252
+ >
253
+ <Icon name="ArrowLeftIcon" variant="24-outline" size={18} />
254
+ <span>{t('forgotPassword.backToLogin') ?? 'Volver al inicio de sesión'}</span>
255
+ </button>
256
+ )}
257
+
258
+ {/* Formulario */}
259
+ <form onSubmit={handleSubmit} className="space-y-4">
260
+ {/* Input de email */}
261
+ <div>
262
+ <label
263
+ className="block mb-2 font-medium text-sm"
264
+ style={{ color: '#2D5C63' }}
265
+ >
266
+ {t('login.email') ?? t('form.email') ?? 'Correo electrónico'}
267
+ </label>
268
+ <input
269
+ type="email"
270
+ placeholder={t('login.emailPlaceholder') ?? t('placeholder.email') ?? 'Ingresa tu correo electrónico'}
271
+ value={email}
272
+ onChange={handleEmailChange}
273
+ className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm text-gray-900"
274
+ style={{
275
+ borderColor: errors.email ? '#ef4444' : '#2D5C63',
276
+ borderWidth: '1px',
277
+ borderStyle: 'solid',
278
+ }}
279
+ />
280
+ {errors.email && (
281
+ <p className="mt-1 text-sm text-red-600">{errors.email}</p>
282
+ )}
283
+ </div>
284
+
285
+ {/* Error general */}
286
+ {error && (
287
+ <div
288
+ className="rounded-lg p-4 border"
289
+ style={{
290
+ backgroundColor: '#fecaca',
291
+ borderColor: '#ef4444',
292
+ }}
293
+ >
294
+ <Typography
295
+ variant="body2"
296
+ className="font-bold"
297
+ style={{ color: '#b91c1c' }}
298
+ >
299
+ {error}
300
+ </Typography>
301
+ </div>
302
+ )}
303
+
304
+ {/* Botón de envío */}
305
+ <div className="pt-4">
306
+ <Button
307
+ tipo="Primary"
308
+ color="Teal"
309
+ tamaño="Default"
310
+ type="submit"
311
+ className="w-full"
312
+ disabled={loading || !hasAtSymbol(email)}
313
+ >
314
+ <span
315
+ style={{
316
+ fontFamily: "'IBM Plex Sans', sans-serif",
317
+ fontWeight: 400,
318
+ fontStyle: 'normal',
319
+ fontSize: '16px',
320
+ lineHeight: '24px',
321
+ letterSpacing: '0%',
322
+ }}
323
+ >
324
+ {loading
325
+ ? t('forgotPassword.sending') ?? 'Enviando...'
326
+ : t('forgotPassword.button') ?? 'Enviar link'}
327
+ </span>
328
+ </Button>
329
+ </div>
330
+ </form>
331
+ </>
332
+ )}
333
+ </div>
334
+ </div>
335
+ </div>
336
+ </div>
337
+ );
338
+ };
339
+
340
+ export default ForgotPassword;
341
+