Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

Commit

Permalink
feat(Toolbar): add support for radio & checkbox groups (#1920)
Browse files Browse the repository at this point in the history
* wip!

* wip

* wip

* add UT

* wip!

* wip!

* add behaviors to tests

* fix UTs

* add CH

* rename to `toggle`

* rename to `toggle`

* fix TS issues

* nit fixes

* fix CI

* add role presentation on li to enable reader narrate position

* restore type change

* add UT, fix TS error

* remove tabIndex

* add activeIndex to ToolbarRadioGroup

* remove test for `tabIndex`

* rename to active
  • Loading branch information
layershifter authored Sep 18, 2019
1 parent 79cabdc commit 94c7905
Show file tree
Hide file tree
Showing 21 changed files with 459 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Features
- Add `TextArea` component @lucivpav ([#1897](https://github.com/stardust-ui/react/pull/1897))
- Export `bell-slash` and `bell-snooze` icon to Teams theme @musingh ([#1921](https://github.com/stardust-ui/react/pull/1921))
- Add support for radio & checkbox groups to `Toolbar` component @layershifter ([#1920](https://github.com/stardust-ui/react/pull/1920))

<!--------------------------------[ v0.38.1 ]------------------------------- -->
## [v0.38.1](https://github.com/stardust-ui/react/tree/v0.38.1) (2019-09-13)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Toolbar, ToolbarMenuItemProps } from '@stardust-ui/react'
import * as _ from 'lodash'
import * as React from 'react'

const ToolbarExampleMenuItemToggle = () => {
const [menuOpen, setMenuOpen] = React.useState(false)
const [activeIndexes, setActiveIndexes] = React.useState<number[]>([])

const handleToggleClick = (e: React.SyntheticEvent, props: ToolbarMenuItemProps) => {
if (_.includes(activeIndexes, props.index)) {
setActiveIndexes(_.without(activeIndexes, props.index))
} else {
setActiveIndexes([...activeIndexes, props.index])
}
}

return (
<Toolbar
items={[
{
key: 'more',
active: menuOpen,
icon: 'more',
menu: [
{
key: 'bold',
active: _.includes(activeIndexes, 0),
content: 'Bold',
kind: 'toggle',
icon: 'bold',
index: 0,
onClick: handleToggleClick,
},
{
key: 'italic',
active: _.includes(activeIndexes, 1),
content: 'Italic',
kind: 'toggle',
icon: 'italic',
index: 1,
onClick: handleToggleClick,
},
{ key: 'divider', kind: 'divider' },
'About...',
],
menuOpen,
onMenuOpenChange: (e, { menuOpen }) => setMenuOpen(menuOpen),
},
]}
/>
)
}

export default ToolbarExampleMenuItemToggle
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Toolbar } from '@stardust-ui/react'
import * as React from 'react'

const ToolbarExampleMenuRadioGroup = () => {
const [activeIndex, setActiveIndex] = React.useState(0)
const [menuOpen, setMenuOpen] = React.useState()

return (
<Toolbar
items={[
{
key: 'more',
active: menuOpen,
icon: 'more',
menu: [
{
key: 'group',
activeIndex,
kind: 'group',
items: [
{ key: 'left', content: 'Left' },
{ key: 'center', content: 'Center' },
{ key: 'right', content: 'Right' },
{ key: 'justify', content: 'Justify' },
],
onItemClick: (e, data) => setActiveIndex(data.index),
},
{ key: 'divider', kind: 'divider' },
'About...',
],
menuOpen,
onMenuOpenChange: (e, { menuOpen }) => {
setMenuOpen(menuOpen)
},
},
]}
/>
)
}

export default ToolbarExampleMenuRadioGroup
16 changes: 13 additions & 3 deletions docs/src/examples/components/Toolbar/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,25 @@ const Content = () => (
}
examplePath="components/Toolbar/Content/ToolbarExamplePopup"
/>
<ComponentExample
title="Toolbar can contain a radio group"
description="Toolbar items can be grouped into radio group. Up/Down arrow keys can be used to cycle between radio items. Only one of the radio items can be selected at a time, should be implemented additionally."
examplePath="components/Toolbar/Content/ToolbarExampleRadioGroup"
/>
<ComponentExample
title="Toolbar can contain a menu"
description="Toolbar item can open a menu."
examplePath="components/Toolbar/Content/ToolbarExampleMenu"
/>
<ComponentExample
title="Toolbar can contain a radio group"
description="Toolbar items can be grouped into radio group. Up/Down arrow keys can be used to cycle between radio items. Only one of the radio items can be selected at a time, should be implemented additionally."
examplePath="components/Toolbar/Content/ToolbarExampleRadioGroup"
title="Toolbar can contain toggle items in a menu"
description="Toolbar item can open a menu which can contain toggle items."
examplePath="components/Toolbar/Content/ToolbarExampleMenuItemToggle"
/>
<ComponentExample
title="Toolbar can contain a radio group in a menu"
description="Toolbar item can open a menu which can contain radio groups."
examplePath="components/Toolbar/Content/ToolbarExampleMenuRadioGroup"
/>
<ComponentExample
title="Toolbar can contain custom content"
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/components/Toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react'
import * as _ from 'lodash'
import * as customPropTypes from '@stardust-ui/react-proptypes'
import cx from 'classnames'
import * as PropTypes from 'prop-types'
import ReactResizeDetector from 'react-resize-detector'
import { Ref } from '@stardust-ui/react-component-ref'

Expand Down Expand Up @@ -29,9 +30,9 @@ import ToolbarItem, { ToolbarItemProps } from './ToolbarItem'
import ToolbarMenu from './ToolbarMenu'
import ToolbarMenuDivider from './ToolbarMenuDivider'
import ToolbarMenuItem from './ToolbarMenuItem'
import ToolbarMenuRadioGroup from './ToolbarMenuRadioGroup'
import ToolbarRadioGroup from './ToolbarRadioGroup'
import Box, { BoxProps } from '../Box/Box'
import * as PropTypes from 'prop-types'

export type ToolbarItemShorthandKinds = 'divider' | 'item' | 'group' | 'toggle' | 'custom'

Expand Down Expand Up @@ -115,6 +116,7 @@ class Toolbar extends UIComponent<WithAsProp<ToolbarProps>, ToolbarState> {
static Menu = ToolbarMenu
static MenuDivider = ToolbarMenuDivider
static MenuItem = ToolbarMenuItem
static MenuRadioGroup = ToolbarMenuRadioGroup
static RadioGroup = ToolbarRadioGroup

state: ToolbarState = {
Expand Down
33 changes: 28 additions & 5 deletions packages/react/src/components/Toolbar/ToolbarMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import {
import { mergeComponentVariables } from '../../lib/mergeThemes'

import { ComponentEventHandler, ShorthandCollection, withSafeTypeForAs } from '../../types'
import { submenuBehavior } from '../../lib/accessibility'
import { submenuBehavior, toolbarMenuItemCheckboxBehavior } from '../../lib/accessibility'

import ToolbarMenuRadioGroup, { ToolbarMenuRadioGroupProps } from './ToolbarMenuRadioGroup'
import ToolbarMenuDivider from './ToolbarMenuDivider'
import ToolbarMenuItem, { ToolbarMenuItemProps } from './ToolbarMenuItem'

export type ToolbarMenuItemShorthandKinds = 'divider' | 'item'
export type ToolbarMenuItemShorthandKinds = 'divider' | 'item' | 'toggle'

export interface ToolbarMenuProps
extends UIComponentProps,
Expand Down Expand Up @@ -70,16 +71,38 @@ class ToolbarMenu extends UIComponent<ToolbarMenuProps> {
variables: mergeComponentVariables(variables, predefinedProps.variables),
})

handleRadioGroupOverrides = variables => (predefinedProps: ToolbarMenuRadioGroupProps) => ({
onItemClick: (e, itemProps) => {
_.invoke(predefinedProps, 'onItemClick', e, itemProps)
_.invoke(this.props, 'onItemClick', e, itemProps)
},
variables: mergeComponentVariables(variables, predefinedProps.variables),
})

renderItems(items, variables) {
const itemOverridesFn = this.handleItemOverrides(variables)
const dividerOverridesFn = this.handleDividerOverrides(variables)
const radioGroupOverrides = this.handleRadioGroupOverrides(variables)

return _.map(items, item => {
const kind = _.get(item, 'kind', 'item')
if (kind === 'divider') {
return ToolbarMenuDivider.create(item, { overrideProps: dividerOverridesFn })

switch (kind) {
case 'divider':
return ToolbarMenuDivider.create(item, { overrideProps: dividerOverridesFn })

case 'group':
return ToolbarMenuRadioGroup.create(item, { overrideProps: radioGroupOverrides })

case 'toggle':
return ToolbarMenuItem.create(item, {
defaultProps: { accessibility: toolbarMenuItemCheckboxBehavior },
overrideProps: itemOverridesFn,
})

default:
return ToolbarMenuItem.create(item, { overrideProps: itemOverridesFn })
}
return ToolbarMenuItem.create(item, { overrideProps: itemOverridesFn })
})
}

Expand Down
22 changes: 20 additions & 2 deletions packages/react/src/components/Toolbar/ToolbarMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ export interface ToolbarMenuItemProps
/** A toolbar item can be active. */
active?: boolean

/** A slot for a selected indicator in the dropdown list. */
activeIndicator?: ShorthandValue<IconProps>

/** A toolbar item can show it is currently unable to be interacted with. */
disabled?: boolean

/** Name or shorthand for Toolbar Item Icon */
icon?: ShorthandValue<IconProps>

/** ToolbarMenuItem index inside ToolbarMenu. */
index?: number

/**
* Called on click.
*
Expand All @@ -54,6 +60,7 @@ export interface ToolbarMenuItemProps
}

export interface ToolbarMenuItemSlotClassNames {
activeIndicator: string
wrapper: string
}

Expand All @@ -63,6 +70,7 @@ class ToolbarMenuItem extends UIComponent<WithAsProp<ToolbarMenuItemProps>> {
static className = 'ui-toolbar__menuitem'

static slotClassNames: ToolbarMenuItemSlotClassNames = {
activeIndicator: `${ToolbarMenuItem.className}__activeIndicator`,
wrapper: `${ToolbarMenuItem.className}__wrapper`,
}

Expand All @@ -71,15 +79,18 @@ class ToolbarMenuItem extends UIComponent<WithAsProp<ToolbarMenuItemProps>> {
static propTypes = {
...commonPropTypes.createCommon(),
active: PropTypes.bool,
activeIndicator: customPropTypes.itemShorthand,
disabled: PropTypes.bool,
icon: customPropTypes.itemShorthand,
index: PropTypes.number,
onClick: PropTypes.func,
wrapper: customPropTypes.itemShorthand,
}

static defaultProps = {
as: 'button',
accessibility: menuItemBehavior as Accessibility,
activeIndicator: 'stardust-checkmark',
wrapper: { as: 'li' },
}

Expand All @@ -90,8 +101,8 @@ class ToolbarMenuItem extends UIComponent<WithAsProp<ToolbarMenuItemProps>> {
},
}

renderComponent({ ElementType, classes, accessibility, unhandledProps }) {
const { children, content, disabled, icon, wrapper } = this.props
renderComponent({ ElementType, classes, accessibility, unhandledProps, styles }) {
const { active, activeIndicator, children, content, disabled, icon, wrapper } = this.props

const menuItemInner = childrenExist(children) ? (
children
Expand All @@ -110,6 +121,13 @@ class ToolbarMenuItem extends UIComponent<WithAsProp<ToolbarMenuItemProps>> {
<>
{Icon.create(icon, { defaultProps: { xSpacing: !!content ? 'after' : 'none' } })}
{content}
{active &&
Icon.create(activeIndicator, {
defaultProps: {
className: ToolbarMenuItem.slotClassNames.activeIndicator,
styles: styles.activeIndicator,
},
})}
</>
)}
</ElementType>
Expand Down
Loading

0 comments on commit 94c7905

Please sign in to comment.