Skip to content
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
6 changes: 6 additions & 0 deletions src/components/ContextualMenu/ContextualMenu.Props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ export interface IContextualMenuProps extends React.Props<ContextualMenu>, IPosi
*/
beakWidth?: number;

/**
* Aria label for accessibility for the ContextualMenu.
* If none specified no aria label will be applied to the ContextualMenu.
*/
ariaLabel?: string;

}

export interface IContextualMenuItem {
Expand Down
81 changes: 48 additions & 33 deletions src/components/ContextualMenu/ContextualMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,18 +144,19 @@ export class ContextualMenu extends React.Component<IContextualMenuProps, IConte

public render() {
let { className,
items,
isBeakVisible,
labelElementId,
targetElement,
id,
targetPoint,
useTargetPoint,
beakWidth,
directionalHint,
gapSpace,
isSubMenu,
coverTarget } = this.props;
items,
isBeakVisible,
labelElementId,
targetElement,
id,
targetPoint,
useTargetPoint,
beakWidth,
directionalHint,
gapSpace,
isSubMenu,
coverTarget,
ariaLabel } = this.props;

let { submenuProps } = this.state;

Expand Down Expand Up @@ -186,21 +187,24 @@ export class ContextualMenu extends React.Component<IContextualMenuProps, IConte
ref={ (focusZone) => this._focusZone = focusZone }
rootProps={ { role: 'menu' } }
>
<ul className='ms-ContextualMenu-list is-open' onKeyDown={ this._onKeyDown }>
<ul
className='ms-ContextualMenu-list is-open'
onKeyDown={ this._onKeyDown }
aria-label={ ariaLabel } >
{ items.map((item, index) => (
// If the item name is equal to '-', a divider will be generated.
item.name === '-' ? (
<li
role='separator'
key={ item.key || index }
className={ css('ms-ContextualMenu-divider', item.className ) }/>
className={ css('ms-ContextualMenu-divider', item.className) }/>
) : (
<li
role='menuitem'
title={ item.title }
key={ item.key || index }
className={ css('ms-ContextualMenu-item', item.className ) }>
{ this._renderMenuItem(item, index, hasCheckmarks, hasIcons) }
className={ css('ms-ContextualMenu-item', item.className) }>
{ this._renderMenuItem(item, index, hasCheckmarks, hasIcons) }
</li>
)
)) }
Expand All @@ -217,26 +221,37 @@ export class ContextualMenu extends React.Component<IContextualMenuProps, IConte

private _renderMenuItem(item: IContextualMenuItem, index: number, hasCheckmarks: boolean, hasIcons: boolean) {
let { expandedMenuItemKey, subMenuId } = this.state;
let ariaLabel = '';

if (item.ariaLabel) {
ariaLabel = item.ariaLabel;
} else if (item.name) {
ariaLabel = item.name;
}

if (item.onRender) {
return item.onRender(item);
}

return React.createElement(
'button',
{ className: css('ms-ContextualMenu-link', { 'is-expanded': (expandedMenuItemKey === item.key) }),
onClick: item.onClick || (item.items && item.items.length) ? this._onItemClick.bind(this, item) : item.href ? () => { location.href = item.href; } : null,
onKeyDown: item.items && item.items.length ? this._onItemKeyDown.bind(this, item) : null,
onMouseEnter: this._onItemMouseEnter.bind(this, item),
onMouseLeave: this._onMouseLeave,
onMouseDown: (ev: any) => this._onItemMouseDown(item, ev),
disabled: item.isDisabled,
dataCommandKey: index,
role: 'menuitem',
href: item.href,
'aria-haspopup': item.items && item.items.length ? true : null,
'aria-owns': item.key === expandedMenuItemKey ? subMenuId : null },
this._renderMenuItemChildren(item, index, hasCheckmarks, hasIcons));
'button',
{
className: css('ms-ContextualMenu-link', { 'is-expanded': (expandedMenuItemKey === item.key) }),
onClick: item.onClick || (item.items && item.items.length) ? this._onItemClick.bind(this, item) : item.href ? () => { location.href = item.href; } : null,
onKeyDown: item.items && item.items.length ? this._onItemKeyDown.bind(this, item) : null,
onMouseEnter: this._onItemMouseEnter.bind(this, item),
onMouseLeave: this._onMouseLeave,
onMouseDown: (ev: any) => this._onItemMouseDown(item, ev),
disabled: item.isDisabled,
dataCommandKey: index,
role: 'menuitem',
href: item.href,
title: item.title,
'aria-label': ariaLabel,
'aria-haspopup': item.items && item.items.length ? true : null,
'aria-owns': item.key === expandedMenuItemKey ? subMenuId : null
},
this._renderMenuItemChildren(item, index, hasCheckmarks, hasIcons));
}

private _renderMenuItemChildren(item: IContextualMenuItem, index: number, hasCheckmarks: boolean, hasIcons: boolean) {
Expand All @@ -245,17 +260,17 @@ export class ContextualMenu extends React.Component<IContextualMenuProps, IConte
{(hasCheckmarks) ? (
<span
className={
css('ms-ContextualMenu-checkmark', {'ms-Icon ms-Icon--check': item.isChecked, 'not-selected': !item.isChecked})
css('ms-ContextualMenu-checkmark', { 'ms-Icon ms-Icon--check': item.isChecked, 'not-selected': !item.isChecked })
}
onClick={ this._onItemClick.bind(this, item) } />
) : (null) }
{(hasIcons) ? (
<span className={ 'ms-ContextualMenu-icon' + ((item.icon) ? ` ms-Icon ms-Icon--${item.icon}` : ' no-icon') }/>
) : (null)}
) : (null) }
<span className='ms-ContextualMenu-itemText ms-fontWeight-regular'>{ item.name }</span>
{(item.items && item.items.length) ? (
<i className={ css('ms-ContextualMenu-submenuChevron ms-Icon', getRTL() ? 'ms-Icon--chevronLeft' : 'ms-Icon--chevronRight') } />
) : (null)}
) : (null) }
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export class ContextualMenuBasicExample extends React.Component<any, any> {
public render() {
return (
<div>
<Button onClick={this._onClick}> Click for ContextualMenu </Button>
<Button onClick={ this._onClick }> Click for ContextualMenu </Button>
{ this.state.isContextMenuVisible ? (
<ContextualMenu
shouldFocusOnMount={ true }
targetPoint={this.state.target}
useTargetPoint={true}
onDismiss={this._onDismiss}
directionalHint={ getRTL() ? DirectionalHint.bottomRightEdge : DirectionalHint.bottomLeftEdge}
targetPoint={ this.state.target }
useTargetPoint={ true }
onDismiss={ this._onDismiss }
directionalHint={ getRTL() ? DirectionalHint.bottomRightEdge : DirectionalHint.bottomLeftEdge }
items={
[
{
Expand Down