diff --git a/.changeset/deprecate-actionmenuv1-promote-actionmenuv2.md b/.changeset/deprecate-actionmenuv1-promote-actionmenuv2.md
new file mode 100644
index 00000000000..3fbec5cf4eb
--- /dev/null
+++ b/.changeset/deprecate-actionmenuv1-promote-actionmenuv2.md
@@ -0,0 +1,69 @@
+---
+'@primer/react': major
+---
+
+### ActionMenu
+
+ActionMenu has been rewritten with a composable API, design updates and accessibility fixes.
+
+See full list of props and examples: https://primer.style/react/ActionMenu
+
+Main changes:
+
+1. Instead of using `items` prop, use `ActionList` inside `ActionMenu`
+2. Instead of using `anchorContent` on `ActionMenu`, use `ActionMenu.Button` with `children`
+3. Instead of using `onAction` prop on `ActionMenu`, use `onSelect` prop on `ActionList.Item`
+4. Instead of using `groupMetadata` on `ActionMenu`, use `ActionList.Group`
+5. Instead of `overlayProps` on `ActionMenu`, use `ActionMenu.Overlay`
+
+
+
+ Before (v34) After (v35)
+
+
+
+
+```jsx
+
+```
+
+
+
+
+```jsx
+
+ Menu
+
+
+ New file
+ Copy link
+ Edit file
+
+ Delete file
+
+
+
+```
+
+
+
+
+
+To continue to use the deprecated API for now, change the import path to `@primer/react/deprecated`:
+
+```js
+import {ActionMenu} from '@primer/react/deprecated'
+```
+
+You can use the [one-time codemod](https://github.com/primer/react-migrate#readme) to change your import statements automatically.
diff --git a/.changeset/empty-pillows-hunt.md b/.changeset/empty-pillows-hunt.md
index 922ed22e895..bda4d62c285 100644
--- a/.changeset/empty-pillows-hunt.md
+++ b/.changeset/empty-pillows-hunt.md
@@ -2,4 +2,151 @@
'@primer/react': major
---
-Prepare library for `v35`
+### ActionList
+
+ActionList now ships a composable API.
+
+See full list of props and examples: https://primer.style/react/ActionList
+
+
+
+ Before After
+
+
+
+
+```jsx
+
+```
+
+
+
+
+```jsx
+
+ New file
+ Copy link
+ Edit file
+
+ Delete file
+
+```
+
+
+
+
+
+
+```jsx
+ ,
+ text: 'mona',
+ description: 'Monalisa Octocat',
+ descriptionVariant: 'block'
+ },
+ {
+ key: '2',
+ leadingVisual: GearIcon,
+ text: 'View Settings',
+ trailingVisual: ArrowRightIcon
+ }
+ ]}
+/>
+```
+
+
+
+
+```jsx
+
+
+
+
+
+ github/primer
+
+
+
+
+
+ mona
+ Monalisa Octocat
+
+
+
+
+
+ View settings
+
+
+
+
+
+```
+
+
+
+
+
+
+```jsx
+
+```
+
+
+
+
+```jsx
+
+
+ repo:github/github
+
+
+
+
+ Table
+ Board Description>
+
+
+
+ View settings
+
+```
+
+
+
+
+
+To continue to use the deprecated API for now, change the import path to `@primer/react/deprecated`:
+
+```js
+import {ActionList} from '@primer/react/deprecated'
+```
diff --git a/docs/content/ActionList.mdx b/docs/content/ActionList.mdx
index be09bd7efee..f414bdd2f3d 100644
--- a/docs/content/ActionList.mdx
+++ b/docs/content/ActionList.mdx
@@ -444,6 +444,6 @@ render( )
## Related components
-- [ActionMenu](/drafts/ActionMenu2)
+- [ActionMenu](/ActionMenu)
- [DropdownMenu](/DropdownMenu)
- [SelectPanel](/SelectPanel)
diff --git a/docs/content/ActionMenu.mdx b/docs/content/ActionMenu.mdx
index 1c0e13ecd9b..8c97169f556 100644
--- a/docs/content/ActionMenu.mdx
+++ b/docs/content/ActionMenu.mdx
@@ -2,81 +2,338 @@
componentId: action_menu
title: ActionMenu
status: Alpha
+source: https://github.com/primer/react/tree/main/src/ActionMenu.tsx
+storybook: '/react/storybook?path=/story/composite-components-actionmenu'
+description: An ActionMenu is an ActionList-based component for creating a menu of actions that expands through a trigger button.
---
-An `ActionMenu` is an ActionList-based component for creating a menu of actions that expands through a trigger button.
+import {Box, Avatar, ActionList, ActionMenu} from '@primer/react'
-## Default example
+
+
+
+
+ Menu
+
+
+
+ Copy link
+ ⌘C
+
+
+ Quote reply
+ ⌘Q
+
+
+ Edit comment
+ ⌘E
+
+
+
+ Delete file
+ ⌘D
+
+
+
+
+
+
+
+
+```js
+import {ActionMenu} from '@primer/react/drafts'
+```
+
+
+
+## Examples
+
+### Minimal example
+
+`ActionMenu` ships with `ActionMenu.Button` which is an accessible trigger for the overlay. It's recommended to compose `ActionList` with `ActionMenu.Overlay`
+
+
```jsx live
- console.log(text)}
- items={[
- {text: 'New file', key: 'new-file'},
- ActionMenu.Divider,
- {text: 'Copy link', key: 'copy-link'},
- {text: 'Edit file', key: 'edit-file'},
- {text: 'Delete file', variant: 'danger', key: 'delete-file'}
- ]}
-/>
+
+ Menu
+
+
+
+ console.log('New file')}>New file
+ Copy link
+ Edit file
+
+ Delete file
+
+
+
```
-## Example with grouped items
+### With a custom anchor
+
+You can choose to have a different _anchor_ for the Menu dependending on the application's context.
+
+
```jsx live
- console.log(text)}
- groupMetadata={[
- {groupId: '0'},
- {groupId: '1', header: {title: 'Live query', variant: 'subtle'}},
- {groupId: '2', header: {title: 'Layout', variant: 'subtle'}},
- {groupId: '3'},
- {groupId: '4'}
- ]}
- items={[
- {key: '1', leadingVisual: TypographyIcon, text: 'Rename', groupId: '0'},
- {key: '2', leadingVisual: VersionsIcon, text: 'Duplicate', groupId: '0'},
- {key: '3', leadingVisual: SearchIcon, text: 'repo:github/github', groupId: '1'},
- {
- key: '4',
- leadingVisual: NoteIcon,
- text: 'Table',
- description: 'Information-dense table optimized for operations across teams',
- descriptionVariant: 'block',
- groupId: '2'
- },
- {
- key: '5',
- leadingVisual: ProjectIcon,
- text: 'Board',
- description: 'Kanban-style board focused on visual states',
- descriptionVariant: 'block',
- groupId: '2'
- },
- {
- key: '6',
- leadingVisual: FilterIcon,
- text: 'Save sort and filters to current view',
- disabled: true,
- groupId: '3'
- },
- {key: '7', leadingVisual: FilterIcon, text: 'Save sort and filters to new view', groupId: '3'},
- {key: '8', leadingVisual: GearIcon, text: 'View settings', groupId: '4'}
- ]}
-/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rename
+
+
+
+
+
+ Archive all cards
+
+
+
+
+
+ Delete
+
+
+
+
+```
+
+### With Groups
+
+```jsx live
+
+ Open column menu
+
+
+
+
+
+
+
+
+ repo:github/memex,github/github
+
+
+
+
+
+
+
+
+ Table
+
+ Information-dense table optimized for operations across teams
+
+
+
+
+
+
+ Board
+ Kanban-style board focused on visual states
+
+
+
+
+
+
+
+
+ Save sort and filters to current view
+
+
+
+
+
+ Save sort and filters to new view
+
+
+
+
+
+
+
+
+ View settings
+
+
+
+
+
+```
+
+### With selection
+
+Use `selectionVariant` on `ActionList` to create a menu with single or multiple selection.
+
+```javascript live noinline
+const fieldTypes = [
+ {icon: TypographyIcon, name: 'Text'},
+ {icon: NumberIcon, name: 'Number'},
+ {icon: CalendarIcon, name: 'Date'},
+ {icon: SingleSelectIcon, name: 'Single select'},
+ {icon: IterationsIcon, name: 'Iteration'}
+]
+
+const Example = () => {
+ const [selectedIndex, setSelectedIndex] = React.useState(1)
+ const selectedType = fieldTypes[selectedIndex]
+
+ return (
+
+
+ {selectedType.name}
+
+
+
+ {fieldTypes.map((type, index) => (
+ setSelectedIndex(index)}>
+ {type.name}
+
+ ))}
+
+
+
+ )
+}
+
+render( )
```
-## Component props
-
-| Name | Type | Default | Description |
-| :------------ | :------------------------------------ | :---------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| items | `ItemProps[]` | `undefined` | Required. A list of item objects conforming to the `ActionList.Item` props interface. |
-| renderItem | `(props: ItemProps) => JSX.Element` | `ActionList.Item` | Optional. If defined, each item in `items` will be passed to this function, allowing for `ActionList`-wide custom item rendering. |
-| groupMetadata | `GroupProps[]` | `undefined` | Optional. If defined, `ActionList` will group `items` into `ActionList.Group`s separated by `ActionList.Divider` according to their `groupId` property. |
-| renderAnchor | `(props: ButtonProps) => JSX.Element` | `Button` | Optional. If defined, provided component will be used to render the menu anchor. Will receive the selected `Item` text as `children` prop when an item is activated. |
-| anchorContent | React.ReactNode | `undefined` | Optional. If defined, it will be passed to the trigger as the elements child. |
-| onAction | (props: ItemProps) => void | `undefined` | Optional. If defined, this function will be called when a menu item is activated either by a click or a keyboard press. |
-| open | boolean | `undefined` | Optional. If defined, ActionMenu will use this to control the open/closed state of the Overlay instead of controlling the state internally. Should be used in conjunction with the `setOpen` prop. |
-| setOpen | (state: boolean) => void | `undefined` | Optional. If defined, ActionMenu will use this to control the open/closed state of the Overlay instead of controlling the state internally. Should be used in conjunction with the `open` prop. |
+### With External Anchor
+
+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`. Make sure you add `aria-expanded` and `aria-haspopup` to the external anchor:
+
+```javascript live noinline
+const Example = () => {
+ const [open, setOpen] = React.useState(false)
+ const anchorRef = React.createRef()
+
+ return (
+ <>
+ setOpen(!open)}>
+ {open ? 'Close Menu' : 'Open Menu'}
+
+
+
+
+
+ Copy link
+ Quote reply
+ Edit comment
+
+ Delete file
+
+
+
+ >
+ )
+}
+
+render( )
+```
+
+### With Overlay Props
+
+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`:
+
+```javascript live noinline
+const handleEscape = () => alert('you hit escape!')
+
+render(
+
+ Open Actions Menu
+
+
+
+ Open current Codespace
+
+ Your existing Codespace will be opened to its previous state, and you'll be asked to manually switch to
+ new-branch.
+
+ ⌘O
+
+
+ Create new Codespace
+
+ Create a brand new Codespace with a fresh image and checkout this branch.
+
+ ⌘C
+
+
+
+
+)
+```
+
+## Props / API reference
+
+### ActionMenu
+
+| Name | Type | Default | Description |
+| :----------- | :----------------------------- | :-----: | :----------------------------------------------------------------------------------------------------------------------- |
+| children\* | `React.ReactElement[]` | - | Required. Recommended: `ActionMenu.Button` or `ActionMenu.Anchor` with `ActionMenu.Overlay` |
+| open | `boolean` | - | Optional. If defined, will control the open/closed state of the overlay. Must be used in conjuction with `onOpenChange`. |
+| onOpenChange | `(open: boolean) => void` | - | Optional. If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`. |
+| anchorRef | `React.RefObject` | - | Optional. Useful for defining an external anchor |
+
+### ActionMenu.Button
+
+| Type | Default | Description |
+| :----------------------------------------------- | :-----: | :---------------------------------------------------------------------------------------------------------------- |
+| [Button v2 props](/drafts/Button2#api-reference) | - | You can pass all of the props that you would pass to a [`Button`](/drafts/Button2) component like `variant`, `sx` |
+
+### ActionMenu.Anchor
+
+| Name | Type | Default | Description |
+| :--------- | :------------------- | :-----: | :-------------------------------- |
+| children\* | `React.ReactElement` | - | Required. Accepts a single child. |
+
+### ActionMenu.Overlay
+
+| Name | Type | Default | Description |
+| :--------------------------------------- | :-------------------- | :-----------------: | :-------------------------------------------------------------------------------------------- |
+| children\* | `React.ReactElement[] | React.ReactElement` | Required. Recommended: [`ActionList`](/ActionList) |
+| [OverlayProps](/Overlay#component-props) | - | - | Optional. Props to be spread on the internal [`AnchoredOverlay`](/AnchoredOverlay) component. |
+
+## Status
+
+
+
+## Further reading
+
+[Interface guidelines: Action List + Menu](https://primer.style/design/components/action-list)
+
+## Related components
+
+- [ActionList](/ActionList)
+- [SelectPanel](/SelectPanel)
+- [Button](/drafts/Button2)
diff --git a/docs/content/SideNav.md b/docs/content/SideNav.md
index bb6d6bf3f67..04c2ff0535d 100644
--- a/docs/content/SideNav.md
+++ b/docs/content/SideNav.md
@@ -114,11 +114,11 @@ It can also appear nested, as a sub navigation. Use margin/padding [System Props
```jsx live
-
+
Account
-
+
Profile
diff --git a/docs/content/deprecated/ActionMenu.mdx b/docs/content/deprecated/ActionMenu.mdx
new file mode 100644
index 00000000000..0fa65bbc4b9
--- /dev/null
+++ b/docs/content/deprecated/ActionMenu.mdx
@@ -0,0 +1,127 @@
+---
+componentId: action_menu
+title: ActionMenu
+status: Deprecated
+source: https://github.com/primer/react/tree/main/src/deprecated/ActionMenu.tsx
+---
+
+An `ActionMenu` is an ActionList-based component for creating a menu of actions that expands through a trigger button.
+
+## Deprecation
+
+Use [new version of ActionMenu](/ActionMenu) with composable API, design updates and accessibility fixes.
+
+**Before**
+
+```jsx
+
+```
+
+**After**
+
+```jsx
+
+ Menu
+
+
+ New file
+ Copy link
+ Edit file
+
+ Delete file
+
+
+
+```
+
+Or continue using deprecated API:
+
+```js
+import {ActionMenu} from '@primer/react/deprecated'
+```
+
+## Default example
+
+```jsx live deprecated
+ console.log(text)}
+ items={[
+ {text: 'New file', key: 'new-file'},
+ ActionMenu.Divider,
+ {text: 'Copy link', key: 'copy-link'},
+ {text: 'Edit file', key: 'edit-file'},
+ {text: 'Delete file', variant: 'danger', key: 'delete-file'}
+ ]}
+/>
+```
+
+## Example with grouped items
+
+```jsx live deprecated
+ console.log(text)}
+ groupMetadata={[
+ {groupId: '0'},
+ {groupId: '1', header: {title: 'Live query', variant: 'subtle'}},
+ {groupId: '2', header: {title: 'Layout', variant: 'subtle'}},
+ {groupId: '3'},
+ {groupId: '4'}
+ ]}
+ items={[
+ {key: '1', leadingVisual: TypographyIcon, text: 'Rename', groupId: '0'},
+ {key: '2', leadingVisual: VersionsIcon, text: 'Duplicate', groupId: '0'},
+ {key: '3', leadingVisual: SearchIcon, text: 'repo:github/github', groupId: '1'},
+ {
+ key: '4',
+ leadingVisual: NoteIcon,
+ text: 'Table',
+ description: 'Information-dense table optimized for operations across teams',
+ descriptionVariant: 'block',
+ groupId: '2'
+ },
+ {
+ key: '5',
+ leadingVisual: ProjectIcon,
+ text: 'Board',
+ description: 'Kanban-style board focused on visual states',
+ descriptionVariant: 'block',
+ groupId: '2'
+ },
+ {
+ key: '6',
+ leadingVisual: FilterIcon,
+ text: 'Save sort and filters to current view',
+ disabled: true,
+ groupId: '3'
+ },
+ {key: '7', leadingVisual: FilterIcon, text: 'Save sort and filters to new view', groupId: '3'},
+ {key: '8', leadingVisual: GearIcon, text: 'View settings', groupId: '4'}
+ ]}
+/>
+```
+
+## Component props
+
+| Name | Type | Default | Description |
+| :------------ | :------------------------------------ | :---------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| items | `ItemProps[]` | `undefined` | Required. A list of item objects conforming to the `ActionList.Item` props interface. |
+| renderItem | `(props: ItemProps) => JSX.Element` | `ActionList.Item` | Optional. If defined, each item in `items` will be passed to this function, allowing for `ActionList`-wide custom item rendering. |
+| groupMetadata | `GroupProps[]` | `undefined` | Optional. If defined, `ActionList` will group `items` into `ActionList.Group`s separated by `ActionList.Divider` according to their `groupId` property. |
+| renderAnchor | `(props: ButtonProps) => JSX.Element` | `Button` | Optional. If defined, provided component will be used to render the menu anchor. Will receive the selected `Item` text as `children` prop when an item is activated. |
+| anchorContent | React.ReactNode | `undefined` | Optional. If defined, it will be passed to the trigger as the elements child. |
+| onAction | (props: ItemProps) => void | `undefined` | Optional. If defined, this function will be called when a menu item is activated either by a click or a keyboard press. |
+| open | boolean | `undefined` | Optional. If defined, ActionMenu will use this to control the open/closed state of the Overlay instead of controlling the state internally. Should be used in conjunction with the `setOpen` prop. |
+| setOpen | (state: boolean) => void | `undefined` | Optional. If defined, ActionMenu will use this to control the open/closed state of the Overlay instead of controlling the state internally. Should be used in conjunction with the `open` prop. |
diff --git a/docs/content/drafts/ActionMenu2.mdx b/docs/content/drafts/ActionMenu2.mdx
deleted file mode 100644
index 5a1af58d8da..00000000000
--- a/docs/content/drafts/ActionMenu2.mdx
+++ /dev/null
@@ -1,341 +0,0 @@
----
-componentId: action_menu2
-title: ActionMenu v2
-status: Alpha
-source: https://github.com/primer/react/tree/main/src/ActionMenu
-storybook: '/react/storybook?path=/story/composite-components-actionmenu2'
-description: An ActionMenu is an ActionList-based component for creating a menu of actions that expands through a trigger button.
----
-
-import {Box, Avatar, ActionList} from '@primer/react'
-import {ActionMenu} from '@primer/react/drafts'
-import {Props} from '../../src/props'
-
-
-
-
-
- Menu
-
-
-
- Copy link
- ⌘C
-
-
- Quote reply
- ⌘Q
-
-
- Edit comment
- ⌘E
-
-
-
- Delete file
- ⌘D
-
-
-
-
-
-
-
-
-```js
-import {ActionMenu} from '@primer/react/drafts'
-```
-
-
-
-## Examples
-
-### Minimal example
-
-`ActionMenu` ships with `ActionMenu.Button` which is an accessible trigger for the overlay. It's recommended to compose `ActionList` with `ActionMenu.Overlay`
-
-
-
-```jsx live drafts
-
- Menu
-
-
-
- console.log('New file')}>New file
- Copy link
- Edit file
-
- Delete file
-
-
-
-```
-
-### With a custom anchor
-
-You can choose to have a different _anchor_ for the Menu dependending on the application's context.
-
-
-
-```jsx live drafts
-
-
-
-
-
-
-
-
-
-
-
-
-
- Rename
-
-
-
-
-
- Archive all cards
-
-
-
-
-
- Delete
-
-
-
-
-```
-
-### With Groups
-
-```jsx live drafts
-
- Open column menu
-
-
-
-
-
-
-
-
- repo:github/memex,github/github
-
-
-
-
-
-
-
-
- Table
-
- Information-dense table optimized for operations across teams
-
-
-
-
-
-
- Board
- Kanban-style board focused on visual states
-
-
-
-
-
-
-
-
- Save sort and filters to current view
-
-
-
-
-
- Save sort and filters to new view
-
-
-
-
-
-
-
-
- View settings
-
-
-
-
-
-```
-
-### With selection
-
-Use `selectionVariant` on `ActionList` to create a menu with single or multiple selection.
-
-```javascript live noinline drafts
-const fieldTypes = [
- {icon: TypographyIcon, name: 'Text'},
- {icon: NumberIcon, name: 'Number'},
- {icon: CalendarIcon, name: 'Date'},
- {icon: SingleSelectIcon, name: 'Single select'},
- {icon: IterationsIcon, name: 'Iteration'}
-]
-
-const Example = () => {
- const [selectedIndex, setSelectedIndex] = React.useState(1)
- const selectedType = fieldTypes[selectedIndex]
-
- return (
-
-
- {selectedType.name}
-
-
-
- {fieldTypes.map((type, index) => (
- setSelectedIndex(index)}>
- {type.name}
-
- ))}
-
-
-
- )
-}
-
-render( )
-```
-
-### With External Anchor
-
-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`. Make sure you add `aria-expanded` and `aria-haspopup` to the external anchor:
-
-```javascript live noinline drafts
-const Example = () => {
- const [open, setOpen] = React.useState(false)
- const anchorRef = React.createRef()
-
- return (
- <>
- setOpen(!open)}>
- {open ? 'Close Menu' : 'Open Menu'}
-
-
-
-
-
- Copy link
- Quote reply
- Edit comment
-
- Delete file
-
-
-
- >
- )
-}
-
-render( )
-```
-
-### With Overlay Props
-
-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`:
-
-```javascript live noinline drafts
-const handleEscape = () => alert('you hit escape!')
-
-render(
-
- Open Actions Menu
-
-
-
- Open current Codespace
-
- Your existing Codespace will be opened to its previous state, and you'll be asked to manually switch to
- new-branch.
-
- ⌘O
-
-
- Create new Codespace
-
- Create a brand new Codespace with a fresh image and checkout this branch.
-
- ⌘C
-
-
-
-
-)
-```
-
-## Props / API reference
-
-### ActionMenu
-
-| Name | Type | Default | Description |
-| :----------- | :----------------------------- | :-----: | :----------------------------------------------------------------------------------------------------------------------- |
-| children\* | `React.ReactElement[]` | - | Required. Recommended: `ActionMenu.Button` or `ActionMenu.Anchor` with `ActionMenu.Overlay` |
-| open | `boolean` | - | Optional. If defined, will control the open/closed state of the overlay. Must be used in conjuction with `onOpenChange`. |
-| onOpenChange | `(open: boolean) => void` | - | Optional. If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`. |
-| anchorRef | `React.RefObject` | - | Optional. Useful for defining an external anchor |
-
-### ActionMenu.Button
-
-| Type | Default | Description |
-| :----------------------------------------------- | :-----: | :---------------------------------------------------------------------------------------------------------------- |
-| [Button v2 props](/drafts/Button2#api-reference) | - | You can pass all of the props that you would pass to a [`Button`](/drafts/Button2) component like `variant`, `sx` |
-
-### ActionMenu.Anchor
-
-| Name | Type | Default | Description |
-| :--------- | :------------------- | :-----: | :-------------------------------- |
-| children\* | `React.ReactElement` | - | Required. Accepts a single child. |
-
-### ActionMenu.Overlay
-
-| Name | Type | Default | Description |
-| :--------------------------------------- | :-------------------- | :-----------------: | :-------------------------------------------------------------------------------------------- |
-| children\* | `React.ReactElement[] | React.ReactElement` | Required. Recommended: [`ActionList`](/ActionList) |
-| [OverlayProps](/Overlay#component-props) | - | - | Optional. Props to be spread on the internal [`AnchoredOverlay`](/AnchoredOverlay) component. |
-
-## Status
-
-
-
-## Further reading
-
-[Interface guidelines: Action List + Menu](https://primer.style/design/components/action-list)
-
-## Related components
-
-- [ActionList](/ActionList)
-- [SelectPanel](/SelectPanel)
-- [Button](/drafts/Button2)
diff --git a/docs/content/drafts/DropdownMenu2.mdx b/docs/content/drafts/DropdownMenu2.mdx
index 8d517f5facb..47eba34b758 100644
--- a/docs/content/drafts/DropdownMenu2.mdx
+++ b/docs/content/drafts/DropdownMenu2.mdx
@@ -360,5 +360,5 @@ Use `DropdownMenu` to select an option from a small list. If you’re looking fo
## Related components
- [ActionList](/ActionList)
-- [ActionMenu](/ActionMenu2)
+- [ActionMenu](/ActionMenu)
- [SelectPanel](/SelectPanel)
diff --git a/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js b/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js
index 5caa96ccbcd..a9bbaf0e419 100644
--- a/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js
+++ b/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js
@@ -18,7 +18,7 @@ import {
MarkGithubIcon,
NoteIcon,
NumberIcon,
- OctofaceIcon,
+ SmileyIcon,
PencilIcon,
PersonIcon,
ProjectIcon,
@@ -60,7 +60,7 @@ export default function resolveScope(metastring) {
ZapIcon,
XIcon,
DotIcon,
- OctofaceIcon,
+ SmileyIcon,
PersonIcon,
MailIcon,
GitCommitIcon,
diff --git a/docs/src/@primer/gatsby-theme-doctocat/nav.yml b/docs/src/@primer/gatsby-theme-doctocat/nav.yml
index 2c977d13db0..48297fc0d96 100644
--- a/docs/src/@primer/gatsby-theme-doctocat/nav.yml
+++ b/docs/src/@primer/gatsby-theme-doctocat/nav.yml
@@ -37,6 +37,8 @@
children:
- title: ActionList
url: /ActionList
+ - title: ActionMenu
+ url: /ActionMenu
- title: Autocomplete
url: /Autocomplete
- title: Avatar
@@ -145,25 +147,25 @@
url: /drafts/LinkButton
- title: IconButton
url: /drafts/IconButton
- - title: ActionMenu v2
- url: /drafts/ActionMenu2
- title: Deprecated
children:
+ - title: ActionList
+ url: /deprecated/ActionList
+ - title: ActionMenu
+ url: /deprecated/ActionMenu
- title: BorderBox
url: /deprecated/BorderBox
- - title: Flex
- url: /deprecated/Flex
- - title: Grid
- url: /deprecated/Grid
- - title: Position
- url: /deprecated/Position
- title: Dialog
url: /deprecated/Dialog
- title: Dropdown
url: /deprecated/Dropdown
+ - title: Flex
+ url: /deprecated/Flex
- title: FormGroup
url: /FormGroup
+ - title: Grid
+ url: /deprecated/Grid
+ - title: Position
+ url: /deprecated/Position
- title: SelectMenu
url: /deprecated/SelectMenu
- - title: ActionList
- url: /deprecated/ActionList
diff --git a/src/ActionMenu.tsx b/src/ActionMenu.tsx
index 142f18c7e24..0107dd178ac 100644
--- a/src/ActionMenu.tsx
+++ b/src/ActionMenu.tsx
@@ -1,106 +1,131 @@
-import {GroupedListProps, List, ListPropsBase} from './deprecated/ActionList/List'
-import {Item, ItemProps} from './deprecated/ActionList/Item'
-import {Divider} from './deprecated/ActionList/Divider'
-import Button, {ButtonProps} from './Button'
-import React, {useCallback, useMemo} from 'react'
-import {AnchoredOverlay} from './AnchoredOverlay'
-import {useProvidedStateOrCreate} from './hooks/useProvidedStateOrCreate'
+import React from 'react'
+import {useSSRSafeId} from '@react-aria/ssr'
+import {TriangleDownIcon} from '@primer/octicons-react'
+import {AnchoredOverlay, AnchoredOverlayProps} from './AnchoredOverlay'
import {OverlayProps} from './Overlay'
-import {useProvidedRefOrCreate} from './hooks'
-import {AnchoredOverlayWrapperAnchorProps} from './AnchoredOverlay/AnchoredOverlay'
+import {useProvidedRefOrCreate, useProvidedStateOrCreate, useMenuInitialFocus, useTypeaheadFocus} from './hooks'
+import {Divider} from './ActionList/Divider'
+import {ActionListContainerContext} from './ActionList/ActionListContainerContext'
+import {Button, ButtonProps} from './Button2'
+import {MandateProps} from './utils/types'
-interface ActionMenuBaseProps extends Partial>, ListPropsBase {
- /**
- * Content that is passed into the renderAnchor component, which is a button by default.
- */
- anchorContent?: React.ReactNode
+type MenuContextProps = Pick<
+ AnchoredOverlayProps,
+ 'anchorRef' | 'renderAnchor' | 'open' | 'onOpen' | 'onClose' | 'anchorId'
+>
+const MenuContext = React.createContext({renderAnchor: null, open: false})
+export type ActionMenuProps = {
/**
- * A callback that triggers both on clicks and keyboard events. This callback will be overridden by item level `onAction` callbacks.
+ * Recommended: `ActionMenu.Button` or `ActionMenu.Anchor` with `ActionMenu.Overlay`
*/
- onAction?: (props: ItemProps, event?: React.MouseEvent | React.KeyboardEvent) => void
+ children: React.ReactElement[] | React.ReactElement
/**
- * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `setOpen`.
+ * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `onOpenChange`.
*/
open?: boolean
/**
* If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`.
*/
- setOpen?: (s: boolean) => void
-
- /**
- * Props to be spread on the internal `Overlay` component.
- */
- overlayProps?: Partial
-}
+ onOpenChange?: (s: boolean) => void
+} & Pick
-export type ActionMenuProps = ActionMenuBaseProps & AnchoredOverlayWrapperAnchorProps
-
-const ActionMenuItem = (props: ItemProps) =>
-
-ActionMenuItem.displayName = 'ActionMenu.Item'
-
-const ActionMenuBase = ({
- anchorContent,
- renderAnchor = (props: T) => ,
+const Menu: React.FC = ({
anchorRef: externalAnchorRef,
- onAction,
open,
- setOpen,
- overlayProps,
- items,
- ...listProps
-}: ActionMenuProps): JSX.Element => {
- const [combinedOpenState, setCombinedOpenState] = useProvidedStateOrCreate(open, setOpen, false)
+ onOpenChange,
+ children
+}: ActionMenuProps) => {
+ const [combinedOpenState, setCombinedOpenState] = useProvidedStateOrCreate(open, onOpenChange, false)
+ const onOpen = React.useCallback(() => setCombinedOpenState(true), [setCombinedOpenState])
+ const onClose = React.useCallback(() => setCombinedOpenState(false), [setCombinedOpenState])
+
const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
- const onOpen = useCallback(() => setCombinedOpenState(true), [setCombinedOpenState])
- const onClose = useCallback(() => setCombinedOpenState(false), [setCombinedOpenState])
+ const anchorId = useSSRSafeId()
+ let renderAnchor: AnchoredOverlayProps['renderAnchor'] = null
- const renderMenuAnchor = useMemo(() => {
- if (renderAnchor === null) {
+ // 🚨 Hack for good API!
+ // we strip out Anchor from children and pass it to AnchoredOverlay to render
+ // with additional props for accessibility
+ const contents = React.Children.map(children, child => {
+ if (child.type === MenuButton || child.type === Anchor) {
+ renderAnchor = anchorProps => React.cloneElement(child, anchorProps)
return null
}
- return >(props: T) => {
- return renderAnchor({
- 'aria-label': 'menu',
- children: anchorContent,
- ...props
- })
- }
- }, [anchorContent, renderAnchor])
+ return child
+ })
+
+ return (
+
+ {contents}
+
+ )
+}
+
+export type ActionMenuAnchorProps = {children: React.ReactElement}
+const Anchor = React.forwardRef(
+ ({children, ...anchorProps}, anchorRef) => {
+ return React.cloneElement(children, {...anchorProps, ref: anchorRef})
+ }
+)
+
+/** this component is syntactical sugar 🍭 */
+export type ActionMenuButtonProps = ButtonProps
+const MenuButton = React.forwardRef((props, anchorRef) => {
+ return (
+
+
+
+ )
+})
+
+type MenuOverlayProps = Partial & {
+ /**
+ * Recommended: `ActionList`
+ */
+ children: React.ReactElement[] | React.ReactElement
+}
+const Overlay: React.FC = ({children, ...overlayProps}) => {
+ // we typecast anchorRef as required instead of optional
+ // because we know that we're setting it in context in Menu
+ const {anchorRef, renderAnchor, anchorId, open, onOpen, onClose} = React.useContext(MenuContext) as MandateProps<
+ MenuContextProps,
+ 'anchorRef'
+ >
- const itemsToRender = useMemo(() => {
- return items.map(item => {
- return {
- ...item,
- role: 'menuitem',
- onAction: (props, event) => {
- const actionCallback = item.onAction ?? onAction
- actionCallback?.(props as ItemProps, event)
- if (!event.defaultPrevented) {
- onClose()
- }
- }
- } as ItemProps
- })
- }, [items, onAction, onClose])
+ const containerRef = React.createRef()
+ const {openWithFocus} = useMenuInitialFocus(open, onOpen, containerRef)
+ useTypeaheadFocus(open, containerRef)
return (
-
+
)
}
-ActionMenuBase.displayName = 'ActionMenu'
-
-export const ActionMenu = Object.assign(ActionMenuBase, {Divider, Item: ActionMenuItem})
+Menu.displayName = 'ActionMenu'
+export const ActionMenu = Object.assign(Menu, {Button: MenuButton, Anchor, Overlay, Divider})
diff --git a/src/ActionMenu2.tsx b/src/ActionMenu2.tsx
deleted file mode 100644
index 0107dd178ac..00000000000
--- a/src/ActionMenu2.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import React from 'react'
-import {useSSRSafeId} from '@react-aria/ssr'
-import {TriangleDownIcon} from '@primer/octicons-react'
-import {AnchoredOverlay, AnchoredOverlayProps} from './AnchoredOverlay'
-import {OverlayProps} from './Overlay'
-import {useProvidedRefOrCreate, useProvidedStateOrCreate, useMenuInitialFocus, useTypeaheadFocus} from './hooks'
-import {Divider} from './ActionList/Divider'
-import {ActionListContainerContext} from './ActionList/ActionListContainerContext'
-import {Button, ButtonProps} from './Button2'
-import {MandateProps} from './utils/types'
-
-type MenuContextProps = Pick<
- AnchoredOverlayProps,
- 'anchorRef' | 'renderAnchor' | 'open' | 'onOpen' | 'onClose' | 'anchorId'
->
-const MenuContext = React.createContext({renderAnchor: null, open: false})
-
-export type ActionMenuProps = {
- /**
- * Recommended: `ActionMenu.Button` or `ActionMenu.Anchor` with `ActionMenu.Overlay`
- */
- children: React.ReactElement[] | React.ReactElement
-
- /**
- * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `onOpenChange`.
- */
- open?: boolean
-
- /**
- * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`.
- */
- onOpenChange?: (s: boolean) => void
-} & Pick
-
-const Menu: React.FC = ({
- anchorRef: externalAnchorRef,
- open,
- onOpenChange,
- children
-}: ActionMenuProps) => {
- const [combinedOpenState, setCombinedOpenState] = useProvidedStateOrCreate(open, onOpenChange, false)
- const onOpen = React.useCallback(() => setCombinedOpenState(true), [setCombinedOpenState])
- const onClose = React.useCallback(() => setCombinedOpenState(false), [setCombinedOpenState])
-
- const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
- const anchorId = useSSRSafeId()
- let renderAnchor: AnchoredOverlayProps['renderAnchor'] = null
-
- // 🚨 Hack for good API!
- // we strip out Anchor from children and pass it to AnchoredOverlay to render
- // with additional props for accessibility
- const contents = React.Children.map(children, child => {
- if (child.type === MenuButton || child.type === Anchor) {
- renderAnchor = anchorProps => React.cloneElement(child, anchorProps)
- return null
- }
- return child
- })
-
- return (
-
- {contents}
-
- )
-}
-
-export type ActionMenuAnchorProps = {children: React.ReactElement}
-const Anchor = React.forwardRef(
- ({children, ...anchorProps}, anchorRef) => {
- return React.cloneElement(children, {...anchorProps, ref: anchorRef})
- }
-)
-
-/** this component is syntactical sugar 🍭 */
-export type ActionMenuButtonProps = ButtonProps
-const MenuButton = React.forwardRef((props, anchorRef) => {
- return (
-
-
-
- )
-})
-
-type MenuOverlayProps = Partial & {
- /**
- * Recommended: `ActionList`
- */
- children: React.ReactElement[] | React.ReactElement
-}
-const Overlay: React.FC = ({children, ...overlayProps}) => {
- // we typecast anchorRef as required instead of optional
- // because we know that we're setting it in context in Menu
- const {anchorRef, renderAnchor, anchorId, open, onOpen, onClose} = React.useContext(MenuContext) as MandateProps<
- MenuContextProps,
- 'anchorRef'
- >
-
- const containerRef = React.createRef()
- const {openWithFocus} = useMenuInitialFocus(open, onOpen, containerRef)
- useTypeaheadFocus(open, containerRef)
-
- return (
-
-
-
- )
-}
-
-Menu.displayName = 'ActionMenu'
-export const ActionMenu = Object.assign(Menu, {Button: MenuButton, Anchor, Overlay, Divider})
diff --git a/src/__tests__/ActionMenu.test.tsx b/src/__tests__/ActionMenu.test.tsx
index 80a5423d3ad..7aa30c560ec 100644
--- a/src/__tests__/ActionMenu.test.tsx
+++ b/src/__tests__/ActionMenu.test.tsx
@@ -1,31 +1,33 @@
-import {cleanup, render as HTMLRender, act, fireEvent} from '@testing-library/react'
+import {cleanup, render as HTMLRender, waitFor, fireEvent} from '@testing-library/react'
import 'babel-polyfill'
import {axe, toHaveNoViolations} from 'jest-axe'
import React from 'react'
import theme from '../theme'
-import {ActionMenu} from '../ActionMenu'
-import {behavesAsComponent, checkExports} from '../utils/testing'
-import {BaseStyles, SSRProvider, ThemeProvider} from '..'
-import {ItemProps} from '../deprecated/ActionList/Item'
+import {ActionMenu, ActionList, BaseStyles, ThemeProvider, SSRProvider} from '..'
+import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
+import {SingleSelection, MixedSelection} from '../stories/ActionMenu/examples.stories'
+import '@testing-library/jest-dom'
expect.extend(toHaveNoViolations)
-const items = [
- {text: 'New file'},
- {text: 'Copy link'},
- {text: 'Edit file'},
- {text: 'Delete file', variant: 'danger'}
-] as ItemProps[]
-
-const mockOnActivate = jest.fn()
-
-function SimpleActionMenu(): JSX.Element {
+function Example(): JSX.Element {
return (
- X
-
-
+
+ Toggle Menu
+
+
+ New file
+
+ Copy link
+ Edit file
+ event.preventDefault()}>
+ Delete file
+
+
+
+
@@ -33,18 +35,10 @@ function SimpleActionMenu(): JSX.Element {
}
describe('ActionMenu', () => {
- afterEach(() => {
- jest.clearAllMocks()
- })
-
behavesAsComponent({
- Component: ActionMenu,
+ Component: ActionList,
options: {skipAs: true, skipSx: true},
- toRender: () => (
-
-
-
- )
+ toRender: () =>
})
checkExports('ActionMenu', {
@@ -52,85 +46,103 @@ describe('ActionMenu', () => {
ActionMenu
})
- it('should have no axe violations', async () => {
- const {container} = HTMLRender( )
- const results = await axe(container)
- expect(results).toHaveNoViolations()
+ it('should open Menu on MenuButton click', async () => {
+ const component = HTMLRender( )
+ const button = component.getByText('Toggle Menu')
+ fireEvent.click(button)
+ expect(component.getByRole('menu')).toBeInTheDocument()
+ cleanup()
+ })
+
+ it('should open Menu on MenuButton keypress', async () => {
+ const component = HTMLRender( )
+ const button = component.getByText('Toggle Menu')
+
+ // We pass keycode here to navigate a implementation detail in react-testing-library
+ // https://github.com/testing-library/react-testing-library/issues/269#issuecomment-455854112
+ fireEvent.keyDown(button, {key: 'Enter', charCode: 13})
+ expect(component.getByRole('menu')).toBeInTheDocument()
+ cleanup()
+ })
+
+ it('should close Menu on selecting an action with click', async () => {
+ const component = HTMLRender( )
+ const button = component.getByText('Toggle Menu')
+
+ fireEvent.click(button)
+ const menuItems = await waitFor(() => component.getAllByRole('menuitem'))
+ fireEvent.click(menuItems[0])
+ expect(component.queryByRole('menu')).toBeNull()
+
+ cleanup()
+ })
+
+ it('should close Menu on selecting an action with Enter', async () => {
+ const component = HTMLRender( )
+ const button = component.getByText('Toggle Menu')
+
+ fireEvent.click(button)
+ const menuItems = await waitFor(() => component.getAllByRole('menuitem'))
+ fireEvent.keyPress(menuItems[0], {key: 'Enter', charCode: 13})
+ expect(component.queryByRole('menu')).toBeNull()
+
cleanup()
})
- it('should trigger the overlay on trigger click', async () => {
- const menu = HTMLRender( )
- let portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
- expect(portalRoot).toBeNull()
- const anchor = await menu.findByText('Menu')
- act(() => {
- fireEvent.click(anchor)
- })
- portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
- expect(portalRoot).toBeTruthy()
- const itemText = items
- .map((i: ItemProps) => {
- if (i.hasOwnProperty('text')) {
- return i.text
- }
- })
- .join('')
- expect(portalRoot?.textContent?.trim()).toEqual(itemText)
+ it('should not close Menu if event is prevented', async () => {
+ const component = HTMLRender( )
+ const button = component.getByText('Toggle Menu')
+
+ fireEvent.click(button)
+ const menuItems = await waitFor(() => component.getAllByRole('menuitem'))
+ fireEvent.click(menuItems[3])
+ // menu should still be open
+ expect(component.getByRole('menu')).toBeInTheDocument()
+
+ cleanup()
})
- it('should dismiss the overlay on menuitem click', async () => {
- const menu = HTMLRender( )
- let portalRoot = await menu.baseElement.querySelector('#__primerPortalRoot__')
- expect(portalRoot).toBeNull()
- const anchor = await menu.findByText('Menu')
- act(() => {
- fireEvent.click(anchor)
- })
- portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
- expect(portalRoot).toBeTruthy()
- const menuItem = await menu.queryByText(items[0].text!)
- act(() => {
- fireEvent.click(menuItem as Element)
- })
- expect(portalRoot?.textContent).toEqual('') // menu items are hidden
+ it('should be able to select an Item with selectionVariant', async () => {
+ const component = HTMLRender(
+
+
+
+ )
+ const button = component.getByLabelText('Select field type')
+ fireEvent.click(button)
+
+ // select first item by role, that would close the menu
+ fireEvent.click(component.getAllByRole('menuitemradio')[0])
+ expect(component.queryByRole('menu')).not.toBeInTheDocument()
+
+ // open menu again and check if the first option is checked
+ fireEvent.click(button)
+ expect(component.getAllByRole('menuitemradio')[0]).toHaveAttribute('aria-checked', 'true')
+ cleanup()
})
- it('should dismiss the overlay on clicking outside overlay', async () => {
- const menu = HTMLRender( )
- let portalRoot = await menu.baseElement.querySelector('#__primerPortalRoot__')
- expect(portalRoot).toBeNull()
- const anchor = await menu.findByText('Menu')
- act(() => {
- fireEvent.click(anchor)
- })
- portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
- expect(portalRoot).toBeTruthy()
- const somethingElse = (await menu.baseElement.querySelector('#something-else')) as HTMLElement
- act(() => {
- fireEvent.mouseDown(somethingElse)
- })
- expect(portalRoot?.textContent).toEqual('') // menu items are hidden
+ it('should assign the right roles with groups & mixed selectionVariant', async () => {
+ const component = HTMLRender(
+
+
+
+ )
+ const button = component.getByLabelText('Select field type to group by')
+ fireEvent.click(button)
+
+ expect(component.getByLabelText('Status')).toHaveAttribute('role', 'menuitemradio')
+ expect(component.getByLabelText('Clear Group by')).toHaveAttribute('role', 'menuitem')
+
+ cleanup()
})
- it('should pass correct values to onAction on menu click', async () => {
- const menu = HTMLRender( )
- let portalRoot = await menu.baseElement.querySelector('#__primerPortalRoot__')
- expect(portalRoot).toBeNull()
- const anchor = await menu.findByText('Menu')
- act(() => {
- fireEvent.click(anchor)
- })
- portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
- expect(portalRoot).toBeTruthy()
- const menuItem = (await portalRoot?.querySelector("[role='menuitem']")) as HTMLElement
- act(() => {
- fireEvent.click(menuItem)
- })
-
- // onAction has been called with correct argument
- expect(mockOnActivate).toHaveBeenCalledTimes(1)
- const arg = mockOnActivate.mock.calls[0][0]
- expect(arg.text).toEqual(items[0].text)
+ it('should have no axe violations', async () => {
+ const {container} = HTMLRender( )
+ const results = await axe(container)
+ expect(results).toHaveNoViolations()
+ cleanup()
})
+
+ checkStoriesForAxeViolations('ActionMenu/fixtures')
+ checkStoriesForAxeViolations('ActionMenu/examples')
})
diff --git a/src/__tests__/ActionMenu2.test.tsx b/src/__tests__/ActionMenu2.test.tsx
deleted file mode 100644
index 2f465c277cf..00000000000
--- a/src/__tests__/ActionMenu2.test.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import {cleanup, render as HTMLRender, waitFor, fireEvent} from '@testing-library/react'
-import 'babel-polyfill'
-import {axe, toHaveNoViolations} from 'jest-axe'
-import React from 'react'
-import theme from '../theme'
-import {ActionMenu} from '../ActionMenu2'
-import {ActionList} from '../ActionList'
-import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
-import {BaseStyles, ThemeProvider, SSRProvider} from '..'
-import {SingleSelection, MixedSelection} from '../../src/stories/ActionMenu2/examples.stories'
-import '@testing-library/jest-dom'
-expect.extend(toHaveNoViolations)
-
-function Example(): JSX.Element {
- return (
-
-
-
-
- Toggle Menu
-
-
- New file
-
- Copy link
- Edit file
- event.preventDefault()}>
- Delete file
-
-
-
-
-
-
-
- )
-}
-
-describe('ActionMenu', () => {
- behavesAsComponent({
- Component: ActionList,
- options: {skipAs: true, skipSx: true},
- toRender: () =>
- })
-
- checkExports('ActionMenu2', {
- default: undefined,
- ActionMenu
- })
-
- it('should open Menu on MenuButton click', async () => {
- const component = HTMLRender( )
- const button = component.getByText('Toggle Menu')
- fireEvent.click(button)
- expect(component.getByRole('menu')).toBeInTheDocument()
- cleanup()
- })
-
- it('should open Menu on MenuButton keypress', async () => {
- const component = HTMLRender( )
- const button = component.getByText('Toggle Menu')
-
- // We pass keycode here to navigate a implementation detail in react-testing-library
- // https://github.com/testing-library/react-testing-library/issues/269#issuecomment-455854112
- fireEvent.keyDown(button, {key: 'Enter', charCode: 13})
- expect(component.getByRole('menu')).toBeInTheDocument()
- cleanup()
- })
-
- it('should close Menu on selecting an action with click', async () => {
- const component = HTMLRender( )
- const button = component.getByText('Toggle Menu')
-
- fireEvent.click(button)
- const menuItems = await waitFor(() => component.getAllByRole('menuitem'))
- fireEvent.click(menuItems[0])
- expect(component.queryByRole('menu')).toBeNull()
-
- cleanup()
- })
-
- it('should close Menu on selecting an action with Enter', async () => {
- const component = HTMLRender( )
- const button = component.getByText('Toggle Menu')
-
- fireEvent.click(button)
- const menuItems = await waitFor(() => component.getAllByRole('menuitem'))
- fireEvent.keyPress(menuItems[0], {key: 'Enter', charCode: 13})
- expect(component.queryByRole('menu')).toBeNull()
-
- cleanup()
- })
-
- it('should not close Menu if event is prevented', async () => {
- const component = HTMLRender( )
- const button = component.getByText('Toggle Menu')
-
- fireEvent.click(button)
- const menuItems = await waitFor(() => component.getAllByRole('menuitem'))
- fireEvent.click(menuItems[3])
- // menu should still be open
- expect(component.getByRole('menu')).toBeInTheDocument()
-
- cleanup()
- })
-
- it('should be able to select an Item with selectionVariant', async () => {
- const component = HTMLRender(
-
-
-
- )
- const button = component.getByLabelText('Select field type')
- fireEvent.click(button)
-
- // select first item by role, that would close the menu
- fireEvent.click(component.getAllByRole('menuitemradio')[0])
- expect(component.queryByRole('menu')).not.toBeInTheDocument()
-
- // open menu again and check if the first option is checked
- fireEvent.click(button)
- expect(component.getAllByRole('menuitemradio')[0]).toHaveAttribute('aria-checked', 'true')
- cleanup()
- })
-
- it('should assign the right roles with groups & mixed selectionVariant', async () => {
- const component = HTMLRender(
-
-
-
- )
- const button = component.getByLabelText('Select field type to group by')
- fireEvent.click(button)
-
- expect(component.getByLabelText('Status')).toHaveAttribute('role', 'menuitemradio')
- expect(component.getByLabelText('Clear Group by')).toHaveAttribute('role', 'menuitem')
-
- cleanup()
- })
-
- it('should have no axe violations', async () => {
- const {container} = HTMLRender( )
- const results = await axe(container)
- expect(results).toHaveNoViolations()
- cleanup()
- })
-
- checkStoriesForAxeViolations('ActionMenu2/fixtures')
- checkStoriesForAxeViolations('ActionMenu2/examples')
-})
diff --git a/src/__tests__/ConfirmationDialog.test.tsx b/src/__tests__/ConfirmationDialog.test.tsx
index ef91e86786b..7e7cff17bb6 100644
--- a/src/__tests__/ConfirmationDialog.test.tsx
+++ b/src/__tests__/ConfirmationDialog.test.tsx
@@ -3,7 +3,7 @@ import {render as HTMLRender, cleanup, act, fireEvent} from '@testing-library/re
import {axe, toHaveNoViolations} from 'jest-axe'
import React, {useCallback, useRef, useState} from 'react'
-import {ActionMenu} from '../ActionMenu'
+import {ActionMenu} from '../deprecated/ActionMenu'
import BaseStyles from '../BaseStyles'
import Box from '../Box'
import Button from '../Button/Button'
diff --git a/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap b/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap
index 7581ef44ec9..ccf46de1387 100644
--- a/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap
+++ b/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap
@@ -2,79 +2,143 @@
exports[`ActionMenu renders consistently 1`] = `
.c0 {
- position: relative;
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+ line-height: 1.5;
+ color: #24292f;
+}
+
+.c2 {
display: inline-block;
- padding: 6px 16px;
+ margin-left: 8px;
+}
+
+.c1 {
+ border-radius: 6px;
+ border: 1px solid;
+ border-color: rgba(27,31,36,0.15);
font-family: inherit;
font-weight: 600;
line-height: 20px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- border-radius: 6px;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
-webkit-text-decoration: none;
text-decoration: none;
text-align: center;
+ display: grid;
+ grid-template-areas: "leadingIcon text trailingIcon";
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-left: 16px;
+ padding-right: 16px;
font-size: 14px;
color: #24292f;
background-color: #f6f8fa;
- border: 1px solid rgba(27,31,36,0.15);
box-shadow: 0 1px 0 rgba(27,31,36,0.04),inset 0 1px 0 rgba(255,255,255,0.25);
}
-.c0:hover {
- -webkit-text-decoration: none;
- text-decoration: none;
-}
-
-.c0:focus {
+.c1:focus {
outline: none;
}
-.c0:disabled {
+.c1:disabled {
cursor: default;
+ color: #8c959f;
+ background-color: btn.disabledBg;
}
-.c0:disabled svg {
+.c1:disabled svg {
opacity: 0.6;
}
-.c0:hover {
+.c1 > :not(:last-child) {
+ margin-right: 8px;
+}
+
+.c1 [data-component="leadingIcon"] {
+ grid-area: leadingIcon;
+}
+
+.c1 [data-component="text"] {
+ grid-area: text;
+}
+
+.c1 [data-component="trailingIcon"] {
+ grid-area: trailingIcon;
+}
+
+.c1 [data-component="ButtonCounter"] {
+ font-size: 14px;
+}
+
+.c1:hover:not([disabled]) {
background-color: #f3f4f6;
- border-color: rgba(27,31,36,0.15);
}
-.c0:focus {
- border-color: rgba(27,31,36,0.15);
+.c1:focus:not([disabled]) {
box-shadow: 0 0 0 3px rgba(9,105,218,0.3);
}
-.c0:active {
+.c1:active:not([disabled]) {
background-color: hsla(220,14%,94%,1);
box-shadow: inset 0 0.15em 0.3em rgba(27,31,36,0.15);
}
-.c0:disabled {
- color: #8c959f;
- background-color: #f6f8fa;
- border-color: rgba(27,31,36,0.15);
-}
-
-
+ color="fg.default"
+ data-portal-root={true}
+ fontFamily="normal"
+>
+
+
+ Toggle Menu
+
+
+ ",
+ }
+ }
+ fill="currentColor"
+ height={16}
+ role="img"
+ style={
+ Object {
+ "display": "inline-block",
+ "overflow": "visible",
+ "userSelect": "none",
+ "verticalAlign": "text-bottom",
+ }
+ }
+ viewBox="0 0 16 16"
+ width={16}
+ />
+
+
+
`;
diff --git a/src/__tests__/__snapshots__/ActionMenu2.test.tsx.snap b/src/__tests__/__snapshots__/ActionMenu2.test.tsx.snap
deleted file mode 100644
index ccf46de1387..00000000000
--- a/src/__tests__/__snapshots__/ActionMenu2.test.tsx.snap
+++ /dev/null
@@ -1,144 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ActionMenu renders consistently 1`] = `
-.c0 {
- font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
- line-height: 1.5;
- color: #24292f;
-}
-
-.c2 {
- display: inline-block;
- margin-left: 8px;
-}
-
-.c1 {
- border-radius: 6px;
- border: 1px solid;
- border-color: rgba(27,31,36,0.15);
- font-family: inherit;
- font-weight: 600;
- line-height: 20px;
- white-space: nowrap;
- vertical-align: middle;
- cursor: pointer;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- -webkit-text-decoration: none;
- text-decoration: none;
- text-align: center;
- display: grid;
- grid-template-areas: "leadingIcon text trailingIcon";
- padding-top: 5px;
- padding-bottom: 5px;
- padding-left: 16px;
- padding-right: 16px;
- font-size: 14px;
- color: #24292f;
- background-color: #f6f8fa;
- box-shadow: 0 1px 0 rgba(27,31,36,0.04),inset 0 1px 0 rgba(255,255,255,0.25);
-}
-
-.c1:focus {
- outline: none;
-}
-
-.c1:disabled {
- cursor: default;
- color: #8c959f;
- background-color: btn.disabledBg;
-}
-
-.c1:disabled svg {
- opacity: 0.6;
-}
-
-.c1 > :not(:last-child) {
- margin-right: 8px;
-}
-
-.c1 [data-component="leadingIcon"] {
- grid-area: leadingIcon;
-}
-
-.c1 [data-component="text"] {
- grid-area: text;
-}
-
-.c1 [data-component="trailingIcon"] {
- grid-area: trailingIcon;
-}
-
-.c1 [data-component="ButtonCounter"] {
- font-size: 14px;
-}
-
-.c1:hover:not([disabled]) {
- background-color: #f3f4f6;
-}
-
-.c1:focus:not([disabled]) {
- box-shadow: 0 0 0 3px rgba(9,105,218,0.3);
-}
-
-.c1:active:not([disabled]) {
- background-color: hsla(220,14%,94%,1);
- box-shadow: inset 0 0.15em 0.3em rgba(27,31,36,0.15);
-}
-
-
-
-
- Toggle Menu
-
-
- ",
- }
- }
- fill="currentColor"
- height={16}
- role="img"
- style={
- Object {
- "display": "inline-block",
- "overflow": "visible",
- "userSelect": "none",
- "verticalAlign": "text-bottom",
- }
- }
- viewBox="0 0 16 16"
- width={16}
- />
-
-
-
-`;
diff --git a/src/__tests__/deprecated/ActionMenu.test.tsx b/src/__tests__/deprecated/ActionMenu.test.tsx
new file mode 100644
index 00000000000..e535bce9c84
--- /dev/null
+++ b/src/__tests__/deprecated/ActionMenu.test.tsx
@@ -0,0 +1,136 @@
+import {cleanup, render as HTMLRender, act, fireEvent} from '@testing-library/react'
+import 'babel-polyfill'
+import {axe, toHaveNoViolations} from 'jest-axe'
+import React from 'react'
+import theme from '../../theme'
+import {ActionMenu} from '../../deprecated'
+import {behavesAsComponent, checkExports} from '../../utils/testing'
+import {BaseStyles, SSRProvider, ThemeProvider} from '../..'
+import {ItemProps} from '../../deprecated/ActionList/Item'
+expect.extend(toHaveNoViolations)
+
+const items = [
+ {text: 'New file'},
+ {text: 'Copy link'},
+ {text: 'Edit file'},
+ {text: 'Delete file', variant: 'danger'}
+] as ItemProps[]
+
+const mockOnActivate = jest.fn()
+
+function SimpleActionMenu(): JSX.Element {
+ return (
+
+
+
+ X
+
+
+
+
+
+ )
+}
+
+describe('ActionMenu', () => {
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+
+ behavesAsComponent({
+ Component: ActionMenu,
+ options: {skipAs: true, skipSx: true},
+ toRender: () => (
+
+
+
+ )
+ })
+
+ checkExports('deprecated/ActionMenu', {
+ default: undefined,
+ ActionMenu
+ })
+
+ it('should have no axe violations', async () => {
+ const {container} = HTMLRender( )
+ const results = await axe(container)
+ expect(results).toHaveNoViolations()
+ cleanup()
+ })
+
+ it('should trigger the overlay on trigger click', async () => {
+ const menu = HTMLRender( )
+ let portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
+ expect(portalRoot).toBeNull()
+ const anchor = await menu.findByText('Menu')
+ act(() => {
+ fireEvent.click(anchor)
+ })
+ portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
+ expect(portalRoot).toBeTruthy()
+ const itemText = items
+ .map((i: ItemProps) => {
+ if (i.hasOwnProperty('text')) {
+ return i.text
+ }
+ })
+ .join('')
+ expect(portalRoot?.textContent?.trim()).toEqual(itemText)
+ })
+
+ it('should dismiss the overlay on menuitem click', async () => {
+ const menu = HTMLRender( )
+ let portalRoot = await menu.baseElement.querySelector('#__primerPortalRoot__')
+ expect(portalRoot).toBeNull()
+ const anchor = await menu.findByText('Menu')
+ act(() => {
+ fireEvent.click(anchor)
+ })
+ portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
+ expect(portalRoot).toBeTruthy()
+ const menuItem = await menu.queryByText(items[0].text!)
+ act(() => {
+ fireEvent.click(menuItem as Element)
+ })
+ expect(portalRoot?.textContent).toEqual('') // menu items are hidden
+ })
+
+ it('should dismiss the overlay on clicking outside overlay', async () => {
+ const menu = HTMLRender( )
+ let portalRoot = await menu.baseElement.querySelector('#__primerPortalRoot__')
+ expect(portalRoot).toBeNull()
+ const anchor = await menu.findByText('Menu')
+ act(() => {
+ fireEvent.click(anchor)
+ })
+ portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
+ expect(portalRoot).toBeTruthy()
+ const somethingElse = (await menu.baseElement.querySelector('#something-else')) as HTMLElement
+ act(() => {
+ fireEvent.mouseDown(somethingElse)
+ })
+ expect(portalRoot?.textContent).toEqual('') // menu items are hidden
+ })
+
+ it('should pass correct values to onAction on menu click', async () => {
+ const menu = HTMLRender( )
+ let portalRoot = await menu.baseElement.querySelector('#__primerPortalRoot__')
+ expect(portalRoot).toBeNull()
+ const anchor = await menu.findByText('Menu')
+ act(() => {
+ fireEvent.click(anchor)
+ })
+ portalRoot = menu.baseElement.querySelector('#__primerPortalRoot__')
+ expect(portalRoot).toBeTruthy()
+ const menuItem = (await portalRoot?.querySelector("[role='menuitem']")) as HTMLElement
+ act(() => {
+ fireEvent.click(menuItem)
+ })
+
+ // onAction has been called with correct argument
+ expect(mockOnActivate).toHaveBeenCalledTimes(1)
+ const arg = mockOnActivate.mock.calls[0][0]
+ expect(arg.text).toEqual(items[0].text)
+ })
+})
diff --git a/src/__tests__/deprecated/__snapshots__/ActionMenu.test.tsx.snap b/src/__tests__/deprecated/__snapshots__/ActionMenu.test.tsx.snap
new file mode 100644
index 00000000000..7581ef44ec9
--- /dev/null
+++ b/src/__tests__/deprecated/__snapshots__/ActionMenu.test.tsx.snap
@@ -0,0 +1,80 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ActionMenu renders consistently 1`] = `
+.c0 {
+ position: relative;
+ display: inline-block;
+ padding: 6px 16px;
+ font-family: inherit;
+ font-weight: 600;
+ line-height: 20px;
+ white-space: nowrap;
+ vertical-align: middle;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ border-radius: 6px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ text-align: center;
+ font-size: 14px;
+ color: #24292f;
+ background-color: #f6f8fa;
+ border: 1px solid rgba(27,31,36,0.15);
+ box-shadow: 0 1px 0 rgba(27,31,36,0.04),inset 0 1px 0 rgba(255,255,255,0.25);
+}
+
+.c0:hover {
+ -webkit-text-decoration: none;
+ text-decoration: none;
+}
+
+.c0:focus {
+ outline: none;
+}
+
+.c0:disabled {
+ cursor: default;
+}
+
+.c0:disabled svg {
+ opacity: 0.6;
+}
+
+.c0:hover {
+ background-color: #f3f4f6;
+ border-color: rgba(27,31,36,0.15);
+}
+
+.c0:focus {
+ border-color: rgba(27,31,36,0.15);
+ box-shadow: 0 0 0 3px rgba(9,105,218,0.3);
+}
+
+.c0:active {
+ background-color: hsla(220,14%,94%,1);
+ box-shadow: inset 0 0.15em 0.3em rgba(27,31,36,0.15);
+}
+
+.c0:disabled {
+ color: #8c959f;
+ background-color: #f6f8fa;
+ border-color: rgba(27,31,36,0.15);
+}
+
+
+`;
diff --git a/src/deprecated/ActionMenu.tsx b/src/deprecated/ActionMenu.tsx
new file mode 100644
index 00000000000..c2a34dcff42
--- /dev/null
+++ b/src/deprecated/ActionMenu.tsx
@@ -0,0 +1,109 @@
+import {GroupedListProps, List, ListPropsBase} from './ActionList/List'
+import {Item, ItemProps} from './ActionList/Item'
+import {Divider} from './ActionList/Divider'
+import Button, {ButtonProps} from '../Button'
+import React, {useCallback, useMemo} from 'react'
+import {AnchoredOverlay} from '../AnchoredOverlay'
+import {useProvidedStateOrCreate} from '../hooks/useProvidedStateOrCreate'
+import {OverlayProps} from '../Overlay'
+import {useProvidedRefOrCreate} from '../hooks'
+import {AnchoredOverlayWrapperAnchorProps} from '../AnchoredOverlay/AnchoredOverlay'
+
+interface ActionMenuBaseProps extends Partial>, ListPropsBase {
+ /**
+ * Content that is passed into the renderAnchor component, which is a button by default.
+ */
+ anchorContent?: React.ReactNode
+
+ /**
+ * A callback that triggers both on clicks and keyboard events. This callback will be overridden by item level `onAction` callbacks.
+ */
+ onAction?: (props: ItemProps, event?: React.MouseEvent | React.KeyboardEvent) => void
+
+ /**
+ * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `setOpen`.
+ */
+ open?: boolean
+
+ /**
+ * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`.
+ */
+ setOpen?: (s: boolean) => void
+
+ /**
+ * Props to be spread on the internal `Overlay` component.
+ */
+ overlayProps?: Partial
+}
+
+export type ActionMenuProps = ActionMenuBaseProps & AnchoredOverlayWrapperAnchorProps
+
+const ActionMenuItem = (props: ItemProps) =>
+
+ActionMenuItem.displayName = 'ActionMenu.Item'
+
+const ActionMenuBase = ({
+ anchorContent,
+ renderAnchor = (props: T) => ,
+ anchorRef: externalAnchorRef,
+ onAction,
+ open,
+ setOpen,
+ overlayProps,
+ items,
+ ...listProps
+}: ActionMenuProps): JSX.Element => {
+ const [combinedOpenState, setCombinedOpenState] = useProvidedStateOrCreate(open, setOpen, false)
+ const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
+ const onOpen = useCallback(() => setCombinedOpenState(true), [setCombinedOpenState])
+ const onClose = useCallback(() => setCombinedOpenState(false), [setCombinedOpenState])
+
+ const renderMenuAnchor = useMemo(() => {
+ if (renderAnchor === null) {
+ return null
+ }
+ return >(props: T) => {
+ return renderAnchor({
+ 'aria-label': 'menu',
+ children: anchorContent,
+ ...props
+ })
+ }
+ }, [anchorContent, renderAnchor])
+
+ const itemsToRender = useMemo(() => {
+ return items.map(item => {
+ return {
+ ...item,
+ role: 'menuitem',
+ onAction: (props, event) => {
+ const actionCallback = item.onAction ?? onAction
+ actionCallback?.(props as ItemProps, event)
+ if (!event.defaultPrevented) {
+ onClose()
+ }
+ }
+ } as ItemProps
+ })
+ }, [items, onAction, onClose])
+
+ return (
+
+
+
+ )
+}
+
+ActionMenuBase.displayName = 'ActionMenu'
+
+/**
+ * @deprecated Use ActionMenu with composable API instead. See https://primer.style/react/ActionMenu for more details.
+ */
+export const ActionMenu = Object.assign(ActionMenuBase, {Divider, Item: ActionMenuItem})
diff --git a/src/deprecated/index.ts b/src/deprecated/index.ts
index 7a74cf6deca..b5641b8e5c1 100644
--- a/src/deprecated/index.ts
+++ b/src/deprecated/index.ts
@@ -39,3 +39,5 @@ export type {
} from '../SelectMenu'
export {ActionList} from './ActionList'
export type {ActionListProps} from './ActionList'
+export {ActionMenu} from './ActionMenu'
+export type {ActionMenuProps} from './ActionMenu'
diff --git a/src/drafts/index.ts b/src/drafts/index.ts
index 1c27dea498a..69143a5a08b 100644
--- a/src/drafts/index.ts
+++ b/src/drafts/index.ts
@@ -7,6 +7,5 @@
// Components
export * from '../Button2'
-export * from '../ActionMenu2'
export * from '../DropdownMenu2'
export * from '../Label2'
diff --git a/src/index.ts b/src/index.ts
index 8f3edfff6ee..c1f9aca96f8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -39,7 +39,7 @@ export type {
ActionListTrailingVisualProps
} from './ActionList'
export {ActionMenu} from './ActionMenu'
-export type {ActionMenuProps} from './ActionMenu'
+export type {ActionMenuProps, ActionMenuAnchorProps, ActionMenuButtonProps} from './ActionMenu'
export {default as Autocomplete} from './Autocomplete'
export type {AutocompleteMenuProps, AutocompleteInputProps, AutocompleteOverlayProps} from './Autocomplete'
export {default as Avatar} from './Avatar'
diff --git a/src/stories/ActionMenu2/examples.stories.tsx b/src/stories/ActionMenu/examples.stories.tsx
similarity index 98%
rename from src/stories/ActionMenu2/examples.stories.tsx
rename to src/stories/ActionMenu/examples.stories.tsx
index 3f363b4a2bc..9bce65fcc8b 100644
--- a/src/stories/ActionMenu2/examples.stories.tsx
+++ b/src/stories/ActionMenu/examples.stories.tsx
@@ -1,7 +1,6 @@
import React from 'react'
import {Meta} from '@storybook/react'
-import {ThemeProvider, BaseStyles, Box, Text, Avatar, ActionList} from '../..'
-import {ActionMenu} from '../../drafts'
+import {ThemeProvider, BaseStyles, Box, Text, Avatar, ActionMenu, ActionList} from '../..'
import {
GearIcon,
MilestoneIcon,
@@ -17,7 +16,7 @@ import {
} from '@primer/octicons-react'
const meta: Meta = {
- title: 'Composite components/ActionMenu2/examples',
+ title: 'Composite components/ActionMenu/examples',
component: ActionMenu,
decorators: [
(Story: React.ComponentType): JSX.Element => (
diff --git a/src/stories/ActionMenu2/fixtures.stories.tsx b/src/stories/ActionMenu/fixtures.stories.tsx
similarity index 99%
rename from src/stories/ActionMenu2/fixtures.stories.tsx
rename to src/stories/ActionMenu/fixtures.stories.tsx
index d59b9efdd3f..ebf881eea59 100644
--- a/src/stories/ActionMenu2/fixtures.stories.tsx
+++ b/src/stories/ActionMenu/fixtures.stories.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import {Meta} from '@storybook/react'
-import {ThemeProvider, BaseStyles, Box, Text, TextInput, StyledOcticon, FormGroup, ActionList} from '../..'
-import {ActionMenu, Button, IconButton} from '../../drafts'
+import {ThemeProvider, BaseStyles, Box, Text, TextInput, StyledOcticon, FormGroup, ActionMenu, ActionList} from '../..'
+import {Button, IconButton} from '../../drafts'
import {
ServerIcon,
PlusCircleIcon,
@@ -25,7 +25,7 @@ import {
} from '@primer/octicons-react'
const meta: Meta = {
- title: 'Composite components/ActionMenu2/fixtures',
+ title: 'Composite components/ActionMenu/fixtures',
component: ActionMenu,
decorators: [
(Story: React.ComponentType): JSX.Element => (
diff --git a/src/stories/ConfirmationDialog.stories.tsx b/src/stories/ConfirmationDialog.stories.tsx
index 69e9619f4e9..8e91c8a9f39 100644
--- a/src/stories/ConfirmationDialog.stories.tsx
+++ b/src/stories/ConfirmationDialog.stories.tsx
@@ -3,7 +3,7 @@ import {Meta} from '@storybook/react'
import {BaseStyles, Button, Box, ThemeProvider, useTheme} from '..'
import {ConfirmationDialog, useConfirm} from '../Dialog/ConfirmationDialog'
-import {ActionMenu} from '../ActionMenu'
+import {ActionMenu} from '../deprecated/ActionMenu'
export default {
title: 'Internal components/ConfirmationDialog',
diff --git a/src/stories/Overlay.stories.tsx b/src/stories/Overlay.stories.tsx
index 5b4d69802fe..d5235581ff6 100644
--- a/src/stories/Overlay.stories.tsx
+++ b/src/stories/Overlay.stories.tsx
@@ -19,11 +19,11 @@ import {
TextInput,
Link,
Label,
- ActionList
+ ActionList,
+ ActionMenu
} from '..'
import type {AnchorSide} from '@primer/behaviors'
import {DropdownMenu, DropdownButton} from '../DropdownMenu'
-import {ActionMenu} from '../drafts'
import {ItemInput} from '../deprecated/ActionList/List'
export default {
diff --git a/src/stories/ActionMenu.stories.tsx b/src/stories/deprecated/ActionMenu.stories.tsx
similarity index 96%
rename from src/stories/ActionMenu.stories.tsx
rename to src/stories/deprecated/ActionMenu.stories.tsx
index 7f421ce2a46..05ec0a193bb 100644
--- a/src/stories/ActionMenu.stories.tsx
+++ b/src/stories/deprecated/ActionMenu.stories.tsx
@@ -13,16 +13,16 @@ import {
import {Meta} from '@storybook/react'
import React, {useCallback, useState, useRef} from 'react'
import styled from 'styled-components'
-import {ThemeProvider} from '..'
-import {ActionMenu, ActionMenuProps} from '../ActionMenu'
-import Link, {LinkProps} from '../Link'
-import Button from '../Button'
-import {ActionList, ItemProps} from '../deprecated/ActionList'
-import BaseStyles from '../BaseStyles'
-import {DropdownButton} from '../DropdownMenu'
+import {ThemeProvider} from '../..'
+import Link, {LinkProps} from '../../Link'
+import Button from '../../Button'
+import {ActionMenu, ActionMenuProps, ActionList} from '../../deprecated'
+import {ItemProps} from '../../deprecated/ActionList'
+import BaseStyles from '../../BaseStyles'
+import {DropdownButton} from '../../DropdownMenu'
const meta: Meta = {
- title: 'Composite components/ActionMenu',
+ title: 'Deprecated components/ActionMenu',
component: ActionMenu,
decorators: [
(Story: React.ComponentType): JSX.Element => (