diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2023-12-21 20:25:08 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2024-01-29 09:44:53 +0100 |
| commit | 2f6004e35484e07988463b2a9097114b17718fea (patch) | |
| tree | d85c62b6c243f90b7babd6bc0b75a090689c3439 /gui/src/renderer | |
| parent | 9cc0fe0f18801c5f605fe05864e12c2984415300 (diff) | |
| download | mullvadvpn-2f6004e35484e07988463b2a9097114b17718fea.tar.xz mullvadvpn-2f6004e35484e07988463b2a9097114b17718fea.zip | |
Add radio button group component
Diffstat (limited to 'gui/src/renderer')
| -rw-r--r-- | gui/src/renderer/components/cell/SettingsRadioGroup.tsx | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/gui/src/renderer/components/cell/SettingsRadioGroup.tsx b/gui/src/renderer/components/cell/SettingsRadioGroup.tsx new file mode 100644 index 0000000000..46f3ccded7 --- /dev/null +++ b/gui/src/renderer/components/cell/SettingsRadioGroup.tsx @@ -0,0 +1,119 @@ +import { useCallback, useId, useState } from 'react'; +import { styled } from 'styled-components'; + +import { colors } from '../../../config.json'; +import { AriaInput, AriaInputGroup, AriaLabel } from '../AriaGroup'; +import { smallNormalText } from '../common-styles'; +import { SettingsSelectItem } from './SettingsSelect'; + +const StyledRadioGroup = styled.div({ + display: 'flex', +}); + +interface SettingsSelectProps<T extends string> { + defaultValue?: T; + items: Array<SettingsSelectItem<T>>; + onUpdate: (value: T) => void; +} + +export function SettingsRadioGroup<T extends string>(props: SettingsSelectProps<T>) { + const [value, setValue] = useState<T>(props.defaultValue ?? props.items[0]?.value ?? ''); + const key = useId(); + + const onSelect = useCallback((value: T) => { + setValue(value); + props.onUpdate(value); + }, []); + + return ( + <StyledRadioGroup> + {props.items.map((item) => ( + <RadioButton + key={item.value} + group={key} + item={item} + selected={item.value === value} + onSelect={onSelect} + /> + ))} + </StyledRadioGroup> + ); +} + +const StyledRadioButton = styled.input.attrs({ type: 'radio' })({ + position: 'relative', + margin: 0, + appearance: 'none', + backgroundColor: 'transparent', + width: '12px', + height: '12px', + + '&&::before': { + position: 'absolute', + content: '""', + width: '12px', + height: '12px', + borderRadius: '50%', + backgroundColor: 'transparent', + border: `1px ${colors.white} solid`, + top: 0, + left: 0, + }, + + '&&:checked::after': { + position: 'absolute', + content: '""', + width: '8px', + height: '8px', + borderRadius: '50%', + backgroundColor: colors.white, + top: '3px', + left: '3px', + }, +}); + +const StyledRadioButtonContainer = styled.div({ + display: 'flex', + alignItems: 'center', + flexWrap: 'nowrap', + marginLeft: '16px', +}); + +const StyledRadioButtonLabel = styled.label(smallNormalText, { + color: colors.white, + marginLeft: '8px', +}); + +interface RadioButtonProps<T extends string> { + group: string; + item: SettingsSelectItem<T>; + selected: boolean; + onSelect: (value: T) => void; +} + +function RadioButton<T extends string>(props: RadioButtonProps<T>) { + const onChange = useCallback( + (event: React.ChangeEvent<HTMLInputElement>) => { + props.onSelect(event.target.value as T); + }, + [props.onSelect], + ); + + return ( + <StyledRadioButtonContainer> + <AriaInputGroup> + <AriaInput> + <StyledRadioButton + name={props.group} + value={props.item.value} + onChange={onChange} + checked={props.selected} + /> + </AriaInput> + <AriaLabel> + <StyledRadioButtonLabel>{props.item.label}</StyledRadioButtonLabel> + </AriaLabel> + </AriaInputGroup> + </StyledRadioButtonContainer> + ); +} |
