Skip to content
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
5 changes: 5 additions & 0 deletions packages/eui/changelogs/upcoming/8846.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Updates to `EuiFlyoutSessionProvider`
* Remove the onUnmount callbacks from various flyout configurations
* Consolidate unmount behavior with a single onUnmount prop at the provider level.
* Removed onCloseFlyout and onCloseChildFlyout from the flyout render context.
* Fixed the canGoBack logic in packages/eui/src/components/flyout/sessions/use_eui_flyout.ts.
1 change: 1 addition & 0 deletions packages/eui/src/components/flyout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ export type {
EuiFlyoutSessionOpenMainOptions,
EuiFlyoutSessionProviderComponentProps,
EuiFlyoutSessionRenderContext,
EuiFlyoutSessionApi,
} from './sessions';
export { EuiFlyoutSessionProvider, useEuiFlyoutSession } from './sessions';
35 changes: 21 additions & 14 deletions packages/eui/src/components/flyout/sessions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,19 @@ The `EuiFlyoutSessionProvider` is the core stateful component. You must wrap it
The `flyoutContext` object passed to your render prop functions is of type `EuiFlyoutSessionRenderContext<MetaType>` and contains the following useful properties:

* **`meta`**: `MetaType` - The arbitrary data object you passed into the `meta` property when calling `openFlyout` or `openChildFlyout`. This is a generic, allowing you to have type safety for your custom data.
* **`onCloseFlyout`**: `() => void` - A callback function that closes the current main flyout.
* **`onCloseChildFlyout`**: `() => void` - A callback function that closes the current child flyout.
* **`flyoutSize`**: The size of the currently active flyout.
* **`flyoutType`**: `'main' | 'child'` - Indicates which flyout the render prop is being called for.
* **`activeFlyoutGroup`**: `EuiFlyoutSessionGroup` - The currently active flyout group.

## `useEuiFlyoutSession` Hook API

The `useEuiFlyoutSession` hook is generic and can be typed to match the `meta` object you are working with (e.g., `useEuiFlyoutSession<MyMetaType>()`).

### Methods

* `openFlyout(options)`: Opens a new main flyout. If a flyout is already open, it adds the new one to a history stack.
* `openChildFlyout(options)`: Opens a new child flyout to the left of the main flyout.
* `openFlyoutGroup(options)`: Opens a group containing a main flyout and a child flyout.
* `closeFlyout()`: Closes the currently open main flyout. If there's a previous flyout in the history stack, it will be shown.
* `openFlyout(options: EuiFlyoutSessionOpenMainOptions<MetaType>)`: Opens a new main flyout. If a flyout is already open, it adds the new one to a history stack.
* `openChildFlyout(options: EuiFlyoutSessionOpenChildOptions<MetaType>)`: Opens a new child flyout to the left of the main flyout.
* `openFlyoutGroup(options: EuiFlyoutSessionOpenGroupOptions<MetaType>)`: Opens a group containing a main flyout and a child flyout.
* `closeChildFlyout()`: Closes the currently open child flyout.
* `goBack()`: If there's a previous flyout in the history stack, it will be shown.
* `clearHistory()`: Closes all flyouts by clearing the history stack of all flyouts in the session.

### State Values
Expand All @@ -55,6 +52,8 @@ import {
EuiButton,
EuiFlyoutBody,
EuiText,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutSessionProvider,
useEuiFlyoutSession,
} from '@elastic/eui';
Expand All @@ -67,6 +66,7 @@ const FlyoutAppControls: React.FC = () => {
// will add the new flyout to the history stack.
openFlyout({
size: 'm',
meta: { title: 'My Flyout' },
});
};

Expand All @@ -81,12 +81,19 @@ const FlyoutApp: React.FC = () => {
// The EuiFlyoutSessionRenderContext is passed to your render prop functions.
// This can contain a custom `meta` object (set in the `openFlyout` function call)
// which allows you to customize the content shown in the flyout.
const renderMainFlyoutContent = (context: EuiFlyoutSessionRenderContext) => (
<EuiFlyoutBody>
<EuiText>
<p>Simple flyout content</p>
</EuiText>
</EuiFlyoutBody>
const renderMainFlyoutContent = (flyoutContext: EuiFlyoutSessionRenderContext<{ title: string }>) => (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h2>{flyoutContext.meta.title}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiText>
<p>Simple flyout content</p>
</EuiText>
</EuiFlyoutBody>
</>
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ const ShoppingCartContent: React.FC<ShoppingCartContentProps> = ({
closeChildFlyout(); // If we add an onClose handler to the child flyout, we have to call closeChildFlyout within it for the flyout to actually close
},
},
onUnmount: () => console.log('Unmounted item details child flyout'),
};
openChildFlyout(options);
};
Expand All @@ -98,8 +97,6 @@ const ShoppingCartContent: React.FC<ShoppingCartContentProps> = ({
...config?.mainFlyoutProps,
'aria-label': 'Review order',
},
onUnmount: () =>
console.log(`Unmounted review order flyout (${reviewFlyoutSize})`),
};
openFlyout(options);
};
Expand Down Expand Up @@ -258,6 +255,13 @@ const WithHistoryAppControls: React.FC = () => {
clearHistory,
} = useEuiFlyoutSession();

