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,87 @@
1
+ import React, { useState } from 'react';
2
+ import AlertModal from './AlertModal';
3
+
4
+ export default {
5
+ title: 'Components/AlertModal',
6
+ component: AlertModal,
7
+ };
8
+
9
+ export const Plain = {
10
+ args: {
11
+ open: true,
12
+ title: 'Alerta',
13
+ message: '¿Estás seguro de que deseas continuar con esta acción?',
14
+ confirmText: 'Ok',
15
+ cancelText: 'Cancelar',
16
+ onClose: () => console.log('closed'),
17
+ onConfirm: () => console.log('confirmed'),
18
+ },
19
+ };
20
+
21
+ export const WithTextInput = {
22
+ render: (args) => {
23
+ const [value, setValue] = useState('');
24
+ return (
25
+ <AlertModal
26
+ {...args}
27
+ inputValue={value}
28
+ onInputChange={(e) => setValue(e.target.value)}
29
+ />
30
+ );
31
+ },
32
+ args: {
33
+ open: true,
34
+ title: 'Renombrar elemento',
35
+ message: 'Ingresa el nuevo nombre para continuar.',
36
+ showInput: true,
37
+ inputType: 'text',
38
+ inputLabel: 'Nombre',
39
+ inputPlaceholder: 'Escribe un nombre...',
40
+ confirmText: 'Guardar',
41
+ cancelText: 'Cancelar',
42
+ onClose: () => console.log('closed'),
43
+ onConfirm: () => console.log('confirmed'),
44
+ },
45
+ };
46
+
47
+ export const WithSelectInput = {
48
+ render: (args) => {
49
+ const [value, setValue] = useState('');
50
+ return (
51
+ <AlertModal
52
+ {...args}
53
+ inputValue={value}
54
+ onInputChange={(e) => setValue(e.target.value)}
55
+ />
56
+ );
57
+ },
58
+ args: {
59
+ open: true,
60
+ title: 'Seleccionar motivo',
61
+ message: 'Elige el motivo de esta acción.',
62
+ showInput: true,
63
+ inputType: 'select',
64
+ inputLabel: 'Motivo',
65
+ inputPlaceholder: 'Selecciona una opción',
66
+ inputOptions: [
67
+ { value: 'duplicado', label: 'Registro duplicado' },
68
+ { value: 'error', label: 'Error de captura' },
69
+ { value: 'otro', label: 'Otro motivo' },
70
+ ],
71
+ confirmText: 'Aceptar',
72
+ cancelText: 'Cancelar',
73
+ onClose: () => console.log('closed'),
74
+ onConfirm: () => console.log('confirmed'),
75
+ },
76
+ };
77
+
78
+ export const WithImage = {
79
+ args: {
80
+ open: true,
81
+ title: 'Foto de Perfil',
82
+ imageUrl: 'https://i.pravatar.cc/300?img=12',
83
+ confirmText: 'Cerrar',
84
+ showWarningIcon: false,
85
+ onClose: () => console.log('closed'),
86
+ },
87
+ };
File without changes
File without changes
File without changes
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import styles from './index.module.css';
3
+
4
+ const Badge = ({
5
+ icon: Icon,
6
+ label,
7
+ value,
8
+ iconColor = "#000000ff",
9
+ bgColor,
10
+ borderColor,
11
+ labelColor,
12
+ valueColor,
13
+ style = {},
14
+ }) => {
15
+ return (
16
+ <div
17
+ className={styles.badgeContainer}
18
+ style={{
19
+ ...(bgColor ? { '--badge-bg': bgColor } : {}),
20
+ ...(borderColor ? { '--badge-border': borderColor } : {}),
21
+ ...(labelColor ? { '--badge-label': labelColor } : {}),
22
+ ...(valueColor ? { '--badge-value': valueColor } : {}),
23
+ ...style,
24
+ }}
25
+ >
26
+ {Icon && <Icon size={18} color={iconColor} />}
27
+ {label && <span className={styles.badgeLabel}>{label}:</span>}
28
+ <span className={styles.badgeValue}>{value}</span>
29
+ </div>
30
+ );
31
+ };
32
+
33
+ export default Badge;
@@ -0,0 +1,18 @@
1
+ .badgeContainer {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 0.5rem;
5
+ background: var(--badge-bg, #fff);
6
+ border: 1px solid var(--badge-border, #e2e8f0);
7
+ padding: 0.5rem 1rem;
8
+ border-radius: 8px;
9
+ }
10
+
11
+ .badgeLabel {
12
+ font-weight: 600;
13
+ color: var(--badge-label, #334155);
14
+ }
15
+
16
+ .badgeValue {
17
+ color: var(--badge-value, #64748b);
18
+ }
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { Star, Clock, AlertTriangle } from 'lucide-react';
3
+ import Badge from './index';
4
+
5
+ export default {
6
+ title: 'Components/Badge',
7
+ component: Badge,
8
+ };
9
+
10
+ export const Default = {
11
+ args: {
12
+ label: 'Estado',
13
+ value: 'Activo',
14
+ },
15
+ };
16
+
17
+ export const WithIcon = {
18
+ args: {
19
+ icon: Star,
20
+ label: 'Puntaje',
21
+ value: '4.8',
22
+ iconColor: '#f59e0b',
23
+ },
24
+ };
25
+
26
+ export const ValueOnly = {
27
+ args: {
28
+ icon: Clock,
29
+ value: '12:30',
30
+ },
31
+ };
32
+
33
+ export const Warning = {
34
+ args: {
35
+ icon: AlertTriangle,
36
+ label: 'Alerta',
37
+ value: 'Revisar',
38
+ iconColor: '#ef4444',
39
+ },
40
+ };
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import styles from './index.module.css';
3
+
4
+ const Barnner = ({ children, icon: Icon, type = 'default', className = '', bgColor, textColor, borderColor }) => {
5
+ const typeClass = styles[`badge${type.charAt(0).toUpperCase() + type.slice(1)}`] || styles.badgeDefault;
6
+
7
+ const overrideStyle = {
8
+ ...(bgColor ? { backgroundColor: bgColor } : {}),
9
+ ...(textColor ? { color: textColor } : {}),
10
+ ...(borderColor ? { borderColor, borderStyle: 'solid', borderWidth: '1px' } : {}),
11
+ };
12
+
13
+ return (
14
+ <div className={`${styles.badge} ${typeClass} ${className}`} style={overrideStyle}>
15
+ {Icon && <Icon size={14} />}
16
+ <span>{children}</span>
17
+ </div>
18
+ );
19
+ };
20
+
21
+ export default Barnner;
@@ -0,0 +1,33 @@
1
+ .badge {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: 6px;
6
+ padding: 4px 10px;
7
+ border-radius: 7px;
8
+ font-size: 12px;
9
+ font-weight: 700;
10
+ letter-spacing: 0.02em;
11
+ text-transform: capitalize;
12
+ width: max-content;
13
+ }
14
+
15
+ .badgeDefault {
16
+ background: #e2e8f0;
17
+ color: #334155;
18
+ }
19
+
20
+ .badgeSuccess {
21
+ background: #dcfce7;
22
+ color: #166534;
23
+ }
24
+
25
+ .badgeWarning {
26
+ background: #fef3c7;
27
+ color: #92400e;
28
+ }
29
+
30
+ .badgeDanger {
31
+ background: #fee2e2;
32
+ color: #991b1b;
33
+ }
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { CheckCircle, AlertTriangle, XCircle } from 'lucide-react';
3
+ import Barnner from './index';
4
+
5
+ export default {
6
+ title: 'Components/Barnner',
7
+ component: Barnner,
8
+ };
9
+
10
+ export const Default = {
11
+ args: {
12
+ type: 'default',
13
+ children: 'Default',
14
+ },
15
+ };
16
+
17
+ export const Success = {
18
+ args: {
19
+ type: 'success',
20
+ icon: CheckCircle,
21
+ children: 'Completado',
22
+ },
23
+ };
24
+
25
+ export const Warning = {
26
+ args: {
27
+ type: 'warning',
28
+ icon: AlertTriangle,
29
+ children: 'Pendiente',
30
+ },
31
+ };
32
+
33
+ export const Danger = {
34
+ args: {
35
+ type: 'danger',
36
+ icon: XCircle,
37
+ children: 'Cancelado',
38
+ },
39
+ };
File without changes
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import ModalTooltip from '../ModalTooltip/ModalTooltip';
3
+ import styles from './index.module.css';
4
+
5
+ export default function BlueLink({ label, url, navigate, color }) {
6
+ const handleClick = (e) => {
7
+ e.preventDefault();
8
+ if (url.startsWith('http://') || url.startsWith('https://')) {
9
+ window.open(url, '_blank');
10
+ } else {
11
+ if (navigate) navigate(url);
12
+ }
13
+ };
14
+
15
+ return (
16
+ <ModalTooltip text={`Ir a ${label}`}>
17
+ <a
18
+ href={url}
19
+ className={styles.blueLink}
20
+ onClick={handleClick}
21
+ style={color ? { '--bluelink-color': color } : undefined}
22
+ >
23
+ {label}
24
+ </a>
25
+ </ModalTooltip>
26
+ );
27
+ }
@@ -0,0 +1,11 @@
1
+ .blueLink {
2
+ color: var(--bluelink-color, #2563eb);
3
+ text-decoration: underline;
4
+ font-weight: 500;
5
+ cursor: pointer;
6
+ transition: color 0.2s ease;
7
+ }
8
+
9
+ .blueLink:hover {
10
+ color: #1d4ed8;
11
+ }
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import BlueLink from './index';
3
+
4
+ export default {
5
+ title: 'Components/BlueLink',
6
+ component: BlueLink,
7
+ };
8
+
9
+ export const ExternalUrl = {
10
+ args: {
11
+ label: 'Visitar sitio web',
12
+ url: 'https://example.com',
13
+ },
14
+ };
15
+
16
+ export const InternalNavigation = {
17
+ args: {
18
+ label: 'Ir al perfil',
19
+ url: '/profile/123',
20
+ navigate: (path) => console.log('navigate to', path),
21
+ },
22
+ };
23
+
24
+ export const LongLabel = {
25
+ args: {
26
+ label: 'Ver documento adjunto completo',
27
+ url: 'https://example.com/documento.pdf',
28
+ },
29
+ };
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { ChevronRight, Home } from 'lucide-react';
3
+ import styles from './index.module.css';
4
+
5
+ /**
6
+ * Generic breadcrumb trail. `items` is the full path in order — the last
7
+ * entry is always rendered as the current page (a highlighted chip, not a
8
+ * link), regardless of whether it has an `onClick`; every earlier entry
9
+ * with an `onClick` renders as a clickable crumb.
10
+ *
11
+ * Crumbs animate in with a small staggered fade (via --crumb-index) so the
12
+ * trail feels alive when it changes — e.g. drilling into a session — rather
13
+ * than an instant text swap. Respects prefers-reduced-motion.
14
+ */
15
+ export const Breadcrumbs = ({ items = [], className = '', iconColor, activeBgColor, linkColor }) => {
16
+ if (!items.length) return null;
17
+
18
+ return (
19
+ <nav
20
+ aria-label="breadcrumb"
21
+ className={`${styles.breadcrumbs} ${className}`}
22
+ style={{
23
+ ...(iconColor ? { '--crumb-icon-color': iconColor } : {}),
24
+ ...(activeBgColor ? { '--crumb-active-bg': activeBgColor } : {}),
25
+ ...(linkColor ? { '--crumb-link-color': linkColor } : {}),
26
+ }}
27
+ >
28
+ <ol className={styles.list}>
29
+ {items.map((item, index) => {
30
+ const isLast = index === items.length - 1;
31
+
32
+ return (
33
+ <li
34
+ key={item.id ?? item.label ?? index}
35
+ className={styles.item}
36
+ style={{ '--crumb-index': index }}
37
+ >
38
+ {index === 0 && <Home size={14} className={styles.homeIcon} aria-hidden="true" />}
39
+
40
+ {!isLast && item.onClick ? (
41
+ <button type="button" className={styles.link} onClick={item.onClick}>
42
+ <span className={styles.linkLabel}>{item.label}</span>
43
+ </button>
44
+ ) : (
45
+ <span
46
+ className={`${styles.crumbText} ${isLast ? styles.current : ''}`}
47
+ aria-current={isLast ? 'page' : undefined}
48
+ >
49
+ {item.label}
50
+ </span>
51
+ )}
52
+
53
+ {!isLast && <ChevronRight size={14} className={styles.separator} aria-hidden="true" />}
54
+ </li>
55
+ );
56
+ })}
57
+ </ol>
58
+ </nav>
59
+ );
60
+ };
61
+
62
+ export default Breadcrumbs;
@@ -0,0 +1,158 @@
1
+ .breadcrumbs {
2
+ display: flex;
3
+ align-items: center;
4
+ min-width: 0;
5
+ }
6
+
7
+ .list {
8
+ display: flex;
9
+ align-items: center;
10
+ list-style: none;
11
+ margin: 0;
12
+ padding: 0;
13
+ gap: 4px;
14
+ flex-wrap: wrap;
15
+ min-width: 0;
16
+ }
17
+
18
+ .item {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: 4px;
22
+ min-width: 0;
23
+ animation: crumbEnter 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
24
+ animation-delay: calc(var(--crumb-index, 0) * 45ms);
25
+ }
26
+
27
+ @keyframes crumbEnter {
28
+ from {
29
+ opacity: 0;
30
+ transform: translateY(3px);
31
+ }
32
+ to {
33
+ opacity: 1;
34
+ transform: translateY(0);
35
+ }
36
+ }
37
+
38
+ .homeIcon {
39
+ color: var(--crumb-icon-color, #7c9cff);
40
+ flex-shrink: 0;
41
+ transition: transform 0.2s ease;
42
+ }
43
+
44
+ .breadcrumbs:hover .homeIcon {
45
+ transform: scale(1.08) rotate(-4deg);
46
+ }
47
+
48
+ /* Clickable crumb: plain text weight/size, with an animated underline that
49
+ grows from the left on hover instead of a boxy button background. */
50
+ .link {
51
+ position: relative;
52
+ display: inline-flex;
53
+ align-items: center;
54
+ border: none;
55
+ background: transparent;
56
+ padding: 3px 2px;
57
+ margin: 0;
58
+ font: inherit;
59
+ font-size: 0.95rem;
60
+ font-weight: 600;
61
+ line-height: 1.2;
62
+ color: var(--crumb-link-color, #9ba1ae);
63
+ cursor: pointer;
64
+ border-radius: 4px;
65
+ transition: color 0.18s ease;
66
+ }
67
+
68
+ .linkLabel {
69
+ position: relative;
70
+ display: inline-block;
71
+ }
72
+
73
+ .linkLabel::after {
74
+ content: '';
75
+ position: absolute;
76
+ left: 0;
77
+ right: 0;
78
+ bottom: -3px;
79
+ height: 1.5px;
80
+ background: currentColor;
81
+ border-radius: 1px;
82
+ transform: scaleX(0);
83
+ transform-origin: left;
84
+ transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
85
+ }
86
+
87
+ .link:hover,
88
+ .link:focus-visible {
89
+ color: #e6e8ec;
90
+ }
91
+
92
+ .link:hover .linkLabel::after,
93
+ .link:focus-visible .linkLabel::after {
94
+ transform: scaleX(1);
95
+ }
96
+
97
+ .link:active {
98
+ color: var(--crumb-icon-color, #7c9cff);
99
+ transition: color 0.05s ease;
100
+ }
101
+
102
+ .link:focus-visible {
103
+ outline: 2px solid var(--crumb-icon-color, #7c9cff);
104
+ outline-offset: 2px;
105
+ }
106
+
107
+ .crumbText {
108
+ font-size: 0.95rem;
109
+ font-weight: 600;
110
+ color: var(--crumb-link-color, #9ba1ae);
111
+ white-space: nowrap;
112
+ overflow: hidden;
113
+ text-overflow: ellipsis;
114
+ max-width: 360px;
115
+ }
116
+
117
+ /* Current page: a soft pill instead of plain text, with a quick pulse on
118
+ mount so a newly-arrived crumb (e.g. opening a session) draws the eye. */
119
+ .current {
120
+ font-weight: 700;
121
+ color: #e6e8ec;
122
+ padding: 3px 10px;
123
+ border-radius: 999px;
124
+ background: var(--crumb-active-bg, rgba(124, 156, 255, 0.14));
125
+ animation: currentPulse 0.5s ease;
126
+ }
127
+
128
+ @keyframes currentPulse {
129
+ from {
130
+ background: rgba(124, 156, 255, 0.32);
131
+ transform: scale(1.03);
132
+ }
133
+ to {
134
+ background: rgba(124, 156, 255, 0.14);
135
+ transform: scale(1);
136
+ }
137
+ }
138
+
139
+ .separator {
140
+ color: #4b5563;
141
+ flex-shrink: 0;
142
+ }
143
+
144
+ @media (prefers-reduced-motion: reduce) {
145
+ .item {
146
+ animation: none;
147
+ }
148
+
149
+ .current {
150
+ animation: none;
151
+ }
152
+
153
+ .homeIcon,
154
+ .link,
155
+ .linkLabel::after {
156
+ transition: none;
157
+ }
158
+ }
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import { Breadcrumbs } from './index';
3
+
4
+ export default {
5
+ title: 'Components/Breadcrumbs',
6
+ component: Breadcrumbs,
7
+ };
8
+
9
+ export const Default = {
10
+ args: {
11
+ items: [
12
+ { label: 'Inicio', onClick: () => console.log('go home') },
13
+ { label: 'Pacientes', onClick: () => console.log('go patients') },
14
+ { label: 'Juan Pérez' },
15
+ ],
16
+ },
17
+ };
18
+
19
+ export const TwoLevels = {
20
+ args: {
21
+ items: [
22
+ { label: 'Inicio', onClick: () => console.log('go home') },
23
+ { label: 'Configuración' },
24
+ ],
25
+ },
26
+ };
27
+
28
+ export const DeepPath = {
29
+ args: {
30
+ items: [
31
+ { label: 'Inicio', onClick: () => console.log('go home') },
32
+ { label: 'Sesiones', onClick: () => console.log('go sessions') },
33
+ { label: '2024-05-10', onClick: () => console.log('go date') },
34
+ { label: 'Detalle' },
35
+ ],
36
+ },
37
+ };