Skip to content

Commit 4809747

Browse files
ktaborsdevongovettLFDanLusnowystinger
authored
ActionMenu docs (#2227)
* ActionMenu docs * copy edits * docs usage import cleanup * adding shouldflip to the direction align story * copy edit * fixing an example * Update Events section copy Co-authored-by: Devon Govett <[email protected]> * example improvements * Update packages/@react-spectrum/menu/stories/ActionMenu.stories.tsx Co-authored-by: Daniel Lu <[email protected]> * fixing a merge conflix mistake * adding an isQuiet example to docs * simplifying quiet example and changing order * removing the deploy after version * upgrading mdx comment style * updating keys to match names * adding open and disabled exampels to the docs Co-authored-by: Devon Govett <[email protected]> Co-authored-by: Daniel Lu <[email protected]> Co-authored-by: Robert Snow <[email protected]>
1 parent a4daf9b commit 4809747

File tree

5 files changed

+325
-4
lines changed

5 files changed

+325
-4
lines changed
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
{/* Copyright 2022 Adobe. All rights reserved.
2+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License. You may obtain a copy
4+
of the License at http://www.apache.org/licenses/LICENSE-2.0
5+
Unless required by applicable law or agreed to in writing, software distributed under
6+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
7+
OF ANY KIND, either express or implied. See the License for the specific language
8+
governing permissions and limitations under the License. */}
9+
10+
import {Layout} from '@react-spectrum/docs';
11+
export default Layout;
12+
13+
import docs from 'docs:@react-spectrum/menu';
14+
import {HeaderInfo, PropTable} from '@react-spectrum/docs';
15+
import packageData from '@react-spectrum/menu/package.json';
16+
17+
```jsx import
18+
import {ActionMenu, Item, Section} from '@react-spectrum/menu';
19+
import Copy from '@spectrum-icons/workflow/Copy';
20+
import Cut from '@spectrum-icons/workflow/Cut';
21+
import {Flex} from '@react-spectrum/layout';
22+
import Paste from '@spectrum-icons/workflow/Paste';
23+
```
24+
25+
---
26+
category: Collections
27+
keywords: [action menu, dropdown]
28+
---
29+
30+
# ActionMenu
31+
32+
<p>{docs.exports.ActionMenu.description}</p>
33+
34+
<HeaderInfo
35+
packageData={packageData}
36+
componentNames={['ActionMenu']}
37+
sourceData={[
38+
{type: 'Spectrum', url: 'https://spectrum.adobe.com/page/popover/'}
39+
]} />
40+
41+
## Example
42+
43+
```tsx example
44+
<ActionMenu>
45+
<Item>Cut</Item>
46+
<Item>Copy</Item>
47+
<Item>Paste</Item>
48+
</ActionMenu>
49+
```
50+
51+
## Content
52+
53+
ActionMenu follows the [Collection Components](../react-stately/collections.html) API,
54+
accepting both static and dynamic collections. See [Menu](Menu.html#content) for further explanation.
55+
56+
```tsx example
57+
function Example() {
58+
let actionMenuItems = [
59+
{name: 'Cut'},
60+
{name: 'Copy'},
61+
{name: 'Paste'},
62+
{name: 'Select All'}
63+
];
64+
65+
return (
66+
<ActionMenu items={actionMenuItems}>
67+
{item => <Item key={item.name}>{item.name}</Item>}
68+
</ActionMenu>
69+
);
70+
}
71+
```
72+
73+
### Trays
74+
On mobile, ActionMenus automatically display in a tray instead of a popover to improve usability.
75+
76+
### Internationalization
77+
In order to internationalize an ActionMenu, the content provided to all child items should be localized.
78+
For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of the ActionMenu is flipped.
79+
80+
## Events
81+
ActionMenu supports the `onAction` prop, which is called when the user presses a menu item. It receives the key of the item as an argument, which can be used to perform the relevant action.
82+
83+
```tsx example
84+
function Example() {
85+
let [action, setAction] = React.useState('');
86+
87+
return (
88+
<>
89+
<ActionMenu onAction={setAction}>
90+
<Item key="cut">Cut</Item>
91+
<Item key="copy">Copy</Item>
92+
<Item key="paste">Paste</Item>
93+
</ActionMenu>
94+
<p>Action: {action}</p>
95+
</>
96+
);
97+
}
98+
```
99+
100+
## Sections
101+
ActionMenu supports sections in order to group options. Sections can be used by wrapping groups of Items in a `Section` component. Each `Section` takes a `title` and `key` prop.
102+
103+
### Static Items
104+
105+
```tsx example
106+
<ActionMenu>
107+
<Section title="File">
108+
<Item key="new">New</Item>
109+
<Item key="open">Open...</Item>
110+
</Section>
111+
<Section title="Save">
112+
<Item key="save">Save</Item>
113+
<Item key="saveAs">Save As...</Item>
114+
<Item key="saveAll">Save All</Item>
115+
</Section>
116+
</ActionMenu>
117+
```
118+
119+
### Dynamic Items
120+
121+
Sections used with dynamic items are populated from a hierarchical data structure. Similarly to the props on ActionMenu, `<Section>` takes an array of data using the `items` prop.
122+
123+
```tsx example
124+
function Example() {
125+
let openWindows = [
126+
{
127+
name: 'Reversion',
128+
id: 'reversion',
129+
children: [
130+
{id: 'undo', name: 'Undo'},
131+
{id: 'redo', name: 'Redo'}
132+
]
133+
},
134+
{
135+
name: 'Clipboard',
136+
id: 'clipboard',
137+
children: [
138+
{id: 'cut', name: 'Cut'},
139+
{id: 'copy', name: 'Copy'},
140+
{id: 'paste', name: 'Paste'}
141+
]
142+
}
143+
];
144+
145+
return (
146+
<ActionMenu
147+
items={openWindows}>
148+
{item => (
149+
<Section items={item.children} title={item.name}>
150+
{item => <Item>{item.name}</Item>}
151+
</Section>
152+
)}
153+
</ActionMenu>
154+
);
155+
}
156+
```
157+
158+
### Accessibility
159+
160+
Sections without a `title` must provide an `aria-label` for accessibility.
161+
162+
## Complex Menu Items
163+
164+
Items within ActionMenu also allow for additional content used to better communicate options. Icons and descriptions can be added to the `children` of `Item` as shown in the example below. If a description is added, the prop `slot="description"` must be used to distinguish the different `<Text>` elements.
165+
166+
```tsx example
167+
import {Keyboard, Text} from '@react-spectrum/text';
168+
<ActionMenu>
169+
<Item key="cut" textValue="cut">
170+
<Cut size="S"/>
171+
<Text>Cut</Text>
172+
<Keyboard>⌘X</Keyboard>
173+
</Item>
174+
<Item key="copy" textValue="copy">
175+
<Copy size="S"/>
176+
<Text>Copy</Text>
177+
<Keyboard>⌘C</Keyboard>
178+
</Item>
179+
<Item key="paste" textValue="paste">
180+
<Paste size="S"/>
181+
<Text>Paste</Text>
182+
<Keyboard>⌘V</Keyboard>
183+
</Item>
184+
</ActionMenu>
185+
```
186+
187+
## Props
188+
189+
<PropTable component={docs.exports.ActionMenu} links={docs.links} />
190+
191+
## Visual options
192+
193+
### Quiet
194+
195+
[View guidelines](https://spectrum.adobe.com/page/action-button/#Quiet)
196+
197+
```tsx example
198+
<ActionMenu
199+
isQuiet
200+
items={[
201+
{name: 'Cut', id: 'cut'},
202+
{name: 'Copy', id: 'copy'},
203+
{name: 'Paste', id: 'paste'}
204+
]}>
205+
{item => <Item>{item.name}</Item>}
206+
</ActionMenu>
207+
```
208+
209+
### Autofocus
210+
211+
Applying `autoFocus` to the ActionMenu sets focus to the ActionMenu trigger.
212+
213+
### Disabled
214+
215+
```tsx example
216+
<ActionMenu
217+
items={[
218+
{name: 'Undo', id: 'undo'},
219+
{name: 'Redo', id: 'redo'},
220+
{name: 'Cut', id: 'cut'},
221+
{name: 'Copy', id: 'copy'},
222+
{name: 'Paste', id: 'paste'}
223+
]}
224+
isDisabled>
225+
{item => <Item>{item.name}</Item>}
226+
</ActionMenu>
227+
```
228+
229+
### Disabled items
230+
231+
```tsx example
232+
<ActionMenu
233+
items={[
234+
{name: 'Undo', id: 'undo'},
235+
{name: 'Redo', id: 'redo'},
236+
{name: 'Cut', id: 'cut'},
237+
{name: 'Copy', id: 'copy'},
238+
{name: 'Paste', id: 'paste'}
239+
]}
240+
disabledKeys={['redo', 'paste']}>
241+
{item => <Item>{item.name}</Item>}
242+
</ActionMenu>
243+
```
244+
245+
### Align and direction
246+
247+
[View guidelines](https://spectrum.adobe.com/page/popover/#Placement)
248+
249+
The `align` prop aligns the ActionMenu menu relative to the trigger and the `direction` prop controls the direction the ActionMenu will render.
250+
251+
```tsx example
252+
<Flex gap="size-100">
253+
<ActionMenu align="start">
254+
<Item key="cut">Cut</Item>
255+
<Item key="copy">Copy</Item>
256+
<Item key="paste">Paste</Item>
257+
</ActionMenu>
258+
<ActionMenu align="end" direction="top" shouldFlip={false}>
259+
<Item key="cut">Cut</Item>
260+
<Item key="copy">Copy</Item>
261+
<Item key="paste">Paste</Item>
262+
</ActionMenu>
263+
<ActionMenu direction="start" align="start">
264+
<Item key="cut">Cut</Item>
265+
<Item key="copy">Copy</Item>
266+
<Item key="paste">Paste</Item>
267+
</ActionMenu>
268+
<ActionMenu direction="end" align="end">
269+
<Item key="cut">Cut</Item>
270+
<Item key="copy">Copy</Item>
271+
<Item key="paste">Paste</Item>
272+
</ActionMenu>
273+
</Flex>
274+
```
275+
276+
### Flipping
277+
By default, the ActionMenu menu flips direction automatically upon opening when space is limited. To change this, set the `shouldFlip` prop to `false`. Try scrolling the viewport close to the edge of the trigger in the example to see this in action.
278+
279+
```tsx example
280+
<Flex gap="size-100">
281+
<ActionMenu shouldFlip>
282+
<Item key="cut">Cut</Item>
283+
<Item key="copy">Copy</Item>
284+
<Item key="paste">Paste</Item>
285+
</ActionMenu>
286+
<ActionMenu shouldFlip={false}>
287+
<Item key="cut">Cut</Item>
288+
<Item key="copy">Copy</Item>
289+
<Item key="paste">Paste</Item>
290+
</ActionMenu>
291+
</Flex>
292+
```
293+
294+
### Open
295+
296+
The `isOpen` and `defaultOpen` props on the ActionMenu control whether the menu is open by default.
297+
They apply controlled and uncontrolled behavior on the Menu respectively.
298+
299+
```tsx example
300+
function Example() {
301+
let [open, setOpen] = React.useState(false);
302+
303+
return (
304+
<ActionMenu
305+
isOpen={open}
306+
onOpenChange={setOpen}>
307+
<Item key="cut">Cut</Item>
308+
<Item key="copy">Copy</Item>
309+
<Item key="paste">Paste</Item>
310+
</ActionMenu>
311+
);
312+
}
313+
```

packages/@react-spectrum/menu/docs/Menu.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ function Example() {
8989
On mobile, Menus automatically display in a tray instead of a popover to improve usability.
9090

9191
### Internationalization
92-
In order to internationalize an Menu, the content provided to all child items should be localized.
92+
In order to internationalize a Menu, the content provided to all child items should be localized.
9393
For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of the Menu is flipped.
9494

9595
## Events

packages/@react-spectrum/menu/docs/MenuTrigger.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,4 @@ function Example() {
203203
</MenuTrigger>
204204
);
205205
}
206+
```

packages/@react-spectrum/menu/src/ActionMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ function ActionMenu<T extends object>(props: SpectrumActionMenuProps<T>, ref: Fo
5555
}
5656

5757
/**
58-
* Convenience component to display an ActionButton with a Menu.
58+
* ActionMenu combines an ActionButton with a Menu for simple "more actions" use cases.
5959
*/
6060
let _ActionMenu = React.forwardRef(ActionMenu);
6161
export {_ActionMenu as ActionMenu};

packages/@react-spectrum/menu/stories/ActionMenu.stories.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import {action} from '@storybook/addon-actions';
1414
import {ActionMenu} from '..';
1515
import {Alignment} from '@react-types/shared';
16+
import {Checkbox} from '@react-spectrum/checkbox';
1617
import {Flex} from '../../layout';
1718
import {Item} from '../';
1819
import {Meta, Story} from '@storybook/react';
@@ -84,6 +85,7 @@ function isOfAlignment(key: string): key is Alignment {
8485
function DirectionAlignment() {
8586
const [align, setAlignment] = useState<Alignment>('start');
8687
const [direction, setDirection] = useState<Direction>('bottom');
88+
const [shouldFlip, setShouldFlip] = useState(true);
8789

8890
const handleAlignChange = (key) => {
8991
if (isOfAlignment(key)) {
@@ -104,10 +106,12 @@ function DirectionAlignment() {
104106
<Picker label="Direction" items={directionItems} selectedKey={direction} onSelectionChange={handleDirectionChange}>
105107
{(item) => <Item key={item.key}>{item.label}</Item>}
106108
</Picker>
109+
<Checkbox isSelected={shouldFlip} onChange={setShouldFlip}>Should Flip</Checkbox>
107110
<ActionMenu
108111
onAction={action('action')}
109112
align={align}
110-
direction={direction}>
113+
direction={direction}
114+
shouldFlip={shouldFlip}>
111115
<Item key="one">One</Item>
112116
<Item key="two">Two</Item>
113117
<Item key="three">Three</Item>
@@ -130,6 +134,9 @@ Quiet.args = {isQuiet: true};
130134
export const Disabled = Template().bind({});
131135
Disabled.args = {isDisabled: true};
132136

137+
export const DisabledKeys = Template().bind({});
138+
DisabledKeys.args = {disabledKeys: ['two']};
139+
133140
export const AutoFocus = Template().bind({});
134141
AutoFocus.args = {autoFocus: true};
135142

@@ -151,7 +158,7 @@ export const ControlledOpen = () => {
151158
);
152159
};
153160

154-
export const DirectionAlign = () => <DirectionAlignment />;
161+
export const DirectionAlignFlip = () => <DirectionAlignment />;
155162

156163
export const WithTooltip = () => (
157164
<TooltipTrigger delay={0}>

0 commit comments

Comments
 (0)