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

[NO-TICKET] Use the external link component in doc site #2522

Merged
merged 5 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,68 +14,70 @@ function renderThirdPartyExternalLink(customProps = {}) {
return render(<ThirdPartyExternalLink {...props} />);
}

function getLink() {
return screen.getByRole('link');
}

describe('ThirdPartyExternalLink', () => {
it('renders external link', () => {
renderThirdPartyExternalLink();

const button = screen.getByRole('button');

expect(button.classList).toContain('ds-c-button');
expect(button).toMatchSnapshot();
expect(getLink()).toMatchSnapshot();
});

it('renders external link with custom class', () => {
renderThirdPartyExternalLink({ className: 'custom-class' });

const button = screen.getByRole('button');

expect(button.classList).toContain('custom-class');
expect(getLink().classList).toContain('custom-class');
});

it('renders external link with custom text', () => {
renderThirdPartyExternalLink({ children: 'Custom text' });

const button = screen.getByRole('button');

expect(button.textContent).toContain('Custom text');
expect(getLink().textContent).toContain('Custom text');
});

describe('ThirdPartyExternalLink dialog', () => {
it('renders external link dialog', () => {
renderThirdPartyExternalLink();

userEvent.click(screen.getByRole('button'));
userEvent.click(getLink());

const dialog = screen.getByRole('dialog');
expect(dialog).toMatchSnapshot();
});

it('renders custom URL in Learn More link', () => {
renderThirdPartyExternalLink({ learnMoreUrl: 'https://www.google.com/' });

userEvent.click(screen.getByRole('button'));
userEvent.click(getLink());

const learnMoreLink = screen.getByText('Learn more about links to third-party sites');
expect(learnMoreLink.getAttribute('href')).toBe('https://www.google.com/');
});

it('renders confirmation link with custom href', () => {
renderThirdPartyExternalLink({ href: 'https://www.google.com/' });

userEvent.click(screen.getByRole('button'));
userEvent.click(getLink());

const confirmButton = screen.getByText('Continue');

expect(confirmButton.getAttribute('href')).toBe('https://www.google.com/');
});

it('closes on cancel', () => {
renderThirdPartyExternalLink();
userEvent.click(screen.getByRole('button'));

userEvent.click(getLink());

const dialog = screen.getByRole('dialog');
expect(dialog).toBeInTheDocument();

userEvent.click(screen.getByText('Cancel'));
expect(dialog).not.toBeInTheDocument();
});

it('renders custom origin text in heading and body', () => {
renderThirdPartyExternalLink({ origin: 'Custom origin' });

userEvent.click(screen.getByRole('button'));
userEvent.click(getLink());

const dialog = screen.getByRole('dialog');
const dialogHeading = screen.getByRole('heading');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button } from '../Button';
import { Dialog } from '../Dialog';
import { ExternalLinkIcon } from '../Icons';
import { t } from '../i18n';
import classNames from 'classnames';

interface ThirdPartyExternalLinkProps {
/** External link url. The destination. */
Expand All @@ -26,27 +27,30 @@ const ThirdPartyExternalLink = ({
origin,
}: ThirdPartyExternalLinkProps) => {
const [showDialog, setShowDialog] = useState(false);
function open(event: React.SyntheticEvent<any>) {
event.preventDefault();
setShowDialog(true);
}
function close() {
setShowDialog(false);
}

return (
<>
<Button variation="ghost" onClick={() => setShowDialog(true)} className={className}>
<a className={classNames('ds-c-external-link', className)} onClick={open} href={href}>
{children}
<ExternalLinkIcon className="ds-u-margin-left--05" />
</Button>
<ExternalLinkIcon className="ds-c-external-link__icon" />
</a>
{showDialog && (
<Dialog
onExit={() => setShowDialog(false)}
onExit={close}
heading={t('thirdPartyExternalLink.dialogHeading', { origin })}
closeButtonText=""
actions={[
<Button variation="solid" key="external-link__confirm" href={href}>
{t('thirdPartyExternalLink.confirmationButtonText')}
</Button>,
<Button
variation="ghost"
onClick={() => setShowDialog(false)}
key="external-link__cancel"
>
<Button variation="ghost" onClick={close} key="external-link__cancel">
{t('thirdPartyExternalLink.cancelButtonText')}
</Button>,
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ exports[`ThirdPartyExternalLink ThirdPartyExternalLink dialog renders external l
`;

exports[`ThirdPartyExternalLink renders external link 1`] = `
<button
class="ds-c-button ds-c-button--ghost"
type="button"
<a
class="ds-c-external-link"
href="foo"
>
External site link
<svg
aria-hidden="false"
aria-labelledby="icon-1__title"
class="ds-c-icon ds-c-icon--external-link ds-u-margin-left--05"
class="ds-c-icon ds-c-icon--external-link ds-c-external-link__icon"
focusable="false"
id="icon-1"
role="img"
Expand All @@ -107,5 +107,5 @@ exports[`ThirdPartyExternalLink renders external link 1`] = `
d="M497.6,0,334.4.17A14.4,14.4,0,0,0,320,14.57V47.88a14.4,14.4,0,0,0,14.69,14.4l73.63-2.72,2.06,2.06L131.52,340.49a12,12,0,0,0,0,17l23,23a12,12,0,0,0,17,0L450.38,101.62l2.06,2.06-2.72,73.63A14.4,14.4,0,0,0,464.12,192h33.31a14.4,14.4,0,0,0,14.4-14.4L512,14.4A14.4,14.4,0,0,0,497.6,0ZM432,288H416a16,16,0,0,0-16,16V458a6,6,0,0,1-6,6H54a6,6,0,0,1-6-6V118a6,6,0,0,1,6-6H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V304A16,16,0,0,0,432,288Z"
/>
</svg>
</button>
</a>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@use '../layout' as *;

.ds-c-external-link__icon {
height: 1em;
margin-inline-start: $spacer-1;
position: relative;
top: -2px;
width: 1em;
}
1 change: 1 addition & 0 deletions packages/design-system/src/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
// Mask, LabelMask and Date field styles need useed
// after TextField since they modify it
@use 'components/TextField';
@use 'components/ThirdPartyExternalLink';
@use 'components/Tooltip';
@use 'components/TooltipIcon';
@use 'components/Mask';
Expand Down
7 changes: 7 additions & 0 deletions packages/docs/src/components/content/ContentRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import Prism from 'prismjs';
import { ThirdPartyExternalLink } from '@cmsgov/design-system';

import { MDXRenderer } from 'gatsby-plugin-mdx';
import { MDXProvider } from '@mdx-js/react';
Expand Down Expand Up @@ -67,6 +68,12 @@ const TextWithMaxWidth = (props: any, Component) => {
* Each mapping has a key with the element name and a value of a functional component to be used for that element
*/
const customComponents = (theme) => ({
a: (props) => {
if (props.href.startsWith('http')) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be more discriminating and not put the external link treatment on .gov sites like healthcare.gov.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did it in 670e9cb

return <ThirdPartyExternalLink {...props} />;
}
return <a {...props} />;
},
ButtonMigrationTable: (props) => <ButtonMigrationTable theme={theme} {...props} />,
ButtonVariationsTable: (props) => <ButtonVariationsTable theme={theme} {...props} />,
code: CodeWithSyntaxHighlighting,
Expand Down