Skip to content

Commit f1fa229

Browse files
committed
API CHANGE: session={'start' | 'never' | 'inherit'}
where inherit is the default value
1 parent a3384b5 commit f1fa229

8 files changed

Lines changed: 143 additions & 97 deletions

File tree

packages/eui/src/components/collapsible_nav/collapsible_nav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export const EuiCollapsibleNav: FunctionComponent<EuiCollapsibleNavProps> = ({
116116
const flyout = (
117117
<EuiFlyout
118118
id={flyoutID}
119-
session={false}
119+
session="never"
120120
css={cssStyles}
121121
className={classes}
122122
// Flyout props we set different defaults for

packages/eui/src/components/collapsible_nav_beta/collapsible_nav_beta.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ const _EuiCollapsibleNavBeta: FunctionComponent<EuiCollapsibleNavBetaProps> = ({
211211
aria-label={defaultAriaLabel}
212212
{...rest} // EuiCollapsibleNav is significantly less permissive than EuiFlyout
213213
id={flyoutID}
214-
session={false}
214+
session="never"
215215
css={cssStyles}
216216
className={classes}
217217
size={width}

packages/eui/src/components/flyout/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44

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

1212
#### `session` Prop Behavior
1313
The `session` prop controls whether a flyout participates in the session management system:
14-
- **`session={true}`**: Explicitly opt-in to session management. The flyout will be managed as a main flyout.
15-
- **`session={false}`**: Explicitly opt-out of session management. The flyout will render as an unmanaged standard flyout, bypassing all session logic. This is useful for wrapper components like `EuiCollapsibleNav` that manage their own lifecycle.
16-
- **`session={undefined}`** (default): Automatically participate in sessions if one is active. If no session is active, renders as a standard flyout.
14+
- **`session="start"`**: Explicitly opt-in to session management. The flyout will be managed as a main flyout and create a new session.
15+
- **`session="never"`**: Explicitly opt-out of session management. The flyout will render as an unmanaged standard flyout, bypassing all session logic. This is useful for wrapper components like `EuiCollapsibleNav` that manage their own lifecycle.
16+
- **`session="inherit"`** (default): Automatically participate in sessions if one is active. If no session is active, renders as a standard flyout.
1717

1818
### `src/components/flyout/flyout.component.tsx`
1919
The core flyout implementation with comprehensive functionality:
@@ -46,7 +46,7 @@ The central state management system for flyout sessions:
4646
- **Responsive Layout**: `useFlyoutLayoutMode` hook manages responsive behavior for managed flyouts with 90% viewport width rule for switching between `side-by-side` and `stacked` layouts
4747

4848
### `src/components/flyout/manager/flyout_main.tsx`
49-
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.
49+
Renders the primary flyout in a session. Currently a simple wrapper around `EuiManagedFlyout` with `session="start"`. TODO items include handling child flyout presence and adjusting focus/shadow behavior.
5050

5151
### `src/components/flyout/manager/flyout_child.tsx`
5252
Renders child flyouts within a session:

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

Lines changed: 89 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
FLYOUT_SIZES,
2020
} from './flyout';
2121
import { EuiProvider } from '../provider';
22+
import { EuiFlyoutManager } from './manager';
2223

2324
jest.mock('../overlay_mask', () => ({
2425
EuiOverlayMask: ({ headerZindexLocation, maskRef, ...props }: any) => (
@@ -439,114 +440,129 @@ describe('EuiFlyout', () => {
439440
});
440441

441442
describe('flyout routing logic', () => {
442-
// Mock the manager hooks to control routing behavior
443-
const mockUseHasActiveSession = jest.fn();
444-
const mockUseIsInManagedFlyout = jest.fn();
445-
446-
beforeEach(() => {
447-
jest.clearAllMocks();
448-
// Mock the manager hooks
449-
jest.doMock('./manager', () => ({
450-
...jest.requireActual('./manager'),
451-
useHasActiveSession: mockUseHasActiveSession,
452-
useIsInManagedFlyout: mockUseIsInManagedFlyout,
453-
}));
454-
});
455-
456-
afterEach(() => {
457-
jest.dontMock('./manager');
458-
});
459-
460443
it('routes to child flyout when session is undefined and there is an active session', () => {
461-
// Setup: There's an active session but flyout is not in managed context
462-
mockUseHasActiveSession.mockReturnValue(true);
463-
mockUseIsInManagedFlyout.mockReturnValue(false);
444+
// First render with just the main flyout to establish a session
445+
const { rerender, getByTestSubject } = render(
446+
<EuiFlyoutManager>
447+
<EuiFlyout
448+
onClose={() => {}}
449+
session="start"
450+
flyoutMenuProps={{ title: 'Main Flyout' }}
451+
data-test-subj="main-flyout"
452+
/>
453+
</EuiFlyoutManager>
454+
);
464455

465-
const { getByTestSubject } = render(
466-
<EuiFlyout
467-
onClose={() => {}}
468-
data-test-subj="child-flyout"
469-
// session is undefined (not explicitly set)
470-
/>
456+
// Now render with the child flyout added - it should detect the active session
457+
rerender(
458+
<EuiFlyoutManager>
459+
<EuiFlyout
460+
onClose={() => {}}
461+
session="start"
462+
flyoutMenuProps={{ title: 'Main Flyout' }}
463+
data-test-subj="main-flyout"
464+
/>
465+
<EuiFlyout
466+
onClose={() => {}}
467+
data-test-subj="child-flyout"
468+
// session is undefined (not explicitly set)
469+
/>
470+
</EuiFlyoutManager>
471471
);
472472

473473
// Should render as child flyout (EuiFlyoutChild)
474-
const flyout = getByTestSubject('child-flyout');
475-
expect(flyout).toBeInTheDocument();
474+
const childFlyout = getByTestSubject('child-flyout');
475+
expect(childFlyout).toHaveAttribute('data-managed-flyout-level', 'child');
476476
});
477477

478478
it('routes to main flyout when session is explicitly true', () => {
479-
// Setup: There's an active session and flyout is not in managed context
480-
mockUseHasActiveSession.mockReturnValue(true);
481-
mockUseIsInManagedFlyout.mockReturnValue(false);
482-
483479
const { getByTestSubject } = render(
484-
<EuiFlyout
485-
onClose={() => {}}
486-
data-test-subj="main-flyout"
487-
session={true} // Explicitly creating a new session
488-
flyoutMenuProps={{ title: 'Test Main Flyout' }} // Required for managed flyouts
489-
/>
480+
<EuiFlyoutManager>
481+
<EuiFlyout
482+
onClose={() => {}}
483+
data-test-subj="flyout"
484+
session="start" // Explicitly creating a new session
485+
flyoutMenuProps={{ title: 'Test Main Flyout' }} // Required for managed flyouts
486+
/>
487+
</EuiFlyoutManager>
490488
);
491489

492490
// Should render as main flyout (EuiFlyoutMain)
493-
const flyout = getByTestSubject('main-flyout');
494-
expect(flyout).toBeInTheDocument();
491+
const flyout = getByTestSubject('flyout');
492+
expect(flyout).toHaveAttribute('data-managed-flyout-level', 'main');
495493
});
496494

497-
it('routes to main flyout when session is explicitly false and there is an active session', () => {
498-
// Setup: There's an active session and flyout is not in managed context
499-
mockUseHasActiveSession.mockReturnValue(true);
500-
mockUseIsInManagedFlyout.mockReturnValue(false);
501-
495+
it('routes to standard flyout when session is explicitly "never" and there is an active session', () => {
502496
const { getByTestSubject } = render(
503-
<EuiFlyout
504-
onClose={() => {}}
505-
data-test-subj="main-flyout"
506-
session={false} // Explicitly not creating a new session, but still routes to main
507-
flyoutMenuProps={{ title: 'Test Main Flyout' }} // Required for managed flyouts
508-
/>
497+
<EuiFlyoutManager>
498+
{/* Create an active session */}
499+
<EuiFlyout
500+
onClose={() => {}}
501+
session="start"
502+
flyoutMenuProps={{ title: 'Main Flyout' }}
503+
data-test-subj="main-flyout"
504+
/>
505+
{/* This flyout explicitly opts out of session management */}
506+
<EuiFlyout
507+
onClose={() => {}}
508+
data-test-subj="standard-flyout"
509+
session="never" // Explicitly opts out of session management
510+
/>
511+
</EuiFlyoutManager>
509512
);
510513

511-
// Should render as main flyout (EuiFlyoutMain)
512-
const flyout = getByTestSubject('main-flyout');
513-
expect(flyout).toBeInTheDocument();
514+
// Should render as standard flyout (EuiFlyoutComponent)
515+
const flyout = getByTestSubject('standard-flyout');
516+
expect(flyout).not.toHaveAttribute('data-managed-flyout-level');
514517
});
515518

516519
it('routes to child flyout when in managed context and there is an active session', () => {
517-
// Setup: There's an active session and flyout is in managed context
518-
mockUseHasActiveSession.mockReturnValue(true);
519-
mockUseIsInManagedFlyout.mockReturnValue(true);
520+
// First render with just the main flyout to establish a session
521+
const { rerender, getByTestSubject } = render(
522+
<EuiFlyoutManager>
523+
<EuiFlyout
524+
onClose={() => {}}
525+
session="start"
526+
flyoutMenuProps={{ title: 'Main Flyout' }}
527+
data-test-subj="main-flyout"
528+
/>
529+
</EuiFlyoutManager>
530+
);
520531

521-
const { getByTestSubject } = render(
522-
<EuiFlyout
523-
onClose={() => {}}
524-
data-test-subj="child-flyout"
525-
session={undefined} // Not explicitly set
526-
/>
532+
// Now render with the child flyout added - it should detect the active session
533+
rerender(
534+
<EuiFlyoutManager>
535+
<EuiFlyout
536+
onClose={() => {}}
537+
session="start"
538+
flyoutMenuProps={{ title: 'Main Flyout' }}
539+
data-test-subj="main-flyout"
540+
/>
541+
<EuiFlyout
542+
onClose={() => {}}
543+
data-test-subj="child-flyout"
544+
session={undefined} // Not explicitly set, should inherit
545+
/>
546+
</EuiFlyoutManager>
527547
);
528548

529549
// Should render as child flyout (EuiFlyoutChild)
530550
const flyout = getByTestSubject('child-flyout');
531-
expect(flyout).toBeInTheDocument();
551+
expect(flyout).toHaveAttribute('data-managed-flyout-level', 'child');
532552
});
533553

534554
it('routes to standard flyout when there is no active session', () => {
535-
// Setup: No active session
536-
mockUseHasActiveSession.mockReturnValue(false);
537-
mockUseIsInManagedFlyout.mockReturnValue(false);
538-
539555
const { getByTestSubject } = render(
540556
<EuiFlyout
541557
onClose={() => {}}
542-
data-test-subj="standard-flyout"
558+
data-test-subj="flyout"
543559
session={undefined} // Not explicitly set
544560
/>
545561
);
546562

547-
// Should render as standard flyout (EuiFlyoutComponent)
548-
const flyout = getByTestSubject('standard-flyout');
549-
expect(flyout).toBeInTheDocument();
563+
// Should render as standard flyout (EuiFlyoutComponent) - no manager context
564+
const flyout = getByTestSubject('flyout');
565+
expect(flyout).not.toHaveAttribute('data-managed-flyout-level');
550566
});
551567
});
552568
});

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

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ export type EuiFlyoutProps<T extends ElementType = 'div' | 'nav'> = Omit<
3434
EuiFlyoutComponentProps<T>,
3535
'as'
3636
> & {
37-
session?: boolean;
37+
/**
38+
* Controls the way the session is managed for this flyout.
39+
* - `start`: Creates a new flyout session. Use this for the main flyout.
40+
* - `inherit`: (default) Inherits an existing session if one is active, otherwise functions as a standard flyout.
41+
* - `never`: Opts out of session management and always functions as a standard flyout.
42+
* @default 'inherit'
43+
*/
44+
session?: 'start' | 'inherit' | 'never';
3845
onActive?: () => void;
3946
as?: T;
4047
};
@@ -43,20 +50,26 @@ export const EuiFlyout = forwardRef<
4350
HTMLDivElement | HTMLElement,
4451
EuiFlyoutProps<'div' | 'nav'>
4552
>((props, ref) => {
46-
const { session, as, onClose, onActive, ...rest } =
47-
usePropsWithComponentDefaults('EuiFlyout', props);
53+
const {
54+
as,
55+
onClose,
56+
onActive,
57+
session = 'inherit',
58+
...rest
59+
} = usePropsWithComponentDefaults('EuiFlyout', props);
4860
const hasActiveSession = useRef(useHasActiveSession());
4961
const isUnmanagedFlyout = useRef(false);
5062

5163
/*
5264
* Flyout routing logic:
53-
* - session={true} → Main flyout (creates new session)
54-
* - session={undefined} + active session → Child flyout (auto-joins, works across React roots!)
55-
* - session={undefined} + no session → Standard flyout
56-
* - session={false} → Standard flyout (explicit opt-out)
65+
* - session="start" → Main flyout (creates new session)
66+
* - session="inherit" + active session → Child flyout (auto-joins, works across React roots!)
67+
* - session="inherit" + no session → Standard flyout
68+
* - session="never" → Standard flyout (explicit opt-out)
5769
*/
58-
if (session !== false) {
59-
if (session === true) {
70+
if (session !== 'never') {
71+
if (session === 'start') {
72+
// session=start: create new session
6073
if (isUnmanagedFlyout.current) {
6174
// TODO: @tkajtoch - We need to find a better way to handle the missing event.
6275
onClose?.({} as any);
@@ -72,8 +85,11 @@ export const EuiFlyout = forwardRef<
7285
);
7386
}
7487

75-
// Auto-join existing session as child
76-
if (hasActiveSession.current && session === undefined) {
88+
// session=inherit: auto-join existing session as child
89+
if (
90+
hasActiveSession.current &&
91+
(session === undefined || session === 'inherit')
92+
) {
7793
return (
7894
<EuiFlyoutChild
7995
{...rest}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ describe('EuiFlyoutChild', () => {
128128
{isMainOpen && (
129129
<EuiFlyout
130130
id="main-flyout"
131-
session={true}
131+
session="start"
132132
aria-label="Main flyout"
133133
size="m"
134134
onClose={() => {}}

packages/eui/src/components/flyout/manager/flyout_manager.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ const StatefulFlyout: React.FC<FlyoutChildStoryArgs> = ({
219219

220220
{isMainOpen && (
221221
<EuiFlyout
222-
session={true}
222+
session="start"
223223
id="flyout-manager-playground-main"
224224
size={mainSize}
225225
type={mainFlyoutType}

0 commit comments

Comments
 (0)