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,399 @@
1
+ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { ChevronLeft, ChevronRight, Pencil, Trash2, Pin, PinOff, GripVertical, Users } from 'lucide-react';
4
+ import Button from '../Button/index';
5
+ import Skeleton from '../Skeleton/index';
6
+ import styles from './index.module.css';
7
+ import { TEXTS } from './constants';
8
+ import { getBadgeClass, getInitial, isSidebarToggleShortcut, toggleSidebarCollapsed } from './utils';
9
+
10
+ import ModalTooltip from '../ModalTooltip/ModalTooltip';
11
+
12
+ const DEFAULT_METHOD_BADGE_COLORS = {
13
+ post: { bg: '#dcfce7', color: '#15803d' },
14
+ get: { bg: '#e0f2fe', color: '#0369a1' },
15
+ put: { bg: '#fef3c7', color: '#b45309' },
16
+ delete: { bg: '#fee2e2', color: '#b91c1c' },
17
+ info: { bg: '#f1f5f9', color: '#475569' },
18
+ };
19
+
20
+ export const ProSidebar = ({
21
+ groups,
22
+ activeId,
23
+ onSelect,
24
+ collapsible = true,
25
+ loading = false,
26
+ skeletonItems = 15,
27
+ className = '',
28
+ headerContent,
29
+ footerContent,
30
+ user,
31
+ userMenuItems = [],
32
+ onItemEdit,
33
+ onItemDelete,
34
+ onItemPin,
35
+ onItemCollaborators,
36
+ onReorderItems,
37
+ avatarBgColor,
38
+ pinnedIconColor,
39
+ methodBadgeColors,
40
+ }) => {
41
+ const [isCollapsed, setIsCollapsed] = useState(() => {
42
+ return localStorage.getItem('sidebar_collapsed') === 'true';
43
+ });
44
+ const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
45
+ const [userMenuPosition, setUserMenuPosition] = useState(null);
46
+ const [contextMenu, setContextMenu] = useState(null);
47
+ const [draggedItemId, setDraggedItemId] = useState(null);
48
+ const [dragOverItemId, setDragOverItemId] = useState(null);
49
+ const userFooterButtonRef = useRef(null);
50
+ const userMenuRef = useRef(null);
51
+ const contextMenuRef = useRef(null);
52
+
53
+ const canShowItemContextMenu = Boolean(onItemEdit || onItemDelete || onItemPin);
54
+ const canReorder = Boolean(onReorderItems);
55
+
56
+ const badgeThemeVars = Object.keys(DEFAULT_METHOD_BADGE_COLORS).reduce((acc, key) => {
57
+ const override = methodBadgeColors && methodBadgeColors[key];
58
+ if (override?.bg) acc[`--sidebar-badge-${key}-bg`] = override.bg;
59
+ if (override?.color) acc[`--sidebar-badge-${key}-color`] = override.color;
60
+ return acc;
61
+ }, {});
62
+
63
+ const themeVars = {
64
+ ...(avatarBgColor ? { '--sidebar-avatar-bg': avatarBgColor } : {}),
65
+ ...(pinnedIconColor ? { '--sidebar-pinned-icon': pinnedIconColor } : {}),
66
+ ...badgeThemeVars,
67
+ };
68
+
69
+ const handleDragStart = (event, item) => {
70
+ event.dataTransfer.effectAllowed = 'move';
71
+ setDraggedItemId(item.id);
72
+ };
73
+
74
+ const handleDragOver = (event, item) => {
75
+ event.preventDefault();
76
+ if (item.id !== draggedItemId) setDragOverItemId(item.id);
77
+ };
78
+
79
+ const handleDragLeave = (item) => {
80
+ setDragOverItemId((prev) => (prev === item.id ? null : prev));
81
+ };
82
+
83
+ const handleDrop = (event, groupItems, targetItem) => {
84
+ event.preventDefault();
85
+ setDragOverItemId(null);
86
+ if (!draggedItemId || draggedItemId === targetItem.id) return;
87
+
88
+ const fromIndex = groupItems.findIndex((it) => it.id === draggedItemId);
89
+ const toIndex = groupItems.findIndex((it) => it.id === targetItem.id);
90
+ if (fromIndex === -1 || toIndex === -1) return;
91
+
92
+ const reordered = groupItems.map((it) => it.id);
93
+ const [movedId] = reordered.splice(fromIndex, 1);
94
+ reordered.splice(toIndex, 0, movedId);
95
+
96
+ onReorderItems && onReorderItems(reordered);
97
+ };
98
+
99
+ const handleDragEnd = () => {
100
+ setDraggedItemId(null);
101
+ setDragOverItemId(null);
102
+ };
103
+
104
+ useLayoutEffect(() => {
105
+ if (!isUserMenuOpen || !userFooterButtonRef.current) return;
106
+ const rect = userFooterButtonRef.current.getBoundingClientRect();
107
+ setUserMenuPosition({
108
+ bottom: window.innerHeight - rect.top + 8,
109
+ left: rect.left,
110
+ width: isCollapsed ? 220 : rect.width,
111
+ });
112
+ }, [isUserMenuOpen, isCollapsed]);
113
+
114
+ useEffect(() => {
115
+ if (!isUserMenuOpen) return undefined;
116
+ // The menu is rendered in a portal to document.body, so it sits outside
117
+ // the sidebar's DOM subtree (and outside the app's root container).
118
+ // Relying on synthetic-event bubbling/stopPropagation across that
119
+ // boundary is unreliable, so this checks real DOM containment via refs
120
+ // instead — mousedown fires before the menu item's own click, so its
121
+ // early return here (when the ref *does* contain the target) is what
122
+ // lets that click go on to fire normally.
123
+ const handlePointerDown = (event) => {
124
+ const target = event.target;
125
+ if (userMenuRef.current?.contains(target)) return;
126
+ if (userFooterButtonRef.current?.contains(target)) return;
127
+ setIsUserMenuOpen(false);
128
+ };
129
+ document.addEventListener('mousedown', handlePointerDown);
130
+ return () => document.removeEventListener('mousedown', handlePointerDown);
131
+ }, [isUserMenuOpen]);
132
+
133
+ useEffect(() => {
134
+ if (!collapsible) return undefined;
135
+ const handleToggleShortcut = (event) => {
136
+ if (!isSidebarToggleShortcut(event)) return;
137
+ event.preventDefault();
138
+ setIsCollapsed(toggleSidebarCollapsed);
139
+ };
140
+ document.addEventListener('keydown', handleToggleShortcut);
141
+ return () => document.removeEventListener('keydown', handleToggleShortcut);
142
+ }, [collapsible]);
143
+
144
+ useEffect(() => {
145
+ if (!contextMenu) return undefined;
146
+ const handlePointerDown = (event) => {
147
+ if (contextMenuRef.current?.contains(event.target)) return;
148
+ setContextMenu(null);
149
+ };
150
+ const closeMenu = () => setContextMenu(null);
151
+ document.addEventListener('mousedown', handlePointerDown);
152
+ document.addEventListener('contextmenu', handlePointerDown);
153
+ document.addEventListener('scroll', closeMenu, true);
154
+ return () => {
155
+ document.removeEventListener('mousedown', handlePointerDown);
156
+ document.removeEventListener('contextmenu', handlePointerDown);
157
+ document.removeEventListener('scroll', closeMenu, true);
158
+ };
159
+ }, [contextMenu]);
160
+
161
+ const handleItemContextMenu = (event, item) => {
162
+ if (!canShowItemContextMenu) return;
163
+ event.preventDefault();
164
+ event.stopPropagation();
165
+ setContextMenu({ x: event.clientX, y: event.clientY, item });
166
+ };
167
+
168
+ const renderLeftContent = (item) => {
169
+ const Icon = item.icon;
170
+ if (Icon) {
171
+ // Si Icon es un componente React válido
172
+ if (typeof Icon === 'function' || typeof Icon === 'object') {
173
+ return <span className={styles.icon}><Icon size={20} /></span>;
174
+ }
175
+ // Si es un nodo ya renderizado
176
+ return <span className={styles.icon}>{Icon}</span>;
177
+ }
178
+ return null;
179
+ };
180
+
181
+ return (
182
+ <aside
183
+ className={`${styles.sidebar} ${isCollapsed ? styles.collapsed : ''} ${className}`}
184
+ style={themeVars}
185
+ >
186
+ {(headerContent || collapsible) && (
187
+ <div className={styles.sidebarHeader}>
188
+ {!isCollapsed && headerContent && (
189
+ <div className={styles.headerContent}>{headerContent}</div>
190
+ )}
191
+
192
+ {collapsible && (
193
+ <Button
194
+ circle
195
+ size="small"
196
+ variant={Button.VARIANTS.GHOST}
197
+ className={styles.toggleBtn}
198
+ onClick={() => setIsCollapsed(toggleSidebarCollapsed)}
199
+ color="transparent"
200
+ >
201
+ <ModalTooltip
202
+ key={isCollapsed}
203
+ text={isCollapsed ? TEXTS.openMenu : TEXTS.closeMenu}
204
+ position="bottom"
205
+ >
206
+ {isCollapsed ? <ChevronRight size={16} /> : <ChevronLeft size={16} />}
207
+ </ModalTooltip>
208
+ </Button>
209
+ )}
210
+ </div>
211
+ )}
212
+
213
+ <div className={styles.navContainer}>
214
+ {loading ? (
215
+ <div className={styles.menuGroup}>
216
+ <div className={styles.buttonList}>
217
+ {Array.from({ length: skeletonItems }).map((_, idx) => (
218
+ <div key={`skeleton-${idx}`} className={styles.sidebarItemBtn} style={{ cursor: 'default' }}>
219
+ <div className={styles.btnLayout}>
220
+ <div className={styles.leftPart}>
221
+ <Skeleton circle width={20} height={20} />
222
+ {!isCollapsed && <Skeleton width={`${60 + (idx % 3) * 15}%`} height="12px" />}
223
+ </div>
224
+ </div>
225
+ </div>
226
+ ))}
227
+ </div>
228
+ </div>
229
+ ) : groups && groups.map((group, gIdx) => (
230
+ <div key={gIdx} className={styles.menuGroup}>
231
+ {group.title && !isCollapsed && (
232
+ <span className={styles.groupTitle}>{group.title}</span>
233
+ )}
234
+
235
+ <div className={styles.buttonList}>
236
+ {group.items && group.items.map((item) => {
237
+ const isActive = activeId === item.id;
238
+ const isDragging = draggedItemId === item.id;
239
+ const isDragOver = canReorder && dragOverItemId === item.id && !isDragging;
240
+
241
+ return (
242
+ <Button
243
+ key={item.id}
244
+ fullWidth
245
+ color="transparent"
246
+ active={isActive}
247
+ draggable={canReorder}
248
+ onDragStart={canReorder ? (event) => handleDragStart(event, item) : undefined}
249
+ onDragOver={canReorder ? (event) => handleDragOver(event, item) : undefined}
250
+ onDragLeave={canReorder ? () => handleDragLeave(item) : undefined}
251
+ onDrop={canReorder ? (event) => handleDrop(event, group.items, item) : undefined}
252
+ onDragEnd={canReorder ? handleDragEnd : undefined}
253
+ onClick={() => onSelect && onSelect(item)}
254
+ onContextMenu={(event) => handleItemContextMenu(event, item)}
255
+ className={`${styles.sidebarItemBtn} ${isActive ? styles.activeBtn : ''} ${isDragging ? styles.dragging : ''} ${isDragOver ? styles.dragOver : ''}`}
256
+ ToolTip={isCollapsed ? item.label : undefined}
257
+ >
258
+ <div className={styles.btnLayout}>
259
+ <div className={styles.leftPart}>
260
+ {canReorder && !isCollapsed && (
261
+ <span className={styles.dragHandle}><GripVertical size={14} /></span>
262
+ )}
263
+ {renderLeftContent(item)}
264
+ {!isCollapsed && <span className={styles.label}>{item.label}</span>}
265
+ {!isCollapsed && item.pinned && (
266
+ <Pin size={12} className={styles.pinnedIcon} />
267
+ )}
268
+ </div>
269
+
270
+ {!isCollapsed && item.badge && (
271
+ <span className={`${styles.badge} ${getBadgeClass(styles, item.badgeType)}`}>
272
+ {item.badge}
273
+ </span>
274
+ )}
275
+ </div>
276
+ </Button>
277
+ );
278
+ })}
279
+ </div>
280
+ </div>
281
+ ))}
282
+ </div>
283
+
284
+ {(footerContent || user?.email) && (
285
+ <div className={styles.footerGroup}>
286
+ {footerContent && !isCollapsed && (
287
+ <div className={styles.footerContent}>{footerContent}</div>
288
+ )}
289
+
290
+ {user?.email && (
291
+ <div className={styles.userFooter}>
292
+ <button
293
+ type="button"
294
+ ref={userFooterButtonRef}
295
+ className={styles.userFooterButton}
296
+ onClick={() => setIsUserMenuOpen((prev) => !prev)}
297
+ >
298
+ <span className={styles.avatar}>{getInitial(user.email)}</span>
299
+ {!isCollapsed && <span className={styles.userEmail}>{user.email}</span>}
300
+ </button>
301
+ </div>
302
+ )}
303
+ </div>
304
+ )}
305
+
306
+ {isUserMenuOpen && userMenuPosition && user?.email && createPortal(
307
+ <div
308
+ ref={userMenuRef}
309
+ className={styles.userMenu}
310
+ style={{ bottom: userMenuPosition.bottom, left: userMenuPosition.left, width: userMenuPosition.width }}
311
+ >
312
+ <div className={styles.userMenuEmail}>{user.email}</div>
313
+ {userMenuItems.map((menuItem) => {
314
+ const Icon = menuItem.icon;
315
+ return (
316
+ <button
317
+ key={menuItem.id ?? menuItem.label}
318
+ type="button"
319
+ className={`${styles.userMenuItem} ${menuItem.variant === 'danger' ? styles.userMenuItemDanger : ''}`}
320
+ onClick={() => {
321
+ setIsUserMenuOpen(false);
322
+ menuItem.onClick && menuItem.onClick();
323
+ }}
324
+ >
325
+ {Icon && <Icon size={16} />}
326
+ <span>{menuItem.label}</span>
327
+ </button>
328
+ );
329
+ })}
330
+ </div>,
331
+ document.body
332
+ )}
333
+
334
+ {contextMenu && createPortal(
335
+ <div
336
+ ref={contextMenuRef}
337
+ className={styles.contextMenu}
338
+ style={{ top: contextMenu.y, left: contextMenu.x }}
339
+ >
340
+ {onItemEdit && contextMenu.item.isOwner !== false && (
341
+ <button
342
+ type="button"
343
+ className={styles.contextMenuItem}
344
+ onClick={() => {
345
+ onItemEdit(contextMenu.item);
346
+ setContextMenu(null);
347
+ }}
348
+ >
349
+ <Pencil size={14} />
350
+ <span>{TEXTS.editItem}</span>
351
+ </button>
352
+ )}
353
+ {onItemCollaborators && contextMenu.item.isOwner !== false && contextMenu.item.isShared && (
354
+ <button
355
+ type="button"
356
+ className={styles.contextMenuItem}
357
+ onClick={() => {
358
+ onItemCollaborators(contextMenu.item);
359
+ setContextMenu(null);
360
+ }}
361
+ >
362
+ <Users size={14} />
363
+ <span>{TEXTS.collaboratorsItem}</span>
364
+ </button>
365
+ )}
366
+ {onItemPin && (
367
+ <button
368
+ type="button"
369
+ className={styles.contextMenuItem}
370
+ onClick={() => {
371
+ onItemPin(contextMenu.item);
372
+ setContextMenu(null);
373
+ }}
374
+ >
375
+ {contextMenu.item.pinned ? <PinOff size={14} /> : <Pin size={14} />}
376
+ <span>{contextMenu.item.pinned ? TEXTS.unpinItem : TEXTS.pinItem}</span>
377
+ </button>
378
+ )}
379
+ {onItemDelete && (
380
+ <button
381
+ type="button"
382
+ className={`${styles.contextMenuItem} ${styles.contextMenuItemDanger}`}
383
+ onClick={() => {
384
+ onItemDelete(contextMenu.item);
385
+ setContextMenu(null);
386
+ }}
387
+ >
388
+ <Trash2 size={14} />
389
+ <span>{contextMenu.item.isOwner === false ? TEXTS.leaveItem : TEXTS.deleteItem}</span>
390
+ </button>
391
+ )}
392
+ </div>,
393
+ document.body
394
+ )}
395
+ </aside>
396
+ );
397
+ };
398
+
399
+ export default ProSidebar;