Skip to content

Commit

Permalink
feat(components): add Anchor component
Browse files Browse the repository at this point in the history
feature/button-enums

feature/button-enums

feature/button-enums
  • Loading branch information
connor-baer committed May 1, 2020
1 parent 8ee2d45 commit 58ae91f
Show file tree
Hide file tree
Showing 9 changed files with 540 additions and 0 deletions.
121 changes: 121 additions & 0 deletions src/__snapshots__/storyshots.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -24478,6 +24478,127 @@ HTMLCollection [
]
`;

exports[`Storyshots Typography/Anchor As Button 1`] = `
.circuit-0 {
font-weight: 400;
margin-bottom: 16px;
font-size: 16px;
line-height: 24px;
display: inline-block;
-webkit-text-decoration: underline;
text-decoration: underline;
-webkit-text-decoration-skip-ink: auto;
text-decoration-skip-ink: auto;
border: 0;
outline: none;
background: none;
padding: 0;
margin-top: 0;
margin-left: 0;
margin-right: 0;
color: #1760CE;
}
@media (min-width:480px) {
.circuit-0 {
font-size: 16px;
line-height: 24px;
}
}
.circuit-0:hover,
.circuit-0:active {
color: #003C8B;
cursor: pointer;
}
.circuit-0:visited {
color: #8928A2;
}
.circuit-0:visited:hover,
.circuit-0:visited:active {
color: #5F1D6B;
}
.circuit-0:focus {
outline: 0;
box-shadow: 0 0 0 4px #AFD0FE;
border-radius: 5px;
}
.circuit-0:focus::-moz-focus-inner {
border: 0;
}
<button
class="circuit-0"
>
Say hello
</button>
`;

exports[`Storyshots Typography/Anchor As Link 1`] = `
.circuit-0 {
font-weight: 400;
margin-bottom: 16px;
font-size: 16px;
line-height: 24px;
display: inline-block;
-webkit-text-decoration: underline;
text-decoration: underline;
-webkit-text-decoration-skip-ink: auto;
text-decoration-skip-ink: auto;
border: 0;
outline: none;
background: none;
padding: 0;
margin-top: 0;
margin-left: 0;
margin-right: 0;
color: #1760CE;
}
@media (min-width:480px) {
.circuit-0 {
font-size: 16px;
line-height: 24px;
}
}
.circuit-0:hover,
.circuit-0:active {
color: #003C8B;
cursor: pointer;
}
.circuit-0:visited {
color: #8928A2;
}
.circuit-0:visited:hover,
.circuit-0:visited:active {
color: #5F1D6B;
}
.circuit-0:focus {
outline: 0;
box-shadow: 0 0 0 4px #AFD0FE;
border-radius: 5px;
}
.circuit-0:focus::-moz-focus-inner {
border: 0;
}
<a
class="circuit-0"
href="https://opensource.sumup.com"
>
View SumUp's OSS projects
</a>
`;

exports[`Storyshots Typography/Heading Base 1`] = `
.circuit-0 {
font-weight: 700;
Expand Down
21 changes: 21 additions & 0 deletions src/components/Anchor/Anchor.docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Status, Props, Story } from '../../../.storybook/components';
import Anchor from '.';

# Anchor

<Status.Stable />

Anchor is used to display a link or button that visually looks like a hyperlink.

<Story id="typography-anchor--as-link" />
<Props of={Anchor} />

## Usage guidelines

- **Do** use the `mega` size as the default for body text
- **Do** open the link in the same window unless it leads to an external source.
- **Do not** use in combination with the button component, use the tertiary button variant instead

### As button

<Story id="typography-anchor--as-button" />
105 changes: 105 additions & 0 deletions src/components/Anchor/Anchor.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Copyright 2020, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';

import {
create,
render,
renderToHtml,
axe,
RenderFn,
act,
userEvent
} from '../../util/test-utils';

import { Anchor, AnchorProps } from './Anchor';

