Skip to content

Commit

Permalink
πŸ“ Add ButtonGroup to Storybook (#2420)
Browse files Browse the repository at this point in the history
* πŸ“ Update Usage

* πŸ“ Add ButtonGroup

* ✏️ Fix typo

* πŸ“ Add Introduction

* ✨ Add vertical prop & story

* 🚧  Add SplitButton

* ✨ Add role to Group

* πŸ’„ Fix menu btn position

* πŸ’„ Update group style

* ♿️  Add aria-label & a11y description

* ♿️ Update a11y description

* πŸ“ Update component name from Group to ButtonGroup

* πŸ’„  Change display

* πŸ’„  Update display to flex-inline

* 🎨  Move ButtonGroup under Button

* ♿️  Add aria-label

* ✏️ Fix typo
  • Loading branch information
martalalik authored Aug 24, 2022
1 parent 6aa0d28 commit edb4de3
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 85 deletions.
78 changes: 60 additions & 18 deletions packages/eds-core-react/src/components/Button/Button.docs.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

import { Story, PropsTable, Links } from './../../../.storybook/components'
import { Button } from '.'

import { Button } from '.'

# Button
Allows users to take action with a single click or tap.
Expand All @@ -26,68 +25,111 @@ import { Button } from '@equinor/eds-core-react'
<Button>Click me!</Button>
```

### Using the 'as' prop to render as another component

The `as` prop allows you to render the `Button` as another html element or even react component.

```tsx
<Button as="a" href="/about" target="_blank" rel="noreferrer">About</Button>
```
This is especially useful if you want to use the `Button` as as a navigation component for a third party routing library such as react-router.

```tsx
import { Link } from "react-router-dom"
import { Button } from '@equinor/eds-core-react'

<Button as={Link} to="/about">About</Button>
```

## Examples

### Basic

The `Button` come with four variants; `contained`, `outlined`, `ghost` and `ghost_icon`.

<Story id="inputs-button--basic" />

### Icon button

The `ghost_icon` & `contained_icon`variant is meant for standalone icon buttons using the `<Icon>` component.
Make sure to defined `aria-label` with descriptive text of `Button` interaction.

<Story id="inputs-button--icon-button" />

### Color

<Story id="inputs-button--color"/>

### Hierarchy

Use variants to differentiate the hierarchy.
<ul>
<li><code>Contained</code> - being high-emphasis.</li>
<li><code>Outlined</code> - being medium-emphasis.</li>
<li><code>Ghost</code> - being low-emphasis.</li>
</ul>

<Story id="inputs-button--hierarchy" />

### File upload

Example of how to do file upload button. Please note this demo only works in Storybook Canvas (isolated example only).

<Story id="inputs-button--file-upload" />

### Progress button

Use the `Progress` component if you need progress indication on a button.

<Story id="inputs-button--progress-button" />

### All

Example All button combinations.

<Story id="inputs-button--all" />

### Full width

<Story id="inputs-button--full-width" />

### Compact

Compact `Button` using `EdsProvider`.

<Story id="inputs-button--compact" />

### Using the 'as' prop to render as another component
## Button.Group
Is used to group related buttons.

The `as` prop allows you to render the `Button` as another html element or even react component.
### Usage
The buttons can be grouped by wrapping them with the `Button.Group` component.

```tsx
<Button as="a" href="/about" target="_blank" rel="noreferrer">About</Button>
```
This is especially useful if you want to use the `Button` as as a navigation component for a third party routing library such as react-router.

```tsx
import { Link } from "react-router-dom"
import { Button } from '@equinor/eds-core-react'

<Button as={Link} to="/about">About</Button>
<Button.Group aria-label="button group">
<Button>Click me!</Button>
<Button>Click me!</Button>
<Button>Click me!</Button>
</Button.Group>
```

### Accessibility

#### ARIA
<ul>
<li>Button.Group has <code>role="group"</code>.</li>
<li>You should provide an accessible label with <code>aria-label="label"</code>, <code>aria-labelledby="id"</code> or <code>&lt;label&gt;</code>.</li>
</ul>

## Examples

### Horizontal
The `Button.Group` is displayed horizontally by default.

<Story id="inputs-button--group-horizontal" />

### Vertical
The `Button.Group` can be displayed vertically using the `vertical` prop.

<Story id="inputs-button--group-vertical" />

### Split
`Button.Group` can also be used to create a split button. <br />
The dropdown can change the button action (as in this example) or be used to immediately trigger a related action.

<Story id="inputs-button--group-split" />
135 changes: 114 additions & 21 deletions packages/eds-core-react/src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { useState, useEffect } from 'react'
import { action } from '@storybook/addon-actions'
import {
Button,
Icon,
ButtonProps,
ButtonGroupProps,
Icon,
EdsProvider,
Density,
Menu,
Progress,
} from '../..'
import { Story, ComponentMeta } from '@storybook/react'
import { menu, add, save } from '@equinor/eds-icons'
import { Stack } from './../../../.storybook/components'
// import { Group } from '../Group'
import page from './Button.docs.mdx'

export default {
Expand Down Expand Up @@ -254,30 +256,30 @@ export const FullWidth: Story<ButtonProps> = () => (
<Button disabled fullWidth>
Disabled
</Button>
<Button fullWidth>
<Button fullWidth aria-label="save action">
<Icon data={save}></Icon>Primary
</Button>
<Button color="secondary" fullWidth>
<Button color="secondary" fullWidth aria-label="save action">
<Icon data={save}></Icon>Secondary
</Button>
<Button color="danger" fullWidth>
<Button color="danger" fullWidth aria-label="save action">
<Icon data={save}></Icon>Danger
</Button>
<Button disabled fullWidth>
<Button disabled fullWidth aria-label="save action">
<Icon data={save}></Icon>Disabled
</Button>
<Button fullWidth>
<Button fullWidth aria-label="save action">
Primary <Icon data={save}></Icon>
</Button>
<Button color="secondary" fullWidth>
<Button color="secondary" fullWidth aria-label="save action">
Secondary
<Icon data={save}></Icon>
</Button>
<Button color="danger" fullWidth>
<Button color="danger" fullWidth aria-label="save action">
Danger
<Icon data={save}></Icon>
</Button>
<Button disabled fullWidth>
<Button disabled fullWidth aria-label="save action">
Disabled
<Icon data={save}></Icon>
</Button>
Expand Down Expand Up @@ -305,9 +307,15 @@ export const Compact: Story<ButtonProps> = () => {
<Button>Contained</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="ghost_icon">
<Button variant="ghost_icon" aria-label="menu action">
<Icon data={menu} title="Ghost icon menu"></Icon>
</Button>
<Button.Group aria-label="button group compact">
<Button>Contained</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="outlined">Outlined</Button>
</Button.Group>
</EdsProvider>
)
}
Expand All @@ -319,13 +327,98 @@ Compact.decorators = [
),
]

// export const ButtonGroup: Story<ButtonProps> = () => (
// <Stack style={{ gridGap: 0 }}>
// <Group>
// <Button>Contained</Button>
// <Button variant="outlined">Outlined</Button>
// <Button variant="outlined">Outlined</Button>
// <Button variant="outlined">Outlined</Button>
// </Group>
// </Stack>
// )
export const GroupHorizontal: Story<ButtonGroupProps> = () => (
<Button.Group aria-label="button group">
<Button>Contained</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="outlined">Outlined</Button>
</Button.Group>
)
GroupHorizontal.decorators = [
(Story) => (
<Stack>
<Story />
</Stack>
),
]

export const GroupVertical: Story<ButtonGroupProps> = () => (
<Button.Group aria-label="vertical" vertical>
<Button>Contained</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="outlined">Outlined</Button>
</Button.Group>
)
GroupVertical.decorators = [
(Story) => (
<Stack>
<Story />
</Stack>
),
]

export const GroupSplit: Story<ButtonGroupProps> = () => {
const options = ['Create task', 'Update task', 'Delete task']
const [isOpen, setIsOpen] = useState<boolean>(false)
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>(null)
const [selectedIndex, setSelectedIndex] = useState(0)

const handleMenuItemClick = (event: React.MouseEvent, index: number) => {
action('click')(event)
event.stopPropagation()
setSelectedIndex(index)
}

const openMenu = () => {
setIsOpen(true)
}

const closeMenu = () => {
setIsOpen(false)
}

return (
<Button.Group aria-label="split button" style={{ gap: '1px' }}>
<Button>{options[selectedIndex]}</Button>
<Button
ref={setAnchorEl}
aria-label="select task action"
aria-haspopup="true"
aria-controls="menu-default"
id="anchor-split"
onClick={() => (isOpen ? closeMenu() : openMenu())}
style={{ padding: '0 4px' }}
>
<Icon name="arrow_drop_down" title="arrow_down"></Icon>
</Button>
<Menu
open={isOpen}
id="menu-split"
aria-labelledby="anchor-split"
onClose={closeMenu}
anchorEl={anchorEl}
>
{options.map((option, index) => (
<Menu.Item
key={option}
disabled={index === 2}
onClick={(event: React.MouseEvent) =>
handleMenuItemClick(event, index)
}
>
{option}
</Menu.Item>
))}
</Menu>
</Button.Group>
)
}
GroupSplit.decorators = [
(Story) => (
<Stack>
<Story />
</Stack>
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { forwardRef, HTMLAttributes } from 'react'
import styled, { css } from 'styled-components'
import { group as tokens } from './ButtonGroup.tokens'

const { border } = tokens

export type ButtonGroupProps = {
/** Display ButtonGroup vertically. */
vertical?: boolean
} & HTMLAttributes<HTMLDivElement>

const radius = border.type === 'border' && border.radius

const ButtonGroupBase = styled.div<ButtonGroupProps>`
display: inline-flex;
> * {
border-radius: 0;
@media (hover: hover) and (pointer: fine) {
&:hover {
border-radius: 0;
}
}
}
${({ vertical }) =>
vertical
? css`
flex-direction: column;
> :first-child {
border-top-left-radius: ${radius};
border-top-right-radius: ${radius};
}
> :last-child {
border-bottom-left-radius: ${radius};
border-bottom-right-radius: ${radius};
}
> :not(:last-child) {
border-bottom: none;
}
`
: css`
> :first-child {
border-top-left-radius: ${radius};
border-bottom-left-radius: ${radius};
}
> :last-child {
border-top-right-radius: ${radius};
border-bottom-right-radius: ${radius};
}
> :not(:last-child) {
border-right: none;
}
`}
`

export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
function ButtonGroup({ children, vertical, ...rest }, ref) {
const props = {
ref,
vertical,
...rest,
}
return (
<ButtonGroupBase role="group" {...props}>
{children}
</ButtonGroupBase>
)
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ButtonGroup'
Loading

0 comments on commit edb4de3

Please sign in to comment.