diff options
| author | Oliver <oliver@mohlin.dev> | 2025-01-09 12:26:47 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2025-01-13 15:02:51 +0100 |
| commit | 1e5964d0ee1defcfb18fa170f64c897d81c413e4 (patch) | |
| tree | 3fca44cec7a713a409fed7bdf6ffafff3da2808a | |
| parent | e311a5e181e4115f35cd800cc52b2100ef853379 (diff) | |
| download | mullvadvpn-1e5964d0ee1defcfb18fa170f64c897d81c413e4.tar.xz mullvadvpn-1e5964d0ee1defcfb18fa170f64c897d81c413e4.zip | |
Use AppNavigationHeader in app
35 files changed, 401 insertions, 723 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Account.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Account.tsx index 1cc666910e..115196a49d 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Account.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Account.tsx @@ -7,6 +7,7 @@ import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; import { useEffectEvent } from '../lib/utility-hooks'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import AccountNumberLabel from './AccountNumberLabel'; import { AccountContainer, @@ -23,7 +24,6 @@ import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGrou import DeviceInfoButton from './DeviceInfoButton'; import { BackAction } from './KeyboardNavigation'; import { Footer, Layout, SettingsContainer } from './Layout'; -import { NavigationBar, NavigationItems, TitleBarItem } from './NavigationBar'; import { RedeemVoucherButton } from './RedeemVoucher'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; @@ -49,16 +49,12 @@ export default function Account() { <BackAction action={history.pop}> <Layout> <SettingsContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('account-view', 'Account') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('account-view', 'Account') + } + /> <AccountContainer> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ApiAccessMethods.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ApiAccessMethods.tsx index bf1a2025df..83142d29d7 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/ApiAccessMethods.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/ApiAccessMethods.tsx @@ -14,6 +14,7 @@ import { generateRoutePath } from '../lib/routeHelpers'; import { RoutePath } from '../lib/routes'; import { useBoolean } from '../lib/utility-hooks'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import * as Cell from './cell'; import { ContextMenu, @@ -26,13 +27,7 @@ import InfoButton from './InfoButton'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer, SettingsContent, SettingsNavigationScrollbars } from './Layout'; import { ModalAlert, ModalAlertType } from './Modal'; -import { - NavigationBar, - NavigationContainer, - NavigationInfoButton, - NavigationItems, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; import { SmallButton, SmallButtonColor } from './SmallButton'; @@ -42,7 +37,7 @@ const StyledContextMenuButton = styled(Cell.Icon)({ marginRight: Spacings.spacing3, }); -const StyledMethodInfoButton = styled(InfoButton)({ +const StyledMethodInfoButton = styled(InfoButton).attrs({ size: 'small' })({ marginRight: Spacings.spacing4, }); @@ -82,32 +77,28 @@ export default function ApiAccessMethods() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('navigation-bar', 'API access') - } - </TitleBarItem> - <NavigationInfoButton - message={[ - messages.pgettext( - 'api-access-methods-view', - 'The app needs to communicate with a Mullvad API server to log you in, fetch server lists, and other critical operations.', - ), - messages.pgettext( - 'api-access-methods-view', - 'On some networks, where various types of censorship are being used, the API servers might not be directly reachable.', - ), - messages.pgettext( - 'api-access-methods-view', - 'This feature allows you to circumvent that censorship by adding custom ways to access the API via proxies and similar methods.', - ), - ]} - /> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('navigation-bar', 'API access') + }> + <AppNavigationHeader.InfoButton + message={[ + messages.pgettext( + 'api-access-methods-view', + 'The app needs to communicate with a Mullvad API server to log you in, fetch server lists, and other critical operations.', + ), + messages.pgettext( + 'api-access-methods-view', + 'On some networks, where various types of censorship are being used, the API servers might not be directly reachable.', + ), + messages.pgettext( + 'api-access-methods-view', + 'This feature allows you to circumvent that censorship by adding custom ways to access the API via proxies and similar methods.', + ), + ]} + /> + </AppNavigationHeader> <SettingsNavigationScrollbars fillContainer> <SettingsContent> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/DaitaSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/DaitaSettings.tsx index 264b389f84..a3ebac27be 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/DaitaSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/DaitaSettings.tsx @@ -10,19 +10,15 @@ import { Spacings } from '../lib/foundations'; import { useHistory } from '../lib/history'; import { useBoolean } from '../lib/utility-hooks'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import InfoButton from './InfoButton'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import PageSlider from './PageSlider'; import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; import { SmallButton, SmallButtonColor } from './SmallButton'; @@ -44,11 +40,7 @@ export default function DaitaSettings() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem>{strings.daita}</TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader title={strings.daita} /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Debug.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Debug.tsx index fee86b00cc..6e3fe6540c 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Debug.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Debug.tsx @@ -4,17 +4,13 @@ import styled from 'styled-components'; import { Spacings } from '../lib/foundations'; import { useHistory } from '../lib/history'; import { useBoolean } from '../lib/utility-hooks'; +import { AppNavigationHeader } from './'; import * as AppButton from './AppButton'; import { measurements } from './common-styles'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const StyledContent = styled.div({ @@ -36,11 +32,7 @@ export default function Debug() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem>Developer tools</TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader title="Developer tools" /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/EditApiAccessMethod.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/EditApiAccessMethod.tsx index 79c7de6861..6dec1e8eb6 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/EditApiAccessMethod.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/EditApiAccessMethod.tsx @@ -14,11 +14,12 @@ import { useApiAccessMethodTest } from '../lib/api-access-methods'; import { useHistory } from '../lib/history'; import { useLastDefinedValue } from '../lib/utility-hooks'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { SettingsForm } from './cell/SettingsForm'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer, SettingsContent, SettingsNavigationScrollbars } from './Layout'; import { ModalAlert, ModalAlertType } from './Modal'; -import { NavigationBar, NavigationContainer, NavigationItems, TitleBarItem } from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; import { NamedProxyForm } from './ProxyForm'; import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; import { SmallButton } from './SmallButton'; @@ -95,11 +96,7 @@ function AccessMethodForm() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem>{title}</TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader title={title} /> <SettingsNavigationScrollbars fillContainer> <SettingsContent> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/EditCustomBridge.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/EditCustomBridge.tsx index 2ab8f81083..6d45eda229 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/EditCustomBridge.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/EditCustomBridge.tsx @@ -6,11 +6,12 @@ import { useBridgeSettingsUpdater } from '../lib/constraint-updater'; import { useHistory } from '../lib/history'; import { useBoolean } from '../lib/utility-hooks'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { SettingsForm } from './cell/SettingsForm'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer, SettingsContent, SettingsNavigationScrollbars } from './Layout'; import { ModalAlert, ModalAlertType } from './Modal'; -import { NavigationBar, NavigationContainer, NavigationItems, TitleBarItem } from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; import { ProxyForm } from './ProxyForm'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; import { SmallButton, SmallButtonColor } from './SmallButton'; @@ -66,11 +67,7 @@ function CustomBridgeForm() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem>{title}</TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader title={title} /> <SettingsNavigationScrollbars fillContainer> <SettingsContent> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Filter.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Filter.tsx index 940563fd87..790215cab4 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Filter.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Filter.tsx @@ -15,6 +15,7 @@ import { useNormalRelaySettings, useTunnelProtocol } from '../lib/relay-settings import { useBoolean } from '../lib/utility-hooks'; import { IRelayLocationCountryRedux } from '../redux/settings/reducers'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import Accordion from './Accordion'; import * as AppButton from './AppButton'; import { AriaInputGroup, AriaLabel } from './AriaGroup'; @@ -24,13 +25,8 @@ import { normalText } from './common-styles'; import ImageView from './ImageView'; import { BackAction } from './KeyboardNavigation'; import { Footer, Layout, SettingsContainer } from './Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; const StyledNavigationScrollbars = styled(NavigationScrollbars)({ backgroundColor: colors.darkBlue, @@ -83,16 +79,13 @@ export default function Filter() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar alwaysDisplayBarTitle={true}> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('filter-nav', 'Filter') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('filter-nav', 'Filter') + } + titleVisible + /> <StyledNavigationScrollbars> <FilterByOwnership ownership={ownership} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/InfoButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/InfoButton.tsx index 7f77f115e7..8243e5b3be 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/InfoButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/InfoButton.tsx @@ -1,20 +1,10 @@ -import styled from 'styled-components'; - import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; +import { Button, IconButton, IconButtonProps } from '../lib/components'; import { useBoolean } from '../lib/utility-hooks'; -import * as AppButton from './AppButton'; import ImageView from './ImageView'; import { ModalAlert, ModalAlertType } from './Modal'; -const StyledInfoButton = styled.button({ - margin: '0 16px 0 8px', - borderWidth: 0, - padding: 0, - cursor: 'default', - backgroundColor: 'transparent', -}); - interface IInfoIconProps { className?: string; size?: number; @@ -34,39 +24,35 @@ export function InfoIcon(props: IInfoIconProps) { ); } -export interface IInfoButtonProps extends React.HTMLAttributes<HTMLButtonElement> { +export interface InfoButtonProps extends Omit<IconButtonProps, 'icon'> { + title?: string; message?: string | Array<string>; children?: React.ReactNode; - title?: string; - size?: number; - tintColor?: string; - tintHoverColor?: string; } -export default function InfoButton(props: IInfoButtonProps) { - const { message, children, size, tintColor, tintHoverColor, ...otherProps } = props; +export default function InfoButton({ title, message, children, ...props }: InfoButtonProps) { const [isOpen, show, hide] = useBoolean(false); return ( <> - <StyledInfoButton + <IconButton + icon="icon-info" onClick={show} aria-label={messages.pgettext('accessibility', 'More information')} - {...otherProps}> - <InfoIcon size={size} tintColor={tintColor} tintHoverColor={tintHoverColor} /> - </StyledInfoButton> + {...props} + /> <ModalAlert isOpen={isOpen} - title={props.title} - message={props.message} + title={title} + message={message} type={ModalAlertType.info} buttons={[ - <AppButton.BlueButton key="back" onClick={hide}> + <Button key="back" onClick={hide}> {messages.gettext('Got it!')} - </AppButton.BlueButton>, + </Button>, ]} close={hide}> - {props.children} + {children} </ModalAlert> </> ); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx index 9ea55431f0..ff7c8c7943 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components'; import { Flex } from '../lib/components'; import { Colors, Spacings } from '../lib/foundations'; import { measurements } from './common-styles'; -import { NavigationScrollbars } from './NavigationBar'; +import { NavigationScrollbars } from './NavigationScrollbars'; export const Container = styled.div({ display: 'flex', diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/MultihopSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/MultihopSettings.tsx index e7e16b395a..d885dd8533 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/MultihopSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/MultihopSettings.tsx @@ -8,18 +8,14 @@ import { Flex } from '../lib/components'; import { useRelaySettingsUpdater } from '../lib/constraint-updater'; import { useHistory } from '../lib/history'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import { StyledIllustration } from './DaitaSettings'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; export default function MultihopSettings() { @@ -30,13 +26,7 @@ export default function MultihopSettings() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - {messages.pgettext('wireguard-settings-view', 'Multihop')} - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader title={messages.pgettext('wireguard-settings-view', 'Multihop')} /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NavigationBar.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NavigationBar.tsx deleted file mode 100644 index 680393a6c7..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NavigationBar.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react'; -import styled from 'styled-components'; - -import { colors } from '../../config.json'; -import { messages } from '../../shared/gettext'; -import { useAppContext } from '../context'; -import { transitions, useHistory } from '../lib/history'; -import { useCombinedRefs, useEffectEvent } from '../lib/utility-hooks'; -import CustomScrollbars, { CustomScrollbarsRef, IScrollEvent } from './CustomScrollbars'; -import InfoButton from './InfoButton'; -import { BackActionContext } from './KeyboardNavigation'; -import { - StyledBackBarItemButton, - StyledBackBarItemIcon, - StyledNavigationBar, - StyledNavigationBarSeparator, - StyledNavigationItems, - StyledTitleBarItemLabel, -} from './NavigationBarStyles'; - -interface INavigationContainerProps { - children?: React.ReactNode; -} - -interface INavigationContainerState { - showsBarTitle: boolean; - showsBarSeparator: boolean; -} - -const NavigationScrollContext = React.createContext({ - showsBarTitle: false, - showsBarSeparator: false, - onScroll(_event: IScrollEvent): void { - throw Error('NavigationScrollContext provider missing'); - }, -}); - -export class NavigationContainer extends React.Component< - INavigationContainerProps, - INavigationContainerState -> { - public state = { - showsBarTitle: false, - showsBarSeparator: false, - }; - - public componentDidMount() { - this.updateBarAppearance({ scrollLeft: 0, scrollTop: 0 }); - } - - public render() { - return ( - <NavigationScrollContext.Provider - value={{ - ...this.state, - onScroll: this.onScroll, - }}> - {this.props.children} - </NavigationScrollContext.Provider> - ); - } - - public onScroll = (event: IScrollEvent) => { - this.updateBarAppearance(event); - }; - - private updateBarAppearance(event: IScrollEvent) { - // that's where SettingsHeader.HeaderTitle intersects the navigation bar - const showsBarSeparator = event.scrollTop > 11; - - // that's when SettingsHeader.HeaderTitle goes behind the navigation bar - const showsBarTitle = event.scrollTop > 20; - - if ( - this.state.showsBarSeparator !== showsBarSeparator || - this.state.showsBarTitle !== showsBarTitle - ) { - this.setState({ showsBarSeparator, showsBarTitle }); - } - } -} - -interface INavigationScrollbarsProps { - className?: string; - fillContainer?: boolean; - children?: React.ReactNode; -} - -export const NavigationScrollbars = React.forwardRef(function NavigationScrollbarsT( - props: INavigationScrollbarsProps, - forwardedRef?: React.Ref<CustomScrollbarsRef>, -) { - const history = useHistory(); - const { setNavigationHistory } = useAppContext(); - const { onScroll } = useContext(NavigationScrollContext); - - const ref = useRef<CustomScrollbarsRef>(); - const combinedRefs = useCombinedRefs(forwardedRef, ref); - - const beforeunload = useEffectEvent(() => { - if (ref.current) { - history.recordScrollPosition(ref.current.getScrollPosition()); - setNavigationHistory(history.asObject); - } - }); - - useEffect(() => { - window.addEventListener('beforeunload', beforeunload); - return () => window.removeEventListener('beforeunload', beforeunload); - }, []); - - const onMount = useEffectEvent(() => { - const location = history.location; - if (history.action === 'POP') { - ref.current?.scrollTo(...location.state.scrollPosition); - } - }); - - const onUnmount = useEffectEvent(() => { - if (history.action === 'PUSH' && ref.current) { - history.recordScrollPosition(ref.current.getScrollPosition()); - setNavigationHistory(history.asObject); - } - }); - - useLayoutEffect(() => { - onMount(); - return () => onUnmount(); - }, []); - - const handleScroll = useCallback( - (event: IScrollEvent) => { - onScroll(event); - }, - [onScroll], - ); - - return ( - <CustomScrollbars - ref={combinedRefs} - className={props.className} - fillContainer={props.fillContainer} - onScroll={handleScroll}> - {props.children} - </CustomScrollbars> - ); -}); - -const TitleBarItemContext = React.createContext({ - visible: false, -}); - -interface INavigationBarProps { - children?: React.ReactNode; - alwaysDisplayBarTitle?: boolean; -} - -export const NavigationBar = function NavigationBarT(props: INavigationBarProps) { - const { showsBarSeparator, showsBarTitle } = useContext(NavigationScrollContext); - - return ( - <StyledNavigationBar> - <TitleBarItemContext.Provider - value={{ visible: props.alwaysDisplayBarTitle || showsBarTitle }}> - {props.children} - </TitleBarItemContext.Provider> - {showsBarSeparator && <StyledNavigationBarSeparator />} - </StyledNavigationBar> - ); -}; - -interface INavigationItemsProps { - children: React.ReactNode; -} - -export function NavigationItems(props: INavigationItemsProps) { - const { parentBackAction } = useContext(BackActionContext); - return ( - <StyledNavigationItems> - {parentBackAction && <BackBarItem />} - {props.children} - </StyledNavigationItems> - ); -} - -interface ITitleBarItemProps { - children?: React.ReactText; -} - -export const TitleBarItem = React.memo(function TitleBarItemT(props: ITitleBarItemProps) { - const { visible } = useContext(TitleBarItemContext); - return <StyledTitleBarItemLabel $visible={visible}>{props.children}</StyledTitleBarItemLabel>; -}); - -export function BackBarItem() { - const history = useHistory(); - // Compare the transition name with dismiss to infer wheter or not the view will slide - // horizontally or vertically and then use matching button. - const backIcon = useMemo( - () => history.getPopTransition().name !== transitions.dismiss.name, - [history], - ); - const { parentBackAction } = useContext(BackActionContext); - const iconSource = backIcon ? 'icon-back' : 'icon-close-down'; - const ariaLabel = backIcon ? messages.gettext('Back') : messages.gettext('Close'); - - return ( - <StyledBackBarItemButton aria-label={ariaLabel} onClick={parentBackAction}> - <StyledBackBarItemIcon source={iconSource} tintColor={colors.white40} width={24} /> - </StyledBackBarItemButton> - ); -} - -const navigationRightHandSideButton: React.CSSProperties = { - justifySelf: 'end', - borderWidth: 0, - padding: 0, - margin: 0, - cursor: 'default', - backgroundColor: 'transparent', -}; - -export const NavigationBarButton = styled.button({ ...navigationRightHandSideButton }); -export const NavigationInfoButton = styled(InfoButton).attrs({ - size: 24, - tintColor: colors.white40, - tintHoverColor: colors.white60, -})({ - ...navigationRightHandSideButton, -}); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NavigationBarStyles.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NavigationBarStyles.tsx deleted file mode 100644 index eb4472c900..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NavigationBarStyles.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import styled from 'styled-components'; - -import { colors } from '../../config.json'; -import { normalText } from './common-styles'; -import ImageView from './ImageView'; - -export const StyledNavigationBarSeparator = styled.div({ - backgroundColor: 'rgba(0, 0, 0, 0.2)', - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - height: '1px', -}); - -export const StyledNavigationItems = styled.div({ - flex: 1, - display: 'grid', - gridTemplateColumns: '1fr auto 1fr', - alignItems: 'center', -}); - -export const StyledNavigationBar = styled.nav({ - flex: 0, - padding: '12px', -}); - -export const StyledTitleBarItemLabel = styled.h1<{ $visible?: boolean }>(normalText, (props) => ({ - fontWeight: 400, - lineHeight: '22px', - color: colors.white, - padding: '0 5px', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - opacity: props.$visible ? 1 : 0, - transition: 'opacity 250ms ease-in-out', -})); - -export const StyledBackBarItemButton = styled.button({ - justifySelf: 'start', - borderWidth: 0, - padding: 0, - margin: 0, - cursor: 'default', - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - backgroundColor: 'transparent', -}); - -export const StyledBackBarItemIcon = styled(ImageView)({ - marginRight: '8px', - [StyledBackBarItemButton + ':hover &&']: { - backgroundColor: colors.white60, - }, -}); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NavigationContainer.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NavigationContainer.tsx new file mode 100644 index 0000000000..5bfeb24253 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NavigationContainer.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import { IScrollEvent } from './CustomScrollbars'; + +interface NavigationContainerProps { + children?: React.ReactNode; +} + +interface NavigationContainerState { + showsBarTitle: boolean; + showsBarSeparator: boolean; +} + +export class NavigationContainer extends React.Component< + NavigationContainerProps, + NavigationContainerState +> { + public state = { + showsBarTitle: false, + showsBarSeparator: false, + }; + + public componentDidMount() { + this.updateBarAppearance({ scrollLeft: 0, scrollTop: 0 }); + } + + public render() { + return ( + <NavigationScrollContext.Provider + value={{ + ...this.state, + onScroll: this.onScroll, + }}> + {this.props.children} + </NavigationScrollContext.Provider> + ); + } + + public onScroll = (event: IScrollEvent) => { + this.updateBarAppearance(event); + }; + + private updateBarAppearance(event: IScrollEvent) { + // that's where SettingsHeader.HeaderTitle intersects the navigation bar + const showsBarSeparator = event.scrollTop > 11; + + // that's when SettingsHeader.HeaderTitle goes behind the navigation bar + const showsBarTitle = event.scrollTop > 20; + + if ( + this.state.showsBarSeparator !== showsBarSeparator || + this.state.showsBarTitle !== showsBarTitle + ) { + this.setState({ showsBarSeparator, showsBarTitle }); + } + } +} +export const NavigationScrollContext = React.createContext({ + showsBarTitle: false, + showsBarSeparator: false, + onScroll(_event: IScrollEvent): void { + throw Error('NavigationScrollContext provider missing'); + }, +}); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NavigationScrollbars.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NavigationScrollbars.tsx new file mode 100644 index 0000000000..81583b1c4e --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NavigationScrollbars.tsx @@ -0,0 +1,73 @@ +import React, { useCallback, useContext, useEffect, useLayoutEffect, useRef } from 'react'; + +import { useAppContext } from '../context'; +import { useHistory } from '../lib/history'; +import { useCombinedRefs, useEffectEvent } from '../lib/utility-hooks'; +import CustomScrollbars, { CustomScrollbarsRef, IScrollEvent } from './CustomScrollbars'; +import { NavigationScrollContext } from './NavigationContainer'; + +export interface NavigationScrollbarsProps { + className?: string; + fillContainer?: boolean; + children?: React.ReactNode; +} + +export const NavigationScrollbars = React.forwardRef(function NavigationScrollbarsT( + props: NavigationScrollbarsProps, + forwardedRef?: React.Ref<CustomScrollbarsRef>, +) { + const history = useHistory(); + const { setNavigationHistory } = useAppContext(); + const { onScroll } = useContext(NavigationScrollContext); + + const ref = useRef<CustomScrollbarsRef>(); + const combinedRefs = useCombinedRefs(forwardedRef, ref); + + const beforeunload = useEffectEvent(() => { + if (ref.current) { + history.recordScrollPosition(ref.current.getScrollPosition()); + setNavigationHistory(history.asObject); + } + }); + + useEffect(() => { + window.addEventListener('beforeunload', beforeunload); + return () => window.removeEventListener('beforeunload', beforeunload); + }, []); + + const onMount = useEffectEvent(() => { + const location = history.location; + if (history.action === 'POP') { + ref.current?.scrollTo(...location.state.scrollPosition); + } + }); + + const onUnmount = useEffectEvent(() => { + if (history.action === 'PUSH' && ref.current) { + history.recordScrollPosition(ref.current.getScrollPosition()); + setNavigationHistory(history.asObject); + } + }); + + useLayoutEffect(() => { + onMount(); + return () => onUnmount(); + }, []); + + const handleScroll = useCallback( + (event: IScrollEvent) => { + onScroll(event); + }, + [onScroll], + ); + + return ( + <CustomScrollbars + ref={combinedRefs} + className={props.className} + fillContainer={props.fillContainer} + onScroll={handleScroll}> + {props.children} + </CustomScrollbars> + ); +}); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/OpenVpnSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/OpenVpnSettings.tsx index 37e72b13e7..d1288b30a1 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/OpenVpnSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/OpenVpnSettings.tsx @@ -17,19 +17,15 @@ import { useRelaySettingsUpdater } from '../lib/constraint-updater'; import { useHistory } from '../lib/history'; import { formatHtml } from '../lib/html-formatter'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import Selector, { SelectorItem } from './cell/Selector'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; import { ModalMessage } from './Modal'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const MIN_MSSFIX_VALUE = 1000; @@ -70,19 +66,15 @@ export default function OpenVpnSettings() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - {sprintf( - // TRANSLATORS: Title label in navigation bar - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(openvpn)s - Will be replaced with "OpenVPN" - messages.pgettext('openvpn-settings-nav', '%(openvpn)s settings'), - { openvpn: strings.openvpn }, - )} - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={sprintf( + // TRANSLATORS: Title label in navigation bar + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(openvpn)s - Will be replaced with "OpenVPN" + messages.pgettext('openvpn-settings-nav', '%(openvpn)s settings'), + { openvpn: strings.openvpn }, + )} + /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ProblemReport.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ProblemReport.tsx index 36ddc09c45..e4213d871f 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/ProblemReport.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/ProblemReport.tsx @@ -20,13 +20,13 @@ import { useHistory } from '../lib/history'; import { useEffectEvent } from '../lib/utility-hooks'; import { useSelector } from '../redux/store'; import support from '../redux/support/actions'; +import { AppNavigationHeader } from './'; import * as AppButton from './AppButton'; import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup'; import ImageView from './ImageView'; import { BackAction } from './KeyboardNavigation'; import { Footer, Layout, SettingsContainer } from './Layout'; import { ModalAlert, ModalAlertType } from './Modal'; -import { NavigationBar, NavigationItems, TitleBarItem } from './NavigationBar'; import { StyledContent, StyledContentContainer, @@ -66,16 +66,12 @@ function ProblemReportComponent() { <BackAction action={history.pop}> <Layout> <SettingsContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('support-view', 'Report a problem') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('support-view', 'Report a problem') + } + /> <StyledContentContainer> <Header /> <Content /> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SelectLanguage.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SelectLanguage.tsx index 84f572fc5a..91ee78429a 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SelectLanguage.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SelectLanguage.tsx @@ -5,18 +5,14 @@ import { useAppContext } from '../../renderer/context'; import { messages } from '../../shared/gettext'; import { useHistory } from '../lib/history'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaInputGroup } from './AriaGroup'; import Selector, { SelectorItem } from './cell/Selector'; import { CustomScrollbarsRef } from './CustomScrollbars'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const StyledSelector = styled(Selector)({ @@ -56,16 +52,12 @@ export default function SelectLanguage() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('select-language-nav', 'Select language') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('select-language-nav', 'Select language') + } + /> <NavigationScrollbars ref={scrollView}> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx index f2c4c2760a..1eb7253533 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx @@ -7,6 +7,7 @@ import { Button, TitleBig } from '../lib/components'; import { useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import * as Cell from './cell'; import { BackAction } from './KeyboardNavigation'; import { @@ -19,7 +20,7 @@ import { SettingsNavigationScrollbars, SettingsStack, } from './Layout'; -import { NavigationBar, NavigationContainer, NavigationItems, TitleBarItem } from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; import SettingsHeader from './SettingsHeader'; export default function Support() { @@ -37,16 +38,12 @@ export default function Support() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('navigation-bar', 'Settings') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('navigation-bar', 'Settings') + } + /> <SettingsNavigationScrollbars fillContainer> <SettingsContent> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SettingsImport.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SettingsImport.tsx index 8b7169ab06..b2ebe680d8 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SettingsImport.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SettingsImport.tsx @@ -13,17 +13,12 @@ import { RoutePath } from '../lib/routes'; import { useBoolean, useEffectEvent } from '../lib/utility-hooks'; import settingsImportActions from '../redux/settings-import/actions'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { measurements, normalText, tinyText } from './common-styles'; import ImageView from './ImageView'; import { BackAction } from './KeyboardNavigation'; import { Footer, Layout, SettingsContainer } from './Layout'; import { ModalAlert, ModalAlertType } from './Modal'; -import { - NavigationBar, - NavigationInfoButton, - NavigationItems, - TitleBarItem, -} from './NavigationBar'; import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; import { SmallButton, SmallButtonColor, SmallButtonGrid } from './SmallButton'; @@ -116,34 +111,31 @@ export default function SettingsImport() { <BackAction action={history.pop}> <Layout> <SettingsContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar. This is for a feature that lets - // TRANSLATORS: users import server IP settings. - messages.pgettext('settings-import', 'Server IP override') - } - </TitleBarItem> - <NavigationInfoButton - title={messages.pgettext('settings-import', 'Server IP override')} - message={[ - messages.pgettext( - 'settings-import', - 'On some networks, where various types of censorship are being used, our server IP addresses are sometimes blocked.', - ), - messages.pgettext( - 'settings-import', - 'To circumvent this you can import a file or a text, provided by our support team, with new IP addresses that override the default addresses of the servers in the Select location view.', - ), - messages.pgettext( - 'settings-import', - 'If you are having issues connecting to VPN servers, please contact support.', - ), - ]} - /> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar. This is for a feature that lets + // TRANSLATORS: users import server IP settings. + messages.pgettext('settings-import', 'Server IP override') + }> + <AppNavigationHeader.InfoButton + title={messages.pgettext('settings-import', 'Server IP override')} + variant="secondary" + message={[ + messages.pgettext( + 'settings-import', + 'On some networks, where various types of censorship are being used, our server IP addresses are sometimes blocked.', + ), + messages.pgettext( + 'settings-import', + 'To circumvent this you can import a file or a text, provided by our support team, with new IP addresses that override the default addresses of the servers in the Select location view.', + ), + messages.pgettext( + 'settings-import', + 'If you are having issues connecting to VPN servers, please contact support.', + ), + ]} + /> + </AppNavigationHeader> <Flex $flexDirection="column" $flex={1}> <SettingsHeader> <HeaderTitle> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SettingsTextImport.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SettingsTextImport.tsx index c6cb325b0c..ed2dd84695 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SettingsTextImport.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SettingsTextImport.tsx @@ -8,10 +8,9 @@ import { useHistory } from '../lib/history'; import { useCombinedRefs, useRefCallback, useStyledRef } from '../lib/utility-hooks'; import settingsImportActions from '../redux/settings-import/actions'; import { useSelector } from '../redux/store'; -import ImageView from './ImageView'; +import { AppNavigationHeader } from './'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; -import { NavigationBar, NavigationBarButton, NavigationItems, TitleBarItem } from './NavigationBar'; const StyledTextArea = styled.textarea({ width: '100%', @@ -54,25 +53,18 @@ export default function SettingsTextImport() { <BackAction action={back}> <Layout> <SettingsContainer> - <NavigationBar alwaysDisplayBarTitle> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('settings-import', 'Import via text') - } - </TitleBarItem> - <NavigationBarButton onClick={save} aria-label={messages.gettext('Save')}> - <ImageView - source="icon-check" - tintColor={colors.white40} - tintHoverColor={colors.white60} - height={24} - width={24} - /> - </NavigationBarButton> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('settings-import', 'Import via text') + } + titleVisible> + <AppNavigationHeader.IconButton + icon="icon-check" + onClick={save} + aria-label={messages.gettext('Save')} + /> + </AppNavigationHeader> <StyledTextArea ref={combinedTextAreaRef} /> </SettingsContainer> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Shadowsocks.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Shadowsocks.tsx index 4c59bde50e..b23fe817fc 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Shadowsocks.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Shadowsocks.tsx @@ -8,18 +8,14 @@ import { removeNonNumericCharacters } from '../../shared/string-helpers'; import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaDescription, AriaInputGroup } from './AriaGroup'; import * as Cell from './cell'; import { SelectorItem, SelectorWithCustomItem } from './cell/Selector'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const PORTS: Array<SelectorItem<number>> = []; @@ -44,16 +40,12 @@ export default function Shadowsocks() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('wireguard-settings-nav', 'Shadowsocks') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('wireguard-settings-nav', 'Shadowsocks') + } + /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx index cab859b8c9..849b420cd1 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx @@ -16,6 +16,7 @@ import { useHistory } from '../lib/history'; import { formatHtml } from '../lib/html-formatter'; import { useEffectEvent, useStyledRef } from '../lib/utility-hooks'; import { IReduxState } from '../redux/store'; +import { AppNavigationHeader } from './'; import Accordion from './Accordion'; import * as AppButton from './AppButton'; import * as Cell from './cell'; @@ -26,7 +27,7 @@ import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; import List from './List'; import { ModalAlert, ModalAlertType } from './Modal'; -import { NavigationBar, NavigationContainer, NavigationItems, TitleBarItem } from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; import { StyledActionIcon, @@ -60,11 +61,7 @@ export default function SplitTunneling() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem>{strings.splitTunneling}</TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader title={strings.splitTunneling} /> <StyledNavigationScrollbars ref={scrollbarsRef}> <PlatformSpecificSplitTunnelingSettings diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx index 554f2a2a11..98438116c9 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx @@ -6,7 +6,7 @@ import * as AppButton from './AppButton'; import * as Cell from './cell'; import { measurements, normalText } from './common-styles'; import ImageView from './ImageView'; -import { NavigationScrollbars } from './NavigationBar'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SearchBar from './SearchBar'; import { SmallButton } from './SmallButton'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Support.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Support.tsx index 2785883ecf..34f49a1c30 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Support.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Support.tsx @@ -7,6 +7,7 @@ import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaDescribed, AriaDescription, @@ -18,13 +19,8 @@ import { import * as Cell from './cell'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const StyledContent = styled.div({ @@ -42,16 +38,12 @@ export default function Support() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('support-view', 'Support') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('support-view', 'Support') + } + /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/UdpOverTcp.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/UdpOverTcp.tsx index 7179daca77..c6e6ff43f6 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/UdpOverTcp.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/UdpOverTcp.tsx @@ -6,19 +6,15 @@ import { messages } from '../../shared/gettext'; import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaInputGroup } from './AriaGroup'; import * as Cell from './cell'; import Selector, { SelectorItem } from './cell/Selector'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; import { ModalMessage } from './Modal'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const UDP2TCP_PORTS = [80, 5001]; @@ -46,16 +42,12 @@ export default function UdpOverTcp() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('wireguard-settings-nav', 'UDP-over-TCP') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('wireguard-settings-nav', 'UDP-over-TCP') + } + /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/UserInterfaceSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/UserInterfaceSettings.tsx index 38a0f71bbd..13c29a7ea7 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/UserInterfaceSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/UserInterfaceSettings.tsx @@ -6,6 +6,7 @@ import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import { BackAction } from './KeyboardNavigation'; @@ -17,13 +18,8 @@ import { SettingsGroup, SettingsStack, } from './Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const StyledAnimateMapCellGroup = styled(SettingsGroup)({ @@ -41,16 +37,12 @@ export default function UserInterfaceSettings() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('user-interface-settings-view', 'User interface settings') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('user-interface-settings-view', 'User interface settings') + } + /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/VpnSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/VpnSettings.tsx index 5754ebce72..a45d461dbc 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/VpnSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/VpnSettings.tsx @@ -16,25 +16,23 @@ import { RoutePath } from '../lib/routes'; import { useBoolean } from '../lib/utility-hooks'; import { RelaySettingsRedux } from '../redux/settings/reducers'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import * as AppButton from './AppButton'; import { AriaDescription, AriaDetails, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import Selector, { SelectorItem } from './cell/Selector'; import CustomDnsSettings from './CustomDnsSettings'; -import InfoButton, { InfoIcon } from './InfoButton'; +import InfoButton from './InfoButton'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer, SettingsContent, SettingsGroup, SettingsStack } from './Layout'; import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; -const StyledInfoIcon = styled(InfoIcon)({ +const StyledInfoButton = styled(InfoButton).attrs({ + size: 'small', +})({ marginRight: Spacings.spacing5, }); @@ -63,16 +61,12 @@ export default function VpnSettings() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('vpn-settings-view', 'VPN settings') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('vpn-settings-view', 'VPN settings') + } + /> <NavigationScrollbars> <SettingsHeader> @@ -204,7 +198,7 @@ function AllowLan() { </Cell.InputLabel> </AriaLabel> <AriaDetails> - <InfoButton> + <StyledInfoButton> <ModalMessage> {messages.pgettext( 'vpn-settings-view', @@ -225,7 +219,7 @@ function AllowLan() { <li>fc00::/7</li> </LanIpRanges> </ModalMessage> - </InfoButton> + </StyledInfoButton> </AriaDetails> <AriaInput> <Cell.Switch isOn={allowLan} onChange={setAllowLan} /> @@ -263,7 +257,7 @@ function DnsBlockers() { <StyledTitleLabel as="label" disabled={dns.state === 'custom'}> {messages.pgettext('vpn-settings-view', 'DNS content blockers')} </StyledTitleLabel> - <InfoButton> + <StyledInfoButton> <ModalMessage> {messages.pgettext( 'vpn-settings-view', @@ -287,7 +281,7 @@ function DnsBlockers() { ), )} </ModalMessage> - </InfoButton> + </StyledInfoButton> </> ); @@ -368,14 +362,14 @@ function BlockMalware() { </IndentedValueLabel> </AriaLabel> <AriaDetails> - <InfoButton> + <StyledInfoButton> <ModalMessage> {messages.pgettext( 'vpn-settings-view', 'Warning: The malware blocker is not an anti-virus and should not be treated as such, this is just an extra layer of protection.', )} </ModalMessage> - </InfoButton> + </StyledInfoButton> </AriaDetails> <AriaInput> <Cell.Switch @@ -510,7 +504,7 @@ function EnableIpv6() { <Cell.InputLabel>{messages.pgettext('vpn-settings-view', 'Enable IPv6')}</Cell.InputLabel> </AriaLabel> <AriaDetails> - <InfoButton> + <StyledInfoButton> <ModalMessage> {messages.pgettext( 'vpn-settings-view', @@ -523,7 +517,7 @@ function EnableIpv6() { 'IPv4 is always enabled and the majority of websites and applications use this protocol. We do not recommend enabling IPv6 unless you know you need it.', )} </ModalMessage> - </InfoButton> + </StyledInfoButton> </AriaDetails> <AriaInput> <Cell.Switch isOn={enableIpv6} onChange={setEnableIpv6} /> @@ -545,7 +539,7 @@ function KillSwitchInfo() { {messages.pgettext('vpn-settings-view', 'Kill switch')} </Cell.InputLabel> </AriaLabel> - <StyledInfoIcon /> + <StyledInfoButton /> <AriaInput> <Cell.Switch isOn disabled /> </AriaInput> @@ -622,7 +616,7 @@ function LockdownMode() { </Cell.InputLabel> </AriaLabel> <AriaDetails> - <InfoButton> + <StyledInfoButton> <ModalMessage> {messages.pgettext( 'vpn-settings-view', @@ -635,7 +629,7 @@ function LockdownMode() { 'With Lockdown Mode enabled, you must be connected to a Mullvad VPN server to be able to reach the internet. Manually disconnecting or quitting the app will block your connection.', )} </ModalMessage> - </InfoButton> + </StyledInfoButton> </AriaDetails> <AriaInput> <Cell.Switch isOn={blockWhenDisconnected} onChange={setLockDownMode} /> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/WireguardSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/WireguardSettings.tsx index 5a87ffdb8b..450ced9d31 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/WireguardSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/WireguardSettings.tsx @@ -17,19 +17,15 @@ import { useRelaySettingsUpdater } from '../lib/constraint-updater'; import { useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { useSelector } from '../redux/store'; +import { AppNavigationHeader } from './'; import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import Selector, { SelectorItem, SelectorWithCustomItem } from './cell/Selector'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer, SettingsContent, SettingsGroup, SettingsStack } from './Layout'; import { ModalMessage } from './Modal'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from './NavigationBar'; +import { NavigationContainer } from './NavigationContainer'; +import { NavigationScrollbars } from './NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const MIN_WIREGUARD_MTU_VALUE = 1280; @@ -52,19 +48,15 @@ export default function WireguardSettings() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - {sprintf( - // TRANSLATORS: Title label in navigation bar - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard" - messages.pgettext('wireguard-settings-nav', '%(wireguard)s settings'), - { wireguard: strings.wireguard }, - )} - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={sprintf( + // TRANSLATORS: Title label in navigation bar + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard" + messages.pgettext('wireguard-settings-nav', '%(wireguard)s settings'), + { wireguard: strings.wireguard }, + )} + /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Selector.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Selector.tsx index b1e20a0c41..4fc8e15925 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Selector.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Selector.tsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import { colors } from '../../../config.json'; import { messages } from '../../../shared/gettext'; +import { Spacings } from '../../lib/foundations'; import { useHistory } from '../../lib/history'; import { RoutePath } from '../../lib/routes'; import { useStyledRef } from '../../lib/utility-hooks'; @@ -15,6 +16,10 @@ const StyledTitleLabel = styled(Cell.SectionTitle)({ flex: 1, }); +const StyledInfoButton = styled(InfoButton)({ + marginRight: Spacings.spacing5, +}); + export interface SelectorItem<T> { label: string; value: T; @@ -94,7 +99,9 @@ export default function Selector<T, U>(props: SelectorProps<T, U>) { </AriaLabel> {props.details && ( <AriaDetails> - <InfoButton title={props.infoTitle}>{props.details}</InfoButton> + <StyledInfoButton title={props.infoTitle} size="small"> + {props.details} + </StyledInfoButton> </AriaDetails> )} </> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/cell/SettingsGroup.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/cell/SettingsGroup.tsx index c1b9ac80d1..ccca7cdc0b 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/cell/SettingsGroup.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/cell/SettingsGroup.tsx @@ -85,7 +85,7 @@ export function SettingsGroup(props: React.PropsWithChildren<SettingsGroupProps> <StyledTitle> {props.title} {props.infoMessage !== undefined && ( - <StyledInfoButton size={12} message={props.infoMessage} /> + <StyledInfoButton size="small" message={props.infoMessage} /> )} </StyledTitle> )} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/index.ts new file mode 100644 index 0000000000..612a0df28f --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/index.ts @@ -0,0 +1 @@ +export * from './app-navigation-header'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx index 32c516d0a0..7779d6fb2b 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx @@ -4,6 +4,7 @@ import { sprintf } from 'sprintf-js'; import { colors, strings } from '../../../config.json'; import { Ownership } from '../../../shared/daemon-rpc-types'; import { messages } from '../../../shared/gettext'; +import { IconButton } from '../../lib/components'; import { useRelaySettingsUpdater } from '../../lib/constraint-updater'; import { daitaFilterActive, filterSpecialLocations } from '../../lib/filter-locations'; import { useHistory } from '../../lib/history'; @@ -11,19 +12,14 @@ import { formatHtml } from '../../lib/html-formatter'; import { useNormalRelaySettings } from '../../lib/relay-settings-hooks'; import { RoutePath } from '../../lib/routes'; import { useSelector } from '../../redux/store'; +import { AppNavigationHeader } from '../'; import * as Cell from '../cell'; import { useFilteredProviders } from '../Filter'; import ImageView from '../ImageView'; import { BackAction } from '../KeyboardNavigation'; import { Layout, SettingsContainer } from '../Layout'; -import { - NavigationBar, - NavigationBarButton, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from '../NavigationBar'; +import { NavigationContainer } from '../NavigationContainer'; +import { NavigationScrollbars } from '../NavigationScrollbars'; import CombinedLocationList, { CombinedLocationListProps } from './CombinedLocationList'; import CustomLists from './CustomLists'; import { useRelayListContext } from './RelayListContext'; @@ -133,26 +129,19 @@ export default function SelectLocation() { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar alwaysDisplayBarTitle> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('select-location-nav', 'Select location') - } - </TitleBarItem> - - <NavigationBarButton onClick={onViewFilter} aria-label={messages.gettext('Filter')}> - <ImageView - source="icon-filter-round" - tintColor={colors.white40} - tintHoverColor={colors.white60} - height={24} - width={24} - /> - </NavigationBarButton> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader + title={ + // TRANSLATORS: Title label in navigation bar + messages.pgettext('select-location-nav', 'Select location') + } + titleVisible> + <IconButton + icon="icon-filter-round" + variant="secondary" + onClick={onViewFilter} + aria-label={messages.gettext('Filter')} + /> + </AppNavigationHeader> <StyledNavigationBarAttachment> {allowEntrySelection && ( diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SpecialLocationList.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SpecialLocationList.tsx index e4d105d635..552f86d9fc 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SpecialLocationList.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SpecialLocationList.tsx @@ -35,7 +35,9 @@ export default function SpecialLocationList<T>({ source, ...props }: SpecialLoca ); } -const StyledSpecialLocationInfoButton = styled(InfoButton)({ padding: '0 25px', margin: 0 }); +const StyledSpecialLocationInfoButton = styled(InfoButton).attrs({ + size: 'small', +})({ width: '56px', height: '48px' }); const StyledSpecialLocationSideButton = styled(ImageView)({ padding: '0 3px' }); interface SpecialLocationRowProps<T> { diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/AppInfoView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/AppInfoView.tsx index 41671c46a9..dedf6b72bb 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/AppInfoView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/AppInfoView.tsx @@ -1,14 +1,10 @@ import { messages } from '../../../../shared/gettext'; import { useHistory } from '../../../lib/history'; +import { AppNavigationHeader } from '../../'; import { BackAction } from '../../KeyboardNavigation'; import { Layout, SettingsContainer, SettingsGroup, SettingsStack } from '../../Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from '../../NavigationBar'; +import { NavigationContainer } from '../../NavigationContainer'; +import { NavigationScrollbars } from '../../NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from '../../SettingsHeader'; import { AppVersionListItem, ChangelogListItem } from './components'; @@ -19,11 +15,7 @@ export const AppInfoView = () => { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem>{messages.pgettext('app-info-view', 'App info')}</TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader title={messages.pgettext('app-info-view', 'App info')} /> <NavigationScrollbars> <SettingsHeader> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/changelog/ChangelogView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/changelog/ChangelogView.tsx index 4a93908e65..19a499a34d 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/views/changelog/ChangelogView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/changelog/ChangelogView.tsx @@ -4,21 +4,16 @@ import styled from 'styled-components'; import { links } from '../../../../config.json'; import { messages } from '../../../../shared/gettext'; import { useAppContext } from '../../../context'; -import { BodySmall, Button, Flex, TitleBig, TitleLarge } from '../../../lib/components'; -import { Container } from '../../../lib/components'; +import { BodySmall, Button, Container, Flex, TitleBig, TitleLarge } from '../../../lib/components'; import { Colors, Spacings } from '../../../lib/foundations'; import { useHistory } from '../../../lib/history'; import { useSelector } from '../../../redux/store'; +import { AppNavigationHeader } from '../../'; import ImageView from '../../ImageView'; import { BackAction } from '../../KeyboardNavigation'; import { Layout, SettingsContainer } from '../../Layout'; -import { - NavigationBar, - NavigationContainer, - NavigationItems, - NavigationScrollbars, - TitleBarItem, -} from '../../NavigationBar'; +import { NavigationContainer } from '../../NavigationContainer'; +import { NavigationScrollbars } from '../../NavigationScrollbars'; import SettingsHeader from '../../SettingsHeader'; const StyledList = styled(Flex)({ @@ -49,11 +44,7 @@ export const ChangelogView = () => { <Layout> <SettingsContainer> <NavigationContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem>{messages.pgettext('changelog-view', 'What’s new')}</TitleBarItem> - </NavigationItems> - </NavigationBar> + <AppNavigationHeader title={messages.pgettext('changelog-view', 'What’s new')} /> <NavigationScrollbars> <SettingsHeader> |
