summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOliver <oliver@mohlin.dev>2025-09-02 11:54:22 +0200
committerTobias Järvelöv <tobias.jarvelov@mullvad.net>2025-09-22 12:35:43 +0200
commit469028cf96a750b22f7e252fbd7b8c9aa501d77e (patch)
treeabe3b1611f6b0bbd5c76bcebd2bc0d1e1cb18ae8
parentd692e28a4dd4025ba2867c19ad482889f2decb3e (diff)
downloadmullvadvpn-469028cf96a750b22f7e252fbd7b8c9aa501d77e.tar.xz
mullvadvpn-469028cf96a750b22f7e252fbd7b8c9aa501d77e.zip
Link feature indicators to settings in open vpn settings
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/main/components/connection-panel/components/feature-indicators/hooks/use-get-feature-indicator/useGetFeatureIndicator.ts26
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/bridge-mode-setting/BridgeModeSetting.tsx122
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/mss-fix-setting/MssFixSetting.tsx125
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/open-vpn-port-setting/OpenVpnPortSetting.tsx49
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/transport-protocol-setting/TransportProtocolSetting.tsx90
5 files changed, 234 insertions, 178 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/main/components/connection-panel/components/feature-indicators/hooks/use-get-feature-indicator/useGetFeatureIndicator.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/main/components/connection-panel/components/feature-indicators/hooks/use-get-feature-indicator/useGetFeatureIndicator.ts
index 0bc0d9d336..5e09a00d54 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/main/components/connection-panel/components/feature-indicators/hooks/use-get-feature-indicator/useGetFeatureIndicator.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/main/components/connection-panel/components/feature-indicators/hooks/use-get-feature-indicator/useGetFeatureIndicator.ts
@@ -124,6 +124,30 @@ export const useGetFeatureIndicator = () => {
});
}, [history]);
+ const gotoBridgeMode = React.useCallback(() => {
+ history.push(RoutePath.openVpnSettings, {
+ transition: TransitionType.show,
+ options: [
+ {
+ type: 'scroll-to-anchor',
+ id: 'bridge-mode-setting',
+ },
+ ],
+ });
+ }, [history]);
+
+ const goToMssFix = React.useCallback(() => {
+ history.push(RoutePath.openVpnSettings, {
+ transition: TransitionType.show,
+ options: [
+ {
+ type: 'scroll-to-anchor',
+ id: 'mss-fix-setting',
+ },
+ ],
+ });
+ }, [history]);
+
const featureMap: Record<FeatureIndicator, { label: string; onClick?: () => void }> = {
[FeatureIndicator.daita]: { label: strings.daita, onClick: gotoDaitaFeature },
[FeatureIndicator.daitaMultihop]: {
@@ -171,6 +195,7 @@ export const useGetFeatureIndicator = () => {
},
[FeatureIndicator.bridgeMode]: {
label: messages.pgettext('openvpn-settings-view', 'Bridge mode'),
+ onClick: gotoBridgeMode,
},
[FeatureIndicator.lanSharing]: {
label: messages.pgettext('vpn-settings-view', 'Local network sharing'),
@@ -178,6 +203,7 @@ export const useGetFeatureIndicator = () => {
},
[FeatureIndicator.customMssFix]: {
label: messages.pgettext('openvpn-settings-view', 'Mssfix'),
+ onClick: goToMssFix,
},
[FeatureIndicator.lockdownMode]: {
label: messages.pgettext('vpn-settings-view', 'Lockdown mode'),
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/bridge-mode-setting/BridgeModeSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/bridge-mode-setting/BridgeModeSetting.tsx
index 3ad7ae02c3..eaa246d2cb 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/bridge-mode-setting/BridgeModeSetting.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/bridge-mode-setting/BridgeModeSetting.tsx
@@ -1,6 +1,5 @@
-import { useCallback, useMemo } from 'react';
+import React, { useCallback, useMemo } from 'react';
import { sprintf } from 'sprintf-js';
-import styled from 'styled-components';
import { strings } from '../../../../../../shared/constants';
import {
@@ -11,23 +10,24 @@ import {
import { messages } from '../../../../../../shared/gettext';
import log from '../../../../../../shared/logging';
import { useAppContext } from '../../../../../context';
+import { useScrollToListItem } from '../../../../../hooks';
+import { Listbox } from '../../../../../lib/components/listbox/Listbox';
import { formatHtml } from '../../../../../lib/html-formatter';
import { useSelector } from '../../../../../redux/store';
-import { AriaDescription, AriaInputGroup } from '../../../../AriaGroup';
-import * as Cell from '../../../../cell';
-import Selector, { SelectorItem } from '../../../../cell/Selector';
+import { DefaultListboxOption } from '../../../../default-listbox-option';
+import InfoButton from '../../../../InfoButton';
import { ModalMessage } from '../../../../Modal';
-const StyledSelectorContainer = styled.div({
- flex: 0,
-});
-
export function BridgeModeSetting() {
const { setBridgeState: setBridgeStateImpl } = useAppContext();
const relaySettings = useSelector((state) => state.settings.relaySettings);
const bridgeState = useSelector((state) => state.settings.bridgeState);
+ const id = 'bridge-mode-setting';
+ const ref = React.useRef<HTMLDivElement>(null);
+ const scrollToAnchor = useScrollToListItem(ref, id);
+
const tunnelProtocol = useMemo(() => {
const protocol = 'normal' in relaySettings ? relaySettings.normal.tunnelProtocol : 'any';
return protocol === 'any' ? null : protocol;
@@ -38,22 +38,6 @@ export function BridgeModeSetting() {
return protocol === 'any' ? null : protocol;
}, [relaySettings]);
- const options: SelectorItem<BridgeState>[] = useMemo(
- () => [
- {
- label: messages.gettext('On'),
- value: 'on',
- disabled: tunnelProtocol !== 'openvpn' || transportProtocol === 'udp',
- 'data-testid': 'bridge-mode-on',
- },
- {
- label: messages.gettext('Off'),
- value: 'off',
- },
- ],
- [tunnelProtocol, transportProtocol],
- );
-
const setBridgeState = useCallback(
async (bridgeState: BridgeState) => {
try {
@@ -76,50 +60,56 @@ export function BridgeModeSetting() {
const footerText = bridgeModeFooterText(bridgeState === 'on', tunnelProtocol, transportProtocol);
return (
- <>
- <AriaInputGroup>
- <StyledSelectorContainer>
- <Selector
- title={
+ <Listbox
+ value={bridgeState}
+ onValueChange={onSelectBridgeState}
+ animation={scrollToAnchor?.animation}>
+ <Listbox.Item ref={ref}>
+ <Listbox.Content>
+ <Listbox.Label>
+ {
// TRANSLATORS: The title for the shadowsocks bridge selector section.
messages.pgettext('openvpn-settings-view', 'Bridge mode')
}
- infoTitle={messages.pgettext('openvpn-settings-view', 'Bridge mode')}
- details={
- <>
- <ModalMessage>
- {sprintf(
- // TRANSLATORS: This is used as a description for the bridge mode
- // TRANSLATORS: setting.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(openvpn)s - will be replaced with OpenVPN
- messages.pgettext(
- 'openvpn-settings-view',
- 'Helps circumvent censorship, by routing your traffic through a bridge server before reaching an %(openvpn)s server. Obfuscation is added to make fingerprinting harder.',
- ),
- { openvpn: strings.openvpn },
- )}
- </ModalMessage>
- <ModalMessage>
- {messages.gettext('This setting increases latency. Use only if needed.')}
- </ModalMessage>
- </>
- }
- items={options}
- value={bridgeState}
- onSelect={onSelectBridgeState}
- automaticValue={'auto' as const}
- />
- </StyledSelectorContainer>
- {footerText !== undefined && (
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>{footerText}</Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- )}
- </AriaInputGroup>
- </>
+ </Listbox.Label>
+ <InfoButton>
+ <>
+ <ModalMessage>
+ {sprintf(
+ // TRANSLATORS: This is used as a description for the bridge mode
+ // TRANSLATORS: setting.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(openvpn)s - will be replaced with OpenVPN
+ messages.pgettext(
+ 'openvpn-settings-view',
+ 'Helps circumvent censorship, by routing your traffic through a bridge server before reaching an %(openvpn)s server. Obfuscation is added to make fingerprinting harder.',
+ ),
+ { openvpn: strings.openvpn },
+ )}
+ </ModalMessage>
+ <ModalMessage>
+ {messages.gettext('This setting increases latency. Use only if needed.')}
+ </ModalMessage>
+ </>
+ </InfoButton>
+ </Listbox.Content>
+ </Listbox.Item>
+ <Listbox.Options>
+ <DefaultListboxOption value={'auto'}>{messages.gettext('Automatic')}</DefaultListboxOption>
+ <DefaultListboxOption
+ value={'on'}
+ disabled={tunnelProtocol !== 'openvpn' || transportProtocol === 'udp'}
+ data-testid="bridge-mode-on">
+ {messages.gettext('On')}
+ </DefaultListboxOption>
+ <DefaultListboxOption value={'off'}>{messages.gettext('Off')}</DefaultListboxOption>
+ </Listbox.Options>
+ {footerText !== undefined && (
+ <Listbox.Footer>
+ <Listbox.Text>{footerText}</Listbox.Text>
+ </Listbox.Footer>
+ )}
+ </Listbox>
);
}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/mss-fix-setting/MssFixSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/mss-fix-setting/MssFixSetting.tsx
index d9f727addf..0107faca44 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/mss-fix-setting/MssFixSetting.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/mss-fix-setting/MssFixSetting.tsx
@@ -1,4 +1,4 @@
-import { useCallback } from 'react';
+import React, { useCallback } from 'react';
import { sprintf } from 'sprintf-js';
import { strings } from '../../../../../../shared/constants';
@@ -6,9 +6,10 @@ import { messages } from '../../../../../../shared/gettext';
import log from '../../../../../../shared/logging';
import { removeNonNumericCharacters } from '../../../../../../shared/string-helpers';
import { useAppContext } from '../../../../../context';
+import { useScrollToListItem } from '../../../../../hooks';
+import { ListItem } from '../../../../../lib/components/list-item';
+import { useTextField } from '../../../../../lib/components/text-field';
import { useSelector } from '../../../../../redux/store';
-import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from '../../../../AriaGroup';
-import * as Cell from '../../../../cell';
const MIN_MSSFIX_VALUE = 1000;
const MAX_MSSFIX_VALUE = 1450;
@@ -17,6 +18,14 @@ export function MssFixSetting() {
const { setOpenVpnMssfix: setOpenVpnMssfixImpl } = useAppContext();
const mssfix = useSelector((state) => state.settings.openVpn.mssfix);
+ const id = 'mss-fix-setting';
+ const ref = React.useRef<HTMLDivElement>(null);
+ const scrollToAnchor = useScrollToListItem(ref, id);
+
+ const inputRef = React.useRef<HTMLInputElement>(null);
+ const labelId = React.useId();
+ const descriptionId = React.useId();
+
const setOpenVpnMssfix = useCallback(
async (mssfix?: number) => {
try {
@@ -39,48 +48,76 @@ export function MssFixSetting() {
[setOpenVpnMssfix],
);
+ const { value, handleChange, invalid, dirty, blur, reset } = useTextField({
+ inputRef,
+ defaultValue: mssfix ? mssfix.toString() : '',
+ format: removeNonNumericCharacters,
+ validate: mssfixIsValid,
+ });
+
+ const handleBlur = React.useCallback(async () => {
+ if (!invalid && dirty) {
+ await onMssfixSubmit(value);
+ }
+ if (invalid) {
+ reset();
+ }
+ }, [dirty, invalid, onMssfixSubmit, reset, value]);
+
+ const handleSubmit = React.useCallback(
+ async (event: React.FormEvent) => {
+ event.preventDefault();
+ if (!invalid) {
+ await onMssfixSubmit(value);
+ blur();
+ }
+ },
+ [blur, invalid, onMssfixSubmit, value],
+ );
+
return (
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>{messages.pgettext('openvpn-settings-view', 'Mssfix')}</Cell.InputLabel>
- </AriaLabel>
- <AriaInput>
- <Cell.AutoSizingTextInput
- initialValue={mssfix ? mssfix.toString() : ''}
- inputMode={'numeric'}
- maxLength={4}
- placeholder={messages.gettext('Default')}
- onSubmitValue={onMssfixSubmit}
- validateValue={mssfixIsValid}
- submitOnBlur={true}
- modifyValue={removeNonNumericCharacters}
- />
- </AriaInput>
- </Cell.Container>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {sprintf(
- // TRANSLATORS: The hint displayed below the Mssfix input field.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(openvpn)s - will be replaced with "OpenVPN"
- // TRANSLATORS: %(max)d - the maximum possible mssfix value
- // TRANSLATORS: %(min)d - the minimum possible mssfix value
- messages.pgettext(
- 'openvpn-settings-view',
- 'Set %(openvpn)s MSS value. Valid range: %(min)d - %(max)d.',
- ),
- {
- openvpn: strings.openvpn,
- min: MIN_MSSFIX_VALUE,
- max: MAX_MSSFIX_VALUE,
- },
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
+ <ListItem animation={scrollToAnchor?.animation}>
+ <ListItem.Item ref={ref}>
+ <ListItem.Content>
+ <ListItem.Label id={labelId}>
+ {messages.pgettext('openvpn-settings-view', 'Mssfix')}
+ </ListItem.Label>
+ <ListItem.TextField invalid={invalid} onSubmit={handleSubmit}>
+ <ListItem.TextField.Input
+ ref={inputRef}
+ value={value}
+ placeholder={messages.gettext('Default')}
+ inputMode="numeric"
+ maxLength={4}
+ aria-labelledby={labelId}
+ aria-describedby={descriptionId}
+ onBlur={handleBlur}
+ onChange={handleChange}
+ />
+ </ListItem.TextField>
+ </ListItem.Content>
+ </ListItem.Item>
+ <ListItem.Footer>
+ <ListItem.Text id={descriptionId}>
+ {sprintf(
+ // TRANSLATORS: The hint displayed below the Mssfix input field.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(openvpn)s - will be replaced with "OpenVPN"
+ // TRANSLATORS: %(max)d - the maximum possible mssfix value
+ // TRANSLATORS: %(min)d - the minimum possible mssfix value
+ messages.pgettext(
+ 'openvpn-settings-view',
+ 'Set %(openvpn)s MSS value. Valid range: %(min)d - %(max)d.',
+ ),
+ {
+ openvpn: strings.openvpn,
+ min: MIN_MSSFIX_VALUE,
+ max: MAX_MSSFIX_VALUE,
+ },
+ )}
+ </ListItem.Text>
+ </ListItem.Footer>
+ </ListItem>
);
}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/open-vpn-port-setting/OpenVpnPortSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/open-vpn-port-setting/OpenVpnPortSetting.tsx
index 1c7df82270..40fd659b45 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/open-vpn-port-setting/OpenVpnPortSetting.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/open-vpn-port-setting/OpenVpnPortSetting.tsx
@@ -4,10 +4,11 @@ import styled from 'styled-components';
import { wrapConstraint } from '../../../../../../shared/daemon-rpc-types';
import { messages } from '../../../../../../shared/gettext';
+import { Listbox } from '../../../../../lib/components/listbox/Listbox';
import { useRelaySettingsUpdater } from '../../../../../lib/constraint-updater';
import { useSelector } from '../../../../../redux/store';
-import { AriaInputGroup } from '../../../../AriaGroup';
-import Selector, { SelectorItem } from '../../../../cell/Selector';
+import { SelectorItem } from '../../../../cell/Selector';
+import { DefaultListboxOption } from '../../../../default-listbox-option';
const UDP_PORTS = [1194, 1195, 1196, 1197, 1300, 1301, 1302];
const TCP_PORTS = [80, 443];
@@ -54,24 +55,30 @@ export function OpenVpnPortSetting() {
}
return (
- <StyledSelectorContainer>
- <AriaInputGroup>
- <Selector
- title={sprintf(
- // TRANSLATORS: The title for the port selector section.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(portType)s - a selected protocol (either TCP or UDP)
- messages.pgettext('openvpn-settings-view', '%(portType)s port'),
- {
- portType: protocol.toUpperCase(),
- },
- )}
- items={portItems[protocol]}
- value={port}
- onSelect={onSelect}
- automaticValue={null}
- />
- </AriaInputGroup>
- </StyledSelectorContainer>
+ <Listbox value={port} onValueChange={onSelect}>
+ <Listbox.Item>
+ <Listbox.Content>
+ <Listbox.Label>
+ {sprintf(
+ // TRANSLATORS: The title for the port selector section.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(portType)s - a selected protocol (either TCP or UDP)
+ messages.pgettext('openvpn-settings-view', '%(portType)s port'),
+ {
+ portType: protocol.toUpperCase(),
+ },
+ )}
+ </Listbox.Label>
+ </Listbox.Content>
+ </Listbox.Item>
+ <Listbox.Options>
+ <DefaultListboxOption value={null}>{messages.gettext('Automatic')}</DefaultListboxOption>
+ {portItems[protocol].map((item) => (
+ <DefaultListboxOption key={item.value} value={item.value}>
+ {item.label}
+ </DefaultListboxOption>
+ ))}
+ </Listbox.Options>
+ </Listbox>
);
}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/transport-protocol-setting/TransportProtocolSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/transport-protocol-setting/TransportProtocolSetting.tsx
index 5b89da0833..89abfa35a8 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/transport-protocol-setting/TransportProtocolSetting.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/open-vpn-settings/components/transport-protocol-setting/TransportProtocolSetting.tsx
@@ -1,20 +1,25 @@
-import { useCallback, useMemo } from 'react';
+import React, { useCallback, useMemo } from 'react';
import { RelayProtocol, wrapConstraint } from '../../../../../../shared/daemon-rpc-types';
import { messages } from '../../../../../../shared/gettext';
+import { useScrollToListItem } from '../../../../../hooks';
+import { Listbox } from '../../../../../lib/components/listbox/Listbox';
import { useRelaySettingsUpdater } from '../../../../../lib/constraint-updater';
import { formatHtml } from '../../../../../lib/html-formatter';
import { useSelector } from '../../../../../redux/store';
-import { AriaDescription, AriaInputGroup } from '../../../../AriaGroup';
-import * as Cell from '../../../../cell';
-import Selector, { SelectorItem } from '../../../../cell/Selector';
-import { StyledSelectorContainer } from '../../OpenVpnSettingsView';
+import { DefaultListboxOption } from '../../../../default-listbox-option';
export function TransportProtocolSetting() {
const relaySettingsUpdater = useRelaySettingsUpdater();
const relaySettings = useSelector((state) => state.settings.relaySettings);
const bridgeState = useSelector((state) => state.settings.bridgeState);
+ const id = 'transport-protocol-setting';
+ const ref = React.useRef<HTMLDivElement>(null);
+ const scrollToListItem = useScrollToListItem(ref, id);
+
+ const descriptionId = React.useId();
+
const protocol = useMemo(() => {
const protocol = 'normal' in relaySettings ? relaySettings.normal.openvpn.protocol : 'any';
return protocol === 'any' ? null : protocol;
@@ -31,48 +36,39 @@ export function TransportProtocolSetting() {
[relaySettingsUpdater],
);
- const items: SelectorItem<RelayProtocol>[] = useMemo(
- () => [
- {
- label: messages.gettext('TCP'),
- value: 'tcp',
- },
- {
- label: messages.gettext('UDP'),
- value: 'udp',
- disabled: bridgeState === 'on',
- },
- ],
- [bridgeState],
- );
-
return (
- <StyledSelectorContainer>
- <AriaInputGroup>
- <Selector
- title={messages.pgettext('openvpn-settings-view', 'Transport protocol')}
- items={items}
- value={protocol}
- onSelect={onSelect}
- automaticValue={null}
- />
- {bridgeState === 'on' && (
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {formatHtml(
- // TRANSLATORS: This is used to instruct users how to make UDP mode
- // TRANSLATORS: available.
- messages.pgettext(
- 'openvpn-settings-view',
- 'To activate UDP, change <b>Bridge mode</b> to <b>Automatic</b> or <b>Off</b>.',
- ),
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- )}
- </AriaInputGroup>
- </StyledSelectorContainer>
+ <Listbox animation={scrollToListItem?.animation} value={protocol} onValueChange={onSelect}>
+ <Listbox.Item ref={ref}>
+ <Listbox.Content>
+ <Listbox.Label>
+ {messages.pgettext('openvpn-settings-view', 'Transport protocol')}
+ </Listbox.Label>
+ </Listbox.Content>
+ </Listbox.Item>
+ <Listbox.Options>
+ <DefaultListboxOption value={null}>{messages.gettext('Automatic')}</DefaultListboxOption>
+ <DefaultListboxOption value={'tcp'}>{messages.gettext('TCP')}</DefaultListboxOption>
+ <DefaultListboxOption
+ value={'udp'}
+ disabled={bridgeState === 'on'}
+ aria-describedby={bridgeState === 'on' ? descriptionId : undefined}>
+ {messages.gettext('UDP')}
+ </DefaultListboxOption>
+ </Listbox.Options>
+ {bridgeState === 'on' && (
+ <Listbox.Footer>
+ <Listbox.Text id={descriptionId}>
+ {formatHtml(
+ // TRANSLATORS: This is used to instruct users how to make UDP mode
+ // TRANSLATORS: available.
+ messages.pgettext(
+ 'openvpn-settings-view',
+ 'To activate UDP, change <b>Bridge mode</b> to <b>Automatic</b> or <b>Off</b>.',
+ ),
+ )}
+ </Listbox.Text>
+ </Listbox.Footer>
+ )}
+ </Listbox>
);
}