diff --git a/src/components/Menu/Menu.stories.args.ts b/src/components/Menu/Menu.stories.args.ts index ec320e735..ddf6e370a 100644 --- a/src/components/Menu/Menu.stories.args.ts +++ b/src/components/Menu/Menu.stories.args.ts @@ -34,7 +34,7 @@ export default { table: { category: 'React Aria - Menu', type: { - summary: '(key: Key) => void', + summary: "(keys: 'all' | Set) => void", }, defaultValue: { summary: 'undefined', @@ -127,9 +127,8 @@ export default { }, }, }, - 'ariaLabelledby': { - description: - 'The aria-labelledby for the menu wrapper', + ariaLabelledby: { + description: 'The aria-labelledby for the menu wrapper', control: { type: 'text' }, table: { type: { @@ -139,14 +138,15 @@ export default { }, }, selectionMode: { - description: 'Selection mode single, multiple or none', + description: 'Default selection mode for the whole menu', + default: undefined, control: { type: 'select' }, options: ['single', 'multiple', 'none'], table: { type: { summary: "'single' | 'multiple' | 'none'", }, + defaultValue: 'multiple', }, }, }; - diff --git a/src/components/Menu/Menu.stories.tsx b/src/components/Menu/Menu.stories.tsx index 00b15e7e4..bf620a3bc 100644 --- a/src/components/Menu/Menu.stories.tsx +++ b/src/components/Menu/Menu.stories.tsx @@ -5,7 +5,7 @@ import { DocumentationPage } from '../../storybook/helper.stories.docs'; import StyleDocs from '../../storybook/docs.stories.style.mdx'; import { Item, Section } from '@react-stately/collections'; -import Menu, { MenuProps } from './'; +import Menu, { MenuProps, SelectionGroup } from './'; import argTypes from './Menu.stories.args'; import Documentation from './Menu.stories.docs.mdx'; import { action } from '@storybook/addon-actions'; @@ -13,7 +13,6 @@ import Flex from '../Flex'; import Avatar from '../Avatar'; import { PresenceType } from '../Avatar/Avatar.types'; import { ListHeader, ListItemBaseSection, Icon } from '..'; -import { SelectionGroup } from './Menu.utils'; export default { title: 'Momentum UI/Menu', @@ -26,6 +25,14 @@ export default { }, }; +const menuOnSelectionChange = (...rest) => { + console.log('menuOnSelectionChange', rest); +}; + +const menuOnAction = (...rest) => { + console.log('menuOnAction', rest); +}; + const Example = Template>(Menu).bind({}); Example.argTypes = { ...argTypes }; @@ -73,8 +80,8 @@ Sections.parameters = { { itemSize: 32, isTickOnLeftSide: true, - label: 'Where would you like to live?', - onSelectionChange: action('onSelectionChange'), + onSelectionChange: menuOnSelectionChange, + onAction: menuOnAction, children: [
Japan
,
- America - - } - > - USA - Mexico - Canada -
, + key="2" + title={ + + America + + } + > + USA + Mexico + Canada + , ], }, ], }; -const SelectionGroupExample = MultiTemplate>(Menu).bind({}); +const SelectionGroups = MultiTemplate>(Menu).bind({}); -SelectionGroupExample.argTypes = { ...argTypes }; +SelectionGroups.argTypes = { ...argTypes }; delete Sections.argTypes.children; delete Sections.argTypes.isTickOnLeftSide; delete Sections.argTypes.itemSize; -SelectionGroupExample.args = { +SelectionGroups.args = { 'aria-label': 'Menu with multiple selection modes component', onSelectionChange: action('onSelectionChange'), }; -SelectionGroupExample.parameters = { +SelectionGroups.parameters = { variants: [ { - selectionMode: 'none', + selectionMode: 'multiple', // this is the default for all the group itemSize: 32, isTickOnLeftSide: true, + onSelectionChange: menuOnSelectionChange, + onAction: menuOnAction, children: [ { - console.log('multipleselection', rest); + console.log('singleselection1', rest); + }} + onAction={(...rest) => { + console.log('selectionOnAction1', rest); }} title={ - Speaker + + Speaker (you can choose many) + } > - Use system setting (internal speakers) - Internal speaker - Bose Headset 100 + System default speaker + Default - External Headphones (Built-in) + Desk Pro Web Camera + MacBook Pro Speakers + Webex Media Audio Device , { - console.log('singleselection', rest); + console.log('singleselection2', rest); + }} + onAction={(...rest) => { + console.log('selectionOnAction2', rest); }} title={ <> @@ -169,29 +188,43 @@ SelectionGroupExample.parameters = { - Microphone + + Microphone (you can choose one) + } > - Use system setting (internal microphone) - Bose Headset 100 + No Microphone + Default - External Microhpone (Built-in) + Desk Pro Web Microphone + MacBook Pro Microphone + Webex Media Audio Device , { - console.log('singleselection', rest); + console.log('singleselection3', rest); + }} + onAction={(...rest) => { + console.log('selectionOnAction3', rest); }} title={ <> - + + + + Webex smart audio (You can choose one) - Devices } @@ -211,6 +244,8 @@ const Common = MultiTemplate>(Menu).bind({}); Common.argTypes = { ...argTypes }; delete Common.argTypes.children; +delete Common.argTypes.selectionMode; +delete Common.argTypes.itemShape; Common.args = { 'aria-label': 'Menu component', @@ -250,7 +285,6 @@ Common.parameters = { , ], }, - { selectionMode: 'single', itemShape: 'isPilled', @@ -280,4 +314,4 @@ Common.parameters = { delete Common.argTypes.onAction; delete Common.argTypes.disabledKeys; -export { Example, ActionMenu, Sections, SelectionGroupExample, Common }; +export { Example, ActionMenu, Sections, SelectionGroups, Common }; diff --git a/src/components/Menu/Menu.types.ts b/src/components/Menu/Menu.types.ts index 45a3d88eb..ab18ea026 100644 --- a/src/components/Menu/Menu.types.ts +++ b/src/components/Menu/Menu.types.ts @@ -1,4 +1,4 @@ -import { CSSProperties, HTMLAttributes, MutableRefObject } from 'react'; +import { CSSProperties, HTMLAttributes, Key, MutableRefObject } from 'react'; import { AriaMenuProps } from '@react-types/menu'; import { CollectionBase, @@ -67,6 +67,6 @@ export interface SelectionGroupProps extends Omit, 'children' | 'items'>, Omit, 'disabledKeys'>, Omit { - onAction?: () => void; + onAction?: (key: Key) => void; selectionMode: Exclude; } diff --git a/src/components/Menu/Menu.unit.test.tsx b/src/components/Menu/Menu.unit.test.tsx index 2aa49d426..cb6cf4977 100644 --- a/src/components/Menu/Menu.unit.test.tsx +++ b/src/components/Menu/Menu.unit.test.tsx @@ -332,6 +332,80 @@ describe('', () => { expect(checkboxItems[1]).toHaveFocus(); }); + it('should handle click - for vertical menu with SelectionGroup', async () => { + const user = userEvent.setup(); + + const { getAllByRole } = render( + + + One + Two + + + Three + Four + + + ); + + const radioItems = getAllByRole('menuitemradio'); + const checkboxItems = getAllByRole('menuitemcheckbox'); + + await user.click(radioItems[1]); + + expect(radioItems[0]).not.toHaveFocus(); + expect(radioItems[0]).not.toBeChecked(); + expect(radioItems[1]).toHaveFocus(); + expect(radioItems[1]).toBeChecked(); + expect(checkboxItems[0]).not.toHaveFocus(); + expect(checkboxItems[0]).not.toBeChecked(); + expect(checkboxItems[1]).not.toHaveFocus(); + expect(checkboxItems[1]).not.toBeChecked(); + + await user.click(checkboxItems[0]); + + expect(radioItems[0]).not.toHaveFocus(); + expect(radioItems[0]).not.toBeChecked(); + expect(radioItems[1]).not.toHaveFocus(); + expect(radioItems[1]).toBeChecked(); + expect(checkboxItems[0]).toHaveFocus(); + expect(checkboxItems[0]).toBeChecked(); + expect(checkboxItems[1]).not.toHaveFocus(); + expect(checkboxItems[1]).not.toBeChecked(); + + await user.click(checkboxItems[1]); + + expect(radioItems[0]).not.toHaveFocus(); + expect(radioItems[0]).not.toBeChecked(); + expect(radioItems[1]).not.toHaveFocus(); + expect(radioItems[1]).toBeChecked(); + expect(checkboxItems[0]).not.toHaveFocus(); + expect(checkboxItems[0]).toBeChecked(); + expect(checkboxItems[1]).toHaveFocus(); + expect(checkboxItems[1]).toBeChecked(); + + await user.click(radioItems[0]); + + expect(radioItems[0]).toHaveFocus(); + expect(radioItems[0]).toBeChecked(); + expect(radioItems[1]).not.toHaveFocus(); + expect(radioItems[1]).not.toBeChecked(); + expect(checkboxItems[0]).not.toHaveFocus(); + expect(checkboxItems[0]).toBeChecked(); + expect(checkboxItems[1]).not.toHaveFocus(); + expect(checkboxItems[1]).toBeChecked(); + }); + it('should render MenuSelectionGroup if children has SelectionGroup', () => { const wrapper = mount( @@ -364,7 +438,7 @@ describe('', () => { title: 'SelectionGroup 1', 'aria-label': 'selection1', children: expect.any(Object), - selectionGroup: true + selectionGroup: true, }); expect(wrapper.find(MenuSelectionGroup).at(1).props()).toEqual({ item: expect.any(Object), @@ -374,7 +448,7 @@ describe('', () => { title: 'SelectionGroup 2', 'aria-label': 'selection2', children: expect.any(Object), - selectionGroup: true + selectionGroup: true, }); }); });