Skip to content

Commit 23e010e

Browse files
authored
feat(dropdowns): add hasSelection prop for use with <Option type="next"> (#1971)
1 parent ac05020 commit 23e010e

File tree

18 files changed

+376
-261
lines changed

18 files changed

+376
-261
lines changed

packages/dropdowns/demo/menu.stories.mdx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,24 @@ import { useArgs } from '@storybook/client-api';
33
import { Menu, Item, ItemGroup, Separator } from '@zendeskgarden/react-dropdowns';
44
import { MenuStory } from './stories/MenuStory';
55
import README from '../README.md';
6-
import { ITEMS } from './stories/data';
6+
import { BUTTON_TYPE, ITEMS } from './stories/data';
77

88
<Meta
99
title="Packages/Dropdowns/Menu"
1010
component={Menu}
1111
subcomponents={{ Item, 'Item.Meta': Item.Meta, Separator, ItemGroup }}
1212
argTypes={{
13-
appendToNode: { control: false }
13+
appendToNode: { control: false },
14+
button: {
15+
control: 'radio',
16+
options: BUTTON_TYPE
17+
},
18+
label: { name: 'Button label', table: { category: 'Story' } }
1419
}}
1520
args={{
16-
button: 'Menu',
21+
button: BUTTON_TYPE[0],
1722
items: ITEMS,
23+
label: 'Menu',
1824
placement: Menu.defaultProps.placement,
1925
maxHeight: Menu.defaultProps.maxHeight,
2026
zIndex: Menu.defaultProps.zIndex

packages/dropdowns/demo/stories/MenuStory.tsx

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import LeafIcon from '@zendeskgarden/svg-icons/src/16/leaf-stroke.svg';
1111
import CartIcon from '@zendeskgarden/svg-icons/src/16/shopping-cart-stroke.svg';
1212
import { Grid } from '@zendeskgarden/react-grid';
1313
import { IMenuProps, Item, ItemGroup, Separator, Menu } from '@zendeskgarden/react-dropdowns';
14-
import { IItem, Items } from './types';
14+
import { IconButton } from '@zendeskgarden/react-buttons';
15+
import { ButtonType, IItem, Items } from './types';
1516

1617
const MenuItem = ({ icon, meta, ...item }: IItem) => {
1718
return (
@@ -23,43 +24,55 @@ const MenuItem = ({ icon, meta, ...item }: IItem) => {
2324
};
2425

2526
interface IArgs extends IMenuProps {
27+
button: ButtonType;
2628
items: Items;
29+
label: string;
2730
}
2831

29-
export const MenuStory: StoryFn<IArgs> = ({ items, ...args }) => {
30-
return (
31-
<Grid>
32-
<Grid.Row justifyContent="center" style={{ height: 800 }}>
33-
<Grid.Col alignSelf="center" textAlign="center">
34-
<div style={{ display: 'inline-block', position: 'relative', width: 200 }}>
35-
<Menu {...args}>
36-
{items.map(item => {
37-
if ('items' in item) {
38-
return (
39-
<ItemGroup
40-
legend={item.legend}
41-
aria-label={item['aria-label']}
42-
key={item.legend || item['aria-label']}
43-
type={item.type}
44-
icon={item.icon ? <CartIcon /> : undefined}
45-
>
46-
{item.items.map(groupItem => (
47-
<MenuItem key={groupItem.value} {...groupItem} />
48-
))}
49-
</ItemGroup>
50-
);
51-
}
32+
export const MenuStory: StoryFn<IArgs> = ({ button, items, label, ...args }) => (
33+
<Grid>
34+
<Grid.Row justifyContent="center" style={{ height: 800 }}>
35+
<Grid.Col alignSelf="center" textAlign="center">
36+
<div style={{ display: 'inline-block', position: 'relative', width: 200 }}>
37+
<Menu
38+
{...args}
39+
button={
40+
button === 'string'
41+
? label
42+
: /* eslint-disable-next-line react/no-unstable-nested-components */
43+
props => (
44+
<IconButton {...props} aria-label={label}>
45+
<LeafIcon />
46+
</IconButton>
47+
)
48+
}
49+
>
50+
{items.map(item => {
51+
if ('items' in item) {
52+
return (
53+
<ItemGroup
54+
legend={item.legend}
55+
aria-label={item['aria-label']}
56+
key={item.legend || item['aria-label']}
57+
type={item.type}
58+
icon={item.icon ? <CartIcon /> : undefined}
59+
>
60+
{item.items.map(groupItem => (
61+
<MenuItem key={groupItem.value} {...groupItem} />
62+
))}
63+
</ItemGroup>
64+
);
65+
}
5266

53-
if ('isSeparator' in item) {
54-
return <Separator key={item.value} />;
55-
}
67+
if ('isSeparator' in item) {
68+
return <Separator key={item.value} />;
69+
}
5670

57-
return <MenuItem key={item.value} {...item} />;
58-
})}
59-
</Menu>
60-
</div>
61-
</Grid.Col>
62-
</Grid.Row>
63-
</Grid>
64-
);
65-
};
71+
return <MenuItem key={item.value} {...item} />;
72+
})}
73+
</Menu>
74+
</div>
75+
</Grid.Col>
76+
</Grid.Row>
77+
</Grid>
78+
);

packages/dropdowns/demo/stories/data.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import { Items, Options } from './types';
99

10+
export const BUTTON_TYPE = ['string', 'icon'];
11+
1012
export const ITEMS: Items = [
1113
{
1214
value: 'item',

packages/dropdowns/demo/stories/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
IOptGroupProps,
1212
IOptionProps
1313
} from '@zendeskgarden/react-dropdowns';
14+
import { BUTTON_TYPE } from './data';
1415

1516
export interface IOption extends Omit<IOptionProps, 'icon'> {
1617
icon?: boolean;
@@ -36,3 +37,5 @@ export interface IItemGroup extends Omit<IItemGroupProps, 'icon'> {
3637
export type Options = (IOption | IOptGroup)[];
3738

3839
export type Items = (IItem | IItemGroup)[];
40+
41+
export type ButtonType = (typeof BUTTON_TYPE)[number];
Lines changed: 33 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,59 @@
11
import { useCallback } from 'react';
22
import { Meta, Canvas, Story } from '@storybook/addon-docs';
33
import { useArgs } from '@storybook/client-api';
4-
import { Combobox } from '@zendeskgarden/react-dropdowns';
5-
import { ListboxStory } from './stories/ListboxStory';
6-
import { MenuAppendStory } from './stories/MenuAppendStory';
7-
import { MenuButtonStory } from './stories/MenuButtonStory';
8-
import { MenuNestedStory } from './stories/MenuNestedStory';
9-
import { BASE_ITEMS, NESTED_ITEMS } from './stories/data';
4+
import { NestedStory } from './stories/NestedStory';
5+
import { PortalStory } from './stories/PortalStory';
106

117
<Meta title="Packages/Dropdowns/[patterns]" />
128

139
# Patterns
1410

15-
## Render listbox in a root level React portal
11+
## Nested
1612

17-
The `listboxAppendToNode` property can be used to render the listbox in a
18-
different DOM location than inline with the Combobox component. This is done via
19-
React portals under the hood.
13+
### Listbox
2014

21-
You typically will need to set this property if you are using the `Combobox`
22-
inside an element with `overflow: hidden` / `auto` / `scroll` CSS styles.
15+
A `Combobox` with `<Option type="next">` can be controlled to enable nested
16+
listbox behavior. The nested listbox will then need an `<Option
17+
type="previous">` to allow backwards navigation to the previous listbox. Use
18+
`<Option type="next" hasSelection>` to indicate that the nested listbox contains
19+
one or more selected options.
2320

24-
See in this example, that the listbox is currently getting cropped. Enable the
25-
`listboxAppendToNode` property to see the full listbox.
21+
### Menu
2622

27-
<Canvas>
28-
<Story
29-
name="Listbox"
30-
args={{ listboxAppendToNode: false }}
31-
argTypes={{ listboxAppendToNode: { control: 'boolean' } }}
32-
>
33-
{args => <ListboxStory {...args} />}
34-
</Story>
35-
</Canvas>
36-
37-
## Render menu in a root level React portal
38-
39-
The `appendToNode` property can be used to render the menu popover in a
40-
different DOM location than inline with the menu button. This is done via
41-
React portals under the hood.
42-
43-
You typically will need to set this property if you are using `Menu` inside an
44-
element with `overflow: hidden` / `auto` / `scroll` CSS styles.
45-
46-
See in this example that the menu will attempt to reposition, however it's
47-
ultimately still cropped. Enable the `appendToNode` property to see the full menu.
23+
Adding an `Item` with `type="next"` will enable nested menu behavior. It can be
24+
implemented with or without controlled focus. The subsequent nested menu will
25+
then need an `Item` with `type="previous"` to allow backwards navigation to the
26+
previous menu.
4827

4928
<Canvas>
50-
<Story
51-
name="Menu portal"
52-
args={{ appendToNode: false }}
53-
argTypes={{ appendToNode: { control: 'boolean' } }}
54-
>
55-
{args => <MenuAppendStory {...args} />}
56-
</Story>
29+
<Story name="Nested">{args => <NestedStory {...args} />}</Story>
5730
</Canvas>
5831

59-
## Render menu with custom button
60-
61-
The `button` property can alternatively be set as a callback function that returns
62-
custom button JSX. By default, `Menu` will use a Garden `Button` internally.
32+
## Portal
6333

64-
This is an option for things like icon buttons.
34+
Dropdowns can be rendered in a different DOM location than inline with their
35+
associated trigger component. This is done via React portals under the hood.
36+
You typically will need to portal if you are using dropdown components inside an
37+
element with `overflow: hidden` / `auto` / `scroll` CSS styles. See in these
38+
examples that the dropdowns are currently getting cropped.
6539

66-
<Canvas>
67-
<Story name="Menu button">{args => <MenuButtonStory {...args} />}</Story>
68-
</Canvas>
40+
### Listbox portal
6941

70-
## Menu with nested items
42+
Enable the `listboxAppendToNode` property to see the full listbox.
7143

72-
Adding an `Item` with `type="next"` will enable nested menu
73-
behavior. It can be implemented with or without controlled focus.
44+
### Menu portal
7445

75-
The subsequent nested menu will then need an `Item` with `type="previous"`
76-
to allow backwards navigation to the previous menu.
46+
Enable the `appendToNode` property to see the full menu.
7747

7848
<Canvas>
79-
<Story name="Menu nested" args={{ items: BASE_ITEMS }}>
80-
{args => {
81-
const [_, updateArgs, resetArgs] = useArgs();
82-
const onChange = useCallback(({ type, isExpanded }) => {
83-
const isNext = type.includes('next');
84-
const isPrev = type.includes('previous');
85-
if (isNext || isPrev) {
86-
updateArgs({ items: isNext ? NESTED_ITEMS : BASE_ITEMS });
87-
} else if (isExpanded === false) {
88-
resetArgs(['items']);
89-
}
90-
}, []);
91-
return <MenuNestedStory {...args} onChange={onChange} />;
49+
<Story
50+
name="Portal"
51+
args={{ listboxAppendToNode: false, menuAppendToNode: false }}
52+
argTypes={{
53+
listboxAppendToNode: { control: 'boolean', name: 'Combobox listboxAppendToNode' },
54+
menuAppendToNode: { control: 'boolean', name: 'Menu appendToNode' }
9255
}}
56+
>
57+
{args => <PortalStory {...args} />}
9358
</Story>
9459
</Canvas>

packages/dropdowns/demo/~patterns/stories/MenuAppendStory.tsx

Lines changed: 0 additions & 52 deletions
This file was deleted.

packages/dropdowns/demo/~patterns/stories/MenuButtonStory.tsx

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)