Skip to content

Commit 40bfb3e

Browse files
authored
Merge branch 'main' into pk/text-input-enhancements
2 parents 50ebb05 + a13efa4 commit 40bfb3e

File tree

12 files changed

+963
-23
lines changed

12 files changed

+963
-23
lines changed

.changeset/composable-actionmenu.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/components': minor
3+
---
4+
5+
Add composable `ActionMenu` to `@primer/components/drafts`

docs/content/ActionList2.mdx renamed to docs/content/drafts/ActionList2.mdx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,13 @@ storybook: '/react/storybook?path=/story/composite-components-actionlist2'
66
description: An ActionList is a list of items that can be activated or selected. ActionList is the base component for many menu-type components, including DropdownMenu and ActionMenu.
77
---
88

9-
import {BorderBox, Avatar} from '@primer/components'
9+
import {Box, Avatar} from '@primer/components'
1010
import {ActionList} from '@primer/components/drafts'
11-
import {ComponentChecklist} from '../src/component-checklist'
12-
13-
import {ImageContainer} from '@primer/gatsby-theme-doctocat'
1411
import {LinkIcon, AlertIcon, ArrowRightIcon} from '@primer/octicons-react'
1512

1613
<br />
1714

18-
<BorderBox sx={{padding: 6}}>
15+
<Box sx={{border: '1px solid', borderColor: 'border.default', borderRadius: 2, padding: 6}}>
1916
<ActionList sx={{width: 320}}>
2017
<ActionList.Item>
2118
<ActionList.LeadingVisual>
@@ -43,7 +40,7 @@ import {LinkIcon, AlertIcon, ArrowRightIcon} from '@primer/octicons-react'
4340
</ActionList.TrailingVisual>
4441
</ActionList.Item>
4542
</ActionList>
46-
</BorderBox>
43+
</Box>
4744

4845
<br />
4946

@@ -351,11 +348,10 @@ render(<SelectFields />)
351348

