Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiHeaderLinks] Allow consumers to close the mobile popover from within the popover content #7603

Merged
merged 4 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelogs/upcoming/7603.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `EuiHeaderLinks` now accepts a `children` render function that will be passed a `closeMobilePopover` callback, allowing consumers to close the mobile popover by its content
14 changes: 11 additions & 3 deletions src-docs/src/views/header/header_links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ export default () => {

<EuiHeaderSectionItem>
<EuiHeaderLinks aria-label="App navigation links example">
<EuiHeaderLink isActive>Docs</EuiHeaderLink>
{(closeMobilePopover) => (
<>
<EuiHeaderLink isActive onClick={closeMobilePopover}>
Docs
</EuiHeaderLink>

<EuiHeaderLink>Code</EuiHeaderLink>
<EuiHeaderLink onClick={closeMobilePopover}>Code</EuiHeaderLink>

<EuiHeaderLink iconType="help">Help</EuiHeaderLink>
<EuiHeaderLink iconType="help" onClick={closeMobilePopover}>
Help
</EuiHeaderLink>
</>
)}
</EuiHeaderLinks>
</EuiHeaderSectionItem>
</EuiHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,7 @@ exports[`EuiHeaderLinks is rendered 1`] = `
</nav>
`;

exports[`EuiHeaderLinks popover props is never rendered with "none" 1`] = `
<nav
aria-label="App menu"
class="euiHeaderLinks emotion-euiHeaderLinks"
>
<div
class="euiHeaderLinks__list emotion-euiHeaderLinks__list-s-EuiHeaderLinks"
/>
</nav>
`;

exports[`EuiHeaderLinks popover props is rendered 1`] = `
exports[`EuiHeaderLinks mobile menu/popover renders various popover props 1`] = `
<nav
aria-label="App menu"
class="euiHeaderLinks emotion-euiHeaderLinks"
Expand Down
34 changes: 28 additions & 6 deletions src/components/header/header_links/header_links.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
*/

import React from 'react';
import { fireEvent } from '@testing-library/react';
import {
render,
waitForEuiPopoverOpen,
waitForEuiPopoverClose,
} from '../../../test/rtl';
import { requiredProps } from '../../../test';
import { shouldRenderCustomStyles } from '../../../test/internal';
import { render } from '../../../test/rtl';

import { EuiHeaderLinks, GUTTER_SIZES } from './header_links';

Expand All @@ -36,8 +41,8 @@ describe('EuiHeaderLinks', () => {
});
});

describe('popover props', () => {
test('is rendered', () => {
describe('mobile menu/popover', () => {
it('renders various popover props', () => {
const { container } = render(
<EuiHeaderLinks
popoverBreakpoints={['xs', 's', 'm', 'l', 'xl']}
Expand All @@ -52,12 +57,29 @@ describe('EuiHeaderLinks', () => {
expect(container.firstChild).toMatchSnapshot();
});

test('is never rendered with "none"', () => {
it('never renders a popover with "none" breakpoint', () => {
const { container } = render(
<EuiHeaderLinks popoverBreakpoints={'none'} />
<EuiHeaderLinks popoverBreakpoints="none" />
);

expect(container.firstChild).toMatchSnapshot();
expect(container.querySelector('.euiPopover')).toBeNull();
});

it('passes a callback that closes the menu/popover to children render props', () => {
const { getByLabelText, getByText } = render(
<EuiHeaderLinks popoverBreakpoints="all">
{(closePopover) => (
<a href="#" onClick={closePopover}>
This link should close the popover
</a>
)}
</EuiHeaderLinks>
);

fireEvent.click(getByLabelText('Open menu'));
waitForEuiPopoverOpen();
fireEvent.click(getByText('This link should close the popover'));
waitForEuiPopoverClose();
});
});
});
17 changes: 14 additions & 3 deletions src/components/header/header_links/header_links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import React, {
ReactNode,
HTMLAttributes,
FunctionComponent,
useState,
Expand Down Expand Up @@ -39,7 +40,14 @@ type EuiHeaderLinksPopoverButtonProps =
};

export type EuiHeaderLinksProps = CommonProps &
HTMLAttributes<HTMLElement> & {
Omit<HTMLAttributes<HTMLElement>, 'children'> & {
/**
* Takes any rendered node(s), typically of `EuiHeaderLink`s.
*
* Optionally takes a render function that will pass a callback allowing you
* to close the mobile popover from within your popover content.
*/
children?: ReactNode | ((closeMobilePopover: () => void) => ReactNode);
/**
* Spacing between direct children
*/
Expand Down Expand Up @@ -116,6 +124,9 @@ export const EuiHeaderLinks: FunctionComponent<EuiHeaderLinksProps> = ({
</EuiI18n>
);

const renderedChildren =
typeof children === 'function' ? children(closeMenu) : children;

return (
<EuiI18n token="euiHeaderLinks.appNavigation" default="App menu">
{(appNavigation: string) => (
Expand All @@ -133,7 +144,7 @@ export const EuiHeaderLinks: FunctionComponent<EuiHeaderLinksProps> = ({
styles.gutterSizes[gutterSize],
]}
>
{children}
{renderedChildren}
</div>
</EuiHideFor>

Expand All @@ -151,7 +162,7 @@ export const EuiHeaderLinks: FunctionComponent<EuiHeaderLinksProps> = ({
className="euiHeaderLinks__mobileList"
css={styles.euiHeaderLinks__mobileList}
>
{children}
{renderedChildren}
</div>
</EuiPopover>
</EuiShowFor>
Expand Down
Loading