Skip to content

Commit

Permalink
fix(button): conditionally use external link icon for anchor buttons (#…
Browse files Browse the repository at this point in the history
…1363)

* fix(button): conditionally use external icon for as="a" buttons

* docs(website): update docs for buttons as="a"

* chore: changeset
  • Loading branch information
TheSisb committed Apr 14, 2021
1 parent 2baa0d7 commit 174be6d
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 46 deletions.
6 changes: 6 additions & 0 deletions .changeset/happy-phones-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@twilio-paste/button': patch
'@twilio-paste/core': patch
---

Buttons that behave as links (<Button as="a" href="">) now correctly use the external link icon for external links.
23 changes: 15 additions & 8 deletions packages/paste-core/components/button/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Spinner} from '@twilio-paste/spinner';
import {secureExternalLink} from '@twilio-paste/anchor';
import {useSpring, animated} from '@twilio-paste/animation-library';
import {ArrowForwardIcon} from '@twilio-paste/icons/esm/ArrowForwardIcon';
import {LinkExternalIcon} from '@twilio-paste/icons/esm/LinkExternalIcon';
import type {
ButtonProps,
ButtonSizes,
Expand Down Expand Up @@ -197,22 +198,28 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) =>
const showLoading = buttonState === 'loading';
const showDisabled = buttonState !== 'default';
const ButtonComponent = getButtonComponent(variant);
const externalLinkProps = props.href != null ? secureExternalLink(props.href) : null;

