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

[Link] Address issues identified during the verification process #217

Merged
merged 7 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 0 additions & 9 deletions src/components/Link/Link.less

This file was deleted.

109 changes: 69 additions & 40 deletions src/components/Link/Link.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,106 +5,135 @@ import {
Link,
LinkText,
List,
ListLink as ListLinkComponent
ListLink
} from '~/src/index';

const meta: Meta<typeof Link> = {
title: 'Components (Draft)/Links',
component: Link,
parameters: {
docs: {
description: {
component: `
### CFPB DS Link component

https://cfpb.github.io/design-system/components/links
`
}
}
}
component: Link
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
const DefaultArguments = {
args: {
href: '#',
children: <LinkText>Link Text</LinkText>
children: 'Link Text'
}
};

export const ListLink: Story = {
export const Inline: Story = {
name: 'Inline links',
render: () => (
<p>
Here&apos;s the <Link href='/#'>default link style</Link>. For reference,
here&apos;s the{' '}
<Link href='/#' className='hover'>
hover link style
</Link>
. Train your eyes on the{' '}
<Link href='/#' className='focus'>
focused link style
</Link>
. Jump to the{' '}
<Link href='/#' className='active'>
active link style
</Link>
. We&apos;ve all been to the{' '}
<Link href='/#' className='visited'>
visited link style
</Link>
.
</p>
)
};

export const CallToAction: Story = {
name: 'Call-to-action links',
args: {
...Default.args
...DefaultArguments.args
},
render: arguments_ => (
<List isLinks>
<ListLinkComponent {...arguments_} />
<ListLink {...arguments_}>Sample call-to-action link</ListLink>
<ListLink {...arguments_}>Another sample call-to-action link</ListLink>
</List>
)
};

export const Destructive: Story = {
name: 'Destructive links',
args: {
...Default.args
...DefaultArguments.args,
children: 'Sample destructive link'
},
render: arguments_ => <DestructiveLink {...arguments_} />
};

export const WithIcon: Story = {
export const StandardLinkWithIcon: Story = {
name: 'Standard link with icon',
args: {
...Default.args,
hasIcon: true,
type: 'default'
...DefaultArguments.args,
hasIcon: true
},
render: arguments_ => (
<Link {...arguments_}>
<LinkText>Download file</LinkText> <Icon name='download' />
</Link>
<>
The document icon should emphasize a link that contains a{' '}
<Link {...arguments_}>
<LinkText>file or document</LinkText> <Icon name='download' />
</Link>
. Use the external link icon to emphasize{' '}
<Link {...arguments_}>
<LinkText>a non-CFPB webpage</LinkText> <Icon name='external-link' />
</Link>{' '}
for further details.
</>
)
};

export const WithIconNoWrapping: Story = {
export const StandardLinkWithIconNoWrapping: Story = {
name: 'Non-wrapping icon links',
args: {
...WithIcon.args,
...StandardLinkWithIcon.args,
noWrap: true
},
render: arguments_ => (
<Link {...arguments_}>
<LinkText>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Obcaecati
incidunt explicabo, odio delectus quia magnam non . teeeeext
</LinkText>

<Icon name='document' />
</Link>
<>
The document icon should emphasize a link that contains a{' '}
<Link {...arguments_}>
<LinkText>file or document</LinkText> <Icon name='download' />
</Link>
.
</>
)
};

export const JumpLink: Story = {
name: 'Jump link',
args: {
...Default.args,
...DefaultArguments.args,
isJump: true
},
render: arguments_ => (
<Link {...arguments_}>
<LinkText>Jump link</LinkText>
<LinkText>Default jump link</LinkText>&nbsp;
<Icon name='right' />
</Link>
)
};

export const JumpLinkIconLeft: Story = {
name: 'Jump link with icon on left',
args: {
...Default.args,
...DefaultArguments.args,
isJumpLeft: true
},
render: arguments_ => (
<Link {...arguments_}>
<Icon name='left' />
<LinkText>Jump link</LinkText>
&nbsp;<LinkText>Jump link with icon on left</LinkText>
</Link>
)
};
101 changes: 101 additions & 0 deletions src/components/Link/Link.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { DestructiveLink, Link, LinkText, ListLink } from '~/src/index';

describe('<Link />', () => {
const linkBaseProperties = {
href: '/#',
'data-testid': 'link-test-id'
};

const testId = linkBaseProperties['data-testid'];

it('Type: "default"', () => {
render(<Link {...linkBaseProperties} />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link');
expect(link).toHaveAttribute('href', '/#');
});

it('Type: "destructive"', () => {
render(<Link {...linkBaseProperties} type='destructive' />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link a-btn a-btn__link a-btn__warning');
});

it('Type: "list"', () => {
render(<Link {...linkBaseProperties} type='list' />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('m-list_link');
expect(link).not.toHaveClass('a-link');
});

it('Option: noWrap - it adds classnames', () => {
render(<Link {...linkBaseProperties} noWrap />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link__no-wrap');
});

it('Option: isJump - it adds classnames', () => {
render(<Link {...linkBaseProperties} isJump />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link__jump a-link__icon-after-text');
});

it('Option: isJumpLeft - it adds classnames', () => {
render(<Link {...linkBaseProperties} isJumpLeft />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link__jump a-link__icon-before-text');
});

it('Option: hasIcon - it adds classnames', () => {
render(<Link {...linkBaseProperties} hasIcon />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link a-link__icon');
});

it('Other: propagates other attributes', () => {
render(<Link {...linkBaseProperties} target='_blank' />);
const link = screen.getByTestId(testId);
expect(link).toHaveAttribute('target', '_blank');
});
});

describe('<LinkText>', () => {
it('includes appropriate classnames', () => {
render(<LinkText data-testid='link-text'>Test text</LinkText>);
const linkText = screen.getByTestId('link-text');
expect(linkText).toHaveClass('a-link_text');
expect(linkText).toHaveTextContent('Test text');
});
});

describe('<ListLink>', () => {
const testId = 'list-link';

it('includes all expected elements', () => {
render(<ListLink data-testid={testId}>Test text</ListLink>);
// ListItem
const listItem = screen.getByRole('listitem');
expect(listItem).toBeInTheDocument();
expect(listItem).toHaveTextContent('Test text');

// Link
const linkText = screen.getByTestId(testId);
expect(linkText).toHaveClass('m-list_link');
expect(linkText).toHaveTextContent('Test text');
});
});

describe('<DestructiveLink>', () => {
const testId = 'destructive-link';

it('includes all expected elements', () => {
render(<DestructiveLink data-testid={testId}>Test text</DestructiveLink>);
const linkDestructive = screen.getByTestId(testId);
expect(linkDestructive).toHaveClass(
'a-link a-btn a-btn__link a-btn__warning'
);
expect(linkDestructive).toHaveTextContent('Test text');
});
});
6 changes: 5 additions & 1 deletion src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import classnames from 'classnames';
import type { JSXElement } from '../../types/jsxElement';
import ListItem from '../List/ListItem';
import './Link.less';

interface LinkProperties extends React.HTMLProps<HTMLAnchorElement> {
type?: 'default' | 'destructive' | 'list';
Expand All @@ -11,6 +10,11 @@ interface LinkProperties extends React.HTMLProps<HTMLAnchorElement> {
isJumpLeft?: boolean;
}

/**
* Links lead users to a different page or further information. In contrast, buttons are used to signal actions. Users should be able to identify links without relying on color or styling alone.
*
* Source: https://cfpb.github.io/design-system/components/links
*/
export default function Link({
children,
type = 'default',
Expand Down