summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2023-12-01 10:19:03 +0100
committerOskar Nyberg <oskar@mullvad.net>2024-01-29 09:44:53 +0100
commit5ae5ba71914e35c613aeb127ebc45bc377a43b3a (patch)
treec5db0f68f11884212d494cc9e97261a320981a8d /gui/src
parent798c12c0e7432dc7d0b2f2ed59932ed7d868a916 (diff)
downloadmullvadvpn-5ae5ba71914e35c613aeb127ebc45bc377a43b3a.tar.xz
mullvadvpn-5ae5ba71914e35c613aeb127ebc45bc377a43b3a.zip
Add text input component for new settings row
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/cell/SettingsTextInput.tsx124
1 files changed, 124 insertions, 0 deletions
diff --git a/gui/src/renderer/components/cell/SettingsTextInput.tsx b/gui/src/renderer/components/cell/SettingsTextInput.tsx
new file mode 100644
index 0000000000..6d4c22dcbb
--- /dev/null
+++ b/gui/src/renderer/components/cell/SettingsTextInput.tsx
@@ -0,0 +1,124 @@
+import { useCallback, useEffect } from 'react';
+import styled from 'styled-components';
+
+import { colors } from '../../../config.json';
+import { AriaInput } from '../AriaGroup';
+import { smallNormalText } from '../common-styles';
+import { useSettingsFormSubmittableReporter } from './SettingsForm';
+import { useSettingsRowContext } from './SettingsRow';
+
+const StyledInput = styled.input(smallNormalText, {
+ flex: 1,
+ textAlign: 'right',
+ background: 'transparent',
+ border: 'none',
+ color: colors.white,
+ width: '100px',
+
+ '&&::placeholder': {
+ color: colors.white50,
+ },
+});
+
+interface SettingsTextInputProps extends InputProps<'text'> {
+ defaultValue?: string;
+}
+
+export function SettingsTextInput(props: SettingsTextInputProps) {
+ return <Input type="text" {...props} />;
+}
+
+interface SettingsNumberInputProps
+ extends Omit<InputProps<'number'>, 'onUpdate' | 'validate' | 'value'> {
+ defaultValue?: number;
+ value?: number | '';
+ onUpdate: (value: number | undefined) => void;
+ validate?: (value: number) => boolean;
+}
+
+// NumberInput is basically a text input but it parses all values as numbers.
+export function SettingsNumberInput(props: SettingsNumberInputProps) {
+ const { onUpdate, validate, value, ...otherProps } = props;
+
+ const parse = useCallback((value: string) => {
+ const parsedValue = parseInt(value);
+ return isNaN(parsedValue) ? undefined : parsedValue;
+ }, []);
+
+ const onNumberUpdate = useCallback(
+ (value: string) => {
+ onUpdate(parse(value));
+ },
+ [onUpdate],
+ );
+
+ const validateNumber = useCallback(
+ (value: string) => {
+ const parsedValue = parse(value);
+ return (parsedValue === undefined || validate?.(parsedValue)) ?? true;
+ },
+ [validate],
+ );
+
+ return (
+ <Input
+ {...otherProps}
+ value={value ?? ''}
+ onUpdate={onNumberUpdate}
+ validate={validateNumber}
+ />
+ );
+}
+
+type ValueTypes = 'text' | 'number';
+type ValueType<T extends ValueTypes> = T extends 'number' ? number | '' : string;
+
+interface InputProps<T extends ValueTypes> extends React.HTMLAttributes<HTMLInputElement> {
+ type?: T;
+ value?: ValueType<T>;
+ defaultValue?: ValueType<T>;
+ onUpdate: (value: string) => void;
+ validate?: (value: string) => boolean;
+ optionalInForm?: boolean;
+}
+
+function Input<T extends ValueTypes>(props: InputProps<T>) {
+ const { onUpdate, onChange: propsOnChange, validate, optionalInForm, ...otherProps } = props;
+ const reportSubmittable = useSettingsFormSubmittableReporter();
+
+ const { setInvalid } = useSettingsRowContext();
+
+ const onChange = useCallback(
+ (event: React.ChangeEvent<HTMLInputElement>) => {
+ const value = event.target.value;
+
+ // Report change to parent
+ propsOnChange?.(event);
+ onUpdate(value);
+
+ if (validate?.(value) === false && value !== '') {
+ // Report validity and submittability to settings row context and form context.
+ setInvalid(true);
+ reportSubmittable(false);
+ } else {
+ setInvalid(false);
+ reportSubmittable(value !== '' || optionalInForm === true);
+ }
+ },
+ [onUpdate, propsOnChange, validate, optionalInForm],
+ );
+
+ // Report submittability to form context on load.
+ useEffect(() => {
+ const value = props.value ?? props.defaultValue ?? '';
+ reportSubmittable(
+ (value !== '' || optionalInForm === true) && validate?.(`${value}`) !== false,
+ );
+ }, []);
+
+ return (
+ <AriaInput>
+ <StyledInput {...otherProps} onChange={onChange} />
+ </AriaInput>
+ );
+}