diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-08-17 21:43:29 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-08-17 21:43:29 +0200 |
commit | bb900e1bd40a6b7efd7a538114d985ea7f7e3e88 (patch) | |
tree | bb15a937ade92313dc654a640bc1de925442eff2 /subprojects/frontend/src/editor/SearchToolbar.tsx | |
parent | refactor(frondend): improve editor store and theme (diff) | |
download | refinery-bb900e1bd40a6b7efd7a538114d985ea7f7e3e88.tar.gz refinery-bb900e1bd40a6b7efd7a538114d985ea7f7e3e88.tar.zst refinery-bb900e1bd40a6b7efd7a538114d985ea7f7e3e88.zip |
feat(frontend): custom search panel
Also improves editor styling (to enable panel styling).
Diffstat (limited to 'subprojects/frontend/src/editor/SearchToolbar.tsx')
-rw-r--r-- | subprojects/frontend/src/editor/SearchToolbar.tsx | 198 |
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 @@ | |||
1 | import CloseIcon from '@mui/icons-material/Close'; | ||
2 | import FindReplaceIcon from '@mui/icons-material/FindReplace'; | ||
3 | import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; | ||
4 | import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; | ||
5 | import SearchIcon from '@mui/icons-material/Search'; | ||
6 | import Button from '@mui/material/Button'; | ||
7 | import Checkbox from '@mui/material/Checkbox'; | ||
8 | import FormControlLabel from '@mui/material/FormControlLabel'; | ||
9 | import FormHelperText from '@mui/material/FormHelperText'; | ||
10 | import IconButton from '@mui/material/IconButton'; | ||
11 | import Stack from '@mui/material/Stack'; | ||
12 | import TextField from '@mui/material/TextField'; | ||
13 | import Toolbar from '@mui/material/Toolbar'; | ||
14 | import { observer } from 'mobx-react-lite'; | ||
15 | import React, { useCallback } from 'react'; | ||
16 | |||
17 | import type SearchPanelStore from './SearchPanelStore'; | ||
18 | |||
19 | function 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 | |||
198 | export default observer(SearchToolbar); | ||