From c3df7221b185996713a05d06ef585020bfa67c6b Mon Sep 17 00:00:00 2001 From: Peter Pal Hudak Date: Fri, 8 Dec 2023 15:25:27 +0100 Subject: [PATCH] feat(ui-drilldown,ui-top-nav-bar): add shouldCloseOnClick Closes: INSTUI-3911 --- .../src/Drilldown/DrilldownOption/index.tsx | 3 +- .../src/Drilldown/DrilldownOption/props.ts | 13 ++- packages/ui-drilldown/src/Drilldown/props.ts | 2 +- .../ui-top-nav-bar/src/TopNavBar/README.md | 4 +- .../src/TopNavBar/TopNavBarItem/index.tsx | 3 +- .../src/TopNavBar/TopNavBarItem/props.ts | 12 ++- .../TopNavBarSmallViewportLayout.test.tsx | 93 +++++++++++++++++++ .../SmallViewportLayout/index.tsx | 6 +- .../TopNavBar/utils/mapItemsForDrilldown.tsx | 6 +- 9 files changed, 130 insertions(+), 12 deletions(-) diff --git a/packages/ui-drilldown/src/Drilldown/DrilldownOption/index.tsx b/packages/ui-drilldown/src/Drilldown/DrilldownOption/index.tsx index cd1f161820..98856e7cc6 100644 --- a/packages/ui-drilldown/src/Drilldown/DrilldownOption/index.tsx +++ b/packages/ui-drilldown/src/Drilldown/DrilldownOption/index.tsx @@ -52,7 +52,8 @@ class DrilldownOption extends Component { beforeLabelContentVAlign: 'start', afterLabelContentVAlign: 'start', as: 'li', - role: 'menuitem' + role: 'menuitem', + shouldCloseOnClick: 'auto' } render() { diff --git a/packages/ui-drilldown/src/Drilldown/DrilldownOption/props.ts b/packages/ui-drilldown/src/Drilldown/DrilldownOption/props.ts index a86fb25b2c..2501f3066d 100644 --- a/packages/ui-drilldown/src/Drilldown/DrilldownOption/props.ts +++ b/packages/ui-drilldown/src/Drilldown/DrilldownOption/props.ts @@ -44,6 +44,8 @@ type DrilldownOptionValue = string | number | undefined type RenderContentVAlign = 'start' | 'center' | 'end' +type ShouldCloseOnClick = 'auto' | 'always' | 'never' + type DrilldownOptionVariant = Exclude type RenderContentProps = { @@ -171,6 +173,11 @@ type DrilldownOptionOwnProps = { * Provides a reference to the underlying html root element */ elementRef?: (element: Element | null) => void + + /** + * Should close the container menu component, if clicked on the option marked with this prop + */ + shouldCloseOnClick?: ShouldCloseOnClick } type PropKeys = keyof DrilldownOptionOwnProps @@ -200,7 +207,8 @@ const propTypes: PropValidators = { descriptionRole: PropTypes.string, onOptionClick: PropTypes.func, defaultSelected: PropTypes.bool, - elementRef: PropTypes.func + elementRef: PropTypes.func, + shouldCloseOnClick: PropTypes.oneOf(['auto', 'always', 'never']) } const allowedProps: AllowedPropKeys = [ @@ -222,7 +230,8 @@ const allowedProps: AllowedPropKeys = [ 'descriptionRole', 'onOptionClick', 'defaultSelected', - 'elementRef' + 'elementRef', + 'shouldCloseOnClick' ] export type { DrilldownOptionProps, DrilldownOptionValue } diff --git a/packages/ui-drilldown/src/Drilldown/props.ts b/packages/ui-drilldown/src/Drilldown/props.ts index 02e931a6d1..2931127ca3 100644 --- a/packages/ui-drilldown/src/Drilldown/props.ts +++ b/packages/ui-drilldown/src/Drilldown/props.ts @@ -146,7 +146,7 @@ type DrilldownOwnProps = { defaultShow?: boolean /** - * Is the `` open (should be accompanied by `onToggle`) + * Is the `` open (should be accompanied by `onToggle` and `trigger`) */ show?: boolean // TODO: type controllable(PropTypes.bool, 'onToggle', 'defaultShow') diff --git a/packages/ui-top-nav-bar/src/TopNavBar/README.md b/packages/ui-top-nav-bar/src/TopNavBar/README.md index 81a6f0d4eb..31a052203f 100644 --- a/packages/ui-top-nav-bar/src/TopNavBar/README.md +++ b/packages/ui-top-nav-bar/src/TopNavBar/README.md @@ -848,7 +848,7 @@ class PlaygroundExample extends React.Component { Course page 2 Course page 3 Course page 4 - Course page 5 + Course page 5 )} @@ -1271,7 +1271,7 @@ type: example Course page 2 Course page 3 Course page 4 - Course page 5 + Course page 5 )} diff --git a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarItem/index.tsx b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarItem/index.tsx index 0a55dac5cf..f8d7962c03 100644 --- a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarItem/index.tsx +++ b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarItem/index.tsx @@ -90,7 +90,8 @@ class TopNavBarItem extends Component { static defaultProps = { status: 'default', variant: 'default', - showSubmenuChevron: true + showSubmenuChevron: true, + shouldCloseOnClick: 'auto' } as const declare context: React.ContextType diff --git a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarItem/props.ts b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarItem/props.ts index ac32bfcebd..a97124f9fe 100644 --- a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarItem/props.ts +++ b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarItem/props.ts @@ -49,6 +49,7 @@ import { TopNavBarItem } from './index' type ItemChild = React.ComponentElement type DrilldownSubmenu = React.ComponentElement +type ShouldCloseOnClick = 'auto' | 'always' | 'never' type TopNavBarItemTooltipType = | string @@ -241,6 +242,11 @@ type TopNavBarItemOwnProps = { * A function that returns a reference to the button/link HTML element */ itemRef?: (el: HTMLButtonElement | HTMLLinkElement | null) => void + + /** + * Should close the container menu component, if clicked on the option marked with this prop + */ + shouldCloseOnClick?: ShouldCloseOnClick } type PropKeys = keyof TopNavBarItemOwnProps @@ -300,7 +306,8 @@ const propTypes: PropValidators = { onKeyDown: PropTypes.func, onKeyUp: PropTypes.func, elementRef: PropTypes.func, - itemRef: PropTypes.func + itemRef: PropTypes.func, + shouldCloseOnClick: PropTypes.oneOf(['auto', 'always', 'never']) } const allowedProps: AllowedPropKeys = [ @@ -325,7 +332,8 @@ const allowedProps: AllowedPropKeys = [ 'onKeyDown', 'onKeyUp', 'elementRef', - 'itemRef' + 'itemRef', + 'shouldCloseOnClick' ] export type { diff --git a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/__new-tests__/TopNavBarSmallViewportLayout.test.tsx b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/__new-tests__/TopNavBarSmallViewportLayout.test.tsx index 3dc6f4ca25..4652f16989 100644 --- a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/__new-tests__/TopNavBarSmallViewportLayout.test.tsx +++ b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/__new-tests__/TopNavBarSmallViewportLayout.test.tsx @@ -44,6 +44,7 @@ import { SmallViewportModeWrapper } from '../../../utils/exampleHelpers' import { TopNavBarItem } from '../../../TopNavBarItem' +import { TopNavBarMenuItems } from '../../../TopNavBarMenuItems' import { TopNavBarUser } from '../../../TopNavBarUser' import { TopNavBarSmallViewportLayout } from '../index' @@ -1501,6 +1502,98 @@ describe('', () => { }) }) + describe('shouldCloseOnClick prop', () => { + it('should be closed an item with shouldCloseOnClick prop is selected', async () => { + const onDropdownMenuSelect = jest.fn() + const onDropdownMenuToggle = jest.fn() + + render( + + + `${hiddenChildrenCount} More` + } + > + + Always close + + + Never close + + + Auto behavior with href + + + Auto behavior without href + + + } + /> + + ) + + const menuTriggerButton = screen.getByRole('button') + + fireEvent.click(menuTriggerButton) + + // Opening menu + expect(onDropdownMenuToggle).toHaveBeenCalledTimes(1) + + const alwaysCloseOption = await screen.findByText('Always close') + + fireEvent.click(alwaysCloseOption) + + // Selecting Always close option, menu is closing + expect(onDropdownMenuSelect).toHaveBeenCalledTimes(1) + expect(onDropdownMenuToggle).toHaveBeenCalledTimes(2) + + // Opening menu again + fireEvent.click(menuTriggerButton) + + expect(onDropdownMenuToggle).toHaveBeenCalledTimes(3) + + const neverCloseOption = await screen.findByText('Never close') + + fireEvent.click(neverCloseOption) + + // Selecting Never close option, menu is not closing + expect(onDropdownMenuSelect).toHaveBeenCalledTimes(2) + expect(onDropdownMenuToggle).toHaveBeenCalledTimes(3) + + const autoCloseOptionWithHref = await screen.findByText( + 'Auto behavior with href' + ) + + fireEvent.click(autoCloseOptionWithHref) + + // Selecting Auto behavior with href option, menu is closing + expect(onDropdownMenuSelect).toHaveBeenCalledTimes(3) + expect(onDropdownMenuToggle).toHaveBeenCalledTimes(4) + + // Opening menu again + fireEvent.click(menuTriggerButton) + + expect(onDropdownMenuToggle).toHaveBeenCalledTimes(5) + + const autoCloseOptionWithoutHref = await screen.findByText( + 'Auto behavior without href' + ) + + fireEvent.click(autoCloseOptionWithoutHref) + + // Selecting Auto behavior without href option, menu is not closing + expect(onDropdownMenuSelect).toHaveBeenCalledTimes(4) + expect(onDropdownMenuToggle).toHaveBeenCalledTimes(5) + }) + }) + describe('should be accessible', () => { it('a11y', async () => { const { container } = render( diff --git a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/index.tsx b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/index.tsx index 521ef1a594..29a47666ec 100644 --- a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/index.tsx +++ b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/index.tsx @@ -458,7 +458,11 @@ class TopNavBarSmallViewportLayout extends Component< onDropdownMenuSelect(e, args) } - if (args.selectedOption.props.href) { + if ( + (args.selectedOption.props.shouldCloseOnClick === 'auto' && + !!args.selectedOption.props.href) || + args.selectedOption.props.shouldCloseOnClick === 'always' + ) { this.toggleDropdownMenu() } }} diff --git a/packages/ui-top-nav-bar/src/TopNavBar/utils/mapItemsForDrilldown.tsx b/packages/ui-top-nav-bar/src/TopNavBar/utils/mapItemsForDrilldown.tsx index d6c0220cdf..b3a8067f15 100644 --- a/packages/ui-top-nav-bar/src/TopNavBar/utils/mapItemsForDrilldown.tsx +++ b/packages/ui-top-nav-bar/src/TopNavBar/utils/mapItemsForDrilldown.tsx @@ -75,7 +75,8 @@ const mapItemsForDrilldown = ( status, variant, href, - onClick + onClick, + shouldCloseOnClick } = item.props let submenu: TopNavBarItemProps['renderSubmenu'] = renderSubmenu @@ -182,7 +183,8 @@ const mapItemsForDrilldown = ( }) : children, subPageId: optionSubPageId, - 'aria-current': ariaCurrent + 'aria-current': ariaCurrent, + shouldCloseOnClick: shouldCloseOnClick } }) })