Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ yarn-error.log*
!.yarn/sdks
!.yarn/versions
yarn-error.log
.cursorrules
WARP.md
1 change: 1 addition & 0 deletions packages/eui/changelogs/upcoming/8851.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds a new `EuiFlyoutMenu` component that provides a standardized top menu bar for flyouts.
1 change: 1 addition & 0 deletions packages/eui/changelogs/upcoming/8897.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Flyout system menu bar: require tile, support custom actions
1 change: 1 addition & 0 deletions packages/eui/changelogs/upcoming/9003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Flyout System] Added back button, history stack managed history controls. Added history navigation events.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would more alined with how changelogs are written in EUI, but feel free to take it or not. I think the important bit is having at least 1 bullet point and start with either "Added" or "Updated", while also mentioning the public-facing component affected:

Suggested change
[Flyout System] Added back button, history stack managed history controls. Added history navigation events.
- Updated `EuiFlyout` with new `onActive` callback and enable stack managed history controls.
- Updated `EuiFlyoutMenu` with new prop `historyItems` and refactored props for back button.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { createContext, MouseEventHandler } from 'react';

import { _EuiFlyoutSide } from '../flyout/flyout';
import { _EuiFlyoutSide } from '../flyout/const';

type _EuiCollapsibleNavContext = {
isCollapsed: boolean;
Expand Down
175 changes: 175 additions & 0 deletions packages/eui/src/components/flyout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# EUI Flyout System

## Core Flyout Components

### `src/components/flyout/flyout.tsx`
The main flyout component that serves as the entry point for all flyout functionality. It intelligently renders different flyout types based on context:
- **Session flyouts**: When `session={true}` or within an active session, renders `EuiFlyoutMain`
- **Child flyouts**: When within a managed flyout context, renders `EuiFlyoutChild`
- **Standard flyouts**: Default behavior renders `EuiFlyoutComponent`
- **Resizable flyouts**: `EuiFlyoutResizable` component exists but is not integrated into main routing logic

### `src/components/flyout/flyout.component.tsx`
The core flyout implementation with comprehensive functionality:
- **Props**: Extensive configuration options including size, padding, positioning, focus management
- **Types**: Support for `push` and `overlay` types, left/right sides, various sizes (s/m/l)
- **Accessibility**: Built-in screen reader support, focus trapping, keyboard navigation with sophisticated ESC key handling
- **Styling**: Dynamic width handling, responsive behavior, theme integration
- **Portal/Overlay**: Conditional portal rendering and overlay mask management
- **Session Logic**: Complex routing logic that determines flyout type based on session state and managed context
- **Responsive Behavior**: Adaptive layout switching for managed flyouts based on viewport width and flyout size combinations

### `src/components/flyout/flyout.styles.ts`
Contains the emotion-based styling for the flyout component, including:
- Base flyout styles
- Size-specific styles (s/m/l)
- Padding size variations
- Push vs overlay type styles
- Side-specific positioning (left/right)
- Animation and transition styles

## Flyout Management System

### `src/components/flyout/manager/flyout_manager.tsx`
The central state management system for flyout sessions:
- **Context Provider**: `EuiFlyoutManager` provides global flyout state
- **Session Management**: Tracks main and child flyout relationships with complex state transitions
- **State Reducer**: Handles flyout lifecycle (add, close, set active, set width)
- **Hooks**: Provides utilities like `useHasActiveSession`, `useCurrentSession`, `useFlyoutWidth`
- **Actions**: `addFlyout`, `closeFlyout`, `setActiveFlyout`, `setFlyoutWidth`
- **Responsive Layout**: `useFlyoutLayoutMode` hook manages responsive behavior for managed flyouts with 90% viewport width rule for switching between `side-by-side` and `stacked` layouts

### `src/components/flyout/manager/flyout_main.tsx`
Renders the primary flyout in a session. Currently a simple wrapper around `EuiManagedFlyout` with `session={true}`. TODO items include handling child flyout presence and adjusting focus/shadow behavior.

### `src/components/flyout/manager/flyout_child.tsx`
Renders child flyouts within a session:
- **Positioning**: Automatically positions relative to main flyout width
- **Styling**: Supports `backgroundStyle` prop for default/shaded backgrounds
- **Constraints**: Forces `type="overlay"` and `ownFocus={false}`
- **Width Integration**: Uses main flyout width for positioning

### `src/components/flyout/manager/flyout_managed.tsx`
The managed flyout wrapper that integrates with the flyout manager system, handling registration and lifecycle management. Includes validation of props for managed flyouts.

### `src/components/flyout/manager/flyout_validation.ts`
Validation utilities for managed flyout props:
- **Named Size Validation**: Managed flyouts must use named sizes (s, m, l)
- **Size Combination Rules**: Parent and child can't both be 'm', parent can't be 'l' with child
- **Title**: Must be provided either through `flyoutMenuProps` or `aria-label`
- **Error Handling**: Comprehensive error messages for invalid configurations

### `src/components/flyout/manager/index.ts`
Exports all manager-related components and utilities for easy importing.

## Specialized Flyout Components

### `src/components/flyout/flyout_resizable.tsx`
A resizable flyout variant that adds drag-to-resize functionality:
- **Drag Resize**: Mouse/touch drag to resize flyout width
- **Keyboard Resize**: Arrow key navigation for accessibility
- **Constraints**: Configurable min/max width with window bounds checking
- **Callbacks**: `onResize` callback for width change notifications
- **Visual Indicator**: Resize handle with border indicator
- **Note**: Not yet integrated into main flyout routing logic

### `src/components/flyout/flyout_menu.tsx`
A specialized flyout component for menu-style content:
- **Layout**: Flex-based header with back button, popover, title, and close button
- **Context Integration**: Uses `EuiFlyoutMenuContext` for close handling
- **Accessibility**: Proper ARIA labels and screen reader support
- **Styling**: Custom menu-specific styling via `flyout_menu.styles.ts`

### `src/components/flyout/flyout_menu_context.ts`
React context for flyout menu components, providing `onClose` callback to child components.

## Styling and Theming

### `src/components/flyout/flyout.styles.ts`
Core flyout styling with emotion CSS-in-JS:
- Responsive design patterns
- Theme variable integration
- Animation and transition styles
- Size and positioning utilities

### `src/components/flyout/flyout_menu.styles.ts`
Menu-specific styling for the flyout menu component.

### `src/components/flyout/manager/flyout.styles.ts`
Managed flyout styling, including background styles for child flyouts.

## Testing and Documentation

### `src/components/flyout/flyout.spec.tsx`
Unit tests for the main flyout component functionality.

### `src/components/flyout/flyout.test.tsx`
Additional test coverage for flyout behavior and edge cases.

### `src/components/flyout/flyout_menu.stories.tsx`
Storybook stories demonstrating flyout menu usage and variations.

### `src/components/flyout/manager/flyout_manager.stories.tsx`
Storybook stories for the flyout manager system and session management.

### `src/components/flyout/manager/flyout_child.stories.tsx`
Storybook stories showcasing child flyout behavior and positioning.

## Integration

### `src/components/flyout/index.ts`
Main export file that exposes all public flyout APIs:
- Core components: `EuiFlyout`, `EuiFlyoutComponent`
- Body/Header/Footer components
- Resizable and menu variants
- Animation utilities

### `src/components/provider/provider.tsx`
The EUI provider that includes `EuiFlyoutManager` in its component tree, ensuring flyout management is available throughout the application.

## Key Features

- **Session Management**: Multi-level flyout sessions with main/child relationships
- **Accessibility**: Full keyboard navigation, screen reader support, focus management with sophisticated ESC key handling
- **Responsive Design**: Adaptive behavior based on screen size and breakpoints with intelligent layout switching for managed flyouts (side-by-side vs stacked) when combined flyout widths exceed 90% of viewport
- **Theme Integration**: Seamless integration with EUI's theming system
- **Type Safety**: Comprehensive TypeScript support with proper prop typing and validation
- **Performance**: Optimized rendering with proper cleanup and memory management
- **Size Validation**: Business rule enforcement for flyout size combinations and managed flyout constraints

## TODOs

### Performance Issues

- **Excessive Re-renders**: The flyout manager reducer creates new arrays on every action, causing unnecessary re-renders for all flyout components
- **Unmemoized Style Calculations**: The `cssStyles` array in `flyout.component.tsx` is recalculated on every render without memoization
- **Memory Leaks**: `document.activeElement` is stored in a ref but never cleaned up, potentially causing memory leaks
- **Inefficient DOM Queries**: Focus trap selectors query the DOM on every render without caching

### Accessibility Issues

- **Focus Trap Edge Cases**: The focus trap logic with shards could fail if DOM elements are removed or changed during flyout lifecycle
- **Missing Error Recovery**: No fallback behavior when focus management fails
- **Inconsistent Keyboard Navigation**: Different flyout types may have different keyboard behavior patterns

### Architectural Concerns

- **Tight Coupling**: The flyout system is tightly coupled to the provider system, making it difficult to use standalone
- **State Management Complexity**: The session management system has complex state transitions that could lead to inconsistent UI states
- **Missing Error Boundaries**: No error handling for flyout rendering failures or state corruption
- **Unclear Session Logic**: The complex session routing logic in `flyout.tsx` (lines 40-50) is difficult to understand and maintain
- **Incomplete Integration**: Resizable flyout functionality exists but is not integrated into main routing logic
- **Missing Cleanup**: Focus references and event listeners are not properly cleaned up

### Recommended Improvements

1. **Memoize Style Calculations**: Use `useMemo` for the `cssStyles` array to prevent unnecessary recalculations
2. **Add Error Boundaries**: Wrap flyout components in error boundaries to handle rendering failures gracefully
3. **Improve Type Safety**: Replace `any` types with proper type guards and add comprehensive prop validation
4. **Optimize State Updates**: Use immutable update patterns that minimize re-renders in the manager
5. **Add Cleanup Logic**: Properly clean up focus references and event listeners in useEffect cleanup functions
6. **Simplify Session Logic**: Break down the complex session routing logic into smaller, testable functions
7. **Integrate Resizable Flyouts**: Complete the integration of resizable flyout functionality into the main routing logic
8. **Add Comprehensive Testing**: Add unit tests for complex state transitions and edge cases
9. **Improve Documentation**: Add inline documentation for complex logic and state management patterns
10. **Performance Monitoring**: Add performance monitoring for flyout rendering and state updates
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,61 @@ exports[`EuiFlyout props size accepts custom number 1`] = `
</body>
`;

exports[`EuiFlyout props size fill is rendered 1`] = `
<body
class="euiBody--hasFlyout"
style=""
>
<div>
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
data-focus-lock-disabled="false"
>
<div
aria-describedby="generated-id"
aria-modal="true"
class="euiFlyout emotion-euiFlyout-l-fill-noMaxWidth-overlay-right-right"
data-autofocus="true"
role="dialog"
tabindex="0"
>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id"
>
You are in a modal dialog. Press Escape or tap/click outside the dialog on the shadowed overlay to close.

</p>
<button
aria-label="Close this dialog"
class="euiButtonIcon euiFlyout__closeButton emotion-euiButtonIcon-xs-empty-text-euiFlyout__closeButton-inside"
data-test-subj="euiFlyoutCloseButton"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
</div>
</body>
`;

exports[`EuiFlyout props size l is rendered 1`] = `
<body
class="euiBody--hasFlyout"
Expand Down
48 changes: 48 additions & 0 deletions packages/eui/src/components/flyout/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { EuiBreakpointSize } from '../../services';

/** Allowed flyout render types. */
export const FLYOUT_TYPES = ['push', 'overlay'] as const;
/** Type representing a supported flyout render type. */
export type _EuiFlyoutType = (typeof FLYOUT_TYPES)[number];

/** Allowed flyout attachment sides. */
export const FLYOUT_SIDES = ['left', 'right'] as const;
/** Type representing a supported flyout side. */
export type _EuiFlyoutSide = (typeof FLYOUT_SIDES)[number];

/** Allowed named flyout sizes used by the manager. */
export const FLYOUT_SIZES = ['s', 'm', 'l', 'fill'] as const;
/** Type representing a supported named flyout size. */
export type EuiFlyoutSize = (typeof FLYOUT_SIZES)[number];

/** Allowed padding sizes for flyout content. */
export const FLYOUT_PADDING_SIZES = ['none', 's', 'm', 'l'] as const;
/** Type representing a supported flyout padding size. */
export type _EuiFlyoutPaddingSize = (typeof FLYOUT_PADDING_SIZES)[number];

/** Default minimum breakpoint at which push-type flyouts begin to push content. */
export const DEFAULT_PUSH_MIN_BREAKPOINT: EuiBreakpointSize = 'l';
/** Default flyout type when none is provided. */
export const DEFAULT_TYPE: _EuiFlyoutType = 'overlay';
/** Default side where flyouts anchor when none is provided. */
export const DEFAULT_SIDE: _EuiFlyoutSide = 'right';
/** Default named flyout size. */
export const DEFAULT_SIZE: EuiFlyoutSize = 'm';
/** Default padding size inside flyouts. */
export const DEFAULT_PADDING_SIZE: _EuiFlyoutPaddingSize = 'l';

/**
* Custom type checker for named flyout sizes since the prop
* `size` can also be CSSProperties['width'] (string | number)
*/
export function isEuiFlyoutSizeNamed(value: unknown): value is EuiFlyoutSize {
return FLYOUT_SIZES.includes(value as EuiFlyoutSize);
}
Loading