Skip to content

Commit

Permalink
feat(dropdowns): add hasSelection prop for use with `<Option type="…
Browse files Browse the repository at this point in the history
…next">` (#1971)
  • Loading branch information
jzempel authored Nov 18, 2024
1 parent ac05020 commit 23e010e
Show file tree
Hide file tree
Showing 18 changed files with 376 additions and 261 deletions.
12 changes: 9 additions & 3 deletions packages/dropdowns/demo/menu.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ import { useArgs } from '@storybook/client-api';
import { Menu, Item, ItemGroup, Separator } from '@zendeskgarden/react-dropdowns';
import { MenuStory } from './stories/MenuStory';
import README from '../README.md';
import { ITEMS } from './stories/data';
import { BUTTON_TYPE, ITEMS } from './stories/data';

<Meta
title="Packages/Dropdowns/Menu"
component={Menu}
subcomponents={{ Item, 'Item.Meta': Item.Meta, Separator, ItemGroup }}
argTypes={{
appendToNode: { control: false }
appendToNode: { control: false },
button: {
control: 'radio',
options: BUTTON_TYPE
},
label: { name: 'Button label', table: { category: 'Story' } }
}}
args={{
button: 'Menu',
button: BUTTON_TYPE[0],
items: ITEMS,
label: 'Menu',
placement: Menu.defaultProps.placement,
maxHeight: Menu.defaultProps.maxHeight,
zIndex: Menu.defaultProps.zIndex
Expand Down
85 changes: 49 additions & 36 deletions packages/dropdowns/demo/stories/MenuStory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import LeafIcon from '@zendeskgarden/svg-icons/src/16/leaf-stroke.svg';
import CartIcon from '@zendeskgarden/svg-icons/src/16/shopping-cart-stroke.svg';
import { Grid } from '@zendeskgarden/react-grid';
import { IMenuProps, Item, ItemGroup, Separator, Menu } from '@zendeskgarden/react-dropdowns';
import { IItem, Items } from './types';
import { IconButton } from '@zendeskgarden/react-buttons';
import { ButtonType, IItem, Items } from './types';

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

interface IArgs extends IMenuProps {
button: ButtonType;
items: Items;
label: string;
}

export const MenuStory: StoryFn<IArgs> = ({ items, ...args }) => {
return (
<Grid>
<Grid.Row justifyContent="center" style={{ height: 800 }}>
<Grid.Col alignSelf="center" textAlign="center">
<div style={{ display: 'inline-block', position: 'relative', width: 200 }}>
<Menu {...args}>
{items.map(item => {
if ('items' in item) {
return (
<ItemGroup
legend={item.legend}
aria-label={item['aria-label']}
key={item.legend || item['aria-label']}
type={item.type}
icon={item.icon ? <CartIcon /> : undefined}
>
{item.items.map(groupItem => (
<MenuItem key={groupItem.value} {...groupItem} />
))}
</ItemGroup>
);
}
export const MenuStory: StoryFn<IArgs> = ({ button, items, label, ...args }) => (
<Grid>
<Grid.Row justifyContent="center" style={{ height: 800 }}>
<Grid.Col alignSelf="center" textAlign="center">
<div style={{ display: 'inline-block', position: 'relative', width: 200 }}>
<Menu
{...args}
button={
button === 'string'
? label
: /* eslint-disable-next-line react/no-unstable-nested-components */
props => (
<IconButton {...props} aria-label={label}>
<LeafIcon />
</IconButton>
)
}
>
{items.map(item => {
if ('items' in item) {
return (
<ItemGroup
legend={item.legend}
aria-label={item['aria-label']}
key={item.legend || item['aria-label']}
type={item.type}
icon={item.icon ? <CartIcon /> : undefined}
>
{item.items.map(groupItem => (
<MenuItem key={groupItem.value} {...groupItem} />
))}
</ItemGroup>
);
}

if ('isSeparator' in item) {
return <Separator key={item.value} />;
}
if ('isSeparator' in item) {
return <Separator key={item.value} />;
}

return <MenuItem key={item.value} {...item} />;
})}
</Menu>
</div>
</Grid.Col>
</Grid.Row>
</Grid>
);
};
return <MenuItem key={item.value} {...item} />;
})}
</Menu>
</div>
</Grid.Col>
</Grid.Row>
</Grid>
);
2 changes: 2 additions & 0 deletions packages/dropdowns/demo/stories/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

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

export const BUTTON_TYPE = ['string', 'icon'];

export const ITEMS: Items = [
{
value: 'item',
Expand Down
3 changes: 3 additions & 0 deletions packages/dropdowns/demo/stories/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
IOptGroupProps,
IOptionProps
} from '@zendeskgarden/react-dropdowns';
import { BUTTON_TYPE } from './data';

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

export type Items = (IItem | IItemGroup)[];

export type ButtonType = (typeof BUTTON_TYPE)[number];
101 changes: 33 additions & 68 deletions packages/dropdowns/demo/~patterns/patterns.stories.mdx
Original file line number Diff line number Diff line change
@@ -1,94 +1,59 @@
import { useCallback } from 'react';
import { Meta, Canvas, Story } from '@storybook/addon-docs';
import { useArgs } from '@storybook/client-api';
import { Combobox } from '@zendeskgarden/react-dropdowns';
import { ListboxStory } from './stories/ListboxStory';
import { MenuAppendStory } from './stories/MenuAppendStory';
import { MenuButtonStory } from './stories/MenuButtonStory';
import { MenuNestedStory } from './stories/MenuNestedStory';
import { BASE_ITEMS, NESTED_ITEMS } from './stories/data';
import { NestedStory } from './stories/NestedStory';
import { PortalStory } from './stories/PortalStory';

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

# Patterns

## Render listbox in a root level React portal
## Nested

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

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

See in this example, that the listbox is currently getting cropped. Enable the
`listboxAppendToNode` property to see the full listbox.
### Menu

<Canvas>
<Story
name="Listbox"
args={{ listboxAppendToNode: false }}
argTypes={{ listboxAppendToNode: { control: 'boolean' } }}
>
{args => <ListboxStory {...args} />}
</Story>
</Canvas>

## Render menu in a root level React portal

The `appendToNode` property can be used to render the menu popover in a
different DOM location than inline with the menu button. This is done via
React portals under the hood.

You typically will need to set this property if you are using `Menu` inside an
element with `overflow: hidden` / `auto` / `scroll` CSS styles.

See in this example that the menu will attempt to reposition, however it's
ultimately still cropped. Enable the `appendToNode` property to see the full menu.
Adding an `Item` with `type="next"` will enable nested menu behavior. It can be
implemented with or without controlled focus. The subsequent nested menu will
then need an `Item` with `type="previous"` to allow backwards navigation to the
previous menu.

<Canvas>
<Story
name="Menu portal"
args={{ appendToNode: false }}
argTypes={{ appendToNode: { control: 'boolean' } }}
>
{args => <MenuAppendStory {...args} />}
</Story>
<Story name="Nested">{args => <NestedStory {...args} />}</Story>
</Canvas>

## Render menu with custom button

The `button` property can alternatively be set as a callback function that returns
custom button JSX. By default, `Menu` will use a Garden `Button` internally.
## Portal

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

<Canvas>
<Story name="Menu button">{args => <MenuButtonStory {...args} />}</Story>
</Canvas>
### Listbox portal

## Menu with nested items
Enable the `listboxAppendToNode` property to see the full listbox.

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

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

<Canvas>
<Story name="Menu nested" args={{ items: BASE_ITEMS }}>
{args => {
const [_, updateArgs, resetArgs] = useArgs();
const onChange = useCallback(({ type, isExpanded }) => {
const isNext = type.includes('next');
const isPrev = type.includes('previous');
if (isNext || isPrev) {
updateArgs({ items: isNext ? NESTED_ITEMS : BASE_ITEMS });
} else if (isExpanded === false) {
resetArgs(['items']);
}
}, []);
return <MenuNestedStory {...args} onChange={onChange} />;
<Story
name="Portal"
args={{ listboxAppendToNode: false, menuAppendToNode: false }}
argTypes={{
listboxAppendToNode: { control: 'boolean', name: 'Combobox listboxAppendToNode' },
menuAppendToNode: { control: 'boolean', name: 'Menu appendToNode' }
}}
>
{args => <PortalStory {...args} />}
</Story>
</Canvas>
52 changes: 0 additions & 52 deletions packages/dropdowns/demo/~patterns/stories/MenuAppendStory.tsx

This file was deleted.

42 changes: 0 additions & 42 deletions packages/dropdowns/demo/~patterns/stories/MenuButtonStory.tsx

This file was deleted.

Loading

0 comments on commit 23e010e

Please sign in to comment.