summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOliver <oliver@mohlin.dev>2025-03-10 10:21:11 +0100
committerMarkus Pettersson <markus.pettersson@mullvad.net>2025-03-18 21:32:10 +0100
commit8bc21e0298d121bef5836d6f788be5436dffc2d3 (patch)
tree9d2d643af5271ff756eb645bbd99c2cf561858f8
parentb4f392dbc133f9e77d8722317277a0cabb92e21e (diff)
downloadmullvadvpn-8bc21e0298d121bef5836d6f788be5436dffc2d3.tar.xz
mullvadvpn-8bc21e0298d121bef5836d6f788be5436dffc2d3.zip
Refactor app specific logic in Link into ExternalLink and InternalLink
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/ExternalLink.tsx24
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/InternalLink.tsx24
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx8
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/typography/Link.tsx48
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/notifications/new-version.ts2
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts6
6 files changed, 77 insertions, 35 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ExternalLink.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ExternalLink.tsx
new file mode 100644
index 0000000000..6ef57cc4eb
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/ExternalLink.tsx
@@ -0,0 +1,24 @@
+import { useCallback } from 'react';
+
+import { Url } from '../../shared/constants';
+import { useAppContext } from '../context';
+import { Link, LinkProps } from '../lib/components';
+
+export type ExternalLinkProps = Omit<LinkProps<'a'>, 'href' | 'as'> & {
+ to: Url;
+};
+
+export const ExternalLink = ({ to, onClick, ...props }: ExternalLinkProps) => {
+ const { openUrl } = useAppContext();
+ const navigate = useCallback(
+ (e: React.MouseEvent<HTMLAnchorElement>) => {
+ e.preventDefault();
+ if (onClick) {
+ onClick(e);
+ }
+ return openUrl(to);
+ },
+ [onClick, openUrl, to],
+ );
+ return <Link href="" onClick={navigate} {...props} />;
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/InternalLink.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/InternalLink.tsx
new file mode 100644
index 0000000000..6b45d99fee
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/InternalLink.tsx
@@ -0,0 +1,24 @@
+import { useCallback } from 'react';
+
+import { Link, LinkProps } from '../lib/components';
+import { useHistory } from '../lib/history';
+import { RoutePath } from '../lib/routes';
+
+export type InternalLinkProps = Omit<LinkProps<'a'>, 'href' | 'as'> & {
+ to: RoutePath;
+};
+
+export const InternalLink = ({ to, onClick, ...props }: InternalLinkProps) => {
+ const history = useHistory();
+ const navigate = useCallback(
+ (e: React.MouseEvent<HTMLAnchorElement>) => {
+ e.preventDefault();
+ if (onClick) {
+ onClick(e);
+ }
+ return history.push(to);
+ },
+ [history, to, onClick],
+ );
+ return <Link href="" onClick={navigate} {...props} />;
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx
index d9c6cbb666..b0588e6c0d 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx
@@ -16,7 +16,6 @@ import {
} from '../../shared/notifications';
import { useAppContext } from '../context';
import useActions from '../lib/actionsHook';
-import { Link } from '../lib/components';
import { Colors } from '../lib/foundations';
import { transitions, useHistory } from '../lib/history';
import { formatHtml } from '../lib/html-formatter';
@@ -28,6 +27,7 @@ import { RoutePath } from '../lib/routes';
import accountActions from '../redux/account/actions';
import { IReduxState, useSelector } from '../redux/store';
import * as AppButton from './AppButton';
+import { InternalLink } from './InternalLink';
import { ModalAlert, ModalAlertType, ModalMessage, ModalMessageList } from './Modal';
import {
NotificationActions,
@@ -141,13 +141,13 @@ export default function NotificationArea(props: IProps) {
{notification.title}
</NotificationTitle>
<NotificationSubtitle data-testid="notificationSubTitle">
- {notification.subtitleAction?.type === 'navigate' ? (
- <Link
+ {notification.subtitleAction?.type === 'navigate-internal' ? (
+ <InternalLink
variant="labelTiny"
color={Colors.white60}
{...notification.subtitleAction.link}>
{formatHtml(notification.subtitle ?? '')}
- </Link>
+ </InternalLink>
) : (
formatHtml(notification.subtitle ?? '')
)}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/typography/Link.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/typography/Link.tsx
index a504e8b956..eb1d75917c 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/typography/Link.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/typography/Link.tsx
@@ -1,29 +1,28 @@
-import React, { useCallback } from 'react';
+import React from 'react';
import styled from 'styled-components';
import { Colors, Radius } from '../../foundations';
-import { useHistory } from '../../history';
-import { RoutePath } from '../../routes';
-import { buttonReset } from '../../styles';
import { Text, TextProps } from './Text';
-export interface LinkProps extends Omit<TextProps<'button'>, 'color'> {
- to: RoutePath;
- color?: Colors;
-}
+export type LinkProps<T extends React.ElementType = 'a'> = TextProps<T> & {
+ onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
+};
const StyledText = styled(Text)<{
$hoverColor: Colors | undefined;
}>((props) => ({
- ...buttonReset,
background: 'transparent',
+ cursor: 'default',
+ textDecoration: 'none',
+ display: 'inline-flex',
+ alignItems: 'center',
- '&:hover': {
+ '&&:hover': {
textDecorationLine: 'underline',
textUnderlineOffset: '2px',
color: props.$hoverColor,
},
- '&:focus-visible': {
+ '&&:focus-visible': {
borderRadius: Radius.radius4,
outline: `2px solid ${Colors.white}`,
outlineOffset: '2px',
@@ -39,25 +38,20 @@ const getHoverColor = (color: Colors | undefined) => {
}
};
-export const Link = ({ to, children, color, onClick, ...props }: LinkProps) => {
- const history = useHistory();
- const navigate = useCallback(
- (e: React.MouseEvent<HTMLButtonElement>) => {
- if (onClick) {
- onClick(e);
- }
- return history.push(to);
- },
- [history, to, onClick],
- );
+export const Link = <T extends React.ElementType = 'a'>({
+ as: forwardedAs,
+ color,
+ ...props
+}: LinkProps<T>) => {
+ // If `as` is provided we need to pass it as `forwardedAs` for it to
+ // be correctly passed to the `Text` component.
+ const componentProps = forwardedAs ? { ...props, forwardedAs } : props;
return (
<StyledText
- onClick={navigate}
- as={'button'}
+ forwardedAs="a"
color={color}
$hoverColor={getHoverColor(color)}
- {...props}>
- {children}
- </StyledText>
+ {...componentProps}
+ />
);
};
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
index 49be1a2718..337ba016c5 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/new-version.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/notifications/new-version.ts
@@ -29,7 +29,7 @@ export class NewVersionNotificationProvider implements InAppNotificationProvider
title,
subtitle,
subtitleAction: {
- type: 'navigate',
+ type: 'navigate-internal',
link: {
to: RoutePath.changelog,
onClick: this.context.close,
diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts
index d46f344a09..80a40deef2 100644
--- a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts
+++ b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts
@@ -1,4 +1,4 @@
-import { LinkProps } from '../../renderer/lib/components';
+import { InternalLinkProps } from '../../renderer/components/InternalLink';
import { Url } from '../constants';
export type NotificationAction = {
@@ -31,8 +31,8 @@ export type InAppNotificationAction =
close: () => void;
}
| {
- type: 'navigate';
- link: Pick<LinkProps, 'to' | 'onClick' | 'aria-label'>;
+ type: 'navigate-internal';
+ link: Pick<InternalLinkProps, 'to' | 'onClick' | 'aria-label'>;
};
export type InAppNotificationIndicatorType = 'success' | 'warning' | 'error';