Provide React Context composition; new Kibana Context, consolidate/deprecate.

This commit is contained in:
Clint Andrew Hall 2023-05-31 11:07:42 -04:00
parent 663066b3f9
commit 815226a926
62 changed files with 539 additions and 497 deletions

2
.github/CODEOWNERS vendored
View file

@ -511,6 +511,8 @@ src/plugins/presentation_util @elastic/kibana-presentation
x-pack/plugins/profiling @elastic/profiling-ui
x-pack/packages/kbn-random-sampling @elastic/kibana-visualizations
packages/kbn-react-field @elastic/kibana-data-discovery
packages/react/kibana_context @elastic/appex-sharedux
packages/react/kibana_mount @elastic/appex-sharedux
x-pack/plugins/remote_clusters @elastic/platform-deployment-management
test/plugin_functional/plugins/rendering_plugin @elastic/kibana-core
packages/kbn-repo-file-maps @elastic/kibana-operations

View file

@ -76,6 +76,7 @@
"newsfeed": "src/plugins/newsfeed",
"presentationUtil": "src/plugins/presentation_util",
"randomSampling": "x-pack/packages/kbn-random-sampling",
"reactPackages": "packages/react",
"textBasedEditor": "packages/kbn-text-based-editor",
"reporting": "packages/kbn-reporting/common",
"savedObjects": "src/plugins/saved_objects",

View file

@ -519,6 +519,8 @@
"@kbn/profiling-plugin": "link:x-pack/plugins/profiling",
"@kbn/random-sampling": "link:x-pack/packages/kbn-random-sampling",
"@kbn/react-field": "link:packages/kbn-react-field",
"@kbn/react-kibana-context": "link:packages/react/kibana_context",
"@kbn/react-kibana-mount": "link:packages/react/kibana_mount",
"@kbn/remote-clusters-plugin": "link:x-pack/plugins/remote_clusters",
"@kbn/rendering-plugin": "link:test/plugin_functional/plugins/rendering_plugin",
"@kbn/repo-info": "link:packages/kbn-repo-info",

View file

