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.
- package/.github/workflows/ci.yml +31 -0
- package/.github/workflows/publish.yml +45 -0
- package/.npmrc.bak +1 -0
- package/.storybook/main.js +15 -0
- package/.storybook/preview.jsx +26 -0
- package/AnteriorityUI.md +2400 -0
- package/CLAUDE.md +91 -0
- package/README.md +182 -0
- package/assets/anteriorityUI/AnteriorityUI.logo.jpeg +0 -0
- package/assets/favicon/anteriorityUI.ico +0 -0
- package/common/api/client.js +43 -0
- package/common/storage/LocalStorage.js +13 -0
- package/common/styles/typography.css +5 -0
- package/common/styles/variables.css +192 -0
- package/common/theme/muiTheme.js +9 -0
- package/features/password-reset/api.js +39 -0
- package/features/user-profile/api.js +5 -0
- package/hooks/useAdminPasswordReset.js +133 -0
- package/index.js +57 -0
- package/package.json +57 -0
- package/pnpm-workspace.yaml +3 -0
- package/screens/components/AlertModal/AlertModal.jsx +259 -0
- package/screens/components/AlertModal/AlertModal.module.css +185 -0
- package/screens/components/AlertModal/AlertModal.stories.jsx +87 -0
- package/screens/components/Avatar/index.jsx +0 -0
- package/screens/components/Avatar/index.module.css +0 -0
- package/screens/components/Avatar/utils.js +0 -0
- package/screens/components/Badge/index.jsx +33 -0
- package/screens/components/Badge/index.module.css +18 -0
- package/screens/components/Badge/index.stories.jsx +40 -0
- package/screens/components/Barnner/index.jsx +21 -0
- package/screens/components/Barnner/index.module.css +33 -0
- package/screens/components/Barnner/index.stories.jsx +39 -0
- package/screens/components/Barnner/utils.js +0 -0
- package/screens/components/BlueLink/index.jsx +27 -0
- package/screens/components/BlueLink/index.module.css +11 -0
- package/screens/components/BlueLink/index.stories.jsx +29 -0
- package/screens/components/Breadcrumbs/index.jsx +62 -0
- package/screens/components/Breadcrumbs/index.module.css +158 -0
- package/screens/components/Breadcrumbs/index.stories.jsx +37 -0
- package/screens/components/Button/index.jsx +169 -0
- package/screens/components/Button/index.module.css +168 -0
- package/screens/components/Button/index.stories.jsx +71 -0
- package/screens/components/Button/utils.js +28 -0
- package/screens/components/CalendarComponent/CustomDatePicker.jsx +248 -0
- package/screens/components/CalendarComponent/CustomDatePicker.module.css +323 -0
- package/screens/components/CalendarComponent/CustomDatePicker.stories.jsx +45 -0
- package/screens/components/CalendarPicker/Constants.js +12 -0
- package/screens/components/CalendarPicker/index.jsx +263 -0
- package/screens/components/CalendarPicker/index.module.css +147 -0
- package/screens/components/CalendarPicker/index.stories.jsx +45 -0
- package/screens/components/CalendarPicker/utils.js +42 -0
- package/screens/components/CardV1/index.jsx +165 -0
- package/screens/components/CardV1/index.module.css +232 -0
- package/screens/components/CardV1/index.stories.jsx +62 -0
- package/screens/components/CardV1/utils.js +49 -0
- package/screens/components/ChatWindow/index.jsx +251 -0
- package/screens/components/ChatWindow/index.module.css +311 -0
- package/screens/components/ChatWindow/index.stories.jsx +62 -0
- package/screens/components/Checks/index.jsx +31 -0
- package/screens/components/Checks/index.module.css +32 -0
- package/screens/components/Checks/index.stories.jsx +32 -0
- package/screens/components/Container/index.jsx +36 -0
- package/screens/components/Container/index.module.css +24 -0
- package/screens/components/Container/index.stories.jsx +32 -0
- package/screens/components/CopyText/index.jsx +42 -0
- package/screens/components/CopyText/index.module.css +52 -0
- package/screens/components/CopyText/index.stories.jsx +24 -0
- package/screens/components/CountdownBar/constants.js +3 -0
- package/screens/components/CountdownBar/index.jsx +81 -0
- package/screens/components/CountdownBar/index.module.css +45 -0
- package/screens/components/CountdownBar/index.stories.jsx +43 -0
- package/screens/components/CountdownBar/utils.js +119 -0
- package/screens/components/DateTag/index.jsx +33 -0
- package/screens/components/DateTag/index.module.css +12 -0
- package/screens/components/DateTag/index.stories.jsx +29 -0
- package/screens/components/DesplegablePanel/constants.js +0 -0
- package/screens/components/DesplegablePanel/index.jsx +61 -0
- package/screens/components/DesplegablePanel/index.module.css +120 -0
- package/screens/components/DesplegablePanel/index.stories.jsx +44 -0
- package/screens/components/DesplegablePanel/utils.js +0 -0
- package/screens/components/DrawPanel/index.jsx +217 -0
- package/screens/components/DrawPanel/index.module.css +252 -0
- package/screens/components/DrawPanel/index.stories.jsx +66 -0
- package/screens/components/DrawPanel/utils.js +0 -0
- package/screens/components/Filter/index.jsx +105 -0
- package/screens/components/Filter/index.module.css +91 -0
- package/screens/components/Filter/index.stories.jsx +46 -0
- package/screens/components/Filter/utils.js +0 -0
- package/screens/components/FormControls/FormControls.module.css +86 -0
- package/screens/components/FormControls/index.jsx +51 -0
- package/screens/components/FormControls/index.stories.jsx +36 -0
- package/screens/components/Frame/index.jsx +54 -0
- package/screens/components/Frame/index.module.css +102 -0
- package/screens/components/Frame/index.stories.jsx +37 -0
- package/screens/components/Frame/utils.js +0 -0
- package/screens/components/GreenHighlight/index.jsx +11 -0
- package/screens/components/GreenHighlight/index.module.css +7 -0
- package/screens/components/GreenHighlight/index.stories.jsx +26 -0
- package/screens/components/GreenHighlight/utils.jsx +45 -0
- package/screens/components/HoursPicker/Constants.js +26 -0
- package/screens/components/HoursPicker/index.jsx +350 -0
- package/screens/components/HoursPicker/index.module.css +99 -0
- package/screens/components/HoursPicker/index.stories.jsx +44 -0
- package/screens/components/HoursPicker/utils.js +48 -0
- package/screens/components/InfoTooltip/index.jsx +59 -0
- package/screens/components/InfoTooltip/index.module.css +98 -0
- package/screens/components/InfoTooltip/index.stories.jsx +35 -0
- package/screens/components/Input/index.jsx +36 -0
- package/screens/components/Input/index.module.css +49 -0
- package/screens/components/Input/index.stories.jsx +37 -0
- package/screens/components/Input/utils.js +0 -0
- package/screens/components/Loading/index.jsx +76 -0
- package/screens/components/Loading/index.module.css +193 -0
- package/screens/components/Loading/index.stories.jsx +38 -0
- package/screens/components/Loading/utils.js +0 -0
- package/screens/components/MarkdownContent/index.jsx +16 -0
- package/screens/components/MarkdownContent/index.module.css +149 -0
- package/screens/components/MarkdownContent/index.stories.jsx +30 -0
- package/screens/components/MarkdownEditor/index.jsx +430 -0
- package/screens/components/MarkdownEditor/index.module.css +192 -0
- package/screens/components/MarkdownEditor/index.stories.jsx +50 -0
- package/screens/components/Material-UI/Components/InputWithIcon/index.jsx +87 -0
- package/screens/components/Material-UI/Components/InputWithIcon/index.stories.jsx +49 -0
- package/screens/components/Material-UI/Components/MenuPopover/index.jsx +117 -0
- package/screens/components/Material-UI/Components/MenuPopover/index.stories.jsx +36 -0
- package/screens/components/Material-UI/Components/MultilinePlaceholder/index.jsx +44 -0
- package/screens/components/Material-UI/Components/MultilinePlaceholder/index.stories.jsx +45 -0
- package/screens/components/Material-UI/Components/Paginador/index.jsx +79 -0
- package/screens/components/Material-UI/Components/Paginador/index.stories.jsx +49 -0
- package/screens/components/Material-UI/Components/Search/index.jsx +81 -0
- package/screens/components/Material-UI/Components/Search/index.stories.jsx +68 -0
- package/screens/components/Material-UI/Components/Selector/index.jsx +100 -0
- package/screens/components/Material-UI/Components/Selector/index.module.css +0 -0
- package/screens/components/Material-UI/Components/Selector/index.stories.jsx +52 -0
- package/screens/components/Material-UI/Components/Selector/utils.js +0 -0
- package/screens/components/Material-UI/Components/TextField/index.jsx +61 -0
- package/screens/components/Material-UI/Components/TextField/index.stories.jsx +53 -0
- package/screens/components/ModalTooltip/ModalTooltip.jsx +122 -0
- package/screens/components/ModalTooltip/ModalTooltip.module.css +65 -0
- package/screens/components/ModalTooltip/ModalTooltip.stories.jsx +39 -0
- package/screens/components/Notification/Error/ErrorMessage.jsx +30 -0
- package/screens/components/Notification/Error/ErrorMessage.module.css +57 -0
- package/screens/components/Notification/Error/ErrorMessage.stories.jsx +37 -0
- package/screens/components/PasswordResetPanel/index.jsx +325 -0
- package/screens/components/PasswordResetPanel/index.stories.jsx +26 -0
- package/screens/components/PlaceholderButton/index.jsx +169 -0
- package/screens/components/PlaceholderButton/index.module.css +162 -0
- package/screens/components/PlaceholderButton/index.stories.jsx +69 -0
- package/screens/components/PlaceholderButton/utils.js +42 -0
- package/screens/components/Profile/Avatar/index.jsx +125 -0
- package/screens/components/Profile/Avatar/index.module.css +86 -0
- package/screens/components/Profile/Avatar/index.stories.jsx +48 -0
- package/screens/components/Profile/Avatar/utils.js +18 -0
- package/screens/components/Profile/index.jsx +58 -0
- package/screens/components/Profile/index.module.css +0 -0
- package/screens/components/Profile/index.stories.jsx +38 -0
- package/screens/components/Profile/utils.js +0 -0
- package/screens/components/ScrollBar/index.jsx +62 -0
- package/screens/components/ScrollBar/index.module.css +78 -0
- package/screens/components/ScrollBar/index.stories.jsx +64 -0
- package/screens/components/ScrollBar/utils.js +127 -0
- package/screens/components/Sidebar/constants.js +16 -0
- package/screens/components/Sidebar/index.jsx +399 -0
- package/screens/components/Sidebar/index.module.css +405 -0
- package/screens/components/Sidebar/index.stories.jsx +86 -0
- package/screens/components/Sidebar/utils.js +75 -0
- package/screens/components/Skeleton/index.jsx +11 -0
- package/screens/components/Skeleton/index.module.css +26 -0
- package/screens/components/Skeleton/index.stories.jsx +42 -0
- package/screens/components/SubSidebarLayout/index.jsx +44 -0
- package/screens/components/SubSidebarLayout/index.module.css +80 -0
- package/screens/components/SubSidebarLayout/index.stories.jsx +51 -0
- package/screens/components/SummaryCard/index.jsx +50 -0
- package/screens/components/SummaryCard/index.module.css +30 -0
- package/screens/components/SummaryCard/index.stories.jsx +29 -0
- package/screens/components/Swich/constants.js +4 -0
- package/screens/components/Swich/index.jsx +85 -0
- package/screens/components/Swich/index.module.css +68 -0
- package/screens/components/Swich/index.stories.jsx +44 -0
- package/screens/components/Swich/utils.js +0 -0
- package/screens/components/TableA/index.jsx +156 -0
- package/screens/components/TableA/index.module.css +105 -0
- package/screens/components/TableA/index.stories.jsx +116 -0
- package/screens/components/TableA/utils.js +11 -0
- package/screens/components/TableB/index.jsx +222 -0
- package/screens/components/TableB/index.module.css +295 -0
- package/screens/components/TableB/index.stories.jsx +110 -0
- package/screens/components/TableB/utils.js +11 -0
- package/screens/components/ToggleCard/index.jsx +64 -0
- package/screens/components/ToggleCard/index.module.css +111 -0
- package/screens/components/ToggleCard/index.stories.jsx +49 -0
- package/screens/components/UserEditPopover/index.jsx +102 -0
- package/screens/components/UserEditPopover/index.module.css +97 -0
- package/screens/components/UserEditPopover/index.stories.jsx +49 -0
- package/scripts/build.mjs +129 -0
- package/scripts/check-case.mjs +127 -0
- package/scripts/verify-dist.mjs +45 -0
- 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;
|