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

Add hyp-link pattern and Link component #211

Merged
merged 2 commits into from
Oct 18, 2021
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
33 changes: 33 additions & 0 deletions src/components/Link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import classnames from 'classnames';

/**
* @typedef {import('preact').ComponentChildren} Children
*
* @typedef LinkBaseProps
* @prop {Children} children
* @prop {string} [classes] - Additional CSS classes to apply
* @prop {import('preact').Ref<HTMLAnchorElement>} [linkRef] - Optional ref for
* the rendered anchor element
*/

/**
* @typedef {LinkBaseProps & import('preact').JSX.HTMLAttributes<HTMLAnchorElement>} LinkProps
*/

/**
* Style and add some attributes to an anchor (`<a>`) element
*
* @param {LinkProps} props
*/
export function Link({ children, classes = '', linkRef, ...restProps }) {
return (
<a
className={classnames('Hyp-Link', classes)}
ref={linkRef}
rel="noopener noreferrer"
{...restProps}
>
{children}
</a>
);
}
43 changes: 43 additions & 0 deletions src/components/test/Link-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { mount } from 'enzyme';
import { createRef } from 'preact';

import { Link } from '../Link';

describe('Link', () => {
const createComponent = (props = {}) => {
return mount(
<Link href="http://www.example.com" {...props}>
This is content inside of a Link
</Link>
);
};

it('renders children with appropriate classnames', () => {
const wrapper = createComponent();

assert.isTrue(wrapper.find('a').first().hasClass('Hyp-Link'));
});

it('applies extra classes', () => {
const wrapper = createComponent({ classes: 'foo bar' });
assert.deepEqual(
[...wrapper.find(`a.Hyp-Link.foo.bar`).getDOMNode().classList.values()],
['Hyp-Link', 'foo', 'bar']
);
});

it('passes along a `ref` to the `a` element through `linkRef`', () => {
const linkRef = createRef();
createComponent({ linkRef });

assert.instanceOf(linkRef.current, Node);
});

it('adds common `rel` attributes', () => {
const wrapper = createComponent();
assert.equal(
wrapper.find('a').first().getDOMNode().getAttribute('rel'),
'noopener noreferrer'
);
});
});
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { IconButton, LabeledButton, LinkButton } from './components/buttons';
export { LabeledCheckbox, Checkbox } from './components/Checkbox';
export { Frame, Card, Actions, Scrollbox } from './components/containers';
export { Dialog } from './components/Dialog';
export { Link } from './components/Link';
export { Modal, ConfirmModal } from './components/Modal';
export { Panel } from './components/Panel';
export { Spinner } from './components/Spinner';
Expand Down
5 changes: 3 additions & 2 deletions src/pattern-library/components/LibraryHome.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Link } from '../..';
import Library from './Library';

