diff options
Diffstat (limited to 'packages/renderer/src/components/locationBar/LocationTextField.tsx')
-rw-r--r-- | packages/renderer/src/components/locationBar/LocationTextField.tsx | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/packages/renderer/src/components/locationBar/LocationTextField.tsx b/packages/renderer/src/components/locationBar/LocationTextField.tsx new file mode 100644 index 0000000..9b028b3 --- /dev/null +++ b/packages/renderer/src/components/locationBar/LocationTextField.tsx | |||
@@ -0,0 +1,130 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import FilledInput from '@mui/material/FilledInput'; | ||
22 | import { styled } from '@mui/material/styles'; | ||
23 | import { autorun } from 'mobx'; | ||
24 | import { observer } from 'mobx-react-lite'; | ||
25 | import React, { useCallback, useEffect, useState } from 'react'; | ||
26 | |||
27 | import Service from '../../stores/Service.js'; | ||
28 | |||
29 | import GoButton from './GoButton.js'; | ||
30 | import LocationOverlayInput from './LocationOverlayInput.js'; | ||
31 | import SecurityLabel from './SecurityLabel.js'; | ||
32 | import UrlOverlay from './UrlOverlay.js'; | ||
33 | |||
34 | const LocationTextFieldRoot = styled(FilledInput, { | ||
35 | name: 'LocationTextField', | ||
36 | slot: 'Root', | ||
37 | })(({ theme }) => ({ | ||
38 | padding: '0 12px', | ||
39 | flex: '1 0 200px', | ||
40 | borderRadius: 18, | ||
41 | '&.Mui-focused': { | ||
42 | outline: `2px solid ${theme.palette.primary.main}`, | ||
43 | }, | ||
44 | })); | ||
45 | |||
46 | function LocationTextField({ service }: { service: Service }): JSX.Element { | ||
47 | const [inputFocused, setInputFocused] = useState(false); | ||
48 | const [changed, setChanged] = useState(false); | ||
49 | const [value, setValue] = useState(''); | ||
50 | |||
51 | const resetValue = useCallback(() => { | ||
52 | setValue(service.currentUrl ?? ''); | ||
53 | setChanged(false); | ||
54 | }, [service]); | ||
55 | |||
56 | useEffect( | ||
57 | () => | ||
58 | autorun(() => { | ||
59 | resetValue(); | ||
60 | }), | ||
61 | [resetValue], | ||
62 | ); | ||
63 | |||
64 | const inputRefCallback = useCallback( | ||
65 | (input: HTMLInputElement) => { | ||
66 | setInputFocused( | ||
67 | document.activeElement !== null && document.activeElement === input, | ||
68 | ); | ||
69 | }, | ||
70 | [setInputFocused], | ||
71 | ); | ||
72 | |||
73 | return ( | ||
74 | <LocationTextFieldRoot | ||
75 | inputComponent={LocationOverlayInput} | ||
76 | inputProps={{ | ||
77 | 'aria-label': 'Location', | ||
78 | spellCheck: false, | ||
79 | overlayVisible: !inputFocused && !changed, | ||
80 | overlay: ( | ||
81 | <UrlOverlay | ||
82 | url={service.currentUrl ?? ''} | ||
83 | alert={service.hasSecurityLabelWarning ?? false} | ||
84 | /> | ||
85 | ), | ||
86 | }} | ||
87 | inputRef={inputRefCallback} | ||
88 | onFocus={() => setInputFocused(true)} | ||
89 | onBlur={() => setInputFocused(false)} | ||
90 | onChange={({ target: { value: newValue } }) => { | ||
91 | setValue(newValue); | ||
92 | setChanged(true); | ||
93 | }} | ||
94 | onKeyDown={(event) => { | ||
95 | switch (event.key) { | ||
96 | case 'Escape': | ||
97 | resetValue(); | ||
98 | break; | ||
99 | case 'Enter': | ||
100 | service.go(value); | ||
101 | break; | ||
102 | default: | ||
103 | // Nothing to do, let the key event through. | ||
104 | return; | ||
105 | } | ||
106 | event.preventDefault(); | ||
107 | event.stopPropagation(); | ||
108 | }} | ||
109 | size="small" | ||
110 | fullWidth | ||
111 | hiddenLabel | ||
112 | disableUnderline | ||
113 | startAdornment={ | ||
114 | <SecurityLabel | ||
115 | kind={service.securityLabel} | ||
116 | changed={changed} | ||
117 | position="start" | ||
118 | /> | ||
119 | } | ||
120 | endAdornment={ | ||
121 | changed ? ( | ||
122 | <GoButton onClick={() => service.go(value)} position="end" /> | ||
123 | ) : undefined | ||
124 | } | ||
125 | value={value} | ||
126 | /> | ||
127 | ); | ||
128 | } | ||
129 | |||
130 | export default observer(LocationTextField); | ||