352349
- [Interface guidelines: Action List](https://primer.style/design/components/action-list)
353350

354-
<br />
355-
356351
## Related components
357352

358-
- [ActionMenu](/ActionMenu)
353+
- [ActionMenu](/drafts/ActionMenu2)
354+
- [DropdownMenu](/DropdownMenu)
359355
- [SelectPanel](/SelectPanel)
360356

361357
## Component status

docs/content/drafts/ActionMenu2.mdx

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
---
2+
title: ActionMenu
3+
status: Alpha
4+
source: https://github.com/primer/react/tree/main/src/ActionMenu
5+
storybook: '/react/storybook?path=/story/composite-components-actionmenu2'
6+
description: An ActionMenu is an ActionList-based component for creating a menu of actions that expands through a trigger button.
7+
---
8+
9+
import {Box, Avatar} from '@primer/components'
10+
import {ActionMenu, ActionList} from '@primer/components/drafts'
11+
import {Props} from '../../src/props'
12+
13+
<br />
14+
15+
<Box sx={{border: '1px solid', borderColor: 'border.default', borderRadius: 2, padding: 6}}>
16+
<ActionMenu>
17+
<ActionMenu.Button>Menu</ActionMenu.Button>
18+
<ActionList>
19+
<ActionList.Item onSelect={() => onSelect('Copy link')}>
20+
Copy link
21+
<ActionList.TrailingVisual>⌘C</ActionList.TrailingVisual>
22+
</ActionList.Item>
23+
<ActionList.Item onSelect={() => onSelect('Quote reply')}>
24+
Quote reply
25+
<ActionList.TrailingVisual>⌘Q</ActionList.TrailingVisual>
26+
</ActionList.Item>
27+
<ActionList.Item onSelect={() => onSelect('Edit comment')}>
28+
Edit comment
29+
<ActionList.TrailingVisual>⌘E</ActionList.TrailingVisual>
30+
</ActionList.Item>
31+
<ActionList.Divider />
32+
<ActionList.Item variant="danger" onSelect={() => onSelect('Delete file')}>
33+
Delete file
34+
<ActionList.TrailingVisual>⌘D</ActionList.TrailingVisual>
35+
</ActionList.Item>
36+
</ActionList>
37+
38+
</ActionMenu>
39+
</Box>
40+
41+
<br />
42+
43+
```js
44+
import {ActionMenu} from '@primer/components/drafts'
45+
```
46+
47+
<br />
48+
49+
## Examples
50+
51+
### Minimal example
52+
53+
`ActionMenu` ships with `ActionMenu.Button` which is an accessible trigger for the overlay. It's recommended to compose `ActionList` with this component.
54+
55+
&nbsp;
56+
57+
```javascript live noinline
58+
// import {ActionMenu, ActionList} from '@primer/components/drafts'
59+
const {ActionMenu, ActionList} = drafts // ignore docs silliness; import like that ↑
60+
61+
render(
62+
<ActionMenu>
63+
<ActionMenu.Button>Menu</ActionMenu.Button>
64+
65+
<ActionList>
66+
<ActionList.Item onSelect={event => console.log('New file')}>New file</ActionList.Item>
67+
<ActionList.Item>Copy link</ActionList.Item>
68+
<ActionList.Item>Edit file</ActionList.Item>
69+
<ActionList.Divider />
70+
<ActionList.Item variant="danger">Delete file</ActionList.Item>
71+
</ActionList>
72+
</ActionMenu>
73+
)
74+
```
75+
76+
### With a custom anchor
77+
78+
You can choose to have a different _anchor_ for the Menu dependending on the application's context.
79+
80+
&nbsp;
81+
82+
```javascript live noinline
83+
// import {ActionMenu, ActionList} from '@primer/components/drafts'
84+
const {ActionMenu, ActionList} = drafts // ignore docs silliness; import like that ↑
85+
86+
render(
87+
<ActionMenu>
88+
<ActionMenu.Anchor>
89+
<ButtonInvisible aria-label="Open column options">
90+
<KebabHorizontalIcon />
91+
</ButtonInvisible>
92+
</ActionMenu.Anchor>
93+
94+
<ActionList>
95+
<ActionList.Item>
96+
<ActionList.LeadingVisual>
97+
<PencilIcon />
98+
</ActionList.LeadingVisual>
99+
Rename
100+
</ActionList.Item>
101+
<ActionList.Item>
102+
<ActionList.LeadingVisual>
103+
<ArchiveIcon />
104+
</ActionList.LeadingVisual>
105+
Archive all cards
106+
</ActionList.Item>
107+
<ActionList.Item variant="danger">
108+
<ActionList.LeadingVisual>
109+
<TrashIcon />
110+
</ActionList.LeadingVisual>
111+
Delete
112+
</ActionList.Item>
113+
</ActionList>
114+
</ActionMenu>
115+
)
116+
```
117+
118+
### With Groups
119+
120+
```javascript live noinline
121+
// import {ActionMenu, ActionList} from '@primer/components/drafts'
122+
const {ActionMenu, ActionList} = drafts // ignore docs silliness; import like that ↑
123+
124+
render(
125+
<ActionMenu>
126+
<ActionMenu.Button>Open column menu</ActionMenu.Button>
127+
128+
<ActionList showDividers>
129+
<ActionList.Group title="Live query">
130+
<ActionList.Item>
131+
<ActionList.LeadingVisual>
132+
<SearchIcon />
133+
</ActionList.LeadingVisual>
134+
repo:github/memex,github/github
135+
</ActionList.Item>
136+
</ActionList.Group>
137+
<ActionList.Divider />
138+
<ActionList.Group title="Layout" variant="subtle">
139+
<ActionList.Item>
140+
<ActionList.LeadingVisual>
141+
<NoteIcon />
142+
</ActionList.LeadingVisual>
143+
Table
144+
<ActionList.Description variant="block">
145+
Information-dense table optimized for operations across teams
146+
</ActionList.Description>
147+
</ActionList.Item>
148+
<ActionList.Item role="listitem">
149+
<ActionList.LeadingVisual>
150+
<ProjectIcon />
151+
</ActionList.LeadingVisual>
152+
Board
153+
<ActionList.Description variant="block">Kanban-style board focused on visual states</ActionList.Description>
154+
</ActionList.Item>
155+
</ActionList.Group>
156+
<ActionList.Divider />
157+
<ActionList.Group>
158+
<ActionList.Item>
159+
<ActionList.LeadingVisual>
160+
<FilterIcon />
161+
</ActionList.LeadingVisual>
162+
Save sort and filters to current view
163+
</ActionList.Item>
164+
<ActionList.Item>
165+
<ActionList.LeadingVisual>
166+
<FilterIcon />
167+
</ActionList.LeadingVisual>
168+
Save sort and filters to new view
169+
</ActionList.Item>
170+
</ActionList.Group>
171+
<ActionList.Divider />
172+
<ActionList.Group>
173+
<ActionList.Item>
174+
<ActionList.LeadingVisual>
175+
<GearIcon />
176+
</ActionList.LeadingVisual>
177+
View settings
178+
</ActionList.Item>
179+
</ActionList.Group>
180+
</ActionList>
181+
</ActionMenu>
182+
)
183+
```
184+
185+
### With External Anchor
186+
187+
To create an anchor outside of the menu, you need to switch to controlled mode for the menu and pass it as `anchorRef` to `ActionMenu`:
188+
189+
```javascript live noinline
190+
// import {ActionMenu, ActionList} from '@primer/components/drafts'
191+
const {ActionMenu, ActionList} = drafts // ignore docs silliness; import like that ↑
192+
193+
const Example = () => {
194+
const [open, setOpen] = React.useState(false)
195+
const anchorRef = React.createRef()
196+
197+
return (
198+
<>
199+
<Button ref={anchorRef} onClick={() => setOpen(!open)}>
200+
{open ? 'Close Menu' : 'Open Menu'}
201+
</Button>
202+
203+
<ActionMenu open={open} onOpenChange={setOpen} anchorRef={anchorRef}>
204+
<ActionList>
205+
<ActionList.Item>Copy link</ActionList.Item>
206+
<ActionList.Item>Quote reply</ActionList.Item>
207+
<ActionList.Item>Edit comment</ActionList.Item>
208+
<ActionList.Divider />
209+
<ActionList.Item variant="danger">Delete file</ActionList.Item>
210+
</ActionList>
211+
</ActionMenu>
212+
</>
213+
)
214+
}
215+
216+
render(<Example />)
217+
```
218+
219+
## Props / API reference
220+
221+
### ActionMenu
222+
223+
| Name | Type | Default | Description |
224+
| :----------- | :-------------------------------------------------- | :-----: | :----------------------------------------------------------------------------------------------------------------------- |
225+
| children\* | `React.ReactElement[]` | - | Required. Recommended: `ActionMenu.Button` or `ActionMenu.Anchor` with [`ActionList`](/drafts/ActionList2) |
226+
| open | `boolean` | - | Optional. If defined, will control the open/closed state of the overlay. Must be used in conjuction with `onOpenChange`. |
227+
| onOpenChange | `(open: boolean) => void` | - | Optional. If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`. |
228+
| anchorRef | `React.RefObject<HTMLElement>` | - | Optional. Useful for defining an external anchor |
229+
| overlayProps | [`Partial<OverlayProps>`](/Overlay#component-props) | - | Optional. Props to be spread on the internal [`AnchoredOverlay`](/AnchoredOverlay) component. |
230+
231+
### ActionMenu.Button
232+
233+
| Type | Default | Description |
234+
| :-------------------------------------- | :-----: | :------------------------------------------------------------------------------------------------------------------- |
235+
| [ButtonProps](/Buttons#component-props) | - | Optional. You can pass all of the props that you would pass to a [`Button`](/Buttons) component like `variant`, `sx` |
236+
237+
### ActionMenu.Anchor
238+
239+
| Name | Type | Default | Description |
240+
| :--------- | :------------------- | :-----: | :-------------------------------- |
241+
| children\* | `React.ReactElement` | - | Required. Accepts a single child. |
242+
243+
## Further reading
244+
245+
[Interface guidelines: Action List + Menu](https://primer.style/design/components/action-list)
246+
247+
## Related components
248+
249+
- [ActionList](/drafts/ActionList2)
250+
- [DropdownMenu](/DropdownMenu)
251+
- [SelectPanel](/SelectPanel)

docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ import {
2424
LawIcon,
2525
StarIcon,
2626
AlertIcon,
27-
ArrowRightIcon
27+
ArrowRightIcon,
28+
KebabHorizontalIcon,
29+
PencilIcon,
30+
ArchiveIcon,
31+
TrashIcon
2832
} from '@primer/octicons-react'
2933
import State from '../../../components/State'
3034
import {Dialog as Dialog2} from '../../../../src/Dialog/Dialog'
@@ -65,6 +69,10 @@ export default {
6569
StarIcon,
6670
AlertIcon,
6771
ArrowRightIcon,
72+
KebabHorizontalIcon,
73+
PencilIcon,
74+
ArchiveIcon,
75+
TrashIcon,
6876
Dialog2,
6977
ConfirmationDialog,
7078
useConfirm,

src/ActionList2/Divider.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@ import React from 'react'
22
import Box from '../Box'
33
import {get} from '../constants'
44
import {Theme} from '../ThemeProvider'
5+
import {SxProp, merge} from '../sx'
56

67
/**
78
* Visually separates `Item`s or `Group`s in an `ActionList`.
89
*/
9-
export function Divider(): JSX.Element {
10+
11+
export const Divider: React.FC<SxProp> = ({sx = {}}) => {
1012
return (
1113
<Box
1214
as="li"
1315
role="separator"
14-
sx={{
15-
height: 1,
16-
backgroundColor: 'actionListItem.inlineDivider',
17-
marginTop: (theme: Theme) => `calc(${get('space.2')(theme)} - 1px)`,
18-
marginBottom: 2,
19-
listStyle: 'none' // hide the ::marker inserted by browser's stylesheet
20-
}}
16+
sx={merge(
17+
{
18+
height: 1,
19+
backgroundColor: 'actionListItem.inlineDivider',
20+
marginTop: (theme: Theme) => `calc(${get('space.2')(theme)} - 1px)`,
21+
marginBottom: 2,
22+
listStyle: 'none' // hide the ::marker inserted by browser's stylesheet
23+
},
24+
sx as SxProp
25+
)}
2126
data-component="ActionList.Divider"
2227
/>
2328
)

0 commit comments

Comments
 (0)