Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: website link component defaults target to _blank when the href is external #2038

Merged
merged 5 commits into from Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/website/components/breadcrumbs/breadcrumbs.js
Expand Up @@ -10,7 +10,7 @@ import Link from '../link/link';
* @param {Object} props
* @param {String} props.variant
* @param {Object} [props.items]
* @param {Function} props.click
* @param {React.MouseEventHandler<HTMLAnchorElement>} props.click
*/
export default function Breadcrumbs({ variant, click, items }) {
return (
Expand Down
63 changes: 55 additions & 8 deletions packages/website/components/link/link.js
Expand Up @@ -2,14 +2,61 @@ import React from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link';

const WrappedLink = ({ tabIndex = 0, href, target = '_self', ...otherProps }) => (
<Link href={href} {...otherProps}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a target={target} {...otherProps} tabIndex={tabIndex} onClick={otherProps.onClick}>
{otherProps.children}
</a>
</Link>
);
/**
* Return whether a url is 'external' relative to another URL.
* The url is considered external if it has a different hostname.
* @param {URL} url
* @param {URL} relativeTo - url to compare for externality
*/
function isExternalLink(url, relativeTo) {
return url.hostname !== relativeTo.hostname;
}

/**
* @param {string} href - href attribute of an <a>
* @returns {boolean} - whether the provided href is known to be external from the current document
*/
function useHrefExternality(href) {
const [document, setDocument] = React.useState(/** @type {Document|undefined} */ (undefined));
// useEffect because next ssr wont have a document
React.useEffect(() => {
if (typeof globalThis.document !== 'undefined') {
setDocument(globalThis.document);
}
}, []);
const isExternalHref = React.useMemo(() => {
if (!document) {
return false;
}
const documentURL = new URL(document.URL);
const isExternal = isExternalLink(new URL(href, documentURL), documentURL);
return isExternal;
}, [document, href]);
return isExternalHref;
}

/**
* A generic hyperlink component.
* * by default, links to external sites will have target=_blank if they are an external link
gobengo marked this conversation as resolved.
Show resolved Hide resolved
* @param {object} props
* @param {string} props.href - the href attribute for the link
* @param {number} [props.tabIndex] - the tabIndex attribute for the link
* @param {string} [props.target] - the target attribute for the link
* @param {React.ReactNode} [props.children]
* @param {React.MouseEventHandler<HTMLAnchorElement>} [props.onClick] - the onClick handler for the link
*/
const WrappedLink = ({ tabIndex = 0, href, target, ...otherProps }) => {
yusefnapora marked this conversation as resolved.
Show resolved Hide resolved
const isExternalHref = useHrefExternality(href);
const derrivedTarget = isExternalHref ? '_blank' : '_self';
return (
<Link href={href} {...otherProps}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a target={derrivedTarget} {...otherProps} tabIndex={tabIndex} onClick={otherProps.onClick}>
{otherProps.children}
</a>
</Link>
);
};

WrappedLink.propTypes = {
onClick: PropTypes.func,
Expand Down