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,49 @@
1
+ import { ShieldAlert, AlertTriangle, Info, Bell } from 'lucide-react';
2
+
3
+ /**
4
+ * Utility configuration for mapping priority strings/numbers to their visual representation.
5
+ */
6
+ export const priorityConfig = {
7
+ '3': { color: '#ef4444', label: 'ALTA', icon: ShieldAlert },
8
+ 'alta': { color: '#ef4444', label: 'ALTA', icon: ShieldAlert },
9
+ '2': { color: '#f59e42', label: 'MEDIA', icon: AlertTriangle },
10
+ 'media': { color: '#f59e42', label: 'MEDIA', icon: AlertTriangle },
11
+ '1': { color: '#22c55e', label: 'BAJA', icon: Info },
12
+ 'baja': { color: '#22c55e', label: 'BAJA', icon: Info },
13
+ };
14
+
15
+ /**
16
+ * Returns priority data (color, label, icon) for a given priority value.
17
+ * @param {string|number} priority - The priority to evaluate.
18
+ * @returns {Object} { color, label, icon }
19
+ */
20
+ export const getPriorityData = (priority) => {
21
+ const key = String(priority || '').toLowerCase();
22
+ return priorityConfig[key] || { color: '#64748b', label: 'ALERTAS', icon: Bell };
23
+ };
24
+
25
+ /**
26
+ * Calculates the color representing how soon an appointment is.
27
+ * @param {string} horaCitaStr - Time in "HH:MM:SS" or "HH:MM" format.
28
+ * @returns {string} HEX color code.
29
+ */
30
+ export const getAppointmentColorByTime = (horaCitaStr) => {
31
+ if (!horaCitaStr) return '#94a3b8'; // gray default
32
+
33
+ const now = new Date();
34
+ const parts = horaCitaStr.split(':').map(Number);
35
+ const hours = parts[0];
36
+ const minutes = parts[1];
37
+ const seconds = parts[2] || 0;
38
+
39
+ const appointmentTime = new Date(now);
40
+ appointmentTime.setHours(hours, minutes, seconds, 0);
41
+
42
+ const diffMs = appointmentTime - now;
43
+ const diffHours = diffMs / (1000 * 60 * 60);
44
+
45
+ if (diffHours < 0) return '#ef4444'; // past (red)
46
+ if (diffHours <= 1) return '#ef4444'; // less than 1 hour (red)
47
+ if (diffHours <= 4) return '#eab308'; // less than 4 hours (yellow)
48
+ return '#22c55e'; // more than 4 hours (green)
49
+ };
@@ -0,0 +1,251 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import Popover from '@mui/material/Popover';
3
+ import { User, HelpCircle, Bold, Link, Copy, Send } from 'lucide-react';
4
+ import styles from './index.module.css';
5
+
6
+ // Reusable screen components
7
+ import UserAvatar from '../Profile/Avatar/index';
8
+ import Loading from '../Loading/index';
9
+ import CopyableText from '../CopyText/index';
10
+ import GreenHighlight from '../GreenHighlight/index';
11
+ import BlueLink from '../BlueLink/index';
12
+ import MarkdownContent from '../MarkdownContent/index';
13
+
14
+ // Convierte la sintaxis propia del chat (**resaltado**, $etiqueta|url$, (copiable))
15
+ // a Markdown real antes de pasarla al renderer, manteniendo esos formatos como
16
+ // una capa personalizada sobre Markdown estándar en vez de un parser aparte.
17
+ const toMarkdownSource = (content) => {
18
+ if (typeof content !== 'string') return '';
19
+ // Un salto de línea simple debe verse como salto de línea en el chat, no
20
+ // como texto pegado (Markdown solo respeta líneas en blanco por defecto).
21
+ let source = content.replace(/\n(?!\n)/g, ' \n');
22
+ source = source.replace(/\$([^$]+)\$/g, (_, inner) => {
23
+ const [rawLabel, rawUrl] = inner.split('|');
24
+ const label = rawLabel.trim();
25
+ const url = (rawUrl ?? rawLabel).trim();
26
+ return `[${label}](${url})`;
27
+ });
28
+ source = source.replace(/(?<!\])\(([^()]+)\)/g, (_, inner) => `[${inner}](copyable:${encodeURIComponent(inner)})`);
29
+ return source;
30
+ };
31
+
32
+ const flattenToText = (node) => (Array.isArray(node) ? node.join('') : node ?? '');
33
+
34
+ const chatMarkdownComponents = {
35
+ strong: ({ children }) => <GreenHighlight>{children}</GreenHighlight>,
36
+ a: ({ href = '', children }) => {
37
+ if (href.startsWith('copyable:')) {
38
+ return <CopyableText text={decodeURIComponent(href.slice('copyable:'.length))} />;
39
+ }
40
+ return <BlueLink label={flattenToText(children)} url={href} navigate={() => {}} />;
41
+ },
42
+ };
43
+
44
+ export default function ChatWindow({
45
+ messages = [],
46
+ currentUser = {},
47
+ selectedContact = {},
48
+ isTyping = false,
49
+ onSend,
50
+ showHeader = true,
51
+ isMobile = false,
52
+ showContactsList = false,
53
+ handleBackToList,
54
+ isLoading = false,
55
+ placeholder = "Escribe tu mensaje aquí...",
56
+ accentColor,
57
+ accentColorDark
58
+ }) {
59
+ const [inputText, setInputText] = useState('');
60
+ const [anchorEl, setAnchorEl] = useState(null);
61
+ const chatMessagesRef = useRef(null);
62
+
63
+ const lastScrolledContactIdRef = useRef(null);
64
+
65
+ // Auto scroll to bottom when messages update, but respect user's scroll position
66
+ useEffect(() => {
67
+ if (chatMessagesRef.current && !isLoading && messages.length > 0) {
68
+ const container = chatMessagesRef.current;
69
+
70
+ // Check if user is already near the bottom (within a threshold of 150px)
71
+ const isAtBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 150;
72
+
73
+ // Check if this is the first scroll/load for this contact
74
+ const isFirstLoadForContact = lastScrolledContactIdRef.current !== selectedContact?.id;
75
+
76
+ // Check if the last message was sent by the current user
77
+ const lastMessage = messages[messages.length - 1];
78
+ const lastMessageIsMe = lastMessage && lastMessage.from === currentUser?.id;
79
+
80
+ // Always scroll to bottom if:
81
+ // 1. First time loading messages for this contact (including component mount)
82
+ // 2. The user was already scrolled to the bottom
83
+ // 3. The last message was sent by the current user
84
+ if (isFirstLoadForContact || isAtBottom || lastMessageIsMe) {
85
+ const timer = setTimeout(() => {
86
+ if (chatMessagesRef.current) {
87
+ chatMessagesRef.current.scrollTop = chatMessagesRef.current.scrollHeight;
88
+ }
89
+ }, 60);
90
+ lastScrolledContactIdRef.current = selectedContact?.id;
91
+ return () => clearTimeout(timer);
92
+ }
93
+ }
94
+ }, [messages, isTyping, selectedContact?.id, currentUser?.id, isLoading]);
95
+
96
+ const handleOpenFormats = (e) => {
97
+ setAnchorEl(e.currentTarget);
98
+ };
99
+
100
+ const handleCloseFormats = () => {
101
+ setAnchorEl(null);
102
+ };
103
+
104
+ const openFormats = Boolean(anchorEl);
105
+
106
+ const handleSubmit = async (e) => {
107
+ e.preventDefault();
108
+ const trimmed = inputText.trim();
109
+ if (trimmed && onSend) {
110
+ setInputText('');
111
+ await onSend(trimmed);
112
+ }
113
+ };
114
+
115
+ const handleKeyDown = (e) => {
116
+ if (e.key === 'Enter' && !e.shiftKey) {
117
+ e.preventDefault();
118
+ handleSubmit(e);
119
+ }
120
+ };
121
+
122
+ return (
123
+ <div
124
+ className={styles.chatWindowContainer}
125
+ style={{
126
+ ...(accentColor ? { '--chat-accent': accentColor } : {}),
127
+ ...(accentColorDark ? { '--chat-accent-dark': accentColorDark } : {}),
128
+ }}
129
+ >
130
+ {showHeader && (
131
+ <header className={styles.threadHeader}>
132
+ {isMobile && (
133
+ <button className={styles.backBtn} onClick={handleBackToList}>
134
+ ← Volver
135
+ </button>
136
+ )}
137
+ <div className={styles.threadAvatar}>
138
+ {selectedContact ? (
139
+ <UserAvatar
140
+ src={selectedContact.avatarUrl}
141
+ name={selectedContact.name}
142
+ size={40}
143
+ editable={false}
144
+ viewable={true}
145
+ />
146
+ ) : (
147
+ <User size={20} />
148
+ )}
149
+ </div>
150
+ <div className={styles.threadInfo}>
151
+ <h2 className={styles.threadName}>{selectedContact?.name || 'Contacto'}</h2>
152
+ <span className={styles.threadEmail}>{selectedContact?.raw?.email || `ID: ${selectedContact?.id}`}</span>
153
+ </div>
154
+ </header>
155
+ )}
156
+
157
+ <div className={styles.chatMessages} ref={chatMessagesRef}>
158
+ {isLoading && <Loading size="medium" text="Cargando hilo..." />}
159
+
160
+ {messages.map((m) => {
161
+ const isMe = m.from === currentUser?.id;
162
+ return (
163
+ <div key={m.id} className={`${styles.messageRow} ${isMe ? styles.messageMe : styles.messageThem}`}>
164
+ <div className={styles.messageBubble}>
165
+ {typeof m.content === 'string' ? (
166
+ <MarkdownContent components={chatMarkdownComponents} style={{ color: 'inherit' }}>
167
+ {toMarkdownSource(m.content)}
168
+ </MarkdownContent>
169
+ ) : m.content}
170
+ </div>
171
+ <span className={styles.messageTime}>
172
+ {new Date(m.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
173
+ </span>
174
+ </div>
175
+ );
176
+ })}
177
+
178
+ {isTyping && (
179
+ <div className={`${styles.messageRow} ${styles.messageThem}`}>
180
+ <div className={styles.messageBubble}>
181
+ <div className={styles.typingIndicator}>
182
+ <span></span>
183
+ <span></span>
184
+ <span></span>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ )}
189
+ </div>
190
+
191
+ <div className={styles.threadComposer}>
192
+ {/* Formats Helper */}
193
+ <div className={styles.formatsHelperContainer}>
194
+ <button
195
+ type="button"
196
+ onClick={handleOpenFormats}
197
+ className={`${styles.formatsBtn} ${openFormats ? styles.formatsActive : ''}`}
198
+ >
199
+ <HelpCircle size={14} style={{ marginRight: '4px' }} />
200
+ Formas de escribir
201
+ </button>
202
+
203
+ <Popover
204
+ open={openFormats}
205
+ anchorEl={anchorEl}
206
+ onClose={handleCloseFormats}
207
+ anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
208
+ transformOrigin={{ vertical: 'bottom', horizontal: 'left' }}
209
+ slotProps={{ paper: { className: styles.popoverPaper } }}
210
+ >
211
+ <div className={styles.formatsDropdownMenu}>
212
+ <div className={styles.formatMenuTitle}>Guía de formatos rápidos:</div>
213
+ <ul className={styles.formatList}>
214
+ <li>
215
+ <Bold size={13} className={styles.formatMenuIcon} />
216
+ <span>Resaltado verde: <code>**texto**</code></span>
217
+ </li>
218
+ <li>
219
+ <Link size={13} className={styles.formatMenuIcon} />
220
+ <span>Enlaces web: <code>$Texto opcional|https://url.com$</code></span>
221
+ </li>
222
+ <li>
223
+ <Copy size={13} className={styles.formatMenuIcon} />
224
+ <span>Bloque copiable: <code>(texto a copiar)</code></span>
225
+ </li>
226
+ </ul>
227
+ </div>
228
+ </Popover>
229
+ </div>
230
+
231
+ <form onSubmit={handleSubmit} className={styles.chatComposerForm}>
232
+ <textarea
233
+ className={styles.composerInput}
234
+ placeholder={placeholder}
235
+ value={inputText}
236
+ onChange={(e) => setInputText(e.target.value)}
237
+ onKeyDown={handleKeyDown}
238
+ rows={1}
239
+ />
240
+ <button
241
+ type="submit"
242
+ className={styles.sendBtn}
243
+ disabled={isTyping || !inputText.trim()}
244
+ >
245
+ <Send size={18} />
246
+ </button>
247
+ </form>
248
+ </div>
249
+ </div>
250
+ );
251
+ }
@@ -0,0 +1,311 @@
1
+ .chatWindowContainer {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ width: 100%;
6
+ background-color: #ffffff;
7
+ }
8
+
9
+ /* Header Styling */
10
+ .threadHeader {
11
+ padding: 1rem 1.5rem;
12
+ border-bottom: 1px solid #e2e8f0;
13
+ display: flex;
14
+ align-items: center;
15
+ background-color: #ffffff;
16
+ flex-shrink: 0;
17
+ }
18
+
19
+ .backBtn {
20
+ background: none;
21
+ border: none;
22
+ color: #2563eb;
23
+ font-size: 0.9rem;
24
+ font-weight: 600;
25
+ cursor: pointer;
26
+ margin-right: 1rem;
27
+ padding: 4px 8px;
28
+ border-radius: 4px;
29
+ transition: background-color 0.2s;
30
+ }
31
+
32
+ .backBtn:hover {
33
+ background-color: #f1f5f9;
34
+ }
35
+
36
+ .threadAvatar {
37
+ margin-right: 0.75rem;
38
+ display: flex;
39
+ align-items: center;
40
+ }
41
+
42
+ .threadInfo {
43
+ display: flex;
44
+ flex-direction: column;
45
+ }
46
+
47
+ .threadName {
48
+ font-size: 1rem;
49
+ font-weight: 700;
50
+ color: #0f172a;
51
+ margin: 0;
52
+ line-height: 1.2;
53
+ }
54
+
55
+ .threadEmail {
56
+ font-size: 0.75rem;
57
+ color: #64748b;
58
+ margin-top: 2px;
59
+ }
60
+
61
+ /* Chat Messages */
62
+ .chatMessages {
63
+ flex: 1;
64
+ overflow-y: auto;
65
+ padding: 1rem 1.5rem;
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 1.25rem;
69
+ scrollbar-width: thin;
70
+ scrollbar-color: #cbd5e1 transparent;
71
+ }
72
+
73
+ .chatMessages::-webkit-scrollbar {
74
+ width: 4px;
75
+ }
76
+
77
+ .chatMessages::-webkit-scrollbar-track {
78
+ background: transparent;
79
+ }
80
+
81
+ .chatMessages::-webkit-scrollbar-thumb {
82
+ background-color: #cbd5e1;
83
+ border-radius: 10px;
84
+ }
85
+
86
+ /* Message Rows & Bubbles */
87
+ .messageRow {
88
+ display: flex;
89
+ flex-direction: column;
90
+ max-width: 82%;
91
+ }
92
+
93
+ .messageMe {
94
+ align-self: flex-end;
95
+ align-items: flex-end;
96
+ }
97
+
98
+ .messageThem {
99
+ align-self: flex-start;
100
+ align-items: flex-start;
101
+ }
102
+
103
+ .messageBubble {
104
+ padding: 11px 15px;
105
+ border-radius: 16px;
106
+ font-size: 0.92rem;
107
+ line-height: 1.55;
108
+ word-break: break-word;
109
+ white-space: pre-wrap;
110
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
111
+ }
112
+
113
+ .messageMe .messageBubble {
114
+ background-color: var(--chat-accent, #2563eb);
115
+ color: #ffffff;
116
+ border-bottom-right-radius: 4px;
117
+ }
118
+
119
+ .messageThem .messageBubble {
120
+ background-color: #f1f5f9;
121
+ color: #1e293b;
122
+ border-bottom-left-radius: 4px;
123
+ border: 1px solid #e2e8f0;
124
+ }
125
+
126
+ .messageTime {
127
+ font-size: 0.72rem;
128
+ color: #94a3b8;
129
+ margin-top: 4px;
130
+ padding: 0 4px;
131
+ }
132
+
133
+ /* Composer Container */
134
+ .threadComposer {
135
+ padding: 1rem 1.5rem;
136
+ border-top: 1px solid #f1f5f9;
137
+ background-color: #ffffff;
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: 8px;
141
+ flex-shrink: 0;
142
+ }
143
+
144
+ .chatComposerForm {
145
+ display: flex;
146
+ gap: 10px;
147
+ align-items: flex-end;
148
+ }
149
+
150
+ .composerInput {
151
+ flex: 1;
152
+ border: 1px solid #cbd5e1;
153
+ border-radius: 18px;
154
+ padding: 10px 14px;
155
+ font-size: 0.9rem;
156
+ resize: none;
157
+ max-height: 90px;
158
+ outline: none;
159
+ font-family: inherit;
160
+ transition: border-color 0.2s, background-color 0.2s;
161
+ background-color: #f8fafc;
162
+ line-height: 1.4;
163
+ }
164
+
165
+ .composerInput:focus {
166
+ border-color: #2563eb;
167
+ background-color: #ffffff;
168
+ }
169
+
170
+ .sendBtn {
171
+ background: linear-gradient(135deg, var(--chat-accent, #2563eb) 0%, var(--chat-accent-dark, #1d4ed8) 100%);
172
+ color: white;
173
+ border: none;
174
+ width: 38px;
175
+ height: 38px;
176
+ border-radius: 50%;
177
+ display: flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ cursor: pointer;
181
+ transition: background-color 0.2s, transform 0.1s;
182
+ flex-shrink: 0;
183
+ box-shadow: 0 4px 10px rgba(37, 99, 235, 0.2);
184
+ }
185
+
186
+ .sendBtn:hover:not(:disabled) {
187
+ transform: scale(1.06);
188
+ }
189
+
190
+ .sendBtn:active:not(:disabled) {
191
+ transform: scale(0.95);
192
+ }
193
+
194
+ .sendBtn:disabled {
195
+ background: #e2e8f0;
196
+ color: #94a3b8;
197
+ box-shadow: none;
198
+ cursor: not-allowed;
199
+ }
200
+
201
+ /* Formats Helper Link */
202
+ .formatsHelperContainer {
203
+ display: flex;
204
+ align-items: center;
205
+ }
206
+
207
+ .formatsBtn {
208
+ background: none;
209
+ border: none;
210
+ font-size: 0.78rem;
211
+ color: #64748b;
212
+ cursor: pointer;
213
+ display: flex;
214
+ align-items: center;
215
+ padding: 2px 6px;
216
+ border-radius: 4px;
217
+ transition: color 0.2s, background-color 0.2s;
218
+ }
219
+
220
+ .formatsBtn:hover {
221
+ color: #0f172a;
222
+ background-color: #f1f5f9;
223
+ }
224
+
225
+ .formatsActive {
226
+ color: #2563eb;
227
+ background-color: #eff6ff;
228
+ }
229
+
230
+ /* Popover Content */
231
+ .popoverPaper {
232
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1) !important;
233
+ border: 1px solid #e2e8f0 !important;
234
+ border-radius: 8px !important;
235
+ }
236
+
237
+ .formatsDropdownMenu {
238
+ padding: 12px 16px;
239
+ max-width: 280px;
240
+ }
241
+
242
+ .formatMenuTitle {
243
+ font-size: 0.8rem;
244
+ font-weight: 700;
245
+ color: #0f172a;
246
+ margin-bottom: 8px;
247
+ }
248
+
249
+ .formatList {
250
+ list-style: none;
251
+ padding: 0;
252
+ margin: 0;
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: 8px;
256
+ }
257
+
258
+ .formatList li {
259
+ display: flex;
260
+ align-items: flex-start;
261
+ font-size: 0.76rem;
262
+ color: #475569;
263
+ }
264
+
265
+ .formatMenuIcon {
266
+ margin-right: 8px;
267
+ margin-top: 2px;
268
+ color: #64748b;
269
+ flex-shrink: 0;
270
+ }
271
+
272
+ .formatList code {
273
+ background-color: #f1f5f9;
274
+ padding: 1px 4px;
275
+ border-radius: 3px;
276
+ color: #0f172a;
277
+ font-family: monospace;
278
+ }
279
+
280
+ /* Typing Indicator Animation */
281
+ .typingIndicator {
282
+ display: flex;
283
+ gap: 5px;
284
+ padding: 6px 4px;
285
+ align-items: center;
286
+ }
287
+
288
+ .typingIndicator span {
289
+ width: 7px;
290
+ height: 7px;
291
+ background-color: #64748b;
292
+ border-radius: 50%;
293
+ display: inline-block;
294
+ animation: bounce 1.4s infinite ease-in-out both;
295
+ }
296
+
297
+ .typingIndicator span:nth-child(1) {
298
+ animation-delay: -0.32s;
299
+ }
300
+
301
+ .typingIndicator span:nth-child(2) {
302
+ animation-delay: -0.16s;
303
+ }
304
+
305
+ @keyframes bounce {
306
+ 0%, 80%, 100% {
307
+ transform: scale(0);
308
+ } 40% {
309
+ transform: scale(1);
310
+ }
311
+ }
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import ChatWindow from './index';
3
+
4
+ export default {
5
+ title: 'Components/ChatWindow',
6
+ component: ChatWindow,
7
+ };
8
+
9
+ const currentUser = { id: 'user-1' };
10
+
11
+ const selectedContact = {
12
+ id: 'contact-1',
13
+ name: 'María González',
14
+ avatarUrl: '',
15
+ raw: { email: 'maria@example.com' },
16
+ };
17
+
18
+ const messages = [
19
+ { id: 1, from: 'contact-1', content: 'Hola, ¿cómo estás?', createdAt: new Date().toISOString() },
20
+ { id: 2, from: 'user-1', content: 'Todo bien, ¿y vos? **Gracias por escribir**', createdAt: new Date().toISOString() },
21
+ { id: 3, from: 'contact-1', content: 'Podés revisar esto: $Sitio|https://example.com$', createdAt: new Date().toISOString() },
22
+ ];
23
+
24
+ export const Default = {
25
+ args: {
26
+ messages,
27
+ currentUser,
28
+ selectedContact,
29
+ onSend: (text) => console.log('send', text),
30
+ },
31
+ };
32
+
33
+ export const Typing = {
34
+ args: {
35
+ messages,
36
+ currentUser,
37
+ selectedContact,
38
+ isTyping: true,
39
+ onSend: (text) => console.log('send', text),
40
+ },
41
+ };
42
+
43
+ export const LoadingThread = {
44
+ args: {
45
+ messages: [],
46
+ currentUser,
47
+ selectedContact,
48
+ isLoading: true,
49
+ onSend: (text) => console.log('send', text),
50
+ },
51
+ };
52
+
53
+ export const MobileView = {
54
+ args: {
55
+ messages,
56
+ currentUser,
57
+ selectedContact,
58
+ isMobile: true,
59
+ handleBackToList: () => console.log('back to list'),
60
+ onSend: (text) => console.log('send', text),
61
+ },
62
+ };
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import Checkbox from '@mui/material/Checkbox';
3
+ import ButtonBase from '@mui/material/ButtonBase';
4
+ import styles from './index.module.css';
5
+
6
+ import ModalTooltip from '../ModalTooltip/ModalTooltip';
7
+
8
+ export default function PermissionCheckboxItem({ label, checked, onChange, disabled, checkedBgColor }) {
9
+ return (
10
+ <ButtonBase
11
+ component="div"
12
+ className={`${styles.itemContainer} ${checked ? styles.checked : ''}`}
13
+ onClick={!disabled ? onChange : undefined}
14
+ disabled={disabled}
15
+ focusRipple
16
+ style={checkedBgColor ? { '--checks-checked-bg': checkedBgColor } : undefined}
17
+ >
18
+ <ModalTooltip text={label}>
19
+ <Checkbox
20
+ checked={checked}
21
+ disabled={disabled}
22
+ disableRipple
23
+ tabIndex={-1}
24
+ />
25
+ </ModalTooltip>
26
+
27
+ <span className={styles.label}>{label}</span>
28
+
29
+ </ButtonBase>
30
+ );
31
+ }