diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2023-12-01 10:19:03 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2024-01-29 09:44:53 +0100 |
| commit | 5ae5ba71914e35c613aeb127ebc45bc377a43b3a (patch) | |
| tree | c5db0f68f11884212d494cc9e97261a320981a8d /gui/src/renderer | |
| parent | 798c12c0e7432dc7d0b2f2ed59932ed7d868a916 (diff) | |
| download | mullvadvpn-5ae5ba71914e35c613aeb127ebc45bc377a43b3a.tar.xz mullvadvpn-5ae5ba71914e35c613aeb127ebc45bc377a43b3a.zip | |
Add text input component for new settings row
Diffstat (limited to 'gui/src/renderer')
| -rw-r--r-- | gui/src/renderer/components/cell/SettingsTextInput.tsx | 124 |
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> + ); +} |
