Skip to content

Commit

Permalink
Allow overriding ContextualMenu's internal List's role (microsoft#13908)
Browse files Browse the repository at this point in the history
#### Pull request checklist

- [X] Addresses an existing issue: Fixes microsoft#13971 
- [X] Include a change request file using `$ yarn change`

#### Description of changes

Currently, the `ContextualMenu` control hard-code the `role` attribute of the internal List as `menu`. In my usage, I want to set a different role, specifically `grid`. There's currently no way to achieve this.

In this PR, I'm adding a `role` property to the `IContextualMenuListProps` struct. The idea is that the consumer of `ContextualMenu` would provide a custom implementation of `onRenderMenuList` callback. Inside the callback, it can set the `role` attribute to `grid`, before invoking the default renderer.

The default renderer inside `ContextualMenu` is modified to honor the `role` property. If `role` is not set, it falls back to `menu`, hence preserving the existing behavior. 

#### Focus areas to test

Added unit tests.
  • Loading branch information
dotnetjunky committed Jul 15, 2020
1 parent f58148f commit e94ddb7
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "minor",
"comment": "Allow consumer of ContextualMenu control to override the role of internal list.",
"packageName": "office-ui-fabric-react",
"email": "kinhln@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-07-02T21:53:01.420Z"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3020,6 +3020,8 @@ export interface IContextualMenuListProps {
// (undocumented)
items: IContextualMenuItem[];
// (undocumented)
role?: string;
// (undocumented)
totalItemCount: number;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,17 +484,11 @@ export class ContextualMenuBase extends React.Component<IContextualMenuProps, IC
defaultRender?: IRenderFunction<IContextualMenuListProps>,
): JSX.Element => {
let indexCorrection = 0;
const { items, totalItemCount, hasCheckmarks, hasIcons, role } = menuListProps;
return (
<ul className={this._classNames.list} onKeyDown={this._onKeyDown} onKeyUp={this._onKeyUp} role="menu">
{menuListProps.items.map((item, index) => {
const menuItem = this._renderMenuItem(
item,
index,
indexCorrection,
menuListProps.totalItemCount,
menuListProps.hasCheckmarks,
menuListProps.hasIcons,
);
<ul className={this._classNames.list} onKeyDown={this._onKeyDown} onKeyUp={this._onKeyUp} role={role ?? 'menu'}>
{items.map((item, index) => {
const menuItem = this._renderMenuItem(item, index, indexCorrection, totalItemCount, hasCheckmarks, hasIcons);
if (item.itemType !== ContextualMenuItemType.Divider && item.itemType !== ContextualMenuItemType.Header) {
const indexIncrease = item.customOnRenderListLength ? item.customOnRenderListLength : 1;
indexCorrection += indexIncrease;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import * as Utilities from '../../Utilities';
import { IContextualMenuProps, IContextualMenuStyles, IContextualMenu } from './ContextualMenu.types';
import { ContextualMenu } from './ContextualMenu';
import { canAnyMenuItemsCheck } from './ContextualMenu.base';
import { IContextualMenuItem, ContextualMenuItemType } from './ContextualMenu.types';
import { IContextualMenuItem, ContextualMenuItemType, IContextualMenuListProps } from './ContextualMenu.types';
import { IContextualMenuRenderItem, IContextualMenuItemStyles } from './ContextualMenuItem.types';
import { DefaultButton, IButton } from 'office-ui-fabric-react/lib/Button';
import { IRenderFunction } from '@uifabric/utilities';

describe('ContextualMenu', () => {
afterEach(() => {
Expand Down Expand Up @@ -1265,6 +1266,49 @@ describe('ContextualMenu', () => {
});
});

describe('onRenderMenuList function tests', () => {
it('List has default role as menu.', () => {
const items: IContextualMenuItem[] = [
{
text: 'TestText 1',
key: 'TestKey1',
},
];

ReactTestUtils.renderIntoDocument<IContextualMenuProps>(<ContextualMenu items={items} />);

const internalList = document.querySelector('ul.ms-ContextualMenu-list') as HTMLUListElement;

expect(internalList).toBeTruthy();
expect(internalList.getAttribute('role')).toEqual('menu');
});

it('List applies role that is set by custom onRenderMenuList function.', () => {
const items: IContextualMenuItem[] = [
{
text: 'TestText 1',
key: 'TestKey1',
},
];

const onRenderMenuList: IRenderFunction<IContextualMenuListProps> = (props, defaultRender) => {
if (props) {
props.role = 'grid';
}
return defaultRender?.(props) ?? null;
};

ReactTestUtils.renderIntoDocument<IContextualMenuProps>(
<ContextualMenu items={items} onRenderMenuList={onRenderMenuList} />,
);

const internalList = document.querySelector('ul.ms-ContextualMenu-list') as HTMLUListElement;

expect(internalList).toBeTruthy();
expect(internalList.getAttribute('role')).toEqual('grid');
});
});

it('applies styles prop for menu if provided', () => {
const items: IContextualMenuItem[] = [
{ text: 'TestText 1', key: 'TestKey1', canCheck: true, isChecked: true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ export interface IContextualMenuListProps {
hasCheckmarks: boolean;
hasIcons: boolean;
defaultMenuItemRenderer: (item: IContextualMenuItemRenderProps) => React.ReactNode;
role?: string;
}

/**
Expand Down

0 comments on commit e94ddb7

Please sign in to comment.