Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
44 changes: 23 additions & 21 deletions packages/eui/src/components/flyout/_flyout_overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,29 @@
* Side Public License, v 1.
*/

import React, { PropsWithChildren } from 'react';
import React, { PropsWithChildren, useMemo } from 'react';
import { css, cx } from '@emotion/css';
import type { EuiFlyoutComponentProps } from './flyout.component';
import { EuiOverlayMask } from '../overlay_mask';
import { EuiPortal } from '../portal';
import { useEuiMemoizedStyles, type UseEuiTheme } from '../../services';
import type { EuiFlyoutComponentProps } from './flyout.component';

export interface EuiFlyoutOverlayProps extends PropsWithChildren {
hasOverlayMask: boolean;
maskProps: EuiFlyoutComponentProps['maskProps'];
isPushed: boolean;
maskZIndex: number;
}

const getEuiFlyoutOverlayStyles = ({ euiTheme }: UseEuiTheme) => {
// TODO(tkajtoch): This should likely depend on maskProps.headerZIndexLocation
// in cases where the mask has z-index 6000
const maskLevel = Number(euiTheme.levels.flyout) - 1;

return {
overlayMask: css`
/*
This needs to have !important to override the default EuiOverlayMask
z-index based on the headerZindexLocation prop. Using the style attribute
doesn't work since EuiOverlayMask requires a string style prop that
causes React errors in the test environment.
*/
z-index: ${maskLevel} !important;
`,
};
const getEuiFlyoutOverlayStyles = (zIndex: number) => {
/*
This needs to have !important to override the default EuiOverlayMask
z-index based on the headerZindexLocation prop. Using the style attribute
doesn't work since EuiOverlayMask requires a string style prop that
causes React errors in the test environment.
*/
return css`
z-index: ${zIndex} !important;
`;
};

/**
Expand All @@ -50,20 +44,28 @@ export const EuiFlyoutOverlay = ({
isPushed,
maskProps,
hasOverlayMask,
maskZIndex,
}: EuiFlyoutOverlayProps) => {
const styles = useEuiMemoizedStyles(getEuiFlyoutOverlayStyles);
const styles = useMemo(
() => getEuiFlyoutOverlayStyles(maskZIndex),
[maskZIndex]
);

let content = children;

if (!isPushed || hasOverlayMask) {
content = <EuiPortal>{content}</EuiPortal>;
}

const classes = cx(maskProps?.className, styles);

return (
<>
{hasOverlayMask && (
<EuiOverlayMask
headerZindexLocation="below"
{...maskProps}
className={cx(maskProps?.className, styles.overlayMask)}
className={classes}
/>
)}
{content}
Expand Down
11 changes: 10 additions & 1 deletion packages/eui/src/components/flyout/flyout.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { EuiFlyoutOverlay } from './_flyout_overlay';
import { EuiFlyoutResizeButton } from './_flyout_resize_button';
import { useEuiFlyoutResizable } from './use_flyout_resizable';
import type { EuiFlyoutCloseEvent } from './types';
import { useEuiFlyoutZIndex } from './use_flyout_z_index';

interface _EuiFlyoutComponentProps {
/**
Expand Down Expand Up @@ -403,6 +404,11 @@ export const EuiFlyoutComponent = forwardRef(

const siblingFlyoutWidth = useFlyoutWidth(siblingFlyoutId);

const { flyoutZIndex, maskZIndex } = useEuiFlyoutZIndex({
maskProps,
isPushed,
});

/**
* Set inline styles
*/
Expand All @@ -412,7 +418,8 @@ export const EuiFlyoutComponent = forwardRef(
layoutMode,
siblingFlyoutId,
siblingFlyoutWidth || null,
maxWidth
maxWidth,
flyoutZIndex
);

return { ...style, ...composedStyles };
Expand All @@ -423,6 +430,7 @@ export const EuiFlyoutComponent = forwardRef(
siblingFlyoutId,
siblingFlyoutWidth,
maxWidth,
flyoutZIndex,
]);

