aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/editor/SearchToolbar.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/editor/SearchToolbar.tsx')
-rw-r--r--subprojects/frontend/src/editor/SearchToolbar.tsx198
1 files changed, 198 insertions, 0 deletions
diff --git a/subprojects/frontend/src/editor/SearchToolbar.tsx b/subprojects/frontend/src/editor/SearchToolbar.tsx
new file mode 100644
index 00000000..2840290b
--- /dev/null
+++ b/subprojects/frontend/src/editor/SearchToolbar.tsx
@@ -0,0 +1,198 @@
1import CloseIcon from '@mui/icons-material/Close';
2import FindReplaceIcon from '@mui/icons-material/FindReplace';
3import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
4import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
5import SearchIcon from '@mui/icons-material/Search';
6import Button from '@mui/material/Button';
7import Checkbox from '@mui/material/Checkbox';
8import FormControlLabel from '@mui/material/FormControlLabel';
9import FormHelperText from '@mui/material/FormHelperText';
10import IconButton from '@mui/material/IconButton';
11import Stack from '@mui/material/Stack';
12import TextField from '@mui/material/TextField';
13import Toolbar from '@mui/material/Toolbar';
14import { observer } from 'mobx-react-lite';
15import React, { useCallback } from 'react';
16
17import type SearchPanelStore from './SearchPanelStore';
18
19function SearchToolbar({ store }: { store: SearchPanelStore }): JSX.Element {
20 const {
21 id: panelId,
22 query: { search, valid, caseSensitive, literal, regexp, replace },
23 invalidRegexp,
24 } = store;
25
26 const searchHelperId = `${panelId}-search-helper`;
27
28 const searchFieldRef = useCallback(
29 (element: HTMLInputElement | null) =>
30 store.setSearchField(element ?? undefined),
31 [store],
32 );
33
34 return (
35 <Toolbar variant="dense" sx={{ py: 0.5, alignItems: 'start' }}>
36 <Stack
37 direction="row"
38 flexWrap="wrap"
39 alignItems="center"
40 rowGap={0.5}
41 flexGrow={1}
42 >
43 <Stack direction="row" flexWrap="wrap" alignItems="center" rowGap={0.5}>
44 <TextField
45 type="search"
46 placeholder="Search"
47 aria-label="Search"
48 {...(invalidRegexp && {
49 'aria-describedby': searchHelperId,
50 })}
51 value={search}
52 error={invalidRegexp}
53 onChange={(event) =>
54 store.updateQuery({ search: event.target.value })
55 }
56 onKeyDown={(event) => {
57 if (event.key === 'Enter') {
58 event.preventDefault();
59 if (event.shiftKey) {
60 store.findPrevious();
61 } else {
62 store.findNext();
63 }
64 }
65 }}
66 variant="standard"
67 size="small"
68 sx={{ my: 0.25, mr: 1 }}
69 inputRef={searchFieldRef}
70 />
71 {invalidRegexp && (
72 <FormHelperText
73 id={searchHelperId}
74 sx={(theme) => ({
75 my: 0,
76 mr: 1,
77 fontSize: 'inherit',
78 color: theme.palette.error.main,
79 })}
80 >
81 Invalid regexp
82 </FormHelperText>
83 )}
84 <Stack
85 direction="row"
86 flexWrap="wrap"
87 alignItems="center"
88 mr={1}
89 rowGap={0.5}
90 >
91 <IconButton
92 aria-label="Previous"
93 disabled={!valid}
94 onClick={() => store.findPrevious()}
95 >
96 <KeyboardArrowUpIcon fontSize="small" />
97 </IconButton>
98 <IconButton
99 aria-label="Next"
100 disabled={!valid}
101 onClick={() => store.findNext()}
102 >
103 <KeyboardArrowDownIcon fontSize="small" />
104 </IconButton>
105 <Button
106 disabled={!valid}
107 onClick={() => store.selectMatches()}
108 color="inherit"
109 startIcon={<SearchIcon fontSize="inherit" />}
110 >
111 Find all
112 </Button>
113 </Stack>
114 <Stack direction="row" flexWrap="wrap" rowGap={0.5}>
115 <FormControlLabel
116 control={
117 <Checkbox
118 checked={caseSensitive}
119 onChange={(event) =>
120 store.updateQuery({ caseSensitive: event.target.checked })
121 }
122 size="small"
123 />
124 }
125 label="Match case"
126 />
127 <FormControlLabel
128 control={
129 <Checkbox
130 checked={literal}
131 onChange={(event) =>
132 store.updateQuery({ literal: event.target.checked })
133 }
134 size="small"
135 />
136 }
137 label="Literal"
138 />
139 <FormControlLabel
140 control={
141 <Checkbox
142 checked={regexp}
143 onChange={(event) =>
144 store.updateQuery({ regexp: event.target.checked })
145 }
146 size="small"
147 />
148 }
149 label="Regexp"
150 />
151 </Stack>
152 </Stack>
153 <Stack direction="row" flexWrap="wrap" alignItems="center" rowGap={0.5}>
154 <TextField
155 placeholder="Replace with"
156 aria-label="Replace with"
157 value={replace}
158 onChange={(event) =>
159 store.updateQuery({ replace: event.target.value })
160 }
161 onKeyDown={(event) => {
162 if (event.key === 'Enter') {
163 event.preventDefault();
164 store.replaceNext();
165 }
166 }}
167 variant="standard"
168 size="small"
169 sx={{ mr: 1 }}
170 />
171 <Stack direction="row" flexWrap="wrap" rowGap={0.5}>
172 <Button
173 disabled={!valid}
174 onClick={() => store.replaceNext()}
175 color="inherit"
176 startIcon={<FindReplaceIcon fontSize="inherit" />}
177 >
178 Replace
179 </Button>
180 <Button
181 disabled={!valid}
182 onClick={() => store.replaceAll()}
183 color="inherit"
184 startIcon={<FindReplaceIcon fontSize="inherit" />}
185 >
186 Replace all
187 </Button>
188 </Stack>
189 </Stack>
190 </Stack>
191 <IconButton onClick={() => store.close()} sx={{ ml: 1 }}>
192 <CloseIcon fontSize="small" />
193 </IconButton>
194 </Toolbar>
195 );
196}
197
198export default observer(SearchToolbar);