Skip to content

Commit

Permalink
feat(core-button): add new variants
Browse files Browse the repository at this point in the history
  • Loading branch information
janeszelag authored and jraff committed Nov 4, 2020
1 parent 8b8ad6a commit 0ba3c31
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 41 deletions.
159 changes: 132 additions & 27 deletions packages/Button/Button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ import styled from 'styled-components'
import { componentWithName, or, htmlElement } from '@tds/util-prop-types'
import { borders, forms } from '@tds/shared-styles'
import { medium, boldFont } from '@tds/shared-typography'
import { colorPrimary, colorSecondary, colorWhite, colorText } from '@tds/core-colours'
import {
colorPrimary,
colorSecondary,
colorWhite,
colorText,
colorAccessibleGreen,
colorTelusPurple,
colorCardinal,
} from '@tds/core-colours'
import { safeRest } from '@tds/util-helpers'

import { warn } from '../../shared/utils/warn'
Expand All @@ -18,43 +26,135 @@ const preventDisabling = ({ disabled, ...props }) => {
return props
}

export const StyledButton = styled.button(
borders.none,
borders.rounded,
medium,
boldFont,
forms.font,
forms.baseButton,
({ variant }) => {
let backgroundColor
let color
const hover = {
boxShadow: '0 0 0 1px',
}

if (variant === 'primary') {
const getVariant = ({ variant, rank }) => {
let backgroundColor
let color
let border
let height
let paddingTop
let paddingBottom
let transition
const hover = {}
const active = {}
const focus = {}
if (
variant === 'standard' ||
variant === 'brand' ||
variant === 'inverse' ||
variant === 'danger'
) {
height = '3.25rem'
paddingTop = '0.8125rem'
paddingBottom = '0.9375rem'
focus.outline = 'none !important'
transition = 'background 0.2s, color 0.2s, border 0.2s ease'
} else {
hover.boxShadow = '0 0 0 0.0625rem'
}

switch (variant) {
case 'primary':
backgroundColor = colorPrimary
color = colorWhite
hover.backgroundColor = colorWhite
hover.color = colorPrimary
} else if (variant === 'secondary') {
break

case 'secondary':
backgroundColor = colorSecondary
color = colorWhite
hover.backgroundColor = colorWhite
hover.color = colorSecondary
} else {
break

case 'inverted':
backgroundColor = colorWhite
color = colorText
hover.backgroundColor = 'transparent'
hover.color = colorWhite
}
break
case 'standard':
if (rank === 'main') {
backgroundColor = colorAccessibleGreen
color = colorWhite
hover.backgroundColor = '#1F5C09'
hover.boxShadow = '0 0 0 0.125rem #1F5C09'
active.backgroundColor = '#163E06 !important'
focus.backgroundColor = '#1F5C09'
focus.boxShadow = `0 0 0 0.1875rem #509F33, 0 0 0 0.125rem ${colorWhite} inset`
} else {
backgroundColor = colorWhite
color = colorAccessibleGreen
border = `0.0625rem solid ${colorAccessibleGreen}`
hover.boxShadow = `0 0 0 0.125rem ${colorAccessibleGreen}`
active.backgroundColor = '#F4F9F2 !important'
active.color = '#1F5C09'
focus.border = '0.0625rem solid #509F33'
focus.boxShadow = `0 0 0 0.125rem #509F33, 0 0 0 0.125rem ${colorWhite} inset, 0 0 0 0.1875rem ${colorAccessibleGreen} inset`
}
break

case 'brand':
if (rank === 'main') {
backgroundColor = colorTelusPurple
color = colorWhite
hover.backgroundColor = '#371E4F'
hover.boxShadow = '0 0 0 0.125rem #371E4F'
active.backgroundColor = '#231332 !important'
focus.backgroundColor = '#371E4F'
focus.boxShadow = `0 0 0 0.1875rem #7C53A5 , 0 0 0 0.125rem ${colorWhite} inset`
} else {
backgroundColor = colorWhite
color = colorTelusPurple
border = `0.0625rem solid ${colorTelusPurple}`
hover.boxShadow = `0 0 0 0.125rem ${colorTelusPurple}`
active.color = '#371E4F'
active.backgroundColor = '#F2EFF4 !important'
focus.border = '0.0625rem solid #7C53A5'
focus.boxShadow = `0 0 0 0.125rem #7C53A5, 0 0 0 0.125rem ${colorWhite} inset, 0 0 0 0.1875rem ${colorTelusPurple} inset`
}
break

case 'danger':
backgroundColor = colorWhite
color = colorCardinal
border = `0.0625rem solid ${colorCardinal}`
hover.boxShadow = `0 0 0 0.125rem ${colorCardinal}`
active.color = '#770F1B'
active.backgroundColor = '#FFF6F8 !important'
focus.border = '0.0625rem solid #D7707B'
focus.boxShadow = `0 0 0 0.125rem #D7707B, 0 0 0 0.125rem ${colorWhite} inset, 0 0 0 0.1875rem ${colorCardinal} inset`
break

return {
backgroundColor,
color,
'&:hover': hover,
}
default:
break
}

return {
backgroundColor,
color,
border,
height,
paddingTop,
paddingBottom,
transition,
'&:hover': hover,
'&:active': active,
'&:focus': focus,
'@media (prefers-reduced-motion: reduce)': {
transition: 'none !important',
},
}
}

export const StyledButton = styled.button(
borders.none,
borders.rounded,
medium,
boldFont,
forms.font,
forms.baseButton,
getVariant
)

export const ButtonTextWrapper = styled.span({
Expand All @@ -64,11 +164,11 @@ export const ButtonTextWrapper = styled.span({
/**
* @version ./package.json
*/
const Button = forwardRef(({ type, variant, children, ...rest }, ref) => {
const Button = forwardRef(({ type, variant, rank, children, ...rest }, ref) => {
const restNoDisabled = preventDisabling(rest)

return (
<StyledButton {...safeRest(restNoDisabled)} variant={variant} type={type} ref={ref}>
<StyledButton {...safeRest(restNoDisabled)} variant={variant} rank={rank} type={type} ref={ref}>
<ButtonTextWrapper>{children}</ButtonTextWrapper>
</StyledButton>
)
Expand All @@ -84,7 +184,11 @@ Button.propTypes = {
/**
* The style.
*/
variant: PropTypes.oneOf(['primary', 'secondary', 'inverted']),
variant: PropTypes.oneOf(['primary', 'secondary', 'inverted', 'standard', 'brand', 'danger']),
/**
* More style.
*/
rank: PropTypes.oneOf(['main', 'common']),
/**
* The label. It can include the `A11yContent` component, strings, or strings wrapped in a `<span>`.
*/
Expand All @@ -94,6 +198,7 @@ Button.propTypes = {
Button.defaultProps = {
type: 'button',
variant: 'primary',
rank: 'common',
}

export default Button
100 changes: 88 additions & 12 deletions packages/Button/Button.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ Provide a function as the `onClick` prop to perform an action when clicked. **Av
### Usage criteria

- Use buttons to move through a transaction
- Aim to use only one primary button per page
- Keep the text short and able to fit on a single line (our recommendation is 2-24 characters)
- Aim to use only one primary button variant per page
- Keep the text short and precise. It should fit on a single line (our recommendation is 2-24 characters for French and English)
- Make use of the A11yContent core component to provide more written context for assistive technology users for when they navigate a page using only button landmarks
- Make sure the button text describes an action
- Buttons should not be disabled. Use inline (on blur) error messaging to provide feedback when the form is invalid
Buttons should not be disabled. Use inline (on blur) error messaging to provide feedback when the form is invalid

By default, Buttons will be displayed in the `primary` variant. Use primary buttons for the main action on a page or
in a form.
### OLD Button variants

**NOTE:** New Button variants have been introduced. Future projects should adopt the new variants, as the older variants will have a deprecation plan. Avoid using both new and old variants together within the same experience.

Currently, by default, Buttons will be displayed in the `Primary` variant.

```jsx
<Button>Submit</Button>
Expand All @@ -23,10 +27,61 @@ Specify the `variant` to create a button for secondary actions.
<Button variant="secondary">Find out more</Button>
```

### NEW Button variants and ranks

New Buttons and their associated colours are meant for different purposes. Choose different variants according to their context and rank.

#### Ranks

By default, new Button variants will be displayed in the `Common` `rank`. Use Common `rank` for medium to low emphasis.

Use `Main` rank for the most emphasis and main action on a page or in a form.

#### StandardCommon + StandardMain

Used mainly for marketing pages for commerce actions where the direct/immediate action is to purchase. It can also be used for login or registration as these will lead a user to pay their bill or add services.

Examples: TELUS.com, TELUS International, My TELUS

```jsx
<Button variant="standard">Standard Common</Button>
```

```jsx
<Button variant="standard" rank="main">
Standard Main
</Button>
```

#### BrandCommon + BrandMain

Used mainly for editorial, non-commerce-related actions where the direct/immediate action goes to a learn/info/editorial experience.

Examples: About TELUS, Annual Report, surveys, modals, Brand Resource Center, TELUS Blog

```jsx
<Button variant="brand">Brand Common</Button>
```

```jsx
<Button variant="brand" rank="main">
Brand Main
</Button>
```

#### DangerCommon

Reserved for applications, native apps, and internal tools. Cancel of private/legal/monetary effect. If an action can be undone, do not use this.

Examples: MyTELUS web & app, CASA

```jsx
<Button variant="danger">Danger</Button>
```

### Sizes

All buttons are inline, with a minimum width of 180px for screens larger than 768px. They will occupy 100% width of their
parent's width at the small viewport and below. Resize your browser window to see this behaviour.
All buttons are inline, with a minimum width of `180px` for screens larger than `768px`. They will occupy 100% width of their parent's width at the small viewport (≤ `767px`) and below. Resize your browser window to see this behaviour.

### Placing buttons on dark backgrounds

Expand All @@ -39,12 +94,33 @@ text is too low in the hover state.
<Button variant="inverted">Get started</Button>
```

### Using A11yContent
### Accessibility features

The Button component includes:

Use the `A11yContent` component to create invisible text that is read out loud by screen readers.
- High-contrast interactive states (default, active, focus) for all new button variants and ranks
- Makes use of the `<button>` element to provide contextual feedback to assistive technology
- Is compatible with the A11yContent core component to provide more written context to assistive technology without occupying too much visual space

```jsx
<Button>
Shop <A11yContent>iPhone</A11yContent>
</Button>
<Button variant="standard">Shop</Button>
```

### Disabled buttons

Buttons should not be disabled on TELUS experiences. The following is a list of reasons why:

- Disabled buttons fool users into thinking they can proceed with an action
- Disabled buttons are intentionally designed to be “hard to see”; however this is visually inaccessible
- Disabled buttons do not inherently give any feedback or reason why they are disabled
- They give design teams a reason to rush through error handling as opposed to providing the correct feedback to guide users to their next steps
- Disabled buttons may be cut off for low vision users who zoom-in on their devices causing confusion as to why they cannot proceed and important information to be missed
- Disabled buttons are usually not navigable with assistive technologies like screen readers and switches. This usually means that the disabled buttons will be skipped over

**What to do instead of using a disabled button:**

If you show the button in its disabled state with no explanation, there can be confusion as to why it is disabled. To avoid user confusion, implement full error handling and feedback to your interactive experiences.

To better express to a user that they cannot proceed with form submission, show the submit Button, but perform error validation when the user clicks it, and display all errors as a group within an InputFeedback component adjacent to the submit button.

This allows for clear actions laid out for the user to take and should include skip links for easy navigation to parts of the form that need action
6 changes: 6 additions & 0 deletions packages/Button/__tests__/Button.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ describe('Button', () => {
expect(button).toMatchSnapshot()
})

it('can be presented as one of the new allowed variants', () => {
const button = render(<Button variant="standard">Submit</Button>)

expect(button).toMatchSnapshot()
})

it('can not be disabled', () => {
const button = doShallow({ disabled: true })

Expand Down

0 comments on commit 0ba3c31

Please sign in to comment.