// Automatically inject AnchorForwardIcon for link's dressed as buttons when possible
const injectIconChildren =
props.as === 'a' && props.href != null && typeof children === 'string' && variant !== 'reset' ? (
let injectIconChildren = children;
if (props.as === 'a' && props.href != null && typeof children === 'string' && variant !== 'reset') {
injectIconChildren = (
<>
{children}
<AnimatedBox style={arrowIconStyles}>
<ArrowForwardIcon decorative />
</AnimatedBox>
{externalLinkProps != null ? (
<LinkExternalIcon decorative={false} title="link takes you to an external page" />
) : (
<AnimatedBox style={arrowIconStyles}>
<ArrowForwardIcon decorative />
</AnimatedBox>
)}
</>
) : (
children
);
}

return (
<ButtonComponent
{...(rest.href != null ? secureExternalLink(rest.href) : null)}
{...externalLinkProps}
{...rest}
onMouseEnter={(event) => {
if (typeof rest.onMouseEnter === 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,12 @@ export const ButtonAsAnchor = (): React.ReactNode => {
<>
<Box padding="space30">
<Button as="a" href="https://twilio.com" variant="primary">
Automatically adds link icon
Automatically adds external link icon
</Button>
</Box>
<Box padding="space30">
<Button as="a" href="https://twilio.com" variant="secondary">
Automatically adds link icon
<Button as="a" href="/" variant="secondary">
Automatically adds internal link icon
</Button>
</Box>
<Box padding="space30">
Expand Down
30 changes: 30 additions & 0 deletions packages/paste-website/src/pages/components/button/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,36 @@ Use link-style buttons when other types of buttons may be too distracting.
</Stack>`}
</LivePreview>

#### Buttons with link functionality

<Callout>
<CalloutTitle as="h4">Hot accessibility tip</CalloutTitle>
<CalloutText>
To reiterate, be mindful when choosing this variant as dictation software users may experience usability issues.
Read the guidelines first.
</CalloutText>
</Callout>

Buttons that **navigate** the user can only be represented by the `primary` and `secondary` variants.
When this is used, it must be accompanied by an arrow pointing to the right or an external
link icon after the text. The icons help to indicate that the action performed on click is a navigation.
These button types do not have `disabled` or `loading` states, as anchors cannot be in those states.

To create a button-styled anchor, use the Button component and add the `as="a"` prop so that it is rendered as an anchor semantically, while maintaining button styling.

**Note:** The same guidance applies for any action deemed "primary"; use only one per page.

<LivePreview scope={{Button, Stack}} language="jsx">
{`<Stack orientation="horizontal" spacing="space30">
<Button as="a" href="#" variant="primary">
Button as anchor (internal link)
</Button>
<Button as="a" href="https://twilio.com" variant="secondary">
Button as anchor (external link)
</Button>
</Stack>`}
</LivePreview>

#### Small button

Use small buttons sparingly, only when needed for vertical density.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ If you decide to place an icon in a button, all button icons should be placed to

<LivePreview scope={{Button, PlusIcon, Stack}} language="jsx">
{`<Stack orientation="horizontal" spacing="space30">
<Button variant="primary">
<Button variant="primary" onClick={() => {}}>>
Primary
</Button>
<Button variant="primary">
<Button variant="primary" onClick={() => {}}>>
<PlusIcon decorative />
Create new
</Button>
Expand All @@ -173,7 +173,7 @@ Most actions on a page are secondary buttons. Use them for safe and easily rever
There are some cases where a secondary button may be used for an irreversible action. An example of this would be a ‘Cancel' secondary button that may cause a customer to redo a long user flow. In this case, it should trigger an "Are you sure?" dialog or something similar. Check out the [delete pattern](/patterns/delete) for more guidance. This would be best to test with your users.

<LivePreview scope={{Button}} language="jsx">
{`<Button variant="secondary">
{`<Button variant="secondary" onClick={() => {}}>>
Secondary
</Button>`}
</LivePreview>
Expand All @@ -198,65 +198,66 @@ When making this decision, it's **a great opportunity to test** which buttons yo

<LivePreview scope={{Button, Stack}} language="jsx">
{`<Stack orientation="horizontal" spacing="space30">
<Button variant="link">
<Button variant="link" onClick={() => {}}>
Link-style button
</Button>
<Button variant="destructive_link">
<Button variant="destructive_link" onClick={() => {}}>
Link-style button
</Button>
</Stack>`}
</LivePreview>

## Anchors

### Basic Anchor

For all anchors, the only functionality it supports is navigating. For this reason, an external link is generally the only icon that should be paired with anchors.

<LivePreview scope={{Anchor, Stack}} language="jsx">
{`<Stack orientation="horizontal" spacing="space30">
<Anchor href="#">
Anchor
</Anchor>
<Anchor href="#" showExternal>
External anchor
</Anchor>
</Stack>`}
</LivePreview>

![card layout with internal and external anchors](../../../assets/images/patterns/button-card-external-anchor.png)

### Anchors that look like buttons
### Buttons with link functionality

<Callout variant="warning">
<CalloutTitle>🚨 This is not a variant we recommend you use often.</CalloutTitle>
<CalloutText>
When using anchors that look like buttons, be very strategic in their use as dictation software users may experience
usability issues.
When using buttons as anchors, be very strategic in their use and placement. Screen dictation software may
experience usability issues, as it is confusing to hear a link read out when looking at a button.
</CalloutText>
</Callout>

There are two variants of an anchor that look like a primary and secondary button. These are to be used when the primary action on the page is **navigating** the user somewhere. When this is used, it must be accompanied by an arrow pointing to the right after the text. This helps to indicate that we are navigating the user to the page in the button.

To create a button-styled anchor, use the Button component and add the as="a" prop so that it is rendered as an anchor semantically, while maintaining button styling.
Buttons that **navigate** the user can only be represented by the `primary` and `secondary` variants.
When this is used, it must be accompanied by an arrow pointing to the right or an external
link icon after the text. The icons help to indicate that the action performed on click is a navigation.
These button types do not have `disabled` or `loading` states, as anchors cannot be in those states.

The same guidance applies here for any action deemed "primary": use only one per page. If you have a primary button and a primary anchor styled as a button, only use one. Be sure to research which action is most important for your users.
To create a button-styled anchor, use the Button component and add the `as="a"` prop so that it is rendered as an anchor semantically, while maintaining button styling.

**Note:** Anchors that look like Buttons don't have the same disabled or loading functionality.
**Note:** The same guidance applies for any action deemed "primary"; use only one per page.

<LivePreview scope={{Button, Stack}} language="jsx">
{`<Stack orientation="horizontal" spacing="space30">
<Button as="a" href="#" variant="primary">
Button as anchor
Button as anchor (internal link)
</Button>
<Button as="a" href="#" variant="secondary">
Button as anchor
<Button as="a" href="https://twilio.com" variant="secondary">
Button as anchor (external link)
</Button>
</Stack>`}
</LivePreview>

![layout with anchors and anchors that look like buttons](../../../assets/images/patterns/button-anchor-like-buttons.png)

## Anchors

### Basic Anchor

For all anchors, the only functionality it supports is navigating. For this reason, an external link is generally the only icon that should be paired with anchors.

<LivePreview scope={{Anchor, Stack}} language="jsx">
{`<Stack orientation="horizontal" spacing="space30">
<Anchor href="#">
Anchor
</Anchor>
<Anchor href="#" showExternal>
External anchor
</Anchor>
</Stack>`}
</LivePreview>

![card layout with internal and external anchors](../../../assets/images/patterns/button-card-external-anchor.png)

### Anchors in text

Anchors should be the **ONLY** thing we underline in text, because underlined static text could be mistaken for a link. Links can be used within bodies of text to indicate the presence of related content. If you're linking to an external page, use the [external anchor](/components/anchor#external-anchor).
Expand Down

2 comments on commit 174be6d

@vercel
Copy link

@vercel vercel bot commented on 174be6d Apr 14, 2021

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

paste – ./

paste-twilio-dsys.vercel.app
paste-git-main-twilio-dsys.vercel.app
paste.twilio.design

@vercel
Copy link

@vercel vercel bot commented on 174be6d Apr 14, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.