const handleCloseOrGoBack = () => {
if (canGoBack) {
goBack();
} else {
clearHistory();
}
};
const handleOpenShoppingCart = () => {
const options: EuiFlyoutSessionOpenMainOptions<WithHistoryAppMeta> = {
size: 'm',
Expand All @@ -272,7 +276,6 @@ const WithHistoryAppControls: React.FC = () => {
clearHistory(); // If we add an onClose handler to the main flyout, we have to call clearHistory within it for the flyout to actually close
},
},
onUnmount: () => console.log('Unmounted shopping cart flyout'),
};
openFlyout(options);
};
Expand All @@ -297,7 +300,11 @@ const WithHistoryAppControls: React.FC = () => {
Close child flyout
</EuiButton>
<EuiSpacer size="s" />
<EuiButton onClick={goBack} isDisabled={!canGoBack} color="warning">
<EuiButton
onClick={handleCloseOrGoBack}
isDisabled={!isFlyoutOpen}
color="warning"
>
Close/Go back
</EuiButton>
<EuiSpacer size="s" />
Expand Down Expand Up @@ -351,6 +358,7 @@ const WithHistoryApp: React.FC = () => {
<EuiFlyoutSessionProvider
renderMainFlyoutContent={renderMainFlyoutContent}
renderChildFlyoutContent={renderChildFlyoutContent}
onUnmount={() => console.log('All flyouts have been unmounted')}
Copy link
Contributor

Choose a reason for hiding this comment

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

ℹ️ We rather use the storybook action() to log as it shows up in the actions control then instead of the console. (example)

>
<WithHistoryAppControls />
</EuiFlyoutSessionProvider>
Expand Down Expand Up @@ -386,15 +394,13 @@ const GroupOpenerControls: React.FC = () => {
className: 'groupOpenerMainFlyout',
'aria-label': 'Main flyout',
},
onUnmount: () => console.log('Unmounted main flyout'),
},
child: {
size: 's',
flyoutProps: {
className: 'groupOpenerChildFlyout',
'aria-label': 'Child flyout',
},
onUnmount: () => console.log('Unmounted child flyout'),
},
};
openFlyoutGroup(options);
Expand Down
32 changes: 11 additions & 21 deletions packages/eui/src/components/flyout/sessions/flyout_provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
interface FlyoutSessionContextProps {
state: EuiFlyoutSessionHistoryState;
dispatch: React.Dispatch<EuiFlyoutSessionAction>;
onUnmount?: EuiFlyoutSessionProviderComponentProps['onUnmount'];
}

