diff --git a/docs/migration.md b/docs/migration.md index 17a24065004..9f41ef2d6d4 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -119,6 +119,9 @@ consider additional positioning prop support on a case-by-case basis. - The `v8` version of `@zendeskgarden/react-dropdowns` is no longer maintained and is renamed to `@zendeskgarden/react-dropdowns.legacy` in `v9` - `Menu`: value `auto` is no longer valid for the `fallbackPlacements` prop. +- `Menu`: new `restoreFocus` prop (default: `true`) returns focus to trigger + after menu interaction. To keep the dropdown open on selection, + set `restoreFocus={false}` and manage focus manually. - Removed `label` prop from `OptGroup`. Use `legend` instead. #### @zendeskgarden/react-forms diff --git a/package-lock.json b/package-lock.json index 80445030028..bd85a2b254a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12637,14 +12637,14 @@ } }, "node_modules/@zendeskgarden/container-menu": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@zendeskgarden/container-menu/-/container-menu-0.4.2.tgz", - "integrity": "sha512-ilxh/5I3FM6NWveJx7R0fTohbYaZJc2am33FWUxDnf5rvv426pjPqQO29pW732EDzsb+b0/OdBT2c5S7efoLyw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@zendeskgarden/container-menu/-/container-menu-0.5.0.tgz", + "integrity": "sha512-X2iQPHqls+8O3d8Ohm3t6oNnh+lQ6KRhpySYwvfaVEdsnxQtchHBImnrAJt33GVGMz1lBQu0dj5DxT4AALVHUQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.8.4", - "@zendeskgarden/container-selection": "^3.1.1", - "@zendeskgarden/container-utilities": "^2.0.1" + "@zendeskgarden/container-selection": "^3.1.2", + "@zendeskgarden/container-utilities": "^2.0.2" }, "peerDependencies": { "prop-types": "^15.6.1", @@ -12653,13 +12653,13 @@ } }, "node_modules/@zendeskgarden/container-menu/node_modules/@zendeskgarden/container-selection": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@zendeskgarden/container-selection/-/container-selection-3.1.1.tgz", - "integrity": "sha512-ubdPSdWKWKxE9mErWIIj3M7Iw6Yfp7MDnH9JAAlORWrKROXRejWMeYMcZjgeShOp8qAtCVVz3CrdBYs/pNYjtw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@zendeskgarden/container-selection/-/container-selection-3.1.2.tgz", + "integrity": "sha512-UXJfa0ohCEtelUW0sFoI4eREwibnpymOpXLAFax+u5DwcWAVWb1IzIK06XD90EmDRO/yauKQU5md1bzfSSFHIg==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.8.4", - "@zendeskgarden/container-utilities": "^2.0.1" + "@zendeskgarden/container-utilities": "^2.0.2" }, "peerDependencies": { "prop-types": "^15.6.1", @@ -12802,9 +12802,9 @@ } }, "node_modules/@zendeskgarden/container-utilities": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@zendeskgarden/container-utilities/-/container-utilities-2.0.1.tgz", - "integrity": "sha512-1GOF0weUTRCcc+4jYJY6ET5QgQVSCVYzEz4UJJidSuZmamgxlLWgI49Ll5JPzG0nQz6DKpmDY4pXnmYk2UzgEA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@zendeskgarden/container-utilities/-/container-utilities-2.0.2.tgz", + "integrity": "sha512-IvPxhHwR8AaaZuS7yZM4Ea0GLgrfTblbNd+4fWbg8a4zioExdMU8uy036cjL97A0Ega7v++PAqFLR3Y9dWCNRQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.8.4", @@ -53328,7 +53328,7 @@ "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@zendeskgarden/container-combobox": "^2.0.0", - "@zendeskgarden/container-menu": "^0.4.0", + "@zendeskgarden/container-menu": "0.5.0", "@zendeskgarden/container-utilities": "^2.0.0", "@zendeskgarden/react-buttons": "^9.0.0-next.26", "@zendeskgarden/react-forms": "^9.0.0-next.26", diff --git a/packages/dropdowns/demo/stories/MenuStory.tsx b/packages/dropdowns/demo/stories/MenuStory.tsx index 84309418c21..1f64c7c5a74 100644 --- a/packages/dropdowns/demo/stories/MenuStory.tsx +++ b/packages/dropdowns/demo/stories/MenuStory.tsx @@ -12,6 +12,7 @@ import CartIcon from '@zendeskgarden/svg-icons/src/16/shopping-cart-stroke.svg'; import { Grid } from '@zendeskgarden/react-grid'; import { IMenuProps, Item, ItemGroup, Separator, Menu } from '@zendeskgarden/react-dropdowns'; import { IItem, Items } from './types'; +import { Modal } from '@zendeskgarden/react-modals'; const MenuItem = ({ icon, meta, ...item }: IItem) => { return ( @@ -27,12 +28,19 @@ interface IArgs extends IMenuProps { } export const MenuStory: StoryFn = ({ items, ...args }) => { + const [openModal, setOpenModal] = React.useState(false); + return (
- + { + changes.value && setOpenModal(true); + }} + > {items.map(item => { if ('items' in item) { return ( @@ -59,6 +67,13 @@ export const MenuStory: StoryFn = ({ items, ...args }) => {
+ {openModal && ( + setOpenModal(false)}> + +

Modal content

+
+
+ )}
); diff --git a/packages/dropdowns/package.json b/packages/dropdowns/package.json index a7c17537ced..d5a0695a688 100644 --- a/packages/dropdowns/package.json +++ b/packages/dropdowns/package.json @@ -23,7 +23,7 @@ "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@zendeskgarden/container-combobox": "^2.0.0", - "@zendeskgarden/container-menu": "^0.4.0", + "@zendeskgarden/container-menu": "0.5.0", "@zendeskgarden/container-utilities": "^2.0.0", "@zendeskgarden/react-buttons": "^9.0.0-next.26", "@zendeskgarden/react-forms": "^9.0.0-next.26", diff --git a/packages/dropdowns/src/elements/menu/Menu.spec.tsx b/packages/dropdowns/src/elements/menu/Menu.spec.tsx index d4f340a2405..9166c4470ba 100644 --- a/packages/dropdowns/src/elements/menu/Menu.spec.tsx +++ b/packages/dropdowns/src/elements/menu/Menu.spec.tsx @@ -132,9 +132,9 @@ describe('Menu', () => { expect(item).toBeVisible(); }); - it('applies `defaultFocusedValue`', async () => { - const { getByTestId } = render( - + it('applies `defaultFocusedValue` after a keyboard event opens the menu', async () => { + const { getByTestId, getByRole } = render( + Flower Smooth @@ -148,8 +148,22 @@ describe('Menu', () => { ); await floating(); + const trigger = getByRole('button'); + + // open menu with onClick + await user.click(trigger); const item = getByTestId('item-02'); + // focus remains on trigger with mouseEvents + expect(trigger).toHaveFocus(); + // close menu + await user.click(trigger); + expect(item).not.toBeVisible(); + + // open menu with keyboard + trigger.focus(); + await user.keyboard(' '); + // focus is on the focused item associated with `defaultFocusedValue` expect(item).toHaveFocus(); }); @@ -603,7 +617,7 @@ describe('Menu', () => { }); it('calls onChange as expected', async () => { - const { getByTestId } = render( + const { getByRole, getByTestId } = render( @@ -611,6 +625,7 @@ describe('Menu', () => { ); await floating(); + const trigger = getByRole('button'); const item1 = getByTestId('flower'); await act(async () => { @@ -620,6 +635,7 @@ describe('Menu', () => { const changeTypes = handleChange.mock.calls.map(([change]) => change.type); expect(changeTypes).toMatchObject(['menuItem:mouseMove', 'menuItem:click']); + expect(trigger).toHaveFocus(); }); it('handles `focusedValue` and `isExpanded` as expected', async () => {