Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
9568ccc
Add FlyoutSystemMenu component (#8851)
tsullivan Jul 25, 2025
c75a0ba
[flyouts] Developer API + sessions (#8939)
clintandrewhall Sep 2, 2025
67b1f48
[Flyout System] Support size="fill" (#8982)
tsullivan Sep 4, 2025
fc0c507
test(EuiFlyout): update failing snapshot coming from `feat/flyout-sys…
tkajtoch Sep 8, 2025
d3ff0a9
Make title required
tsullivan Sep 4, 2025
ed75f1e
Ideal `flyout_managed` structure
tsullivan Sep 5, 2025
912dfc6
Merge-friendly structure
tsullivan Sep 5, 2025
5f67e89
Updte flyout_menu: showCustomActions, showBackButton and historyItems
tsullivan Sep 5, 2025
a67e1fe
Show flyout manager context on the Multi-session example
tsullivan Sep 5, 2025
83171b2
Show past flyouts from manager state in the history popover
tsullivan Sep 5, 2025
ff841a0
GoBackAction and GoToFlyoutAction
tsullivan Sep 5, 2025
244164c
Add onUnregister
tsullivan Sep 5, 2025
a02cc8b
Update tests to use new `title` param in `addFlyout`
tsullivan Sep 6, 2025
b84bca8
changelog
tsullivan Sep 8, 2025
9231f54
Move changes into TODOs for next PR
tsullivan Sep 8, 2025
861da92
add unit tests
tsullivan Sep 8, 2025
c5169f6
add changelog
tsullivan Sep 9, 2025
8a862a4
[Flyout System] Add managed history controls
tsullivan Sep 9, 2025
3331dce
handle lifecycle callbacks
tsullivan Sep 9, 2025
fe67206
fix lint
tsullivan Sep 15, 2025
228aef4
[Flyout System] Support resizing flyouts (#8999)
tkajtoch Sep 16, 2025
2c31f4f
[Flyout system] Improve flyout animations (#9025)
tkajtoch Sep 16, 2025
9231390
--wip-- [skip ci]
tsullivan Sep 16, 2025
065f1b1
Merge branch 'feat/flyout-system' into session-flyouts/improve-menu-bar
tsullivan Sep 16, 2025
19c0714
fix snapshot whitespace
tsullivan Sep 16, 2025
71a4227
Merge branch 'session-flyouts/improve-menu-bar' into session-flyouts/…
tsullivan Sep 16, 2025
6c9eac3
[Flyout System] require title for session flyouts, support custom act…
tsullivan Sep 16, 2025
1b9e56c
Merge branch 'feat/flyout-system' into session-flyouts/track-history
tsullivan Sep 16, 2025
171998a
sync with feat/flyout-system
tsullivan Sep 16, 2025
08c7bd8
Cleanup “legacy”
tsullivan Sep 16, 2025
54866da
Cleanup console.log
tsullivan Sep 16, 2025
9c8a1d4
Clean up onClose tracking
tsullivan Sep 16, 2025
ae35c36
Remove `previousSessionRef`
tsullivan Sep 16, 2025
1e0c6c8
Cleanup
tsullivan Sep 16, 2025
c295374
Cleanup console.log and some comments
tsullivan Sep 16, 2025
1fbfddd
Orchestrate timing of calling and unregistering callbacks with plain …
tsullivan Sep 17, 2025
1aca48f
WIP
tsullivan Sep 17, 2025
d775787
Merge branch 'feat/flyout-system' into session-flyouts/track-history
tsullivan Sep 17, 2025
becc9c7
cleanup
tsullivan Sep 17, 2025
e511dd6
Cosmetic
tsullivan Sep 18, 2025
cf4ac22
Comment fields of the FlyoutSession interface
tsullivan Sep 18, 2025
185b1c2
Attempt to make sure the onActive callback is only fired on the flyou…
tsullivan Sep 18, 2025
3bc5ef2
Make the resizable hook inert when resizable is not enabled
tsullivan Sep 23, 2025
17cfce9
Merge branch 'flyout-system/fix-9048' into session-flyouts/track-history
tsullivan Sep 23, 2025
b47bca5
Cosmetic diff reduction
tsullivan Sep 23, 2025
a7be91b
Memoize result of complex operations
tsullivan Sep 23, 2025
0b83bc8
Add missing actions to reducer test
tsullivan Sep 23, 2025
c75422e
do not call callbacks from reducer
tsullivan Sep 23, 2025
5349112
Merge branch 'feat/flyout-system' into session-flyouts/track-history
tsullivan Sep 24, 2025
4500f38
Merge branch 'feat/flyout-system' into session-flyouts/track-history
tsullivan Sep 25, 2025
3c3551b
Fix an auto-close issue
tsullivan Sep 25, 2025
ef5f3b7
Fix multi-session stories for `isOpen`
tsullivan Sep 25, 2025
9a3890f
Close button should go back
tsullivan Sep 25, 2025
774a68b
Optimize storybook
tsullivan Sep 25, 2025
7172f79
Fix back button bug: should call onClose
tsullivan Sep 25, 2025
cf70846
Fix some edge cases
tsullivan Sep 25, 2025
fd8b026
Storybook
tsullivan Sep 25, 2025
c8134bd
Improve tests
tsullivan Sep 25, 2025
6cb11fb
Remove test of implementation details
tsullivan Sep 25, 2025
576dcb5
clean up handlers in storybook
tsullivan Sep 28, 2025
fbc92b6
feedback: replace handling of state with selector
tsullivan Sep 28, 2025
4d018ff
mainFlyoutId: string; childFlyoutId: string;
tsullivan Sep 28, 2025
03b3dfa
consolidate EuiFlyoutCloseEvent export
tsullivan Sep 29, 2025
274bda3
feedback: clean up interface declaration
tsullivan Sep 29, 2025
9c0fc33
feedback: update changelog message
tsullivan Sep 29, 2025
20b0cdc
add translations
tsullivan Sep 29, 2025
eeb8d2a
fix "Open Session E" button in multi-session example
tsullivan Sep 29, 2025
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
2 changes: 2 additions & 0 deletions packages/eui/changelogs/upcoming/9003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Updated `EuiFlyout` with new `onActive` callback and enable stack managed history controls.
- Updated `EuiFlyoutMenu` with new prop `historyItems` and refactored props for back button.
17 changes: 10 additions & 7 deletions packages/eui/src/components/flyout/flyout.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import {
useEuiFlyoutOpenState,
type EuiFlyoutOpenState,
} from './use_open_state';
import type { EuiFlyoutCloseEvent } from './types';

interface _EuiFlyoutComponentProps {
/**
Expand All @@ -89,14 +90,14 @@ interface _EuiFlyoutComponentProps {
*
* Use this callback to toggle your internal `isOpen` flyout state.
*/
onClose: (event?: MouseEvent | TouchEvent | KeyboardEvent) => void;
onClose: (event?: EuiFlyoutCloseEvent) => void;
/**
* An optional callback function fired when the flyout begins closing.
*
* Use in case you need to support any extra logic that relies on the flyout
* closing state. In most cases this callback doesn't need to be handled.
*/
onClosing?: (event?: MouseEvent | TouchEvent | KeyboardEvent) => void;
onClosing?: (event?: EuiFlyoutCloseEvent) => void;
/**
* Defines the width of the panel.
* Pass a predefined size of `s | m | l`, or pass any number/string compatible with the CSS `width` attribute
Expand Down Expand Up @@ -384,9 +385,9 @@ export const EuiFlyoutComponent = forwardRef(
}

const siblingFlyoutId =
currentSession.main === flyoutId
? currentSession.child
: currentSession.main;
currentSession.mainFlyoutId === flyoutId
? currentSession.childFlyoutId
: currentSession.mainFlyoutId;

return {
siblingFlyoutId,
Expand All @@ -398,9 +399,11 @@ export const EuiFlyoutComponent = forwardRef(
// Destructure for easier use
const { siblingFlyoutId } = flyoutIdentity;

const hasChildFlyout = currentSession?.child != null;
const hasChildFlyout = currentSession?.childFlyoutId != null;
const isChildFlyout =
isInManagedContext && hasChildFlyout && currentSession?.child === id;
isInManagedContext &&
hasChildFlyout &&
currentSession?.childFlyoutId === id;

const shouldCloseOnEscape = useMemo(() => {
// Regular flyout - always close on ESC
Expand Down
20 changes: 14 additions & 6 deletions packages/eui/src/components/flyout/flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,16 @@ export type EuiFlyoutProps<T extends ElementType = 'div' | 'nav'> = Omit<
'as'
> & {
session?: boolean;
onActive?: () => void;
as?: T;
};

export const EuiFlyout = forwardRef<
HTMLDivElement | HTMLElement,
EuiFlyoutProps<'div' | 'nav'>
>((props, ref) => {
const { session, as, onClose, ...rest } = usePropsWithComponentDefaults(
'EuiFlyout',
props
);
const { session, as, onClose, onActive, ...rest } =
usePropsWithComponentDefaults('EuiFlyout', props);
const hasActiveSession = useHasActiveSession();
const isUnmanagedFlyout = useRef(false);
const isInManagedFlyout = useIsInManagedFlyout();
Expand All @@ -68,12 +67,21 @@ export const EuiFlyout = forwardRef<
onClose?.({} as any);
return null;
}
return <EuiFlyoutMain {...rest} onClose={onClose} as="div" />;
return (
<EuiFlyoutMain {...rest} onClose={onClose} onActive={onActive} as="div" />
);
}

// Else if this flyout is a child of a session AND within a managed flyout context, render EuiChildFlyout.
if (hasActiveSession && isInManagedFlyout) {
return <EuiFlyoutChild {...rest} onClose={onClose} as="div" />;
return (
<EuiFlyoutChild
{...rest}
onClose={onClose}
onActive={onActive}
as="div"
/>
);
}

// TODO: if resizeable={true}, render EuiResizableFlyout.
Expand Down
91 changes: 28 additions & 63 deletions packages/eui/src/components/flyout/flyout_menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,103 +10,67 @@ import React, { useState } from 'react';

import { action } from '@storybook/addon-actions';
import { Meta, StoryObj } from '@storybook/react';
import { EuiButton, EuiButtonEmpty, EuiButtonIcon } from '../button';
import { EuiButton } from '../button';
import { EuiSpacer } from '../spacer';
import { EuiText } from '../text';
import { EuiFlyout } from './flyout';
import { EuiFlyoutBody } from './flyout_body';
import { EuiFlyoutMenu, EuiFlyoutMenuProps } from './flyout_menu';
import { EuiIcon } from '../icon';
import { EuiPopover } from '../popover';
import { EuiListGroup, EuiListGroupItem } from '../list_group';

interface Args extends EuiFlyoutMenuProps {
showBackButton: boolean;
showCustomActions: boolean;
showPopover: boolean;
showHistoryItems: boolean;
}

const meta: Meta<Args> = {
title: 'Layout/EuiFlyout/EuiFlyoutMenu',
component: EuiFlyoutMenu,
argTypes: {
showBackButton: { control: 'boolean' },
showCustomActions: { control: 'boolean' },
'aria-label': { table: { disable: true } },
backButtonProps: { table: { disable: true } },
customActions: { table: { disable: true } },
showPopover: { control: 'boolean' },
backButton: { table: { disable: true } },
popover: { table: { disable: true } },
historyItems: { table: { disable: true } },
},
args: {
hideCloseButton: false,
showBackButton: true,
showCustomActions: true,
showPopover: true,
showHistoryItems: true,
},
};

export default meta;

const MenuBarFlyout = (args: Args) => {
const { showCustomActions, hideCloseButton, showBackButton, showPopover } =
args;
const {
hideCloseButton,
showBackButton,
showCustomActions,
showHistoryItems,
} = args;

const [isFlyoutOpen, setIsFlyoutOpen] = useState(true);
const openFlyout = () => setIsFlyoutOpen(true);
const closeFlyout = () => {
setIsFlyoutOpen(false);
};

/* Back button */

// TODO: back button should be internalized in EuiFlyoutMenu when historyItems are passed
const backButton = (
<EuiButtonEmpty size="xs" color="text">
<EuiIcon type="editorUndo" /> Back
</EuiButtonEmpty>
);

/* History popover */

const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const handlePopoverButtonClick = () => {
setIsPopoverOpen(!isPopoverOpen);
const backButtonProps = {
onClick: () => {
action('back button')('click');
},
};

const historyItems = [
{ config: { mainTitle: 'First item' } },
{ config: { mainTitle: 'Second item' } },
{ config: { mainTitle: 'Third item' } },
];

// TODO: history popover should be internalized in EuiFlyoutMenu when historyItems are passed
const historyPopover = (
<EuiPopover
button={
<EuiButtonIcon iconType="arrowDown" color="text" aria-label="History" />
}
isOpen={isPopoverOpen}
onClick={handlePopoverButtonClick}
closePopover={() => setIsPopoverOpen(false)}
panelPaddingSize="xs"
anchorPosition="downLeft"
>
<EuiListGroup gutterSize="none">
{historyItems.map((item, index) => (
<EuiListGroupItem
key={index}
label={item.config.mainTitle}
size="s"
onClick={() => {
action(`Clicked ${item.config.mainTitle}`)();
setIsPopoverOpen(false);
}}
>
{item.config.mainTitle}
</EuiListGroupItem>
))}
</EuiListGroup>
</EuiPopover>
);
const historyItems = showHistoryItems
? ['First item', 'Second item', 'Third item'].map((title) => ({
title,
onClick: () => {
action('history item')(`${title} clicked`);
},
}))
: undefined;

const customActions = ['gear', 'broom'].map((iconType) => ({
iconType,
Expand All @@ -133,8 +97,9 @@ const MenuBarFlyout = (args: Args) => {
flyoutMenuProps={{
title: 'Flyout title',
hideCloseButton,
backButton: showBackButton ? backButton : undefined,
popover: showPopover ? historyPopover : undefined,
showBackButton,
backButtonProps,
historyItems,
customActions: showCustomActions ? customActions : undefined,
}}
>
Expand Down
104 changes: 92 additions & 12 deletions packages/eui/src/components/flyout/flyout_menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,107 @@
*/

import classNames from 'classnames';
import React, { FunctionComponent, HTMLAttributes, useContext } from 'react';
import React, {
FunctionComponent,
HTMLAttributes,
useContext,
useState,
} from 'react';

import { useEuiMemoizedStyles, useGeneratedHtmlId } from '../../services';
import { EuiButtonIcon } from '../button';
import { CommonProps } from '../common';
import { EuiButtonEmpty, EuiButtonIcon, EuiButtonProps } from '../button';
import { CommonProps, PropsForAnchor } from '../common';
import { EuiFlexGroup, EuiFlexItem } from '../flex';
import { EuiListGroup, EuiListGroupItem } from '../list_group';
import { EuiPopover } from '../popover';
import { EuiTitle } from '../title';
import { EuiFlyoutCloseButton } from './_flyout_close_button';
import { euiFlyoutMenuStyles } from './flyout_menu.styles';
import { EuiFlyoutMenuContext } from './flyout_menu_context';
import type { EuiFlyoutCloseEvent } from './types';
import { EuiI18n, useEuiI18n } from '../i18n';

type EuiFlyoutMenuBackButtonProps = Pick<
PropsForAnchor<EuiButtonProps>,
'aria-label' | 'data-test-subj' | 'onClick'
>;

type EuiFlyoutHistoryItem = {
title: string;
onClick: () => void;
};

export type EuiFlyoutMenuProps = CommonProps &
HTMLAttributes<HTMLDivElement> & {
backButton?: React.ReactNode;
popover?: React.ReactNode;
title?: React.ReactNode;
hideCloseButton?: boolean;
showBackButton?: boolean;
backButtonProps?: EuiFlyoutMenuBackButtonProps;
historyItems?: EuiFlyoutHistoryItem[];
customActions?: Array<{
iconType: string;
onClick: () => void;
'aria-label': string;
}>;
};

const BackButton: React.FC<EuiFlyoutMenuBackButtonProps> = (props) => {
return (
<EuiButtonEmpty size="xs" color="text" iconType="editorUndo" {...props}>
<EuiI18n token="euiFlyoutMenu.back" default="Back" />
</EuiButtonEmpty>
);
};

const HistoryPopover: React.FC<{
items: EuiFlyoutHistoryItem[];
}> = ({ items }) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const handlePopoverButtonClick = () => {
setIsPopoverOpen(!isPopoverOpen);
};

return (
<EuiPopover
button={
<EuiButtonIcon
iconType="arrowDown"
color="text"
aria-label={useEuiI18n('euiFlyoutMenu.history', 'History')}
/>
}
isOpen={isPopoverOpen}
onClick={handlePopoverButtonClick}
closePopover={() => setIsPopoverOpen(false)}
panelPaddingSize="xs"
anchorPosition="downLeft"
>
<EuiListGroup gutterSize="none">
{items.map((item, index) => (
<EuiListGroupItem
key={`history-item-${index}`}
label={item.title}
size="s"
onClick={() => {
item.onClick();
setIsPopoverOpen(false);
}}
>
{item.title}
</EuiListGroupItem>
))}
</EuiListGroup>
</EuiPopover>
);
};

export const EuiFlyoutMenu: FunctionComponent<EuiFlyoutMenuProps> = ({
className,
backButton,
popover,
title,
hideCloseButton,
historyItems = [],
showBackButton,
backButtonProps,
customActions,
...rest
}) => {
Expand All @@ -54,9 +126,7 @@ export const EuiFlyoutMenu: FunctionComponent<EuiFlyoutMenuProps> = ({
);
}

const handleClose = (
event: MouseEvent | TouchEvent | KeyboardEvent | undefined
) => {
const handleClose = (event: EuiFlyoutCloseEvent | undefined) => {
onClose?.(event);
};

Expand All @@ -76,8 +146,18 @@ export const EuiFlyoutMenu: FunctionComponent<EuiFlyoutMenuProps> = ({
gutterSize="none"
responsive={false}
>
{backButton && <EuiFlexItem grow={false}>{backButton}</EuiFlexItem>}
{popover && <EuiFlexItem grow={false}>{popover}</EuiFlexItem>}
{showBackButton && (
<EuiFlexItem grow={false}>
<BackButton {...backButtonProps} />
</EuiFlexItem>
)}

{historyItems.length > 0 && (
<EuiFlexItem grow={false}>
<HistoryPopover items={historyItems} />
</EuiFlexItem>
)}

{titleNode && <EuiFlexItem grow={false}>{titleNode}</EuiFlexItem>}

<EuiFlexItem grow={true}></EuiFlexItem>
Expand Down
Loading