describe('Anchor', () => {
function renderAnchor(renderFn: RenderFn, props: AnchorProps) {
return renderFn(<Anchor {...props} />);
}

const baseProps = { children: 'Anchor' };

describe('styles', () => {
it('should render as a `span` when neither href nor onClick is passed', () => {
const actual = renderAnchor(create, baseProps);
expect(actual).toMatchSnapshot();
});

it('should render as an `a` when an href (and onClick) is passed', () => {
const props = {
...baseProps,
href: 'https://sumup.com',
onClick: jest.fn()
};
const actual = renderAnchor(create, props);
expect(actual).toMatchSnapshot();
});

it('should render as a `button` when an onClick is passed', () => {
const props = { ...baseProps, onClick: jest.fn() };
const actual = renderAnchor(create, props);
expect(actual).toMatchSnapshot();
});
});

describe('business logic', () => {
it('should call the onClick handler when rendered as a link', () => {
const props = {
...baseProps,
href: 'https://sumup.com',
onClick: jest.fn(event => event.preventDefault()),
'data-testid': 'anchor'
};
const { getByTestId } = renderAnchor(render, props);

act(() => {
userEvent.click(getByTestId('anchor'));
});

expect(props.onClick).toHaveBeenCalledTimes(1);
});

it('should call the onClick handler when rendered as a button', () => {
const props = {
...baseProps,
onClick: jest.fn(),
'data-testid': 'anchor'
};
const { getByTestId } = renderAnchor(render, props);

act(() => {
userEvent.click(getByTestId('anchor'));
});

expect(props.onClick).toHaveBeenCalledTimes(1);
});
});

describe('accessibility', () => {
it('should meet accessibility guidelines', async () => {
const props = {
...baseProps,
href: 'https://sumup.com',
onClick: jest.fn()
};
const wrapper = renderAnchor(renderToHtml, props);
const actual = await axe(wrapper);
expect(actual).toHaveNoViolations();
});
});
});
38 changes: 38 additions & 0 deletions src/components/Anchor/Anchor.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright 2020, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React, { FunctionComponent } from 'react';

import Anchor from '.';
import docs from './Anchor.docs.mdx';

export default {
title: 'Typography/Anchor',
component: Anchor,
parameters: {
docs: { page: docs },
jest: ['Anchor']
}
};

export const AsLink: FunctionComponent = () => (
<Anchor href="https://opensource.sumup.com">
{`View SumUp's OSS projects`}
</Anchor>
);

export const AsButton: FunctionComponent = () => (
<Anchor onClick={() => alert('Hello')}>Say hello</Anchor>
);
91 changes: 91 additions & 0 deletions src/components/Anchor/Anchor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Copyright 2020, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React, { HTMLProps, ReactNode, ReactElement } from 'react';
import { css } from '@emotion/core';

import styled, { StyleProps } from '../../styles/styled';
import { focusOutline } from '../../styles/style-helpers';
import Text from '../Text';
import { TextProps } from '../Text/Text';
import { useComponents } from '../ComponentsContext';

interface BaseProps extends TextProps {
children: ReactNode;
}

type LinkElProps = Omit<HTMLProps<HTMLAnchorElement>, 'size'>;
type ButtonElProps = Omit<HTMLProps<HTMLButtonElement>, 'size'>;

export type AnchorProps = BaseProps & LinkElProps & ButtonElProps;

type ReturnType = ReactElement<any, any> | null;

const baseStyles = ({ theme }: StyleProps) => css`
display: inline-block;
text-decoration: underline;
text-decoration-skip-ink: auto;
border: 0;
outline: none;
background: none;
padding: 0;
margin-top: 0;
margin-left: 0;
margin-right: 0;
color: ${theme.colors.p700};
&:hover,
&:active {
color: ${theme.colors.p900};
cursor: pointer;
}
&:visited {
color: ${theme.colors.v700};
&:hover,
&:active {
color: ${theme.colors.v900};
}
}
&:focus {
${focusOutline({ theme })};
border-radius: ${theme.borderRadius.giga};
}
`;

const BaseAnchor = styled(Text)<AnchorProps>(baseStyles);

/**
* The Anchor is used to display a link or button that visually looks like
* a hyperlink. Based on the Text component, so it also supports its props.
*/
export function Anchor(props: BaseProps & LinkElProps): ReturnType;
export function Anchor(props: BaseProps & ButtonElProps): ReturnType;
export function Anchor(props: AnchorProps): ReturnType {
const { Link } = useComponents();
const AnchorLink = BaseAnchor.withComponent(Link);

if (!props.href && !props.onClick) {
return <Text as="span" {...props} />;
}

if (props.href) {
return <AnchorLink {...props} />;
}

return <BaseAnchor as="button" {...props} />;
}
Loading

0 comments on commit 58ae91f

Please sign in to comment.