summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-10-04 11:09:41 +0200
committerOskar Nyberg <oskar@mullvad.net>2022-10-04 13:59:01 +0200
commitba8259249ce68959a8460bc184258a79f416d9b4 (patch)
tree262c27830c7d7de3d1df5422dc847ba899c1fd4f /gui/src
parenta739838f13d4cf7b79391f71dc5b00cd46b688f6 (diff)
downloadmullvadvpn-ba8259249ce68959a8460bc184258a79f416d9b4.tar.xz
mullvadvpn-ba8259249ce68959a8460bc184258a79f416d9b4.zip
Make input controlled
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/cell/Selector.tsx136
1 files changed, 59 insertions, 77 deletions
diff --git a/gui/src/renderer/components/cell/Selector.tsx b/gui/src/renderer/components/cell/Selector.tsx
index 11a2bc5e28..e225595e22 100644
--- a/gui/src/renderer/components/cell/Selector.tsx
+++ b/gui/src/renderer/components/cell/Selector.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useRef, useState } from 'react';
+import { useCallback, useRef, useState } from 'react';
import styled from 'styled-components';
import { colors } from '../../../config.json';
@@ -202,108 +202,90 @@ export function SelectorWithCustomItem<T, U>(props: SelectorWithCustomItemProps<
...otherProps
} = props;
- // The component needs to keep track of when the custom item should look selected before it has a
- // value.
- const [customWithoutValue, setCustomWithoutValue, unsetCustomWithoutValue] = useBoolean(false);
-
const isNonCustomItem = (value: T | U | undefined) =>
props.items.some((item) => item.value === value) || props.automaticValue === value;
const itemIsSelected = isNonCustomItem(value);
- const customIsSelected = !itemIsSelected || customWithoutValue;
- // The input key is used to clear the input state.
- const [inputKey, setInputKey] = useState(1);
- const resetInput = () => setInputKey((key) => key + 1);
+ // Value of custom input. The value is undefined when custom isn't picked.
+ const [customValue, setCustomValue] = useState(itemIsSelected ? undefined : `${value}`);
+ const customIsSelected = customValue !== undefined;
+
const inputRef = useRef() as React.RefObject<HTMLInputElement>;
- const handleClick = useCallback(() => {
+ const handleClickCustom = useCallback(() => {
inputRef.current?.focus();
- if (!customIsSelected) {
- setCustomWithoutValue();
- }
- }, [customIsSelected, inputRef.current]);
-
- const handleMouseDown = useCallback((event: React.MouseEvent) => event.preventDefault(), []);
+ setCustomValue((customValue) => customValue ?? '');
+ }, [customValue, inputRef.current]);
- // Wrap onSelect to be able to catch when a new value is selected during the
- // customIsSelectedWithoutValue phase. Value wont be undefined here since undefined items aren't
- // allowed.
- const handleSelectValue = useCallback(
+ const handleSelectItem = useCallback(
(newValue: T | U | undefined) => {
- resetInput();
+ setCustomValue(undefined);
+
onSelect(newValue!);
},
[value, onSelect],
);
- const validateStringValue = useCallback(
+ const validateCustomValue = useCallback(
(value: string) => validateValue?.(parseValue(value)) ?? true,
[parseValue, validateValue],
);
- const handleSubmit = useCallback(
- (stringValue: string) => {
- const value = parseValue(stringValue);
+ const handleSubmitCustom = useCallback(
+ (newStringValue: string) => {
+ const newValue = parseValue(newStringValue);
- if (isNonCustomItem(value)) {
- resetInput();
+ if (isNonCustomItem(newValue)) {
+ handleSelectItem(newValue);
+ } else {
+ onSelect(newValue);
}
-
- onSelect(value);
},
- [parseValue, onSelect],
+ [value, parseValue, onSelect],
);
- const handleInvalid = useCallback(() => {
- resetInput();
- unsetCustomWithoutValue();
- }, []);
-
- useEffect(() => {
- if (customWithoutValue && itemIsSelected) {
- unsetCustomWithoutValue();
- }
- }, [value]);
+ const handleInvalidCustom = useCallback(
+ () => setCustomValue(itemIsSelected ? undefined : `${value}`),
+ [itemIsSelected, value],
+ );
return (
- <div onMouseDown={handleMouseDown}>
- <Selector<T | undefined, U>
- {...otherProps}
- onSelect={handleSelectValue}
- value={customIsSelected ? undefined : value}>
- <StyledCustomContainer
- ref={customIsSelected ? props.selectedCellRef : undefined}
- onClick={handleClick}
- selected={customIsSelected}
- disabled={props.disabled}
- role="option"
- aria-selected={customIsSelected}
- aria-disabled={props.disabled}>
- <StyledCellIcon
- visible={customIsSelected}
- source="icon-tick"
- width={18}
- tintColor={colors.white}
+ <Selector<T | undefined, U>
+ {...otherProps}
+ onSelect={handleSelectItem}
+ value={customIsSelected ? undefined : value}>
+ <StyledCustomContainer
+ ref={customIsSelected ? props.selectedCellRef : undefined}
+ onClick={handleClickCustom}
+ selected={customIsSelected}
+ disabled={props.disabled}
+ role="option"
+ aria-selected={customIsSelected}
+ aria-disabled={props.disabled}>
+ <StyledCellIcon
+ visible={customIsSelected}
+ source="icon-tick"
+ width={18}
+ tintColor={colors.white}
+ />
+ <StyledLabel>{messages.gettext('Custom')}</StyledLabel>
+ <AriaInput>
+ <Cell.AutoSizingTextInput
+ ref={inputRef}
+ value={customValue ?? ''}
+ placeholder={inputPlaceholder}
+ inputMode={'numeric'}
+ maxLength={maxLength ?? 4}
+ onChangeValue={setCustomValue}
+ onSubmitValue={handleSubmitCustom}
+ onInvalidValue={handleInvalidCustom}
+ submitOnBlur={true}
+ validateValue={validateCustomValue}
+ modifyValue={modifyValue}
/>
- <StyledLabel>{messages.gettext('Custom')}</StyledLabel>
- <AriaInput>
- <Cell.AutoSizingTextInput
- key={inputKey}
- ref={inputRef}
- value={itemIsSelected || customWithoutValue ? '' : `${props.value}`}
- placeholder={inputPlaceholder}
- inputMode={'numeric'}
- maxLength={maxLength ?? 4}
- onSubmitValue={handleSubmit}
- onInvalidValue={handleInvalid}
- submitOnBlur={true}
- validateValue={validateStringValue}
- modifyValue={modifyValue}
- />
- </AriaInput>
- </StyledCustomContainer>
- </Selector>
- </div>
+ </AriaInput>
+ </StyledCustomContainer>
+ </Selector>
);
}