diff --git a/packages/eui/src/components/collapsible_nav/__snapshots__/collapsible_nav.test.tsx.snap b/packages/eui/src/components/collapsible_nav/__snapshots__/collapsible_nav.test.tsx.snap index d817e32f254..6d1052f9d55 100644 --- a/packages/eui/src/components/collapsible_nav/__snapshots__/collapsible_nav.test.tsx.snap +++ b/packages/eui/src/components/collapsible_nav/__snapshots__/collapsible_nav.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiCollapsibleNav close button can be hidden 1`] = `
`; exports[`EuiCollapsibleNav is rendered 1`] = `
{ + // Keep track of unmanaged flyouts to properly calculate z-index + // values for all flyouts + if (!isInManagedContext) { + flyoutManagerRef.current?.addUnmanagedFlyout(flyoutId); + + return () => flyoutManagerRef.current?.closeUnmanagedFlyout(flyoutId); + } + }, [isInManagedContext, flyoutId]); + const { onMouseDown: onMouseDownResizableButton, onKeyDown: onKeyDownResizableButton, @@ -451,9 +463,16 @@ export const EuiFlyoutComponent = forwardRef( const siblingFlyoutWidth = useFlyoutWidth(siblingFlyoutId); + let managedFlyoutIndex = currentZIndexRef.current; + if (isInManagedContext && currentSession) { + managedFlyoutIndex = currentSession.zIndex; + } + const { flyoutZIndex, maskZIndex } = useEuiFlyoutZIndex({ maskProps, isPushed, + managedFlyoutIndex, + isChildFlyout: isChildFlyout, }); /** diff --git a/packages/eui/src/components/flyout/manager/actions.ts b/packages/eui/src/components/flyout/manager/actions.ts index 2f169e839a6..870e47185d0 100644 --- a/packages/eui/src/components/flyout/manager/actions.ts +++ b/packages/eui/src/components/flyout/manager/actions.ts @@ -37,6 +37,10 @@ export const ACTION_GO_BACK = `${PREFIX}/goBack` as const; export const ACTION_GO_TO_FLYOUT = `${PREFIX}/goToFlyout` as const; /** Dispatched to set push padding offset for a side. */ export const ACTION_SET_PUSH_PADDING = `${PREFIX}/setPushPadding` as const; +export const ACTION_ADD_UNMANAGED_FLYOUT = + `${PREFIX}/addUnmanagedFlyout` as const; +export const ACTION_CLOSE_UNMANAGED_FLYOUT = + `${PREFIX}/closeUnmanagedFlyout` as const; /** * Add a flyout to manager state. The manager will create or update @@ -100,6 +104,16 @@ export interface SetPushPaddingAction extends BaseAction { width: number; } +export interface AddUnmanagedFlyoutAction extends BaseAction { + type: typeof ACTION_ADD_UNMANAGED_FLYOUT; + flyoutId: string; +} + +export interface CloseUnmanagedFlyoutAction extends BaseAction { + type: typeof ACTION_CLOSE_UNMANAGED_FLYOUT; + flyoutId: string; +} + /** Union of all flyout manager actions. */ export type Action = | AddFlyoutAction @@ -110,7 +124,9 @@ export type Action = | SetActivityStageAction | GoBackAction | GoToFlyoutAction - | SetPushPaddingAction; + | SetPushPaddingAction + | AddUnmanagedFlyoutAction + | CloseUnmanagedFlyoutAction; /** * Register a flyout with the manager. @@ -193,3 +209,19 @@ export const setPushPadding = ( side, width, }); + +/** Register an unmanaged flyout for z-index positioning purposes */ +export const addUnmanagedFlyout = ( + flyoutId: string +): AddUnmanagedFlyoutAction => ({ + type: ACTION_ADD_UNMANAGED_FLYOUT, + flyoutId, +}); + +/** Unregister an unmanaged flyout */ +export const closeUnmanagedFlyout = ( + flyoutId: string +): CloseUnmanagedFlyoutAction => ({ + type: ACTION_CLOSE_UNMANAGED_FLYOUT, + flyoutId, +}); diff --git a/packages/eui/src/components/flyout/manager/flyout_sessions.stories.tsx b/packages/eui/src/components/flyout/manager/flyout_sessions.stories.tsx index dafa9ee6856..35162e47dc4 100644 --- a/packages/eui/src/components/flyout/manager/flyout_sessions.stories.tsx +++ b/packages/eui/src/components/flyout/manager/flyout_sessions.stories.tsx @@ -313,7 +313,6 @@ const NonSessionFlyout: React.FC<{ size: string }> = ({ size }) => { ownFocus={flyoutOwnFocus} pushAnimation={true} onClose={flyoutOnClose} - side="left" > diff --git a/packages/eui/src/components/flyout/manager/reducer.test.ts b/packages/eui/src/components/flyout/manager/reducer.test.ts index 2bead31aea9..9fa1aa4a4d3 100644 --- a/packages/eui/src/components/flyout/manager/reducer.test.ts +++ b/packages/eui/src/components/flyout/manager/reducer.test.ts @@ -23,6 +23,8 @@ import { setActivityStage, goBack, goToFlyout, + addUnmanagedFlyout, + closeUnmanagedFlyout, } from './actions'; import { LAYOUT_MODE_SIDE_BY_SIDE, @@ -50,6 +52,8 @@ describe('flyoutManagerReducer', () => { flyouts: [], layoutMode: LAYOUT_MODE_SIDE_BY_SIDE, pushPadding: { left: 0, right: 0 }, + unmanagedFlyouts: [], + currentZIndex: 0, }); }); }); @@ -72,6 +76,7 @@ describe('flyoutManagerReducer', () => { mainFlyoutId: 'main-1', childFlyoutId: null, title: 'main', + zIndex: 0, }); }); @@ -133,11 +138,13 @@ describe('flyoutManagerReducer', () => { mainFlyoutId: 'main-1', childFlyoutId: 'child-1', title: 'main', + zIndex: 0, }); expect(state.sessions[1]).toEqual({ mainFlyoutId: 'main-2', childFlyoutId: null, title: 'main', + zIndex: 3, }); }); }); @@ -196,6 +203,26 @@ describe('flyoutManagerReducer', () => { expect(newState).toEqual(initialState); }); + + it('should reset currentZIndex value when all unmanaged and managed flyouts are closed', () => { + let state = flyoutManagerReducer( + initialState, + addFlyout('main-1', 'main', LEVEL_MAIN) + ); + + state = flyoutManagerReducer( + state, + addFlyout('main-2', 'main 2', LEVEL_MAIN) + ); + state = flyoutManagerReducer(state, addUnmanagedFlyout('unmanaged-1')); + + state = flyoutManagerReducer(state, closeFlyout('main-2')); + state = flyoutManagerReducer(state, closeUnmanagedFlyout('unmanaged-1')); + expect(state.currentZIndex).toEqual(8); + + state = flyoutManagerReducer(state, closeFlyout('main-1')); + expect(state.currentZIndex).toEqual(0); + }); }); describe('ACTION_SET_ACTIVE', () => { @@ -576,6 +603,129 @@ describe('flyoutManagerReducer', () => { }); }); + describe('ACTION_ADD_UNMANAGED', () => { + it('should add an unmanaged flyout', () => { + let state = flyoutManagerReducer( + initialState, + addUnmanagedFlyout('unmanaged-1') + ); + + expect(state.unmanagedFlyouts).toEqual(['unmanaged-1']); + + state = flyoutManagerReducer(state, addUnmanagedFlyout('unmanaged-2')); + + expect(state.unmanagedFlyouts).toEqual(['unmanaged-1', 'unmanaged-2']); + }); + + it('should ignore duplicated flyouts', () => { + let state = flyoutManagerReducer( + initialState, + addUnmanagedFlyout('unmanaged-1') + ); + + state = flyoutManagerReducer(state, addUnmanagedFlyout('unmanaged-1')); + + expect(state.unmanagedFlyouts).toEqual(['unmanaged-1']); + }); + + it('should correctly update currentZIndex value', () => { + let state = flyoutManagerReducer( + initialState, + addUnmanagedFlyout('unmanaged-1') + ); + + expect(state.currentZIndex).toEqual(2); + + state = flyoutManagerReducer(state, addUnmanagedFlyout('unmanaged-2')); + expect(state.currentZIndex).toEqual(4); + }); + + it('should correctly update currentZIndex value when there are managed flyout sessions registered', () => { + let state = flyoutManagerReducer( + initialState, + addUnmanagedFlyout('unmanaged-1') + ); + + state = flyoutManagerReducer( + state, + addFlyout('main-1', 'main', LEVEL_MAIN) + ); + + state = flyoutManagerReducer(state, addUnmanagedFlyout('unmanaged-2')); + + expect(state.currentZIndex).toEqual(7); + }); + }); + + describe('ACTION_CLOSE_UNMANAGED', () => { + it('should close an unmanaged flyout', () => { + let state = flyoutManagerReducer( + initialState, + addUnmanagedFlyout('unmanaged-1') + ); + + state = flyoutManagerReducer(state, addUnmanagedFlyout('unmanaged-2')); + expect(state.unmanagedFlyouts).toEqual(['unmanaged-1', 'unmanaged-2']); + + state = flyoutManagerReducer(state, closeUnmanagedFlyout('unmanaged-2')); + expect(state.unmanagedFlyouts).toEqual(['unmanaged-1']); + + state = flyoutManagerReducer(state, closeUnmanagedFlyout('unmanaged-1')); + expect(state.unmanagedFlyouts).toHaveLength(0); + }); + + it('should reset currentZIndex value when all unmanaged flyouts are closed', () => { + let state = flyoutManagerReducer( + initialState, + addUnmanagedFlyout('unmanaged-1') + ); + + state = flyoutManagerReducer(state, addUnmanagedFlyout('unmanaged-2')); + + state = flyoutManagerReducer(state, closeUnmanagedFlyout('unmanaged-2')); + expect(state.currentZIndex).toEqual(4); + + state = flyoutManagerReducer(state, closeUnmanagedFlyout('unmanaged-1')); + expect(state.currentZIndex).toEqual(0); + }); + + it('should not update currentZIndex value all unmanaged flyouts are closed but some sessions exist', () => { + let state = flyoutManagerReducer( + initialState, + addUnmanagedFlyout('unmanaged-1') + ); + + state = flyoutManagerReducer( + state, + addFlyout('main-1', 'main', LEVEL_MAIN) + ); + + state = flyoutManagerReducer(state, closeUnmanagedFlyout('unmanaged-1')); + expect(state.currentZIndex).toEqual(5); + }); + + it('should reset currentZIndex value when all unmanaged and managed flyouts are closed', () => { + let state = flyoutManagerReducer( + initialState, + addUnmanagedFlyout('unmanaged-1') + ); + + state = flyoutManagerReducer(state, addUnmanagedFlyout('unmanaged-2')); + + state = flyoutManagerReducer( + state, + addFlyout('main-1', 'main', LEVEL_MAIN) + ); + + state = flyoutManagerReducer(state, closeUnmanagedFlyout('unmanaged-1')); + state = flyoutManagerReducer(state, closeFlyout('main-1')); + expect(state.currentZIndex).toEqual(7); + + state = flyoutManagerReducer(state, closeUnmanagedFlyout('unmanaged-2')); + expect(state.currentZIndex).toEqual(0); + }); + }); + describe('default case', () => { it('should return current state for unknown actions', () => { const unknownAction = { type: 'UNKNOWN_ACTION' } as any; @@ -658,11 +808,13 @@ describe('flyoutManagerReducer', () => { mainFlyoutId: 'main-1', childFlyoutId: 'child-1', title: 'main', + zIndex: 0, }); expect(state.sessions[1]).toEqual({ mainFlyoutId: 'main-2', childFlyoutId: null, title: 'main', + zIndex: 3, }); // Close first session's main flyout diff --git a/packages/eui/src/components/flyout/manager/reducer.ts b/packages/eui/src/components/flyout/manager/reducer.ts index aa70e7f3746..1324e12dd69 100644 --- a/packages/eui/src/components/flyout/manager/reducer.ts +++ b/packages/eui/src/components/flyout/manager/reducer.ts @@ -16,6 +16,8 @@ import { ACTION_GO_BACK, ACTION_GO_TO_FLYOUT, ACTION_SET_PUSH_PADDING, + ACTION_ADD_UNMANAGED_FLYOUT, + ACTION_CLOSE_UNMANAGED_FLYOUT, Action, } from './actions'; import { LAYOUT_MODE_SIDE_BY_SIDE, LEVEL_MAIN, STAGE_OPENING } from './const'; @@ -33,6 +35,8 @@ export const initialState: EuiFlyoutManagerState = { flyouts: [], layoutMode: LAYOUT_MODE_SIDE_BY_SIDE, pushPadding: { left: 0, right: 0 }, + currentZIndex: 0, + unmanagedFlyouts: [], }; /** @@ -43,6 +47,38 @@ export function flyoutManagerReducer( action: Action ): EuiFlyoutManagerState { switch (action.type) { + case ACTION_ADD_UNMANAGED_FLYOUT: { + if (state.unmanagedFlyouts.includes(action.flyoutId)) { + return state; + } + + return { + ...state, + // Increment by 2 for each new unmanaged flyout. + // Unmanaged flyouts render on z-index of `n`, and their overlay mask + // on `n - 1`. + currentZIndex: state.currentZIndex + 2, + unmanagedFlyouts: [...state.unmanagedFlyouts, action.flyoutId], + }; + } + + case ACTION_CLOSE_UNMANAGED_FLYOUT: { + const newUnmanagedFlyouts = state.unmanagedFlyouts.filter( + (flyoutId) => flyoutId !== action.flyoutId + ); + + let newCurrentZIndex = state.currentZIndex; + if (state.sessions.length === 0 && newUnmanagedFlyouts.length === 0) { + newCurrentZIndex = 0; + } + + return { + ...state, + unmanagedFlyouts: newUnmanagedFlyouts, + currentZIndex: newCurrentZIndex, + }; + } + // Register a flyout. // - Ignore duplicates by `flyoutId`. // - For a `main` flyout, start a new session { main, child: null }. @@ -72,12 +108,17 @@ export function flyoutManagerReducer( mainFlyoutId: flyoutId, title: title, childFlyoutId: null, + zIndex: state.currentZIndex, }; return { ...state, sessions: [...state.sessions, newSession], flyouts: newFlyouts, + // Increment by 3 for each new flyout session. + // Managed flyouts render main flyouts on z-index of `n`, + // child flyouts on `n - 1` and the overlay mask on `n - 2`. + currentZIndex: state.currentZIndex + 3, }; } @@ -130,7 +171,19 @@ export function flyoutManagerReducer( (session) => session.mainFlyoutId !== action.flyoutId ); - return { ...state, sessions: newSessions, flyouts: newFlyouts }; + let newCurrentZIndex = state.currentZIndex; + if (newSessions.length === 0 && state.unmanagedFlyouts.length === 0) { + // Reset to initial value if no flyouts remain open to avoid + // the value going too high during the lifecycle of the app + newCurrentZIndex = 0; + } + + return { + ...state, + sessions: newSessions, + flyouts: newFlyouts, + currentZIndex: newCurrentZIndex, + }; } } diff --git a/packages/eui/src/components/flyout/manager/selectors.test.tsx b/packages/eui/src/components/flyout/manager/selectors.test.tsx index 978175cac6c..3dd33859a5b 100644 --- a/packages/eui/src/components/flyout/manager/selectors.test.tsx +++ b/packages/eui/src/components/flyout/manager/selectors.test.tsx @@ -39,6 +39,8 @@ describe('Flyout Manager Selectors', () => { sessions: [], flyouts: [], layoutMode: 'side-by-side', + unmanagedFlyouts: [], + currentZIndex: 0, }, dispatch: jest.fn(), addFlyout: jest.fn(), @@ -48,6 +50,8 @@ describe('Flyout Manager Selectors', () => { setPushPadding: jest.fn(), goBack: jest.fn(), goToFlyout: jest.fn(), + addUnmanagedFlyout: jest.fn(), + closeUnmanagedFlyout: jest.fn(), historyItems: [], }); @@ -72,6 +76,8 @@ describe('Flyout Manager Selectors', () => { }, ], layoutMode: 'side-by-side', + currentZIndex: 0, + unmanagedFlyouts: [], }, dispatch: jest.fn(), addFlyout: jest.fn(), @@ -81,6 +87,8 @@ describe('Flyout Manager Selectors', () => { setPushPadding: jest.fn(), goBack: jest.fn(), goToFlyout: jest.fn(), + addUnmanagedFlyout: jest.fn(), + closeUnmanagedFlyout: jest.fn(), historyItems: [], }); @@ -97,6 +105,8 @@ describe('Flyout Manager Selectors', () => { sessions: [], flyouts: [], layoutMode: 'side-by-side', + currentZIndex: 0, + unmanagedFlyouts: [], }, dispatch: jest.fn(), addFlyout: jest.fn(), @@ -106,6 +116,8 @@ describe('Flyout Manager Selectors', () => { setPushPadding: jest.fn(), goBack: jest.fn(), goToFlyout: jest.fn(), + addUnmanagedFlyout: jest.fn(), + closeUnmanagedFlyout: jest.fn(), historyItems: [], }); @@ -129,6 +141,8 @@ describe('Flyout Manager Selectors', () => { }, ], layoutMode: 'side-by-side', + currentZIndex: 0, + unmanagedFlyouts: [], }, dispatch: jest.fn(), addFlyout: jest.fn(), @@ -138,6 +152,8 @@ describe('Flyout Manager Selectors', () => { setPushPadding: jest.fn(), goBack: jest.fn(), goToFlyout: jest.fn(), + addUnmanagedFlyout: jest.fn(), + closeUnmanagedFlyout: jest.fn(), historyItems: [], }); @@ -154,6 +170,7 @@ describe('Flyout Manager Selectors', () => { mainFlyoutId: 'parent-flyout', childFlyoutId: 'child-flyout', title: 'Parent Flyout', + zIndex: 0, }, ], flyouts: [ @@ -171,6 +188,8 @@ describe('Flyout Manager Selectors', () => { }, ], layoutMode: 'side-by-side', + currentZIndex: 0, + unmanagedFlyouts: [], }, dispatch: jest.fn(), addFlyout: jest.fn(), @@ -180,6 +199,8 @@ describe('Flyout Manager Selectors', () => { setPushPadding: jest.fn(), goBack: jest.fn(), goToFlyout: jest.fn(), + addUnmanagedFlyout: jest.fn(), + closeUnmanagedFlyout: jest.fn(), historyItems: [], }); diff --git a/packages/eui/src/components/flyout/manager/selectors.ts b/packages/eui/src/components/flyout/manager/selectors.ts index eda7f0fe506..14051073ef8 100644 --- a/packages/eui/src/components/flyout/manager/selectors.ts +++ b/packages/eui/src/components/flyout/manager/selectors.ts @@ -7,6 +7,7 @@ */ import { useFlyoutManager } from './provider'; +import { useRef } from 'react'; export const useSession = (flyoutId?: string | null) => { const context = useFlyoutManager(); @@ -102,3 +103,10 @@ export const useHasPushPadding = () => { const pushPadding = usePushPaddingOffsets(); return pushPadding.left > 0 || pushPadding.right > 0; }; + +/** Get the ref for the current flyout z-index to be used */ +export const useCurrentFlyoutZIndexRef = () => { + const context = useFlyoutManager(); + + return useRef(context?.state.currentZIndex || 0); +}; diff --git a/packages/eui/src/components/flyout/manager/store.ts b/packages/eui/src/components/flyout/manager/store.ts index 580be158f46..6a4f038a4db 100644 --- a/packages/eui/src/components/flyout/manager/store.ts +++ b/packages/eui/src/components/flyout/manager/store.ts @@ -16,6 +16,8 @@ import { setPushPadding as setPushPaddingAction, goBack as goBackAction, goToFlyout as goToFlyoutAction, + addUnmanagedFlyout as addUnmanagedFlyoutAction, + closeUnmanagedFlyout as closeUnmanagedFlyoutAction, } from './actions'; import { flyoutManagerReducer, initialState } from './reducer'; @@ -38,6 +40,8 @@ export interface FlyoutManagerStore { setPushPadding: (side: 'left' | 'right', width: number) => void; goBack: () => void; goToFlyout: (flyoutId: string) => void; + addUnmanagedFlyout: (flyoutId: string) => void; + closeUnmanagedFlyout: (flyoutId: string) => void; historyItems: Array<{ title: string; onClick: () => void; @@ -112,6 +116,10 @@ function createStore( dispatch(setPushPaddingAction(side, width)), goBack: () => dispatch(goBackAction()), goToFlyout: (flyoutId) => dispatch(goToFlyoutAction(flyoutId)), + addUnmanagedFlyout: (flyoutId) => + dispatch(addUnmanagedFlyoutAction(flyoutId)), + closeUnmanagedFlyout: (flyoutId) => + dispatch(closeUnmanagedFlyoutAction(flyoutId)), historyItems: computeHistoryItems(), // Initialize with current state }; diff --git a/packages/eui/src/components/flyout/manager/types.ts b/packages/eui/src/components/flyout/manager/types.ts index 5d200e768e6..18a4e38aa43 100644 --- a/packages/eui/src/components/flyout/manager/types.ts +++ b/packages/eui/src/components/flyout/manager/types.ts @@ -54,6 +54,8 @@ export interface FlyoutSession { childFlyoutId: string | null; /** Title of the main flyout in this session */ title: string; + /** z-index value to be used by the flyout session */ + zIndex: number; } export interface PushPaddingOffsets { @@ -69,6 +71,8 @@ export interface EuiFlyoutManagerState { layoutMode: EuiFlyoutLayoutMode; /** Active push padding offsets (updated by active push flyouts) */ pushPadding?: PushPaddingOffsets; + currentZIndex: number; + unmanagedFlyouts: string[]; } /** @@ -90,6 +94,8 @@ export interface FlyoutManagerApi { setPushPadding: (side: 'left' | 'right', width: number) => void; goBack: () => void; goToFlyout: (flyoutId: string) => void; + addUnmanagedFlyout: (flyoutId: string) => void; + closeUnmanagedFlyout: (flyoutId: string) => void; historyItems: Array<{ title: string; onClick: () => void; diff --git a/packages/eui/src/components/flyout/use_flyout_z_index.test.ts b/packages/eui/src/components/flyout/use_flyout_z_index.test.ts index dcd01d8c1fe..07f53b5eed6 100644 --- a/packages/eui/src/components/flyout/use_flyout_z_index.test.ts +++ b/packages/eui/src/components/flyout/use_flyout_z_index.test.ts @@ -9,34 +9,75 @@ import { renderHook } from '../../test/rtl/render_hook'; import { UseEuiFlyoutZIndex, useEuiFlyoutZIndex } from './use_flyout_z_index'; +const defaultProps: UseEuiFlyoutZIndex = { + isPushed: false, + managedFlyoutIndex: 0, + isChildFlyout: false, +}; + describe('useEuiFlyoutZIndex', () => { - const render = (initialProps: UseEuiFlyoutZIndex) => + const render = (initialProps: Partial = {}) => renderHook((props: UseEuiFlyoutZIndex) => useEuiFlyoutZIndex(props), { - initialProps, + initialProps: { + ...defaultProps, + ...initialProps, + }, }); + it('returns values including the `managedFlyoutIndex` value', () => { + const { result, rerender } = render(); + expect(result.current.flyoutZIndex).toEqual(1000); + expect(result.current.maskZIndex).toEqual(998); + + rerender({ ...defaultProps, managedFlyoutIndex: 100 }); + expect(result.current.flyoutZIndex).toEqual(1100); + expect(result.current.maskZIndex).toEqual(1098); + }); + + it('returns correct values when isChildFlyout = true', () => { + const { result, rerender } = render({ isChildFlyout: true }); + expect(result.current.flyoutZIndex).toEqual(999); + expect(result.current.maskZIndex).toEqual(998); + + rerender({ ...defaultProps, isChildFlyout: true, managedFlyoutIndex: 100 }); + expect(result.current.flyoutZIndex).toEqual(1099); + expect(result.current.maskZIndex).toEqual(1098); + }); + it('returns flyout level based z-index values when isPushed = true', () => { - const { result, rerender } = render({ isPushed: true }); + const { result, rerender } = render(); expect(result.current.flyoutZIndex).toEqual(1000); - expect(result.current.maskZIndex).toEqual(999); + expect(result.current.maskZIndex).toEqual(998); - rerender({ isPushed: true, maskProps: { headerZindexLocation: 'above' } }); + rerender({ + ...defaultProps, + isPushed: true, + maskProps: { headerZindexLocation: 'above' }, + }); expect(result.current.flyoutZIndex).toEqual(1000); - expect(result.current.maskZIndex).toEqual(999); + expect(result.current.maskZIndex).toEqual(998); - rerender({ isPushed: true, maskProps: { headerZindexLocation: 'below' } }); + rerender({ + ...defaultProps, + isPushed: true, + maskProps: { headerZindexLocation: 'below' }, + }); expect(result.current.flyoutZIndex).toEqual(1000); - expect(result.current.maskZIndex).toEqual(999); + expect(result.current.maskZIndex).toEqual(998); }); it('returns flyout level based z-index values when maskProps.headerZindexLocation != "above"', () => { const { result, rerender } = render({ isPushed: false, maskProps: {} }); expect(result.current.flyoutZIndex).toEqual(1000); - expect(result.current.maskZIndex).toEqual(999); + expect(result.current.maskZIndex).toEqual(998); - rerender({ isPushed: false, maskProps: { headerZindexLocation: 'below' } }); + rerender({ + ...defaultProps, + isPushed: false, + maskProps: { headerZindexLocation: 'below' }, + }); expect(result.current.flyoutZIndex).toEqual(1000); - expect(result.current.maskZIndex).toEqual(999); + expect(result.current.maskZIndex).toEqual(998); }); it('returns mask level based z-index values when maskProps.headerZindexLocation = "above"', () => { @@ -45,6 +86,6 @@ describe('useEuiFlyoutZIndex', () => { maskProps: { headerZindexLocation: 'above' }, }); expect(result.current.flyoutZIndex).toEqual(6000); - expect(result.current.maskZIndex).toEqual(5999); + expect(result.current.maskZIndex).toEqual(5998); }); }); diff --git a/packages/eui/src/components/flyout/use_flyout_z_index.ts b/packages/eui/src/components/flyout/use_flyout_z_index.ts index 5f6403201eb..b0151da5108 100644 --- a/packages/eui/src/components/flyout/use_flyout_z_index.ts +++ b/packages/eui/src/components/flyout/use_flyout_z_index.ts @@ -16,36 +16,46 @@ import type { EuiOverlayMaskProps } from '../overlay_mask'; export interface UseEuiFlyoutZIndex { maskProps?: EuiOverlayMaskProps; isPushed: boolean; + managedFlyoutIndex: number; + isChildFlyout: boolean; } -const calculateZIndex = (initialValue: CSSProperties['zIndex']) => { - const valueAsNumber = Number(initialValue); +const calculateZIndex = ( + baseLevel: CSSProperties['zIndex'], + isChildFlyout: boolean +) => { + const level = Number(baseLevel); return { - flyoutZIndex: valueAsNumber, - maskZIndex: valueAsNumber - 1, + // Child flyouts slide in from below and need to have a lower z-index + flyoutZIndex: isChildFlyout ? level - 1 : level, + maskZIndex: level - 2, }; }; /** - * TODO: Calculate z-index values so that the latest flyout is always on top - * https://github.com/elastic/eui/issues/9160 * @internal */ export const useEuiFlyoutZIndex = ({ maskProps, isPushed, + managedFlyoutIndex, + isChildFlyout, }: UseEuiFlyoutZIndex) => { const { euiTheme } = useEuiTheme(); + let baseLevel = Number(euiTheme.levels.flyout); + // The default headerZindexLocation for EuiFlyout is "below" // which is different from what EuiOverlayMask fallbacks to - see // _flyout_overlay.tsx. // We set z-index to mask level only when explicitly overridden // via the maskProps prop if (!isPushed && maskProps?.headerZindexLocation === 'above') { - return calculateZIndex(euiTheme.levels.mask); + baseLevel = Number(euiTheme.levels.mask); } - return calculateZIndex(euiTheme.levels.flyout); + baseLevel += managedFlyoutIndex; + + return calculateZIndex(baseLevel, isChildFlyout); };