1 import { type FC, memo, useEffect, useMemo, useRef, useState } from 'react';
2 import { useSelector } from 'react-redux';
4 import { c } from 'ttag';
6 import { Button, Input } from '@proton/atoms';
7 import { Icon } from '@proton/components';
8 import { usePassCore } from '@proton/pass/components/Core/PassCoreProvider';
9 import { getItemTypeOptions } from '@proton/pass/components/Item/Filters/Type';
10 import { useNavigation } from '@proton/pass/components/Navigation/NavigationProvider';
11 import { useDebouncedValue } from '@proton/pass/hooks/useDebouncedValue';
12 import { useSearchShortcut } from '@proton/pass/hooks/useSearchShortcut';
13 import { selectShare } from '@proton/pass/store/selectors';
14 import type { MaybeNull, ShareType } from '@proton/pass/types';
15 import { TelemetryEventName } from '@proton/pass/types/data/telemetry';
16 import { isEmptyString } from '@proton/pass/utils/string/is-empty-string';
18 import './SearchBar.scss';
22 initial?: MaybeNull<string>;
26 const SEARCH_DEBOUNCE_TIME = 75;
28 const SearchBarRaw: FC<Props> = ({ disabled, initial, trash }) => {
29 const { onTelemetry } = usePassCore();
30 const { filters, setFilters, matchTrash } = useNavigation();
32 const [search, setSearch] = useState<string>(initial ?? '');
33 const debouncedSearch = useDebouncedValue(search, SEARCH_DEBOUNCE_TIME);
35 const inputRef = useRef<HTMLInputElement>(null);
36 const { selectedShareId, type = '*' } = filters;
38 const vault = useSelector(selectShare<ShareType.Vault>(selectedShareId));
40 const placeholder = useMemo(() => {
41 const ITEM_TYPE_TO_LABEL_MAP = getItemTypeOptions();
42 const pluralItemType = ITEM_TYPE_TO_LABEL_MAP[type].label.toLowerCase();
43 const vaultName = matchTrash ? c('Label').t`Trash` : vault?.content.name.trim();
47 return vault || matchTrash
48 ? c('Placeholder').t`Search in ${vaultName}`
49 : c('Placeholder').t`Search in all vaults`;
51 // translator: ${pluralItemType} can be either "logins", "notes", "aliases", or "cards". Full sentence example: "Search notes in all vaults"
52 return vault || matchTrash
53 ? c('Placeholder').t`Search ${pluralItemType} in ${vaultName}`
54 : c('Placeholder').t`Search ${pluralItemType} in all vaults`;
57 }, [vault, type, matchTrash]);
59 const handleClear = () => {
61 setFilters({ search: '' });
62 inputRef.current?.focus();
65 const handleFocus = () => inputRef.current?.select();
67 const handleBlur = () => {
68 if (isEmptyString(search)) return;
69 void onTelemetry(TelemetryEventName.SearchTriggered, {}, {});
72 useEffect(handleFocus, []);
73 useEffect(() => setFilters({ search: debouncedSearch }), [debouncedSearch]);
74 useEffect(() => setSearch((value) => filters.search || value), [filters.search]);
75 useSearchShortcut(handleFocus);
80 className="pass-searchbar"
81 inputClassName="text-rg"
86 placeholder={`${trash ? c('Placeholder').t`Search in Trash` : placeholder}…`}
87 prefix={<Icon name="magnifier" />}
98 title={c('Action').t`Clear search`}
100 <Icon name="cross" />
109 export const SearchBar = memo(SearchBarRaw);