Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / pass / components / Invite / InviteRecommendations.tsx
blob6dd91c091afb98f242a6639f1b240c1772a389bc
1 import { type FC, useMemo, useRef, useState } from 'react';
2 import { useSelector } from 'react-redux';
3 import type { List } from 'react-virtualized';
5 import { c } from 'ttag';
7 import { Button, CircleLoader } from '@proton/atoms';
8 import { Checkbox } from '@proton/components';
9 import { ButtonBar } from '@proton/pass/components/Layout/Button/ButtonBar';
10 import { ShareMemberAvatar } from '@proton/pass/components/Share/ShareMemberAvatar';
11 import { useDebouncedValue } from '@proton/pass/hooks/useDebouncedValue';
12 import { useInviteRecommendations } from '@proton/pass/hooks/useInviteRecommendations';
13 import { selectDefaultVault } from '@proton/pass/store/selectors';
14 import type { MaybeNull } from '@proton/pass/types';
15 import { isEmptyString } from '@proton/pass/utils/string/is-empty-string';
16 import clsx from '@proton/utils/clsx';
18 import { VirtualList } from '../Layout/List/VirtualList';
20 type Props = {
21     autocomplete: string;
22     excluded: Set<string>;
23     selected: Set<string>;
24     shareId?: string;
25     onToggle: (email: string, selected: boolean) => void;
28 const pageSize = 50;
30 export const InviteRecommendations: FC<Props> = (props) => {
31     const { autocomplete, excluded, selected, onToggle } = props;
32     const [view, setView] = useState<MaybeNull<string>>(null);
33     const listRef = useRef<List>(null);
35     const startsWith = useDebouncedValue(autocomplete, 250);
36     const defaultVault = useSelector(selectDefaultVault);
37     const shareId = props.shareId ?? defaultVault?.shareId ?? '';
39     const { loadMore, state } = useInviteRecommendations(startsWith, { pageSize, shareId });
40     const { organization, emails, loading } = state;
42     const displayedEmails = useMemo(() => {
43         const startsWith = autocomplete.toLowerCase();
44         const displayed = organization !== null && view === organization.name ? organization.emails : emails;
46         return isEmptyString(startsWith)
47             ? displayed
48             : displayed.filter((email) => email.toLowerCase().startsWith(startsWith));
49     }, [emails, organization, view, autocomplete]);
51     /** Used to compute the virtual list min-height as this component
52      * may be wrapped in a scrollable element */
53     const maxVisibleItems = Math.min(displayedEmails.length, pageSize - 1);
55     return (
56         <>
57             <h2 className="text-lg text-bold color-weak pb-2 shrink-0">
58                 {c('Title').t`Suggestions`} {loading && <CircleLoader size="small" className="ml-2" />}
59             </h2>
61             {organization !== null && (
62                 <ButtonBar className="anime-fade-in shrink-0 mb-3" size="small">
63                     <Button
64                         onClick={() => setView(null)}
65                         selected={view === null}
66                         className="flex-auto text-semibold"
67                         pill
68                     >
69                         {
70                             // translator: this is a label to show recent emails
71                             c('Label').t`Recent`
72                         }
73                     </Button>
74                     <Button
75                         onClick={() => setView(organization.name)}
76                         selected={view === organization.name}
77                         className="flex-auto text-semibold"
78                         pill
79                     >
80                         {organization.name}
81                     </Button>
82                 </ButtonBar>
83             )}
85             <div
86                 className="flex-1 min-h-custom overflow-hidden"
87                 style={{ '--min-h-custom': `${40 * maxVisibleItems + 15}px` }}
88             >
89                 {displayedEmails.length === 0 && !loading ? (
90                     <em className="color-weak anime-fade-in"> {c('Warning').t`No results`}</em>
91                 ) : (
92                     <VirtualList
93                         ref={listRef}
94                         onScrollEnd={() => {
95                             /** recent emails are not paginated - only trigger a new paginated
96                              * request if we have more organization suggestions to load */
97                             if (view === organization?.name) loadMore();
98                         }}
99                         rowHeight={() => 40}
100                         rowRenderer={({ style, index, key }) => {
101                             const email = displayedEmails[index];
102                             const disabled = excluded.has(email);
103                             return (
104                                 <div style={style} key={key} className="flex anime-fade-in">
105                                     <Checkbox
106                                         key={`suggestion-${view}-${index}`}
107                                         className={clsx('flex flex-row-reverse flex-1 ml-2', disabled && 'opacity-0')}
108                                         disabled={disabled}
109                                         checked={selected.has(email) || disabled}
110                                         onChange={({ target }) => onToggle(email, target.checked)}
111                                     >
112                                         <div className="flex flex-nowrap items-center flex-1">
113                                             <ShareMemberAvatar value={email.toUpperCase().slice(0, 2) ?? ''} />
114                                             <div className="flex-1 text-ellipsis color-white mr-2">{email}</div>
115                                         </div>
116                                     </Checkbox>
117                                 </div>
118                             );
119                         }}
120                         rowCount={displayedEmails.length}
121                     />
122                 )}
123             </div>
124         </>
125     );