diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-12-17 12:17:39 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-12-17 12:17:39 +0100 |
| commit | 489a788d81e78d123b73f06dbf7ce663f48c1b33 (patch) | |
| tree | d035eb3a307a420cd3bb4df3e5607acdc27c0f1a | |
| parent | a6b6f093d1d932ea825b0a25ee32e3292777d24a (diff) | |
| parent | ac5676f75212fd3851324a02879d9173290f3bf7 (diff) | |
| download | mullvadvpn-489a788d81e78d123b73f06dbf7ce663f48c1b33.tar.xz mullvadvpn-489a788d81e78d123b73f06dbf7ce663f48c1b33.zip | |
Merge branch 'implement-changes-dialog-available-elsewhere-des-1168'
85 files changed, 1190 insertions, 440 deletions
diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot index bb4882f513..3d6ac611fc 100644 --- a/desktop/packages/mullvad-vpn/locales/messages.pot +++ b/desktop/packages/mullvad-vpn/locales/messages.pot @@ -168,7 +168,6 @@ msgstr "" msgid "Go to System Settings" msgstr "" -#. This is a button which closes a dialog. msgid "Got it!" msgstr "" @@ -344,6 +343,10 @@ msgid "More information" msgstr "" msgctxt "accessibility" +msgid "New version installed, click here to see the changelog" +msgstr "" + +msgctxt "accessibility" msgid "Opens externally" msgstr "" @@ -579,6 +582,22 @@ msgctxt "api-access-methods-view" msgid "With the “Mullvad bridges” method, the app communicates with a Mullvad API server via a Mullvad bridge server. It does this by sending the traffic obfuscated by Shadowsocks." msgstr "" +msgctxt "app-info-view" +msgid "App info" +msgstr "" + +msgctxt "app-info-view" +msgid "App is out of sync. Please quit and restart." +msgstr "" + +msgctxt "app-info-view" +msgid "App version" +msgstr "" + +msgctxt "app-info-view" +msgid "Update available. Install the latest app version to stay up to date." +msgstr "" + msgctxt "auth-failure" msgid "Blocking internet: account is out of time" msgstr "" @@ -596,7 +615,15 @@ msgid "You are logged in with an invalid account number. Please log out and try msgstr "" msgctxt "changelog" -msgid "Changes in this version:" +msgid "See full changelog" +msgstr "" + +msgctxt "changelog-view" +msgid "No updates or changes were made in this release for this platform." +msgstr "" + +msgctxt "changelog-view" +msgid "What's new" msgstr "" #. The selected location label displayed on the main view, when a user selected a specific host to connect to. @@ -885,6 +912,10 @@ msgctxt "in-app-notifications" msgid "BLOCKING INTERNET" msgstr "" +msgctxt "in-app-notifications" +msgid "Click here to see what's new." +msgstr "" + #. The in-app banner displayed to the user when the app update is available. msgctxt "in-app-notifications" msgid "Install the latest app version to stay up to date." @@ -899,6 +930,10 @@ msgid "NEW DEVICE CREATED" msgstr "" msgctxt "in-app-notifications" +msgid "NEW VERSION INSTALLED" +msgstr "" + +msgctxt "in-app-notifications" msgid "Please quit and restart the app." msgstr "" @@ -1523,11 +1558,7 @@ msgid "API access" msgstr "" msgctxt "settings-view" -msgid "App is out of sync. Please quit and restart." -msgstr "" - -msgctxt "settings-view" -msgid "App version" +msgid "App info" msgstr "" msgctxt "settings-view" @@ -1538,10 +1569,6 @@ msgctxt "settings-view" msgid "Support" msgstr "" -msgctxt "settings-view" -msgid "Update available. Install the latest app version to stay up to date." -msgstr "" - #. Navigation button to the 'User interface settings' view msgctxt "settings-view" msgid "User interface settings" @@ -1552,6 +1579,10 @@ msgctxt "settings-view" msgid "VPN settings" msgstr "" +msgctxt "settings-view" +msgid "What's new" +msgstr "" + msgctxt "split-tunneling-view" msgid "%(applicationName)s is problematic and can’t be excluded from the VPN tunnel." msgstr "" @@ -2306,9 +2337,6 @@ msgstr "" msgid "Always-on VPN assigned to other VPN" msgstr "" -msgid "App info" -msgstr "" - msgid "At least one method needs to be enabled" msgstr "" @@ -2345,6 +2373,9 @@ msgstr "" msgid "Changelog" msgstr "" +msgid "Changes in this version:" +msgstr "" + msgid "Changes to DNS related settings might not go into effect immediately due to cached results." msgstr "" diff --git a/desktop/packages/mullvad-vpn/src/config.json b/desktop/packages/mullvad-vpn/src/config.json index a77c47de45..43229834ca 100644 --- a/desktop/packages/mullvad-vpn/src/config.json +++ b/desktop/packages/mullvad-vpn/src/config.json @@ -4,7 +4,8 @@ "purchase": "https://mullvad.net/account/", "faq": "https://mullvad.net/help/tag/mullvad-app/", "privacyGuide": "https://mullvad.net/help/first-steps-towards-online-privacy/", - "download": "https://mullvad.net/download/vpn/" + "download": "https://mullvad.net/download/vpn/", + "changelog": "https://github.com/mullvad/mullvadvpn-app/blob/main/CHANGELOG.md" }, "colors": { "darkerBlue": "rgba(25, 38, 56, 0.95)", diff --git a/desktop/packages/mullvad-vpn/src/main/account.ts b/desktop/packages/mullvad-vpn/src/main/account.ts index 024e4a7208..7fc6b48d47 100644 --- a/desktop/packages/mullvad-vpn/src/main/account.ts +++ b/desktop/packages/mullvad-vpn/src/main/account.ts @@ -13,7 +13,7 @@ import { AccountExpiredNotificationProvider, CloseToAccountExpiryNotificationProvider, SystemNotificationCategory, -} from '../shared/notifications/notification'; +} from '../shared/notifications'; import { Scheduler } from '../shared/scheduler'; import AccountDataCache from './account-data-cache'; import { DaemonRpc } from './daemon-rpc'; diff --git a/desktop/packages/mullvad-vpn/src/main/index.ts b/desktop/packages/mullvad-vpn/src/main/index.ts index ea2f12199c..f855278ba9 100644 --- a/desktop/packages/mullvad-vpn/src/main/index.ts +++ b/desktop/packages/mullvad-vpn/src/main/index.ts @@ -363,6 +363,7 @@ class ApplicationMain this.daemonRpc.disconnect(); } + this.settings.gui.changelogDisplayedForVersion = this.version.currentVersion.gui; for (const logger of [log, this.rendererLog]) { try { logger?.disposeDisposableOutputs(); @@ -786,7 +787,6 @@ class ApplicationMain splitTunnelingApplications: this.splitTunnelingApplications, macOsScrollbarVisibility: this.macOsScrollbarVisibility, changelog: this.changelog ?? [], - forceShowChanges: CommandLineOptions.showChanges.match, navigationHistory: this.navigationHistory, currentApiAccessMethod: this.currentApiAccessMethod, isMacOs13OrNewer: isMacOs13OrNewer(), diff --git a/desktop/packages/mullvad-vpn/src/main/notification-controller.ts b/desktop/packages/mullvad-vpn/src/main/notification-controller.ts index 925bff0812..664f102709 100644 --- a/desktop/packages/mullvad-vpn/src/main/notification-controller.ts +++ b/desktop/packages/mullvad-vpn/src/main/notification-controller.ts @@ -16,7 +16,7 @@ import { SystemNotificationCategory, SystemNotificationProvider, SystemNotificationSeverityType, -} from '../shared/notifications/notification'; +} from '../shared/notifications'; import { Scheduler } from '../shared/scheduler'; const THROTTLE_DELAY = 500; diff --git a/desktop/packages/mullvad-vpn/src/main/version.ts b/desktop/packages/mullvad-vpn/src/main/version.ts index 1bab7728b4..676391e150 100644 --- a/desktop/packages/mullvad-vpn/src/main/version.ts +++ b/desktop/packages/mullvad-vpn/src/main/version.ts @@ -8,7 +8,7 @@ import { SystemNotificationCategory, UnsupportedVersionNotificationProvider, UpdateAvailableNotificationProvider, -} from '../shared/notifications/notification'; +} from '../shared/notifications'; import { DaemonRpc } from './daemon-rpc'; import { IpcMainEventChannel } from './ipc-event-channel'; import { NotificationSender } from './notification-controller'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/app.tsx b/desktop/packages/mullvad-vpn/src/renderer/app.tsx index 7c7b0edbfa..67e7a4db33 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/app.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/app.tsx @@ -39,7 +39,7 @@ import log, { ConsoleOutput } from '../shared/logging'; import { LogLevel } from '../shared/logging-types'; import { Scheduler } from '../shared/scheduler'; import AppRouter from './components/AppRouter'; -import { Changelog } from './components/Changelog'; +import { VariablesGlobalStyle } from './components/common/variables'; import ErrorBoundary from './components/ErrorBoundary'; import KeyboardNavigation from './components/KeyboardNavigation'; import Lang from './components/Lang'; @@ -243,7 +243,7 @@ export default class AppRenderer { this.setUpgradeVersion(initialState.upgradeVersion); this.setGuiSettings(initialState.guiSettings); this.storeAutoStart(initialState.autoStart); - this.setChangelog(initialState.changelog, initialState.forceShowChanges); + this.setChangelog(initialState.changelog); this.setCurrentApiAccessMethod(initialState.currentApiAccessMethod); this.reduxActions.userInterface.setIsMacOs13OrNewer(initialState.isMacOs13OrNewer); @@ -296,8 +296,9 @@ export default class AppRenderer { <ErrorBoundary> <ModalContainer> <KeyboardNavigation> - <AppRouter /> - <Changelog /> + <VariablesGlobalStyle> + <AppRouter /> + </VariablesGlobalStyle> </KeyboardNavigation> {window.env.platform === 'darwin' && <MacOsScrollbarDetection />} </ModalContainer> @@ -1002,9 +1003,8 @@ export default class AppRenderer { this.reduxActions.settings.updateAutoStart(autoStart); } - private setChangelog(changelog: IChangelog, forceShowChanges: boolean) { + private setChangelog(changelog: IChangelog) { this.reduxActions.userInterface.setChangelog(changelog); - this.reduxActions.userInterface.setForceShowChanges(forceShowChanges); } private updateLocation() { diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ApiAccessMethods.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ApiAccessMethods.tsx index 99f405b2a8..f1e5a5e5ae 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/ApiAccessMethods.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/ApiAccessMethods.tsx @@ -12,9 +12,9 @@ import { generateRoutePath } from '../lib/routeHelpers'; import { RoutePath } from '../lib/routes'; import { useBoolean } from '../lib/utility-hooks'; import { useSelector } from '../redux/store'; -import { Spacings } from '../tokens'; import * as Cell from './cell'; import { Flex } from './common/layout'; +import { Spacings } from './common/variables'; import { ContextMenu, ContextMenuContainer, diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx index 3541c095d6..6ad0a9e199 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx @@ -38,6 +38,7 @@ import TooManyDevices from './TooManyDevices'; import TransitionContainer, { TransitionView } from './TransitionContainer'; import UdpOverTcp from './UdpOverTcp'; import UserInterfaceSettings from './UserInterfaceSettings'; +import { AppInfoView, ChangelogView } from './views'; import VpnSettings from './VpnSettings'; import WireguardSettings from './WireguardSettings'; @@ -103,6 +104,8 @@ export default function AppRouter() { <Route exact path={RoutePath.selectLocation} component={SelectLocation} /> <Route exact path={RoutePath.editCustomBridge} component={EditCustomBridge} /> <Route exact path={RoutePath.filter} component={Filter} /> + <Route exact path={RoutePath.appInfo} component={AppInfoView} /> + <Route exact path={RoutePath.changelog} component={ChangelogView} /> </Switch> </TransitionView> </TransitionContainer> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Changelog.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Changelog.tsx deleted file mode 100644 index ea1b42f5c2..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Changelog.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { useCallback } from 'react'; -import styled from 'styled-components'; - -import { messages } from '../../shared/gettext'; -import { useAppContext } from '../context'; -import { useBoolean } from '../lib/utility-hooks'; -import { useSelector } from '../redux/store'; -import * as AppButton from './AppButton'; -import { hugeText, smallText } from './common-styles'; -import { ModalAlert, ModalMessage } from './Modal'; - -const StyledTitle = styled.h1(hugeText, { - textAlign: 'center', - margin: '7px 0 4px', -}); - -const StyledSubTitle = styled.span(smallText, { - marginTop: '10px', - fontWeight: 700, -}); - -const StyledList = styled.ul({ - listStyle: 'disc outside', - marginLeft: '20px', -}); - -const StyledMessage = styled(ModalMessage)({ - fontSize: '12px', - marginTop: '6px', -}); - -export function Changelog() { - const currentVersion = useSelector((state) => state.version.current); - const changelogDisplayedForVersion = useSelector( - (state) => state.settings.guiSettings.changelogDisplayedForVersion, - ); - const changelog = useSelector((state) => state.userInterface.changelog); - const initialForceShowChanges = useSelector((state) => state.userInterface.forceShowChanges); - - const { setDisplayedChangelog } = useAppContext(); - - const [forceShowChanges, , stopForceShowChanges] = useBoolean(initialForceShowChanges); - - const close = useCallback(() => { - setDisplayedChangelog(); - stopForceShowChanges(); - }, [setDisplayedChangelog, stopForceShowChanges]); - - const visible = - forceShowChanges || - (changelogDisplayedForVersion !== currentVersion && - changelog.length > 0 && - !window.env.development && - !/-dev-[0-9a-f]{6}$/.test(currentVersion)); - - return ( - <ModalAlert - isOpen={visible} - buttons={[ - <AppButton.BlueButton key="close" onClick={close}> - { - // TRANSLATORS: This is a button which closes a dialog. - messages.gettext('Got it!') - } - </AppButton.BlueButton>, - ]}> - <StyledTitle>{currentVersion}</StyledTitle> - <StyledSubTitle>{messages.pgettext('changelog', 'Changes in this version:')}</StyledSubTitle> - <StyledMessage> - <StyledList> - {changelog.map((item, i) => ( - <li key={i}>{item}</li> - ))} - </StyledList> - </StyledMessage> - </ModalAlert> - ); -} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/DaitaSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/DaitaSettings.tsx index 54b101d5fd..b3a88688e0 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/DaitaSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/DaitaSettings.tsx @@ -8,10 +8,10 @@ import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; import { useBoolean } from '../lib/utility-hooks'; import { useSelector } from '../redux/store'; -import { Spacings } from '../tokens'; import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import { Flex } from './common/layout'; +import { Spacings } from './common/variables'; import InfoButton from './InfoButton'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Debug.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Debug.tsx index 9522b422e5..a1aa849b50 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Debug.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Debug.tsx @@ -3,8 +3,8 @@ import styled from 'styled-components'; import { useHistory } from '../lib/history'; import { useBoolean } from '../lib/utility-hooks'; -import { Spacings } from '../tokens'; import * as AppButton from './AppButton'; +import { Spacings } from './common/variables'; import { measurements } from './common-styles'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx index a7ece12a7d..6145d902ce 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Layout.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; -import { Colors, Spacings } from '../tokens'; import { Flex } from './common/layout'; +import { Colors, Spacings } from './common/variables'; import { measurements } from './common-styles'; import HeaderBar from './HeaderBar'; import { NavigationScrollbars } from './NavigationBar'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/MultiButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/MultiButton.tsx index 3129abcbb2..ef002c023d 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/MultiButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/MultiButton.tsx @@ -1,42 +1,39 @@ import React from 'react'; import styled from 'styled-components'; -const SIDE_BUTTON_WIDTH = 44; +import { ButtonProps } from './common/molecules'; const ButtonRow = styled.div({ display: 'flex', - flexDirection: 'row', + gap: '1px', }); const MainButton = styled.button({ - display: 'flex', - flex: 1, borderTopRightRadius: 0, borderBottomRightRadius: 0, + paddingLeft: '44px', + '&:focus-visible': { + zIndex: 10, + }, }); const SideButton = styled.button({ - display: 'flex', borderTopLeftRadius: 0, borderBottomLeftRadius: 0, - width: SIDE_BUTTON_WIDTH, - marginLeft: '1px !important', + '&:focus-visible': { + zIndex: 10, + }, }); -export interface MultiButtonCompatibleProps { - className?: string; - textOffset?: number; -} - -interface IMultiButtonProps { - mainButton: React.ComponentType<MultiButtonCompatibleProps>; - sideButton: React.ComponentType<MultiButtonCompatibleProps>; +interface MultiButtonProps { + mainButton: React.ComponentType<ButtonProps>; + sideButton: React.ComponentType<ButtonProps>; } -export function MultiButton(props: IMultiButtonProps) { +export function MultiButton(props: MultiButtonProps) { return ( <ButtonRow> - <MainButton as={props.mainButton} textOffset={SIDE_BUTTON_WIDTH + 1} /> + <MainButton as={props.mainButton} /> <SideButton as={props.sideButton} /> </ButtonRow> ); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx index dac62db192..a63754a691 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx @@ -1,9 +1,7 @@ import { useCallback, useState } from 'react'; -import { useSelector } from 'react-redux'; import { messages } from '../../shared/gettext'; import log from '../../shared/logging'; -import { NewDeviceNotificationProvider } from '../../shared/notifications/new-device'; import { BlockWhenDisconnectedNotificationProvider, CloseToAccountExpiryNotificationProvider, @@ -16,15 +14,21 @@ import { ReconnectingNotificationProvider, UnsupportedVersionNotificationProvider, UpdateAvailableNotificationProvider, -} from '../../shared/notifications/notification'; +} from '../../shared/notifications'; import { useAppContext } from '../context'; import useActions from '../lib/actionsHook'; import { transitions, useHistory } from '../lib/history'; import { formatHtml } from '../lib/html-formatter'; +import { + NewDeviceNotificationProvider, + NewVersionNotificationProvider, +} from '../lib/notifications'; import { RoutePath } from '../lib/routes'; import accountActions from '../redux/account/actions'; -import { IReduxState } from '../redux/store'; +import { IReduxState, useSelector } from '../redux/store'; import * as AppButton from './AppButton'; +import { Link } from './common/text'; +import { Colors } from './common/variables'; import { ModalAlert, ModalAlertType, ModalMessage, ModalMessageList } from './Modal'; import { NotificationActions, @@ -59,6 +63,18 @@ export default function NotificationArea(props: IProps) { const { hideNewDeviceBanner } = useActions(accountActions); + const { setDisplayedChangelog } = useAppContext(); + + const currentVersion = useSelector((state) => state.version.current); + const displayedForVersion = useSelector( + (state) => state.settings.guiSettings.changelogDisplayedForVersion, + ); + const changelog = useSelector((state) => state.userInterface.changelog); + + const close = useCallback(() => { + setDisplayedChangelog(); + }, [setDisplayedChangelog]); + const notificationProviders: InAppNotificationProvider[] = [ new ConnectingNotificationProvider({ tunnelState }), new ReconnectingNotificationProvider(tunnelState), @@ -84,6 +100,12 @@ export default function NotificationArea(props: IProps) { deviceName: account.deviceName ?? '', close: hideNewDeviceBanner, }), + new NewVersionNotificationProvider({ + currentVersion, + displayedForVersion, + changelog, + close, + }), new UpdateAvailableNotificationProvider(version), ); @@ -106,7 +128,16 @@ export default function NotificationArea(props: IProps) { {notification.title} </NotificationTitle> <NotificationSubtitle data-testid="notificationSubTitle"> - {formatHtml(notification.subtitle ?? '')} + {notification.subtitleAction?.type === 'navigate' ? ( + <Link + variant="labelTiny" + color={Colors.white60} + {...notification.subtitleAction.link}> + {formatHtml(notification.subtitle ?? '')} + </Link> + ) : ( + formatHtml(notification.subtitle ?? '') + )} </NotificationSubtitle> </NotificationContent> {notification.action && <NotificationActionWrapper action={notification.action} />} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationBanner.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationBanner.tsx index 924f65ff99..12f9fbef6e 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationBanner.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationBanner.tsx @@ -6,6 +6,7 @@ import { messages } from '../../shared/gettext'; import { InAppNotificationIndicatorType } from '../../shared/notifications/notification'; import { useEffectEvent, useLastDefinedValue, useStyledRef } from '../lib/utility-hooks'; import * as AppButton from './AppButton'; +import { IconButton } from './common/molecules'; import { tinyText } from './common-styles'; import ImageView from './ImageView'; @@ -81,12 +82,14 @@ export function NotificationTroubleshootDialogAction(props: NotificationActionPr export function NotificationCloseAction(props: NotificationActionProps) { return ( - <NotificationActionButton + <IconButton aria-describedby={NOTIFICATION_AREA_ID} aria-label={messages.pgettext('accessibility', 'Close notification')} - onClick={props.onClick}> - <NotificationActionButtonInner source="icon-close" width={16} tintColor={colors.white60} /> - </NotificationActionButton> + onClick={props.onClick} + icon="icon-close" + size="small" + variant="secondary" + /> ); } diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx index 15f760941f..2294d74a7d 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx @@ -2,20 +2,17 @@ import { useCallback } from 'react'; import { strings } from '../../config.json'; import { messages } from '../../shared/gettext'; -import { getDownloadUrl } from '../../shared/version'; import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { useSelector } from '../redux/store'; -import { Colors } from '../tokens'; -import { RedButton } from './AppButton'; import * as Cell from './cell'; +import { Button } from './common/molecules'; import { TitleBig } from './common/text'; import { BackAction } from './KeyboardNavigation'; import { ButtonStack, Footer, - LabelStack, Layout, SettingsContainer, SettingsContent, @@ -86,7 +83,7 @@ export default function Support() { <SettingsGroup> <SupportButton /> - <AppVersionButton /> + <AppInfoButton /> </SettingsGroup> {window.env.development && ( @@ -204,59 +201,16 @@ function ApiAccessMethodsButton() { ); } -function AppVersionButton() { +function AppInfoButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.appInfo), [history]); const appVersion = useSelector((state) => state.version.current); - const consistentVersion = useSelector((state) => state.version.consistent); - const upToDateVersion = useSelector((state) => (state.version.suggestedUpgrade ? false : true)); - const suggestedIsBeta = useSelector((state) => state.version.suggestedIsBeta ?? false); - const isOffline = useSelector((state) => state.connection.isBlocked); - - const { openUrl } = useAppContext(); - const openDownloadLink = useCallback( - () => openUrl(getDownloadUrl(suggestedIsBeta)), - [openUrl, suggestedIsBeta], - ); - - let alertIcon; - let footer; - if (!consistentVersion || !upToDateVersion) { - const inconsistentVersionMessage = messages.pgettext( - 'settings-view', - 'App is out of sync. Please quit and restart.', - ); - - const updateAvailableMessage = messages.pgettext( - 'settings-view', - 'Update available. Install the latest app version to stay up to date.', - ); - - const message = !consistentVersion ? inconsistentVersionMessage : updateAvailableMessage; - - alertIcon = <Cell.UntintedIcon source="icon-alert" width={18} tintColor={Colors.red} />; - footer = ( - <Cell.CellFooter> - <Cell.CellFooterText>{message}</Cell.CellFooterText> - </Cell.CellFooter> - ); - } return ( - <> - <Cell.CellNavigationButton - disabled={isOffline} - onClick={openDownloadLink} - icon={{ - source: 'icon-extLink', - 'aria-label': messages.pgettext('accessibility', 'Opens externally'), - }}> - <LabelStack> - {alertIcon} - <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label> - </LabelStack> - <Cell.SubText>{appVersion}</Cell.SubText> - </Cell.CellNavigationButton> - {footer} - </> + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label>{messages.pgettext('settings-view', 'App info')}</Cell.Label> + <Cell.SubText>{appVersion}</Cell.SubText> + </Cell.CellNavigationButton> ); } @@ -287,10 +241,10 @@ function QuitButton() { const tunnelState = useSelector((state) => state.connection.status); return ( - <RedButton onClick={quit}> + <Button variant="destructive" onClick={quit}> {tunnelState.state === 'disconnected' ? messages.gettext('Quit') : messages.gettext('Disconnect & quit')} - </RedButton> + </Button> ); } diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SettingsHeader.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SettingsHeader.tsx index deac04b26b..adb8357ac8 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SettingsHeader.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SettingsHeader.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import styled from 'styled-components'; -import { Colors, Spacings } from '../tokens'; import { Flex } from './common/layout'; import { LabelTiny, TitleBig } from './common/text'; +import { Colors, Spacings } from './common/variables'; export const HeaderTitle = styled(TitleBig)({ wordWrap: 'break-word', @@ -11,7 +11,7 @@ export const HeaderTitle = styled(TitleBig)({ }); export const HeaderSubTitle = styled(LabelTiny).attrs({ - $color: Colors.white60, + color: Colors.white60, })({}); interface SettingsHeaderProps { diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SmallButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SmallButton.tsx index c91fbbdb20..b9ce4a6a59 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SmallButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SmallButton.tsx @@ -3,7 +3,6 @@ import styled from 'styled-components'; import { colors } from '../../config.json'; import { smallText } from './common-styles'; -import { MultiButtonCompatibleProps } from './MultiButton'; export enum SmallButtonColor { blue, @@ -84,6 +83,11 @@ const StyledTextOffset = styled.span<{ $width: number }>((props) => ({ flex: `0 1 ${props.$width}px`, })); +export interface MultiButtonCompatibleProps { + className?: string; + textOffset?: number; +} + interface SmallButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'color'>, MultiButtonCompatibleProps { diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx index b2e25c9e63..97254d5e3f 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx @@ -14,12 +14,12 @@ import { useHistory } from '../lib/history'; import { formatHtml } from '../lib/html-formatter'; import { useEffectEvent, useStyledRef } from '../lib/utility-hooks'; import { IReduxState } from '../redux/store'; -import { Colors, Spacings } from '../tokens'; import Accordion from './Accordion'; import * as AppButton from './AppButton'; import * as Cell from './cell'; import { Flex } from './common/layout/Flex'; import { FootnoteMini } from './common/text/FootnoteMini'; +import { Colors, Spacings } from './common/variables'; import { CustomScrollbarsRef } from './CustomScrollbars'; import ImageView from './ImageView'; import { BackAction } from './KeyboardNavigation'; @@ -551,7 +551,7 @@ function MacOsSplitTunnelingAvailability({ <WideSmallButton onClick={showFullDiskAccessSettings}> {messages.pgettext('split-tunneling-view', 'Open System Settings')} </WideSmallButton> - <FootnoteMini $color={Colors.white60}> + <FootnoteMini color={Colors.white60}> {messages.pgettext( 'split-tunneling-view', 'Enabled "Full disk access" and still having issues?', diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx index 84764439ef..5423c8a212 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx @@ -1,9 +1,9 @@ import styled from 'styled-components'; import { colors } from '../../config.json'; -import { Spacings } from '../tokens'; import * as AppButton from './AppButton'; import * as Cell from './cell'; +import { Spacings } from './common/variables'; import { measurements, normalText } from './common-styles'; import ImageView from './ImageView'; import { NavigationScrollbars } from './NavigationBar'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/VpnSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/VpnSettings.tsx index c27072cab2..7c58f43ab0 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/VpnSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/VpnSettings.tsx @@ -15,11 +15,11 @@ import { RoutePath } from '../lib/routes'; import { useBoolean } from '../lib/utility-hooks'; import { RelaySettingsRedux } from '../redux/settings/reducers'; import { useSelector } from '../redux/store'; -import { Colors, Spacings } from '../tokens'; 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 { Colors, Spacings } from './common/variables'; import CustomDnsSettings from './CustomDnsSettings'; import InfoButton, { InfoIcon } from './InfoButton'; import { BackAction } from './KeyboardNavigation'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/cell/CellButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/cell/CellButton.tsx index f637008a68..8f0336c13c 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/cell/CellButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/cell/CellButton.tsx @@ -1,8 +1,8 @@ import React, { useContext } from 'react'; import styled from 'styled-components'; -import { Colors, Spacings } from '../../tokens'; import { Center } from '../common/layout'; +import { Colors, Spacings } from '../common/variables'; import { IImageViewProps } from '../ImageView'; import { CellDisabledContext } from './Container'; import { Icon } from './Label'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Container.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Container.tsx index f8db1c45e6..551b5d7c20 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Container.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Container.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styled from 'styled-components'; -import { Spacings } from '../../tokens'; +import { Spacings } from '../common/variables'; import { Row } from './Row'; const StyledContainer = styled(Row)({ diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Footer.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Footer.tsx index 2454225638..058c96db82 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Footer.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Footer.tsx @@ -1,12 +1,12 @@ import styled from 'styled-components'; -import { Colors, Spacings } from '../../tokens'; import { LabelTiny } from '../common/text'; +import { Colors, Spacings } from '../common/variables'; export const CellFooter = styled.div({ margin: `${Spacings.spacing1} ${Spacings.spacing6} 0px`, }); export const CellFooterText = styled(LabelTiny).attrs({ - $color: Colors.white60, + color: Colors.white60, })({}); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Label.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Label.tsx index 3123a31dc4..5bce2c71af 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Label.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Label.tsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import styled from 'styled-components'; -import { Colors, Spacings } from '../../tokens'; +import { Colors, Spacings } from '../common/variables'; import { buttonText, normalText, tinyText } from '../common-styles'; import ImageView, { IImageViewProps } from '../ImageView'; import { CellButton } from './CellButton'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Section.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Section.tsx index 0e390eb901..002b3ac478 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/cell/Section.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/cell/Section.tsx @@ -4,9 +4,9 @@ import styled from 'styled-components'; import { useAppContext } from '../../context'; import { useHistory } from '../../lib/history'; import { useBoolean, useEffectEvent } from '../../lib/utility-hooks'; -import { Colors, Spacings } from '../../tokens'; import Accordion from '../Accordion'; import ChevronButton from '../ChevronButton'; +import { Colors, Spacings } from '../common/variables'; import { buttonText, openSans, sourceSansPro } from '../common-styles'; import { Container } from './Container'; import { Row } from './Row'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common-styles.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common-styles.ts index fc08d8411d..51ddba958f 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common-styles.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common-styles.ts @@ -1,6 +1,6 @@ import React from 'react'; -import { Colors, Spacings } from '../tokens'; +import { Colors, Spacings } from './common/variables'; export const openSans: React.CSSProperties = { fontFamily: 'Open Sans', diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Container.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Container.tsx new file mode 100644 index 0000000000..79e8ed316a --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Container.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import styled from 'styled-components'; + +import { Spacings } from '../variables'; +import { Flex, FlexProps } from './Flex'; + +export interface ContainerProps extends FlexProps { + size?: '3' | '4'; + children: React.ReactNode; +} + +const sizes: Record<'3' | '4', string> = { + '3': `calc(100% - ${Spacings.spacing6} * 2)`, + '4': `calc(100% - ${Spacings.spacing5} * 2)`, +}; + +const StyledFlex = styled(Flex)<{ $size: string }>((props) => ({ + width: props.$size, + margin: 'auto', +})); + +export const Container = React.forwardRef<HTMLDivElement, ContainerProps>( + ({ size = '4', ...props }, ref) => { + return <StyledFlex ref={ref} $size={sizes[size]} {...props} />; + }, +); + +Container.displayName = 'Container'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Flex.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Flex.tsx index ede836194b..13a4804fa9 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Flex.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Flex.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styled from 'styled-components'; -import { Spacings } from '../../../tokens'; +import { Spacings } from '../variables'; import { Layout, LayoutProps } from './Layout'; export interface FlexProps extends LayoutProps { @@ -12,11 +12,21 @@ export interface FlexProps extends LayoutProps { $justifyContent?: React.CSSProperties['justifyContent']; $flexGrow?: React.CSSProperties['flexGrow']; $flexShrink?: React.CSSProperties['flexShrink']; + $flexBasis?: React.CSSProperties['flexBasis']; children?: React.ReactNode; } export const Flex = styled(Layout)<FlexProps>( - ({ $gap, $flex, $flexDirection, $alignItems, $justifyContent, $flexGrow, $flexShrink }) => ({ + ({ + $gap, + $flex, + $flexDirection, + $alignItems, + $justifyContent, + $flexGrow, + $flexShrink, + $flexBasis, + }) => ({ display: 'flex', gap: $gap, flex: $flex, @@ -25,5 +35,6 @@ export const Flex = styled(Layout)<FlexProps>( justifyContent: $justifyContent, flexGrow: $flexGrow, flexShrink: $flexShrink, + flexBasis: $flexBasis, }), ); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Layout.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Layout.tsx index fe1dfbb94e..03e4d5371d 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Layout.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/Layout.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components'; -import { Spacings } from '../../../tokens'; +import { Spacings } from '../variables'; import { margin } from './margin'; import { padding } from './padding'; import { LayoutSpacings } from './types'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/margin.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/margin.ts index c410e47c34..bcc32fcaee 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/margin.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/margin.ts @@ -1,4 +1,4 @@ -import { Spacings } from '../../../tokens'; +import { Spacings } from '../variables'; import { LayoutSpacings } from './types'; export const all = (margin: Spacings) => ({ margin }); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/padding.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/padding.ts index d98a209203..16c66f6e64 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/padding.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/padding.ts @@ -1,4 +1,4 @@ -import { Spacings } from '../../../tokens'; +import { Spacings } from '../variables'; import { LayoutSpacings } from './types'; export const all = (padding: Spacings) => ({ padding }); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/types.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/types.ts index f31c458917..1f6ff5407f 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/types.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/layout/types.ts @@ -1,4 +1,4 @@ -import { Spacings } from '../../../tokens'; +import { Spacings } from '../variables'; export interface LayoutSpacings { all?: 'string'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/mixins/button-reset.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/mixins/button-reset.ts new file mode 100644 index 0000000000..4ef5844150 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/mixins/button-reset.ts @@ -0,0 +1,10 @@ +export const buttonReset = { + border: 'none', + padding: 0, + margin: 0, + font: 'inherit', + color: 'inherit', + textAlign: 'inherit', + lineHeight: 'inherit', + cursor: 'default', +} as React.CSSProperties; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/mixins/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/mixins/index.ts new file mode 100644 index 0000000000..c546f737b6 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/mixins/index.ts @@ -0,0 +1 @@ +export * from './button-reset'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/Button.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/Button.tsx new file mode 100644 index 0000000000..ca8ff74883 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/Button.tsx @@ -0,0 +1,105 @@ +import React, { forwardRef } from 'react'; +import styled from 'styled-components'; + +import { Flex } from '../layout'; +import { buttonReset } from '../mixins'; +import { BodySmallSemiBold } from '../text'; +import { Colors, Radius, Spacings } from '../variables'; + +export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { + variant?: 'primary' | 'success' | 'destructive'; + size?: 'tiny' | 'small' | 'regular' | 'full' | '1/2'; + leading?: React.ReactNode; + trailing?: React.ReactNode; +} + +const variants = { + primary: { + background: Colors.blue, + hover: Colors.blue60, + disabled: Colors.blue50, + }, + success: { + background: Colors.green, + hover: Colors.green90, + disabled: Colors.green40, + }, + destructive: { + background: Colors.red, + hover: Colors.red80, + disabled: Colors.red60, + }, +} as const; + +const sizes = { + tiny: '44px', + small: '60px', + regular: '272px', + full: '100%', + '1/2': '50%', +}; + +const StyledButton = styled.button({ + ...buttonReset, + + minHeight: '32px', + borderRadius: Radius.radius4, + width: 'var(--size)', + background: 'var(--background)', + '&:not(:disabled):hover': { + background: 'var(--hover)', + }, + '&:disabled': { + background: 'var(--disabled)', + }, + '&:focus-visible': { + outline: `2px solid ${Colors.white}`, + outlineOffset: '2px', + }, +}); + +export const Button = forwardRef<HTMLButtonElement, ButtonProps>( + ( + { variant = 'primary', size = 'regular', leading, trailing, children, disabled, ...props }, + ref, + ) => { + const styles = variants[variant]; + return ( + <StyledButton + ref={ref} + style={ + { + '--background': styles.background, + '--hover': styles.hover, + '--disabled': styles.disabled, + '--size': sizes[size], + } as React.CSSProperties + } + disabled={disabled} + {...props}> + <Flex + $flex={1} + $gap={Spacings.spacing3} + $justifyContent="space-between" + $padding={{ + horizontal: Spacings.spacing3, + }} + $alignItems="center"> + {leading} + <Flex $flex={1} $justifyContent="center" $alignItems="center"> + {typeof children === 'string' ? ( + <BodySmallSemiBold color={disabled ? Colors.white40 : Colors.white}> + {children} + </BodySmallSemiBold> + ) : ( + children + )} + </Flex> + {trailing} + </Flex> + </StyledButton> + ); + }, +); + +Button.displayName = 'Button'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/IconButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/IconButton.tsx new file mode 100644 index 0000000000..6d43fef048 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/IconButton.tsx @@ -0,0 +1,65 @@ +import React, { forwardRef } from 'react'; +import styled from 'styled-components'; + +import ImageView from '../../ImageView'; +import { buttonReset } from '../mixins'; +import { Colors } from '../variables'; + +export interface IconButtonProps + extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> { + variant?: 'primary' | 'secondary'; + size?: 'small' | 'regular'; + icon: string; +} + +const variants = { + primary: { + background: Colors.white, + hover: Colors.white60, + disabled: Colors.white50, + }, + secondary: { + background: Colors.white60, + hover: Colors.white80, + disabled: Colors.white50, + }, +} as const; + +const sizes = { + small: 16, + regular: 24, +}; + +const StyledButton = styled.button({ + ...buttonReset, + + background: 'transparent', + height: 'var(--size)', + width: 'var(--size)', + '&:focus-visible': { + outline: `2px solid ${Colors.white}`, + outlineOffset: '2px', + borderRadius: '100%', + }, +}); + +export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>( + ({ icon, variant = 'primary', size: sizeProp = 'regular', disabled, ...props }, ref) => { + const styles = variants[variant]; + const size = sizes[sizeProp]; + return ( + <StyledButton ref={ref} disabled={disabled} {...props}> + <ImageView + source={icon} + tintColor={styles.background} + tintHoverColor={styles.hover} + disabled={disabled} + height={size} + width={size} + /> + </StyledButton> + ); + }, +); + +IconButton.displayName = 'Button'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/index.ts new file mode 100644 index 0000000000..66f0cf90a7 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/molecules/index.ts @@ -0,0 +1,2 @@ +export * from './Button'; +export * from './IconButton'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/BodySmall.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/BodySmall.tsx index 9cfe1bc9c0..18ab10e202 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/BodySmall.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/BodySmall.tsx @@ -1,8 +1,9 @@ -import styled from 'styled-components'; +import { Text, TextProps } from './Text'; -import { typography } from '../../../tokens'; -import { Text } from './Text'; +export type BodySmallProps = Omit<TextProps, 'variant'>; -export const BodySmall = styled(Text)({ - ...typography['bodySmall'], -}); +export const BodySmall = ({ children, ...props }: BodySmallProps) => ( + <Text variant="bodySmall" {...props}> + {children} + </Text> +); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/BodySmallSemiBold.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/BodySmallSemiBold.tsx index 705a939e5d..f70a2c7b11 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/BodySmallSemiBold.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/BodySmallSemiBold.tsx @@ -1,8 +1,9 @@ -import styled from 'styled-components'; +import { Text, TextProps } from './Text'; -import { typography } from '../../../tokens'; -import { Text } from './Text'; +export type BodySmallSemiBoldProps = Omit<TextProps, 'variant'>; -export const BodySmallSemiBold = styled(Text)({ - ...typography['bodySmallSemibold'], -}); +export const BodySmallSemiBold = ({ children, ...props }: BodySmallSemiBoldProps) => ( + <Text variant="bodySmallSemibold" {...props}> + {children} + </Text> +); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/FootnoteMini.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/FootnoteMini.tsx index adbafb90d1..77d563e03c 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/FootnoteMini.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/FootnoteMini.tsx @@ -1,8 +1,9 @@ -import styled from 'styled-components'; +import { Text, TextProps } from './Text'; -import { typography } from '../../../tokens'; -import { Text } from './Text'; +export type FoonoteMiniProps = Omit<TextProps, 'variant'>; -export const FootnoteMini = styled(Text)({ - ...typography['footnoteMini'], -}); +export const FootnoteMini = ({ children, ...props }: FoonoteMiniProps) => ( + <Text variant="footnoteMini" {...props}> + {children} + </Text> +); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/LabelTiny.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/LabelTiny.tsx index 9f6f8a143c..f7a952e67e 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/LabelTiny.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/LabelTiny.tsx @@ -1,8 +1,8 @@ -import styled from 'styled-components'; +import { Text, TextProps } from './Text'; +export type LabelTinyProps = Omit<TextProps, 'variant'>; -import { typography } from '../../../tokens'; -import { Text } from './Text'; - -export const LabelTiny = styled(Text)({ - ...typography['labelTiny'], -}); +export const LabelTiny = ({ children, ...props }: LabelTinyProps) => ( + <Text variant="labelTiny" {...props}> + {children} + </Text> +); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/Link.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/Link.tsx new file mode 100644 index 0000000000..e1bb272120 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/Link.tsx @@ -0,0 +1,62 @@ +import React, { useCallback } from 'react'; +import styled from 'styled-components'; + +import { useHistory } from '../../../lib/history'; +import { RoutePath } from '../../../lib/routes'; +import { buttonReset } from '../mixins'; +import { Colors, Radius } from '../variables'; +import { Text, TextProps } from './Text'; + +export interface LinkProps extends TextProps, Omit<React.HtmlHTMLAttributes<'button'>, 'color'> { + to: RoutePath; +} + +const StyledText = styled(Text)<{ + $hoverColor: Colors | undefined; +}>((props) => ({ + ...buttonReset, + background: 'transparent', + + '&:hover': { + textDecorationLine: 'underline', + textUnderlineOffset: '2px', + color: props.$hoverColor, + }, + '&:focus-visible': { + borderRadius: Radius.radius4, + outline: `2px solid ${Colors.white}`, + outlineOffset: '2px', + }, +})); + +const getHoverColor = (color: Colors | undefined) => { + switch (color) { + case Colors.white60: + return Colors.white; + default: + return undefined; + } +}; + +export const Link = ({ to, children, color, onClick, ...props }: LinkProps) => { + const history = useHistory(); + const navigate = useCallback( + (e: React.MouseEvent<'button'>) => { + if (onClick) { + onClick(e); + } + return history.push(to); + }, + [history, to, onClick], + ); + return ( + <StyledText + onClick={navigate} + as={'button'} + color={color} + $hoverColor={getHoverColor(color)} + {...props}> + {children} + </StyledText> + ); +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/Text.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/Text.tsx index f4b32d988d..33b0cdc6d9 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/Text.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/Text.tsx @@ -1,11 +1,45 @@ +import { forwardRef } from 'react'; import styled from 'styled-components'; -import { Colors } from '../../../tokens'; +import { TransientProps } from '../../../lib/styles'; +import { Colors, Typography, typography, TypographyProperties } from '../variables'; -interface TextProps { - $color?: Colors; -} +export type TextProps = React.PropsWithChildren<{ + variant?: Typography; + color?: Colors; + as?: React.ElementType; + style?: React.CSSProperties; +}>; -export const Text = styled.div<TextProps>(({ $color }) => ({ - color: $color ? $color : Colors.white, +const StyledText = styled.span<TransientProps<TypographyProperties>>((props) => ({ + color: 'var(--color)', + fontFamily: props.$fontFamily, + fontWeight: props.$fontWeight, + fontSize: props.$fontSize, + lineHeight: props.$lineHeight, })); + +export const Text = forwardRef( + ({ variant = 'bodySmall', color = Colors.white, children, style, ...props }: TextProps, ref) => { + const { fontFamily, fontSize, fontWeight, lineHeight } = typography[variant]; + return ( + <StyledText + ref={ref} + style={ + { + '--color': color, + ...style, + } as React.CSSProperties + } + $fontFamily={fontFamily} + $fontWeight={fontWeight} + $fontSize={fontSize} + $lineHeight={lineHeight} + {...props}> + {children} + </StyledText> + ); + }, +); + +Text.displayName = 'Text'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleBig.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleBig.tsx index 16d76c04ef..4b3b1d84ef 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleBig.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleBig.tsx @@ -1,8 +1,8 @@ -import styled from 'styled-components'; +import { Text, TextProps } from './Text'; +export type TitleBigProps = Omit<TextProps, 'variant'>; -import { typography } from '../../../tokens'; -import { Text } from './Text'; - -export const TitleBig = styled(Text)({ - ...typography['titleBig'], -}); +export const TitleBig = ({ children, ...props }: TitleBigProps) => ( + <Text variant="titleBig" {...props}> + {children} + </Text> +); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleLarge.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleLarge.tsx index e939b099fc..c80814362e 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleLarge.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleLarge.tsx @@ -1,7 +1,8 @@ -import styled from 'styled-components'; +import { Text, TextProps } from './Text'; +export type TitleLargeProps = Omit<TextProps, 'variant'>; -import { typography } from '../../../tokens'; -import { Text } from './Text'; -export const TitleLarge = styled(Text)({ - ...typography['titleLarge'], -}); +export const TitleLarge = ({ children, ...props }: TitleLargeProps) => ( + <Text variant="titleLarge" {...props}> + {children} + </Text> +); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleMedium.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleMedium.tsx index 578d435ef3..5e188698a4 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleMedium.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/TitleMedium.tsx @@ -1,8 +1,8 @@ -import styled from 'styled-components'; +import { Text, TextProps } from './Text'; +export type TitleMediumProps = Omit<TextProps, 'variant'>; -import { typography } from '../../../tokens'; -import { Text } from './Text'; - -export const TitleMedium = styled(Text)({ - ...typography['titleMedium'], -}); +export const TitleMedium = ({ children, ...props }: TitleMediumProps) => ( + <Text variant="titleMedium" {...props}> + {children} + </Text> +); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/index.ts index 56db1927fc..542c9d5642 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/common/text/index.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/text/index.ts @@ -6,3 +6,4 @@ export * from './Text'; export * from './TitleBig'; export * from './TitleLarge'; export * from './TitleMedium'; +export * from './Link'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/VariablesGlobalStyle.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/VariablesGlobalStyle.tsx new file mode 100644 index 0000000000..fbd7487dc5 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/VariablesGlobalStyle.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { createGlobalStyle } from 'styled-components'; + +import { colors, fontFamilies, fontSizes, fontWeights, lineHeights, radius, spacings } from './'; + +type VariablesProps = React.PropsWithChildren<object>; + +const GlobalStyle = createGlobalStyle` + :root { + ${Object.entries({ + ...colors, + ...spacings, + ...radius, + ...fontFamilies, + ...fontSizes, + ...fontWeights, + ...lineHeights, + }).reduce((styleString, [key, value]) => ({ ...styleString, [key]: value }), {})} + } +`; + +export const VariablesGlobalStyle = ({ children }: VariablesProps) => { + return ( + <> + <GlobalStyle /> + {children} + </> + ); +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/color-variables.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/color-variables.ts new file mode 100644 index 0000000000..c048456544 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/color-variables.ts @@ -0,0 +1,67 @@ +import { ColorTokens } from '../../../tokens'; + +export const colors = { + '--color-darker-blue': ColorTokens.darkBlue, + '--color-dark-blue': ColorTokens.darkBlue, + '--color-blue': ColorTokens.blue, + '--color-dark-green': ColorTokens.darkGreen, + '--color-green': ColorTokens.green, + '--color-red': ColorTokens.red, + '--color-darkYellow': ColorTokens.darkYellow, + '--color-yellow': ColorTokens.yellow, + '--color-black': ColorTokens.black, + '--color-white': ColorTokens.white, + '--color-white-90': ColorTokens.white90, + '--color-white-80': ColorTokens.white80, + '--color-white-60': ColorTokens.white60, + '--color-white-50': ColorTokens.white50, + '--color-white-40': ColorTokens.white40, + '--color-white-20': ColorTokens.white20, + '--color-white-10': ColorTokens.white10, + '--color-blue10': ColorTokens.blue10, + '--color-blue-20': ColorTokens.blue20, + '--color-blue-40': ColorTokens.blue40, + '--color-blue-50': ColorTokens.blue50, + '--color-blue-60': ColorTokens.blue60, + '--color-blue-80': ColorTokens.blue80, + '--color-red-95': ColorTokens.red95, + '--color-red-80': ColorTokens.red80, + '--color-red-60': ColorTokens.red60, + '--color-red-45': ColorTokens.red45, + '--color-red-40': ColorTokens.red40, + '--color-green-90': ColorTokens.green90, + '--color-green-40': ColorTokens.green40, +}; + +export enum Colors { + darkerBlue = 'var(--color-darker-blue)', + darkBlue = 'var(--color-dark-blue)', + blue = 'var(--color-blue)', + darkGreen = 'var(--color-dark-green)', + green = 'var(--color-green)', + red = 'var(--color-red)', + darkYellow = 'var(--color-dark-yellow)', + yellow = 'var(--color-yellow)', + black = 'var(--color-black)', + white = 'var(--color-white)', + white90 = 'var(--color-white-90)', + white80 = 'var(--color-white-80)', + white60 = 'var(--color-white-60)', + white50 = 'var(--color-white-50)', + white40 = 'var(--color-white-40)', + white20 = 'var(--color-white-20)', + white10 = 'var(--color-white-10)', + blue10 = 'var(--color-blue-10)', + blue20 = 'var(--color-blue-20)', + blue40 = 'var(--color-blue-40)', + blue50 = 'var(--color-blue-50)', + blue60 = 'var(--color-blue-60)', + blue80 = 'var(--color-blue-80)', + red95 = 'var(--color-red-95)', + red80 = 'var(--color-red-80)', + red60 = 'var(--color-red-60)', + red45 = 'var(--color-red-45)', + red40 = 'var(--color-red-40)', + green90 = 'var(--color-green-90)', + green40 = 'var(--color-green-40)', +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/index.ts new file mode 100644 index 0000000000..3cabd68ee7 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/index.ts @@ -0,0 +1,5 @@ +export * from './color-variables'; +export * from './radius-variables'; +export * from './typography-variables'; +export * from './spacing-variables'; +export * from './VariablesGlobalStyle'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/radius-variables.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/radius-variables.ts new file mode 100644 index 0000000000..78ea604156 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/radius-variables.ts @@ -0,0 +1,15 @@ +import { RadiusTokens } from '../../../tokens'; + +export const radius = { + '--radius-4': RadiusTokens.radius4, + '--radius-8': RadiusTokens.radius8, + '--radius-11': RadiusTokens.radius11, + '--radius-12': RadiusTokens.radius12, +}; + +export enum Radius { + radius4 = 'var(--radius-4)', + radius8 = 'var(--radius-8)', + radius11 = 'var(--radius-11)', + radius12 = 'var(--radius-12)', +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/spacing-variables.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/spacing-variables.ts new file mode 100644 index 0000000000..b6929f070d --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/spacing-variables.ts @@ -0,0 +1,33 @@ +import { SpacingTokens } from '../../../tokens'; + +export const spacings = { + '--spacing-1': SpacingTokens.spacing1, + '--spacing-2': SpacingTokens.spacing2, + '--spacing-3': SpacingTokens.spacing3, + '--spacing-4': SpacingTokens.spacing4, + '--spacing-5': SpacingTokens.spacing5, + '--spacing-6': SpacingTokens.spacing6, + '--spacing-7': SpacingTokens.spacing7, + '--spacing-8': SpacingTokens.spacing8, + '--spacing-9': SpacingTokens.spacing9, + '--spacing-10': SpacingTokens.spacing10, + '--spacing-11': SpacingTokens.spacing11, + '--spacing-12': SpacingTokens.spacing12, + '--spacing-13': SpacingTokens.spacing13, +}; + +export enum Spacings { + spacing1 = 'var(--spacing-1)', + spacing2 = 'var(--spacing-2)', + spacing3 = 'var(--spacing-3)', + spacing4 = 'var(--spacing-4)', + spacing5 = 'var(--spacing-5)', + spacing6 = 'var(--spacing-6)', + spacing7 = 'var(--spacing-7)', + spacing8 = 'var(--spacing-8)', + spacing9 = 'var(--spacing-9)', + spacing10 = 'var(--spacing-10)', + spacing11 = 'var(--spacing-11)', + spacing12 = 'var(--spacing-12)', + spacing13 = 'var(--spacing-13)', +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/typography-variables.ts b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/typography-variables.ts new file mode 100644 index 0000000000..68f6530c26 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/common/variables/typography-variables.ts @@ -0,0 +1,132 @@ +import { + FontFamilyTokens, + FontSizeTokens, + FontWeightTokens, + LineHeightTokens, +} from '../../../tokens'; + +export const fontFamilies = { + '--font-family-open-sans': FontFamilyTokens.openSans, + '--font-family-source-sans-pro': FontFamilyTokens.sourceSansPro, +}; + +export enum FontFamilies { + openSans = 'var(--font-family-open-sans)', + sourceSansPro = 'var(--font-family-source-sans-pro)', +} + +export const fontWeights = { + '--font-weight-regular': FontWeightTokens.regular, + '--font-weight-semi-bold': FontWeightTokens.semiBold, + '--font-weight-bold': FontWeightTokens.bold, +}; + +export enum FontWeights { + regular = 'var(--font-weight-regular)', + semiBold = 'var(--font-weight-semi-bold)', + bold = 'var(--font-weight-bold)', +} + +export const fontSizes = { + '--font-size-big': FontSizeTokens.big, + '--font-size-large': FontSizeTokens.large, + '--font-size-medium': FontSizeTokens.medium, + '--font-size-small': FontSizeTokens.small, + '--font-size-tiny': FontSizeTokens.tiny, + '--font-size-mini': FontSizeTokens.mini, +}; + +export enum FontSizes { + big = 'var(--font-size-big)', + large = 'var(--font-size-large)', + medium = 'var(--font-size-medium)', + small = 'var(--font-size-small)', + tiny = 'var(--font-size-tiny)', + mini = 'var(--font-size-mini)', +} + +export const lineHeights = { + '--line-height-big': LineHeightTokens.big, + '--line-height-large': LineHeightTokens.large, + '--line-height-medium': LineHeightTokens.medium, + '--line-height-small': LineHeightTokens.small, + '--line-height-tiny': LineHeightTokens.tiny, + '--line-height-mini': LineHeightTokens.mini, +}; + +export enum LineHeights { + big = 'var(--line-height-big)', + large = 'var(--line-height-large)', + medium = 'var(--line-height-medium)', + small = 'var(--line-height-small)', + tiny = 'var(--line-height-tiny)', + mini = 'var(--line-height-mini)', +} + +export enum Fonts { + title = FontFamilies.sourceSansPro, + body = FontFamilies.openSans, + label = FontFamilies.openSans, + footnote = FontFamilies.openSans, +} + +export type Typography = + | 'titleBig' + | 'titleLarge' + | 'titleMedium' + | 'bodySmall' + | 'bodySmallSemibold' + | 'labelTiny' + | 'footnoteMini'; + +export interface TypographyProperties { + fontFamily: React.CSSProperties['fontFamily']; + fontSize: React.CSSProperties['fontSize']; + fontWeight: React.CSSProperties['fontWeight']; + lineHeight: React.CSSProperties['lineHeight']; +} + +export const typography: Record<Typography, TypographyProperties> = { + titleBig: { + fontFamily: Fonts.title, + fontWeight: FontWeights.bold, + fontSize: FontSizes.big, + lineHeight: LineHeights.big, + }, + titleLarge: { + fontFamily: Fonts.title, + fontWeight: FontWeights.bold, + fontSize: FontSizes.large, + lineHeight: LineHeights.large, + }, + titleMedium: { + fontFamily: Fonts.title, + fontWeight: FontWeights.semiBold, + fontSize: FontSizes.medium, + lineHeight: LineHeights.medium, + }, + bodySmall: { + fontFamily: Fonts.body, + fontWeight: FontWeights.regular, + fontSize: FontSizes.small, + lineHeight: LineHeights.small, + }, + bodySmallSemibold: { + fontFamily: Fonts.body, + fontWeight: FontWeights.semiBold, + fontSize: FontSizes.small, + lineHeight: LineHeights.small, + }, + labelTiny: { + fontFamily: Fonts.label, + fontWeight: FontWeights.semiBold, + fontSize: FontSizes.tiny, + lineHeight: LineHeights.tiny, + }, + footnoteMini: { + fontFamily: Fonts.footnote, + fontWeight: FontWeights.semiBold, + fontSize: FontSizes.mini, + lineHeight: LineHeights.mini, + }, +} as const; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/ConnectionActionButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/ConnectionActionButton.tsx index 437fa93321..a0126bcd1d 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/ConnectionActionButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/ConnectionActionButton.tsx @@ -1,15 +1,10 @@ import { useCallback } from 'react'; -import styled from 'styled-components'; import { messages } from '../../../shared/gettext'; import log from '../../../shared/logging'; import { useAppContext } from '../../context'; import { useSelector } from '../../redux/store'; -import { SmallButton, SmallButtonColor } from '../SmallButton'; - -const StyledConnectionButton = styled(SmallButton)({ - margin: 0, -}); +import { Button } from '../common/molecules'; export default function ConnectionActionButton() { const tunnelState = useSelector((state) => state.connection.status.state); @@ -21,7 +16,7 @@ export default function ConnectionActionButton() { } } -function ConnectButton(props: Partial<Parameters<typeof SmallButton>[0]>) { +function ConnectButton(props: Partial<Parameters<typeof Button>[0]>) { const { connectTunnel } = useAppContext(); const onConnect = useCallback(async () => { @@ -34,9 +29,9 @@ function ConnectButton(props: Partial<Parameters<typeof SmallButton>[0]>) { }, [connectTunnel]); return ( - <StyledConnectionButton color={SmallButtonColor.green} onClick={onConnect} {...props}> + <Button variant="success" size="full" onClick={onConnect} {...props}> {messages.pgettext('tunnel-control', 'Connect')} - </StyledConnectionButton> + </Button> ); } @@ -56,8 +51,8 @@ function DisconnectButton() { const displayAsCancel = tunnelState !== 'connected'; return ( - <StyledConnectionButton color={SmallButtonColor.red} onClick={onDisconnect}> + <Button variant="destructive" size="full" onClick={onDisconnect}> {displayAsCancel ? messages.gettext('Cancel') : messages.gettext('Disconnect')} - </StyledConnectionButton> + </Button> ); } diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/SelectLocationButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/SelectLocationButton.tsx index 50508a3192..7a6ab7d968 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/SelectLocationButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/SelectLocationButton.tsx @@ -1,6 +1,5 @@ import { useCallback, useMemo } from 'react'; import { sprintf } from 'sprintf-js'; -import styled from 'styled-components'; import { ICustomList } from '../../../shared/daemon-rpc-types'; import { messages, relayLocations } from '../../../shared/gettext'; @@ -10,17 +9,9 @@ import { transitions, useHistory } from '../../lib/history'; import { RoutePath } from '../../lib/routes'; import { IRelayLocationCountryRedux, RelaySettingsRedux } from '../../redux/settings/reducers'; import { useSelector } from '../../redux/store'; +import { Button, ButtonProps } from '../common/molecules'; import ImageView from '../ImageView'; -import { MultiButton, MultiButtonCompatibleProps } from '../MultiButton'; -import { SmallButton, SmallButtonColor } from '../SmallButton'; - -const StyledSmallButton = styled(SmallButton)({ - margin: 0, -}); - -const StyledReconnectButton = styled(StyledSmallButton)({ - padding: '4px 8px 4px 8px', -}); +import { MultiButton } from '../MultiButton'; export default function SelectLocationButtons() { const tunnelState = useSelector((state) => state.connection.status.state); @@ -32,7 +23,7 @@ export default function SelectLocationButtons() { } } -function SelectLocationButton(props: MultiButtonCompatibleProps) { +function SelectLocationButton(props: ButtonProps) { const { push } = useHistory(); const tunnelState = useSelector((state) => state.connection.status.state); @@ -50,8 +41,9 @@ function SelectLocationButton(props: MultiButtonCompatibleProps) { }, [push]); return ( - <StyledSmallButton - color={SmallButtonColor.blue} + <Button + variant="primary" + size="full" onClick={onSelectLocation} aria-label={sprintf( messages.pgettext('accessibility', 'Select location. Current location is %(location)s'), @@ -61,7 +53,7 @@ function SelectLocationButton(props: MultiButtonCompatibleProps) { {tunnelState === 'disconnected' ? selectedRelayName : messages.pgettext('tunnel-control', 'Switch location')} - </StyledSmallButton> + </Button> ); } @@ -119,7 +111,7 @@ function getRelayName( } } -function ReconnectButton(props: MultiButtonCompatibleProps) { +function ReconnectButton(props: ButtonProps) { const { reconnectTunnel } = useAppContext(); const onReconnect = useCallback(async () => { @@ -132,12 +124,8 @@ function ReconnectButton(props: MultiButtonCompatibleProps) { }, [reconnectTunnel]); return ( - <StyledReconnectButton - color={SmallButtonColor.blue} - onClick={onReconnect} - aria-label={messages.gettext('Reconnect')} - {...props}> + <Button onClick={onReconnect} size="tiny" aria-label={messages.gettext('Reconnect')} {...props}> <ImageView height={24} width={24} source="icon-reload" tintColor="white" /> - </StyledReconnectButton> + </Button> ); } 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 new file mode 100644 index 0000000000..41671c46a9 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/AppInfoView.tsx @@ -0,0 +1,48 @@ +import { messages } from '../../../../shared/gettext'; +import { useHistory } from '../../../lib/history'; +import { BackAction } from '../../KeyboardNavigation'; +import { Layout, SettingsContainer, SettingsGroup, SettingsStack } from '../../Layout'; +import { + NavigationBar, + NavigationContainer, + NavigationItems, + NavigationScrollbars, + TitleBarItem, +} from '../../NavigationBar'; +import SettingsHeader, { HeaderTitle } from '../../SettingsHeader'; +import { AppVersionListItem, ChangelogListItem } from './components'; + +export const AppInfoView = () => { + const { pop } = useHistory(); + return ( + <BackAction action={pop}> + <Layout> + <SettingsContainer> + <NavigationContainer> + <NavigationBar> + <NavigationItems> + <TitleBarItem>{messages.pgettext('app-info-view', 'App info')}</TitleBarItem> + </NavigationItems> + </NavigationBar> + + <NavigationScrollbars> + <SettingsHeader> + <HeaderTitle>{messages.pgettext('app-info-view', 'App info')}</HeaderTitle> + </SettingsHeader> + <SettingsContainer> + <SettingsStack> + <SettingsGroup> + <AppVersionListItem /> + </SettingsGroup> + <SettingsGroup> + <ChangelogListItem /> + </SettingsGroup> + </SettingsStack> + </SettingsContainer> + </NavigationScrollbars> + </NavigationContainer> + </SettingsContainer> + </Layout> + </BackAction> + ); +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/AppVersionListItem.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/AppVersionListItem.tsx new file mode 100644 index 0000000000..539a7e46c3 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/AppVersionListItem.tsx @@ -0,0 +1,65 @@ +import { useCallback } from 'react'; + +import { messages } from '../../../../../shared/gettext'; +import { getDownloadUrl } from '../../../../../shared/version'; +import { useAppContext } from '../../../../context'; +import { useSelector } from '../../../../redux/store'; +import * as Cell from '../../../cell'; +import { Colors } from '../../../common/variables'; +import { LabelStack } from '../../../Layout'; + +export function AppVersionListItem() { + const appVersion = useSelector((state) => state.version.current); + const consistentVersion = useSelector((state) => state.version.consistent); + const upToDateVersion = useSelector((state) => (state.version.suggestedUpgrade ? false : true)); + const suggestedIsBeta = useSelector((state) => state.version.suggestedIsBeta ?? false); + const isOffline = useSelector((state) => state.connection.isBlocked); + + const { openUrl } = useAppContext(); + const openDownloadLink = useCallback( + () => openUrl(getDownloadUrl(suggestedIsBeta)), + [openUrl, suggestedIsBeta], + ); + + let alertIcon; + let footer; + if (!consistentVersion || !upToDateVersion) { + const inconsistentVersionMessage = messages.pgettext( + 'app-info-view', + 'App is out of sync. Please quit and restart.', + ); + + const updateAvailableMessage = messages.pgettext( + 'app-info-view', + 'Update available. Install the latest app version to stay up to date.', + ); + + const message = !consistentVersion ? inconsistentVersionMessage : updateAvailableMessage; + + alertIcon = <Cell.UntintedIcon source="icon-alert" width={18} tintColor={Colors.red} />; + footer = ( + <Cell.CellFooter> + <Cell.CellFooterText>{message}</Cell.CellFooterText> + </Cell.CellFooter> + ); + } + + return ( + <> + <Cell.CellNavigationButton + disabled={isOffline} + onClick={openDownloadLink} + icon={{ + source: 'icon-extLink', + 'aria-label': messages.pgettext('accessibility', 'Opens externally'), + }}> + <LabelStack> + {alertIcon} + <Cell.Label>{messages.pgettext('app-info-view', 'App version')}</Cell.Label> + </LabelStack> + <Cell.SubText>{appVersion}</Cell.SubText> + </Cell.CellNavigationButton> + {footer} + </> + ); +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/ChangelogListItem.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/ChangelogListItem.tsx new file mode 100644 index 0000000000..c05b0286b6 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/ChangelogListItem.tsx @@ -0,0 +1,17 @@ +import { useCallback } from 'react'; + +import { messages } from '../../../../../shared/gettext'; +import { useHistory } from '../../../../lib/history'; +import { RoutePath } from '../../../../lib/routes'; +import * as Cell from '../../../cell'; + +export function ChangelogListItem() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.changelog), [history]); + + return ( + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label>{messages.pgettext('settings-view', "What's new")}</Cell.Label> + </Cell.CellNavigationButton> + ); +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/index.ts new file mode 100644 index 0000000000..905bc45397 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/index.ts @@ -0,0 +1,2 @@ +export * from './AppVersionListItem'; +export * from './ChangelogListItem'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/index.ts new file mode 100644 index 0000000000..afbe9686f6 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/index.ts @@ -0,0 +1 @@ +export * from './AppInfoView'; 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 new file mode 100644 index 0000000000..df5453a68f --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/changelog/ChangelogView.tsx @@ -0,0 +1,106 @@ +import { useCallback } from 'react'; +import styled from 'styled-components'; + +import { links } from '../../../../config.json'; +import { messages } from '../../../../shared/gettext'; +import { useAppContext } from '../../../context'; +import { useHistory } from '../../../lib/history'; +import { useSelector } from '../../../redux/store'; +import { Flex } from '../../common/layout'; +import { Container } from '../../common/layout/Container'; +import { Button } from '../../common/molecules/'; +import { BodySmall, TitleBig, TitleLarge } from '../../common/text'; +import { Colors, Spacings } from '../../common/variables'; +import ImageView from '../../ImageView'; +import { BackAction } from '../../KeyboardNavigation'; +import { Layout, SettingsContainer } from '../../Layout'; +import { + NavigationBar, + NavigationContainer, + NavigationItems, + NavigationScrollbars, + TitleBarItem, +} from '../../NavigationBar'; +import SettingsHeader from '../../SettingsHeader'; + +const StyledList = styled(Flex)({ + listStyleType: 'disc', + paddingLeft: 0, + li: { + marginLeft: '1.5em', + }, +}); + +const StyledFooter = styled(Flex)({ + position: 'sticky', + minHeight: '64px', + bottom: 0, + background: Colors.darkBlue, +}); + +export const ChangelogView = () => { + const { pop } = useHistory(); + const { openUrl } = useAppContext(); + const changelog = useSelector((state) => state.userInterface.changelog); + const version = useSelector((state) => state.version.current); + + const url = links.changelog; + const openDownloadLink = useCallback(() => openUrl(url), [openUrl, url]); + return ( + <BackAction action={pop}> + <Layout> + <SettingsContainer> + <NavigationContainer> + <NavigationBar> + <NavigationItems> + <TitleBarItem>{messages.pgettext('changelog-view', "What's new")}</TitleBarItem> + </NavigationItems> + </NavigationBar> + + <NavigationScrollbars> + <SettingsHeader> + <TitleBig as={'h1'}>{messages.pgettext('changelog-view', "What's new")}</TitleBig> + </SettingsHeader> + <Flex $flexDirection="column" $gap={Spacings.spacing3}> + <Container size="4"> + <TitleLarge as="h2">{version}</TitleLarge> + </Container> + <Container size="3" $flexDirection="column"> + {changelog.length ? ( + <StyledList as="ul" $flexDirection="column" $gap={Spacings.spacing5}> + {changelog.map((item, i) => ( + <BodySmall as="li" key={i} color={Colors.white60}> + {item} + </BodySmall> + ))} + </StyledList> + ) : ( + <BodySmall color={Colors.white60}> + {messages.pgettext( + 'changelog-view', + 'No updates or changes were made in this release for this platform.', + )} + </BodySmall> + )} + </Container> + </Flex> + </NavigationScrollbars> + </NavigationContainer> + </SettingsContainer> + <StyledFooter $alignItems="center" $justifyContent="center"> + <Button + onClick={openDownloadLink} + trailing={ + <ImageView + source="icon-extLink" + aria-label={messages.pgettext('accessibility', 'Opens externally')} + tintColor={Colors.white} + /> + }> + {messages.pgettext('changelog', 'See full changelog')} + </Button> + </StyledFooter> + </Layout> + </BackAction> + ); +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/changelog/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/changelog/index.ts new file mode 100644 index 0000000000..2c823d4f13 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/changelog/index.ts @@ -0,0 +1 @@ +export * from './ChangelogView'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/index.ts new file mode 100644 index 0000000000..dff5aa7a90 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/index.ts @@ -0,0 +1,2 @@ +export * from './app-info'; +export * from './changelog'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/index.ts new file mode 100644 index 0000000000..a03af00d06 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/index.ts @@ -0,0 +1,2 @@ +export * from './new-device'; +export * from './new-version'; diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/new-device.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/new-device.ts index 7d0fe9f299..f5bd085d89 100644 --- a/desktop/packages/mullvad-vpn/src/shared/notifications/new-device.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/new-device.ts @@ -1,8 +1,8 @@ import { sprintf } from 'sprintf-js'; -import { messages } from '../../shared/gettext'; -import { capitalizeEveryWord } from '../string-helpers'; -import { InAppNotification, InAppNotificationProvider } from './notification'; +import { messages } from '../../../shared/gettext'; +import { InAppNotification, InAppNotificationProvider } from '../../../shared/notifications'; +import { capitalizeEveryWord } from '../../../shared/string-helpers'; interface NewDeviceNotificationContext { shouldDisplay: boolean; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/new-version.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/new-version.ts new file mode 100644 index 0000000000..73ccf61525 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/new-version.ts @@ -0,0 +1,44 @@ +import { messages } from '../../../shared/gettext'; +import { IChangelog } from '../../../shared/ipc-types'; +import { InAppNotification, InAppNotificationProvider } from '../../../shared/notifications'; +import { RoutePath } from '../routes'; + +interface NewVersionNotificationContext { + currentVersion: string; + displayedForVersion: string; + changelog: IChangelog; + close: () => void; +} + +export class NewVersionNotificationProvider implements InAppNotificationProvider { + public constructor(private context: NewVersionNotificationContext) {} + + public mayDisplay = () => { + return ( + this.context.displayedForVersion !== this.context.currentVersion && + this.context.changelog.length > 0 + ); + }; + + public getInAppNotification(): InAppNotification { + const title = messages.pgettext('in-app-notifications', 'NEW VERSION INSTALLED'); + const subtitle = messages.pgettext('in-app-notifications', "Click here to see what's new."); + return { + indicator: 'success', + action: { type: 'close', close: this.context.close }, + title, + subtitle, + subtitleAction: { + type: 'navigate', + link: { + to: RoutePath.changelog, + onClick: this.context.close, + 'aria-label': messages.pgettext( + 'accessibility', + 'New version installed, click here to see the changelog', + ), + }, + }, + }; + } +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/routes.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/routes.ts index 89b50c1fb0..73f510ea3a 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/routes.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/routes.ts @@ -31,4 +31,6 @@ export enum RoutePath { selectLocation = '/select-location', editCustomBridge = '/select-location/edit-custom-bridge', filter = '/select-location/filter', + appInfo = '/settings/app-info', + changelog = '/settings/changelog', } diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/styles.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/styles.ts index 554e669373..ea527ab791 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/styles.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/styles.ts @@ -4,6 +4,6 @@ export type NonTransientProps<T, K extends NonTransientPropKey<keyof T>> = { [P in keyof T as NonTransientPropKey<P> extends K ? NonTransientPropKey<P> : P]: T[P]; }; -export type TransientProps<T, K extends keyof T> = { +export type TransientProps<T, K extends keyof T = keyof T> = { [P in keyof T as P extends K ? `$${P & string}` : P]: T[P]; }; diff --git a/desktop/packages/mullvad-vpn/src/renderer/redux/userinterface/actions.ts b/desktop/packages/mullvad-vpn/src/renderer/redux/userinterface/actions.ts index 238835318e..83c0252452 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/redux/userinterface/actions.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/redux/userinterface/actions.ts @@ -41,11 +41,6 @@ export interface ISetChangelog { changelog: IChangelog; } -export interface ISetForceShowChanges { - type: 'SET_FORCE_SHOW_CHANGES'; - forceShowChanges: boolean; -} - export interface ISetIsPerformingPostUpgrade { type: 'SET_IS_PERFORMING_POST_UPGRADE'; isPerformingPostUpgrade: boolean; @@ -70,7 +65,6 @@ export type UserInterfaceAction = | ISetConnectedToDaemon | ISetDaemonAllowed | ISetChangelog - | ISetForceShowChanges | ISetIsPerformingPostUpgrade | ISetSelectLocationView | ISetIsMacOs13OrNewer; @@ -132,13 +126,6 @@ function setChangelog(changelog: IChangelog): ISetChangelog { }; } -function setForceShowChanges(forceShowChanges: boolean): ISetForceShowChanges { - return { - type: 'SET_FORCE_SHOW_CHANGES', - forceShowChanges, - }; -} - function setIsPerformingPostUpgrade(isPerformingPostUpgrade: boolean): ISetIsPerformingPostUpgrade { return { type: 'SET_IS_PERFORMING_POST_UPGRADE', @@ -169,7 +156,6 @@ export default { setConnectedToDaemon, setDaemonAllowed, setChangelog, - setForceShowChanges, setIsPerformingPostUpgrade, setSelectLocationView, setIsMacOs13OrNewer, diff --git a/desktop/packages/mullvad-vpn/src/renderer/redux/userinterface/reducers.ts b/desktop/packages/mullvad-vpn/src/renderer/redux/userinterface/reducers.ts index 89427e9b06..46bdfc6308 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/redux/userinterface/reducers.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/redux/userinterface/reducers.ts @@ -12,7 +12,6 @@ export interface IUserInterfaceReduxState { connectedToDaemon: boolean; daemonAllowed?: boolean; changelog: IChangelog; - forceShowChanges: boolean; isPerformingPostUpgrade: boolean; selectLocationView: LocationType; isMacOs13OrNewer: boolean; @@ -26,7 +25,6 @@ const initialState: IUserInterfaceReduxState = { connectedToDaemon: false, daemonAllowed: undefined, changelog: [], - forceShowChanges: false, isPerformingPostUpgrade: false, selectLocationView: LocationType.exit, isMacOs13OrNewer: true, @@ -64,12 +62,6 @@ export default function ( changelog: action.changelog, }; - case 'SET_FORCE_SHOW_CHANGES': - return { - ...state, - forceShowChanges: action.forceShowChanges, - }; - case 'SET_IS_PERFORMING_POST_UPGRADE': return { ...state, diff --git a/desktop/packages/mullvad-vpn/src/renderer/tokens/colors.ts b/desktop/packages/mullvad-vpn/src/renderer/tokens/color-tokens.ts index 46dc8508be..fe42f71f4e 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/tokens/colors.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/tokens/color-tokens.ts @@ -1,4 +1,4 @@ -export enum Colors { +export enum ColorTokens { darkerBlue = 'rgba(25, 38, 56, 0.95)', darkBlue = 'rgb(25, 46, 69)', blue = 'rgb(41, 77, 115)', diff --git a/desktop/packages/mullvad-vpn/src/renderer/tokens/index.ts b/desktop/packages/mullvad-vpn/src/renderer/tokens/index.ts index 53555f1d5a..3c59aa34fd 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/tokens/index.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/tokens/index.ts @@ -1,3 +1,4 @@ -export * from './colors'; -export * from './spacings'; -export * from './typography'; +export * from './color-tokens'; +export * from './radius-tokens'; +export * from './spacing-tokens'; +export * from './typography-tokens'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/tokens/radius-tokens.ts b/desktop/packages/mullvad-vpn/src/renderer/tokens/radius-tokens.ts new file mode 100644 index 0000000000..7eecb31eca --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/tokens/radius-tokens.ts @@ -0,0 +1,6 @@ +export enum RadiusTokens { + radius4 = '4px', + radius8 = '8px', + radius11 = '11px', + radius12 = '12px', +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/tokens/spacings.ts b/desktop/packages/mullvad-vpn/src/renderer/tokens/spacing-tokens.ts index fb0c924863..4216dde90a 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/tokens/spacings.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/tokens/spacing-tokens.ts @@ -1,4 +1,4 @@ -export enum Spacings { +export enum SpacingTokens { spacing1 = '4px', spacing2 = '6px', spacing3 = '8px', diff --git a/desktop/packages/mullvad-vpn/src/renderer/tokens/typography-tokens.ts b/desktop/packages/mullvad-vpn/src/renderer/tokens/typography-tokens.ts new file mode 100644 index 0000000000..9d42a89c9f --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/tokens/typography-tokens.ts @@ -0,0 +1,28 @@ +export enum FontFamilyTokens { + openSans = 'Open Sans', + sourceSansPro = '"Source Sans Pro", "Noto Sans Myanmar", "Noto Sans Thai", sans-serif', +} + +export enum FontWeightTokens { + regular = 400, + semiBold = 600, + bold = 700, +} + +export enum FontSizeTokens { + big = '32px', + large = '24px', + medium = '18px', + small = '14px', + tiny = '12px', + mini = '10px', +} + +export enum LineHeightTokens { + big = '34px', + large = '28px', + medium = '24px', + small = '20px', + tiny = '18px', + mini = '15px', +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/tokens/typography.ts b/desktop/packages/mullvad-vpn/src/renderer/tokens/typography.ts deleted file mode 100644 index df7359b8f0..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/tokens/typography.ts +++ /dev/null @@ -1,96 +0,0 @@ -export enum FontFamilies { - openSans = 'Open Sans', - openSansPro = '"Source Sans Pro", "Noto Sans Myanmar", "Noto Sans Thai", sans-serif', -} - -export enum Fonts { - title = FontFamilies.openSansPro, - body = FontFamilies.openSans, - label = FontFamilies.openSans, - footnote = FontFamilies.openSans, -} - -export enum FontWeights { - regular = 400, - semiBold = 600, - bold = 700, -} - -export enum FontSizes { - big = '32px', - large = '24px', - medium = '18px', - small = '14px', - tiny = '12px', - mini = '10px', -} - -export enum LineHeights { - big = '34px', - large = '28px', - medium = '24px', - small = '20px', - tiny = '18px', - mini = '15px', -} - -interface Typography { - fontFamily: React.CSSProperties['fontFamily']; - fontSize: React.CSSProperties['fontSize']; - fontWeight: React.CSSProperties['fontWeight']; - lineHeight: React.CSSProperties['lineHeight']; -} - -export const typography: Record< - | 'titleBig' - | 'titleLarge' - | 'titleMedium' - | 'bodySmall' - | 'bodySmallSemibold' - | 'labelTiny' - | 'footnoteMini', - Typography -> = { - titleBig: { - fontFamily: Fonts.title, - fontWeight: FontWeights.bold, - fontSize: FontSizes.big, - lineHeight: LineHeights.big, - }, - titleLarge: { - fontFamily: Fonts.title, - fontWeight: FontWeights.bold, - fontSize: FontSizes.large, - lineHeight: LineHeights.large, - }, - titleMedium: { - fontFamily: Fonts.title, - fontWeight: FontWeights.semiBold, - fontSize: FontSizes.medium, - lineHeight: LineHeights.medium, - }, - bodySmall: { - fontFamily: Fonts.body, - fontWeight: FontWeights.regular, - fontSize: FontSizes.small, - lineHeight: LineHeights.small, - }, - bodySmallSemibold: { - fontFamily: Fonts.body, - fontWeight: FontWeights.semiBold, - fontSize: FontSizes.small, - lineHeight: LineHeights.small, - }, - labelTiny: { - fontFamily: Fonts.label, - fontWeight: FontWeights.semiBold, - fontSize: FontSizes.tiny, - lineHeight: LineHeights.tiny, - }, - footnoteMini: { - fontFamily: Fonts.footnote, - fontWeight: FontWeights.semiBold, - fontSize: FontSizes.mini, - lineHeight: LineHeights.mini, - }, -} as const; diff --git a/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts b/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts index 856239c0f5..3580c679f3 100644 --- a/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts +++ b/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts @@ -73,7 +73,6 @@ export interface IAppStateSnapshot { splitTunnelingApplications?: ISplitTunnelingApplication[]; macOsScrollbarVisibility?: MacOsScrollbarVisibility; changelog: IChangelog; - forceShowChanges: boolean; navigationHistory?: IHistoryObject; currentApiAccessMethod?: AccessMethodSetting; isMacOs13OrNewer: boolean; diff --git a/desktop/packages/mullvad-vpn/src/shared/localization-contexts.ts b/desktop/packages/mullvad-vpn/src/shared/localization-contexts.ts index f30212025c..c4d47b1afa 100644 --- a/desktop/packages/mullvad-vpn/src/shared/localization-contexts.ts +++ b/desktop/packages/mullvad-vpn/src/shared/localization-contexts.ts @@ -37,4 +37,6 @@ export type LocalizationContexts = | 'select-language-nav' | 'tray-icon-context-menu' | 'tray-icon-tooltip' - | 'troubleshoot'; + | 'troubleshoot' + | 'app-info-view' + | 'changelog-view'; diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/inconsistent-version.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/inconsistent-version.ts index e4f7a8ddc1..4cfed9f777 100644 --- a/desktop/packages/mullvad-vpn/src/shared/notifications/inconsistent-version.ts +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/inconsistent-version.ts @@ -17,7 +17,10 @@ export class InconsistentVersionNotificationProvider { public constructor(private context: InconsistentVersionNotificationContext) {} - public mayDisplay = () => !this.context.consistent; + public mayDisplay = () => + !this.context.consistent && + process.env.NODE_ENV !== undefined && + process.env.NODE_ENV !== 'development'; public getSystemNotification(): SystemNotification { return { diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/index.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/index.ts new file mode 100644 index 0000000000..5321917af6 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/index.ts @@ -0,0 +1,13 @@ +export * from './account-expired'; +export * from './close-to-account-expiry'; +export * from './block-when-disconnected'; +export * from './connected'; +export * from './connecting'; +export * from './disconnected'; +export * from './daemon-disconnected'; +export * from './error'; +export * from './inconsistent-version'; +export * from './notification'; +export * from './reconnecting'; +export * from './unsupported-version'; +export * from './update-available'; diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts index 87166aab4d..96754811a6 100644 --- a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts @@ -1,3 +1,5 @@ +import { LinkProps } from '../../renderer/components/common/text'; + export type NotificationAction = { type: 'open-url'; url: string; @@ -25,6 +27,10 @@ export type InAppNotificationAction = | { type: 'close'; close: () => void; + } + | { + type: 'navigate'; + link: Pick<LinkProps, 'to' | 'onClick' | 'aria-label'>; }; export type InAppNotificationIndicatorType = 'success' | 'warning' | 'error'; @@ -59,9 +65,10 @@ export interface SystemNotification { export interface InAppNotification { indicator?: InAppNotificationIndicatorType; + action?: InAppNotificationAction; title: string; subtitle?: string; - action?: InAppNotificationAction; + subtitleAction?: InAppNotificationAction; } export interface SystemNotificationProvider extends NotificationProvider { @@ -71,16 +78,3 @@ export interface SystemNotificationProvider extends NotificationProvider { export interface InAppNotificationProvider extends NotificationProvider { getInAppNotification(): InAppNotification | undefined; } - -export * from './account-expired'; -export * from './close-to-account-expiry'; -export * from './block-when-disconnected'; -export * from './connected'; -export * from './connecting'; -export * from './disconnected'; -export * from './daemon-disconnected'; -export * from './error'; -export * from './inconsistent-version'; -export * from './reconnecting'; -export * from './unsupported-version'; -export * from './update-available'; diff --git a/desktop/packages/mullvad-vpn/test/e2e/setup/main.ts b/desktop/packages/mullvad-vpn/test/e2e/setup/main.ts index d8f3ceee48..1795451046 100644 --- a/desktop/packages/mullvad-vpn/test/e2e/setup/main.ts +++ b/desktop/packages/mullvad-vpn/test/e2e/setup/main.ts @@ -171,7 +171,6 @@ class ApplicationMain { splitTunnelingApplications: [], macOsScrollbarVisibility: MacOsScrollbarVisibility.whenScrolling, changelog: [], - forceShowChanges: false, navigationHistory: undefined, scrollPositions: {}, isMacOs13OrNewer: true, diff --git a/desktop/packages/mullvad-vpn/test/unit/notification-evaluation.spec.ts b/desktop/packages/mullvad-vpn/test/unit/notification-evaluation.spec.ts index d05e967efc..50acda1393 100644 --- a/desktop/packages/mullvad-vpn/test/unit/notification-evaluation.spec.ts +++ b/desktop/packages/mullvad-vpn/test/unit/notification-evaluation.spec.ts @@ -9,7 +9,7 @@ import { FirewallPolicyErrorType } from '../../src/shared/daemon-rpc-types'; import { UnsupportedVersionNotificationProvider, UpdateAvailableNotificationProvider, -} from '../../src/shared/notifications/notification'; +} from '../../src/shared/notifications'; function createController() { return new NotificationController({ |