const styles = useEuiMemoizedStyles(euiFlyoutStyles);
Expand Down Expand Up @@ -579,6 +587,7 @@ export const EuiFlyoutComponent = forwardRef(
<EuiFlyoutOverlay
hasOverlayMask={hasOverlayMask}
isPushed={isPushed}
maskZIndex={maskZIndex}
maskProps={{
...maskProps,
maskRef: maskCombinedRefs,
Expand Down
5 changes: 3 additions & 2 deletions packages/eui/src/components/flyout/flyout.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export const euiFlyoutStyles = (euiThemeContext: UseEuiTheme) => {
${logicalCSS('bottom', 0)}
${logicalCSS('top', 'var(--euiFixedHeadersOffset, 0)')}
${logicalCSS('height', 'inherit')}
z-index: ${euiTheme.levels.flyout};
background: ${euiTheme.colors.backgroundBasePlain};
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -361,7 +360,8 @@ export const composeFlyoutInlineStyles = (
layoutMode: 'side-by-side' | 'stacked',
siblingFlyoutId: string | null,
siblingFlyoutWidth: number | null,
maxWidth: boolean | number | string | undefined
maxWidth: boolean | number | string | undefined,
zIndex: number
): React.CSSProperties => {
// Handle custom width values (non-named sizes)
const customWidthStyles = !isEuiFlyoutSizeNamed(size)
Expand Down Expand Up @@ -433,5 +433,6 @@ export const composeFlyoutInlineStyles = (
...dynamicStyles,
...minWidthOverride,
...(finalMaxWidth ? { maxWidth: finalMaxWidth } : {}),
zIndex,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

z-index is now defined inside the inline style tag. It'll become dynamic in #9160, so I thought it's best if I structure the code in a way that makes that work a little simpler.

});
};
50 changes: 50 additions & 0 deletions packages/eui/src/components/flyout/use_flyout_z_index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { renderHook } from '../../test/rtl/render_hook';
import { UseEuiFlyoutZIndex, useEuiFlyoutZIndex } from './use_flyout_z_index';

describe('useEuiFlyoutZIndex', () => {
const render = (initialProps: UseEuiFlyoutZIndex) =>
renderHook((props: UseEuiFlyoutZIndex) => useEuiFlyoutZIndex(props), {
initialProps,
});

it('returns flyout level based z-index values when isPushed = true', () => {
const { result, rerender } = render({ isPushed: true });
expect(result.current.flyoutZIndex).toEqual(1000);
expect(result.current.maskZIndex).toEqual(999);

rerender({ isPushed: true, maskProps: { headerZindexLocation: 'above' } });
expect(result.current.flyoutZIndex).toEqual(1000);
expect(result.current.maskZIndex).toEqual(999);

rerender({ isPushed: true, maskProps: { headerZindexLocation: 'below' } });
expect(result.current.flyoutZIndex).toEqual(1000);
expect(result.current.maskZIndex).toEqual(999);
});

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);

rerender({ isPushed: false, maskProps: { headerZindexLocation: 'below' } });
expect(result.current.flyoutZIndex).toEqual(1000);
expect(result.current.maskZIndex).toEqual(999);
});

it('returns mask level based z-index values when maskProps.headerZindexLocation = "above"', () => {
const { result } = render({
isPushed: false,
maskProps: { headerZindexLocation: 'above' },
});
expect(result.current.flyoutZIndex).toEqual(6000);
expect(result.current.maskZIndex).toEqual(5999);
});
});
51 changes: 51 additions & 0 deletions packages/eui/src/components/flyout/use_flyout_z_index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 type { CSSProperties } from 'react';
import { useEuiTheme } from '../../services';
import type { EuiOverlayMaskProps } from '../overlay_mask';

/**
* @internal
*/
export interface UseEuiFlyoutZIndex {
maskProps?: EuiOverlayMaskProps;
isPushed: boolean;
}

const calculateZIndex = (initialValue: CSSProperties['zIndex']) => {
const valueAsNumber = Number(initialValue);

return {
flyoutZIndex: valueAsNumber,
maskZIndex: valueAsNumber - 1,
};
};

/**
* TODO: Calculate z-index values so that the latest flyout is always on top

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is to be addressed in a separate PR

* https://github.com/elastic/eui/issues/9160
* @internal
*/
export const useEuiFlyoutZIndex = ({
maskProps,
isPushed,
}: UseEuiFlyoutZIndex) => {
const { euiTheme } = useEuiTheme();

// 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);
}

return calculateZIndex(euiTheme.levels.flyout);
};