const EuiFlyoutSessionContext = createContext<FlyoutSessionContextProps | null>(
Expand Down Expand Up @@ -53,7 +54,12 @@ export const useEuiFlyoutSessionContext = () => {
*/
export const EuiFlyoutSessionProvider: React.FC<
EuiFlyoutSessionProviderComponentProps
> = ({ children, renderMainFlyoutContent, renderChildFlyoutContent }) => {
> = ({
children,
renderMainFlyoutContent,
renderChildFlyoutContent,
onUnmount,
}) => {
const [state, dispatch] = useReducer(flyoutReducer, initialFlyoutState);
const { activeFlyoutGroup } = state;

Expand All @@ -69,30 +75,14 @@ export const EuiFlyoutSessionProvider: React.FC<
let childFlyoutContentNode: React.ReactNode = null;

if (activeFlyoutGroup) {
const mainRenderContext: EuiFlyoutSessionRenderContext = {
flyoutProps: activeFlyoutGroup.config.mainFlyoutProps || {},
flyoutSize: activeFlyoutGroup.config.mainSize,
flyoutType: 'main',
dispatch,
const renderContext: EuiFlyoutSessionRenderContext = {
activeFlyoutGroup,
onCloseFlyout: handleClose,
onCloseChildFlyout: handleCloseChild,
meta: activeFlyoutGroup.meta,
};
mainFlyoutContentNode = renderMainFlyoutContent(mainRenderContext);
mainFlyoutContentNode = renderMainFlyoutContent(renderContext);

if (activeFlyoutGroup.isChildOpen && renderChildFlyoutContent) {
const childRenderContext: EuiFlyoutSessionRenderContext = {
flyoutProps: activeFlyoutGroup.config.childFlyoutProps || {},
flyoutSize: activeFlyoutGroup.config.childSize,
flyoutType: 'child',
dispatch,
activeFlyoutGroup,
onCloseFlyout: handleClose,
onCloseChildFlyout: handleCloseChild,
meta: activeFlyoutGroup.meta,
};
childFlyoutContentNode = renderChildFlyoutContent(childRenderContext);
childFlyoutContentNode = renderChildFlyoutContent(renderContext);
} else if (activeFlyoutGroup.isChildOpen && !renderChildFlyoutContent) {
console.warn(
'EuiFlyoutSessionProvider: A child flyout is open, but renderChildFlyoutContent was not provided.'
Expand All @@ -105,7 +95,7 @@ export const EuiFlyoutSessionProvider: React.FC<
const flyoutPropsChild = config?.childFlyoutProps || {};

return (
<EuiFlyoutSessionContext.Provider value={{ state, dispatch }}>
<EuiFlyoutSessionContext.Provider value={{ state, dispatch, onUnmount }}>
{children}
{activeFlyoutGroup?.isMainOpen && (
<EuiFlyout
Expand Down
29 changes: 3 additions & 26 deletions packages/eui/src/components/flyout/sessions/flyout_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function flyoutReducer<FlyoutMeta>(
): EuiFlyoutSessionHistoryState<FlyoutMeta> {
switch (action.type) {
case 'OPEN_MAIN_FLYOUT': {
const { size, flyoutProps, onUnmount } = action.payload;
const { size, flyoutProps } = action.payload;
const newHistory = [...state.history];

if (state.activeFlyoutGroup) {
Expand All @@ -80,8 +80,6 @@ export function flyoutReducer<FlyoutMeta>(
mainFlyoutProps: flyoutProps,
childFlyoutProps: {},
},
mainOnUnmount: onUnmount,
childOnUnmount: undefined,
meta:
action.payload.meta !== undefined
? state.activeFlyoutGroup?.meta !== undefined
Expand All @@ -104,7 +102,7 @@ export function flyoutReducer<FlyoutMeta>(
return state;
}

const { size, flyoutProps, onUnmount } = action.payload;
const { size, flyoutProps } = action.payload;
const updatedActiveGroup: EuiFlyoutSessionGroup<FlyoutMeta> = {
...state.activeFlyoutGroup,
isChildOpen: true,
Expand All @@ -113,7 +111,6 @@ export function flyoutReducer<FlyoutMeta>(
childSize: size,
childFlyoutProps: flyoutProps,
},
childOnUnmount: onUnmount,
meta:
action.payload.meta !== undefined
? state.activeFlyoutGroup.meta !== undefined
Expand Down Expand Up @@ -146,8 +143,6 @@ export function flyoutReducer<FlyoutMeta>(
mainFlyoutProps: main.flyoutProps,
childFlyoutProps: child.flyoutProps,
},
mainOnUnmount: main.onUnmount,
childOnUnmount: child.onUnmount,
meta,
};

Expand All @@ -165,16 +160,13 @@ export function flyoutReducer<FlyoutMeta>(
return state;
}

state.activeFlyoutGroup.childOnUnmount?.();

const updatedActiveGroup: EuiFlyoutSessionGroup<FlyoutMeta> = {
...state.activeFlyoutGroup,
isChildOpen: false,
config: {
...state.activeFlyoutGroup.config,
childFlyoutProps: {},
},
childOnUnmount: undefined,
};

return {
Expand All @@ -187,12 +179,6 @@ export function flyoutReducer<FlyoutMeta>(
if (!state.activeFlyoutGroup)
return initialFlyoutState as EuiFlyoutSessionHistoryState<FlyoutMeta>;

if (state.activeFlyoutGroup.isChildOpen) {
state.activeFlyoutGroup.childOnUnmount?.();
}

state.activeFlyoutGroup.mainOnUnmount?.();

// Restore from history or return to initial state
if (state.history.length > 0) {
const newHistory = [...state.history];
Expand All @@ -214,23 +200,14 @@ export function flyoutReducer<FlyoutMeta>(
return state;
}

const { configChanges, newMainOnUnmount, newChildOnUnmount } =
action.payload;
const { configChanges } = action.payload;

const updatedActiveGroup: EuiFlyoutSessionGroup<FlyoutMeta> = {
...state.activeFlyoutGroup,
config: {
...state.activeFlyoutGroup.config,
...configChanges,
},
mainOnUnmount:
newMainOnUnmount !== undefined
? newMainOnUnmount
: state.activeFlyoutGroup.mainOnUnmount,
childOnUnmount:
newChildOnUnmount !== undefined
? newChildOnUnmount
: state.activeFlyoutGroup.childOnUnmount,
};

const finalUpdatedActiveGroup = applySizeConstraints(updatedActiveGroup);
Expand Down
1 change: 1 addition & 0 deletions packages/eui/src/components/flyout/sessions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export {
useEuiFlyoutSession,
type EuiFlyoutSessionOpenChildOptions,
type EuiFlyoutSessionOpenMainOptions,
type EuiFlyoutSessionApi,
} from './use_eui_flyout';
15 changes: 1 addition & 14 deletions packages/eui/src/components/flyout/sessions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ export interface EuiFlyoutSessionGroup<FlyoutMeta> {
isMainOpen: boolean;
isChildOpen: boolean;
config: EuiFlyoutSessionConfig;
mainOnUnmount?: () => void;
childOnUnmount?: () => void;
meta?: FlyoutMeta;
}

Expand All @@ -46,16 +44,13 @@ export type EuiFlyoutSessionAction<FlyoutMeta = unknown> =
type: 'UPDATE_ACTIVE_FLYOUT_CONFIG';
payload: {
configChanges: Partial<EuiFlyoutSessionConfig>;
newMainOnUnmount?: () => void;
newChildOnUnmount?: () => void;
};
}
| {
type: 'OPEN_MAIN_FLYOUT';
payload: {
size: EuiFlyoutSize;
flyoutProps?: Partial<Omit<EuiFlyoutProps, 'children'>>;
onUnmount?: () => void;
meta?: FlyoutMeta;
};
}
Expand All @@ -64,7 +59,6 @@ export type EuiFlyoutSessionAction<FlyoutMeta = unknown> =
payload: {
size: 's' | 'm';
flyoutProps?: Partial<Omit<EuiFlyoutChildProps, 'children'>>;
onUnmount?: () => void;
meta?: FlyoutMeta;
};
}
Expand All @@ -74,12 +68,10 @@ export type EuiFlyoutSessionAction<FlyoutMeta = unknown> =
main: {
size: EuiFlyoutSize;
flyoutProps?: Partial<Omit<EuiFlyoutProps, 'children'>>;
onUnmount?: () => void;
};
child: {
size: 's' | 'm';
flyoutProps?: Partial<Omit<EuiFlyoutChildProps, 'children'>>;
onUnmount?: () => void;
};
meta?: FlyoutMeta;
};
Expand All @@ -92,13 +84,7 @@ export type EuiFlyoutSessionAction<FlyoutMeta = unknown> =
* Flyout session context managed by `EuiFlyoutSessionProvider`, and passed to the `renderMainFlyoutContent` and `renderChildFlyoutContent` functions.
*/
export interface EuiFlyoutSessionRenderContext<FlyoutMeta = unknown> {
flyoutProps: Partial<EuiFlyoutProps | EuiFlyoutChildProps>;
flyoutSize: EuiFlyoutProps['size'] | EuiFlyoutChildProps['size'];
flyoutType: 'main' | 'child';
dispatch: React.Dispatch<EuiFlyoutSessionAction<FlyoutMeta>>;
activeFlyoutGroup: EuiFlyoutSessionGroup<FlyoutMeta> | null;
onCloseFlyout: () => void;
onCloseChildFlyout: () => void;
meta?: FlyoutMeta;
}

Expand All @@ -107,6 +93,7 @@ export interface EuiFlyoutSessionRenderContext<FlyoutMeta = unknown> {
*/
export interface EuiFlyoutSessionProviderComponentProps<FlyoutMeta = any> {
children: React.ReactNode;
onUnmount?: () => void;
renderMainFlyoutContent: (
context: EuiFlyoutSessionRenderContext<FlyoutMeta>
) => React.ReactNode;
Expand Down
Loading