Skip to content

Commit 20ecef7

Browse files
author
k.golikov
committed
Enhance app search
1 parent 6b05caa commit 20ecef7

File tree

2 files changed

+86
-22
lines changed

2 files changed

+86
-22
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
.container {
2+
position: relative;
3+
}
4+
5+
.autoComplete {
26
margin-left: 80px;
37
width: 230px;
48
max-width: 230px;
59
}
10+
11+
.keyHint {
12+
position: absolute;
13+
right: 40px;
14+
top: -2px;
15+
pointer-events: none;
16+
}

src/layouts/appLayout/components/appHeader/components/appHeaderSearch/AppHeaderSearch.tsx

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { FunctionComponent, useCallback, useRef, useState } from 'react';
22
import Search from 'antd/lib/input/Search';
33
import styles from './AppHeaderSearch.module.scss';
4-
import { AutoComplete } from 'antd';
4+
import { AutoComplete, InputRef } from 'antd';
55
import Text from 'antd/lib/typography/Text';
66
import { DefaultOptionType, FilterFunc, SelectHandler } from 'rc-select/lib/Select';
77
import { BaseSelectRef } from 'rc-select/lib/BaseSelect';
@@ -10,6 +10,8 @@ import { MenuRouteItem } from '../../../../utils/routeMenuItems';
1010
import { useNavigate } from 'react-router-dom';
1111
import { AutoCompleteProps } from 'antd/lib/auto-complete';
1212
import classNames from 'classnames';
13+
import { useKey } from 'rooks';
14+
import { isEmpty } from 'lodash';
1315

1416
interface OptionType extends DefaultOptionType {
1517
data: MenuRouteItem;
@@ -28,12 +30,14 @@ const allSearchOptions: OptionType[] = menuRouteItems.map((item) => {
2830
const filterOption: FilterFunc<OptionType> = (inputValue, option) => {
2931
const query = inputValue.trim().toLocaleLowerCase();
3032

33+
const isEmptyQuery = isEmpty(query);
34+
3135
if (option === undefined) {
32-
return !query;
36+
return isEmptyQuery;
3337
}
3438

35-
if (!query) {
36-
return false;
39+
if (isEmptyQuery) {
40+
return true;
3741
}
3842

3943
return String(option.label).toLocaleLowerCase().includes(query);
@@ -46,36 +50,85 @@ interface Props extends Omit<AutoCompleteProps, 'options' | 'filterOption' | 'on
4650
const AppHeaderSearch: FunctionComponent<Props> = ({ className, inputClassName, ...props }) => {
4751
const navigate = useNavigate();
4852

53+
const searchInputRef = useRef<InputRef>(null);
54+
4955
const [query, setQuery] = useState<string>('');
5056

5157
const autoCompleteRef = useRef<BaseSelectRef>(null);
5258

59+
const selectOption = useCallback((option: MenuRouteItem) => {
60+
const path = option.route.path;
61+
62+
navigate(path);
63+
64+
setTimeout(() => {
65+
setQuery(' ');
66+
});
67+
setTimeout(() => {
68+
setQuery('');
69+
});
70+
autoCompleteRef.current?.blur();
71+
}, []);
72+
5373
const handleSelect = useCallback<SelectHandler<string, OptionType>>(
5474
(label: string, { data }: OptionType) => {
55-
const path = data.route.path;
75+
selectOption(data);
76+
},
77+
[navigate, selectOption]
78+
);
5679

57-
navigate(path);
80+
const handleSearch = useCallback(
81+
(value: string) => {
82+
if (!query.trim().length) {
83+
return;
84+
}
5885

59-
setQuery('');
60-
autoCompleteRef.current?.blur();
86+
const matchingOptions = allSearchOptions.filter((option) => filterOption(value, option));
87+
if (!matchingOptions.length) {
88+
return;
89+
}
90+
91+
const option = matchingOptions[0];
92+
selectOption(option.data);
6193
},
62-
[navigate]
94+
[selectOption, query]
6395
);
6496

97+
useKey(['/'], (event) => {
98+
const activeTag = document.activeElement?.tagName;
99+
if (activeTag && ['input', 'textarea'].includes(activeTag)) {
100+
return;
101+
}
102+
103+
if (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey) {
104+
return;
105+
}
106+
107+
searchInputRef.current?.focus();
108+
event.preventDefault();
109+
});
110+
65111
return (
66-
<AutoComplete
67-
options={allSearchOptions}
68-
className={classNames(styles.container, className)}
69-
filterOption={filterOption}
70-
onSelect={handleSelect}
71-
notFoundContent={<Text type="secondary">No results</Text>}
72-
value={query}
73-
onChange={setQuery}
74-
ref={autoCompleteRef}
75-
{...props}
76-
>
77-
<Search placeholder="Search" className={inputClassName} />
78-
</AutoComplete>
112+
<div className={styles.container}>
113+
<AutoComplete
114+
options={allSearchOptions}
115+
className={classNames(styles.autoComplete, className)}
116+
filterOption={filterOption}
117+
onSelect={handleSelect}
118+
notFoundContent={<Text type="secondary">No results</Text>}
119+
value={query}
120+
onChange={setQuery}
121+
ref={autoCompleteRef}
122+
{...props}
123+
>
124+
<Search ref={searchInputRef} onSearch={handleSearch} placeholder="Search" className={inputClassName} />
125+
</AutoComplete>
126+
{!query.length && (
127+
<Text keyboard className={styles.keyHint} type="secondary">
128+
/
129+
</Text>
130+
)}
131+
</div>
79132
);
80133
};
81134

0 commit comments

Comments
 (0)