-
Notifications
You must be signed in to change notification settings - Fork 294
Expand file tree
/
Copy pathSearchBar.tsx
More file actions
124 lines (116 loc) · 3.95 KB
/
SearchBar.tsx
File metadata and controls
124 lines (116 loc) · 3.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { useSearchContext } from '../SearchContext';
import { useSearchQueriesInProgress } from '../hooks';
import { useTranslationContext } from '../../../context';
import { useStateStore } from '../../../store';
import { Button, IconCircleX, IconMagnifyingGlassSearch } from '../../../components';
import type { SearchControllerState } from 'stream-chat';
const searchControllerStateSelector = (nextValue: SearchControllerState) => ({
isActive: nextValue.isActive,
searchQuery: nextValue.searchQuery,
});
export const SearchBar = () => {
const { t } = useTranslationContext();
const {
disabled,
exitSearchOnInputBlur,
filterButtonsContainerRef,
placeholder,
searchController,
} = useSearchContext();
const queriesInProgress = useSearchQueriesInProgress(searchController);
const clearButtonRef = React.useRef<HTMLButtonElement | null>(null);
const [input, setInput] = useState<HTMLInputElement | null>(null);
const { isActive, searchQuery } = useStateStore(
searchController.state,
searchControllerStateSelector,
);
useEffect(() => {
if (!input) return;
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
input.blur();
searchController.exit();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [searchController, input]);
return (
<div className='str-chat__search-bar' data-testid='search-bar'>
<div
className={clsx('str-chat__search-bar__input-wrapper', {
'str-chat__search-bar__input-wrapper--active': isActive,
})}
>
<IconMagnifyingGlassSearch />
<input
className='str-chat__search-bar__input'
data-testid='search-input'
disabled={disabled}
onBlur={({ currentTarget, relatedTarget }) => {
if (
exitSearchOnInputBlur &&
// input is empty
!currentTarget.value &&
// clicking on filter buttons or clear button shouldn't trigger exit search on blur
!filterButtonsContainerRef.current?.contains(relatedTarget) &&
// clicking clear button shouldn't trigger exit search on blur
(!clearButtonRef.current || relatedTarget !== clearButtonRef.current)
) {
searchController.exit();
}
}}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value) {
searchController.search(event.target.value);
} else if (!event.target.value) {
searchController.clear();
}
}}
onFocus={searchController.activate}
placeholder={placeholder ?? t('Search')}
ref={setInput}
type='text'
value={searchQuery}
/>
{searchQuery && (
<Button
appearance='ghost'
circular
className='str-chat__search-bar__clear-button'
data-testid='clear-input-button'
disabled={queriesInProgress.length > 0} // prevent user from clearing the input while query is in progress and avoid out-of-sync UX
onClick={() => {
searchController.clear();
input?.focus();
}}
ref={clearButtonRef}
size='xs'
variant='secondary'
>
<IconCircleX />
</Button>
)}
</div>
{isActive && (
<Button
appearance='ghost'
className='str-chat__search-bar__exit-search-button'
data-testid='search-bar-button'
onClick={() => {
input?.blur();
searchController.exit();
}}
size='sm'
variant='secondary'
>
{t('Cancel')}
</Button>
)}
</div>
);
};