Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 embed mode for `FocusZone` and use it in newly added Chat behaviors @tomasiser ([#233](https://github.com/stardust-ui/react/pull/233))
- Add default accessibility behavior to `Popup` @sophieH29 ([#218](https://github.com/stardust-ui/react/pull/218))
- Add focus styles for `Menu.Item` component @Bugaa92 ([#286](https://github.com/stardust-ui/react/pull/286))

### Documentation
- Improve `Contributing` documentation @alinais, @levithomason ([#189](https://github.com/stardust-ui/react/pull/189))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'
import { Menu } from '@stardust-ui/react'

const items = [
{ key: 'home', icon: 'home' },
{ key: 'users', icon: 'users' },
{ key: 'search', icon: 'search' },
]

const MenuExampleIconOnlyPrimary = () => (
<Menu iconOnly defaultActiveIndex={0} items={items} type="primary" />
)

export default MenuExampleIconOnlyPrimary
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import { Menu } from '@stardust-ui/react'

const items = [
{ key: 'home', icon: 'home' },
{ key: 'users', icon: 'users' },
{ key: 'search', icon: 'search' },
]

const MenuExampleIconOnlyPrimaryInverted = () => (
<Menu
iconOnly
defaultActiveIndex={0}
items={items}
type="primary"
variables={siteVars => ({
defaultColor: siteVars.gray06,
defaultBackgroundColor: siteVars.brand,
typePrimaryActiveBorderColor: siteVars.white,
})}
/>
)

export default MenuExampleIconOnlyPrimaryInverted
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'
import { Menu } from '@stardust-ui/react'

const items = [
{ key: 'home', icon: 'home' },
{ key: 'users', icon: 'users' },
{ key: 'search', icon: 'search' },
]

const MenuExampleIconOnlyVertical = () => (
<Menu vertical iconOnly defaultActiveIndex={0} items={items} type="primary" />
)

export default MenuExampleIconOnlyVertical
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const items = [
icon: {
name: 'cloud',
circular: true,
xSpacing: 'both',
size: 'small',
},
accessibility: ToolbarButtonBehavior,
Expand All @@ -18,7 +17,6 @@ const items = [
icon: {
name: 'clock',
circular: true,
xSpacing: 'both',
size: 'small',
},
accessibility: ToolbarButtonBehavior,
Expand All @@ -29,7 +27,6 @@ const items = [
icon: {
name: 'book',
circular: true,
xSpacing: 'both',
size: 'small',
},
accessibility: ToolbarButtonBehavior,
Expand Down
17 changes: 16 additions & 1 deletion docs/src/examples/components/Menu/Variations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const Variations = () => (
examplePath="components/Menu/Variations/MenuExampleUnderlined"
/>
<ComponentExample
title="Underlined primary"
title="Underlined Primary"
description="A menu can underline the active element."
examplePath="components/Menu/Variations/MenuExampleUnderlinedPrimary"
/>
Expand All @@ -84,11 +84,26 @@ const Variations = () => (
description="Menu can contain only icons."
examplePath="components/Menu/Variations/MenuExampleIconOnly"
/>
<ComponentExample
title="Icon Only Primary"
description="Menu can contain only icons."
examplePath="components/Menu/Variations/MenuExampleIconOnlyPrimary"
/>
<ComponentExample
title="Icon Only Primary Inverted"
description="Menu can contain only icons."
examplePath="components/Menu/Variations/MenuExampleIconOnlyPrimaryInverted"
/>
<ComponentExample
title="Vertical Icon Only"
description="Vertical menu can contain only icons."
examplePath="components/Menu/Variations/MenuExampleIconOnlyVertical"
/>
<ComponentExample
title="Vertical Icon Only Primary"
description="Vertical menu can contain only icons."
examplePath="components/Menu/Variations/MenuExampleIconOnlyVerticalPrimary"
/>
<ComponentExample
title="Menu as a Tab List"
description="A menu with TabList accessibility behavior."
Expand Down
6 changes: 5 additions & 1 deletion src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ export interface IButtonProps {
variables?: ComponentVariablesInput
}

export interface IButtonState {
[isFromKeyboard.propertyName]: boolean
}

/**
* A button.
* @accessibility
* Other considerations:
* - for disabled buttons, add 'disabled' attribute so that the state is properly recognized by the screen reader
* - if button includes icon only, textual representation needs to be provided by using 'title', 'aria-label', or 'aria-labelledby' attributes
*/
class Button extends UIComponent<Extendable<IButtonProps>, any> {
class Button extends UIComponent<Extendable<IButtonProps>, IButtonState> {
static create: Function

public static displayName = 'Button'
Expand Down
37 changes: 29 additions & 8 deletions src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { childrenExist, createShorthandFactory, customPropTypes, UIComponent } f
import Icon from '../Icon'
import { MenuItemBehavior } from '../../lib/accessibility'
import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibility/interfaces'
import IsFromKeyboard from '../../lib/isFromKeyboard'

import { ComponentVariablesInput, ComponentPartStyle } from '../../../types/theme'
import {
Expand Down Expand Up @@ -37,7 +38,11 @@ export interface IMenuItemProps {
variables?: ComponentVariablesInput
}

class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
export interface IMenuItemState {
[IsFromKeyboard.propertyName]: boolean
}

class MenuItem extends UIComponent<Extendable<IMenuItemProps>, IMenuItemState> {
static displayName = 'MenuItem'

static className = 'ui-menu__item'
Expand Down Expand Up @@ -114,13 +119,7 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
accessibility: MenuItemBehavior as Accessibility,
}

protected actionHandlers: AccessibilityActionHandlers = {
performClick: event => this.handleClick(event),
}

handleClick = e => {
_.invoke(this.props, 'onClick', e, this.props)
}
state = IsFromKeyboard.initial

renderComponent({ ElementType, classes, accessibility, rest }) {
const { children, content, icon } = this.props
Expand All @@ -138,6 +137,8 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
<a
className={cx('ui-menu__item__anchor', classes.anchor)}
onClick={this.handleClick}
onBlur={this.handleBlur}
onFocus={this.handleFocus}
{...accessibility.attributes.anchor}
{...accessibility.keyHandlers.anchor}
>
Expand All @@ -151,6 +152,26 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
</ElementType>
)
}

protected actionHandlers: AccessibilityActionHandlers = {
performClick: event => this.handleClick(event),
}

private handleClick = e => {
_.invoke(this.props, 'onClick', e, this.props)
}

private handleBlur = (e: React.SyntheticEvent) => {
this.setState(IsFromKeyboard.initial)

_.invoke(this.props, 'onBlur', e, this.props)
}

private handleFocus = (e: React.SyntheticEvent) => {
this.setState(IsFromKeyboard.state())

_.invoke(this.props, 'onFocus', e, this.props)
}
}

MenuItem.create = createShorthandFactory(MenuItem, content => ({ content }))
Expand Down
16 changes: 12 additions & 4 deletions src/components/RadioGroup/RadioGroupItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,23 @@ export interface IRadioGroupItemProps {
styles?: ComponentPartStyle
value?: string | number
variables?: ComponentVariablesInput
isFromKeyboard?: boolean
[isFromKeyboard.propertyName]?: boolean
vertical?: boolean
}

export interface IRadioGroupItemState {
checked: boolean
[isFromKeyboard.propertyName]: boolean
}

/**
* @accessibility
* Radio items need to be grouped in RadioGroup component to correctly handle accessibility.
*/
class RadioGroupItem extends AutoControlledComponent<Extendable<IRadioGroupItemProps>, any> {
class RadioGroupItem extends AutoControlledComponent<
Extendable<IRadioGroupItemProps>,
IRadioGroupItemState
> {
static create: Function

static displayName = 'RadioGroupItem'
Expand Down Expand Up @@ -131,7 +139,7 @@ class RadioGroupItem extends AutoControlledComponent<Extendable<IRadioGroupItemP

static autoControlledProps = ['checked', isFromKeyboard.propertyName]

elementRef: HTMLElement
private elementRef: HTMLElement

componentDidMount() {
this.elementRef = ReactDOM.findDOMNode(this) as HTMLElement
Expand All @@ -155,7 +163,7 @@ class RadioGroupItem extends AutoControlledComponent<Extendable<IRadioGroupItemP
_.invoke(this.props, 'onBlur', e, this.props)
}

handleClick = e => {
private handleClick = e => {
_.invoke(this.props, 'onClick', e, this.props)
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export {
} from './htmlPropsUtils'

export { default as isBrowser } from './isBrowser'
export { default as typescriptUtils } from './typescriptUtils'
export { default as doesNodeContainClick } from './doesNodeContainClick'
export { default as leven } from './leven'

Expand Down
17 changes: 11 additions & 6 deletions src/lib/isFromKeyboard.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import whatInput from 'what-input'
import tsUtils from './typescriptUtils'
export interface IState {
isFromKeyboard: boolean
}

export default class IsFromKeyboard {
static state = () => {
const isFromKeyboard = whatInput.ask() === 'keyboard'
return { isFromKeyboard }
}
static readonly propertyName = tsUtils.nameof<IState>('isFromKeyboard')

static initial = { isFromKeyboard: false }
static initial: IState = IsFromKeyboard.getObjectWithPropValue(false)

static propertyName = 'isFromKeyboard'
static state = (): IState => IsFromKeyboard.getObjectWithPropValue(whatInput.ask() === 'keyboard')

private static getObjectWithPropValue(value: boolean): IState {
return { [IsFromKeyboard.propertyName]: value }
}
}
5 changes: 5 additions & 0 deletions src/lib/typescriptUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const nameof = <T>(name: keyof T) => name

export default {
nameof,
}
4 changes: 2 additions & 2 deletions src/themes/teams/components/Button/buttonStyles.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { pxToRem } from '../../../../lib'
import { IComponentPartStylesInput, ICSSInJSStyle } from '../../../../../types/theme'
import { IButtonProps } from '../../../../components/Button/Button'
import { IButtonProps, IButtonState } from '../../../../components/Button/Button'
import { truncateStyle } from '../../../../styles/customCSS'

const buttonStyles: IComponentPartStylesInput<IButtonProps, any> = {
const buttonStyles: IComponentPartStylesInput<IButtonProps & IButtonState, any> = {
root: ({ props, variables }): ICSSInJSStyle => {
const { circular, disabled, fluid, type, text, iconOnly, isFromKeyboard } = props
const primary = type === 'primary'
Expand Down
Loading