Merge branch 'INDA-330-pii-update' into 'main'
[ProtonMail-WebClient.git] / applications / mail / src / app / components / sidebar / SidebarItem.tsx
blobcebab3daf6211c8b937321b6cc590352785043c1
1 import type { ReactNode } from 'react';
2 import { memo, useCallback, useRef } from 'react';
3 import { useHistory } from 'react-router-dom';
5 import type { HotkeyTuple, IconName, IconSize } from '@proton/components';
6 import {
7     SidebarListItem,
8     SidebarListItemContent,
9     SidebarListItemContentIcon,
10     SidebarListItemLink,
11     useEventManager,
12     useHotkeys,
13     useItemsDroppable,
14 } from '@proton/components';
15 import { useLoading } from '@proton/hooks';
16 import { MAILBOX_LABEL_IDS } from '@proton/shared/lib/constants';
17 import { wait } from '@proton/shared/lib/helpers/promise';
18 import clsx from '@proton/utils/clsx';
19 import isTruthy from '@proton/utils/isTruthy';
20 import noop from '@proton/utils/noop';
22 import { useCheckAllRef } from 'proton-mail/containers/CheckAllRefProvider';
23 import useMailModel from 'proton-mail/hooks/useMailModel';
24 import { useSelectAll } from 'proton-mail/hooks/useSelectAll';
26 import { LABEL_IDS_TO_HUMAN } from '../../constants';
27 import { shouldDisplayTotal } from '../../helpers/labels';
28 import type { ApplyLabelsParams } from '../../hooks/actions/label/useApplyLabels';
29 import type { MoveParams } from '../../hooks/actions/move/useMoveToFolder';
30 import { useGetElementsFromIDs } from '../../hooks/mailbox/useElements';
31 import LocationAside from './LocationAside';
33 import './SidebarItem.scss';
35 const { ALL_MAIL, ALMOST_ALL_MAIL, ALL_DRAFTS, ALL_SENT, SCHEDULED, SNOOZED, OUTBOX } = MAILBOX_LABEL_IDS;
37 const NO_DROP_SET: Set<string> = new Set([ALL_MAIL, ALMOST_ALL_MAIL, ALL_DRAFTS, ALL_SENT, SCHEDULED, SNOOZED, OUTBOX]);
39 const defaultShortcutHandlers: HotkeyTuple[] = [];
41 interface Props {
42     currentLabelID: string;
43     labelID: string;
44     isFolder: boolean;
45     isLabel?: boolean;
46     hideCountOnHover?: boolean;
47     icon?: IconName;
48     iconSize?: IconSize;
49     text: string;
50     shortcutText?: string;
51     content?: ReactNode;
52     itemOptions?: ReactNode;
53     color?: string;
54     unreadCount?: number;
55     totalMessagesCount?: number;
56     shortcutHandlers?: HotkeyTuple[];
57     onFocus?: (id: string) => void;
58     isOptionDropdownOpened?: boolean;
59     id?: string;
60     collapsed?: boolean;
61     moveToFolder: (params: MoveParams) => void;
62     applyLabels: (params: ApplyLabelsParams) => void;
65 const SidebarItem = ({
66     currentLabelID,
67     labelID,
68     icon,
69     iconSize,
70     text,
71     shortcutText,
72     content = text,
73     itemOptions,
74     color,
75     isFolder,
76     isLabel,
77     hideCountOnHover = true,
78     unreadCount,
79     totalMessagesCount = 0,
80     shortcutHandlers = defaultShortcutHandlers,
81     onFocus = noop,
82     id,
83     isOptionDropdownOpened,
84     collapsed = false,
85     moveToFolder,
86     applyLabels,
87 }: Props) => {
88     const { call } = useEventManager();
89     const history = useHistory();
90     const { Shortcuts } = useMailModel('MailSettings');
91     const getElementsFromIDs = useGetElementsFromIDs();
92     const { selectAll } = useSelectAll({ labelID });
93     const { checkAllRef } = useCheckAllRef();
95     const [refreshing, withRefreshing] = useLoading(false);
97     const humanID = LABEL_IDS_TO_HUMAN[labelID as MAILBOX_LABEL_IDS]
98         ? LABEL_IDS_TO_HUMAN[labelID as MAILBOX_LABEL_IDS]
99         : labelID;
100     const link = `/${humanID}`;
102     const needsTotalDisplay = shouldDisplayTotal(labelID);
104     const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
105         if (
106             history.location.pathname.endsWith(link) &&
107             // No search, no paging, nothing
108             history.location.hash === '' &&
109             // Not already refreshing
110             !refreshing
111         ) {
112             event.preventDefault();
113             void withRefreshing(Promise.all([call(), wait(1000)]));
114         }
115     };
117     const handleRefresh = () => {
118         void withRefreshing(Promise.all([call(), wait(1000)]));
119     };
121     const dragFilter = useCallback(() => {
122         return !(labelID === currentLabelID) && !NO_DROP_SET.has(labelID);
123     }, [currentLabelID, labelID]);
125     const dropCallback = useCallback(
126         (itemIDs: string[]) => {
127             const elements = getElementsFromIDs(itemIDs);
129             if (dragFilter()) {
130                 if (isFolder) {
131                     void moveToFolder({
132                         elements,
133                         sourceLabelID: currentLabelID,
134                         destinationLabelID: labelID,
135                         folderName: text,
136                         selectAll,
137                         onCheckAll: checkAllRef?.current ? checkAllRef.current : undefined,
138                     });
139                 } else {
140                     void applyLabels({
141                         elements,
142                         changes: { [labelID]: true },
143                         labelID: currentLabelID,
144                         selectAll,
145                         onCheckAll: checkAllRef?.current ? checkAllRef.current : undefined,
146                     });
147                 }
148             }
149         },
150         [applyLabels, dragFilter, checkAllRef, currentLabelID, getElementsFromIDs, isFolder, labelID, selectAll]
151     );
153     const { dragOver, dragProps, handleDrop } = useItemsDroppable(dragFilter, isFolder ? 'move' : 'link', dropCallback);
155     const elementRef = useRef<HTMLAnchorElement>(null);
156     useHotkeys(elementRef, shortcutHandlers);
158     const tooltipText = text;
159     const titleText = shortcutText !== undefined && Shortcuts ? `${text} ${shortcutText}` : text;
161     return (
162         <SidebarListItem
163             className={clsx([
164                 dragOver && 'navigation__dragover',
165                 'group-hover-hide-container group-hover-opacity-container',
166             ])}
167             data-testid={`sidebar-label:${text}`}
168         >
169             <SidebarListItemLink
170                 title={tooltipText}
171                 to={link}
172                 onClick={handleClick}
173                 {...dragProps}
174                 onDrop={handleDrop}
175                 ref={elementRef}
176                 data-testid={`navigation-link:${humanID}`}
177                 data-shortcut-target={['navigation-link', id].filter(isTruthy).join(' ')}
178                 onFocus={() => onFocus(id || '')}
179             >
180                 <SidebarListItemContent
181                     collapsed={collapsed}
182                     left={
183                         icon ? (
184                             <SidebarListItemContentIcon
185                                 className={clsx([
186                                     collapsed && 'flex mx-auto',
187                                     isLabel && 'navigation-icon--fixAliasing',
188                                 ])}
189                                 name={icon}
190                                 color={color}
191                                 size={iconSize}
192                             />
193                         ) : undefined
194                     }
195                     right={
196                         <LocationAside
197                             unreadCount={needsTotalDisplay ? totalMessagesCount : unreadCount}
198                             weak={labelID !== MAILBOX_LABEL_IDS.INBOX}
199                             refreshing={refreshing}
200                             onRefresh={handleRefresh}
201                             shouldDisplayTotal={needsTotalDisplay}
202                             hideCountOnHover={hideCountOnHover}
203                             itemOptions={itemOptions}
204                             isOptionDropdownOpened={isOptionDropdownOpened}
205                             collapsed={collapsed}
206                         />
207                     }
208                 >
209                     <span
210                         className={clsx('text-ellipsis', collapsed && 'sr-only')}
211                         title={collapsed ? undefined : titleText}
212                     >
213                         {content}
214                     </span>
215                 </SidebarListItemContent>
216             </SidebarListItemLink>
217         </SidebarListItem>
218     );
221 export default memo(SidebarItem);