export default function LibraryHome() {
Expand Down Expand Up @@ -30,8 +31,8 @@ export default function LibraryHome() {

<p>
<strong>Components</strong> are{' '}
<a href="https://preactjs.com/">Preact</a> components that are built
using underlying <strong>patterns</strong>.
<Link href="https://preactjs.com/">Preact</Link> components that are
built using underlying <strong>patterns</strong>.
</p>
</Library.Page>
);
Expand Down
12 changes: 6 additions & 6 deletions src/pattern-library/components/PlaygroundApp.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import classnames from 'classnames';

import { SvgIcon } from '../../components/SvgIcon';
import { Link, SvgIcon } from '../../';

import { getRoutes } from '../routes';
import { useRoute } from '../router';
Expand Down Expand Up @@ -53,25 +53,25 @@ export default function PlaygroundApp({
<main className="PlaygroundApp">
<div className="PlaygroundApp__sidebar">
<div className="PlaygroundApp__sidebar-home">
<a href={baseURL} onClick={e => navigate(e, '/')}>
<Link href={baseURL} onClick={e => navigate(e, '/')}>
<SvgIcon name="logo" />
</a>
</Link>
</div>
{routeGroups.map(rGroup => (
<div key={rGroup.title}>
<h2>{rGroup.title}</h2>
<ul>
{rGroup.routes.map(({ route, title }) => (
<li key={title}>
<a
className={classnames('PlaygroundApp__nav-item', {
<Link
classes={classnames('hyp-link PlaygroundApp__nav-item', {
'is-active': activeRoute.route === route,
})}
href={`${route}`}
onClick={e => navigate(e, route)}
>
{title}
</a>
</Link>
</li>
))}
</ul>
Expand Down
20 changes: 20 additions & 0 deletions src/pattern-library/components/patterns/LinkComponents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Link } from '../../../';
import Library from '../Library';

export default function LinkComponents() {
return (
<Library.Page title="Links">
<Library.Pattern title="Link">
<p>
<code>Link</code> components provide some common styling and attribute
for anchor (<code>a</code>) elements.
</p>
<Library.Example>
<Library.Demo withSource>
<Link href="https://www.example.com">A link to somewhere</Link>
</Library.Demo>
</Library.Example>
</Library.Pattern>
</Library.Page>
);
}
24 changes: 24 additions & 0 deletions src/pattern-library/components/patterns/LinkPatterns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Library from '../Library';

export default function LinkPatterns() {
return (
<Library.Page title="Links">
<Library.Pattern title="Link">
<p>
The link pattern for <code>{'<a>'}</code> tags uses brand red and no
underline, as well as a rounded keyboard focus ring.
</p>
<Library.Example>
<Library.Demo withSource>
<a className="hyp-link" href="http://www.example.com">
This is a link
</a>
<a className="hyp-link" href="http://www.example.com">
This is another link
</a>
</Library.Demo>
</Library.Example>
</Library.Pattern>
</Library.Page>
);
}
14 changes: 14 additions & 0 deletions src/pattern-library/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import LayoutFoundations from './components/patterns/LayoutFoundations';

import FormPatterns from './components/patterns/FormPatterns';
import ContainerPatterns from './components/patterns/ContainerPatterns';
import LinkPatterns from './components/patterns/LinkPatterns';
import PanelPatterns from './components/patterns/PanelPatterns';
import SpinnerPatterns from './components/patterns/SpinnerPatterns';
import TablePatterns from './components/patterns/TablePatterns';
Expand All @@ -14,6 +15,7 @@ import ButtonComponents from './components/patterns/ButtonComponents';
import ContainerComponents from './components/patterns/ContainerComponents';
import DialogComponents from './components/patterns/DialogComponents';
import FormComponents from './components/patterns/FormComponents';
import LinkComponents from './components/patterns/LinkComponents';
import PanelComponents from './components/patterns/PanelComponents';
import SpinnerComponents from './components/patterns/SpinnerComponents';
import TableComponents from './components/patterns/TableComponents';
Expand Down Expand Up @@ -63,6 +65,12 @@ const routes = [
component: FormPatterns,
group: 'patterns',
},
{
route: '/patterns-links',
title: 'Links',
component: LinkPatterns,
group: 'patterns',
},
{
route: '/patterns-panels',
title: 'Panels',
Expand Down Expand Up @@ -111,6 +119,12 @@ const routes = [
component: FormComponents,
group: 'components',
},
{
route: '/components-links',
title: 'Links',
component: LinkComponents,
group: 'components',
},
{
route: '/components-panel',
title: 'Panel',
Expand Down
26 changes: 0 additions & 26 deletions styles/base/_elements.scss
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
@use '../mixins/focus';
@use "../variables" as var;

$-border-radius: var.$border-radius;
$-color-link: var.$color-link;
$-color-link--hover: var.$color-link--hover;

// basic standard styling for elements
// TODO: Evaluate if dependencies on variables can be reduced or eliminated
a {
@include focus.outline-on-keyboard-focus;
// Ensure the tag has width in order for :focus styling to render correctly.
display: inline-block;
color: $-color-link;
text-decoration: none;
// Give links a radius to allow :focus styling to appear similar to that of buttons
border-radius: $-border-radius;
}

a:active,
a:focus {
text-decoration: none;
}

a:hover {
text-decoration: none;
color: $-color-link--hover;
}

p {
hyphens: auto;

Expand Down
5 changes: 5 additions & 0 deletions styles/components/Link.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@use '../mixins/patterns/links';

.Hyp-Link {
@include links.link;
}
1 change: 1 addition & 0 deletions styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@use './components/Checkbox';
@use './components/containers';
@use './components/Dialog';
@use './components/Link';
@use './components/Modal';
@use './components/Panel';
@use './components/Spinner';
Expand Down
27 changes: 27 additions & 0 deletions styles/mixins/patterns/_links.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@use '../../variables' as var;

@use '../focus';

$-border-radius: var.$border-radius;
$-color-link: var.$color-link;
$-color-link--hover: var.$color-link--hover;

@mixin link {
@include focus.outline-on-keyboard-focus;
// Ensure the tag has width in order for :focus styling to render correctly.
display: inline-block;
color: $-color-link;
text-decoration: none;
// Give links a radius to allow :focus styling to appear similar to that of buttons
border-radius: $-border-radius;

&:active,
&:focus {
text-decoration: none;
}

&:hover {
text-decoration: none;
color: $-color-link--hover;
}
}
5 changes: 5 additions & 0 deletions styles/patterns/_links.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@use '../mixins/patterns/links';

.hyp-link {
@include links.link;
}
1 change: 1 addition & 0 deletions styles/patterns/index.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@use 'containers';
@use 'forms';
@use 'links';
@use 'panels';
@use 'spinners';
@use 'tables';
Expand Down