diff options
| author | Oliver <oliver@mohlin.dev> | 2025-03-10 10:21:11 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2025-03-18 21:32:10 +0100 |
| commit | 8bc21e0298d121bef5836d6f788be5436dffc2d3 (patch) | |
| tree | 9d2d643af5271ff756eb645bbd99c2cf561858f8 | |
| parent | b4f392dbc133f9e77d8722317277a0cabb92e21e (diff) | |
| download | mullvadvpn-8bc21e0298d121bef5836d6f788be5436dffc2d3.tar.xz mullvadvpn-8bc21e0298d121bef5836d6f788be5436dffc2d3.zip | |
Refactor app specific logic in Link into ExternalLink and InternalLink
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'; |
