Skip to content

Commit

Permalink
(HDS-1975) Adjust to new animation and render style
Browse files Browse the repository at this point in the history
Another PR made the mobileMenu element always visible and animates it vertically.
  • Loading branch information
NikoHelle committed Jan 8, 2024
1 parent f8939ba commit fa46ee6
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
overflow: hidden;
position: absolute;
transform: translateY(-100%) translateY(1px);
transition: var(--animation-duration-dropwdown) transform var(--animation-close-delay-dropdown), var(--animation-duration-dropwdown) min-height calc(var(--animation-duration-dropwdown) + var(--animation-close-delay-dropdown));
transition: var(--animation-duration-dropwdown) transform var(--animation-close-delay-dropdown),
var(--animation-duration-dropwdown) min-height
calc(var(--animation-duration-dropwdown) + var(--animation-close-delay-dropdown));
width: 100%;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type TestTools = RenderResult & {
closeMobileMenu: () => Promise<void>;
findVisibleSectionLinksByMenuIds: (menuIds: string[]) => Promise<HTMLElement[]>;
getNavSections: () => ReturnType<HTMLElement['querySelectorAll']>;
getCSSVisibleSections: () => HTMLElement[];
getActiveLink: () => HTMLAnchorElement;
getPreviousLink: () => HTMLAnchorElement | null;
selectMenuItem: (menuItem: MenuItem) => Promise<HTMLElement[]>;
Expand Down Expand Up @@ -176,21 +177,29 @@ const renderHeader = (): TestTools => {
const result = render(HeaderWithMenus());
const { container, getByText, getAllByText } = result;

const getSections = () => container.querySelectorAll('section');
const getNavSections: TestTools['getNavSections'] = () => container.querySelectorAll('section > nav');
const isElementAriaHidden = (el: Element) => el.getAttribute('aria-hidden') === 'true';

const isElementAriaVisible = (el: Element) => !isElementAriaHidden(el);
const getDropdownButtonForLink = (el: Element) => el.parentElement?.querySelector('button') as HTMLButtonElement;
const getVisibleNav = () => {
const getAriaVisibleNav = () => {
return Array.from(getNavSections()).find((el) =>
isElementAriaVisible(el.parentElement as HTMLElement),
) as HTMLElement;
};

const getCSSVisibleSections = () => {
return Array.from(getSections()).filter((el) => {
const classes = String(el.getAttribute('class'));
return !classes.includes('hidden');
}) as HTMLElement[];
};
const toggleMobileMenu = async (shouldBeOpen: boolean) => {
const menuButton = getByText('Menu') as HTMLButtonElement;
fireEvent.click(menuButton);
await waitFor(() => {
const isClosed = getNavSections().length === 0;
const isClosed = getCSSVisibleSections().length === 0;
if (isClosed === shouldBeOpen) {
throw new Error('Navigation element mismatch');
}
Expand All @@ -206,7 +215,7 @@ const renderHeader = (): TestTools => {
};

const findVisibleSectionLinksByMenuIds: TestTools['findVisibleSectionLinksByMenuIds'] = async (menuIds) => {
const visibleNav = getVisibleNav();
const visibleNav = getAriaVisibleNav();
// ignore links listed in activeListItem. They are invisible.
const activeListItem = visibleNav.querySelector('li.activeListItem');
return waitFor(() => {
Expand All @@ -223,12 +232,12 @@ const renderHeader = (): TestTools => {
};

const getActiveLink: TestTools['getActiveLink'] = () => {
const visibleNav = getVisibleNav();
const visibleNav = getAriaVisibleNav();
return visibleNav.querySelector('a.activeMobileLink') as HTMLAnchorElement;
};

const getPreviousLink: TestTools['getPreviousLink'] = () => {
const visibleNav = getVisibleNav();
const visibleNav = getAriaVisibleNav();
return visibleNav.querySelector('span.previousMobileLink') as HTMLAnchorElement;
};

Expand All @@ -241,7 +250,7 @@ const renderHeader = (): TestTools => {
return Promise.reject(new Error(`Menu item ${item.id} not found`));
}
const getCurrentNavIndex = () => {
const currentNav = getVisibleNav();
const currentNav = getAriaVisibleNav();
const index = Array.from(getNavSections()).indexOf(currentNav);
if (index === -1) {
throw new Error('getCurrentNavIndex is -1');
Expand Down Expand Up @@ -328,6 +337,7 @@ const renderHeader = (): TestTools => {
verifyPreviousItem,
navigateTo,
navigateBack,
getCSSVisibleSections,
};
};

Expand Down Expand Up @@ -370,11 +380,21 @@ describe('<HeaderActionBarNavigationMenu /> spec', () => {
await findVisibleSectionLinksByMenuIds(menus.map((item) => item.id));
expect(getNavSections()).toHaveLength(1);
});
it('Nav sections are rendered only when needed and removed when not needed', async () => {
const { openMobileMenu, navigateTo, getNavSections, navigateBack, closeMobileMenu } = renderHeader();
expect(getNavSections()).toHaveLength(0);
await openMobileMenu();
it('Nav sections are rendered and visible only when needed and removed when not needed', async () => {
const {
openMobileMenu,
navigateTo,
getNavSections,
navigateBack,
closeMobileMenu,
getCSSVisibleSections,
} = renderHeader();
// one is always rendered
expect(getNavSections()).toHaveLength(1);
// but it is hidden
expect(getCSSVisibleSections()).toHaveLength(0);
await openMobileMenu();
expect(getCSSVisibleSections()).toHaveLength(1);
await navigateTo(getMenuItem([0]));
expect(getNavSections()).toHaveLength(2);
await navigateTo(getMenuItem([0, 2]));
Expand All @@ -384,23 +404,24 @@ describe('<HeaderActionBarNavigationMenu /> spec', () => {
await navigateBack(frontPageLabel);
expect(getNavSections()).toHaveLength(1);
await closeMobileMenu();
expect(getNavSections()).toHaveLength(0);
expect(getCSSVisibleSections()).toHaveLength(0);
expect(getNavSections()).toHaveLength(1);
});
it('Previous and active links change while navigating', async () => {
const {
openMobileMenu,
navigateTo,
getNavSections,
navigateBack,
verifyActiveItem,
verifyPreviousItem,
getCSSVisibleSections,
} = renderHeader();
const menu3 = getMenuItem([3]);
const menu31 = getMenuItem([3, 1]);
const menu32 = getMenuItem([3, 2]);
const menu0 = getMenuItem([0]);
const menu02 = getMenuItem([0, 2]);
expect(getNavSections()).toHaveLength(0);
expect(getCSSVisibleSections()).toHaveLength(0);
await openMobileMenu();
expect(verifyActiveItem(frontPageLabel)).toBeTruthy();
expect(verifyPreviousItem(null)).toBeTruthy();
Expand Down Expand Up @@ -440,29 +461,27 @@ describe('<HeaderActionBarNavigationMenu /> spec', () => {
expect(verifyPreviousItem(null)).toBeTruthy();
});
it('If the top level active link is clicked, menu is closed.', async () => {
const { openMobileMenu, getNavSections, getActiveLink } = renderHeader();
expect(getNavSections()).toHaveLength(0);
const { openMobileMenu, getActiveLink, getCSSVisibleSections } = renderHeader();
await openMobileMenu();

const activeLinkToFrontpage = getActiveLink();
// suppress jsdom "navigation not implemented" error
activeLinkToFrontpage.setAttribute('href', '');
fireEvent.click(activeLinkToFrontpage);
await waitFor(() => {
expect(getNavSections()).toHaveLength(0);
expect(getCSSVisibleSections()).toHaveLength(0);
});
});
it('If a lower level active link is clicked, menu is closed and onClick handler is called.', async () => {
const { openMobileMenu, getNavSections, getActiveLink, selectMenuItem } = renderHeader();
expect(getNavSections()).toHaveLength(0);
const { openMobileMenu, getActiveLink, selectMenuItem, getCSSVisibleSections } = renderHeader();
await openMobileMenu();
await selectMenuItem(getMenuItem([0]));

const activeLinkToFrontpage = getActiveLink();
fireEvent.click(activeLinkToFrontpage);
await waitFor(() => {
expect(onClickTracker).toHaveBeenCalledTimes(1);
expect(getNavSections()).toHaveLength(0);
expect(getCSSVisibleSections()).toHaveLength(0);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { HeaderLink } from '../headerLink';
import { IconAngleLeft } from '../../../../icons';
import { LinkProps } from '../../../../internal/LinkItem';
import HeaderActionBarLogo from './HeaderActionBarLogo';
import useIsomorphicLayoutEffect from '../../../../hooks/useIsomorphicLayoutEffect';

type NavigationSectionType = {
logo: React.ReactNode;
Expand Down Expand Up @@ -239,7 +240,7 @@ export const HeaderActionBarNavigationMenu = ({
const goToPreviousMenuLevel = () => {
const last = selectedMenuLevels.pop();

// Last item is removed after animation is done.
// Last item is removed after horizontal animation is done.
// It is marked as inactive here and removed in resetMenusAfterAnimation()
setSelectedMenuLevels([
...selectedMenuLevels.map((item) => {
Expand Down Expand Up @@ -338,8 +339,6 @@ export const HeaderActionBarNavigationMenu = ({
}
});

if (!mobileMenuOpen) return null;

const goDeeper = (link: React.ReactElement) => {
if (isAnimating()) {
return;
Expand All @@ -358,17 +357,34 @@ export const HeaderActionBarNavigationMenu = ({
setMobileMenuOpen(false);
};

const setCurrentMenuHeight = () => {
const containerElement = navContainerRef.current;
if (!containerElement) {
return;
}
// Set the height of the menu container
const renderedChildIndex = resetMenusAfterAnimation() - 1;
const currentMenuSection = containerElement.children[renderedChildIndex];
if (!currentMenuSection) {
return;
}
navContainerRef.current.style.height = `${currentMenuSection.clientHeight}px`;
};

const menuSectionsAnimationDone = async (e: TransitionEvent) => {
const targetElement = e.target as HTMLElement;
if (e.propertyName === 'transform' && targetElement.firstChild.nodeName === 'SECTION') {
shouldSetFocus.current = true;
// Set the height of the menu container
const renderedChildIndex = resetMenusAfterAnimation() - 1;
const currentMenuSection = targetElement.children[renderedChildIndex];
navContainerRef.current.style.height = `${currentMenuSection.clientHeight}px`;
setCurrentMenuHeight();
}
};

useIsomorphicLayoutEffect(() => {
if (mobileMenuOpen) {
setCurrentMenuHeight();
}
}, [mobileMenuOpen]);

const RenderNavigationSection = ({
links,
activeLink,
Expand Down Expand Up @@ -432,8 +448,8 @@ export const HeaderActionBarNavigationMenu = ({
return (
<RenderNavigationSection
activeLink={activeLink}
ariaHidden={!isCurrentMenu}
className={shouldBeVisible ? styles.visible : styles.hidden}
ariaHidden={!mobileMenuOpen || !isCurrentMenu}
className={mobileMenuOpen && shouldBeVisible ? styles.visible : styles.hidden}
key={key}
links={links}
previousLink={previousLink}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ exports[`<HeaderActionBarNavigationMenu /> spec renders the component and menu c
>
<div
class="navigationWrapper left0"
style="height: 0px;"
>
<section
aria-hidden="false"
Expand Down

0 comments on commit fa46ee6

Please sign in to comment.