anteriority-ui 1.0.0

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 (199) hide show
  1. package/.github/workflows/ci.yml +31 -0
  2. package/.github/workflows/publish.yml +45 -0
  3. package/.npmrc.bak +1 -0
  4. package/.storybook/main.js +15 -0
  5. package/.storybook/preview.jsx +26 -0
  6. package/AnteriorityUI.md +2400 -0
  7. package/CLAUDE.md +91 -0
  8. package/README.md +182 -0
  9. package/assets/anteriorityUI/AnteriorityUI.logo.jpeg +0 -0
  10. package/assets/favicon/anteriorityUI.ico +0 -0
  11. package/common/api/client.js +43 -0
  12. package/common/storage/LocalStorage.js +13 -0
  13. package/common/styles/typography.css +5 -0
  14. package/common/styles/variables.css +192 -0
  15. package/common/theme/muiTheme.js +9 -0
  16. package/features/password-reset/api.js +39 -0
  17. package/features/user-profile/api.js +5 -0
  18. package/hooks/useAdminPasswordReset.js +133 -0
  19. package/index.js +57 -0
  20. package/package.json +57 -0
  21. package/pnpm-workspace.yaml +3 -0
  22. package/screens/components/AlertModal/AlertModal.jsx +259 -0
  23. package/screens/components/AlertModal/AlertModal.module.css +185 -0
  24. package/screens/components/AlertModal/AlertModal.stories.jsx +87 -0
  25. package/screens/components/Avatar/index.jsx +0 -0
  26. package/screens/components/Avatar/index.module.css +0 -0
  27. package/screens/components/Avatar/utils.js +0 -0
  28. package/screens/components/Badge/index.jsx +33 -0
  29. package/screens/components/Badge/index.module.css +18 -0
  30. package/screens/components/Badge/index.stories.jsx +40 -0
  31. package/screens/components/Barnner/index.jsx +21 -0
  32. package/screens/components/Barnner/index.module.css +33 -0
  33. package/screens/components/Barnner/index.stories.jsx +39 -0
  34. package/screens/components/Barnner/utils.js +0 -0
  35. package/screens/components/BlueLink/index.jsx +27 -0
  36. package/screens/components/BlueLink/index.module.css +11 -0
  37. package/screens/components/BlueLink/index.stories.jsx +29 -0
  38. package/screens/components/Breadcrumbs/index.jsx +62 -0
  39. package/screens/components/Breadcrumbs/index.module.css +158 -0
  40. package/screens/components/Breadcrumbs/index.stories.jsx +37 -0
  41. package/screens/components/Button/index.jsx +169 -0
  42. package/screens/components/Button/index.module.css +168 -0
  43. package/screens/components/Button/index.stories.jsx +71 -0
  44. package/screens/components/Button/utils.js +28 -0
  45. package/screens/components/CalendarComponent/CustomDatePicker.jsx +248 -0
  46. package/screens/components/CalendarComponent/CustomDatePicker.module.css +323 -0
  47. package/screens/components/CalendarComponent/CustomDatePicker.stories.jsx +45 -0
  48. package/screens/components/CalendarPicker/Constants.js +12 -0
  49. package/screens/components/CalendarPicker/index.jsx +263 -0
  50. package/screens/components/CalendarPicker/index.module.css +147 -0
  51. package/screens/components/CalendarPicker/index.stories.jsx +45 -0
  52. package/screens/components/CalendarPicker/utils.js +42 -0
  53. package/screens/components/CardV1/index.jsx +165 -0
  54. package/screens/components/CardV1/index.module.css +232 -0
  55. package/screens/components/CardV1/index.stories.jsx +62 -0
  56. package/screens/components/CardV1/utils.js +49 -0
  57. package/screens/components/ChatWindow/index.jsx +251 -0
  58. package/screens/components/ChatWindow/index.module.css +311 -0
  59. package/screens/components/ChatWindow/index.stories.jsx +62 -0
  60. package/screens/components/Checks/index.jsx +31 -0
  61. package/screens/components/Checks/index.module.css +32 -0
  62. package/screens/components/Checks/index.stories.jsx +32 -0
  63. package/screens/components/Container/index.jsx +36 -0
  64. package/screens/components/Container/index.module.css +24 -0
  65. package/screens/components/Container/index.stories.jsx +32 -0
  66. package/screens/components/CopyText/index.jsx +42 -0
  67. package/screens/components/CopyText/index.module.css +52 -0
  68. package/screens/components/CopyText/index.stories.jsx +24 -0
  69. package/screens/components/CountdownBar/constants.js +3 -0
  70. package/screens/components/CountdownBar/index.jsx +81 -0
  71. package/screens/components/CountdownBar/index.module.css +45 -0
  72. package/screens/components/CountdownBar/index.stories.jsx +43 -0
  73. package/screens/components/CountdownBar/utils.js +119 -0
  74. package/screens/components/DateTag/index.jsx +33 -0
  75. package/screens/components/DateTag/index.module.css +12 -0
  76. package/screens/components/DateTag/index.stories.jsx +29 -0
  77. package/screens/components/DesplegablePanel/constants.js +0 -0
  78. package/screens/components/DesplegablePanel/index.jsx +61 -0
  79. package/screens/components/DesplegablePanel/index.module.css +120 -0
  80. package/screens/components/DesplegablePanel/index.stories.jsx +44 -0
  81. package/screens/components/DesplegablePanel/utils.js +0 -0
  82. package/screens/components/DrawPanel/index.jsx +217 -0
  83. package/screens/components/DrawPanel/index.module.css +252 -0
  84. package/screens/components/DrawPanel/index.stories.jsx +66 -0
  85. package/screens/components/DrawPanel/utils.js +0 -0
  86. package/screens/components/Filter/index.jsx +105 -0
  87. package/screens/components/Filter/index.module.css +91 -0
  88. package/screens/components/Filter/index.stories.jsx +46 -0
  89. package/screens/components/Filter/utils.js +0 -0
  90. package/screens/components/FormControls/FormControls.module.css +86 -0
  91. package/screens/components/FormControls/index.jsx +51 -0
  92. package/screens/components/FormControls/index.stories.jsx +36 -0
  93. package/screens/components/Frame/index.jsx +54 -0
  94. package/screens/components/Frame/index.module.css +102 -0
  95. package/screens/components/Frame/index.stories.jsx +37 -0
  96. package/screens/components/Frame/utils.js +0 -0
  97. package/screens/components/GreenHighlight/index.jsx +11 -0
  98. package/screens/components/GreenHighlight/index.module.css +7 -0
  99. package/screens/components/GreenHighlight/index.stories.jsx +26 -0
  100. package/screens/components/GreenHighlight/utils.jsx +45 -0
  101. package/screens/components/HoursPicker/Constants.js +26 -0
  102. package/screens/components/HoursPicker/index.jsx +350 -0
  103. package/screens/components/HoursPicker/index.module.css +99 -0
  104. package/screens/components/HoursPicker/index.stories.jsx +44 -0
  105. package/screens/components/HoursPicker/utils.js +48 -0
  106. package/screens/components/InfoTooltip/index.jsx +59 -0
  107. package/screens/components/InfoTooltip/index.module.css +98 -0
  108. package/screens/components/InfoTooltip/index.stories.jsx +35 -0
  109. package/screens/components/Input/index.jsx +36 -0
  110. package/screens/components/Input/index.module.css +49 -0
  111. package/screens/components/Input/index.stories.jsx +37 -0
  112. package/screens/components/Input/utils.js +0 -0
  113. package/screens/components/Loading/index.jsx +76 -0
  114. package/screens/components/Loading/index.module.css +193 -0
  115. package/screens/components/Loading/index.stories.jsx +38 -0
  116. package/screens/components/Loading/utils.js +0 -0
  117. package/screens/components/MarkdownContent/index.jsx +16 -0
  118. package/screens/components/MarkdownContent/index.module.css +149 -0
  119. package/screens/components/MarkdownContent/index.stories.jsx +30 -0
  120. package/screens/components/MarkdownEditor/index.jsx +430 -0
  121. package/screens/components/MarkdownEditor/index.module.css +192 -0
  122. package/screens/components/MarkdownEditor/index.stories.jsx +50 -0
  123. package/screens/components/Material-UI/Components/InputWithIcon/index.jsx +87 -0
  124. package/screens/components/Material-UI/Components/InputWithIcon/index.stories.jsx +49 -0
  125. package/screens/components/Material-UI/Components/MenuPopover/index.jsx +117 -0
  126. package/screens/components/Material-UI/Components/MenuPopover/index.stories.jsx +36 -0
  127. package/screens/components/Material-UI/Components/MultilinePlaceholder/index.jsx +44 -0
  128. package/screens/components/Material-UI/Components/MultilinePlaceholder/index.stories.jsx +45 -0
  129. package/screens/components/Material-UI/Components/Paginador/index.jsx +79 -0
  130. package/screens/components/Material-UI/Components/Paginador/index.stories.jsx +49 -0
  131. package/screens/components/Material-UI/Components/Search/index.jsx +81 -0
  132. package/screens/components/Material-UI/Components/Search/index.stories.jsx +68 -0
  133. package/screens/components/Material-UI/Components/Selector/index.jsx +100 -0
  134. package/screens/components/Material-UI/Components/Selector/index.module.css +0 -0
  135. package/screens/components/Material-UI/Components/Selector/index.stories.jsx +52 -0
  136. package/screens/components/Material-UI/Components/Selector/utils.js +0 -0
  137. package/screens/components/Material-UI/Components/TextField/index.jsx +61 -0
  138. package/screens/components/Material-UI/Components/TextField/index.stories.jsx +53 -0
  139. package/screens/components/ModalTooltip/ModalTooltip.jsx +122 -0
  140. package/screens/components/ModalTooltip/ModalTooltip.module.css +65 -0
  141. package/screens/components/ModalTooltip/ModalTooltip.stories.jsx +39 -0
  142. package/screens/components/Notification/Error/ErrorMessage.jsx +30 -0
  143. package/screens/components/Notification/Error/ErrorMessage.module.css +57 -0
  144. package/screens/components/Notification/Error/ErrorMessage.stories.jsx +37 -0
  145. package/screens/components/PasswordResetPanel/index.jsx +325 -0
  146. package/screens/components/PasswordResetPanel/index.stories.jsx +26 -0
  147. package/screens/components/PlaceholderButton/index.jsx +169 -0
  148. package/screens/components/PlaceholderButton/index.module.css +162 -0
  149. package/screens/components/PlaceholderButton/index.stories.jsx +69 -0
  150. package/screens/components/PlaceholderButton/utils.js +42 -0
  151. package/screens/components/Profile/Avatar/index.jsx +125 -0
  152. package/screens/components/Profile/Avatar/index.module.css +86 -0
  153. package/screens/components/Profile/Avatar/index.stories.jsx +48 -0
  154. package/screens/components/Profile/Avatar/utils.js +18 -0
  155. package/screens/components/Profile/index.jsx +58 -0
  156. package/screens/components/Profile/index.module.css +0 -0
  157. package/screens/components/Profile/index.stories.jsx +38 -0
  158. package/screens/components/Profile/utils.js +0 -0
  159. package/screens/components/ScrollBar/index.jsx +62 -0
  160. package/screens/components/ScrollBar/index.module.css +78 -0
  161. package/screens/components/ScrollBar/index.stories.jsx +64 -0
  162. package/screens/components/ScrollBar/utils.js +127 -0
  163. package/screens/components/Sidebar/constants.js +16 -0
  164. package/screens/components/Sidebar/index.jsx +399 -0
  165. package/screens/components/Sidebar/index.module.css +405 -0
  166. package/screens/components/Sidebar/index.stories.jsx +86 -0
  167. package/screens/components/Sidebar/utils.js +75 -0
  168. package/screens/components/Skeleton/index.jsx +11 -0
  169. package/screens/components/Skeleton/index.module.css +26 -0
  170. package/screens/components/Skeleton/index.stories.jsx +42 -0
  171. package/screens/components/SubSidebarLayout/index.jsx +44 -0
  172. package/screens/components/SubSidebarLayout/index.module.css +80 -0
  173. package/screens/components/SubSidebarLayout/index.stories.jsx +51 -0
  174. package/screens/components/SummaryCard/index.jsx +50 -0
  175. package/screens/components/SummaryCard/index.module.css +30 -0
  176. package/screens/components/SummaryCard/index.stories.jsx +29 -0
  177. package/screens/components/Swich/constants.js +4 -0
  178. package/screens/components/Swich/index.jsx +85 -0
  179. package/screens/components/Swich/index.module.css +68 -0
  180. package/screens/components/Swich/index.stories.jsx +44 -0
  181. package/screens/components/Swich/utils.js +0 -0
  182. package/screens/components/TableA/index.jsx +156 -0
  183. package/screens/components/TableA/index.module.css +105 -0
  184. package/screens/components/TableA/index.stories.jsx +116 -0
  185. package/screens/components/TableA/utils.js +11 -0
  186. package/screens/components/TableB/index.jsx +222 -0
  187. package/screens/components/TableB/index.module.css +295 -0
  188. package/screens/components/TableB/index.stories.jsx +110 -0
  189. package/screens/components/TableB/utils.js +11 -0
  190. package/screens/components/ToggleCard/index.jsx +64 -0
  191. package/screens/components/ToggleCard/index.module.css +111 -0
  192. package/screens/components/ToggleCard/index.stories.jsx +49 -0
  193. package/screens/components/UserEditPopover/index.jsx +102 -0
  194. package/screens/components/UserEditPopover/index.module.css +97 -0
  195. package/screens/components/UserEditPopover/index.stories.jsx +49 -0
  196. package/scripts/build.mjs +129 -0
  197. package/scripts/check-case.mjs +127 -0
  198. package/scripts/verify-dist.mjs +45 -0
  199. package/vitest.config.js +36 -0
