diff options
10 files changed, 42 insertions, 264 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/DeviceRevokedView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/DeviceRevokedView.tsx index c591c816f4..0036876bb4 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/DeviceRevokedView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/DeviceRevokedView.tsx @@ -7,14 +7,9 @@ import { useSelector } from '../redux/store'; import * as AppButton from './AppButton'; import { bigText, measurements, smallText } from './common-styles'; import CustomScrollbars from './CustomScrollbars'; -import { calculateHeaderBarStyle, DefaultHeaderBar } from './HeaderBar'; import ImageView from './ImageView'; -import { Container, Footer } from './Layout'; -import { Layout } from './Layout'; - -export const StyledHeader = styled(DefaultHeaderBar)({ - flex: 0, -}); +import { Container, Footer, Layout } from './Layout'; +import { MainHeader } from './main-header'; export const StyledCustomScrollbars = styled(CustomScrollbars)({ flex: 1, @@ -59,7 +54,10 @@ export function DeviceRevokedView() { return ( <Layout> - <StyledHeader barStyle={calculateHeaderBarStyle(tunnelState)} /> + <MainHeader variant="basedOnConnectionStatus" size="basedOnLoginStatus"> + <MainHeader.AccountButton /> + <MainHeader.SettingsButton /> + </MainHeader> <StyledCustomScrollbars fillContainer> <StyledContainer> <StyledBody> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ErrorView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ErrorView.tsx index 08ed7b0962..edd4f78c7a 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/ErrorView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/ErrorView.tsx @@ -3,9 +3,9 @@ import styled from 'styled-components'; import { colors } from '../../config.json'; import { measurements } from './common-styles'; -import { HeaderBarSettingsButton } from './HeaderBar'; import ImageView from './ImageView'; -import { Container, Header, Layout } from './Layout'; +import { Container, Layout } from './Layout'; +import { MainHeader } from './main-header'; const StyledContainer = styled(Container)({ flex: 1, @@ -56,7 +56,9 @@ interface ErrorViewProps { export default function ErrorView(props: ErrorViewProps) { return ( <Layout> - <Header>{!props.settingsUnavailable && <HeaderBarSettingsButton />}</Header> + <MainHeader logoVariant="none"> + {!props.settingsUnavailable && <MainHeader.SettingsButton />} + </MainHeader> <StyledContainer> <StyledContent> <Logo height={106} width={106} source="logo-icon" /> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountAddTime.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountAddTime.tsx index 2433a29644..e15709275f 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountAddTime.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountAddTime.tsx @@ -18,9 +18,9 @@ import * as AppButton from './AppButton'; import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup'; import { hugeText, measurements, tinyText } from './common-styles'; import CustomScrollbars from './CustomScrollbars'; -import { calculateHeaderBarStyle, DefaultHeaderBar, HeaderBarStyle } from './HeaderBar'; import ImageView from './ImageView'; import { Container, Footer, Layout } from './Layout'; +import { MainHeader } from './main-header'; import { RedeemVoucherContainer, RedeemVoucherInput, @@ -28,10 +28,6 @@ import { RedeemVoucherSubmitButton, } from './RedeemVoucher'; -export const StyledHeader = styled(DefaultHeaderBar)({ - flex: 0, -}); - export const StyledCustomScrollbars = styled(CustomScrollbars)({ flex: 1, }); @@ -261,12 +257,15 @@ function HeaderBar() { const isNewAccount = useSelector( (state) => state.account.status.type === 'ok' && state.account.status.method === 'new_account', ); - const tunnelState = useSelector((state) => state.connection.status); - const headerBarStyle = isNewAccount - ? HeaderBarStyle.default - : calculateHeaderBarStyle(tunnelState); - return <StyledHeader barStyle={headerBarStyle} />; + return ( + <MainHeader + variant={isNewAccount ? 'default' : 'basedOnConnectionStatus'} + size="basedOnLoginStatus"> + <MainHeader.AccountButton /> + <MainHeader.SettingsButton /> + </MainHeader> + ); } function useFinishedCallback() { diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorView.tsx index a98d3e441c..2c95936a4d 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorView.tsx @@ -21,15 +21,14 @@ import { StyledContainer, StyledCustomScrollbars, StyledDeviceLabel, - StyledHeader, StyledMessage, StyledModalCellContainer, StyledStatusIcon, StyledTitle, } from './ExpiredAccountErrorViewStyles'; -import { calculateHeaderBarStyle, HeaderBarStyle } from './HeaderBar'; import ImageView from './ImageView'; import { Footer, Layout } from './Layout'; +import { MainHeader } from './main-header'; import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; enum RecoveryAction { @@ -50,15 +49,9 @@ function ExpiredAccountErrorViewComponent() { const { push } = useHistory(); const { disconnectTunnel } = useAppContext(); - const connection = useSelector((state) => state.connection); - const { recoveryAction } = useRecoveryAction(); const isNewAccount = useIsNewAccount(); - const headerBarStyle = isNewAccount - ? HeaderBarStyle.default - : calculateHeaderBarStyle(connection.status); - const onDisconnect = useCallback(async () => { try { await disconnectTunnel(); @@ -74,7 +67,12 @@ function ExpiredAccountErrorViewComponent() { return ( <Layout> - <StyledHeader barStyle={headerBarStyle} /> + <MainHeader + variant={isNewAccount ? 'default' : 'basedOnConnectionStatus'} + size="basedOnLoginStatus"> + <MainHeader.AccountButton /> + <MainHeader.SettingsButton /> + </MainHeader> <StyledCustomScrollbars fillContainer> <StyledContainer> <StyledBody>{isNewAccount ? <WelcomeView /> : <Content />}</StyledBody> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorViewStyles.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorViewStyles.tsx index f41178a627..5e62751570 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorViewStyles.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorViewStyles.tsx @@ -5,13 +5,8 @@ import AccountNumberLabel from './AccountNumberLabel'; import * as Cell from './cell'; import { hugeText, measurements, tinyText } from './common-styles'; import CustomScrollbars from './CustomScrollbars'; -import { DefaultHeaderBar } from './HeaderBar'; import { Container } from './Layout'; -export const StyledHeader = styled(DefaultHeaderBar)({ - flex: 0, -}); - export const StyledAccountNumberLabel = styled(AccountNumberLabel)({ fontFamily: 'Open Sans', lineHeight: '20px', diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/HeaderBar.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/HeaderBar.tsx deleted file mode 100644 index 08c7219ded..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/components/HeaderBar.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import React, { useCallback } from 'react'; -import { sprintf } from 'sprintf-js'; -import styled from 'styled-components'; - -import { colors } from '../../config.json'; -import { closeToExpiry, formatRemainingTime, hasExpired } from '../../shared/account-expiry'; -import { TunnelState } from '../../shared/daemon-rpc-types'; -import { messages } from '../../shared/gettext'; -import { capitalizeEveryWord } from '../../shared/string-helpers'; -import { Flex, FootnoteMini, IconButton } from '../lib/components'; -import { Colors, Spacings } from '../lib/foundations'; -import { transitions, useHistory } from '../lib/history'; -import { RoutePath } from '../lib/routes'; -import { useSelector } from '../redux/store'; -import { FocusFallback } from './Focus'; -import ImageView from './ImageView'; - -export enum HeaderBarStyle { - default = 'default', - defaultDark = 'defaultDark', - error = 'error', - success = 'success', -} - -const headerBarStyleColorMap = { - [HeaderBarStyle.default]: colors.blue, - [HeaderBarStyle.defaultDark]: colors.darkBlue, - [HeaderBarStyle.error]: colors.red, - [HeaderBarStyle.success]: colors.green, -}; - -const StyledHeader = styled.header<{ - $barStyle?: HeaderBarStyle; - $accountInfoVisible: boolean; -}>(({ $accountInfoVisible, $barStyle }) => ({ - height: $accountInfoVisible ? '80px' : '68px', - minHeight: $accountInfoVisible ? '80px' : '68px', - - backgroundColor: headerBarStyleColorMap[$barStyle ?? HeaderBarStyle.default], - transition: 'height 250ms ease-in-out, min-height 250ms ease-in-out', -})); - -const StyledLogoRow = styled(Flex)({ - height: '38px', -}); - -interface IHeaderBarProps { - barStyle?: HeaderBarStyle; - className?: string; - children?: React.ReactNode; - showAccountInfo?: boolean; -} - -export default function HeaderBar(props: IHeaderBarProps) { - return ( - <StyledHeader - $barStyle={props.barStyle} - $accountInfoVisible={props.showAccountInfo ?? false} - className={props.className}> - <Flex - $flexDirection="column" - $justifyContent="center" - $margin={{ - horizontal: Spacings.spacing5, - top: Spacings.spacing5, - bottom: Spacings.spacing3, - }}> - <StyledLogoRow $justifyContent="space-between" $alignItems="center"> - {props.children} - </StyledLogoRow> - {props.showAccountInfo && <HeaderBarDeviceInfo />} - </Flex> - </StyledHeader> - ); -} - -export function Brand(props: React.HTMLAttributes<HTMLDivElement>) { - return ( - <Flex $flex={1} $alignItems="center" $gap={Spacings.spacing3} {...props}> - <ImageView width={38} height={38} source="logo-icon" /> - <ImageView height={15.4} source="logo-text" /> - </Flex> - ); -} - -const StyledDeviceInfoContainer = styled(Flex)({ - minHeight: '18px', -}); - -const StyledDeviceLabel = styled(FootnoteMini)({ - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', -}); - -const StyledTimeLeftLabel = styled(FootnoteMini)({ - whiteSpace: 'nowrap', -}); - -function HeaderBarDeviceInfo() { - const deviceName = useSelector((state) => state.account.deviceName); - const accountExpiry = useSelector((state) => state.account.expiry); - const isOutOfTime = accountExpiry ? hasExpired(accountExpiry) : false; - const formattedExpiry = isOutOfTime - ? sprintf(messages.ngettext('1 day', '%d days', 0), 0) - : accountExpiry - ? formatRemainingTime(accountExpiry) - : ''; - - return ( - <StyledDeviceInfoContainer $flex={1} $alignItems="flex-end" $gap={Spacings.spacing6}> - <StyledDeviceLabel color={Colors.white80}> - {sprintf( - // TRANSLATORS: A label that will display the newly created device name to inform the user - // TRANSLATORS: about it. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(deviceName)s - The name of the current device - messages.pgettext('device-management', 'Device name: %(deviceName)s'), - { - deviceName: capitalizeEveryWord(deviceName ?? ''), - }, - )} - </StyledDeviceLabel> - {accountExpiry && !closeToExpiry(accountExpiry) && !isOutOfTime && ( - <StyledTimeLeftLabel color={Colors.white80}> - {sprintf(messages.pgettext('device-management', 'Time left: %(timeLeft)s'), { - timeLeft: formattedExpiry, - })} - </StyledTimeLeftLabel> - )} - </StyledDeviceInfoContainer> - ); -} - -interface IHeaderBarSettingsButtonProps { - disabled?: boolean; -} - -export function HeaderBarSettingsButton(props: IHeaderBarSettingsButtonProps) { - const history = useHistory(); - - const openSettings = useCallback(() => { - if (!props.disabled) { - history.push(RoutePath.settings, { transition: transitions.show }); - } - }, [history, props.disabled]); - - return ( - <IconButton - icon="icon-settings" - variant="secondary" - onClick={openSettings} - aria-label={messages.gettext('Settings')} - /> - ); -} - -export function HeaderBarAccountButton() { - const history = useHistory(); - const openAccount = useCallback( - () => history.push(RoutePath.account, { transition: transitions.show }), - [history], - ); - - return ( - <IconButton - icon="icon-account" - variant="secondary" - onClick={openAccount} - data-testid="account-button" - aria-label={messages.gettext('Account settings')} - /> - ); -} - -export function DefaultHeaderBar(props: IHeaderBarProps) { - const loggedIn = useSelector((state) => state.account.status.type === 'ok'); - - return ( - <HeaderBar showAccountInfo={loggedIn} {...props}> - <FocusFallback> - <Brand /> - </FocusFallback> - <Flex $gap={Spacings.spacing5} $alignItems="center"> - {loggedIn && <HeaderBarAccountButton />} - <HeaderBarSettingsButton /> - </Flex> - </HeaderBar> - ); -} - -export function calculateHeaderBarStyle(tunnelState: TunnelState): HeaderBarStyle { - switch (tunnelState.state) { - case 'disconnected': - return HeaderBarStyle.error; - case 'connecting': - case 'connected': - return HeaderBarStyle.success; - case 'error': - return !tunnelState.details.blockingError ? HeaderBarStyle.success : HeaderBarStyle.error; - case 'disconnecting': - switch (tunnelState.details) { - case 'block': - case 'reconnect': - return HeaderBarStyle.success; - case 'nothing': - return HeaderBarStyle.error; - } - } -} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx index 62babaac38..9ea55431f0 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx @@ -3,13 +3,8 @@ import styled from 'styled-components'; import { Flex } from '../lib/components'; import { Colors, Spacings } from '../lib/foundations'; import { measurements } from './common-styles'; -import HeaderBar from './HeaderBar'; import { NavigationScrollbars } from './NavigationBar'; -export const Header = styled(HeaderBar)({ - flex: 0, -}); - export const Container = styled.div({ display: 'flex', flexDirection: 'column', diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Login.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Login.tsx index 0d234bd66b..37f8cc9d48 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Login.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Login.tsx @@ -14,9 +14,8 @@ import { useSelector } from '../redux/store'; import Accordion from './Accordion'; import * as AppButton from './AppButton'; import { AriaControlGroup, AriaControlled, AriaControls } from './AriaGroup'; -import { Brand, HeaderBarSettingsButton } from './HeaderBar'; import ImageView from './ImageView'; -import { Container, Header, Layout } from './Layout'; +import { Container, Layout } from './Layout'; import { StyledAccountDropdownContainer, StyledAccountDropdownItem, @@ -41,6 +40,7 @@ import { StyledTitle, StyledTopInfo, } from './LoginStyles'; +import { MainHeader } from './main-header'; export default function LoginContainer() { const { openUrl, login, clearAccountHistory, createNewAccount } = useAppContext(); @@ -126,10 +126,9 @@ class Login extends React.Component<IProps, IState> { return ( <Layout> - <Header> - <Brand /> - <HeaderBarSettingsButton disabled={!allowInteraction} /> - </Header> + <MainHeader> + <MainHeader.SettingsButton disabled={!allowInteraction} /> + </MainHeader> <Container> <StyledTopInfo> {this.props.showBlockMessage ? <BlockMessage /> : this.getStatusIcon()} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/TooManyDevices.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/TooManyDevices.tsx index 42c5a61373..17adb8c0f2 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/TooManyDevices.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/TooManyDevices.tsx @@ -17,10 +17,10 @@ import * as AppButton from './AppButton'; import * as Cell from './cell'; import { bigText, measurements, normalText, tinyText } from './common-styles'; import CustomScrollbars from './CustomScrollbars'; -import { Brand, HeaderBarSettingsButton } from './HeaderBar'; import ImageView from './ImageView'; -import { Footer, Header, Layout, SettingsContainer } from './Layout'; +import { Footer, Layout, SettingsContainer } from './Layout'; import List from './List'; +import { MainHeader } from './main-header'; import { ModalAlert, ModalAlertType, ModalContainer, ModalMessage } from './Modal'; const StyledCustomScrollbars = styled(CustomScrollbars)({ @@ -124,10 +124,9 @@ export default function TooManyDevices() { return ( <ModalContainer> <Layout> - <Header> - <Brand /> - <HeaderBarSettingsButton /> - </Header> + <MainHeader> + <MainHeader.SettingsButton /> + </MainHeader> <StyledCustomScrollbars fillContainer> <StyledContainer> <StyledBody> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/MainView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/MainView.tsx index 9327094a51..f658f37bb4 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/MainView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/MainView.tsx @@ -1,9 +1,9 @@ import styled from 'styled-components'; import { useSelector } from '../../redux/store'; -import { calculateHeaderBarStyle, DefaultHeaderBar } from '../HeaderBar'; import ImageView from '../ImageView'; import { Container, Layout } from '../Layout'; +import { MainHeader } from '../main-header'; import Map from '../Map'; import NotificationArea from '../NotificationArea'; import ConnectionPanel from './ConnectionPanel'; @@ -49,7 +49,10 @@ export default function MainView() { return ( <Layout> - <DefaultHeaderBar barStyle={calculateHeaderBarStyle(connection.status)} /> + <MainHeader size="basedOnLoginStatus" variant="basedOnConnectionStatus"> + <MainHeader.AccountButton /> + <MainHeader.SettingsButton /> + </MainHeader> <StyledContainer> <Map /> <Content> |
