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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import React, { type ReactNode } from 'react';
import { euiIncludeSelectorInFocusTrap } from '@kbn/core-chrome-layout-constants';
import { styles } from './layout_header.styles';

export interface LayoutHeaderProps {
Expand All @@ -22,7 +23,12 @@ export interface LayoutHeaderProps {
*/
export const LayoutHeader = ({ children }: LayoutHeaderProps) => {
return (
<div css={styles.root} className="kbnChromeLayoutHeader" data-test-subj="kbnChromeLayoutHeader">
<div
css={styles.root}
className="kbnChromeLayoutHeader"
data-test-subj="kbnChromeLayoutHeader"
{...euiIncludeSelectorInFocusTrap.prop}
>
{children}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import React, { type ReactNode } from 'react';
import { euiIncludeSelectorInFocusTrap } from '@kbn/core-chrome-layout-constants';
import { styles } from './layout_navigation.styles';

export interface LayoutNavigationProps {
Expand All @@ -26,6 +27,7 @@ export const LayoutNavigation = ({ children }: LayoutNavigationProps) => {
css={styles.root}
className="kbnChromeLayoutNavigation"
data-test-subj="kbnChromeLayoutNavigation"
{...euiIncludeSelectorInFocusTrap.prop}
>
{children}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@ export const MAIN_CONTENT_SELECTORS = [
'[role="main"]', // Fallback for plugins using deprecated EuiPageContent
'.kbnAppWrapper', // Last-ditch fallback for all plugins regardless of page template
];

/**
* The selector for elements that should be included in the focus trap of a flyout.
* This will allow the flyout focus trap to include header and sidenav by default.
*/
export const euiIncludeSelectorInFocusTrap = {
prop: {
'data-eui-includes-in-flyout-focus-trap': true,
},
selector: `[data-eui-includes-in-flyout-focus-trap="true"]`,
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ import {
APP_MAIN_SCROLL_CONTAINER_ID,
} from '@kbn/core-chrome-layout-constants';
import { CommonGlobalAppStyles } from '../common/global_app_styles';
import {
useHackSyncPushFlyout,
hackEuiPushFlyoutPaddingInlineEnd,
hackEuiPushFlyoutPaddingInlineStart,
} from './hack_use_sync_push_flyout';

const globalLayoutStyles = (euiTheme: UseEuiTheme['euiTheme']) => css`
:root {
Expand Down Expand Up @@ -107,8 +102,8 @@ const globalTempHackStyles = (euiTheme: UseEuiTheme['euiTheme']) => css`

// push flyout should be pushing the application area, instead of body
#${APP_MAIN_SCROLL_CONTAINER_ID} {
${logicalCSS('padding-right', `var(${hackEuiPushFlyoutPaddingInlineEnd}, 0px)`)};
${logicalCSS('padding-left', `var(${hackEuiPushFlyoutPaddingInlineStart}, 0px)`)};
${logicalCSS('padding-right', `var(--euiPushFlyoutOffsetInlineEnd, 0px)`)};
${logicalCSS('padding-left', `var(--euiPushFlyoutOffsetInlineStart, 0px)`)};
}
// this is a temporary hack to override EUI's body padding with push flyout
.kbnBody {
Expand All @@ -119,7 +114,6 @@ const globalTempHackStyles = (euiTheme: UseEuiTheme['euiTheme']) => css`

export const GridLayoutGlobalStyles = () => {
const { euiTheme } = useEuiTheme();
useHackSyncPushFlyout();
return (
<>
<Global styles={[globalLayoutStyles(euiTheme), globalTempHackStyles(euiTheme)]} />
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,29 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { ReactWrapper } from 'enzyme';
import type { FC } from 'react';
import React, { useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import { render, waitFor } from '@testing-library/react';
import { BehaviorSubject, of } from 'rxjs';

import { useEuiTheme } from '@elastic/eui';
import { useEuiTheme, EuiProvider } from '@elastic/eui';
import type { UserProfileService } from '@kbn/core-user-profile-browser';
import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks';
import type { KibanaTheme } from '@kbn/react-kibana-context-common';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { euiIncludeSelectorInFocusTrap } from '@kbn/core-chrome-layout-constants';

import { KibanaEuiProvider } from './eui_provider';

// Mock the EuiProvider component to capture its props
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
...original,
EuiProvider: jest.fn(original.EuiProvider),
};
});

describe('KibanaEuiProvider', () => {
let euiTheme: ReturnType<typeof useEuiTheme> | undefined;
let userProfile: UserProfileService;
Expand All @@ -30,18 +39,9 @@ describe('KibanaEuiProvider', () => {
euiTheme = undefined;
userProfile = userProfileServiceMock.createStart();
consoleWarnMock = jest.spyOn(global.console, 'warn').mockImplementation(() => {});
(EuiProvider as jest.Mock).mockImplementation(jest.requireActual('@elastic/eui').EuiProvider);
});

const flushPromises = async () => {
await new Promise<void>(async (resolve, reject) => {
try {
setImmediate(() => resolve());
} catch (error) {
reject(error);
}
});
};

const InnerComponent: FC = () => {
const theme = useEuiTheme();
useEffect(() => {
Expand All @@ -50,17 +50,10 @@ describe('KibanaEuiProvider', () => {
return <div>foo</div>;
};

const refresh = async (wrapper: ReactWrapper<unknown>) => {
await act(async () => {
await flushPromises();
wrapper.update();
});
};

it('exposes the EUI theme provider', async () => {
const coreTheme: KibanaTheme = { darkMode: true, name: 'amsterdam' };

const wrapper = mountWithIntl(
render(
<KibanaEuiProvider
theme={{ theme$: of(coreTheme) }}
modify={{ breakpoint: { xxl: 1600 } }}
Expand All @@ -70,7 +63,10 @@ describe('KibanaEuiProvider', () => {
</KibanaEuiProvider>
);

await refresh(wrapper);
// Wait for the component to update
await waitFor(() => {
expect(euiTheme).toBeDefined();
});

expect(euiTheme!.colorMode).toEqual('DARK');
expect(euiTheme!.euiTheme.breakpoint.xxl).toEqual(1600);
Expand All @@ -80,23 +76,50 @@ describe('KibanaEuiProvider', () => {
it('propagates changes of the coreTheme observable', async () => {
const coreTheme$ = new BehaviorSubject<KibanaTheme>({ darkMode: true, name: 'amsterdam' });

const wrapper = mountWithIntl(
render(
<KibanaEuiProvider theme={{ theme$: coreTheme$ }} userProfile={userProfile}>
<InnerComponent />
</KibanaEuiProvider>
);

await refresh(wrapper);
// Wait for the component to update with initial theme
await waitFor(() => {
expect(euiTheme).toBeDefined();
});

expect(euiTheme!.colorMode).toEqual('DARK');

await act(async () => {
// Update the theme
act(() => {
coreTheme$.next({ darkMode: false, name: 'amsterdam' });
});

await refresh(wrapper);
// Wait for the component to update with new theme
await waitFor(() => {
expect(euiTheme!.colorMode).toEqual('LIGHT');
});

expect(euiTheme!.colorMode).toEqual('LIGHT');
expect(consoleWarnMock).not.toBeCalled();
});

it('passes component defaults to EuiProvider', async () => {
const coreTheme: KibanaTheme = { darkMode: true, name: 'amsterdam' };

render(
<KibanaEuiProvider theme={{ theme$: of(coreTheme) }} userProfile={userProfile}>
<div>test</div>
</KibanaEuiProvider>
);

expect(EuiProvider).toHaveBeenCalledWith(
expect.objectContaining({
componentDefaults: {
EuiFlyout: {
includeSelectorInFocusTrap: euiIncludeSelectorInFocusTrap.selector,
},
},
}),
expect.anything()
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import React, { FC, PropsWithChildren, useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import createCache from '@emotion/cache';

// We can't use the import directly because the package isn't included in the shared bundle, so below the value is hardcoded.
// However, we import this directly in the test to ensure our hardcoded selector is correct.
// import { euiIncludeSelectorInFocusTrap } from '@kbn/core-chrome-layout-constants';

import { EuiProvider, EuiProviderProps, euiStylisPrefixer } from '@elastic/eui';
import { EUI_STYLES_GLOBAL, EUI_STYLES_UTILS } from '@kbn/core-base-common';
import {
Expand Down Expand Up @@ -73,6 +77,12 @@ utilitiesCache.compat = true;

const cache = { default: emotionCache, global: globalCache, utility: utilitiesCache };

const componentDefaults: EuiProviderProps<unknown>['componentDefaults'] = {
EuiFlyout: {
includeSelectorInFocusTrap: `[data-eui-includes-in-flyout-focus-trap="true"]`,
},
};

/**
* Prepares and returns a configured `EuiProvider` for use in Kibana roots. In most cases, this utility context
* should not be used. Instead, refer to `KibanaRootContextProvider` to set up the root of Kibana.
Expand Down Expand Up @@ -130,6 +140,7 @@ export const KibanaEuiProvider: FC<PropsWithChildren<KibanaEuiProviderProps>> =
utilityClasses: globalStyles,
highContrastMode,
theme: _theme,
componentDefaults,
}}
>
{children}
Expand Down
Loading