@@ -0,0 +1,32 @@
1
+ .itemContainer {
2
+ display: flex !important;
3
+ align-items: center !important;
4
+ justify-content: flex-start !important;
5
+ width: 100%;
6
+ padding: 8px 12px;
7
+ border-radius: 8px;
8
+ cursor: pointer;
9
+ transition: background-color 0.2s ease;
10
+ text-align: left !important;
11
+ user-select: none;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ .itemContainer:hover {
16
+ background-color: rgba(0, 0, 0, 0.04);
17
+ }
18
+
19
+ .checked {
20
+ background-color: var(--checks-checked-bg, rgba(25, 118, 210, 0.08));
21
+ }
22
+
23
+ .label {
24
+ font-size: 0.95rem;
25
+ color: #e5e7eb;
26
+ margin-left: 8px;
27
+ }
28
+
29
+ .checked .label {
30
+ color: #94a3b8;
31
+ text-decoration: line-through;
32
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import PermissionCheckboxItem from './index';
3
+
4
+ export default {
5
+ title: 'Components/Checks',
6
+ component: PermissionCheckboxItem,
7
+ };
8
+
9
+ export const Unchecked = {
10
+ args: {
11
+ label: 'Ver historial clínico',
12
+ checked: false,
13
+ onChange: () => console.log('toggled'),
14
+ },
15
+ };
16
+
17
+ export const Checked = {
18
+ args: {
19
+ label: 'Editar información de pacientes',
20
+ checked: true,
21
+ onChange: () => console.log('toggled'),
22
+ },
23
+ };
24
+
25
+ export const Disabled = {
26
+ args: {
27
+ label: 'Eliminar registros (bloqueado)',
28
+ checked: false,
29
+ disabled: true,
30
+ onChange: () => console.log('toggled'),
31
+ },
32
+ };
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import styles from './index.module.css';
3
+ import Paginador from '../Material-UI/Components/Paginador';
4
+
5
+ export default function Container({
6
+ children,
7
+ className = '',
8
+ showPagination = false,
9
+ paginationCount = 10,
10
+ paginationPage = 1,
11
+ onPaginationChange,
12
+ paginationProps = {},
13
+ pageSize = 10,
14
+ pageSizeOptions = [5, 10, 20, 50],
15
+ onPageSizeChange,
16
+ ...props
17
+ }) {
18
+ return (
19
+ <div className={`${styles.containerCard} ${className}`} {...props}>
20
+ {children}
21
+ {showPagination && (
22
+ <div className={styles.paginationContainer}>
23
+ <Paginador
24
+ count={paginationCount}
25
+ page={paginationPage}
26
+ onChange={onPaginationChange}
27
+ pageSize={pageSize}
28
+ pageSizeOptions={pageSizeOptions}
29
+ onPageSizeChange={onPageSizeChange}
30
+ {...paginationProps}
31
+ />
32
+ </div>
33
+ )}
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,24 @@
1
+ .containerCard {
2
+ position: relative;
3
+ width: 100%;
4
+ max-width: 1300px;
5
+ background: var(--letter-white, #ffffff);
6
+ backdrop-filter: var(--blur, blur(10px));
7
+ -webkit-backdrop-filter: var(--blur, blur(10px));
8
+ padding: 35px;
9
+ border-radius: 12px;
10
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
11
+ border: 1px solid var(--logo-yellow-border, #e2e8f0);
12
+ min-height: 550px; /* Tamaño estático/mínimo por defecto para evitar saltos al cargar */
13
+ box-sizing: border-box;
14
+ display: flex;
15
+ flex-direction: column;
16
+ }
17
+
18
+ @media (max-width: 768px) {
19
+ .containerCard {
20
+ max-width: 100%;
21
+ padding: 1.5rem;
22
+ min-height: auto; /* Permite que sea fluido en móviles */
23
+ }
24
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import Container from './index';
3
+
4
+ export default {
5
+ title: 'Components/Container',
6
+ component: Container,
7
+ };
8
+
9
+ export const Default = {
10
+ args: {
11
+ children: <p>Contenido de ejemplo dentro del contenedor.</p>,
12
+ },
13
+ };
14
+
15
+ export const WithPagination = {
16
+ args: {
17
+ children: <p>Lista de resultados paginados.</p>,
18
+ showPagination: true,
19
+ paginationCount: 50,
20
+ paginationPage: 1,
21
+ pageSize: 10,
22
+ onPaginationChange: (page) => console.log('page changed', page),
23
+ onPageSizeChange: (size) => console.log('page size changed', size),
24
+ },
25
+ };
26
+
27
+ export const CustomClassName = {
28
+ args: {
29
+ children: <p>Contenedor con clase personalizada.</p>,
30
+ className: 'custom-container',
31
+ },
32
+ };
@@ -0,0 +1,42 @@
1
+ import React, { useState } from 'react';
2
+ import { Copy, Check } from 'lucide-react';
3
+ import ModalTooltip from '../ModalTooltip/ModalTooltip';
4
+ import styles from './index.module.css';
5
+
6
+ export default function CopyableText({ text, bgColor, borderColor, textColor }) {
7
+
8
+ const [copied, setCopied] = useState(false);
9
+
10
+ const handleCopy = async (e) => {
11
+ e.preventDefault();
12
+ e.stopPropagation();
13
+ try {
14
+ await navigator.clipboard.writeText(text);
15
+ setCopied(true);
16
+ setTimeout(() => setCopied(false), 2000);
17
+ } catch (err) {
18
+ console.error('Error al copiar el texto: ', err);
19
+ }
20
+ };
21
+
22
+ return (
23
+ <ModalTooltip text={copied ? "¡Copiado!" : "Haga clic para copiar"}>
24
+ <span
25
+ className={styles.copyableContainer}
26
+ onClick={handleCopy}
27
+ style={{
28
+ ...(bgColor ? { '--copytext-bg': bgColor } : {}),
29
+ ...(borderColor ? { '--copytext-border': borderColor } : {}),
30
+ ...(textColor ? { '--copytext-text': textColor } : {}),
31
+ }}
32
+ >
33
+ <span className={styles.copyableText}>{text}</span>
34
+ {copied ? (
35
+ <Check size={14} className={styles.copyIconSuccess} />
36
+ ) : (
37
+ <Copy size={14} className={styles.copyIcon} />
38
+ )}
39
+ </span>
40
+ </ModalTooltip>
41
+ );
42
+ }
@@ -0,0 +1,52 @@
1
+ .copyableContainer {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 6px;
5
+ background-color: var(--copytext-bg, rgba(226, 232, 240, 0.6)); /* Fondo gris suave e interactivo */
6
+ border: 1px solid var(--copytext-border, #cbd5e1);
7
+ padding: 2px 8px;
8
+ border-radius: 6px;
9
+ cursor: pointer;
10
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; /* Tipografía limpia para datos copiables */
11
+ font-weight: 600;
12
+ color: var(--copytext-text, #334155);
13
+ transition: all 0.2s ease;
14
+ user-select: all;
15
+ }
16
+
17
+ .copyableContainer:hover {
18
+ background-color: #e2e8f0;
19
+ border-color: #94a3b8;
20
+ }
21
+
22
+ .copyableText {
23
+ font-size: 0.9em;
24
+ line-height: 1.2;
25
+ }
26
+
27
+ .copyIcon {
28
+ color: #64748b;
29
+ flex-shrink: 0;
30
+ transition: color 0.2s ease;
31
+ }
32
+
33
+ .copyableContainer:hover .copyIcon {
34
+ color: #334155;
35
+ }
36
+
37
+ .copyIconSuccess {
38
+ color: #10b981; /* Verde esmeralda para el feedback visual */
39
+ flex-shrink: 0;
40
+ animation: popIn 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
41
+ }
42
+
43
+ @keyframes popIn {
44
+ 0% {
45
+ transform: scale(0.6);
46
+ opacity: 0;
47
+ }
48
+ 100% {
49
+ transform: scale(1);
50
+ opacity: 1;
51
+ }
52
+ }
@@ -0,0 +1,24 @@
1
+ import CopyableText from './index.jsx';
2
+
3
+ export default {
4
+ title: 'Components/CopyText',
5
+ component: CopyableText,
6
+ };
7
+
8
+ export const Default = {
9
+ args: {
10
+ text: 'ABC-12345-XYZ',
11
+ },
12
+ };
13
+
14
+ export const Email = {
15
+ args: {
16
+ text: 'contacto@empresa.com',
17
+ },
18
+ };
19
+
20
+ export const LongCode = {
21
+ args: {
22
+ text: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
23
+ },
24
+ };
@@ -0,0 +1,3 @@
1
+ export const TEXTS = {
2
+ remainingTime: 'Tiempo restante'
3
+ };
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import styles from './index.module.css';
3
+ import { useCountdown, getColorForProgress } from './utils';
4
+ import ModalTooltip from '../ModalTooltip/ModalTooltip';
5
+
6
+ export default function CountdownBar({
7
+ active,
8
+ duration = 10000,
9
+ onComplete,
10
+ height = '6px',
11
+ width = '100%',
12
+ colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6'], // Del rojo (acabándose) al azul (inicio)
13
+ expiresAt,
14
+ totalDuration = 900000,
15
+ value,
16
+ increment = false,
17
+ isOverlay = false,
18
+ overlayMessage
19
+ }) {
20
+ const timerState = useCountdown(active && value === undefined, duration, onComplete, expiresAt, totalDuration);
21
+
22
+ let progress = value !== undefined ? value : timerState.progress;
23
+ if (increment && value === undefined) {
24
+ progress = 100 - progress;
25
+ }
26
+ const remainingSeconds = timerState.remainingSeconds;
27
+ const remainingTime = remainingSeconds ? remainingSeconds.toFixed(1) : '0.0';
28
+
29
+ if (!active) return null;
30
+
31
+ const currentColor = getColorForProgress(progress, colors);
32
+
33
+ const tooltipText = value !== undefined
34
+ ? `${Math.round(progress)}% completado`
35
+ : `${remainingTime}s restantes`;
36
+
37
+ const barContent = (
38
+ <div className={styles.wrapper} style={{ width }}>
39
+ <div
40
+ className={styles.container}
41
+ style={{ height }}
42
+ aria-valuemin="0"
43
+ aria-valuemax="100"
44
+ aria-valuenow={progress}
45
+ role="progressbar"
46
+ >
47
+ <div
48
+ className={styles.bar}
49
+ style={{
50
+ width: `${progress}%`,
51
+ // Crea un sutil gradiente interno que se mezcla de forma suave
52
+ backgroundImage: `linear-gradient(90deg, ${currentColor}, ${currentColor}dd)`
53
+ }}
54
+ />
55
+ </div>
56
+ </div>
57
+ );
58
+
59
+ if (isOverlay) {
60
+ return (
61
+ <div className={styles.overlayContainer}>
62
+ {overlayMessage && (
63
+ <p className={styles.overlayMessage}>
64
+ {overlayMessage}
65
+ </p>
66
+ )}
67
+ <div style={{ width: '80%' }}>
68
+ <ModalTooltip text={tooltipText} style={{ width: '100%', display: 'block' }}>
69
+ {barContent}
70
+ </ModalTooltip>
71
+ </div>
72
+ </div>
73
+ );
74
+ }
75
+
76
+ return (
77
+ <ModalTooltip text={tooltipText} style={{ width, display: 'block' }}>
78
+ {barContent}
79
+ </ModalTooltip>
80
+ );
81
+ }
@@ -0,0 +1,45 @@
1
+ .wrapper {
2
+ margin-top: 8px;
3
+ cursor: pointer;
4
+ }
5
+
6
+ .container {
7
+ width: 100%;
8
+ background-color: #f1f5f9;
9
+ border-radius: 3px;
10
+ overflow: hidden;
11
+ border: 1px solid #e2e8f0;
12
+ }
13
+
14
+ .bar {
15
+ height: 100%;
16
+ border-radius: 3px;
17
+ transition: width 0.05s linear, background-color 0.3s ease;
18
+ }
19
+
20
+ .overlayContainer {
21
+ position: absolute;
22
+ top: 0;
23
+ left: 0;
24
+ right: 0;
25
+ bottom: 0;
26
+ background-color: rgba(255, 255, 255, 0.4);
27
+ backdrop-filter: blur(5px);
28
+ display: flex;
29
+ flex-direction: column;
30
+ justify-content: center;
31
+ align-items: center;
32
+ z-index: 100;
33
+ border-radius: 4px;
34
+ padding: 0.75rem;
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ .overlayMessage {
39
+ color: #ff0000;
40
+ font-size: 0.95rem;
41
+ font-weight: 700;
42
+ text-align: center;
43
+ margin: 0 0 1rem 0;
44
+ line-height: 1.4;
45
+ }
@@ -0,0 +1,43 @@
1
+ import CountdownBar from './index.jsx';
2
+
3
+ export default {
4
+ title: 'Components/CountdownBar',
5
+ component: CountdownBar,
6
+ };
7
+
8
+ export const Default = {
9
+ args: {
10
+ active: true,
11
+ duration: 10000,
12
+ onComplete: () => console.log('countdown complete'),
13
+ },
14
+ };
15
+
16
+ export const CustomColors = {
17
+ args: {
18
+ active: true,
19
+ duration: 15000,
20
+ height: '10px',
21
+ colors: ['#ef4444', '#f97316', '#22c55e'],
22
+ onComplete: () => console.log('countdown complete'),
23
+ },
24
+ };
25
+
26
+ export const StaticValue = {
27
+ args: {
28
+ active: true,
29
+ value: 65,
30
+ increment: true,
31
+ height: '8px',
32
+ },
33
+ };
34
+
35
+ export const Overlay = {
36
+ args: {
37
+ active: true,
38
+ duration: 8000,
39
+ isOverlay: true,
40
+ overlayMessage: 'Guardando cambios, no cierres esta ventana...',
41
+ onComplete: () => console.log('countdown complete'),
42
+ },
43
+ };
@@ -0,0 +1,119 @@
1
+ import { useEffect, useState, useRef } from 'react';
2
+
3
+ export function useCountdown(active, duration, onComplete, expiresAt, totalDuration = 900000) {
4
+ const [state, setState] = useState({ progress: 100, remainingSeconds: duration / 1000 });
5
+
6
+ // Refs to store stable values
7
+ const expiresAtRef = useRef(expiresAt);
8
+ const totalDurationRef = useRef(totalDuration);
9
+ const durationRef = useRef(duration);
10
+ const onCompleteRef = useRef(onComplete);
11
+
12
+ // Keep refs updated with latest prop values
13
+ useEffect(() => {
14
+ if (!active) {
15
+ expiresAtRef.current = null;
16
+ totalDurationRef.current = null;
17
+ } else {
18
+ // Only capture the initial value when it becomes active
19
+ if (expiresAt && !expiresAtRef.current) {
20
+ expiresAtRef.current = expiresAt;
21
+ }
22
+ if (totalDuration && !totalDurationRef.current) {
23
+ totalDurationRef.current = totalDuration;
24
+ }
25
+ }
26
+ }, [active, expiresAt, totalDuration]);
27
+
28
+ // Keep other refs updated
29
+ useEffect(() => {
30
+ durationRef.current = duration;
31
+ onCompleteRef.current = onComplete;
32
+ }, [duration, onComplete]);
33
+
34
+ useEffect(() => {
35
+ if (!active) {
36
+ setState({ progress: 100, remainingSeconds: durationRef.current / 1000 });
37
+ return;
38
+ }
39
+
40
+ const startTime = Date.now();
41
+ const interval = 50;
42
+
43
+ const timer = setInterval(() => {
44
+ let remaining;
45
+ let percentage;
46
+
47
+ const targetExpiresAt = expiresAtRef.current;
48
+ const targetTotalDuration = totalDurationRef.current || 900000;
49
+
50
+ if (targetExpiresAt) {
51
+ const expirationTime = new Date(targetExpiresAt).getTime();
52
+ remaining = Math.max(0, expirationTime - Date.now());
53
+ percentage = (remaining / targetTotalDuration) * 100;
54
+ } else {
55
+ const elapsed = Date.now() - startTime;
56
+ remaining = Math.max(0, durationRef.current - elapsed);
57
+ percentage = (remaining / durationRef.current) * 100;
58
+ }
59
+
60
+ const finalProgress = Math.max(0, Math.min(100, percentage));
61
+ setState({
62
+ progress: finalProgress,
63
+ remainingSeconds: remaining / 1000
64
+ });
65
+
66
+ if (remaining <= 0) {
67
+ clearInterval(timer);
68
+ if (onCompleteRef.current) {
69
+ onCompleteRef.current();
70
+ }
71
+ }
72
+ }, interval);
73
+
74
+ return () => clearInterval(timer);
75
+ }, [active]);
76
+
77
+ return state;
78
+ }
79
+
80
+ // Funciones auxiliares para interpolar colores en formato HEX
81
+ function hexToRgb(hex) {
82
+ const cleanHex = hex.replace('#', '');
83
+ const bigint = parseInt(cleanHex, 16);
84
+ return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255];
85
+ }
86
+
87
+ function rgbToHex(r, g, b) {
88
+ return '#' + [r, g, b].map(x => {
89
+ const hex = Math.round(x).toString(16);
90
+ return hex.length === 1 ? '0' + hex : hex;
91
+ }).join('');
92
+ }
93
+
94
+ function interpolateColor(color1, color2, factor) {
95
+ const c1 = hexToRgb(color1);
96
+ const c2 = hexToRgb(color2);
97
+ const r = c1[0] + factor * (c2[0] - c1[0]);
98
+ const g = c1[1] + factor * (c2[1] - c1[1]);
99
+ const b = c1[2] + factor * (c2[2] - c1[2]);
100
+ return rgbToHex(r, g, b);
101
+ }
102
+
103
+ export function getColorForProgress(progress, customColors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6']) {
104
+ // progress va de 100 (inicio) a 0 (fin)
105
+ // Invertimos el cálculo para que 100% sea el inicio del array y 0% sea el final
106
+ const p = Math.max(0, Math.min(100, progress)) / 100;
107
+
108
+ if (customColors.length < 2) return customColors[0] || '#3b82f6';
109
+
110
+ const scaled = p * (customColors.length - 1);
111
+ const index = Math.floor(scaled);
112
+ const factor = scaled - index;
113
+
114
+ if (index >= customColors.length - 1) {
115
+ return customColors[customColors.length - 1];
116
+ }
117
+
118
+ return interpolateColor(customColors[index], customColors[index + 1], factor);
119
+ }
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { Calendar } from 'lucide-react';
3
+ import styles from './index.module.css';
4
+
5
+ export default function DateTag({ date, bgColor, textColor, iconColor, style = {} }) {
6
+ if (!date) return null;
7
+
8
+ let formattedDate = '';
9
+ try {
10
+ formattedDate = new Date(date).toLocaleString('es-ES', {
11
+ day: '2-digit',
12
+ month: '2-digit',
13
+ year: 'numeric',
14
+ hour: '2-digit',
15
+ minute: '2-digit'
16
+ });
17
+ } catch (e) {
18
+ formattedDate = String(date);
19
+ }
20
+
21
+ const customStyles = {
22
+ backgroundColor: bgColor || 'var(--tag-bg, #f1f5f9)',
23
+ color: textColor || 'var(--tag-text, #475569)',
24
+ ...style
25
+ };
26
+
27
+ return (
28
+ <div className={styles.tag} style={customStyles}>
29
+ <Calendar size={14} color={iconColor || textColor || 'var(--tag-text, #475569)'} />
30
+ <span>{formattedDate}</span>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,12 @@
1
+ .tag {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 6px;
5
+ padding: 4px 8px;
6
+ border-radius: 4px;
7
+ font-size: 0.75rem;
8
+ font-weight: 500;
9
+ border: 1px solid rgba(0, 0, 0, 0.05);
10
+ width: fit-content;
11
+ white-space: nowrap;
12
+ }
@@ -0,0 +1,29 @@
1
+ import DateTag from './index.jsx';
2
+
3
+ export default {
4
+ title: 'Components/DateTag',
5
+ component: DateTag,
6
+ };
7
+
8
+ export const Default = {
9
+ args: {
10
+ date: '2026-09-12T10:30:00',
11
+ },
12
+ };
13
+
14
+ export const CustomColors = {
15
+ args: {
16
+ date: '2026-01-05T08:00:00',
17
+ bgColor: '#fee2e2',
18
+ textColor: '#b91c1c',
19
+ },
20
+ };
21
+
22
+ export const CustomIconColor = {
23
+ args: {
24
+ date: '2026-12-25T00:00:00',
25
+ bgColor: '#dcfce7',
26
+ textColor: '#166534',
27
+ iconColor: '#16a34a',
28
+ },
29
+ };