Skip to content

Commit

Permalink
chore: Merge master into prerelease/minor
Browse files Browse the repository at this point in the history
  • Loading branch information
workday-canvas-kit committed Oct 10, 2022
2 parents e191e5b + c0232b9 commit 89aa7ac
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 7 deletions.
4 changes: 4 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,8 @@ export const parameters = {
defaultValue: defaultCanvasTheme,
},
},
// Make stories available to our internal code sandbox tool
tesseract: {
enable: true
}
};
19 changes: 15 additions & 4 deletions modules/preview-react/menu/lib/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default class Menu extends React.Component<MenuProps, MenuState> {
} = this.props;
const {selectedItemIndex} = this.state;
const cardWidth = grow ? '100%' : width;
let interactiveItemIndex: number | null = null;

return (
<Card display="inline-block" padding={space.zero} width={cardWidth} depth={3}>
Expand All @@ -133,17 +134,22 @@ export default class Menu extends React.Component<MenuProps, MenuState> {
ref={this.menuRef}
{...elemProps}
>
{React.Children.map(children, (menuItem, index) => {
{React.Children.map(children, menuItem => {
if (!React.isValidElement(menuItem)) {
return;
}
const itemId = `${id}-${index}`;
let itemId = null;
if (!menuItem.props.isHeader) {
interactiveItemIndex = (interactiveItemIndex ?? -1) + 1;
itemId = `${id}-${interactiveItemIndex}`;
}
return (
<React.Fragment key={itemId}>
{React.cloneElement(menuItem, {
onClick: (event: React.MouseEvent) => this.handleClick(event, menuItem.props),
id: itemId,
isFocused: selectedItemIndex === index,
isFocused:
selectedItemIndex === interactiveItemIndex && !menuItem.props.isHeader,
})}
</React.Fragment>
);
Expand Down Expand Up @@ -175,7 +181,9 @@ export default class Menu extends React.Component<MenuProps, MenuState> {
const children = React.Children.toArray(this.props.children);
let nextSelectedIndex = 0;
let isShortcut = false;
const itemCount = children.length;
const itemCount = children.filter(child => {
return !(child as React.ReactElement<MenuItemProps>)?.props?.isHeader;
}).length;
const firstItem = 0;
const lastItem = itemCount - 1;

Expand Down Expand Up @@ -308,6 +316,9 @@ export default class Menu extends React.Component<MenuProps, MenuState> {
};

const firstCharacters = React.Children.map(this.props.children, child => {
if ((child as React.ReactElement<MenuItemProps>)?.props?.isHeader) {
return;
}
return getFirstCharacter(child);
});

Expand Down
14 changes: 12 additions & 2 deletions modules/preview-react/menu/lib/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export interface MenuItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
* @default false
*/
hasDivider?: boolean;
/**
* If true, render a header to group data, this menu item will not be intractable.
* @default false
*/
isHeader?: boolean;
/**
* If true, set the MenuItem to the disabled state so it is not clickable.
* @default false
Expand All @@ -55,7 +60,7 @@ export interface MenuItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
shouldClose?: boolean;
}

const Item = styled('li')<Pick<MenuItemProps, 'isDisabled' | 'isFocused'>>(
const Item = styled('li')<Pick<MenuItemProps, 'isDisabled' | 'isFocused' | 'isHeader'>>(
{
...type.levels.subtext.large,
padding: `${space.xxs} ${space.s}`,
Expand All @@ -71,6 +76,9 @@ const Item = styled('li')<Pick<MenuItemProps, 'isDisabled' | 'isFocused'>>(
outline: 'none',
},
},
({isHeader}) => {
return {pointerEvents: isHeader ? 'none' : 'all'};
},
({isFocused, isDisabled}) => {
if (!isFocused && !isDisabled) {
return {
Expand Down Expand Up @@ -258,6 +266,7 @@ class MenuItem extends React.Component<MenuItemProps> {
hasDivider,
isDisabled,
isFocused,
isHeader,
role,
...elemProps
} = this.props;
Expand All @@ -272,11 +281,12 @@ class MenuItem extends React.Component<MenuItemProps> {
ref={this.ref}
tabIndex={-1}
id={id}
role={role}
role={isHeader ? 'presentation' : role}
onClick={this.handleClick}
aria-disabled={isDisabled ? true : undefined}
isDisabled={!!isDisabled}
isFocused={!!isFocused}
isHeader={!!isHeader}
{...elemProps}
>
{icon && iconProps && <StyledSystemIcon {...iconProps} />}
Expand Down
25 changes: 24 additions & 1 deletion modules/preview-react/menu/spec/Menu.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ describe('Menu Keyboard Shortcuts', () => {
render(
<Menu>
<MenuItem>Alpha</MenuItem>
<MenuItem isHeader>Bravo Header</MenuItem>
<MenuItem>
<em>
Bravo Item (<b>with markup</b>)
Expand Down Expand Up @@ -211,8 +212,10 @@ describe('Menu Keyboard Shortcuts', () => {
it('should loop around selected items using the down arrow', () => {
render(
<Menu isOpen={false}>
<MenuItem isHeader>Beginning</MenuItem>
<MenuItem>Alpha</MenuItem>
<MenuItem>Bravo</MenuItem>
<MenuItem isHeader>End</MenuItem>
</Menu>
);

Expand All @@ -228,8 +231,10 @@ describe('Menu Keyboard Shortcuts', () => {
it('should loop around selected items using the up arrow', () => {
render(
<Menu>
<MenuItem isHeader>Beginning</MenuItem>
<MenuItem>Alpha</MenuItem>
<MenuItem>Bravo</MenuItem>
<MenuItem isHeader>End</MenuItem>
</Menu>
);

Expand All @@ -245,8 +250,10 @@ describe('Menu Keyboard Shortcuts', () => {
it('should select the first items when Home key is pressed', () => {
render(
<Menu initialSelectedItem={1}>
<MenuItem isHeader>Beginning</MenuItem>
<MenuItem>Alpha</MenuItem>
<MenuItem>Bravo</MenuItem>
<MenuItem isHeader>End</MenuItem>
</Menu>
);

Expand All @@ -260,8 +267,10 @@ describe('Menu Keyboard Shortcuts', () => {
it('should select the last items when End key is pressed', () => {
render(
<Menu initialSelectedItem={0}>
<MenuItem isHeader>Beginning</MenuItem>
<MenuItem>Alpha</MenuItem>
<MenuItem>Bravo</MenuItem>
<MenuItem isHeader>End</MenuItem>
</Menu>
);

Expand All @@ -280,7 +289,6 @@ describe('Menu Keyboard Shortcuts', () => {
</Menu>
);

const firstId = screen.getByRole('menuitem', {name: 'Alpha'}).getAttribute('id');
const secondId = screen.getByRole('menuitem', {name: 'Bravo'}).getAttribute('id');

fireEvent.keyDown(screen.getByRole('menu'), {key: 'Meta'});
Expand Down Expand Up @@ -391,4 +399,19 @@ describe('Menu Initial Selected Item', () => {

expect(screen.getByRole('menu')).toHaveAttribute('aria-activedescendant', firstId);
});

it('should select correct when headers are used', () => {
render(
<Menu initialSelectedItem={1}>
<MenuItem isHeader>Alpha Header</MenuItem>
<MenuItem>Alpha</MenuItem>
<MenuItem isHeader>Bravo Header</MenuItem>
<MenuItem>Bravo</MenuItem>
</Menu>
);

const secondId = screen.getByRole('menuitem', {name: 'Bravo'}).getAttribute('id');

expect(screen.getByRole('menu')).toHaveAttribute('aria-activedescendant', secondId);
});
});
8 changes: 8 additions & 0 deletions modules/preview-react/menu/stories/Menu.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {Basic} from './examples/Basic';
import {ContextMenu} from './examples/ContextMenu';
import {CustomMenuItem} from './examples/CustomMenuItem';
import {Icons} from './examples/Icons';
import {Headers} from './examples/Headers';
import {ManyItems} from './examples/ManyItems';

<Meta title="Preview/Menu/React" component={Menu} />
Expand Down Expand Up @@ -74,6 +75,13 @@ Below is an example:

<ExampleCodeBlock code={Icons} />

### Headers

You can group menu items logically by adding a `isHeader` attribute on your `<MenuItems>`. To make
your new items screen reader friendly add an `aria-label` around each grouped item.

<ExampleCodeBlock code={Headers} />

### Many Items

<ExampleCodeBlock code={ManyItems} />
Expand Down
32 changes: 32 additions & 0 deletions modules/preview-react/menu/stories/examples/Headers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import {checkIcon} from '@workday/canvas-system-icons-web';
import {Menu, MenuItem} from '@workday/canvas-kit-preview-react/menu';
import {styled} from '@workday/canvas-kit-react/common';
import {type} from '@workday/canvas-kit-react/tokens';

const Header = styled(MenuItem)({
fontWeight: type.properties.fontWeights.bold,
});

export const Headers = () => {
return (
<Menu>
<Header isHeader={true}>Sort By</Header>
<MenuItem icon={checkIcon}>
<span aria-label="sort by newest">Newest</span>
</MenuItem>
<MenuItem>
<span aria-label="sort by oldest">Oldest</span>
</MenuItem>
<Header isHeader={true} hasDivider={true}>
Display Density
</Header>
<MenuItem icon={checkIcon}>
<span aria-label="display density simple">Simple</span>
</MenuItem>
<MenuItem>
<span aria-label="display density detailed">Detailed</span>
</MenuItem>
</Menu>
);
};

0 comments on commit 89aa7ac

Please sign in to comment.