summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-02-09 12:51:20 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-02-09 12:51:20 +0100
commit5d0be2864ca826669681c0cd3bd818629096225e (patch)
tree8749a291404ee1751b7aed70d5ad9228b4f0e07c /gui/src/renderer
parent00d8d25f997cb6d1c7d1fc68e941d94dbca48dce (diff)
parentf371ebe164a397b42aa4c1f5076e390820e774d6 (diff)
downloadmullvadvpn-5d0be2864ca826669681c0cd3bd818629096225e.tar.xz
mullvadvpn-5d0be2864ca826669681c0cd3bd818629096225e.zip
Merge branch 'improve-small-cell-input'
Diffstat (limited to 'gui/src/renderer')
-rw-r--r--gui/src/renderer/components/AdvancedSettingsStyles.tsx4
-rw-r--r--gui/src/renderer/components/OpenVPNSettings.tsx30
-rw-r--r--gui/src/renderer/components/WireguardSettings.tsx30
-rw-r--r--gui/src/renderer/components/cell/Input.tsx108
4 files changed, 94 insertions, 78 deletions
diff --git a/gui/src/renderer/components/AdvancedSettingsStyles.tsx b/gui/src/renderer/components/AdvancedSettingsStyles.tsx
index c3b9a140d4..9e6ac73160 100644
--- a/gui/src/renderer/components/AdvancedSettingsStyles.tsx
+++ b/gui/src/renderer/components/AdvancedSettingsStyles.tsx
@@ -4,10 +4,6 @@ import * as Cell from './cell';
import { NavigationScrollbars } from './NavigationBar';
import Selector from './cell/Selector';
-export const StyledInputFrame = styled(Cell.InputFrame)({
- flex: 0,
-});
-
export const StyledSelectorContainer = styled.div({
flex: 0,
});
diff --git a/gui/src/renderer/components/OpenVPNSettings.tsx b/gui/src/renderer/components/OpenVPNSettings.tsx
index ce2cbe264b..480180eb67 100644
--- a/gui/src/renderer/components/OpenVPNSettings.tsx
+++ b/gui/src/renderer/components/OpenVPNSettings.tsx
@@ -47,10 +47,6 @@ export const StyledSelectorContainer = styled.div({
flex: 0,
});
-export const StyledInputFrame = styled(Cell.InputFrame)({
- flex: 0,
-});
-
interface IProps {
bridgeModeAvailablity: BridgeModeAvailability;
openvpn: {
@@ -195,20 +191,18 @@ export default class OpenVpnSettings extends React.Component<IProps, IState> {
{messages.pgettext('openvpn-settings-view', 'Mssfix')}
</Cell.InputLabel>
</AriaLabel>
- <StyledInputFrame>
- <AriaInput>
- <Cell.AutoSizingTextInput
- value={this.props.mssfix ? this.props.mssfix.toString() : ''}
- inputMode={'numeric'}
- maxLength={4}
- placeholder={messages.gettext('Default')}
- onSubmitValue={this.onMssfixSubmit}
- validateValue={OpenVpnSettings.mssfixIsValid}
- submitOnBlur={true}
- modifyValue={OpenVpnSettings.removeNonNumericCharacters}
- />
- </AriaInput>
- </StyledInputFrame>
+ <AriaInput>
+ <Cell.AutoSizingTextInput
+ value={this.props.mssfix ? this.props.mssfix.toString() : ''}
+ inputMode={'numeric'}
+ maxLength={4}
+ placeholder={messages.gettext('Default')}
+ onSubmitValue={this.onMssfixSubmit}
+ validateValue={OpenVpnSettings.mssfixIsValid}
+ submitOnBlur={true}
+ modifyValue={OpenVpnSettings.removeNonNumericCharacters}
+ />
+ </AriaInput>
</Cell.Container>
<Cell.Footer>
<AriaDescription>
diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx
index 8d5d47f38c..2159194a74 100644
--- a/gui/src/renderer/components/WireguardSettings.tsx
+++ b/gui/src/renderer/components/WireguardSettings.tsx
@@ -43,10 +43,6 @@ export const StyledSelectorForFooter = (styled(Selector)({
marginBottom: 0,
}) as unknown) as new <T>() => Selector<T>;
-export const StyledInputFrame = styled(Cell.InputFrame)({
- flex: 0,
-});
-
interface IProps {
wireguard: { port?: number; ipVersion?: IpVersion };
wireguardMtu?: number;
@@ -227,20 +223,18 @@ export default class WireguardSettings extends React.Component<IProps, IState> {
{messages.pgettext('wireguard-settings-view', 'MTU')}
</Cell.InputLabel>
</AriaLabel>
- <StyledInputFrame>
- <AriaInput>
- <Cell.AutoSizingTextInput
- value={this.props.wireguardMtu ? this.props.wireguardMtu.toString() : ''}
- inputMode={'numeric'}
- maxLength={4}
- placeholder={messages.gettext('Default')}
- onSubmitValue={this.onWireguardMtuSubmit}
- validateValue={WireguardSettings.wireguarMtuIsValid}
- submitOnBlur={true}
- modifyValue={WireguardSettings.removeNonNumericCharacters}
- />
- </AriaInput>
- </StyledInputFrame>
+ <AriaInput>
+ <Cell.AutoSizingTextInput
+ value={this.props.wireguardMtu ? this.props.wireguardMtu.toString() : ''}
+ inputMode={'numeric'}
+ maxLength={4}
+ placeholder={messages.gettext('Default')}
+ onSubmitValue={this.onWireguardMtuSubmit}
+ validateValue={WireguardSettings.wireguarMtuIsValid}
+ submitOnBlur={true}
+ modifyValue={WireguardSettings.removeNonNumericCharacters}
+ />
+ </AriaInput>
</Cell.Container>
<Cell.Footer>
<AriaDescription>
diff --git a/gui/src/renderer/components/cell/Input.tsx b/gui/src/renderer/components/cell/Input.tsx
index fbc6849de3..4215f6e96e 100644
--- a/gui/src/renderer/components/cell/Input.tsx
+++ b/gui/src/renderer/components/cell/Input.tsx
@@ -5,6 +5,7 @@ import { mediumText } from '../common-styles';
import { CellDisabledContext, Container } from './Container';
import StandaloneSwitch from '../Switch';
import ImageView from '../ImageView';
+import { useBoolean } from '../../lib/utilityHooks';
export const Switch = React.forwardRef(function SwitchT(
props: StandaloneSwitch['props'],
@@ -14,13 +15,6 @@ export const Switch = React.forwardRef(function SwitchT(
return <StandaloneSwitch ref={ref} disabled={disabled} {...props} />;
});
-export const InputFrame = styled.div({
- flexGrow: 0,
- backgroundColor: 'rgba(255,255,255,0.1)',
- borderRadius: '4px',
- padding: '4px 8px',
-});
-
const inputTextStyles: React.CSSProperties = {
...mediumText,
fontWeight: 600,
@@ -29,36 +23,18 @@ const inputTextStyles: React.CSSProperties = {
padding: '0px',
};
-const StyledInput = styled.input({}, (props: { valid?: boolean }) => ({
+const StyledInput = styled.input({}, (props: { focused: boolean; valid?: boolean }) => ({
...inputTextStyles,
backgroundColor: 'transparent',
border: 'none',
width: '100%',
height: '100%',
- color: props.valid !== false ? colors.white : colors.red,
+ color: props.valid === false ? colors.red : props.focused ? colors.blue : colors.white,
'::placeholder': {
- color: colors.white60,
+ color: props.focused ? colors.blue60 : colors.white60,
},
}));
-const StyledAutoSizingTextInputContainer = styled.div({
- position: 'relative',
-});
-
-const StyledAutoSizingTextInputFiller = styled.pre({
- ...inputTextStyles,
- minWidth: '80px',
- color: 'transparent',
-});
-
-const StyledAutoSizingTextInputWrapper = styled.div({
- position: 'absolute',
- top: '0px',
- left: '0px',
- width: '100%',
- height: '100%',
-});
-
interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
value?: string;
validateValue?: (value: string) => boolean;
@@ -123,6 +99,7 @@ export class Input extends React.Component<IInputProps, IInputState> {
ref={this.inputRef}
type="text"
valid={valid}
+ focused={this.state.focused}
aria-invalid={!valid}
onChange={this.onChange}
onFocus={this.onFocus}
@@ -151,9 +128,12 @@ export class Input extends React.Component<IInputProps, IInputState> {
private onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
this.setState({ focused: false });
+
this.props.onBlur?.(event);
- if (this.props.submitOnBlur) {
+ if (this.props.validateValue?.(this.state.value) !== false && this.props.submitOnBlur) {
this.props.onSubmitValue?.(this.state.value);
+ } else {
+ this.setState({ value: this.props.value });
}
};
@@ -166,8 +146,37 @@ export class Input extends React.Component<IInputProps, IInputState> {
};
}
-export function AutoSizingTextInput({ onChangeValue, ...otherProps }: IInputProps) {
+const InputFrame = styled.div((props: { focused: boolean }) => ({
+ display: 'flex',
+ flexGrow: 0,
+ backgroundColor: props.focused ? colors.white : 'rgba(255,255,255,0.1)',
+ borderRadius: '4px',
+ padding: '4px 8px',
+}));
+
+const StyledAutoSizingTextInputContainer = styled.div({
+ position: 'relative',
+});
+
+const StyledAutoSizingTextInputFiller = styled.pre({
+ ...inputTextStyles,
+ minWidth: '80px',
+ color: 'transparent',
+});
+
+const StyledAutoSizingTextInputWrapper = styled.div({
+ position: 'absolute',
+ top: '0px',
+ left: '0px',
+ width: '100%',
+ height: '100%',
+});
+
+export function AutoSizingTextInput(props: IInputProps) {
+ const { onChangeValue, onFocus, onBlur, ...otherProps } = props;
+
const [value, setValue] = useState(otherProps.value ?? '');
+ const [focused, setFocused, setBlurred] = useBoolean(false);
const onChangeValueWrapper = useCallback(
(value: string) => {
@@ -177,15 +186,38 @@ export function AutoSizingTextInput({ onChangeValue, ...otherProps }: IInputProp
[onChangeValue],
);
+ const onBlurWrapper = useCallback(
+ (event: React.FocusEvent<HTMLInputElement>) => {
+ setBlurred();
+ onBlur?.(event);
+ },
+ [onBlur],
+ );
+
+ const onFocusWrapper = useCallback(
+ (event: React.FocusEvent<HTMLInputElement>) => {
+ setFocused();
+ onFocus?.(event);
+ },
+ [onFocus],
+ );
+
return (
- <StyledAutoSizingTextInputContainer>
- <StyledAutoSizingTextInputWrapper>
- <Input onChangeValue={onChangeValueWrapper} {...otherProps} />
- </StyledAutoSizingTextInputWrapper>
- <StyledAutoSizingTextInputFiller className={otherProps.className} aria-hidden={true}>
- {value === '' ? otherProps.placeholder : value}
- </StyledAutoSizingTextInputFiller>
- </StyledAutoSizingTextInputContainer>
+ <InputFrame focused={focused}>
+ <StyledAutoSizingTextInputContainer>
+ <StyledAutoSizingTextInputWrapper>
+ <Input
+ onChangeValue={onChangeValueWrapper}
+ onBlur={onBlurWrapper}
+ onFocus={onFocusWrapper}
+ {...otherProps}
+ />
+ </StyledAutoSizingTextInputWrapper>
+ <StyledAutoSizingTextInputFiller className={otherProps.className} aria-hidden={true}>
+ {value === '' ? otherProps.placeholder : value}
+ </StyledAutoSizingTextInputFiller>
+ </StyledAutoSizingTextInputContainer>
+ </InputFrame>
);
}