diff --git a/apps/vr-tests/src/stories/ContextualMenu.stories.tsx b/apps/vr-tests/src/stories/ContextualMenu.stories.tsx index 4f8dda89f37f24..5e6f6cff36cff8 100644 --- a/apps/vr-tests/src/stories/ContextualMenu.stories.tsx +++ b/apps/vr-tests/src/stories/ContextualMenu.stories.tsx @@ -178,6 +178,48 @@ const itemsWithHeaders = [ } ]; +const itemsWithSplitButtonSubmenu = [ + { + key: 'share', + split: true, + onClick: () => { }, + subMenuProps: { + items: [ + { + key: 'sharetotwitter', + name: 'Share to Twitter', + }, + { + key: 'sharetofacebook', + name: 'Share to Facebook', + }, + { + key: 'sharetoemail', + split: true, + onClick: () => { }, + name: 'Share to Email', + subMenuProps: { + items: [ + { + key: 'sharetooutlook_1', + name: 'Share to Outlook', + title: 'Share to Outlook', + }, + { + key: 'sharetogmail_1', + name: 'Share to Gmail', + title: 'Share to Gmail', + } + ], + }, + }, + ], + }, + name: 'Share' + } +]; + + storiesOf('ContextualMenu', module) .addDecorator(FabricDecorator) .addDecorator(story => ( @@ -214,4 +256,9 @@ storiesOf('ContextualMenu', module) + )) + .add('With split button submenu', () => ( + )); \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/jspurlin-contextualMenuSplitButtonWithoutTouch_2018-04-19-17-33.json b/common/changes/office-ui-fabric-react/jspurlin-contextualMenuSplitButtonWithoutTouch_2018-04-19-17-33.json new file mode 100644 index 00000000000000..cc5f972b8ce07b --- /dev/null +++ b/common/changes/office-ui-fabric-react/jspurlin-contextualMenuSplitButtonWithoutTouch_2018-04-19-17-33.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Contextual Menu: moved out the split button to be its own component, ContextualMenuSplitButton", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "chiechan@microsoft.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx index 447d4f49ecd82d..8a2f460343a583 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx @@ -6,8 +6,7 @@ import { IMenuItemClassNames, IContextualMenuClassNames, getContextualMenuClassNames, - getItemClassNames, - getSplitButtonVerticalDividerClassNames + getItemClassNames } from './ContextualMenu.classNames'; import { BaseComponent, @@ -27,14 +26,12 @@ import { css, shouldWrapFocus } from '../../Utilities'; -import { hasSubmenu, getIsChecked } from '../../utilities/contextualMenu/index'; +import { hasSubmenu, getIsChecked, isItemDisabled } from '../../utilities/contextualMenu/index'; import { withResponsiveMode, ResponsiveMode } from '../../utilities/decorators/withResponsiveMode'; import { Callout } from '../../Callout'; import { IIconProps } from '../../Icon'; -import { - VerticalDivider -} from '../../Divider'; import { ContextualMenuItem } from './ContextualMenuItem'; +import { ContextualMenuSplitButton } from './ContextualMenuSplitButton'; export interface IContextualMenuState { expandedMenuItemKey?: string; @@ -94,8 +91,6 @@ export class ContextualMenu extends BaseComponent; - private _adjustedFocusZoneProps: IFocusZoneProps; constructor(props: IContextualMenuProps) { @@ -114,7 +109,6 @@ export class ContextualMenu extends BaseComponent { @@ -363,7 +357,7 @@ export class ContextualMenu extends BaseComponent - this._splitButtonContainers.set(item.key, el) - } - role={ 'button' } - aria-labelledby={ item.ariaLabel } - className={ classNames.splitContainer } - aria-disabled={ this._isItemDisabled(item) } - aria-haspopup={ true } - aria-describedby={ item.ariaDescription } - aria-checked={ item.isChecked || item.checked } - aria-posinset={ focusableElementIndex + 1 } - aria-setsize={ totalItemCount } - onMouseEnter={ this._onItemMouseEnter.bind(this, { ...item, subMenuProps: null, items: null }) } - onMouseLeave={ this._onMouseItemLeave.bind(this, { ...item, subMenuProps: null, items: null }) } - onMouseMove={ this._onItemMouseMove.bind(this, { ...item, subMenuProps: null, items: null }) } - onKeyDown={ this._onSplitContainerItemKeyDown.bind(this, item) } - onClick={ this._executeItemClick.bind(this, item) } - tabIndex={ 0 } - data-is-focusable={ true } - > - { this._renderSplitPrimaryButton(item, classNames, index, hasCheckmarks!, hasIcons!) } - { this._renderSplitDivider(item) } - { this._renderSplitIconButton(item, classNames, index) } - - ); - } - - private _renderSplitPrimaryButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number, hasCheckmarks: boolean, hasIcons: boolean) { - - const isChecked: boolean | null | undefined = getIsChecked(item); - const canCheck: boolean = isChecked !== null; - const defaultRole = canCheck ? 'menuitemcheckbox' : 'menuitem'; - const { contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem } = this.props; - - const itemProps = { - key: item.key, - disabled: this._isItemDisabled(item) || item.primaryDisabled, - name: item.name, - className: classNames.splitPrimary, - role: item.role || defaultRole, - canCheck: item.canCheck, - isChecked: item.isChecked, - checked: item.checked, - icon: item.icon, - iconProps: item.iconProps, - 'data-is-focusable': false, - 'aria-hidden': true - } as IContextualMenuItem; - return React.createElement('button', - getNativeProps(itemProps, buttonProperties), - , - ); - } - - private _onSplitContainerItemKeyDown(item: any, ev: React.KeyboardEvent) { - if (ev.which === KeyCodes.enter) { - this._executeItemClick(item, ev); - ev.preventDefault(); - ev.stopPropagation(); - } else { - this._onItemKeyDown(item, ev); - } - } - - private _renderSplitIconButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number) { - const { contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem } = this.props; - const itemProps = { - onClick: this._onSplitItemClick.bind(this, item), - disabled: this._isItemDisabled(item), - className: classNames.splitMenu, - subMenuProps: item.subMenuProps, - submenuIconProps: item.submenuIconProps, - split: true, - } as IContextualMenuItem; - - return React.createElement('button', - assign({}, getNativeProps(itemProps, buttonProperties), { - onMouseEnter: this._onItemMouseEnter.bind(this, item), - onMouseLeave: this._onMouseItemLeave.bind(this, item), - onMouseDown: (ev: any) => this._onItemMouseDown(item, ev), - onMouseMove: this._onItemMouseMove.bind(this, item), - 'data-is-focusable': false, - 'aria-hidden': true - }), - + ); } - private _renderSplitDivider(item: IContextualMenuItem) { - const getDividerClassnames = item.getSplitButtonVerticalDividerClassNames || getSplitButtonVerticalDividerClassNames; - return ; - } - private _getIconProps(item: IContextualMenuItem): IIconProps { const iconProps: IIconProps = item.iconProps ? item.iconProps : { iconName: item.icon @@ -781,15 +703,23 @@ export class ContextualMenu extends BaseComponent { this._isScrollIdle = true; }, this._navigationIdleDelay); } - private _onItemMouseEnter(item: any, ev: React.MouseEvent) { + private _onItemMouseEnter = (item: any, ev: React.MouseEvent): void => { + this._onItemMouseEnterBase(item, ev, ev.currentTarget as HTMLElement); + } + + private _onItemMouseEnterBase = (item: any, ev: React.MouseEvent, target?: HTMLElement): void => { if (!this._isScrollIdle) { return; } - this._updateFocusOnMouseEvent(item, ev); + this._updateFocusOnMouseEvent(item, ev, target); } private _onItemMouseMove(item: any, ev: React.MouseEvent) { + this._onItemMouseMoveBase(item, ev, ev.currentTarget as HTMLElement); + } + + private _onItemMouseMoveBase = (item: any, ev: React.MouseEvent, target: HTMLElement): void => { const targetElement = ev.currentTarget as HTMLElement; @@ -799,9 +729,8 @@ export class ContextualMenu extends BaseComponent): void => { if (!this._isScrollIdle) { return; @@ -833,8 +762,8 @@ export class ContextualMenu extends BaseComponent) { - const targetElement = ev.currentTarget as HTMLElement; + private _updateFocusOnMouseEvent(item: IContextualMenuItem, ev: React.MouseEvent, target?: HTMLElement) { + const targetElement = target ? target : ev.currentTarget as HTMLElement; const { subMenuHoverDelay: timeoutDuration = this._navigationIdleDelay } = this.props; if (item.key === this.state.expandedMenuItemKey) { @@ -857,9 +786,7 @@ export class ContextualMenu extends BaseComponent { targetElement.focus(); - const splitButtonContainer = this._splitButtonContainers.get(item.key); - this._onItemSubMenuExpand(item, - ((item.split && splitButtonContainer) ? splitButtonContainer : targetElement) as HTMLElement); + this._onItemSubMenuExpand(item, targetElement); this._enterTimerId = undefined; }, this._navigationIdleDelay); } else { @@ -871,26 +798,25 @@ export class ContextualMenu extends BaseComponent) { + private _onItemMouseDown = (item: IContextualMenuItem, ev: React.MouseEvent): void => { if (item.onMouseDown) { item.onMouseDown(item, ev); } } - private _onItemClick(item: IContextualMenuItem, ev: React.MouseEvent) { + private _onItemClick = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent): void => { this._onItemClickBase(item, ev, ev.currentTarget as HTMLElement); } - private _onSplitItemClick(item: IContextualMenuItem, ev: React.MouseEvent) { - const splitButtonContainer = this._splitButtonContainers.get(item.key); - // get the whole splitButton container to base the menu off of - this._onItemClickBase(item, ev, - (splitButtonContainer ? splitButtonContainer : ev.currentTarget) as HTMLElement); - } - - private _onItemClickBase(item: IContextualMenuItem, ev: React.MouseEvent, target: HTMLElement) { + private _onItemClickBase = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent, target: HTMLElement): void => { const items = getSubmenuItems(item); + // Cancel a async menu item hover timeout action from being taken and instead + // just trigger the click event instead. + if (this._enterTimerId !== undefined) { + this._async.clearTimeout(this._enterTimerId); + this._enterTimerId = undefined; + } if (!hasSubmenu(item) && (!items || !items.length)) { // This is an item without a menu. Click it. this._executeItemClick(item, ev); } else { @@ -910,10 +836,11 @@ export class ContextualMenu extends BaseComponent | React.KeyboardEvent) { + private _executeItemClick = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent): void => { if (item.disabled || item.isDisabled) { return; } + let dismiss = false; if (item.onClick) { dismiss = !!item.onClick(ev, item); @@ -924,7 +851,7 @@ export class ContextualMenu extends BaseComponent) { + private _onItemKeyDown = (item: any, ev: React.KeyboardEvent): void => { const openKey = getRTL() ? KeyCodes.left : KeyCodes.right; if (ev.which === openKey && !item.disabled) { @@ -1032,10 +959,6 @@ export class ContextualMenu extends BaseComponent { + describe('creates a normal split button', () => { + let menuItem: IContextualMenuItem; + let menuClassNames: IMenuItemClassNames; + + let wrapper: ShallowWrapper; + beforeEach(() => { + menuItem = { key: '123' }; + menuClassNames = getMenuItemClassNames(); + wrapper = shallow( + + ); + }); + + it('renders the contextual menu split button correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + }); +}); + +function getMenuItemClassNames(): IMenuItemClassNames { + return { + item: 'item', + divider: '---', + root: 'root', + linkContent: 'linkContent', + icon: 'icon', + checkmarkIcon: 'checkmarkIcon', + subMenuIcon: 'subMenuIcon', + label: 'label', + splitContainer: 'splitContainer', + splitPrimary: 'splitPrimary', + splitMenu: 'splitMenu', + linkContentMenu: 'linkContentMenu', + }; +} diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx new file mode 100644 index 00000000000000..98ed1091a7567f --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -0,0 +1,207 @@ +import * as React from 'react'; +import { + BaseComponent, + assign, + buttonProperties, + getNativeProps, + KeyCodes +} from '../../Utilities'; +import { IContextualMenuItem } from '../../ContextualMenu'; +import { + IMenuItemClassNames, + getSplitButtonVerticalDividerClassNames +} from './ContextualMenu.classNames'; +import { ContextualMenuItem } from './ContextualMenuItem'; + +import { getIsChecked, isItemDisabled } from '../../utilities/contextualMenu/index'; +import { VerticalDivider } from '../../Divider'; +import { IContextualMenuSplitButtonProps } from './ContextualMenuSplitButton.types'; + +export interface IContextualMenuSplitButtonState { } + +export class ContextualMenuSplitButton extends BaseComponent { + private _splitButton: HTMLDivElement; + + public render(): JSX.Element | null { + const { + item, + classNames, + index, + focusableElementIndex, + totalItemCount, + hasCheckmarks, + hasIcons, + onItemMouseLeave + } = this.props; + + return ( +
this._splitButton = splitButton } + role={ 'button' } + aria-labelledby={ item.ariaLabel } + className={ classNames.splitContainer } + aria-disabled={ isItemDisabled(item) } + aria-haspopup={ true } + aria-describedby={ item.ariaDescription } + aria-checked={ item.isChecked || item.checked } + aria-posinset={ focusableElementIndex + 1 } + aria-setsize={ totalItemCount } + onMouseEnter={ this._onItemMouseEnterPrimary } + onMouseLeave={ onItemMouseLeave ? onItemMouseLeave.bind(this, { ...item, subMenuProps: null, items: null }) : undefined } + onMouseMove={ this._onItemMouseMovePrimary } + onKeyDown={ this._onItemKeyDown } + onClick={ this._executeItemClick } + tabIndex={ 0 } + data-is-focusable={ true } + > + { this._renderSplitPrimaryButton(item, classNames, index, hasCheckmarks!, hasIcons!) } + { this._renderSplitDivider(item) } + { this._renderSplitIconButton(item, classNames, index) } +
+ ); + } + + private _renderSplitPrimaryButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number, hasCheckmarks: boolean, hasIcons: boolean) { + const isChecked: boolean | null | undefined = getIsChecked(item); + const canCheck: boolean = isChecked !== null; + const defaultRole = canCheck ? 'menuitemcheckbox' : 'menuitem'; + const { + contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem, + onItemClick + } = this.props; + + const itemProps = { + key: item.key, + disabled: isItemDisabled(item) || item.primaryDisabled, + name: item.name, + className: classNames.splitPrimary, + role: item.role || defaultRole, + canCheck: item.canCheck, + isChecked: item.isChecked, + checked: item.checked, + icon: item.icon, + iconProps: item.iconProps, + 'data-is-focusable': false, + 'aria-hidden': true + } as IContextualMenuItem; + return ( + + ); + } + + private _renderSplitDivider(item: IContextualMenuItem) { + const getDividerClassNames = item.getSplitButtonVerticalDividerClassNames || getSplitButtonVerticalDividerClassNames; + return ; + } + + private _renderSplitIconButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number) { + const { + contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem, + onItemMouseLeave, + onItemMouseDown + } = this.props; + + const itemProps = { + onClick: this._onIconItemClick, + disabled: isItemDisabled(item), + className: classNames.splitMenu, + subMenuProps: item.subMenuProps, + submenuIconProps: item.submenuIconProps, + split: true, + } as IContextualMenuItem; + + const buttonProps = assign({}, getNativeProps(itemProps, buttonProperties), { + onMouseEnter: this._onItemMouseEnterIcon, + onMouseLeave: onItemMouseLeave ? onItemMouseLeave.bind(this, item) : undefined, + onMouseDown: (ev: any) => onItemMouseDown ? onItemMouseDown(item, ev) : undefined, + onMouseMove: this._onItemMouseMoveIcon, + 'data-is-focusable': false, + 'aria-hidden': true + }); + + return ( + + ); + } + + private _onItemMouseEnterPrimary = (ev: React.MouseEvent): void => { + const { + item, + onItemMouseEnter + } = this.props; + if (onItemMouseEnter) { + onItemMouseEnter({ ...item, subMenuProps: undefined, items: undefined }, ev, this._splitButton); + } + } + + private _onItemMouseEnterIcon = (ev: React.MouseEvent): void => { + const { item, onItemMouseEnter } = this.props; + if (onItemMouseEnter) { + onItemMouseEnter(item, ev, this._splitButton); + } + } + + private _onItemMouseMovePrimary = (ev: React.MouseEvent): void => { + const { + item, + onItemMouseMove + } = this.props; + if (onItemMouseMove) { + onItemMouseMove({ ...item, subMenuProps: undefined, items: undefined }, ev, this._splitButton); + } + } + + private _onItemMouseMoveIcon = (ev: React.MouseEvent): void => { + const { + item, + onItemMouseMove + } = this.props; + if (onItemMouseMove) { + onItemMouseMove(item, ev, this._splitButton); + } + } + + private _onIconItemClick = (ev: React.MouseEvent): void => { + const { item, onItemClickBase } = this.props; + if (onItemClickBase) { + onItemClickBase(item, ev, (this._splitButton ? this._splitButton : ev.currentTarget) as HTMLElement); + } + } + + private _executeItemClick = (ev: React.MouseEvent | React.KeyboardEvent): void => { + const { + item, + executeItemClick + } = this.props; + + if (item.disabled || item.isDisabled) { + return; + } + + if (executeItemClick) { + executeItemClick(item, ev); + } + } + + private _onItemKeyDown = (ev: React.KeyboardEvent): void => { + const { item, onItemKeyDown } = this.props; + if (ev.which === KeyCodes.enter) { + this._executeItemClick(ev); + ev.preventDefault(); + ev.stopPropagation(); + } else if (onItemKeyDown) { + onItemKeyDown(item, ev); + } + } +} diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts new file mode 100644 index 00000000000000..1e7ebdb801f2c5 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts @@ -0,0 +1,94 @@ + +import { IContextualMenuItem } from '../../ContextualMenu'; +import { IMenuItemClassNames } from './ContextualMenu.classNames'; +import { IContextualMenuItemProps } from './ContextualMenuItem.types'; +import { ContextualMenuSplitButton } from './ContextualMenuSplitButton'; + +export interface IContextualMenuSplitButtonProps extends React.Props { + /** + * Optional callback to access the ContextualmenuSplitButton interface. Use this instead of ref for accessing + * the public methods and properties of the component. + */ + componentRef?: (component: ContextualMenuSplitButton | null) => void; + + /** + * The item that is used to render the split button. + */ + item: IContextualMenuItem; + + /** + * CSS class to apply to the context menu. + */ + classNames: IMenuItemClassNames; + + /** + * The index number of the split button among all items in the contextual menu including things like dividers and headers. + */ + index: number; + + /** + * The index number of the split button among all items in the contextual menu excluding dividers and headers. + */ + focusableElementIndex: number; + + /** + * The total number of items in the contextual menu. + */ + totalItemCount: number; + + /** + * Whether or not if the item for the split button uses checkmarks. + */ + hasCheckmarks?: boolean; + + /** + * Whether or not the item for the split button uses icons. + */ + hasIcons?: boolean; + + /** + * Method to override the render of the individual menu items. + * @default ContextualMenuItem + */ + contextualMenuItemAs?: React.ComponentClass | React.StatelessComponent; + + /** + * Callback for when the user's mouse enters the split button. + */ + onItemMouseEnter?: (item: IContextualMenuItem, ev: React.MouseEvent, target: HTMLElement) => boolean | void; + + /** + * Callback for when the user's mouse leaves the split button. + */ + onItemMouseLeave?: (item: IContextualMenuItem, ev: React.MouseEvent) => void; + + /** + * Callback for when the user's mouse moves in the split button. + */ + onItemMouseMove?: (item: IContextualMenuItem, ev: React.MouseEvent, target: HTMLElement) => void; + + /** + * Callback for the mousedown event on the icon button in the split menu. + */ + onItemMouseDown?: (item: IContextualMenuItem, ev: React.MouseEvent) => void; + + /** + * Callback for when the click event on the primary button. + */ + executeItemClick?: (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent) => void; + + /** + * Callback for when the click event on the icon button from the split button. + */ + onItemClick?: (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent) => void; + + /** + * Callback for when the click event on the icon button which also takes in a specific HTMLElement that will be focused. + */ + onItemClickBase?: (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent, target: HTMLElement) => void; + + /** + * Callback for keyboard events on the split button. + */ + onItemKeyDown?: (item: IContextualMenuItem, ev: React.KeyboardEvent) => void; +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap new file mode 100644 index 00000000000000..5fe08a6dd930ee --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap @@ -0,0 +1,676 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ContextualMenuSplitButton creates a normal split button renders the contextual menu split button correctly 1`] = ` +ShallowWrapper { + "length": 1, + Symbol(enzyme.__root__): [Circular], + Symbol(enzyme.__unrendered__): , + Symbol(enzyme.__renderer__): Object { + "batchedUpdates": [Function], + "getNode": [Function], + "render": [Function], + "simulateEvent": [Function], + "unmount": [Function], + }, + Symbol(enzyme.__node__): Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-checked": undefined, + "aria-describedby": undefined, + "aria-disabled": false, + "aria-haspopup": true, + "aria-labelledby": undefined, + "aria-posinset": 1, + "aria-setsize": 1, + "children": Array [ + , + , + , + ], + "className": "splitContainer", + "data-is-focusable": true, + "onClick": [Function], + "onKeyDown": [Function], + "onMouseEnter": [Function], + "onMouseLeave": undefined, + "onMouseMove": [Function], + "role": "button", + "tabIndex": 0, + }, + "ref": [Function], + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-hidden": true, + "checked": undefined, + "children": , + "className": "splitPrimary", + "data-is-focusable": false, + "disabled": undefined, + "icon": undefined, + "name": undefined, + "role": "menuitem", + }, + "ref": null, + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "classNames": Object { + "checkmarkIcon": "checkmarkIcon", + "divider": "---", + "icon": "icon", + "item": "item", + "label": "label", + "linkContent": "linkContent", + "linkContentMenu": "linkContentMenu", + "root": "root", + "splitContainer": "splitContainer", + "splitMenu": "splitMenu", + "splitPrimary": "splitPrimary", + "subMenuIcon": "subMenuIcon", + }, + "data-is-focusable": false, + "hasIcons": undefined, + "index": 0, + "item": Object { + "aria-hidden": true, + "canCheck": undefined, + "checked": undefined, + "className": "splitPrimary", + "data-is-focusable": false, + "disabled": undefined, + "icon": undefined, + "iconProps": undefined, + "isChecked": undefined, + "key": "123", + "name": undefined, + "role": "menuitem", + }, + "onCheckmarkClick": undefined, + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + "type": "button", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "getClassNames": [Function], + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-hidden": true, + "children": , + "className": "splitMenu", + "data-is-focusable": false, + "disabled": false, + "onClick": [Function], + "onMouseDown": [Function], + "onMouseEnter": [Function], + "onMouseLeave": undefined, + "onMouseMove": [Function], + }, + "ref": null, + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "classNames": Object { + "checkmarkIcon": "checkmarkIcon", + "divider": "---", + "icon": "icon", + "item": "item", + "label": "label", + "linkContent": "linkContent", + "linkContentMenu": "linkContentMenu", + "root": "root", + "splitContainer": "splitContainer", + "splitMenu": "splitMenu", + "splitPrimary": "splitPrimary", + "subMenuIcon": "subMenuIcon", + }, + "hasIcons": false, + "index": 0, + "item": Object { + "className": "splitMenu", + "disabled": false, + "onClick": [Function], + "split": true, + "subMenuProps": undefined, + "submenuIconProps": undefined, + }, + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + "type": "button", + }, + ], + "type": "div", + }, + Symbol(enzyme.__nodes__): Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-checked": undefined, + "aria-describedby": undefined, + "aria-disabled": false, + "aria-haspopup": true, + "aria-labelledby": undefined, + "aria-posinset": 1, + "aria-setsize": 1, + "children": Array [ + , + , + , + ], + "className": "splitContainer", + "data-is-focusable": true, + "onClick": [Function], + "onKeyDown": [Function], + "onMouseEnter": [Function], + "onMouseLeave": undefined, + "onMouseMove": [Function], + "role": "button", + "tabIndex": 0, + }, + "ref": [Function], + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-hidden": true, + "checked": undefined, + "children": , + "className": "splitPrimary", + "data-is-focusable": false, + "disabled": undefined, + "icon": undefined, + "name": undefined, + "role": "menuitem", + }, + "ref": null, + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "classNames": Object { + "checkmarkIcon": "checkmarkIcon", + "divider": "---", + "icon": "icon", + "item": "item", + "label": "label", + "linkContent": "linkContent", + "linkContentMenu": "linkContentMenu", + "root": "root", + "splitContainer": "splitContainer", + "splitMenu": "splitMenu", + "splitPrimary": "splitPrimary", + "subMenuIcon": "subMenuIcon", + }, + "data-is-focusable": false, + "hasIcons": undefined, + "index": 0, + "item": Object { + "aria-hidden": true, + "canCheck": undefined, + "checked": undefined, + "className": "splitPrimary", + "data-is-focusable": false, + "disabled": undefined, + "icon": undefined, + "iconProps": undefined, + "isChecked": undefined, + "key": "123", + "name": undefined, + "role": "menuitem", + }, + "onCheckmarkClick": undefined, + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + "type": "button", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "getClassNames": [Function], + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-hidden": true, + "children": , + "className": "splitMenu", + "data-is-focusable": false, + "disabled": false, + "onClick": [Function], + "onMouseDown": [Function], + "onMouseEnter": [Function], + "onMouseLeave": undefined, + "onMouseMove": [Function], + }, + "ref": null, + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "classNames": Object { + "checkmarkIcon": "checkmarkIcon", + "divider": "---", + "icon": "icon", + "item": "item", + "label": "label", + "linkContent": "linkContent", + "linkContentMenu": "linkContentMenu", + "root": "root", + "splitContainer": "splitContainer", + "splitMenu": "splitMenu", + "splitPrimary": "splitPrimary", + "subMenuIcon": "subMenuIcon", + }, + "hasIcons": false, + "index": 0, + "item": Object { + "className": "splitMenu", + "disabled": false, + "onClick": [Function], + "split": true, + "subMenuProps": undefined, + "submenuIconProps": undefined, + }, + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + "type": "button", + }, + ], + "type": "div", + }, + ], + Symbol(enzyme.__options__): Object { + "adapter": ReactSixteenAdapter { + "options": Object { + "enableComponentDidUpdateOnSetState": true, + }, + }, + }, +} +`; diff --git a/packages/office-ui-fabric-react/src/utilities/contextualMenu/contextualMenuUtility.ts b/packages/office-ui-fabric-react/src/utilities/contextualMenu/contextualMenuUtility.ts index 434710ccbe961e..d4309c63e9423b 100644 --- a/packages/office-ui-fabric-react/src/utilities/contextualMenu/contextualMenuUtility.ts +++ b/packages/office-ui-fabric-react/src/utilities/contextualMenu/contextualMenuUtility.ts @@ -28,3 +28,7 @@ export function getIsChecked(item: IContextualMenuItem): boolean | null { export function hasSubmenu(item: IContextualMenuItem): boolean { return !!(item.subMenuProps || item.items); } + +export function isItemDisabled(item: IContextualMenuItem): boolean { + return !!(item.isDisabled || item.disabled); +} \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index 54abbbccd466c3..a7bd473e66e63f 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -50,7 +50,7 @@ "bundlesize": [ { "path": "../apps/test-bundle-button/dist/test-bundle-button.min.js", - "maxSize": "47 kB" + "maxSize": "47.6 kB" } ] } \ No newline at end of file