Skip to content

Commit b5a3212

Browse files
committed
EuiFlyoutMenu
1 parent 6112606 commit b5a3212

16 files changed

+783
-87
lines changed

packages/eui/src/components/flyout/flyout.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import { EuiScreenReaderOnly } from '../accessibility';
4747
import { EuiFlyoutCloseButton } from './_flyout_close_button';
4848
import { euiFlyoutStyles } from './flyout.styles';
4949
import { EuiFlyoutChild } from './flyout_child';
50+
import { EuiFlyoutMenuContext } from './flyout_menu_context';
51+
import { EuiFlyoutMenu } from './flyout_menu';
5052
import { EuiFlyoutChildProvider } from './flyout_child_manager';
5153

5254
export const TYPES = ['push', 'overlay'] as const;
@@ -230,6 +232,13 @@ export const EuiFlyout = forwardRef(
230232
const hasChildFlyout = !!childFlyoutElement;
231233

232234
// Validate props, determine close button position and set child flyout classes
235+
const hasFlyoutMenu = React.Children.toArray(children).some(
236+
(child) =>
237+
React.isValidElement(child) &&
238+
(child.type === EuiFlyoutMenu ||
239+
(child.type as any).displayName === 'EuiFlyoutMenu')
240+
);
241+
233242
let closeButtonPosition: 'inside' | 'outside';
234243
let childFlyoutClasses: string[] = [];
235244
if (hasChildFlyout) {
@@ -452,7 +461,7 @@ export const EuiFlyout = forwardRef(
452461
[onClose, hasOverlayMask, outsideClickCloses]
453462
);
454463

455-
const closeButton = !hideCloseButton && (
464+
const closeButton = !hideCloseButton && !hasFlyoutMenu && (
456465
<EuiFlyoutCloseButton
457466
{...closeButtonProps}
458467
onClose={onClose}
@@ -514,7 +523,9 @@ export const EuiFlyout = forwardRef(
514523
>
515524
{!isPushed && screenReaderDescription}
516525
{closeButton}
517-
{contentToRender}
526+
<EuiFlyoutMenuContext.Provider value={{ onClose }}>
527+
{contentToRender}
528+
</EuiFlyoutMenuContext.Provider>
518529
</Element>
519530
</EuiFocusTrap>
520531
</EuiFlyoutWrapper>

packages/eui/src/components/flyout/flyout_child.stories.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { EuiSpacer } from '../spacer';
2020
import { EuiRadioGroup, EuiRadioGroupOption } from '../form';
2121
import { LOKI_SELECTORS } from '../../../.storybook/loki';
2222
import { EuiBreakpointSize } from '../../services';
23+
import { EuiFlyoutMenu } from './flyout_menu';
2324

2425
const breakpointSizes: EuiBreakpointSize[] = ['xs', 's', 'm', 'l', 'xl'];
2526

@@ -314,6 +315,7 @@ const ChildBackgroundStylesFlyout = () => {
314315
type="push"
315316
pushMinBreakpoint="xs"
316317
>
318+
<EuiFlyoutMenu title="Main Flyout" />
317319
<EuiFlyoutBody>
318320
<EuiText>
319321
<p>This is the main flyout content.</p>
@@ -327,6 +329,7 @@ const ChildBackgroundStylesFlyout = () => {
327329
size="s"
328330
backgroundStyle="default"
329331
>
332+
<EuiFlyoutMenu title="Child flyout: Default background style" />
330333
<ChildFlyoutContent />
331334
</EuiFlyoutChild>
332335
)}
@@ -336,6 +339,7 @@ const ChildBackgroundStylesFlyout = () => {
336339
size="s"
337340
backgroundStyle="shaded"
338341
>
342+
<EuiFlyoutMenu title="Child flyout: Shaded background style" />
339343
<ChildFlyoutContent />
340344
</EuiFlyoutChild>
341345
)}

packages/eui/src/components/flyout/flyout_child.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { euiFlyoutChildStyles } from './flyout_child.styles';
2323
import { EuiFlyoutCloseButton } from './_flyout_close_button';
2424
import { EuiFlyoutContext } from './flyout_context';
2525
import { EuiFlyoutBody } from './flyout_body';
26+
import { EuiFlyoutMenu } from './flyout_menu';
27+
import { EuiFlyoutMenuContext } from './flyout_menu_context';
2628
import { EuiFocusTrap } from '../focus_trap';
2729

2830
/**
@@ -117,8 +119,16 @@ export const EuiFlyoutChild: FunctionComponent<EuiFlyoutChildProps> = ({
117119

118120
let flyoutTitleText: string | undefined;
119121
let hasDescribedByBody = false;
122+
let hasFlyoutMenu = false;
120123
Children.forEach(children, (child) => {
121124
if (React.isValidElement(child)) {
125+
if (
126+
child.type === EuiFlyoutMenu ||
127+
(child.type as any).displayName === 'EuiFlyoutMenu'
128+
) {
129+
hasFlyoutMenu = true;
130+
}
131+
122132
if ((child.type as any)?.displayName === 'EuiFlyoutHeader') {
123133
// Attempt to extract string content from header for ARIA
124134
const headerChildren = child.props.children;
@@ -243,7 +253,7 @@ export const EuiFlyoutChild: FunctionComponent<EuiFlyoutChildProps> = ({
243253
{flyoutTitleText}
244254
</h2>
245255
)}
246-
{!hideCloseButton && (
256+
{!hideCloseButton && !hasFlyoutMenu && (
247257
<EuiFlyoutCloseButton
248258
className="euiFlyoutChild__closeButton"
249259
onClose={handleClose}
@@ -270,7 +280,9 @@ export const EuiFlyoutChild: FunctionComponent<EuiFlyoutChildProps> = ({
270280
className="euiFlyoutChild__overflowContent"
271281
css={styles.overflow.wrapper}
272282
>
273-
{processedChildren}
283+
<EuiFlyoutMenuContext.Provider value={{ onClose }}>
284+
{processedChildren}
285+
</EuiFlyoutMenuContext.Provider>
274286
</div>
275287
</div>
276288
</div>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
import { css } from '@emotion/react';
10+
import { useEuiTheme } from '../../services';
11+
12+
export const useEuiFlyoutMenuStyles = () => {
13+
const { euiTheme } = useEuiTheme();
14+
15+
return {
16+
euiFlyoutMenu: css`
17+
block-size: calc(${euiTheme.size.m} * 3.5);
18+
flex-shrink: 0;
19+
padding-block: ${euiTheme.size.m};
20+
padding-inline: ${euiTheme.size.l};
21+
border-block-end: ${euiTheme.border.width.thin} solid
22+
${euiTheme.border.color};
23+
`,
24+
};
25+
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
import classNames from 'classnames';
10+
import React, { FunctionComponent, HTMLAttributes, useContext } from 'react';
11+
import { useGeneratedHtmlId } from '../../services';
12+
import { CommonProps } from '../common';
13+
import { EuiFlexGroup, EuiFlexItem } from '../flex';
14+
import { EuiTitle } from '../title';
15+
import { EuiFlyoutCloseButton } from './_flyout_close_button';
16+
import { useEuiFlyoutMenuStyles } from './flyout_menu.styles';
17+
import { EuiFlyoutMenuContext } from './flyout_menu_context';
18+
19+
export type EuiFlyoutMenuProps = CommonProps &
20+
HTMLAttributes<HTMLDivElement> & {
21+
backButton?: React.ReactNode;
22+
popover?: React.ReactNode;
23+
title?: React.ReactNode;
24+
hideCloseButton?: boolean;
25+
};
26+
27+
export const EuiFlyoutMenu: FunctionComponent<EuiFlyoutMenuProps> = ({
28+
children,
29+
className,
30+
backButton,
31+
popover,
32+
title,
33+
hideCloseButton,
34+
...rest
35+
}) => {
36+
const { onClose } = useContext(EuiFlyoutMenuContext);
37+
38+
const styles = useEuiFlyoutMenuStyles();
39+
const cssStyles = [styles.euiFlyoutMenu];
40+
const classes = classNames('euiFlyoutMenu', className);
41+
const titleId = useGeneratedHtmlId();
42+
43+
let titleNode;
44+
if (title) {
45+
titleNode = (
46+
<EuiTitle size="xxs" id={titleId}>
47+
<h3>{title}</h3>
48+
</EuiTitle>
49+
);
50+
}
51+
52+
const handleClose = (event: MouseEvent | TouchEvent | KeyboardEvent) => {
53+
onClose?.(event);
54+
};
55+
56+
let closeButton;
57+
if (!hideCloseButton) {
58+
closeButton = (
59+
<EuiFlyoutCloseButton
60+
onClose={handleClose}
61+
side="right"
62+
closeButtonPosition="inside"
63+
/>
64+
);
65+
}
66+
67+
return (
68+
<div className={classes} css={cssStyles} {...rest}>
69+
<EuiFlexGroup
70+
alignItems="center"
71+
justifyContent="spaceBetween"
72+
gutterSize="none"
73+
responsive={false}
74+
>
75+
<EuiFlexItem>
76+
<EuiFlexGroup
77+
alignItems="center"
78+
gutterSize="m"
79+
responsive={false}
80+
wrap
81+
>
82+
{backButton && <EuiFlexItem grow={false}>{backButton}</EuiFlexItem>}
83+
{popover && <EuiFlexItem grow={false}>{popover}</EuiFlexItem>}
84+
{titleNode && <EuiFlexItem grow={false}>{titleNode}</EuiFlexItem>}
85+
{children && <EuiFlexItem grow={false}>{children}</EuiFlexItem>}
86+
</EuiFlexGroup>
87+
</EuiFlexItem>
88+
{closeButton && <EuiFlexItem grow={false}>{closeButton}</EuiFlexItem>}
89+
</EuiFlexGroup>
90+
</div>
91+
);
92+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
import { createContext } from 'react';
10+
import { EuiFlyoutProps } from './flyout';
11+
12+
interface EuiFlyoutMenuContextProps {
13+
onClose?: EuiFlyoutProps['onClose'];
14+
}
15+
16+
export const EuiFlyoutMenuContext = createContext<EuiFlyoutMenuContextProps>(
17+
{}
18+
);

packages/eui/src/components/flyout/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@ export { EuiFlyoutResizable } from './flyout_resizable';
2626
export { EuiFlyoutChild } from './flyout_child';
2727
export type { EuiFlyoutChildProps } from './flyout_child';
2828

29+
export type { EuiFlyoutMenuProps } from './flyout_menu';
30+
export { EuiFlyoutMenu } from './flyout_menu';
31+
2932
export type {
3033
EuiFlyoutSessionApi,
3134
EuiFlyoutSessionConfig,
3235
EuiFlyoutSessionOpenChildOptions,
33-
EuiFlyoutSessionOpenMainOptions,
3436
EuiFlyoutSessionOpenGroupOptions,
37+
EuiFlyoutSessionOpenMainOptions,
38+
EuiFlyoutSessionOpenSystemOptions,
3539
EuiFlyoutSessionProviderComponentProps,
3640
EuiFlyoutSessionRenderContext,
3741
} from './sessions';

0 commit comments

Comments
 (0)