@ -68,6 +68,10 @@ Array [
/>
</EuiFlyout>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -141,6 +145,10 @@ Array [
/>
</EuiFlyout>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],

View file

@ -35,6 +35,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -194,6 +198,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -219,6 +227,10 @@ Array [
/>
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -394,6 +406,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -419,6 +435,10 @@ Array [
/>
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -441,6 +461,10 @@ Array [
Some message
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -557,6 +581,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -582,6 +610,10 @@ Array [
/>
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -604,6 +636,10 @@ Array [
Some message
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -725,6 +761,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -750,6 +790,10 @@ Array [
/>
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -772,6 +816,10 @@ Array [
Some message
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -888,6 +936,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -913,6 +965,10 @@ Array [
/>
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -935,6 +991,10 @@ Array [
Some message
</EuiConfirmModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -1118,6 +1178,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -1191,6 +1255,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -1269,6 +1337,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],
@ -1342,6 +1414,10 @@ Array [
/>
</EuiModal>
</CoreThemeProvider>,
"globalStyles": false,
"theme$": Observable {
"_subscribe": [Function],
},
},
Object {},
],

View file

@ -1,19 +0,0 @@
/*
* 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 { convertCoreTheme } from './convert_core_theme';
describe('convertCoreTheme', () => {
it('returns the correct `colorMode` when `darkMode` is enabled', () => {
expect(convertCoreTheme({ darkMode: true }).colorMode).toEqual('DARK');
});
it('returns the correct `colorMode` when `darkMode` is disabled', () => {
expect(convertCoreTheme({ darkMode: false }).colorMode).toEqual('LIGHT');
});
});

View file

@ -1,25 +0,0 @@
/*
* 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 { COLOR_MODES_STANDARD } from '@elastic/eui';
import type { EuiThemeSystem, EuiThemeColorModeStandard } from '@elastic/eui';
import type { CoreTheme } from '@kbn/core-theme-browser';
/** @internal */
export interface EuiTheme {
colorMode: EuiThemeColorModeStandard;
euiThemeSystem?: EuiThemeSystem;
}
/** @internal */
export const convertCoreTheme = (coreTheme: CoreTheme): EuiTheme => {
const { darkMode } = coreTheme;
return {
colorMode: darkMode ? COLOR_MODES_STANDARD.dark : COLOR_MODES_STANDARD.light,
};
};

View file

@ -6,35 +6,31 @@
* Side Public License, v 1.
*/
import React, { FC } from 'react';
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
import React, { FC, useMemo } from 'react';
import type { I18nStart } from '@kbn/core-i18n-browser';
import { CoreThemeProvider } from './core_theme_provider';
import { composeProviders, KibanaThemeProvider } from '@kbn/react-kibana-context';
import { ThemeServiceStart } from '@kbn/core-theme-browser/src/types';
interface CoreContextProviderProps {
theme: ThemeServiceStart;
i18n: I18nStart;
theme: ThemeServiceStart;
globalStyles?: boolean;
}
/**
* utility component exposing all the context providers required by core when integrating with react
* Utility component exposing all the context providers required by core when integrating with React.
**/
export const CoreContextProvider: FC<CoreContextProviderProps> = ({
i18n,
theme,
children,
theme,
globalStyles = false,
}) => {
// `globalStyles` and `utilityClasses` default values are inverted from that of `EuiProvider`.
// Default to `false` (does not add EUI global styles) because more instances use that value.
// A value of `true` (does add EUI global styles) will have `EuiProvider` use its default value.
const includeGlobalStyles = globalStyles === false ? false : undefined;
const Provider = useMemo(() => composeProviders([i18n.Context, KibanaThemeProvider]), [i18n]);
return (
<i18n.Context>
<CoreThemeProvider theme$={theme.theme$} globalStyles={includeGlobalStyles}>
{children}
</CoreThemeProvider>
</i18n.Context>
<Provider theme$={theme.theme$} {...{ globalStyles }}>
{children}
</Provider>
);
};

View file

@ -6,55 +6,12 @@
* Side Public License, v 1.
*/
import React, { FC, useMemo } from 'react';
import { Observable } from 'rxjs';
import useObservable from 'react-use/lib/useObservable';
import createCache from '@emotion/cache';
import { EuiProvider } from '@elastic/eui';
import { EUI_STYLES_GLOBAL } from '@kbn/core-base-common';
import type { CoreTheme } from '@kbn/core-theme-browser';
import { convertCoreTheme } from './convert_core_theme';
const defaultTheme: CoreTheme = {
darkMode: false,
};
interface CoreThemeProviderProps {
theme$: Observable<CoreTheme>;
globalStyles?: boolean;
}
const globalCache = createCache({
key: 'eui',
container: document.querySelector(`meta[name="${EUI_STYLES_GLOBAL}"]`) as HTMLElement,
});
const emotionCache = createCache({
key: 'css',
container: document.querySelector(`meta[name="emotion"]`) as HTMLElement,
});
emotionCache.compat = true;
import { KibanaThemeProvider } from '@kbn/react-kibana-context';
/**
* Wrapper around `EuiProvider` converting (and exposing) core's theme to EUI theme.
* @internal Only meant to be used within core for internal usages of EUI/React
* @deprecated use `KibanaThemeProvider` from `@kbn/react-kibana-context
*/
export const CoreThemeProvider: FC<CoreThemeProviderProps> = ({
theme$,
children,
globalStyles,
}) => {
const coreTheme = useObservable(theme$, defaultTheme);
const euiTheme = useMemo(() => convertCoreTheme(coreTheme), [coreTheme]);
const includeGlobalStyles = globalStyles === false ? false : undefined;
return (
<EuiProvider
globalStyles={includeGlobalStyles}
utilityClasses={includeGlobalStyles}
colorMode={euiTheme.colorMode}
theme={euiTheme.euiThemeSystem}
cache={{ default: emotionCache, global: globalCache }}
>
{children}
</EuiProvider>
);
};
export const CoreThemeProvider = KibanaThemeProvider;
CoreThemeProvider.displayName = 'CoreThemeProvider';

View file

@ -13,12 +13,12 @@
"**/*.tsx",
],
"kbn_references": [
"@kbn/core-base-common",
"@kbn/core-injected-metadata-browser-internal",
"@kbn/core-theme-browser",
"@kbn/core-i18n-browser",
"@kbn/core-injected-metadata-browser-mocks",
"@kbn/test-jest-helpers",
"@kbn/react-kibana-context",
"@kbn/core-i18n-browser",
],
"exclude": [
"target/**/*",

View file

@ -6,34 +6,28 @@
* Side Public License, v 1.
*/
import React from 'react';
import { EuiProvider } from '@elastic/eui';
import createCache from '@emotion/cache';
import React, { useEffect } from 'react';
import type { DecoratorFn } from '@storybook/react';
import { KibanaContextProvider } from '@kbn/react-kibana-context';
import 'core_styles';
import { BehaviorSubject } from 'rxjs';
import { CoreTheme } from '@kbn/core-theme-browser';
const theme$ = new BehaviorSubject<CoreTheme>({ darkMode: false });
/**
* Storybook decorator using the EUI provider. Uses the value from
* `globals` provided by the Storybook theme switcher.
* Storybook decorator using the `KibanaContextProvider`. Uses the value from
* `globals` provided by the Storybook theme switcher to set the `colorMode`.
*/
const EuiProviderDecorator: DecoratorFn = (storyFn, { globals }) => {
const KibanaContextDecorator: DecoratorFn = (storyFn, { globals }) => {
const colorMode = globals.euiTheme === 'v8.dark' ? 'dark' : 'light';
const globalCache = createCache({
key: 'eui',
container: document.querySelector(`meta[name="eui-global"]`) as HTMLElement,
});
const emotionCache = createCache({
key: 'css',
container: document.querySelector(`meta[name="emotion"]`) as HTMLElement,
});
emotionCache.compat = true;
return (
<EuiProvider colorMode={colorMode} cache={{ default: emotionCache, global: globalCache }}>
{storyFn()}
</EuiProvider>
);
useEffect(() => {
theme$.next({ darkMode: colorMode === 'dark' });
}, [colorMode]);
return <KibanaContextProvider {...{ theme$ }}>{storyFn()}</KibanaContextProvider>;
};
export const decorators = [EuiProviderDecorator];
export const decorators = [KibanaContextDecorator];

View file

@ -16,6 +16,8 @@
"@kbn/ui-shared-deps-src",
"@kbn/repo-info",
"@kbn/dev-cli-runner",
"@kbn/react-kibana-context",
"@kbn/core-theme-browser",
],
"exclude": [
"target/**/*",

View file

@ -76,7 +76,7 @@ module.exports = {
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
snapshotSerializers: [
'<rootDir>/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts',
'<rootDir>/packages/react/kibana_mount/test_helpers/react_mount_serializer.ts',
'enzyme-to-json/serializer',
'<rootDir>/packages/kbn-test/src/jest/setup/emotion.js',
],

View file

@ -0,0 +1 @@
TODO

View file

@ -0,0 +1,3 @@
# @kbn/react-kibana-context
Empty package generated by @kbn/generate

View file

@ -0,0 +1,15 @@
/*
* 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.
*/
export { KibanaThemeProvider, wrapWithTheme } from './theme';
export type { KibanaThemeProviderProps } from './theme';
export { KibanaContextProvider, withKibanaContextProvider } from './provider';
export type { KibanaContextProviderProps } from './provider';
export { composeProviders } from './utils';

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/packages/react/kibana_context'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/react-kibana-context",
"owner": "@elastic/appex-sharedux"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/react-kibana-context",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
export { KibanaContextProvider, withKibanaContextProvider } from './kibana_provider';
export type { KibanaContextProviderProps } from './kibana_provider';

View file

@ -0,0 +1,20 @@
/*
* 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 { I18nProvider } from '@kbn/i18n-react';
import { ComponentPropsWithoutRef } from 'react';
import { KibanaThemeProvider } from '../theme';
import { composeProviders } from '../utils';
import { ComposeProvidersFn } from '../utils/compose';
export const KibanaContextProvider = composeProviders([I18nProvider, KibanaThemeProvider]);
export type KibanaContextProviderProps = ComponentPropsWithoutRef<typeof KibanaContextProvider>;
export const withKibanaContextProvider: ComposeProvidersFn = (providers) =>
composeProviders([KibanaContextProvider, ...providers]);

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { getColorMode } from './utils';
import { getColorMode } from './color_mode';
describe('getColorMode', () => {
it('returns the correct `colorMode` when `darkMode` is enabled', () => {

View file

@ -8,12 +8,8 @@
import { COLOR_MODES_STANDARD } from '@elastic/eui';
import type { EuiThemeColorModeStandard } from '@elastic/eui';
import type { Theme } from '../types';
import type { CoreTheme } from '@kbn/core/public';
/**
* Copied from the `kibana_react` plugin, to avoid cyclical dependency
*/
export const getColorMode = (theme: CoreTheme): EuiThemeColorModeStandard => {
export const getColorMode = (theme: Theme): EuiThemeColorModeStandard | undefined => {
return theme.darkMode ? COLOR_MODES_STANDARD.dark : COLOR_MODES_STANDARD.light;
};

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export { KibanaThemeProvider } from './theme';
export type { KibanaThemeProviderProps } from './theme';
export { wrapWithTheme } from './with_theme';

View file

@ -13,10 +13,10 @@ import React, { useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import { BehaviorSubject, of } from 'rxjs';
import type { CoreTheme } from '@kbn/core/public';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { KibanaThemeProvider } from './kibana_theme_provider';
import type { Theme } from '../types';
import { KibanaThemeProvider } from './theme';
describe('KibanaThemeProvider', () => {
let euiTheme: ReturnType<typeof useEuiTheme> | undefined;
@ -51,7 +51,7 @@ describe('KibanaThemeProvider', () => {
};
it('exposes the EUI theme provider', async () => {
const coreTheme: CoreTheme = { darkMode: true };
const coreTheme: Theme = { darkMode: true };
const wrapper = mountWithIntl(
<KibanaThemeProvider theme$={of(coreTheme)}>
@ -65,7 +65,7 @@ describe('KibanaThemeProvider', () => {
});
it('propagates changes of the coreTheme observable', async () => {
const coreTheme$ = new BehaviorSubject<CoreTheme>({ darkMode: true });
const coreTheme$ = new BehaviorSubject<Theme>({ darkMode: true });
const wrapper = mountWithIntl(
<KibanaThemeProvider theme$={coreTheme$}>

View file

@ -0,0 +1,70 @@
/*
* 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 React, { FC, useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { Observable } from 'rxjs';
import createCache from '@emotion/cache';
import { EuiProvider, EuiProviderProps } from '@elastic/eui';
import { getColorMode } from './color_mode';
import type { Theme } from '../types';
export interface KibanaThemeProviderProps
extends Pick<EuiProviderProps<{}>, 'modify' | 'colorMode'> {
theme$: Observable<Theme>;
globalStyles?: boolean;
utilityClasses?: boolean;
}
const defaultTheme: Theme = {
darkMode: false,
};
const globalCache = createCache({
key: 'eui',
container: document.querySelector(`meta[name="eui-global"]`) as HTMLElement,
});
const emotionCache = createCache({
key: 'css',
container: document.querySelector(`meta[name="emotion"]`) as HTMLElement,
});
emotionCache.compat = true;
const cache = { default: emotionCache, global: globalCache };
export const KibanaThemeProvider: FC<KibanaThemeProviderProps> = ({
theme$,
globalStyles: globalStylesProp,
utilityClasses: utilityClassesProp,
colorMode: colorModeProp,
modify,
children,
}) => {
const theme = useObservable(theme$, defaultTheme);
const themeColorMode = useMemo(() => getColorMode(theme), [theme]);
// In some cases-- like in Storybook or testing-- we want to explicitly override the
// colorMode provided by the `theme`.
const colorMode = colorModeProp || themeColorMode;
// This logic was drawn from the Core theme provider, and wasn't present (or even used)
// elsewhere. Should be a passive addition to anyone using the older theme provider(s).
const globalStyles = globalStylesProp === false ? false : undefined;
const utilityClasses =
utilityClassesProp === false || globalStylesProp === false ? false : undefined;
return (
<EuiProvider {...{ cache, colorMode, globalStyles, modify, utilityClasses }}>
{children}
</EuiProvider>
);
};

View file

@ -8,12 +8,10 @@
import React from 'react';
import { Observable } from 'rxjs';
import type { CoreTheme } from '@kbn/core/public';
import { KibanaThemeProvider } from './kibana_theme_provider';
export const wrapWithTheme = (
node: React.ReactNode,
theme$: Observable<CoreTheme>
): React.ReactElement => {
return <KibanaThemeProvider theme$={theme$}>{node}</KibanaThemeProvider>;
};
import { KibanaThemeProvider } from './theme';
import type { Theme } from '../types';
export const wrapWithTheme = (node: React.ReactNode, theme$: Observable<Theme>) => (
<KibanaThemeProvider theme$={theme$}>{node}</KibanaThemeProvider>
);

View file

@ -0,0 +1,22 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/i18n-react",
"@kbn/test-jest-helpers",
]
}

View file

@ -0,0 +1,16 @@
/*
* 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.
*/
// To avoid a circular dependency with the deprecation of `CoreThemeProvider`,
// we need to define the theme type here.
//
// TODO: clintandrewhall - remove this file once `CoreThemeProvider` is removed
export interface Theme {
/** is dark mode enabled or not */
readonly darkMode: boolean;
}

View file

@ -0,0 +1,38 @@
/*
* 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 React, { PropsWithChildren, FC, ComponentPropsWithoutRef } from 'react';
export type UnionToIntersection<U> = (U extends unknown ? (u: U) => void : never) extends (
i: infer I
) => void
? I
: never;
type CombinedProps<P extends Array<FC<any>>> = PropsWithChildren<
UnionToIntersection<ComponentPropsWithoutRef<P[number]>>
>;
const compose = <P extends Array<FC<any>>>(providers: P, props: CombinedProps<P>): FC<{}> => {
return ({ children }) => (
<>
{providers.reduceRight((acc, ContextProvider) => {
return <ContextProvider {...props}>{acc}</ContextProvider>;
}, children)}
</>
);
};
export const composeProviders = <P extends Array<FC<any>>>(providers: P): FC<CombinedProps<P>> => {
return (props: CombinedProps<P>) => {
const ContextProvider = compose(providers, props);
return <ContextProvider>{props.children}</ContextProvider>;
};
};
export type ComposeProvidersFn = typeof composeProviders;

View file

@ -6,6 +6,4 @@
* Side Public License, v 1.
*/
import type { UseEuiTheme } from '@elastic/eui';
export type EuiTheme = UseEuiTheme;
export { composeProviders } from './compose';

View file

@ -0,0 +1,3 @@
# @kbn/react-kibana-mount
Empty package generated by @kbn/generate

View file

@ -7,6 +7,7 @@
*/
export { toMountPoint } from './to_mount_point';
export type { ToMountPointOptions } from './to_mount_point';
export type { ToMountPointParams } from './to_mount_point';
export { MountPointPortal } from './mount_point_portal';
export type { MountPointPortalProps } from './mount_point_portal';
export { useIfMounted } from './utils';

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/packages/react/kibana_mount'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/react-kibana-mount",
"owner": "@elastic/appex-sharedux"
}

View file

@ -12,7 +12,7 @@ import ReactDOM from 'react-dom';
import { MountPoint } from '@kbn/core/public';
import { useIfMounted } from './utils';
interface MountPointPortalProps {
export interface MountPointPortalProps {
setMountPoint: (mountPoint: MountPoint<HTMLElement>) => void;
}
@ -48,7 +48,7 @@ export const MountPointPortal: React.FC<MountPointPortalProps> = ({ children, se
el.current = undefined;
});
};
}, [setMountPoint]);
}, [setMountPoint, ifMounted]);
if (shouldRender && el.current) {
return ReactDOM.createPortal(
@ -77,7 +77,7 @@ class MountPointPortalErrorBoundary extends Component<{}, { error?: unknown }> {
if (this.state.error) {
return (
<p>
{i18n.translate('kibana-react.mountPointPortal.errorMessage', {
{i18n.translate('reactPackages.mountPointPortal.errorMessage', {
defaultMessage: 'Error rendering portal content',
})}
</p>

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/react-kibana-mount",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -15,11 +15,7 @@ import type { CoreTheme } from '@kbn/core/public';
import { toMountPoint } from './to_mount_point';
describe('toMountPoint', () => {
let euiTheme: UseEuiTheme | undefined;
beforeEach(() => {
euiTheme = undefined;
});
let euiTheme: UseEuiTheme;
const InnerComponent: FC = () => {
const theme = useEuiTheme();

View file

@ -9,12 +9,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Observable } from 'rxjs';
import { I18nProvider } from '@kbn/i18n-react';
import type { MountPoint, CoreTheme } from '@kbn/core/public';
import { KibanaThemeProvider } from '../theme';
import { KibanaContextProvider } from '@kbn/react-kibana-context';
export interface ToMountPointOptions {
theme$?: Observable<CoreTheme>;
export interface ToMountPointParams {
theme$: Observable<CoreTheme>;
}
/**
@ -22,18 +21,16 @@ export interface ToMountPointOptions {
*
* @param node to get a mount point for
*/
export const toMountPoint = (
node: React.ReactNode,
{ theme$ }: ToMountPointOptions = {}
): MountPoint => {
const content = theme$ ? <KibanaThemeProvider theme$={theme$}>{node}</KibanaThemeProvider> : node;
export const toMountPoint = (node: React.ReactNode, { theme$ }: ToMountPointParams): MountPoint => {
const mount = (element: HTMLElement) => {
ReactDOM.render(<I18nProvider>{content}</I18nProvider>, element);
ReactDOM.render(<KibanaContextProvider {...{ theme$ }}>{node}</KibanaContextProvider>, element);
return () => ReactDOM.unmountComponentAtNode(element);
};
// only used for tests and snapshots serialization
if (process.env.NODE_ENV !== 'production') {
mount.__reactMount__ = node;
}
return mount;
};

View file

@ -0,0 +1,23 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/react-kibana-context",
"@kbn/core",
"@kbn/i18n",
]
}

View file

@ -6,4 +6,11 @@
* Side Public License, v 1.
*/
export { KibanaThemeProvider } from './kibana_theme_provider';
import { KibanaThemeProvider as ThemeProvider } from '@kbn/react-kibana-context';
export type { KibanaThemeProviderProps } from '@kbn/react-kibana-context';
/**
* @deprecated use `KibanaThemeProvider` from `@kbn/react-kibana-context
*/
export const KibanaThemeProvider = ThemeProvider;

View file

@ -1,57 +0,0 @@
/*
* 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 { EuiProviderProps } from '@elastic/eui';
import { EuiProvider } from '@elastic/eui';
import createCache from '@emotion/cache';
import type { FC } from 'react';
import React, { useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import type { Observable } from 'rxjs';
import type { CoreTheme } from '@kbn/core/public';
import { getColorMode } from './utils';
interface KibanaThemeProviderProps {
theme$: Observable<CoreTheme>;
modify?: EuiProviderProps<{}>['modify'];
}
const defaultTheme: CoreTheme = {
darkMode: false,
};
const globalCache = createCache({
key: 'eui',
container: document.querySelector(`meta[name="eui-styles"]`) as HTMLElement,
});
const emotionCache = createCache({
key: 'css',
container: document.querySelector(`meta[name="emotion"]`) as HTMLElement,
});
emotionCache.compat = true;
/**
* Copied from the `kibana_react` plugin, remove once https://github.com/elastic/kibana/issues/119204 is implemented.
*/
export const KibanaThemeProvider: FC<KibanaThemeProviderProps> = ({ theme$, modify, children }) => {
const theme = useObservable(theme$, defaultTheme);
const colorMode = useMemo(() => getColorMode(theme), [theme]);
return (
<EuiProvider
colorMode={colorMode}
cache={{ default: emotionCache, global: globalCache }}
globalStyles={false}
utilityClasses={false}
modify={modify}
>
{children}
</EuiProvider>
);
};

View file

@ -1,19 +0,0 @@
/*
* 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 { getColorMode } from './utils';
describe('getColorMode', () => {
it('returns the correct `colorMode` when `darkMode` is enabled', () => {
expect(getColorMode({ darkMode: true })).toEqual('DARK');
});
it('returns the correct `colorMode` when `darkMode` is disabled', () => {
expect(getColorMode({ darkMode: false })).toEqual('LIGHT');
});
});

View file

@ -1,19 +0,0 @@
/*
* 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 { COLOR_MODES_STANDARD } from '@elastic/eui';
import type { EuiThemeColorModeStandard } from '@elastic/eui';
import type { CoreTheme } from '@kbn/core/public';
/**
* Copied from the `kibana_react` plugin, remove once https://github.com/elastic/kibana/issues/119204 is implemented.
*/
export const getColorMode = (theme: CoreTheme): EuiThemeColorModeStandard => {
return theme.darkMode ? COLOR_MODES_STANDARD.dark : COLOR_MODES_STANDARD.light;
};

View file

@ -19,6 +19,7 @@
"@kbn/utils",
"@kbn/core-logging-server-mocks",
"@kbn/core-preboot-server",
"@kbn/react-kibana-context",
],
"exclude": [
"target/**/*",

View file

@ -6,6 +6,20 @@
* Side Public License, v 1.
*/
export { wrapWithTheme } from './wrap_with_theme';
export { KibanaThemeProvider } from './kibana_theme_provider';
export type { EuiTheme } from './types';
import {
KibanaThemeProvider as ThemeProvider,
wrapWithTheme as withTheme,
} from '@kbn/react-kibana-context';
export type { KibanaThemeProviderProps } from '@kbn/react-kibana-context';
export type { UseEuiTheme as EuiTheme } from '@elastic/eui';
/**
* @deprecated use `KibanaThemeProvider` from `@kbn/react-kibana-context
*/
export const KibanaThemeProvider = ThemeProvider;
/**
* @deprecated use `wrapWithTheme` from `@kbn/react-kibana-context
*/
export const wrapWithTheme = withTheme;

View file

@ -1,86 +0,0 @@
/*
* 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 React, { FC, useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import type { ReactWrapper } from 'enzyme';
import { of, BehaviorSubject } from 'rxjs';
import { useEuiTheme } from '@elastic/eui';
import type { UseEuiTheme } from '@elastic/eui';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import type { CoreTheme } from '@kbn/core/public';
import { KibanaThemeProvider } from './kibana_theme_provider';
describe('KibanaThemeProvider', () => {
let euiTheme: UseEuiTheme | undefined;
beforeEach(() => {
euiTheme = undefined;
});
const flushPromises = async () => {
await new Promise<void>(async (resolve, reject) => {
try {
setImmediate(() => resolve());
} catch (error) {
reject(error);
}
});
};
const InnerComponent: FC = () => {
const theme = useEuiTheme();
useEffect(() => {
euiTheme = theme;
}, [theme]);
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: CoreTheme = { darkMode: true };
const wrapper = mountWithIntl(
<KibanaThemeProvider theme$={of(coreTheme)}>
<InnerComponent />
</KibanaThemeProvider>
);
await refresh(wrapper);
expect(euiTheme!.colorMode).toEqual('DARK');
});
it('propagates changes of the coreTheme observable', async () => {
const coreTheme$ = new BehaviorSubject<CoreTheme>({ darkMode: true });
const wrapper = mountWithIntl(
<KibanaThemeProvider theme$={coreTheme$}>
<InnerComponent />
</KibanaThemeProvider>
);
await refresh(wrapper);
expect(euiTheme!.colorMode).toEqual('DARK');
await act(async () => {
coreTheme$.next({ darkMode: false });
});
await refresh(wrapper);
expect(euiTheme!.colorMode).toEqual('LIGHT');
});
});

View file

@ -1,54 +0,0 @@
/*
* 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 React, { FC, useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { Observable } from 'rxjs';
import { EuiProvider, EuiProviderProps } from '@elastic/eui';
import createCache from '@emotion/cache';
import type { CoreTheme } from '@kbn/core/public';
import { getColorMode } from './utils';
interface KibanaThemeProviderProps {
theme$: Observable<CoreTheme>;
modify?: EuiProviderProps<{}>['modify'];
}
const defaultTheme: CoreTheme = {
darkMode: false,
};
const globalCache = createCache({
key: 'eui',
container: document.querySelector(`meta[name="eui-global"]`) as HTMLElement,
});
const emotionCache = createCache({
key: 'css',
container: document.querySelector(`meta[name="emotion"]`) as HTMLElement,
});
emotionCache.compat = true;
/* IMPORTANT: This code has been copied to the `interactive_setup` plugin, any changes here should be applied there too.
That copy and this comment can be removed once https://github.com/elastic/kibana/issues/119204 is implemented.*/
// IMPORTANT: This code has been copied to the `kibana_utils` plugin, to avoid cyclical dependency, any changes here should be applied there too.
export const KibanaThemeProvider: FC<KibanaThemeProviderProps> = ({ theme$, modify, children }) => {
const theme = useObservable(theme$, defaultTheme);
const colorMode = useMemo(() => getColorMode(theme), [theme]);
return (
<EuiProvider
colorMode={colorMode}
cache={{ default: emotionCache, global: globalCache }}
globalStyles={false}
utilityClasses={false}
modify={modify}
>
{children}
</EuiProvider>
);
};

View file

@ -1,19 +0,0 @@
/*
* 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 { getColorMode } from './utils';
describe('getColorMode', () => {
it('returns the correct `colorMode` when `darkMode` is enabled', () => {
expect(getColorMode({ darkMode: true })).toEqual('DARK');
});
it('returns the correct `colorMode` when `darkMode` is disabled', () => {
expect(getColorMode({ darkMode: false })).toEqual('LIGHT');
});
});

View file

@ -1,19 +0,0 @@
/*
* 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 { COLOR_MODES_STANDARD } from '@elastic/eui';
import type { EuiThemeColorModeStandard } from '@elastic/eui';
import type { CoreTheme } from '@kbn/core/public';
/* IMPORTANT: This code has been copied to the `interactive_setup` plugin, any changes here should be applied there too.
That copy and this comment can be removed once https://github.com/elastic/kibana/issues/119204 is implemented.*/
// IMPORTANT: This code has been copied to the `kibana_utils` plugin, to avoid cyclical dependency, any changes here should be applied there too.
export const getColorMode = (theme: CoreTheme): EuiThemeColorModeStandard => {
return theme.darkMode ? COLOR_MODES_STANDARD.dark : COLOR_MODES_STANDARD.light;
};

View file

@ -0,0 +1,61 @@
/*
* 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 React from 'react';
import ReactDOM from 'react-dom';
import { Observable } from 'rxjs';
import type { MountPoint, CoreTheme } from '@kbn/core/public';
import {
toMountPoint as _toMountPoint,
MountPointPortal as _MountPointPortal,
useIfMounted as _useIfMounted,
} from '@kbn/react-kibana-mount';
import { I18nProvider } from '@kbn/i18n-react';
/**
* @deprecated use `ToMountPointParams` from `@kbn/react-kibana-mount`
*/
export interface ToMountPointOptions {
theme$?: Observable<CoreTheme>;
}
/**
* @deprecated use `toMountPoint` from `@kbn/react-kibana-mount`
*/
export const toMountPoint = (
node: React.ReactNode,
{ theme$ }: ToMountPointOptions = {}
): MountPoint => {
if (theme$) {
return _toMountPoint(node, { theme$ });
}
// A `theme` should always be included in order to ensure dark mode
// is applied correctly. This code is for compatibility purposes, and
// will be removed when the deprecated usages are removed.
const mount = (element: HTMLElement) => {
ReactDOM.render(<I18nProvider>{node}</I18nProvider>, element);
return () => ReactDOM.unmountComponentAtNode(element);
};
if (process.env.NODE_ENV !== 'production') {
mount.__reactMount__ = node;
}
return mount;
};
/**
* @deprecated use `MountPointPortal` from `@kbn/react-kibana-mount`
*/
export const MountPointPortal = _MountPointPortal;
/**
* @deprecated use `useIfMounted` from `@kbn/react-kibana-mount`
*/
export const useIfMounted = _useIfMounted;

View file

@ -12,6 +12,8 @@
"@kbn/test-jest-helpers",
"@kbn/i18n",
"@kbn/i18n-react",
"@kbn/react-kibana-context",
"@kbn/react-kibana-mount",
],
"exclude": [
"target/**/*",

View file

@ -6,50 +6,7 @@
* Side Public License, v 1.
*/
import { EuiProvider, EuiProviderProps } from '@elastic/eui';
import createCache from '@emotion/cache';
import type { FC } from 'react';
import React, { useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import type { Observable } from 'rxjs';
import { KibanaThemeProvider as _KibanaThemeProvider } from '@kbn/react-kibana-context';
import type { CoreTheme } from '@kbn/core/public';
import { getColorMode } from './utils';
interface KibanaThemeProviderProps {
theme$: Observable<CoreTheme>;
modify?: EuiProviderProps<{}>['modify'];
}
const defaultTheme: CoreTheme = {
darkMode: false,
};
const globalCache = createCache({
key: 'eui',
container: document.querySelector(`meta[name="eui-global"]`) as HTMLElement,
});
const emotionCache = createCache({
key: 'css',
container: document.querySelector(`meta[name="emotion"]`) as HTMLElement,
});
emotionCache.compat = true;
/**
* Copied from the `kibana_react` plugin, to avoid cyclical dependency
*/
export const KibanaThemeProvider: FC<KibanaThemeProviderProps> = ({ theme$, modify, children }) => {
const theme = useObservable(theme$, defaultTheme);
const colorMode = useMemo(() => getColorMode(theme), [theme]);
return (
<EuiProvider
colorMode={colorMode}
cache={{ default: emotionCache, global: globalCache }}
globalStyles={false}
utilityClasses={false}
modify={modify}
>
{children}
</EuiProvider>
);
};
/** @deprecated use `KibanaThemeProvider` from `@kbn/react-kibana-context */
export const KibanaThemeProvider = _KibanaThemeProvider;

View file

@ -21,6 +21,7 @@
"@kbn/rison",
"@kbn/crypto-browser",
"@kbn/core-notifications-browser-mocks",
"@kbn/react-kibana-context",
],
"exclude": [
"target/**/*",

View file

@ -1016,6 +1016,10 @@
"@kbn/random-sampling/*": ["x-pack/packages/kbn-random-sampling/*"],
"@kbn/react-field": ["packages/kbn-react-field"],
"@kbn/react-field/*": ["packages/kbn-react-field/*"],
"@kbn/react-kibana-context": ["packages/react/kibana_context"],
"@kbn/react-kibana-context/*": ["packages/react/kibana_context/*"],
"@kbn/react-kibana-mount": ["packages/react/kibana_mount"],
"@kbn/react-kibana-mount/*": ["packages/react/kibana_mount/*"],
"@kbn/remote-clusters-plugin": ["x-pack/plugins/remote_clusters"],
"@kbn/remote-clusters-plugin/*": ["x-pack/plugins/remote_clusters/*"],
"@kbn/rendering-plugin": ["test/plugin_functional/plugins/rendering_plugin"],

View file

@ -4738,7 +4738,7 @@
"kibana-react.kibanaCodeEditor.ariaLabel": "Éditeur de code",
"kibana-react.kibanaCodeEditor.enterKeyLabel": "Entrée",
"kibana-react.kibanaCodeEditor.escapeKeyLabel": "Échap",
"kibana-react.mountPointPortal.errorMessage": "Erreur lors du rendu du contenu du portail.",
"reactPackages.mountPointPortal.errorMessage": "Erreur lors du rendu du contenu du portail.",
"kibana-react.noDataPage.cantDecide.link": "Consultez la documentation pour en savoir plus.",
"kibana-react.noDataPage.elasticAgentCard.description": "Utilisez Elastic Agent pour collecter de manière simple et unifiée les données de vos machines.",
"kibana-react.noDataPage.elasticAgentCard.noPermission.description": "Cette intégration n'est pas encore activée. Votre administrateur possède les autorisations requises pour l'activer.",

View file

@ -4739,7 +4739,7 @@
"kibana-react.kibanaCodeEditor.ariaLabel": "コードエディター",
"kibana-react.kibanaCodeEditor.enterKeyLabel": "Enter",
"kibana-react.kibanaCodeEditor.escapeKeyLabel": "Esc",
"kibana-react.mountPointPortal.errorMessage": "ポータルコンテンツのレンダリングエラー",
"reactPackages.mountPointPortal.errorMessage": "ポータルコンテンツのレンダリングエラー",
"kibana-react.noDataPage.cantDecide.link": "詳細については、ドキュメントをご確認ください。",
"kibana-react.noDataPage.elasticAgentCard.description": "Elasticエージェントを使用すると、シンプルで統一された方法でコンピューターからデータを収集するできます。",
"kibana-react.noDataPage.elasticAgentCard.noPermission.description": "この統合はまだ有効ではありません。管理者にはオンにするために必要なアクセス権があります。",

View file

@ -4738,7 +4738,7 @@
"kibana-react.kibanaCodeEditor.ariaLabel": "代码编辑器",
"kibana-react.kibanaCodeEditor.enterKeyLabel": "Enter",
"kibana-react.kibanaCodeEditor.escapeKeyLabel": "Esc",
"kibana-react.mountPointPortal.errorMessage": "呈现门户内容时出错",
"reactPackages.mountPointPortal.errorMessage": "呈现门户内容时出错",
"kibana-react.noDataPage.cantDecide.link": "请参阅我们的文档以了解更多信息。",
"kibana-react.noDataPage.elasticAgentCard.description": "使用 Elastic 代理以简单统一的方式从您的计算机中收集数据。",
"kibana-react.noDataPage.elasticAgentCard.noPermission.description": "尚未启用此集成。您的管理员具有打开它所需的权限。",

View file

@ -4779,6 +4779,14 @@
version "0.0.0"
uid ""
"@kbn/react-kibana-context@link:packages/react/kibana_context":
version "0.0.0"
uid ""
"@kbn/react-kibana-mount@link:packages/react/kibana_mount":
version "0.0.0"
uid ""
"@kbn/remote-clusters-plugin@link:x-pack/plugins/remote_clusters":
version "0.0.0"
uid ""