diff --git a/change/@fluentui-react-examples-2020-11-18-08-43-29-sareiff-ariadescr7.json b/change/@fluentui-react-examples-2020-11-18-08-43-29-sareiff-ariadescr7.json new file mode 100644 index 0000000000000..58997fd656ae3 --- /dev/null +++ b/change/@fluentui-react-examples-2020-11-18-08-43-29-sareiff-ariadescr7.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "Add support for ariaDescription in context menu items", + "packageName": "@fluentui/react-examples", + "email": "sareiff@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-11-18T16:43:29.455Z" +} diff --git a/change/office-ui-fabric-react-2020-11-18-08-43-29-sareiff-ariadescr7.json b/change/office-ui-fabric-react-2020-11-18-08-43-29-sareiff-ariadescr7.json new file mode 100644 index 0000000000000..2b3f7fa90c009 --- /dev/null +++ b/change/office-ui-fabric-react-2020-11-18-08-43-29-sareiff-ariadescr7.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "Add support for ariaDescription in context menu items", + "packageName": "office-ui-fabric-react", + "email": "sareiff@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-11-18T16:43:27.009Z" +} diff --git a/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md b/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md index 49237a6f79d8e..01e81c2ca8c0e 100644 --- a/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md +++ b/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md @@ -3178,6 +3178,7 @@ export interface IContextualMenuClassNames { // @public (undocumented) export interface IContextualMenuItem { [propertyName: string]: any; + ariaDescription?: string; ariaLabel?: string; canCheck?: boolean; checked?: boolean; @@ -3290,6 +3291,7 @@ export interface IContextualMenuItemStyles extends IButtonStyles { linkContent: IStyle; linkContentMenu: IStyle; root: IStyle; + screenReaderText: IStyle; secondaryText: IStyle; splitContainer: IStyle; splitMenu: IStyle; @@ -5907,6 +5909,8 @@ export interface IMenuItemClassNames { // (undocumented) root: string; // (undocumented) + screenReaderText: string; + // (undocumented) secondaryText: string; // (undocumented) splitContainer: string; diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.classNames.ts b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.classNames.ts index 46c65df715bbf..be3747223ac4e 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.classNames.ts +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.classNames.ts @@ -1,6 +1,13 @@ import { getDividerClassNames } from '../Divider/VerticalDivider.classNames'; import { getMenuItemStyles } from './ContextualMenu.cnstyles'; -import { ITheme, mergeStyleSets, getGlobalClassNames, getScreenSelector, ScreenWidthMaxMedium } from '../../Styling'; +import { + ITheme, + mergeStyleSets, + getGlobalClassNames, + getScreenSelector, + ScreenWidthMaxMedium, + hiddenContentStyle, +} from '../../Styling'; import { IVerticalDividerClassNames } from '../Divider/VerticalDivider.types'; import { memoizeFunction, IsFocusVisibleClassName } from '../../Utilities'; import { IContextualMenuItemStyles, IContextualMenuItemStyleProps } from './ContextualMenuItem.types'; @@ -35,6 +42,7 @@ export interface IMenuItemClassNames { splitPrimary: string; splitMenu: string; linkContentMenu: string; + screenReaderText: string; } const CONTEXTUAL_SPLIT_MENU_MINWIDTH = '28px'; @@ -79,6 +87,7 @@ const GlobalClassNames = { label: 'ms-ContextualMenu-itemText', secondaryText: 'ms-ContextualMenu-secondaryText', splitMenu: 'ms-ContextualMenu-splitMenu', + screenReaderText: 'ms-ContextualMenu-screenReaderText', }; /** @@ -213,6 +222,12 @@ export const getItemClassNames = memoizeFunction( }, ], ], + screenReaderText: [ + classNames.screenReaderText, + styles.screenReaderText, + hiddenContentStyle, + { visibility: 'hidden' }, + ], }); }, ); diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.deprecated.test.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.deprecated.test.tsx index 171659eb4619a..fbd8b8f56d9bf 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.deprecated.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.deprecated.test.tsx @@ -31,6 +31,7 @@ describe('ContextualMenu', () => { splitPrimary: 'splitPrimaryFoo', splitMenu: 'splitMenuFoo', linkContentMenu: 'linkContentMenuFoo', + screenReaderText: 'screenReaderTextFoo', }; }; }); diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.types.ts b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.types.ts index bf8f26e233543..8103af9cc6932 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.types.ts +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.types.ts @@ -539,6 +539,11 @@ export interface IContextualMenuItem { */ [propertyName: string]: any; + /** + * Detailed description of the menu item for the benefit of screen readers. + */ + ariaDescription?: string; + /** * This prop is no longer used. All contextual menu items are now focusable when disabled. * @deprecated in 6.38.2 will be removed in 7.0.0 diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItem.test.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItem.test.tsx index f14e6381ec2fd..9a96f95a1bb93 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItem.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItem.test.tsx @@ -173,5 +173,6 @@ function getMenuItemClassNames(): IMenuItemClassNames { splitPrimary: 'splitPrimary', splitMenu: 'splitMenu', linkContentMenu: 'linkContentMenu', + screenReaderText: 'screenReaderText', }; } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItem.types.ts b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItem.types.ts index 90323033c15c5..f60d770046964 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItem.types.ts +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItem.types.ts @@ -243,6 +243,11 @@ export interface IContextualMenuItemStyles extends IButtonStyles { * Styles for a menu item that is a link. */ linkContentMenu: IStyle; + + /** + * Styles for hidden screen reader text. + */ + screenReaderText: IStyle; } export interface IContextualMenuItemRenderFunctions { diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.deprecated.test.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.deprecated.test.tsx index 622bde24e7a7b..30e13aaa98a88 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.deprecated.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.deprecated.test.tsx @@ -45,5 +45,6 @@ function getMenuItemClassNames(): IMenuItemClassNames { splitPrimary: 'splitPrimary', splitMenu: 'splitMenu', linkContentMenu: 'linkContentMenu', + screenReaderText: 'screenReaderText', }; } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.test.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.test.tsx index 6bdd2bd3c5256..44804d433f3f7 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.test.tsx @@ -96,5 +96,6 @@ function getMenuItemClassNames(): IMenuItemClassNames { splitPrimary: 'splitPrimary', splitMenu: 'splitMenu', linkContentMenu: 'linkContentMenu', + screenReaderText: 'screenReaderText', }; } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.tsx index a142116f6ed3f..e680d4d201d63 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuAnchor.tsx @@ -1,14 +1,14 @@ import * as React from 'react'; -import { anchorProperties, getNativeProps, memoizeFunction } from '../../../Utilities'; +import { anchorProperties, getNativeProps, memoizeFunction, getId, mergeAriaAttributeValues } from '../../../Utilities'; import { ContextualMenuItemWrapper } from './ContextualMenuItemWrapper'; import { KeytipData } from '../../../KeytipData'; import { isItemDisabled, hasSubmenu } from '../../../utilities/contextualMenu/index'; import { ContextualMenuItem } from '../ContextualMenuItem'; -import { IKeytipDataProps } from '../../KeytipData/KeytipData.types'; import { IKeytipProps } from '../../Keytip/Keytip.types'; export class ContextualMenuAnchor extends ContextualMenuItemWrapper { private _anchor = React.createRef(); + private _ariaDescriptionId: string; private _getMemoizedMenuButtonKeytipProps = memoizeFunction((keytipProps: IKeytipProps) => { return { @@ -43,13 +43,19 @@ export class ContextualMenuAnchor extends ContextualMenuItemWrapper { const itemHasSubmenu = hasSubmenu(item); const nativeProps = getNativeProps>(item, anchorProperties); const disabled = isItemDisabled(item); - const { itemProps } = item; + const { itemProps, ariaDescription } = item; let { keytipProps } = item; if (keytipProps && itemHasSubmenu) { keytipProps = this._getMemoizedMenuButtonKeytipProps(keytipProps); } + // Check for ariaDescription to set the _ariaDescriptionId and render a hidden span with + // the description in it to be added to ariaDescribedBy + if (ariaDescription) { + this._ariaDescriptionId = getId(); + } + return (
- {(keytipAttributes: IKeytipDataProps): JSX.Element => ( + {(keytipAttributes: any): JSX.Element => ( + {this._renderAriaDescription(ariaDescription, classNames.screenReaderText)} )} @@ -111,4 +122,13 @@ export class ContextualMenuAnchor extends ContextualMenuItemWrapper { onItemClick(item, ev); } }; + + protected _renderAriaDescription = (ariaDescription?: string, className?: string) => { + // If ariaDescription is given, descriptionId will be assigned to ariaDescriptionSpan + return ariaDescription ? ( + + {ariaDescription} + + ) : null; + }; } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.deprecated.test.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.deprecated.test.tsx index 938405812fc14..c17cf74be018e 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.deprecated.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.deprecated.test.tsx @@ -45,5 +45,6 @@ function getMenuItemClassNames(): IMenuItemClassNames { splitPrimary: 'splitPrimary', splitMenu: 'splitMenu', linkContentMenu: 'linkContentMenu', + screenReaderText: 'screenReaderText', }; } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.test.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.test.tsx index 2d1df729885f1..cb04ac35ddead 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.test.tsx @@ -108,5 +108,6 @@ function getMenuItemClassNames(): IMenuItemClassNames { splitPrimary: 'splitPrimary', splitMenu: 'splitMenu', linkContentMenu: 'linkContentMenu', + screenReaderText: 'screenReaderText', }; } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.tsx index 93132b301a225..a35d3a28d2f4b 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuButton.tsx @@ -1,14 +1,14 @@ import * as React from 'react'; -import { buttonProperties, getNativeProps, memoizeFunction } from '../../../Utilities'; +import { buttonProperties, getNativeProps, memoizeFunction, getId, mergeAriaAttributeValues } from '../../../Utilities'; import { ContextualMenuItemWrapper } from './ContextualMenuItemWrapper'; import { KeytipData } from '../../../KeytipData'; import { getIsChecked, isItemDisabled, hasSubmenu, getMenuItemAriaRole } from '../../../utilities/contextualMenu/index'; import { ContextualMenuItem } from '../ContextualMenuItem'; -import { IKeytipDataProps } from '../../KeytipData/KeytipData.types'; import { IKeytipProps } from '../../Keytip/Keytip.types'; export class ContextualMenuButton extends ContextualMenuItemWrapper { private _btn = React.createRef(); + private _ariaDescriptionId: string; private _getMemoizedMenuButtonKeytipProps = memoizeFunction((keytipProps: IKeytipProps) => { return { @@ -41,7 +41,7 @@ export class ContextualMenuButton extends ContextualMenuItemWrapper { const canCheck: boolean = isChecked !== null; const defaultRole = getMenuItemAriaRole(item); const itemHasSubmenu = hasSubmenu(item); - const { itemProps, ariaLabel } = item; + const { itemProps, ariaLabel, ariaDescription } = item; const buttonNativeProperties = getNativeProps>( item, @@ -52,6 +52,13 @@ export class ContextualMenuButton extends ContextualMenuItemWrapper { const itemRole = item.role || defaultRole; + // Check for ariaDescription to set the _ariaDescriptionId and render a hidden span with + // the description in it to be added to ariaDescribedBy + if (ariaDescription) { + this._ariaDescriptionId = getId(); + } + const ariaDescribedByIds = ariaDescription ? this._ariaDescriptionId : undefined; + const itemButtonProperties = { className: classNames.root, onClick: this._onItemClick, @@ -64,6 +71,7 @@ export class ContextualMenuButton extends ContextualMenuItemWrapper { href: item.href, title: item.title, 'aria-label': ariaLabel, + 'aria-describedby': ariaDescribedByIds, 'aria-haspopup': itemHasSubmenu || undefined, 'aria-owns': item.key === expandedMenuItemKey ? subMenuId : undefined, 'aria-expanded': itemHasSubmenu ? item.key === expandedMenuItemKey : undefined, @@ -89,8 +97,17 @@ export class ContextualMenuButton extends ContextualMenuItemWrapper { ariaDescribedBy={buttonNativeProperties['aria-describedby']} disabled={isItemDisabled(item)} > - {(keytipAttributes: IKeytipDataProps): JSX.Element => ( - )} ); } + protected _renderAriaDescription = (ariaDescription?: string, className?: string) => { + // If ariaDescription is given, descriptionId will be assigned to ariaDescriptionSpan + return ariaDescription ? ( + + {ariaDescription} + + ) : null; + }; + protected _getSubmenuTarget = (): HTMLElement | undefined => { return this._btn.current ? this._btn.current : undefined; }; diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.deprecated.test.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.deprecated.test.tsx index cd7f576cf57ed..2b99d6d120f03 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.deprecated.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.deprecated.test.tsx @@ -45,5 +45,6 @@ function getMenuItemClassNames(): IMenuItemClassNames { splitPrimary: 'splitPrimary', splitMenu: 'splitMenu', linkContentMenu: 'linkContentMenu', + screenReaderText: 'screenReaderText', }; } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.test.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.test.tsx index 4eb99c4f923b2..85d6623a5c063 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.test.tsx @@ -72,5 +72,6 @@ function getMenuItemClassNames(): IMenuItemClassNames { splitPrimary: 'splitPrimary', splitMenu: 'splitMenu', linkContentMenu: 'linkContentMenu', + screenReaderText: 'screenReaderText', }; } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.tsx index 71eee0760a377..0237d84bed98b 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuItemWrapper/ContextualMenuSplitButton.tsx @@ -7,6 +7,7 @@ import { memoizeFunction, Async, EventGroup, + getId, } from '../../../Utilities'; import { ContextualMenuItem } from '../ContextualMenuItem'; import { IContextualMenuItem } from '../ContextualMenu.types'; @@ -26,6 +27,7 @@ export class ContextualMenuSplitButton extends ContextualMenuItemWrapper { private _splitButton: HTMLDivElement; private _lastTouchTimeoutId: number | undefined; private _processingTouch: boolean; + private _ariaDescriptionId: string; private _async: Async; private _events: EventGroup; @@ -75,6 +77,13 @@ export class ContextualMenuSplitButton extends ContextualMenuItemWrapper { keytipProps = this._getMemoizedMenuButtonKeytipProps(keytipProps); } + // Check for ariaDescription to set the _ariaDescriptionId and render a hidden span with + // the description in it to be added to ariaDescribedBy + const { ariaDescription } = item; + if (ariaDescription) { + this._ariaDescriptionId = getId(); + } + return ( {(keytipAttributes: any): JSX.Element => ( @@ -87,7 +96,10 @@ export class ContextualMenuSplitButton extends ContextualMenuItemWrapper { aria-disabled={isItemDisabled(item)} aria-expanded={itemHasSubmenu ? item.key === expandedMenuItemKey : undefined} aria-haspopup={true} - aria-describedby={mergeAriaAttributeValues(item.ariaDescription, keytipAttributes['aria-describedby'])} + aria-describedby={mergeAriaAttributeValues( + ariaDescription ? this._ariaDescriptionId : undefined, + keytipAttributes['aria-describedby'], + )} aria-checked={item.isChecked || item.checked} aria-posinset={focusableElementIndex + 1} aria-setsize={totalItemCount} @@ -106,12 +118,22 @@ export class ContextualMenuSplitButton extends ContextualMenuItemWrapper { {this._renderSplitPrimaryButton(item, classNames, index, hasCheckmarks!, hasIcons!)} {this._renderSplitDivider(item)} {this._renderSplitIconButton(item, classNames, index, keytipAttributes)} + {this._renderAriaDescription(ariaDescription, classNames.screenReaderText)}
)} ); } + protected _renderAriaDescription = (ariaDescription?: string, className?: string) => { + // If ariaDescription is given, descriptionId will be assigned to ariaDescriptionSpan + return ariaDescription ? ( + + {ariaDescription} + + ) : null; + }; + protected _onItemKeyDown = (ev: React.KeyboardEvent): void => { const { item, onItemKeyDown } = this.props; if (ev.which === KeyCodes.enter) { diff --git a/packages/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.ScreenReader.Example.tsx b/packages/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.ScreenReader.Example.tsx new file mode 100644 index 0000000000000..9a1eb0b44a7a0 --- /dev/null +++ b/packages/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.ScreenReader.Example.tsx @@ -0,0 +1,78 @@ +import * as React from 'react'; +import { IContextualMenuItem, IContextualMenuProps } from 'office-ui-fabric-react/lib/ContextualMenu'; +import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; +import { useConst } from '@uifabric/react-hooks'; + +export const ContextualMenuScreenReaderExample: React.FunctionComponent = () => { + const menuItems: IContextualMenuItem[] = [ + { + key: 'newItem', + subMenuProps: { + items: [ + { key: 'emailMessage', text: 'Email message', ariaDescription: 'Emails are fun' }, + { + key: 'calendarEvent', + text: 'Calendar event', + ariaDescription: 'Calendar events are even more fun', + }, + ], + }, + href: 'https://bing.com', + text: 'New', + target: '_blank', + ariaLabel: 'New. Press enter or right arrow keys to open submenu.', + ariaDescription: 'Additional information about new item', + }, + { + key: 'share', + subMenuProps: { + items: [ + { key: 'sharetotwitter', text: 'Share to Twitter' }, + { key: 'sharetofacebook', text: 'Share to Facebook' }, + { + key: 'sharetoemail', + text: 'Share to Email', + subMenuProps: { + items: [ + { key: 'sharetooutlook_1', text: 'Share to Outlook', title: 'Share to Outlook' }, + { key: 'sharetogmail_1', text: 'Share to Gmail', title: 'Share to Gmail' }, + ], + }, + }, + ], + }, + text: 'Share', + ariaLabel: 'Share. Press enter, space or right arrow keys to open submenu.', + ariaDescription: 'Additional information about share item', + }, + { + key: 'shareSplit', + split: true, + 'aria-roledescription': 'split button', + subMenuProps: { + items: [ + { key: 'sharetotwittersplit', text: 'Share to Twitter' }, + { key: 'sharetofacebooksplit', text: 'Share to Facebook' }, + { + key: 'sharetoemailsplit', + text: 'Share to Email', + subMenuProps: { + items: [ + { key: 'sharetooutlooksplit_1', text: 'Share to Outlook', title: 'Share to Outlook' }, + { key: 'sharetogmailsplit_1', text: 'Share to Gmail', title: 'Share to Gmail' }, + ], + }, + }, + ], + }, + text: 'Share w/ Split', + ariaLabel: 'Share w/ Split. Press enter or space keys to trigger action. Press right arrow key to open submenu.', + ariaDescription: 'Additional information about share split item', + }, + ]; + const menuProps = useConst(() => ({ + items: menuItems, + })); + + return ; +}; diff --git a/packages/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.doc.tsx b/packages/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.doc.tsx index 9c9ea4ecf9f30..cf3b63fa18e03 100644 --- a/packages/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.doc.tsx +++ b/packages/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.doc.tsx @@ -17,6 +17,7 @@ import { ContextualMenuWithCustomMenuItemExample } from './ContextualMenu.Custom import { ContextualMenuWithCustomMenuListExample } from './ContextualMenu.CustomMenuList.Example'; import { ContextualMenuHeaderExample } from './ContextualMenu.Header.Example'; import { ContextualMenuPersistedExample } from './ContextualMenu.Persisted.Example'; +import { ContextualMenuScreenReaderExample } from './ContextualMenu.ScreenReader.Example'; const ContextualMenuBasicExampleCode = require('!raw-loader!@fluentui/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.Basic.Example.tsx') as string; const ContextualMenuDefaultExampleCode = require('!raw-loader!@fluentui/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.Default.Example.tsx') as string; @@ -33,6 +34,7 @@ const ContextualMenuWithScrollBarExampleCode = require('!raw-loader!@fluentui/re const ContextualMenuWithCustomMenuItemExampleCode = require('!raw-loader!@fluentui/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.CustomMenuItem.Example.tsx') as string; const ContextualMenuCustomMenuListExampleCode = require('!raw-loader!@fluentui/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.CustomMenuList.Example.tsx') as string; const ContextualMenuHeaderExampleCode = require('!raw-loader!@fluentui/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.Header.Example.tsx') as string; +const ContextualMenuScreenReaderExampleCode = require('!raw-loader!@fluentui/react-examples/src/office-ui-fabric-react/ContextualMenu/ContextualMenu.ScreenReader.Example.tsx') as string; export const ContextualMenuPageProps: IDocPageProps = { title: 'ContextualMenu', @@ -115,6 +117,11 @@ export const ContextualMenuPageProps: IDocPageProps = { code: ContextualMenuHeaderExampleCode, view: , }, + { + title: 'ContextualMenu with additional screen reader text', + code: ContextualMenuScreenReaderExampleCode, + view: , + }, ], overview: require< string