From e18fee2024227cf23f4be148bd22d1d6911ed0c8 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Mon, 4 Nov 2024 21:38:41 +0100 Subject: [PATCH] fix issues with focus, refactor main logic and add inverse screenshots --- ...erse-with-large-menu-in-desktop-1-snap.png | Bin 0 -> 23591 bytes ...erse-with-small-menu-in-desktop-1-snap.png | Bin 0 -> 23794 bytes .../navigation-bar-screenshot-test.tsx | 12 + src/__tests__/main-navigation-bar-test.tsx | 47 ++- src/navigation-bar.css.ts | 21 +- src/navigation-bar.tsx | 340 +++++++++++------- 6 files changed, 271 insertions(+), 149 deletions(-) create mode 100644 src/__screenshot_tests__/__image_snapshots__/navigation-bar-screenshot-test-tsx-main-navigation-bar-inverse-with-large-menu-in-desktop-1-snap.png create mode 100644 src/__screenshot_tests__/__image_snapshots__/navigation-bar-screenshot-test-tsx-main-navigation-bar-inverse-with-small-menu-in-desktop-1-snap.png diff --git a/src/__screenshot_tests__/__image_snapshots__/navigation-bar-screenshot-test-tsx-main-navigation-bar-inverse-with-large-menu-in-desktop-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/navigation-bar-screenshot-test-tsx-main-navigation-bar-inverse-with-large-menu-in-desktop-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..aafd7f2020f71fd8151c590b621a4cd88f0edebd GIT binary patch literal 23591 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 Qz$*zoUHx3vIVCg!0J^nLZ~y=R literal 0 HcmV?d00001 diff --git a/src/__screenshot_tests__/navigation-bar-screenshot-test.tsx b/src/__screenshot_tests__/navigation-bar-screenshot-test.tsx index 228d924f2..f7f333283 100644 --- a/src/__screenshot_tests__/navigation-bar-screenshot-test.tsx +++ b/src/__screenshot_tests__/navigation-bar-screenshot-test.tsx @@ -216,3 +216,15 @@ test.each(['default', 'custom'])('MainNavigationBar with menu and %s content in await page.click(await screen.findByRole('button', {name: 'Cerrar menú de navegación'})); expect(await page.screenshot()).toMatchImageSnapshot(); }); + +test.each(['large', 'small'])('MainNavigationBar inverse with %s menu in DESKTOP', async (menuType) => { + const page = await openStoryPage({ + id: 'components-navigation-bars-mainnavigationbar--default', + device: 'DESKTOP', + args: {sections: true, desktopSmallMenu: menuType === 'small', menu: 'default', variant: 'inverse'}, + }); + + // first section opened + await page.click(await screen.findByRole('button', {name: 'Start'})); + expect(await page.screenshot()).toMatchImageSnapshot(); +}); diff --git a/src/__tests__/main-navigation-bar-test.tsx b/src/__tests__/main-navigation-bar-test.tsx index 23c736199..127f902e2 100644 --- a/src/__tests__/main-navigation-bar-test.tsx +++ b/src/__tests__/main-navigation-bar-test.tsx @@ -73,6 +73,9 @@ test('MainNavigationBar section with interaction is accessible', async () => { expect(screen.getByRole('link', {name: /item 1-3/})).toBeInTheDocument(); expect(screen.queryByRole('button', {name: 'item 2-1'})).not.toBeInTheDocument(); + // Remove mouse hover from section in order to close the menu + await userEvent.unhover(firstSectionButton); + // Section onPress shouldn't have been called expect(firstSectionOnPressSpy).toHaveBeenCalledTimes(0); @@ -87,7 +90,7 @@ test('MainNavigationBar section with interaction is accessible', async () => { }); // Open second section's menu - await userEvent.click(secondSectionButton); + await userEvent.hover(secondSectionButton); expect(firstSectionMenuButton).toHaveAttribute('aria-expanded', 'false'); expect(secondSectionButton).toHaveAttribute('aria-expanded', 'true'); @@ -98,18 +101,22 @@ test('MainNavigationBar section with interaction is accessible', async () => { // Close the menu with ESC key await userEvent.keyboard('{Escape}'); - await waitFor(() => { - expect(firstSectionMenuButton).toHaveAttribute('aria-expanded', 'false'); - expect(secondSectionButton).toHaveAttribute('aria-expanded', 'false'); - }); - - // Buttons that open the menu should have aria-haspopup - expect(firstSectionMenuButton).toHaveAttribute('aria-haspopup', 'true'); - expect(secondSectionButton).toHaveAttribute('aria-haspopup', 'true'); - - // Menu is closed, no section should be visible - expect(screen.queryByRole('button', {name: 'item 1-1'})).not.toBeInTheDocument(); - expect(screen.queryByRole('button', {name: 'item 2-1'})).not.toBeInTheDocument(); + await waitFor( + () => { + expect(firstSectionMenuButton).toHaveAttribute('aria-expanded', 'false'); + expect(secondSectionButton).toHaveAttribute('aria-expanded', 'false'); + + // Buttons that open the menu should have aria-haspopup + expect(firstSectionMenuButton).toHaveAttribute('aria-haspopup', 'true'); + expect(secondSectionButton).toHaveAttribute('aria-haspopup', 'true'); + + // Menu is closed, no section should be visible + expect(screen.queryByRole('button', {name: 'item 1-1'})).not.toBeInTheDocument(); + expect(screen.queryByRole('button', {name: 'item 2-1'})).not.toBeInTheDocument(); + }, + // queryByRole takes longer to execute + {timeout: 2000} + ); }); test('MainNavigationBar menu closeMenu callback closes the menu', async () => { @@ -132,15 +139,19 @@ test('MainNavigationBar menu closeMenu callback closes the menu', async () => { // Open the menu const sectionButton = await screen.findByRole('button', {name: 'section 1, Abrir submenú'}); - await userEvent.click(sectionButton); + await userEvent.hover(sectionButton); expect(sectionButton).toHaveAttribute('aria-expanded', 'true'); // Close the menu with the closeMenu callback const closeButton = await screen.findByRole('button', {name: 'Close menu'}); await userEvent.click(closeButton); - await waitFor(() => { - expect(sectionButton).toHaveAttribute('aria-expanded', 'false'); - expect(screen.queryByRole('button', {name: 'Close menu'})).not.toBeInTheDocument(); - }); + await waitFor( + () => { + expect(sectionButton).toHaveAttribute('aria-expanded', 'false'); + expect(screen.queryByRole('button', {name: 'Close menu'})).not.toBeInTheDocument(); + }, + // queryByRole takes longer to execute + {timeout: 2000} + ); }); diff --git a/src/navigation-bar.css.ts b/src/navigation-bar.css.ts index 8a93e401f..5fbaa91fd 100644 --- a/src/navigation-bar.css.ts +++ b/src/navigation-bar.css.ts @@ -398,9 +398,28 @@ export const desktopMenuWrapper = sprinkles({ }); export const desktopMenuContainer = style([ + sprinkles({ + position: 'fixed', + left: 0, + right: 0, + }), + { + zIndex: NAVBAR_ZINDEX, + transition: `height ${DESKTOP_MENU_ANIMATION_DURATION_MS}ms cubic-bezier(0.65, 0, 0.35, 1)`, + '@media': { + ['(prefers-reduced-motion)']: { + transition: 'none', + }, + }, + }, +]); + +export const desktopMenuBackgroundContainer = style([ sprinkles({ background: vars.colors.backgroundContainer, - width: '100%', + position: 'absolute', + left: 0, + right: 0, }), { transition: `height ${DESKTOP_MENU_ANIMATION_DURATION_MS}ms cubic-bezier(0.65, 0, 0.35, 1)`, diff --git a/src/navigation-bar.tsx b/src/navigation-bar.tsx index f9ee15ca6..2a4312c93 100644 --- a/src/navigation-bar.tsx +++ b/src/navigation-bar.tsx @@ -38,12 +38,6 @@ import type {Variant} from './theme-variant-context'; import type {TouchableProps} from './touchable'; import type {DataAttributes, HeadingType} from './utils/types'; -const supportsCssMin = () => { - const element = document.createElement('div'); - element.style.height = 'min(2px, 3px)'; - return element.style.height === 'calc(2px)'; -}; - const BurgerMenuIcon = ({isOpen}: {isOpen: boolean}) => { return (
@@ -238,13 +232,44 @@ type MainNavigationBarProps = { desktopSmallMenu?: boolean; }; +type MainNavigationBarMenuStatus = 'opening' | 'opened' | 'closing' | 'closed'; +type MainNavigationBarMenuAction = 'open' | 'finishOpen' | 'close' | 'finishClose'; + +const transitions: Record< + MainNavigationBarMenuStatus, + Partial> +> = { + opening: { + close: 'closing', + finishOpen: 'opened', + }, + opened: { + close: 'closing', + }, + closing: { + // If a section was opened while the menu was closing, the menu should be considered as + // already open. This is useful for example to avoid the new content's fade-in animation + open: 'opened', + finishClose: 'closed', + }, + closed: { + open: 'opening', + }, +}; + +const menuReducer = (state: MainNavigationBarMenuStatus, action: MainNavigationBarMenuAction) => { + return transitions[state][action] || state; +}; + type MainNavigationBarDesktopMenuState = { isMenuOpen: boolean; openedSection: number; + menuHeight: string; + menuStatus: MainNavigationBarMenuStatus; setSectionAsActive: (index: number) => void; setSectionAsInactive: (index: number, forceCloseMenu?: boolean) => void; closeMenu: () => void; - onMenuExited: () => void; + setMenuHeight: (height: string) => void; setIsMenuHovered: (value: boolean) => void; setFocusedItem: (item?: {column: number; index: number}) => void; }; @@ -252,10 +277,12 @@ type MainNavigationBarDesktopMenuState = { const MainNavigationBarDesktopMenuContext = React.createContext({ isMenuOpen: false, openedSection: -1, + menuHeight: '0px', + menuStatus: 'closed', setSectionAsActive: () => {}, setSectionAsInactive: () => {}, closeMenu: () => {}, - onMenuExited: () => {}, + setMenuHeight: () => {}, setIsMenuHovered: () => {}, setFocusedItem: () => {}, }); @@ -271,29 +298,33 @@ const MainNavigationBarDesktopMenuContextProvider = ({ }): JSX.Element => { const {isTabletOrSmaller} = useScreenSize(); const [isMenuOpen, setIsMenuOpen] = React.useState(false); - const [isMenuHovered, setIsMenuHovered] = React.useState(false); + const [menuHeight, setMenuHeight] = React.useState('0px'); + const [menuStatus, dispatch] = React.useReducer(menuReducer, 'closed'); // Item that is currently focused inside a section. This state is used to handle pressing // up/down arrows to navigate through the items of a section. const [focusedItem, setFocusedItem] = React.useState<{column: number; index: number} | undefined>(); - // Section that has been hovered. This state is used to determine whether the menu should be open or closed. - const [activeSection, setActiveSection] = React.useState(-1); + // State that indicated whether the menu container has been hovered + const [isMenuHovered, setIsMenuHovered] = React.useState(false); + + // State that indicates whether the current rendered section has been hovered or its arrow button is focused + const [isSectionHovered, setIsSectionHovered] = React.useState(false); - // Section that is currently being rendered. We keep this as a different state from activeSection - // because when the large menu is closing, we still need to display the section that was opened. + // Section that is currently being rendered const [openedSection, setOpenedSection] = React.useState(-1); const closeMenu = React.useCallback(() => { setIsMenuHovered(false); - setActiveSection(-1); + setIsSectionHovered(false); }, []); // Callback used when a section has been hovered or it's focused arrow has been pressed while it's closed const setSectionAsActive = React.useCallback( (index: number) => { if (sections?.[index]?.menu) { - setActiveSection(index); + setIsSectionHovered(true); + setOpenedSection(index); } else { // If the section has no menu, close the current opened one closeMenu(); @@ -306,43 +337,58 @@ const MainNavigationBarDesktopMenuContextProvider = ({ const setSectionAsInactive = React.useCallback( (index: number, forceCloseMenu?: boolean) => { if (index === openedSection) { + setIsSectionHovered(false); + // We may want to close the menu even when menu is hovered, and if so, we should reset isMenuHovered // (for example, when the current menu is being hovered and we press the arrow from another section) if (forceCloseMenu) { - closeMenu(); - } else { - setActiveSection(-1); + setIsMenuHovered(false); } } }, - [openedSection, closeMenu] + [openedSection] ); - // Callback used to reset openedSection once the large menu close animation has finished - const onMenuExited = React.useCallback(() => { - setOpenedSection(-1); - }, []); - // Close menu when viewport is too small React.useEffect(() => { if (isTabletOrSmaller) { closeMenu(); - setOpenedSection(-1); } }, [isTabletOrSmaller, closeMenu]); React.useEffect(() => { - if (activeSection === -1 && !isMenuHovered) { + if (!isSectionHovered && !isMenuHovered) { setIsMenuOpen(false); - if (isSmallMenu) { - // When rendering the small menu, we don't need to wait for the animation to finish - onMenuExited(); - } - } else if (activeSection !== -1) { - setOpenedSection(activeSection); + setMenuHeight('0px'); + } else { setIsMenuOpen(true); } - }, [isMenuHovered, activeSection, isSmallMenu, onMenuExited]); + }, [isMenuHovered, isSectionHovered]); + + React.useEffect(() => { + const menuAnimationDuration = + isRunningAcceptanceTest() || isSmallMenu ? 0 : styles.DESKTOP_MENU_ANIMATION_DURATION_MS; + + let id: NodeJS.Timeout; + + // If menu started opening/closing + if (!isMenuOpen) { + dispatch('close'); + id = setTimeout(() => dispatch('finishClose'), menuAnimationDuration); + } else { + dispatch('open'); + id = setTimeout(() => dispatch('finishOpen'), menuAnimationDuration); + } + + return () => clearTimeout(id); + }, [isMenuOpen, isSmallMenu]); + + React.useEffect(() => { + // reset openedSection when the menu has been closed + if (menuStatus === 'closed') { + setOpenedSection(-1); + } + }, [menuStatus]); const focusItem = React.useCallback( (item: {column: number; index: number} | undefined) => { @@ -395,12 +441,10 @@ const MainNavigationBarDesktopMenuContextProvider = ({ // Do nothing } }; - // Close menu when ESC key is pressed or when scrolling in the page + // Close menu when ESC key is pressed document.addEventListener('keydown', handleKeyDown, false); - document.addEventListener('scroll', closeMenu); return () => { document.removeEventListener('keydown', handleKeyDown, false); - document.removeEventListener('scroll', closeMenu); }; }, [closeMenu, focusedItem]); @@ -414,10 +458,12 @@ const MainNavigationBarDesktopMenuContextProvider = ({ value={{ isMenuOpen, openedSection, + menuHeight, + menuStatus, setSectionAsActive, setSectionAsInactive, closeMenu, - onMenuExited, + setMenuHeight, setIsMenuHovered, setFocusedItem: focusItem, }} @@ -675,28 +721,28 @@ const MainNavigationBarDesktopMenuSectionColumn = ({ ); }; -const MainNavigationBarDesktopMenu = ({ - sections, +const MainNavigationBarDesktopMenuContent = ({ + section, + index, isLargeNavigationBar, }: { - sections: ReadonlyArray; + section: MainNavigationBarSection; + index: number; isLargeNavigationBar: boolean; }): JSX.Element => { const menuRef = React.useRef(null); - const [menuHeight, setMenuHeight] = React.useState('0px'); const [isMenuContentScrollable, setIsMenuContentScrollable] = React.useState(false); - const isAnySectionOpened = React.useRef(false); const menuAnimationDuration = isRunningAcceptanceTest() ? 0 : styles.DESKTOP_MENU_ANIMATION_DURATION_MS; const topSpace = isLargeNavigationBar ? NAVBAR_HEIGHT_DESKTOP_LARGE : NAVBAR_HEIGHT_DESKTOP; const bottomSpace = 40; - const {isMenuOpen, openedSection, closeMenu, onMenuExited, setIsMenuHovered} = + const {menuStatus, isMenuOpen, openedSection, menuHeight, closeMenu, setIsMenuHovered, setMenuHeight} = useMainNavigationBarDesktopMenuState(); React.useEffect(() => { // Scroll to top of the content if the opened section changed - if (menuRef.current) { + if (menuRef.current && isMenuOpen) { menuRef.current.scrollTop = 0; } @@ -709,54 +755,56 @@ const MainNavigationBarDesktopMenu = ({ } }, [isMenuOpen, openedSection, menuAnimationDuration]); - const columns = sections[openedSection]?.menu?.columns || []; - const customContent = sections[openedSection]?.menu?.content; + const [isContentVisible, setIsContentVisible] = React.useState(true); + + React.useEffect(() => { + if (openedSection === index) { + // If menu is opening, trigger the fade-in effect for current section + if (menuStatus === 'opening') { + setIsContentVisible(false); + // TODO: evaluate if we want a delay in here + setTimeout(() => setIsContentVisible(true), 0); + } else { + setIsContentVisible(true); + } + } + }, [menuStatus, openedSection, index]); + + React.useEffect(() => { + // disable scroll when menu is closing to avoid showing the scrollbar + if (menuStatus === 'closing') { + setIsMenuContentScrollable(false); + } + }, [menuStatus]); + + const columns = section.menu?.columns || []; + const customContent = section?.menu?.content; return (
- -
- { - // Hack to be able to set the fade-in effect in the content after the first render - isAnySectionOpened.current = true; - }} - onExiting={() => setIsMenuContentScrollable(false)} - onExited={() => { - isAnySectionOpened.current = false; - onMenuExited(); - }} - > + + {openedSection === index && ( +
setIsMenuHovered(true)} onMouseLeave={() => setIsMenuHovered(false)} - ref={menuRef} style={{ + top: topSpace, height: menuHeight, - maxHeight: `calc(100vh - ${topSpace}px - ${bottomSpace}px)`, overflowY: isMenuContentScrollable ? 'auto' : 'hidden', }} >
{ - if (el) { - // In old browsers where min() is not supported, the speed of the menu - // height's animation will depend on the height of the content instead of - // the height of the container. - const value = supportsCssMin() - ? `min(${el.scrollHeight}px, calc(100vh - ${topSpace}px - ${bottomSpace}px))` - : `${el.scrollHeight}px`; - setMenuHeight(!isMenuOpen ? '0px' : value); + if (el && isMenuOpen) { + setMenuHeight( + `min(${el.scrollHeight}px, calc(100vh - ${topSpace}px - ${bottomSpace}px))` + ); } }} > @@ -786,9 +834,34 @@ const MainNavigationBarDesktopMenu = ({
-
-
-
+ + )} + +
+ ); +}; + +// This is the menu's background panel. The menu content is rendered separately for each tab +// in order to make the section's content follow the natural focus order of elements +// when using the keyboard or a screen reader to navigate +const MainNavigationBarDesktopMenuBackground = ({ + isLargeNavigationBar, +}: { + isLargeNavigationBar: boolean; +}): JSX.Element => { + const topSpace = isLargeNavigationBar ? NAVBAR_HEIGHT_DESKTOP_LARGE : NAVBAR_HEIGHT_DESKTOP; + const {menuHeight} = useMainNavigationBarDesktopMenuState(); + + return ( +
+
+
+
); }; @@ -815,34 +888,36 @@ const MainNavigationBarDesktopSmallMenu = ({ return (
{index === openedSection && ( -
setIsMenuHovered(true)} - onMouseLeave={() => setIsMenuHovered(false)} - style={{ - top: topSpace, - left: leftPosition, - maxHeight: `calc(100vh - ${topSpace}px - ${bottomSpace}px)`, - }} - > - {customContent ? ( - typeof customContent === 'function' ? ( - customContent({closeMenu}) + +
setIsMenuHovered(true)} + onMouseLeave={() => setIsMenuHovered(false)} + style={{ + top: topSpace, + left: leftPosition, + maxHeight: `calc(100vh - ${topSpace}px - ${bottomSpace}px)`, + }} + > + {customContent ? ( + typeof customContent === 'function' ? ( + customContent({closeMenu}) + ) : ( + customContent + ) ) : ( - customContent - ) - ) : ( - - {columns.map((column, columnIdx) => ( - - ))} - - )} -
+ + {columns.map((column, columnIdx) => ( + + ))} + + )} +
+ )}
); @@ -874,7 +949,8 @@ const MainNavigationBarDesktopSection = ({ const sectionRef = React.useRef(null); const [smallMenuLeftPosition, setSmallMenuLeftPosition] = React.useState(0); const [isArrowFocused, setIsArrowFocused] = React.useState(false); - const {openedSection, setSectionAsActive, setSectionAsInactive} = useMainNavigationBarDesktopMenuState(); + const {isMenuOpen, openedSection, setSectionAsActive, setSectionAsInactive} = + useMainNavigationBarDesktopMenuState(); const hasCustomInteraction = touchableProps.href !== undefined || @@ -931,24 +1007,28 @@ const MainNavigationBarDesktopSection = ({ }; }, [index, isArrowFocused, openSectionMenu, setSectionAsInactive, menu, hasCustomInteraction]); + const isSectionMenuOpen = isMenuOpen && openedSection === index; + + const menuButtonOnPress = React.useCallback(() => { + if (isSectionMenuOpen) { + setSectionAsInactive(index, true); + } else { + openSectionMenu(); + } + }, [isSectionMenuOpen, setSectionAsInactive, openSectionMenu, index]); + const getSectionInteractiveProps = React.useCallback( (touchableProps: InteractiveProps) => { // Open or close the menu when a section without interaction is pressed if (!hasCustomInteraction) { return { - onPress: () => { - if (index !== openedSection) { - openSectionMenu(); - } else { - setSectionAsInactive(index, true); - } - }, + onPress: menuButtonOnPress, }; } return touchableProps as InteractiveProps; }, - [hasCustomInteraction, index, openSectionMenu, openedSection, setSectionAsInactive] + [hasCustomInteraction, menuButtonOnPress] ); return ( @@ -959,7 +1039,8 @@ const MainNavigationBarDesktopSection = ({ [styles.desktopMenuFirstSection]: isFirstSection, [styles.desktopMenuLastSection]: isLastSection, })} - onMouseEnter={() => openSectionMenu()} + // TODO: debounce this! + onMouseEnter={openSectionMenu} onMouseLeave={() => setSectionAsInactive(index)} > { if (isArrowFocused) { - if (index !== openedSection) { - openSectionMenu(); - } else { - setSectionAsInactive(index, true); - } + menuButtonOnPress(); } }} style={{ @@ -1015,19 +1092,25 @@ const MainNavigationBarDesktopSection = ({
)} - {desktopSmallMenu && ( + {desktopSmallMenu ? ( + ) : ( + )} )} @@ -1076,13 +1159,10 @@ export const MainNavigationBar = ({ desktopSmallMenu={desktopSmallMenu} /> ))} - {!desktopSmallMenu && ( - - )} + {!desktopSmallMenu && ( + + )} ); };