mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[context] Unify Contexts, deprecate others (#161914)
> Pre-req for https://github.com/elastic/kibana/issues/56406
## Summary
We've had a long-standing problem in Kibana around our use of React
context, particularly with EUI and i18n. There hasn't existed an
idempotent context structure, and that has lead to a lot of unexpected
results, (e.g. missing translations, inconsistent dark mode, excess
context providers, etc).
The biggest change coming from this PR is knowing exactly which provider
to use in a particular use case. This means, for example,
`ReactDOM.render` calls won't be missing `i18n` or `theme` due to a
missing context. It also allows consumers to use `darkMode` without
having to read the `uiSetting` themselves, instead allowing the context
to do it for them.
We also haven't been honoring the intended [`EuiProvider`
API](https://eui.elastic.co/#/utilities/provider#theming-and-global-styles)...
in some cases we've been creating and re-creating the Emotion caches,
often by copy/paste of the cache code. We've also been nesting
`EuiThemeProvider` contexts unnecessarily-- thinking we need to render a
theme provider in an isolated component-- which renders an additional
`span` element into the DOM.
This PR attempts to address this inconsistency by creating a set of
context providers divided by use case:

### `KibanaRootContextProvider`
A root context provider for Kibana. This is the top level context
provider that wraps the entire application. It is responsible for
initializing all of the other contexts and providing them to the
application. It's provided as a package for specific use cases, (e.g.
the `RenderingService`, cases where we replace the entire page content,
Storybook, testing, etc), but not intended for plugins.
### `KibanaRenderContextProvider`
A render context provider for Kibana. This context is designed to be
used with ad-hoc renders of React components, (usually with
`ReactDOM.render`).
### `KibanaThemeContextProvider`
A theme context provider for Kibana. A corollary to EUI's
`EuiThemeProvider`, it uses Kibana services to ensure the EUI Theme is
customized correctly.
### (deprecated) `KibanaStyledComponentsThemeProvider`
A styled components theme provider for Kibana. This package is supplied
for compatibility with legacy code, but should not be used in new code.
## Deprecation strategy
This PR does *not* change any use of context by consumers. It maps the
existing contexts in `kibanaReact` to the new contexts, (along with the
loose API). This means that we won't have completely fixed all of our
dark mode issues yet. But this is necessary to keep this PR focused on
the change, rather than drawing in a lot of teams to review individual
uses.
We should, however, see an immediate performance improvement in the UI
from the reduction in `EuiProvider` calls.
## Open questions
- [ ] Does it make sense to expose a `useTheme` hook from
`@kbn/react-kibana-context-theme` to replace `useEuiTheme`?
## Next steps
- [ ] Update deprecated uses to new contexts.
- [ ] Audit and update calls to `ReactDOM.render`.
- [ ] Add ESLint rule to warn for use of EUI contexts.
- [ ] Delete code from `kibanaReact`.
This commit is contained in:
parent
0ecc28b3f2
commit
477505a2dd
112 changed files with 2140 additions and 1367 deletions
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
|
@ -541,6 +541,12 @@ 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/common @elastic/appex-sharedux
|
||||
packages/react/kibana_context/render @elastic/appex-sharedux
|
||||
packages/react/kibana_context/root @elastic/appex-sharedux
|
||||
packages/react/kibana_context/styled @elastic/appex-sharedux
|
||||
packages/react/kibana_context/theme @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
|
||||
|
@ -1299,6 +1305,9 @@ x-pack/plugins/translations/translations
|
|||
# Profiling api integration testing
|
||||
x-pack/test/profiling_api_integration @elastic/profiling-ui
|
||||
|
||||
# Shared UX
|
||||
packages/react @elastic/appex-sharedux
|
||||
|
||||
####
|
||||
## These rules are always last so they take ultimate priority over everything else
|
||||
####
|
||||
|
|
|
@ -83,6 +83,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",
|
||||
|
|
|
@ -549,6 +549,12 @@
|
|||
"@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-common": "link:packages/react/kibana_context/common",
|
||||
"@kbn/react-kibana-context-render": "link:packages/react/kibana_context/render",
|
||||
"@kbn/react-kibana-context-root": "link:packages/react/kibana_context/root",
|
||||
"@kbn/react-kibana-context-styled": "link:packages/react/kibana_context/styled",
|
||||
"@kbn/react-kibana-context-theme": "link:packages/react/kibana_context/theme",
|
||||
"@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",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`#add() deletes all children of rootDomElement and renders <FatalErrorScreen /> into it: fatal error screen component 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<CoreContextProvider
|
||||
<KibanaRootContextProvider
|
||||
globalStyles={true}
|
||||
i18n={
|
||||
Object {
|
||||
|
@ -20,7 +20,7 @@ Array [
|
|||
errorInfo$={Rx.Observable}
|
||||
kibanaVersion="kibanaVersion"
|
||||
/>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRootContextProvider>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
|
|
|
@ -14,7 +14,7 @@ import type { InternalInjectedMetadataSetup } from '@kbn/core-injected-metadata-
|
|||
import type { ThemeServiceSetup } from '@kbn/core-theme-browser';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import type { FatalErrorInfo, FatalErrorsSetup } from '@kbn/core-fatal-errors-browser';
|
||||
import { CoreContextProvider } from '@kbn/core-theme-browser-internal';
|
||||
import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root';
|
||||
import { FatalErrorsScreen } from './fatal_errors_screen';
|
||||
import { getErrorInfo } from './get_error_info';
|
||||
|
||||
|
@ -95,13 +95,13 @@ export class FatalErrorsService {
|
|||
this.rootDomElement.appendChild(container);
|
||||
|
||||
render(
|
||||
<CoreContextProvider i18n={i18n} theme={theme} globalStyles={true}>
|
||||
<KibanaRootContextProvider i18n={i18n} theme={theme} globalStyles={true}>
|
||||
<FatalErrorsScreen
|
||||
buildNumber={injectedMetadata.getKibanaBuildNumber()}
|
||||
kibanaVersion={injectedMetadata.getKibanaVersion()}
|
||||
errorInfo$={this.errorInfo$}
|
||||
/>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRootContextProvider>,
|
||||
container
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
"kbn_references": [
|
||||
"@kbn/core-injected-metadata-browser-internal",
|
||||
"@kbn/core-theme-browser",
|
||||
"@kbn/core-theme-browser-internal",
|
||||
"@kbn/core-i18n-browser",
|
||||
"@kbn/core-fatal-errors-browser",
|
||||
"@kbn/i18n-react",
|
||||
|
@ -23,6 +22,7 @@
|
|||
"@kbn/core-theme-browser-mocks",
|
||||
"@kbn/test-subj-selector",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/react-kibana-context-root",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`#start() renders the GlobalToastList into the targetDomElement param 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<CoreContextProvider
|
||||
<KibanaRenderContextProvider
|
||||
i18n={
|
||||
Object {
|
||||
"Context": [Function],
|
||||
|
@ -33,7 +33,7 @@ Array [
|
|||
}
|
||||
}
|
||||
/>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRenderContextProvider>,
|
||||
<div
|
||||
test="target-dom-element"
|
||||
/>,
|
||||
|
|
|
@ -23,7 +23,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import type { OverlayStart } from '@kbn/core-overlays-browser';
|
||||
import { ThemeServiceStart } from '@kbn/core-theme-browser';
|
||||
import { CoreContextProvider } from '@kbn/core-theme-browser-internal';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
|
||||
interface ErrorToastProps {
|
||||
title: string;
|
||||
|
@ -71,7 +71,7 @@ export function showErrorDialog({
|
|||
|
||||
const modal = openModal(
|
||||
mount(
|
||||
<CoreContextProvider i18n={i18n} theme={theme}>
|
||||
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>{title}</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
@ -94,7 +94,7 @@ export function showErrorDialog({
|
|||
/>
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</CoreContextProvider>
|
||||
</KibanaRenderContextProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
|||
|
||||
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import { CoreContextProvider } from '@kbn/core-theme-browser-internal';
|
||||
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
|
||||
import type { OverlayStart } from '@kbn/core-overlays-browser';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { GlobalToastList } from './global_toast_list';
|
||||
import { ToastsApi } from './toasts_api';
|
||||
|
||||
|
@ -42,12 +42,12 @@ export class ToastsService {
|
|||
this.targetDomElement = targetDomElement;
|
||||
|
||||
render(
|
||||
<CoreContextProvider i18n={i18n} theme={theme}>
|
||||
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
|
||||
<GlobalToastList
|
||||
dismissToast={(toastId: string) => this.api!.remove(toastId)}
|
||||
toasts$={this.api!.get$()}
|
||||
/>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRenderContextProvider>,
|
||||
targetDomElement
|
||||
);
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
"@kbn/i18n",
|
||||
"@kbn/utility-types",
|
||||
"@kbn/core-theme-browser",
|
||||
"@kbn/core-theme-browser-internal",
|
||||
"@kbn/core-i18n-browser",
|
||||
"@kbn/core-ui-settings-browser",
|
||||
"@kbn/core-overlays-browser",
|
||||
|
@ -29,6 +28,7 @@
|
|||
"@kbn/core-overlays-browser-mocks",
|
||||
"@kbn/core-theme-browser-mocks",
|
||||
"@kbn/core-mount-utils-browser",
|
||||
"@kbn/react-kibana-context-render",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -11,7 +11,7 @@ Array [
|
|||
exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<CoreContextProvider
|
||||
<KibanaRenderContextProvider
|
||||
i18n={
|
||||
Object {
|
||||
"Context": [MockFunction],
|
||||
|
@ -33,7 +33,7 @@ Array [
|
|||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRenderContextProvider>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
|
@ -44,21 +44,50 @@ exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"<div dat
|
|||
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<CoreContextProvider
|
||||
<KibanaRenderContextProvider
|
||||
i18n={
|
||||
Object {
|
||||
"Context": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"children": <CoreThemeProvider
|
||||
globalStyles={false}
|
||||
theme$={
|
||||
Observable {
|
||||
"_subscribe": [Function],
|
||||
"children": <KibanaThemeProvider
|
||||
theme={
|
||||
Object {
|
||||
"theme$": Observable {
|
||||
"_subscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiErrorBoundary>
|
||||
<EuiFlyout
|
||||
onClose={[Function]}
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</EuiErrorBoundary>
|
||||
</KibanaThemeProvider>,
|
||||
},
|
||||
Object {},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": <KibanaThemeProvider
|
||||
theme={
|
||||
Object {
|
||||
"theme$": Observable {
|
||||
"_subscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiErrorBoundary>
|
||||
<EuiFlyout
|
||||
onClose={[Function]}
|
||||
>
|
||||
|
@ -67,31 +96,8 @@ Array [
|
|||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</CoreThemeProvider>,
|
||||
},
|
||||
Object {},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": <CoreThemeProvider
|
||||
globalStyles={false}
|
||||
theme$={
|
||||
Observable {
|
||||
"_subscribe": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlyout
|
||||
onClose={[Function]}
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</CoreThemeProvider>,
|
||||
</EuiErrorBoundary>
|
||||
</KibanaThemeProvider>,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -113,25 +119,54 @@ Array [
|
|||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRenderContextProvider>,
|
||||
<div />,
|
||||
],
|
||||
Array [
|
||||
<CoreContextProvider
|
||||
<KibanaRenderContextProvider
|
||||
i18n={
|
||||
Object {
|
||||
"Context": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"children": <CoreThemeProvider
|
||||
globalStyles={false}
|
||||
theme$={
|
||||
Observable {
|
||||
"_subscribe": [Function],
|
||||
"children": <KibanaThemeProvider
|
||||
theme={
|
||||
Object {
|
||||
"theme$": Observable {
|
||||
"_subscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiErrorBoundary>
|
||||
<EuiFlyout
|
||||
onClose={[Function]}
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</EuiErrorBoundary>
|
||||
</KibanaThemeProvider>,
|
||||
},
|
||||
Object {},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": <KibanaThemeProvider
|
||||
theme={
|
||||
Object {
|
||||
"theme$": Observable {
|
||||
"_subscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiErrorBoundary>
|
||||
<EuiFlyout
|
||||
onClose={[Function]}
|
||||
>
|
||||
|
@ -140,31 +175,8 @@ Array [
|
|||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</CoreThemeProvider>,
|
||||
},
|
||||
Object {},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": <CoreThemeProvider
|
||||
globalStyles={false}
|
||||
theme$={
|
||||
Observable {
|
||||
"_subscribe": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlyout
|
||||
onClose={[Function]}
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</CoreThemeProvider>,
|
||||
</EuiErrorBoundary>
|
||||
</KibanaThemeProvider>,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -186,7 +198,7 @@ Array [
|
|||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRenderContextProvider>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
|
|
|
@ -14,10 +14,10 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
|||
import { Subject } from 'rxjs';
|
||||
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import { CoreContextProvider } from '@kbn/core-theme-browser-internal';
|
||||
import type { MountPoint, OverlayRef } from '@kbn/core-mount-utils-browser';
|
||||
import { MountWrapper } from '@kbn/core-mount-utils-browser-internal';
|
||||
import type { OverlayFlyoutOpenOptions, OverlayFlyoutStart } from '@kbn/core-overlays-browser';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
|
||||
/**
|
||||
* A FlyoutRef is a reference to an opened flyout panel. It offers methods to
|
||||
|
@ -101,11 +101,11 @@ export class FlyoutService {
|
|||
};
|
||||
|
||||
render(
|
||||
<CoreContextProvider i18n={i18n} theme={theme}>
|
||||
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
|
||||
<EuiFlyout {...options} onClose={onCloseFlyout}>
|
||||
<MountWrapper mount={mount} className="kbnOverlayMountWrapper" />
|
||||
</EuiFlyout>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRenderContextProvider>,
|
||||
this.targetDomElement
|
||||
);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,6 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
|||
import { Subject } from 'rxjs';
|
||||
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import { CoreContextProvider } from '@kbn/core-theme-browser-internal';
|
||||
import type { MountPoint, OverlayRef } from '@kbn/core-mount-utils-browser';
|
||||
import { MountWrapper } from '@kbn/core-mount-utils-browser-internal';
|
||||
import type {
|
||||
|
@ -23,6 +22,7 @@ import type {
|
|||
OverlayModalOpenOptions,
|
||||
OverlayModalStart,
|
||||
} from '@kbn/core-overlays-browser';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
|
||||
/**
|
||||
* A ModalRef is a reference to an opened modal. It offers methods to
|
||||
|
@ -87,11 +87,11 @@ export class ModalService {
|
|||
this.activeModal = modal;
|
||||
|
||||
render(
|
||||
<CoreContextProvider i18n={i18n} theme={theme}>
|
||||
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
|
||||
<EuiModal {...options} onClose={() => modal.close()}>
|
||||
<MountWrapper mount={mount} className="kbnOverlayMountWrapper" />
|
||||
</EuiModal>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRenderContextProvider>,
|
||||
targetDomElement
|
||||
);
|
||||
|
||||
|
@ -147,9 +147,9 @@ export class ModalService {
|
|||
};
|
||||
|
||||
render(
|
||||
<CoreContextProvider i18n={i18n} theme={theme}>
|
||||
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
|
||||
<EuiConfirmModal {...props} />
|
||||
</CoreContextProvider>,
|
||||
</KibanaRenderContextProvider>,
|
||||
targetDomElement
|
||||
);
|
||||
});
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
"kbn_references": [
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/core-theme-browser",
|
||||
"@kbn/core-theme-browser-internal",
|
||||
"@kbn/core-mount-utils-browser-internal",
|
||||
"@kbn/core-i18n-browser",
|
||||
"@kbn/core-ui-settings-browser",
|
||||
|
@ -25,6 +24,7 @@
|
|||
"@kbn/core-mount-utils-browser",
|
||||
"@kbn/core-theme-browser-mocks",
|
||||
"@kbn/i18n",
|
||||
"@kbn/react-kibana-context-render",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -106,7 +106,7 @@ describe('RenderingService#start', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
it('adds global styles via `CoreContextProvider` `globalStyles` configuration', () => {
|
||||
it('adds global styles via `KibanaRootRenderingContext` `globalStyles` configuration', () => {
|
||||
startService();
|
||||
expect(document.querySelector(`style[data-emotion="eui-styles-global"]`)).toBeDefined();
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import { pairwise, startWith } from 'rxjs/operators';
|
|||
|
||||
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import { CoreContextProvider } from '@kbn/core-theme-browser-internal';
|
||||
import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root';
|
||||
import type { OverlayStart } from '@kbn/core-overlays-browser';
|
||||
import type { InternalApplicationStart } from '@kbn/core-application-browser-internal';
|
||||
import type { InternalChromeStart } from '@kbn/core-chrome-browser-internal';
|
||||
|
@ -51,7 +51,7 @@ export class RenderingService {
|
|||
});
|
||||
|
||||
ReactDOM.render(
|
||||
<CoreContextProvider i18n={i18n} theme={theme} globalStyles={true}>
|
||||
<KibanaRootContextProvider i18n={i18n} theme={theme} globalStyles={true}>
|
||||
<>
|
||||
{/* Fixed headers */}
|
||||
{chromeHeader}
|
||||
|
@ -68,7 +68,7 @@ export class RenderingService {
|
|||
{appComponent}
|
||||
</AppWrapper>
|
||||
</>
|
||||
</CoreContextProvider>,
|
||||
</KibanaRootContextProvider>,
|
||||
targetDomElement
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
"@kbn/core-application-common",
|
||||
"@kbn/core-application-browser-internal",
|
||||
"@kbn/core-theme-browser",
|
||||
"@kbn/core-theme-browser-internal",
|
||||
"@kbn/core-i18n-browser",
|
||||
"@kbn/core-overlays-browser",
|
||||
"@kbn/core-chrome-browser-internal",
|
||||
|
@ -25,6 +24,7 @@
|
|||
"@kbn/core-overlays-browser-mocks",
|
||||
"@kbn/core-theme-browser-mocks",
|
||||
"@kbn/core-i18n-browser-mocks",
|
||||
"@kbn/react-kibana-context-root",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -9,4 +9,3 @@
|
|||
export { ThemeService } from './src/theme_service';
|
||||
export { CoreThemeProvider } from './src/core_theme_provider';
|
||||
export type { ThemeServiceSetupDeps } from './src/theme_service';
|
||||
export { CoreContextProvider } from './src/core_context_provider';
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -1,40 +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 } from 'react';
|
||||
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import { CoreThemeProvider } from './core_theme_provider';
|
||||
|
||||
interface CoreContextProviderProps {
|
||||
theme: ThemeServiceStart;
|
||||
i18n: I18nStart;
|
||||
globalStyles?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* utility component exposing all the context providers required by core when integrating with react
|
||||
**/
|
||||
export const CoreContextProvider: FC<CoreContextProviderProps> = ({
|
||||
i18n,
|
||||
theme,
|
||||
children,
|
||||
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;
|
||||
return (
|
||||
<i18n.Context>
|
||||
<CoreThemeProvider theme$={theme.theme$} globalStyles={includeGlobalStyles}>
|
||||
{children}
|
||||
</CoreThemeProvider>
|
||||
</i18n.Context>
|
||||
);
|
||||
};
|
|
@ -6,61 +6,25 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import React, { type FC } from 'react';
|
||||
import { CoreTheme } from '@kbn/core-theme-browser/src/types';
|
||||
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
|
||||
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, EUI_STYLES_UTILS } 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_STYLES_GLOBAL,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_GLOBAL}"]`) as HTMLElement,
|
||||
});
|
||||
globalCache.compat = true;
|
||||
const utilitiesCache = createCache({
|
||||
key: EUI_STYLES_UTILS,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_UTILS}"]`) as HTMLElement,
|
||||
});
|
||||
utilitiesCache.compat = true;
|
||||
const emotionCache = createCache({
|
||||
key: 'css',
|
||||
container: document.querySelector('meta[name="emotion"]') as HTMLElement,
|
||||
});
|
||||
emotionCache.compat = true;
|
||||
|
||||
/**
|
||||
* 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-theme
|
||||
*/
|
||||
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, utility: utilitiesCache }}
|
||||
>
|
||||
{children}
|
||||
</EuiProvider>
|
||||
);
|
||||
};
|
||||
children,
|
||||
}) => (
|
||||
<KibanaThemeProvider {...{ theme: { theme$ }, globalStyles }}>{children}</KibanaThemeProvider>
|
||||
);
|
||||
|
|
|
@ -13,12 +13,11 @@
|
|||
"**/*.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-theme",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -6,44 +6,38 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EUI_STYLES_GLOBAL, EUI_STYLES_UTILS } from '@kbn/core-base-common';
|
||||
import { EuiProvider } from '@elastic/eui';
|
||||
import createCache from '@emotion/cache';
|
||||
import React, { useEffect } from 'react';
|
||||
import type { DecoratorFn } from '@storybook/react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
import 'core_styles';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { CoreTheme } from '@kbn/core-theme-browser';
|
||||
import { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
|
||||
const theme$ = new BehaviorSubject<CoreTheme>({ darkMode: false });
|
||||
|
||||
const i18n: I18nStart = {
|
||||
Context: ({ children }) => <I18nProvider>{children}</I18nProvider>,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_STYLES_GLOBAL,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_GLOBAL}"]`) as HTMLElement,
|
||||
});
|
||||
globalCache.compat = true;
|
||||
const utilitiesCache = createCache({
|
||||
key: EUI_STYLES_UTILS,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_UTILS}"]`) as HTMLElement,
|
||||
});
|
||||
utilitiesCache.compat = true;
|
||||
const emotionCache = createCache({
|
||||
key: 'css',
|
||||
container: document.querySelector('meta[name="emotion"]') as HTMLElement,
|
||||
});
|
||||
emotionCache.compat = true;
|
||||
|
||||
useEffect(() => {
|
||||
theme$.next({ darkMode: colorMode === 'dark' });
|
||||
}, [colorMode]);
|
||||
|
||||
return (
|
||||
<EuiProvider
|
||||
colorMode={colorMode}
|
||||
cache={{ default: emotionCache, global: globalCache, utility: utilitiesCache }}
|
||||
>
|
||||
<KibanaRenderContextProvider {...{ theme: { theme$ }, i18n }}>
|
||||
{storyFn()}
|
||||
</EuiProvider>
|
||||
</KibanaRenderContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const decorators = [EuiProviderDecorator];
|
||||
export const decorators = [KibanaContextDecorator];
|
||||
|
|
|
@ -16,7 +16,10 @@
|
|||
"@kbn/ui-shared-deps-src",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/core-base-common",
|
||||
"@kbn/core-theme-browser",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/core-i18n-browser",
|
||||
"@kbn/react-kibana-context-render",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
|
|
22
packages/react/kibana_context/README.mdx
Normal file
22
packages/react/kibana_context/README.mdx
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: react/context
|
||||
slug: /react/context
|
||||
title: React Contexts in Kibana
|
||||
description: Kibana uses React Context to manage several global states. This is a collection of packages supporting those states.
|
||||
tags: ['shared-ux', 'react', 'context']
|
||||
date: 2023-07-25
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Kibana uses React Context to manage several global states. Those states have been divided into several reusable components in relevant packages.
|
||||
|
||||

|
||||
|
||||
- `KibanaRootContextProvider` - A root context provider for Kibana. This is the top level context provider that wraps the entire application. It is responsible for initializing all of the other contexts and providing them to the application.
|
||||
- `KibanaRenderContextProvider` - A render context provider for Kibana. This context is designed to be used with ad-hoc renders of React components, (usually with `ReactDOM.render`).
|
||||
- `KibanaThemeContextProvider` - A theme context provider for Kibana. A corollary to EUI's `EuiThemeProvider`, it uses Kibana services to ensure the EUI Theme is customized correctly.
|
||||
|
||||
## Deprecated Context Providers
|
||||
|
||||
- `KibanaStyledComponentsThemeProvider` - A styled components theme provider for Kibana. This package is supplied for compatibility with legacy code, but should not be used in new code.
|
BIN
packages/react/kibana_context/assets/diagram.png
Normal file
BIN
packages/react/kibana_context/assets/diagram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
13
packages/react/kibana_context/common/README.mdx
Normal file
13
packages/react/kibana_context/common/README.mdx
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
id: react/context/common
|
||||
slug: /react/context/common
|
||||
title: React Context - Common Types and Utilities
|
||||
description: The React contexts Kibana uses have a lot of common types and utilities. This package is a collection of those types and utilities.
|
||||
tags: ['shared-ux', 'react', 'context']
|
||||
date: 2023-07-25
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
This package contains common types and utilities used by the different React context providers in Kibana.
|
||||
|
|
@ -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', () => {
|
|
@ -8,12 +8,13 @@
|
|||
|
||||
import { COLOR_MODES_STANDARD } from '@elastic/eui';
|
||||
import type { EuiThemeColorModeStandard } from '@elastic/eui';
|
||||
|
||||
import type { CoreTheme } from '@kbn/core/public';
|
||||
import type { KibanaTheme } from './types';
|
||||
|
||||
/**
|
||||
* Copied from the `kibana_react` plugin, to avoid cyclical dependency
|
||||
* Given a `KibanaTheme`, provide a color mode for use with EUI.
|
||||
* @param theme KibanaTheme
|
||||
* @returns EuiThemeColorModeStandard
|
||||
*/
|
||||
export const getColorMode = (theme: CoreTheme): EuiThemeColorModeStandard => {
|
||||
export const getColorMode = (theme: KibanaTheme): EuiThemeColorModeStandard => {
|
||||
return theme.darkMode ? COLOR_MODES_STANDARD.dark : COLOR_MODES_STANDARD.light;
|
||||
};
|
20
packages/react/kibana_context/common/index.ts
Normal file
20
packages/react/kibana_context/common/index.ts
Normal 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.
|
||||
*/
|
||||
|
||||
export { getColorMode } from './color_mode';
|
||||
export type { KibanaTheme, ThemeServiceStart } from './types';
|
||||
|
||||
import type { KibanaTheme } from './types';
|
||||
|
||||
/**
|
||||
* The default `KibanaTheme` for use in Storybook, Jest, or initialization. At
|
||||
* runtime, the theme should always be provided by the `ThemeService`.
|
||||
*/
|
||||
export const defaultTheme: KibanaTheme = {
|
||||
darkMode: false,
|
||||
};
|
|
@ -6,7 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { wrapWithTheme } from './wrap_with_theme';
|
||||
export { KibanaThemeProvider } from './kibana_theme_provider';
|
||||
export { useKibanaTheme } from './use_theme';
|
||||
export type { EuiTheme } from './types';
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/packages/react/kibana_context/common'],
|
||||
};
|
5
packages/react/kibana_context/common/kibana.jsonc
Normal file
5
packages/react/kibana_context/common/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/react-kibana-context-common",
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
6
packages/react/kibana_context/common/package.json
Normal file
6
packages/react/kibana_context/common/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/react-kibana-context-common",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
17
packages/react/kibana_context/common/tsconfig.json
Normal file
17
packages/react/kibana_context/common/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": []
|
||||
}
|
33
packages/react/kibana_context/common/types.ts
Normal file
33
packages/react/kibana_context/common/types.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { Observable } from 'rxjs';
|
||||
|
||||
// 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
|
||||
|
||||
/**
|
||||
* The representation of the Kibana theme, (not to be confused with the EUI theme).
|
||||
*/
|
||||
export interface KibanaTheme {
|
||||
/** is dark mode enabled or not */
|
||||
readonly darkMode: boolean;
|
||||
}
|
||||
|
||||
// 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
|
||||
/**
|
||||
* The `ThemeService` start contract, provided to plugins during the `start` lifecycle.
|
||||
*/
|
||||
export interface ThemeServiceStart {
|
||||
theme$: Observable<KibanaTheme>;
|
||||
}
|
31
packages/react/kibana_context/render/README.mdx
Normal file
31
packages/react/kibana_context/render/README.mdx
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: react/context/render
|
||||
slug: /react/context/render
|
||||
title: React Context - Rendering Provider
|
||||
description: This context provider is used to render a new component tree _without_ the initialization of EUI or Emotion. This provider is typically used with `ReactDOM.render()` calls.
|
||||
tags: ['shared-ux', 'react', 'context']
|
||||
date: 2023-07-25
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
The `KibanaRenderContextProvider` is designed to be used with ad-hoc renders of React components, (usually with `ReactDOM.render`).
|
||||
|
||||
When Kibana starts, the `KibanaRootContextProvider` is used by the `RenderService` to initialize EUI and Emotion... it should only be rendered _once_. Still, there are times when you need to render a new component tree and need things like `i18n` and the current theme to be made available in a consistent way. The `KibanaRenderContextProvider` is designed to be used in these cases. In addition, it allows us to abstract away any changes or other complexities with React context that we may introduce in the future.
|
||||
|
||||
```ts
|
||||
import ReactDOM from 'react-dom';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
|
||||
import { MyComponent } from './my_component';
|
||||
|
||||
const App = ({i18n, theme}: CoreStart) => {
|
||||
return (
|
||||
<KibanaRenderContextProvider {...{i18n, theme}}>
|
||||
<MyComponent />
|
||||
</KibanaRenderContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('some_node'));
|
||||
```
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { UseEuiTheme } from '@elastic/eui';
|
||||
|
||||
export type EuiTheme = UseEuiTheme;
|
||||
export {
|
||||
KibanaRenderContextProvider,
|
||||
type KibanaRenderContextProviderProps,
|
||||
} from './render_provider';
|
13
packages/react/kibana_context/render/jest.config.js
Normal file
13
packages/react/kibana_context/render/jest.config.js
Normal 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/render'],
|
||||
};
|
5
packages/react/kibana_context/render/kibana.jsonc
Normal file
5
packages/react/kibana_context/render/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/react-kibana-context-render",
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
6
packages/react/kibana_context/render/package.json
Normal file
6
packages/react/kibana_context/render/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/react-kibana-context-render",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
40
packages/react/kibana_context/render/render_provider.tsx
Normal file
40
packages/react/kibana_context/render/render_provider.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import {
|
||||
KibanaThemeProvider,
|
||||
type KibanaThemeProviderProps,
|
||||
} from '@kbn/react-kibana-context-theme';
|
||||
|
||||
/** Props for the KibanaContextProvider */
|
||||
export interface KibanaRenderContextProviderProps extends KibanaThemeProviderProps {
|
||||
/** The `I18nStart` API from `CoreStart`. */
|
||||
i18n: I18nStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `KibanaRenderContextProvider` provides the necessary context for an out-of-current
|
||||
* React render, such as using `ReactDOM.render()`.
|
||||
*/
|
||||
export const KibanaRenderContextProvider: FC<KibanaRenderContextProviderProps> = ({
|
||||
children,
|
||||
i18n,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<i18n.Context>
|
||||
<KibanaThemeProvider {...props}>
|
||||
<EuiErrorBoundary>{children}</EuiErrorBoundary>
|
||||
</KibanaThemeProvider>
|
||||
</i18n.Context>
|
||||
);
|
||||
};
|
22
packages/react/kibana_context/render/tsconfig.json
Normal file
22
packages/react/kibana_context/render/tsconfig.json
Normal 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/core-i18n-browser",
|
||||
"@kbn/react-kibana-context-theme",
|
||||
]
|
||||
}
|
47
packages/react/kibana_context/root/README.mdx
Normal file
47
packages/react/kibana_context/root/README.mdx
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
id: react/context/root
|
||||
slug: /react/context/root
|
||||
title: React Context - Root
|
||||
description: This context provider is used only used by the very base root of Kibana. Unless you're writing tests, a Storybook, or working in core code, you likely don't need this.
|
||||
tags: ['shared-ux', 'react', 'context']
|
||||
date: 2023-07-25
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
This package contains a root context provider for Kibana rendering. It handles operations that should only happen _once_ when the browser loads a page.
|
||||
|
||||
While it would be safer to isolate this in a `core` package, we need to use it in other contexts-- like Storybook and Jest.
|
||||
|
||||
```ts
|
||||
import React, { useEffect } from 'react';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root';
|
||||
|
||||
import type { DecoratorFn } from '@storybook/react';
|
||||
import type { CoreTheme } from '@kbn/core-theme-browser';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
|
||||
const theme$ = new BehaviorSubject<CoreTheme>({ darkMode: false });
|
||||
|
||||
const i18n: I18nStart = {
|
||||
Context: ({ children }) => <I18nProvider>{children}</I18nProvider>,
|
||||
};
|
||||
|
||||
export const KibanaContextDecorator: DecoratorFn = (storyFn, { globals }) => {
|
||||
const colorMode = globals.euiTheme === 'v8.dark' ? 'dark' : 'light';
|
||||
|
||||
useEffect(() => {
|
||||
theme$.next({ darkMode: colorMode === 'dark' });
|
||||
}, [colorMode]);
|
||||
|
||||
return (
|
||||
<KibanaRootContextProvider {...{ theme: { theme$ }, i18n }}>
|
||||
{storyFn()}
|
||||
</KibanaRootContextProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
92
packages/react/kibana_context/root/eui_provider.test.tsx
Normal file
92
packages/react/kibana_context/root/eui_provider.test.tsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 { useEuiTheme } from '@elastic/eui';
|
||||
import type { ReactWrapper } from 'enzyme';
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { BehaviorSubject, of } from 'rxjs';
|
||||
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
||||
import type { KibanaTheme } from '@kbn/react-kibana-context-common';
|
||||
import { KibanaEuiProvider } from './eui_provider';
|
||||
|
||||
describe('KibanaEuiProvider', () => {
|
||||
let euiTheme: ReturnType<typeof useEuiTheme> | undefined;
|
||||
let consoleWarnMock: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
euiTheme = undefined;
|
||||
consoleWarnMock = jest.spyOn(global.console, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
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: KibanaTheme = { darkMode: true };
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<KibanaEuiProvider theme={{ theme$: of(coreTheme) }}>
|
||||
<InnerComponent />
|
||||
</KibanaEuiProvider>
|
||||
);
|
||||
|
||||
await refresh(wrapper);
|
||||
|
||||
expect(euiTheme!.colorMode).toEqual('DARK');
|
||||
expect(consoleWarnMock).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('propagates changes of the coreTheme observable', async () => {
|
||||
const coreTheme$ = new BehaviorSubject<KibanaTheme>({ darkMode: true });
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<KibanaEuiProvider theme={{ theme$: coreTheme$ }}>
|
||||
<InnerComponent />
|
||||
</KibanaEuiProvider>
|
||||
);
|
||||
|
||||
await refresh(wrapper);
|
||||
|
||||
expect(euiTheme!.colorMode).toEqual('DARK');
|
||||
|
||||
await act(async () => {
|
||||
coreTheme$.next({ darkMode: false });
|
||||
});
|
||||
|
||||
await refresh(wrapper);
|
||||
|
||||
expect(euiTheme!.colorMode).toEqual('LIGHT');
|
||||
expect(consoleWarnMock).not.toBeCalled();
|
||||
});
|
||||
});
|
75
packages/react/kibana_context/root/eui_provider.tsx
Normal file
75
packages/react/kibana_context/root/eui_provider.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 createCache from '@emotion/cache';
|
||||
|
||||
import { EuiProvider, EuiProviderProps } from '@elastic/eui';
|
||||
import { EUI_STYLES_GLOBAL, EUI_STYLES_UTILS } from '@kbn/core-base-common';
|
||||
import { getColorMode, defaultTheme } from '@kbn/react-kibana-context-common';
|
||||
import { ThemeServiceStart } from '@kbn/react-kibana-context-common';
|
||||
|
||||
/**
|
||||
* Props for the KibanaEuiProvider.
|
||||
*/
|
||||
export interface KibanaEuiProviderProps extends Pick<EuiProviderProps<{}>, 'modify' | 'colorMode'> {
|
||||
theme: ThemeServiceStart;
|
||||
globalStyles?: boolean;
|
||||
}
|
||||
|
||||
// Set up the caches.
|
||||
// https://eui.elastic.co/#/utilities/provider#cache-location
|
||||
const emotionCache = createCache({
|
||||
key: 'css',
|
||||
container: document.querySelector('meta[name="emotion"]') as HTMLElement,
|
||||
});
|
||||
|
||||
const globalCache = createCache({
|
||||
key: EUI_STYLES_GLOBAL,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_GLOBAL}"]`) as HTMLElement,
|
||||
});
|
||||
|
||||
const utilitiesCache = createCache({
|
||||
key: EUI_STYLES_UTILS,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_UTILS}"]`) as HTMLElement,
|
||||
});
|
||||
|
||||
// Enable "compat mode" in Emotion caches.
|
||||
emotionCache.compat = true;
|
||||
globalCache.compat = true;
|
||||
utilitiesCache.compat = true;
|
||||
|
||||
const cache = { default: emotionCache, global: globalCache, utility: utilitiesCache };
|
||||
|
||||
/**
|
||||
* Prepares and returns a configured `EuiProvider` for use in Kibana roots.
|
||||
*/
|
||||
export const KibanaEuiProvider: FC<KibanaEuiProviderProps> = ({
|
||||
theme: { theme$ },
|
||||
globalStyles: globalStylesProp,
|
||||
colorMode: colorModeProp,
|
||||
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;
|
||||
|
||||
return (
|
||||
<EuiProvider {...{ cache, colorMode, globalStyles, utilityClasses: globalStyles }}>
|
||||
{children}
|
||||
</EuiProvider>
|
||||
);
|
||||
};
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { KibanaThemeProvider } from './kibana_theme_provider';
|
||||
export { KibanaRootContextProvider, type KibanaRootContextProviderProps } from './root_provider';
|
13
packages/react/kibana_context/root/jest.config.js
Normal file
13
packages/react/kibana_context/root/jest.config.js
Normal 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/root'],
|
||||
};
|
5
packages/react/kibana_context/root/kibana.jsonc
Normal file
5
packages/react/kibana_context/root/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/react-kibana-context-root",
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
6
packages/react/kibana_context/root/package.json
Normal file
6
packages/react/kibana_context/root/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/react-kibana-context-root",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -13,14 +13,18 @@ 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';
|
||||
import type { KibanaTheme } from '@kbn/react-kibana-context-common';
|
||||
import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks';
|
||||
import { KibanaRootContextProvider } from './root_provider';
|
||||
import { I18nStart } from '@kbn/core-i18n-browser';
|
||||
|
||||
describe('KibanaThemeProvider', () => {
|
||||
describe('KibanaRootContextProvider', () => {
|
||||
let euiTheme: UseEuiTheme | undefined;
|
||||
let i18nMock: I18nStart;
|
||||
|
||||
beforeEach(() => {
|
||||
euiTheme = undefined;
|
||||
i18nMock = i18nServiceMock.createStartContract();
|
||||
});
|
||||
|
||||
const flushPromises = async () => {
|
||||
|
@ -49,12 +53,12 @@ describe('KibanaThemeProvider', () => {
|
|||
};
|
||||
|
||||
it('exposes the EUI theme provider', async () => {
|
||||
const coreTheme: CoreTheme = { darkMode: true };
|
||||
const coreTheme: KibanaTheme = { darkMode: true };
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<KibanaThemeProvider theme$={of(coreTheme)}>
|
||||
<KibanaRootContextProvider i18n={i18nMock} theme={{ theme$: of(coreTheme) }}>
|
||||
<InnerComponent />
|
||||
</KibanaThemeProvider>
|
||||
</KibanaRootContextProvider>
|
||||
);
|
||||
|
||||
await refresh(wrapper);
|
||||
|
@ -63,12 +67,12 @@ describe('KibanaThemeProvider', () => {
|
|||
});
|
||||
|
||||
it('propagates changes of the coreTheme observable', async () => {
|
||||
const coreTheme$ = new BehaviorSubject<CoreTheme>({ darkMode: true });
|
||||
const coreTheme$ = new BehaviorSubject<KibanaTheme>({ darkMode: true });
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<KibanaThemeProvider theme$={coreTheme$}>
|
||||
<KibanaRootContextProvider i18n={i18nMock} theme={{ theme$: coreTheme$ }}>
|
||||
<InnerComponent />
|
||||
</KibanaThemeProvider>
|
||||
</KibanaRootContextProvider>
|
||||
);
|
||||
|
||||
await refresh(wrapper);
|
42
packages/react/kibana_context/root/root_provider.tsx
Normal file
42
packages/react/kibana_context/root/root_provider.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { KibanaEuiProvider, type KibanaEuiProviderProps } from './eui_provider';
|
||||
|
||||
/** Props for the KibanaRootContextProvider */
|
||||
export interface KibanaRootContextProviderProps extends KibanaEuiProviderProps {
|
||||
/** The `I18nStart` API from `CoreStart`. */
|
||||
i18n: I18nStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `KibanaRootContextProvider` provides the necessary context at the root of Kibana, including
|
||||
* initialization and the theme and i18n contexts. This context should only be used _once_, and
|
||||
* at the _very top_ of the application root, rendered by the `RenderingService`.
|
||||
*
|
||||
* While this context is exposed for edge cases and tooling, (e.g. Storybook, Jest, etc.), it should
|
||||
* _not_ be used in applications. Instead, applications should choose the context that makes the
|
||||
* most sense for the problem they are trying to solve:
|
||||
*
|
||||
* - Consider `KibanaRenderContextProvider` for rendering components outside the current tree, (e.g.
|
||||
* with `ReactDOM.render`).
|
||||
* - Consider `KibanaThemeContextProvider` for altering the theme of a component or tree of components.
|
||||
*
|
||||
*/
|
||||
export const KibanaRootContextProvider: FC<KibanaRootContextProviderProps> = ({
|
||||
children,
|
||||
i18n,
|
||||
...props
|
||||
}) => (
|
||||
<KibanaEuiProvider {...props}>
|
||||
<i18n.Context>{children}</i18n.Context>
|
||||
</KibanaEuiProvider>
|
||||
);
|
25
packages/react/kibana_context/root/tsconfig.json
Normal file
25
packages/react/kibana_context/root/tsconfig.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/react-kibana-context-common",
|
||||
"@kbn/core-i18n-browser-mocks",
|
||||
"@kbn/core-i18n-browser",
|
||||
"@kbn/core-base-common",
|
||||
]
|
||||
}
|
15
packages/react/kibana_context/styled/README.mdx
Normal file
15
packages/react/kibana_context/styled/README.mdx
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
id: react/context/styled
|
||||
slug: /react/context/styled
|
||||
title: React Context - `styled-components` provider
|
||||
description: Before `emotion` was introduced, some components used `styled-components` to easily apply EUI variables and apply style. This package is for compatibility with those components and should _not_ be used in new code.
|
||||
tags: ['shared-ux', 'react', 'context']
|
||||
date: 2023-07-25
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Before `emotion` was introduced, some components used `styled-components` to easily apply EUI variables and apply style. This package is an isolated location for this code and included for compatibility with those components.
|
||||
|
||||
It should _not_ be used in new code.
|
||||
|
26
packages/react/kibana_context/styled/index.ts
Normal file
26
packages/react/kibana_context/styled/index.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 {
|
||||
/** @deprecated All Kibana components need to migrate to Emotion. */
|
||||
createGlobalStyle,
|
||||
/** @deprecated All Kibana components need to migrate to Emotion. */
|
||||
type EuiTheme,
|
||||
/** @deprecated All Kibana components need to migrate to Emotion. */
|
||||
css,
|
||||
/** @deprecated All Kibana components need to migrate to Emotion. */
|
||||
euiStyled,
|
||||
/** @deprecated All Kibana components need to migrate to Emotion. */
|
||||
keyframes,
|
||||
/** @deprecated All Kibana components need to migrate to Emotion. */
|
||||
withTheme,
|
||||
/** @deprecated All Kibana components need to migrate to Emotion. */
|
||||
KibanaStyledComponentsThemeProvider,
|
||||
/** @deprecated All Kibana components need to migrate to Emotion. */
|
||||
KibanaStyledComponentsThemeProviderDecorator,
|
||||
} from './styled_provider';
|
13
packages/react/kibana_context/styled/jest.config.js
Normal file
13
packages/react/kibana_context/styled/jest.config.js
Normal 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/styled'],
|
||||
};
|
5
packages/react/kibana_context/styled/kibana.jsonc
Normal file
5
packages/react/kibana_context/styled/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/react-kibana-context-styled",
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
6
packages/react/kibana_context/styled/package.json
Normal file
6
packages/react/kibana_context/styled/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/react-kibana-context-styled",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
84
packages/react/kibana_context/styled/styled_provider.tsx
Normal file
84
packages/react/kibana_context/styled/styled_provider.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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 { DecoratorFn } from '@storybook/react';
|
||||
import React from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import * as styledComponents from 'styled-components';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import { ThemedStyledComponentsModule, ThemeProvider, ThemeProviderProps } from 'styled-components';
|
||||
import { euiThemeVars, euiLightVars, euiDarkVars } from '@kbn/ui-theme';
|
||||
|
||||
/**
|
||||
* A `deprecated` structure representing a Kibana theme containing variables from the current EUI theme.
|
||||
*/
|
||||
export interface EuiTheme {
|
||||
/** EUI theme vars that automaticall adjust to light and dark mode. */
|
||||
eui: typeof euiThemeVars;
|
||||
/** True if the theme is in "dark" mode, false otherwise. */
|
||||
darkMode: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `styled-components` `ThemeProvider` that incorporates EUI dark mode.
|
||||
*/
|
||||
const KibanaStyledComponentsThemeProvider = <
|
||||
OuterTheme extends styledComponents.DefaultTheme = styledComponents.DefaultTheme
|
||||
>({
|
||||
darkMode = false,
|
||||
...otherProps
|
||||
}: Omit<ThemeProviderProps<OuterTheme, OuterTheme & EuiTheme>, 'theme'> & {
|
||||
darkMode?: boolean;
|
||||
}) => (
|
||||
<ThemeProvider
|
||||
{...otherProps}
|
||||
theme={(outerTheme?: OuterTheme) => ({
|
||||
...outerTheme,
|
||||
eui: darkMode ? euiDarkVars : euiLightVars,
|
||||
darkMode,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
/**
|
||||
* Storybook decorator using the EUI theme provider. Uses the value from
|
||||
* `globals` provided by the Storybook theme switcher.
|
||||
*
|
||||
* @deprecated All Kibana components need to migrate to Emotion.
|
||||
*/
|
||||
export const KibanaStyledComponentsThemeProviderDecorator: DecoratorFn = (storyFn, { globals }) => {
|
||||
const darkMode = globals.euiTheme === 'v8.dark' || globals.euiTheme === 'v7.dark';
|
||||
|
||||
return (
|
||||
<KibanaStyledComponentsThemeProvider darkMode={darkMode}>
|
||||
{storyFn()}
|
||||
</KibanaStyledComponentsThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const {
|
||||
/** see https://styled-components.com/docs/api#styled */
|
||||
default: euiStyled,
|
||||
/** see https://styled-components.com/docs/api#css-prop */
|
||||
css,
|
||||
/** see https://styled-components.com/docs/api#createglobalstyle */
|
||||
createGlobalStyle,
|
||||
/** see https://styled-components.com/docs/api#keyframes */
|
||||
keyframes,
|
||||
/** see https://styled-components.com/docs/api#withtheme */
|
||||
withTheme,
|
||||
} = styledComponents as unknown as ThemedStyledComponentsModule<EuiTheme>;
|
||||
|
||||
export {
|
||||
css,
|
||||
euiStyled,
|
||||
KibanaStyledComponentsThemeProvider,
|
||||
createGlobalStyle,
|
||||
keyframes,
|
||||
withTheme,
|
||||
};
|
21
packages/react/kibana_context/styled/tsconfig.json
Normal file
21
packages/react/kibana_context/styled/tsconfig.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/ui-theme",
|
||||
]
|
||||
}
|
66
packages/react/kibana_context/theme/README.mdx
Normal file
66
packages/react/kibana_context/theme/README.mdx
Normal file
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
id: react/context/theme
|
||||
slug: /react/context/theme
|
||||
title: React Context - Theme
|
||||
description: This context allows a one to alter the theme for a given component. This is likely to be the context that is used most often.
|
||||
tags: ['shared-ux', 'react', 'context']
|
||||
date: 2023-07-25
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
This package contains a "theming" context for Kibana. A corollary to EUI's `EuiThemeProvider`, it uses Kibana services to ensure the EUI Theme is customized correctly.
|
||||
|
||||
Up until now, there has been some confusion as to the difference between `EuiThemeProvider` and `EuiProvider`. They've been used interchangeably, which created some unnoticed
|
||||
side effects. In addition, _nesting_ of `EuiThemeProvider` has led to additional nodes being rendered to the DOM.
|
||||
|
||||
This context allows us to have a single source of truth for the theme in Kibana, and ensures that the theme is applied correctly. It also abstracts away any updates or changes
|
||||
made to the `EuiThemeProvider` in the future.
|
||||
|
||||
```ts
|
||||
|
||||
// Make a component always display in dark mode.
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { KibanaThemeContextProvider, type KibanaTheme } from '@kbn/react-kibana-context-theme';
|
||||
|
||||
import { MyComponent } from './my_component';
|
||||
|
||||
export const AlwaysDarkMode = () => {
|
||||
// We've purposefully excluded `colorMode` from the props of the provider
|
||||
// to enforce the use of the `theme$` observable. This prevents consumers
|
||||
// from confusing which takes precedence, (or what needs to be set in most
|
||||
// cases).
|
||||
const theme$ = new BehaviorSubject<KibanaTheme>({ darkMode: true }));
|
||||
|
||||
return (
|
||||
<KibanaThemeContextProvider theme={{ theme$ }}>
|
||||
<MyComponent />
|
||||
</KibanaThemeContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
import { EuiThemeShape, RecursivePartial } from '@elastic/eui';
|
||||
|
||||
// Change the EUI theme colors in dark and light mode.
|
||||
export const ChangeEuiTheme = ({ theme }: CoreStart) => {
|
||||
const modify: RecursivePartial<EuiThemeShape> = {
|
||||
colors: {
|
||||
DARK: {
|
||||
text: '#abc',
|
||||
accent: '#123',
|
||||
},
|
||||
LIGHT: {
|
||||
text: '#123',
|
||||
accent: '#abc',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<KibanaThemeProvider {...{ theme, modify }}>
|
||||
<MyComponent />
|
||||
</KibanaThemeProvider>
|
||||
);
|
||||
};
|
||||
```
|
13
packages/react/kibana_context/theme/index.ts
Normal file
13
packages/react/kibana_context/theme/index.ts
Normal 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.
|
||||
*/
|
||||
|
||||
export { KibanaThemeProvider, type KibanaThemeProviderProps } from './theme_provider';
|
||||
export { wrapWithTheme } from './with_theme';
|
||||
|
||||
// Re-exporting from @kbn/react-kibana-context-common for convenience to consumers.
|
||||
export { defaultTheme, type KibanaTheme } from '@kbn/react-kibana-context-common';
|
13
packages/react/kibana_context/theme/jest.config.js
Normal file
13
packages/react/kibana_context/theme/jest.config.js
Normal 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/theme'],
|
||||
};
|
5
packages/react/kibana_context/theme/kibana.jsonc
Normal file
5
packages/react/kibana_context/theme/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/react-kibana-context-theme",
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
6
packages/react/kibana_context/theme/package.json
Normal file
6
packages/react/kibana_context/theme/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/react-kibana-context-theme",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -11,12 +11,12 @@ import type { ReactWrapper } from 'enzyme';
|
|||
import type { FC } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { BehaviorSubject, of } from 'rxjs';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import type { CoreTheme } from '@kbn/core/public';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { KibanaThemeProvider } from './kibana_theme_provider';
|
||||
import type { KibanaTheme } from '@kbn/react-kibana-context-common';
|
||||
import { KibanaThemeProvider } from './theme_provider';
|
||||
|
||||
describe('KibanaThemeProvider', () => {
|
||||
let euiTheme: ReturnType<typeof useEuiTheme> | undefined;
|
||||
|
@ -51,10 +51,10 @@ describe('KibanaThemeProvider', () => {
|
|||
};
|
||||
|
||||
it('exposes the EUI theme provider', async () => {
|
||||
const coreTheme: CoreTheme = { darkMode: true };
|
||||
const coreTheme$ = new BehaviorSubject<KibanaTheme>({ darkMode: true });
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<KibanaThemeProvider theme$={of(coreTheme)}>
|
||||
<KibanaThemeProvider theme={{ theme$: coreTheme$ }}>
|
||||
<InnerComponent />
|
||||
</KibanaThemeProvider>
|
||||
);
|
||||
|
@ -65,10 +65,10 @@ describe('KibanaThemeProvider', () => {
|
|||
});
|
||||
|
||||
it('propagates changes of the coreTheme observable', async () => {
|
||||
const coreTheme$ = new BehaviorSubject<CoreTheme>({ darkMode: true });
|
||||
const coreTheme$ = new BehaviorSubject<KibanaTheme>({ darkMode: true });
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<KibanaThemeProvider theme$={coreTheme$}>
|
||||
<KibanaThemeProvider theme={{ theme$: coreTheme$ }}>
|
||||
<InnerComponent />
|
||||
</KibanaThemeProvider>
|
||||
);
|
63
packages/react/kibana_context/theme/theme_provider.tsx
Normal file
63
packages/react/kibana_context/theme/theme_provider.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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, { useMemo } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
|
||||
import {
|
||||
CurrentEuiBreakpointProvider,
|
||||
EuiThemeProvider,
|
||||
EuiThemeProviderProps,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import {
|
||||
getColorMode,
|
||||
defaultTheme,
|
||||
type ThemeServiceStart,
|
||||
} from '@kbn/react-kibana-context-common';
|
||||
|
||||
// Extract the `theme` from `EuiThemeProviderProps` as a type.
|
||||
type EuiTheme<T = {}> = EuiThemeProviderProps<T>['theme'];
|
||||
|
||||
// Omit the `theme` and `colorMode` props from `EuiThemeProviderProps` so we can
|
||||
// add our own `euiTheme` prop and derive `colorMode` from the Kibana theme.
|
||||
interface EuiProps<T = {}> extends Omit<EuiThemeProviderProps<T>, 'theme' | 'colorMode'> {
|
||||
euiTheme?: EuiTheme<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the `KibanaThemeProvider`.
|
||||
*/
|
||||
export interface KibanaThemeProviderProps extends EuiProps {
|
||||
/** The `ThemeServiceStart` API. */
|
||||
theme: ThemeServiceStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Kibana-specific theme provider that uses the Kibana theme service to customize the EUI theme.
|
||||
*/
|
||||
export const KibanaThemeProvider = ({
|
||||
theme: { theme$ },
|
||||
euiTheme: theme,
|
||||
children,
|
||||
...props
|
||||
}: KibanaThemeProviderProps) => {
|
||||
const kibanaTheme = useObservable(theme$, defaultTheme);
|
||||
const colorMode = useMemo(() => getColorMode(kibanaTheme), [kibanaTheme]);
|
||||
|
||||
// We have to add a breakpoint provider, because the `EuiProvider` we were using-- instead
|
||||
// of `EuiThemeProvider`-- adds a breakpoint. Without it here now, several Kibana layouts
|
||||
// break, particularly sidebars.
|
||||
//
|
||||
// We can investigate removing it later, but I'm adding it here for now.
|
||||
return (
|
||||
<EuiThemeProvider {...{ colorMode, theme, ...props }}>
|
||||
<CurrentEuiBreakpointProvider>{children}</CurrentEuiBreakpointProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
};
|
22
packages/react/kibana_context/theme/tsconfig.json
Normal file
22
packages/react/kibana_context/theme/tsconfig.json
Normal 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/test-jest-helpers",
|
||||
"@kbn/react-kibana-context-common",
|
||||
]
|
||||
}
|
21
packages/react/kibana_context/theme/with_theme.tsx
Normal file
21
packages/react/kibana_context/theme/with_theme.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { ThemeServiceStart } from '@kbn/react-kibana-context-common';
|
||||
import React from 'react';
|
||||
|
||||
import { KibanaThemeProvider } from './theme_provider';
|
||||
|
||||
/**
|
||||
* A React HOC that wraps a component with the `KibanaThemeProvider`.
|
||||
* @param node The node to wrap.
|
||||
* @param theme The `ThemeServiceStart` API.
|
||||
*/
|
||||
export const wrapWithTheme = (node: React.ReactNode, theme: ThemeServiceStart) => (
|
||||
<KibanaThemeProvider {...{ theme }}>{node}</KibanaThemeProvider>
|
||||
);
|
3
packages/react/kibana_mount/README.md
Normal file
3
packages/react/kibana_mount/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/react-kibana-mount
|
||||
|
||||
Empty package generated by @kbn/generate
|
|
@ -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';
|
13
packages/react/kibana_mount/jest.config.js
Normal file
13
packages/react/kibana_mount/jest.config.js
Normal 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'],
|
||||
};
|
5
packages/react/kibana_mount/kibana.jsonc
Normal file
5
packages/react/kibana_mount/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/react-kibana-mount",
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
|
@ -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>
|
6
packages/react/kibana_mount/package.json
Normal file
6
packages/react/kibana_mount/package.json
Normal 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"
|
||||
}
|
|
@ -13,13 +13,11 @@ import { useEuiTheme } from '@elastic/eui';
|
|||
import type { UseEuiTheme } from '@elastic/eui';
|
||||
import type { CoreTheme } from '@kbn/core/public';
|
||||
import { toMountPoint } from './to_mount_point';
|
||||
import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks';
|
||||
|
||||
describe('toMountPoint', () => {
|
||||
let euiTheme: UseEuiTheme | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
euiTheme = undefined;
|
||||
});
|
||||
let euiTheme: UseEuiTheme;
|
||||
const i18n = i18nServiceMock.createStartContract();
|
||||
|
||||
const InnerComponent: FC = () => {
|
||||
const theme = useEuiTheme();
|
||||
|
@ -40,8 +38,8 @@ describe('toMountPoint', () => {
|
|||
};
|
||||
|
||||
it('exposes the euiTheme when `theme$` is provided', async () => {
|
||||
const theme$ = of<CoreTheme>({ darkMode: true });
|
||||
const mount = toMountPoint(<InnerComponent />, { theme$ });
|
||||
const theme = { theme$: of<CoreTheme>({ darkMode: true }) };
|
||||
const mount = toMountPoint(<InnerComponent />, { theme, i18n });
|
||||
|
||||
const targetEl = document.createElement('div');
|
||||
mount(targetEl);
|
||||
|
@ -54,7 +52,7 @@ describe('toMountPoint', () => {
|
|||
it('propagates changes of the theme$ observable', async () => {
|
||||
const theme$ = new BehaviorSubject<CoreTheme>({ darkMode: true });
|
||||
|
||||
const mount = toMountPoint(<InnerComponent />, { theme$ });
|
||||
const mount = toMountPoint(<InnerComponent />, { theme: { theme$ }, i18n });
|
||||
|
||||
const targetEl = document.createElement('div');
|
||||
mount(targetEl);
|
|
@ -8,32 +8,32 @@
|
|||
|
||||
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/kibana_theme_provider';
|
||||
import type { MountPoint } from '@kbn/core/public';
|
||||
import {
|
||||
KibanaRenderContextProvider,
|
||||
KibanaRenderContextProviderProps,
|
||||
} from '@kbn/react-kibana-context-render';
|
||||
|
||||
export interface ToMountPointOptions {
|
||||
theme$?: Observable<CoreTheme>;
|
||||
}
|
||||
export type ToMountPointParams = Pick<KibanaRenderContextProviderProps, 'i18n' | 'theme'>;
|
||||
|
||||
/**
|
||||
* MountPoint converter for react nodes.
|
||||
*
|
||||
* @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, params: ToMountPointParams): MountPoint => {
|
||||
const mount = (element: HTMLElement) => {
|
||||
ReactDOM.render(<I18nProvider>{content}</I18nProvider>, element);
|
||||
ReactDOM.render(
|
||||
<KibanaRenderContextProvider {...params}>{node}</KibanaRenderContextProvider>,
|
||||
element
|
||||
);
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
|
||||
// only used for tests and snapshots serialization
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
mount.__reactMount__ = node;
|
||||
}
|
||||
|
||||
return mount;
|
||||
};
|
24
packages/react/kibana_mount/tsconfig.json
Normal file
24
packages/react/kibana_mount/tsconfig.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/i18n",
|
||||
"@kbn/core-i18n-browser-mocks",
|
||||
"@kbn/react-kibana-context-render",
|
||||
]
|
||||
}
|
|
@ -141,7 +141,7 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
overrideEditorWillMount,
|
||||
editorDidMount,
|
||||
editorWillMount,
|
||||
useDarkTheme,
|
||||
useDarkTheme: useDarkThemeProp,
|
||||
transparentBackground,
|
||||
suggestionProvider,
|
||||
signatureProvider,
|
||||
|
@ -154,7 +154,8 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
isCopyable = false,
|
||||
allowFullScreen = false,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { colorMode, euiTheme } = useEuiTheme();
|
||||
const useDarkTheme = useDarkThemeProp ?? colorMode === 'DARK';
|
||||
|
||||
// We need to be able to mock the MonacoEditor in our test in order to not test implementation
|
||||
// detail and not have to call methods on the <CodeEditor /> component instance.
|
||||
|
|
|
@ -15,7 +15,6 @@ type PropArguments = Pick<
|
|||
| 'value'
|
||||
| 'aria-label'
|
||||
| 'allowFullScreen'
|
||||
| 'useDarkTheme'
|
||||
| 'transparentBackground'
|
||||
| 'placeholder'
|
||||
>;
|
||||
|
@ -58,12 +57,6 @@ export class CodeEditorStorybookMock extends AbstractStorybookMock<
|
|||
},
|
||||
defaultValue: false,
|
||||
},
|
||||
useDarkTheme: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
defaultValue: false,
|
||||
},
|
||||
transparentBackground: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
|
@ -87,7 +80,6 @@ export class CodeEditorStorybookMock extends AbstractStorybookMock<
|
|||
value: this.getArgumentValue('value', params),
|
||||
'aria-label': this.getArgumentValue('aria-label', params),
|
||||
allowFullScreen: this.getArgumentValue('allowFullScreen', params),
|
||||
useDarkTheme: this.getArgumentValue('useDarkTheme', params),
|
||||
transparentBackground: this.getArgumentValue('transparentBackground', params),
|
||||
placeholder: this.getArgumentValue('placeholder', params),
|
||||
};
|
||||
|
|
|
@ -21,12 +21,6 @@ jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
|
|||
useUiSetting: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public/theme/use_theme', () => ({
|
||||
useKibanaTheme: jest.fn(() => {
|
||||
return { darkMode: false };
|
||||
}),
|
||||
}));
|
||||
|
||||
const defaults = {
|
||||
requiresPageReload: false,
|
||||
readOnly: false,
|
||||
|
|
24
src/plugins/interactive_setup/public/theme/index.tsx
Normal file
24
src/plugins/interactive_setup/public/theme/index.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 React, { type FC } from 'react';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
import type { CoreTheme } from '@kbn/core-theme-browser';
|
||||
import { KibanaThemeProvider as KbnThemeProvider } from '@kbn/react-kibana-context-theme';
|
||||
|
||||
export interface KibanaThemeProviderProps {
|
||||
theme$: Observable<CoreTheme>;
|
||||
modify?: EuiProviderProps<{}>['modify'];
|
||||
}
|
||||
|
||||
/** @deprecated use `KibanaThemeProvider` from `@kbn/react-kibana-context-theme */
|
||||
export const KibanaThemeProvider: FC<KibanaThemeProviderProps> = ({ theme$, modify, children }) => (
|
||||
<KbnThemeProvider {...{ theme: { theme$ }, modify }}>{children}</KbnThemeProvider>
|
||||
);
|
|
@ -1,64 +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 { EUI_STYLES_GLOBAL, EUI_STYLES_UTILS } from '@kbn/core-base-common';
|
||||
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_STYLES_GLOBAL,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_GLOBAL}"]`) as HTMLElement,
|
||||
});
|
||||
globalCache.compat = true;
|
||||
const utilitiesCache = createCache({
|
||||
key: EUI_STYLES_UTILS,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_UTILS}"]`) as HTMLElement,
|
||||
});
|
||||
utilitiesCache.compat = true;
|
||||
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, utility: utilitiesCache }}
|
||||
globalStyles={false}
|
||||
utilityClasses={false}
|
||||
modify={modify}
|
||||
>
|
||||
{children}
|
||||
</EuiProvider>
|
||||
);
|
||||
};
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
};
|
|
@ -3,7 +3,11 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
},
|
||||
"include": ["common/**/*", "public/**/*", "server/**/*"],
|
||||
"include": [
|
||||
"common/**/*",
|
||||
"public/**/*",
|
||||
"server/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/i18n-react",
|
||||
|
@ -19,7 +23,8 @@
|
|||
"@kbn/utils",
|
||||
"@kbn/core-logging-server-mocks",
|
||||
"@kbn/core-preboot-server",
|
||||
"@kbn/core-base-common",
|
||||
"@kbn/react-kibana-context-theme",
|
||||
"@kbn/core-theme-browser",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -6,51 +6,14 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { DecoratorFn } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import * as styledComponents from 'styled-components';
|
||||
import { ThemedStyledComponentsModule, ThemeProvider, ThemeProviderProps } from 'styled-components';
|
||||
import { euiThemeVars, euiLightVars, euiDarkVars } from '@kbn/ui-theme';
|
||||
|
||||
export interface EuiTheme {
|
||||
eui: typeof euiThemeVars;
|
||||
darkMode: boolean;
|
||||
}
|
||||
|
||||
const EuiThemeProvider = <
|
||||
OuterTheme extends styledComponents.DefaultTheme = styledComponents.DefaultTheme
|
||||
>({
|
||||
darkMode = false,
|
||||
...otherProps
|
||||
}: Omit<ThemeProviderProps<OuterTheme, OuterTheme & EuiTheme>, 'theme'> & {
|
||||
darkMode?: boolean;
|
||||
}) => (
|
||||
<ThemeProvider
|
||||
{...otherProps}
|
||||
theme={(outerTheme?: OuterTheme) => ({
|
||||
...outerTheme,
|
||||
eui: darkMode ? euiDarkVars : euiLightVars,
|
||||
darkMode,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
/**
|
||||
* Storybook decorator using the EUI theme provider. Uses the value from
|
||||
* `globals` provided by the Storybook theme switcher.
|
||||
*/
|
||||
export const EuiThemeProviderDecorator: DecoratorFn = (storyFn, { globals }) => {
|
||||
const darkMode = globals.euiTheme === 'v8.dark' || globals.euiTheme === 'v7.dark';
|
||||
|
||||
return <EuiThemeProvider darkMode={darkMode}>{storyFn()}</EuiThemeProvider>;
|
||||
};
|
||||
|
||||
const {
|
||||
default: euiStyled,
|
||||
export {
|
||||
css,
|
||||
euiStyled,
|
||||
KibanaStyledComponentsThemeProvider as EuiThemeProvider,
|
||||
createGlobalStyle,
|
||||
keyframes,
|
||||
withTheme,
|
||||
} = styledComponents as unknown as ThemedStyledComponentsModule<EuiTheme>;
|
||||
KibanaStyledComponentsThemeProviderDecorator as EuiThemeProviderDecorator,
|
||||
} from '@kbn/react-kibana-context-styled';
|
||||
|
||||
export { css, euiStyled, EuiThemeProvider, createGlobalStyle, keyframes, withTheme };
|
||||
export type { EuiTheme } from '@kbn/react-kibana-context-styled';
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
EuiCopy,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { monaco } from '@kbn/monaco';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -140,7 +141,7 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
overrideEditorWillMount,
|
||||
editorDidMount,
|
||||
editorWillMount,
|
||||
useDarkTheme,
|
||||
useDarkTheme: useDarkThemeProp,
|
||||
transparentBackground,
|
||||
suggestionProvider,
|
||||
signatureProvider,
|
||||
|
@ -153,6 +154,9 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
isCopyable = false,
|
||||
allowFullScreen = false,
|
||||
}) => {
|
||||
const { colorMode } = useEuiTheme();
|
||||
const useDarkTheme = useDarkThemeProp ?? colorMode === 'DARK';
|
||||
|
||||
// We need to be able to mock the MonacoEditor in our test in order to not test implementation
|
||||
// detail and not have to call methods on the <CodeEditor /> component instance.
|
||||
const MonacoEditor: typeof ReactMonacoEditor = useMemo(() => {
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiDelayRender, EuiErrorBoundary, EuiSkeletonText } from '@elastic/eui';
|
||||
import { EuiDelayRender, EuiErrorBoundary, EuiSkeletonText, useEuiTheme } from '@elastic/eui';
|
||||
|
||||
import { useKibanaTheme } from '../theme';
|
||||
import type { Props } from './code_editor';
|
||||
|
||||
export * from './languages/constants';
|
||||
|
@ -40,11 +39,12 @@ export type CodeEditorProps = Props;
|
|||
* @see CodeEditorField to render a code editor in the same style as other EUI form fields.
|
||||
*/
|
||||
export const CodeEditor: React.FunctionComponent<Props> = (props) => {
|
||||
const coreTheme = useKibanaTheme();
|
||||
const { colorMode } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<EuiErrorBoundary>
|
||||
<React.Suspense fallback={<Fallback height={props.height} />}>
|
||||
<LazyBaseEditor {...props} useDarkTheme={coreTheme.darkMode} />
|
||||
<LazyBaseEditor {...props} useDarkTheme={colorMode === 'DARK'} />
|
||||
</React.Suspense>
|
||||
</EuiErrorBoundary>
|
||||
);
|
||||
|
@ -54,11 +54,12 @@ export const CodeEditor: React.FunctionComponent<Props> = (props) => {
|
|||
* Renders a Monaco code editor in the same style as other EUI form fields.
|
||||
*/
|
||||
export const CodeEditorField: React.FunctionComponent<Props> = (props) => {
|
||||
const coreTheme = useKibanaTheme();
|
||||
const { colorMode } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<EuiErrorBoundary>
|
||||
<React.Suspense fallback={<Fallback height={props.height} />}>
|
||||
<LazyCodeEditorField {...props} useDarkTheme={coreTheme.darkMode} />
|
||||
<LazyCodeEditorField {...props} useDarkTheme={colorMode === 'DARK'} />
|
||||
</React.Suspense>
|
||||
</EuiErrorBoundary>
|
||||
);
|
||||
|
|
|
@ -54,10 +54,13 @@ export const createKibanaReactContext = <Services extends KibanaServices>(
|
|||
() => createKibanaReactContext({ ...services, ...oldValue.services, ...newServices }),
|
||||
[services, oldValue, newServices]
|
||||
);
|
||||
return createElement(context.Provider, {
|
||||
|
||||
const newProvider = createElement(context.Provider, {
|
||||
value: newValue,
|
||||
children,
|
||||
});
|
||||
|
||||
return newProvider;
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -86,7 +86,8 @@ export type { ToMountPointOptions } from './util';
|
|||
/** @deprecated Use `RedirectAppLinks` from `@kbn/shared-ux-link-redirect-app` */
|
||||
export { RedirectAppLinks } from './app_links';
|
||||
|
||||
export { wrapWithTheme, KibanaThemeProvider, useKibanaTheme } from './theme';
|
||||
/** @deprecated Use `KibanaThemeProvider`, `wrapWithTheme` from `@kbn/react-kibana-context-theme` */
|
||||
export { KibanaThemeProvider, wrapWithTheme, type KibanaThemeProviderProps } from './theme';
|
||||
|
||||
/** dummy plugin, we just want kibanaReact to have its own bundle */
|
||||
export function plugin() {
|
||||
|
|
30
src/plugins/kibana_react/public/theme.tsx
Normal file
30
src/plugins/kibana_react/public/theme.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 {
|
||||
KibanaThemeProvider as KbnThemeProvider,
|
||||
KibanaThemeProviderProps as KbnThemeProviderProps,
|
||||
wrapWithTheme as kbnWrapWithTheme,
|
||||
} from '@kbn/react-kibana-context-theme';
|
||||
|
||||
/** @deprecated Use `KibanaThemeProviderProps` from `@kbn/react-kibana-context-theme` */
|
||||
export type KibanaThemeProviderProps = Pick<KbnThemeProviderProps, 'children' | 'modify'> &
|
||||
KbnThemeProviderProps['theme'];
|
||||
|
||||
/** @deprecated Use `KibanaThemeProvider` from `@kbn/react-kibana-context-theme` */
|
||||
export const KibanaThemeProvider = ({ children, theme$, modify }: KibanaThemeProviderProps) => (
|
||||
<KbnThemeProvider theme={{ theme$ }} {...modify}>
|
||||
{children}
|
||||
</KbnThemeProvider>
|
||||
);
|
||||
|
||||
type Theme = KbnThemeProviderProps['theme']['theme$'];
|
||||
|
||||
export const wrapWithTheme = (node: React.ReactNode, theme$: Theme) =>
|
||||
kbnWrapWithTheme(node, { theme$ });
|
|
@ -1,61 +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 { EUI_STYLES_GLOBAL, EUI_STYLES_UTILS } from '@kbn/core-base-common';
|
||||
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_STYLES_GLOBAL,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_GLOBAL}"]`) as HTMLElement,
|
||||
});
|
||||
globalCache.compat = true;
|
||||
const utilitiesCache = createCache({
|
||||
key: EUI_STYLES_UTILS,
|
||||
container: document.querySelector(`meta[name="${EUI_STYLES_UTILS}"]`) as HTMLElement,
|
||||
});
|
||||
utilitiesCache.compat = true;
|
||||
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, utility: utilitiesCache }}
|
||||
globalStyles={false}
|
||||
utilityClasses={false}
|
||||
modify={modify}
|
||||
>
|
||||
{children}
|
||||
</EuiProvider>
|
||||
);
|
||||
};
|
|
@ -1,58 +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 { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import type { CoreTheme } from '@kbn/core/public';
|
||||
import { KibanaContextProvider } from '../context';
|
||||
import { themeServiceMock } from '@kbn/core/public/mocks';
|
||||
import { useKibanaTheme } from './use_theme';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('useKibanaTheme', () => {
|
||||
let resultTheme: CoreTheme | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
resultTheme = undefined;
|
||||
});
|
||||
|
||||
const InnerComponent: FC = () => {
|
||||
const theme = useKibanaTheme();
|
||||
useEffect(() => {
|
||||
resultTheme = theme;
|
||||
}, [theme]);
|
||||
return <div>foo</div>;
|
||||
};
|
||||
|
||||
it('retrieve CoreTheme when theme service is provided in context', async () => {
|
||||
const expectedCoreTheme: CoreTheme = { darkMode: true };
|
||||
|
||||
const themeServiceStart = themeServiceMock.createStartContract();
|
||||
themeServiceStart.theme$ = of({ darkMode: true });
|
||||
|
||||
mountWithIntl(
|
||||
<KibanaContextProvider services={{ theme: themeServiceStart }}>
|
||||
<InnerComponent />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
|
||||
expect(resultTheme).toEqual(expectedCoreTheme);
|
||||
});
|
||||
|
||||
it('does not throw error when theme service is not provided, default theme applied', async () => {
|
||||
const expectedCoreTheme: CoreTheme = { darkMode: false };
|
||||
|
||||
mountWithIntl(
|
||||
<KibanaContextProvider>
|
||||
<InnerComponent />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
|
||||
expect(resultTheme).toEqual(expectedCoreTheme);
|
||||
});
|
||||
});
|
|
@ -1,30 +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 { CoreTheme } from '@kbn/core-theme-browser';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { of } from 'rxjs';
|
||||
import { useKibana } from '../context/context';
|
||||
|
||||
const defaultTheme: CoreTheme = { darkMode: false };
|
||||
|
||||
export const useKibanaTheme = (): CoreTheme => {
|
||||
const {
|
||||
services: { theme },
|
||||
} = useKibana();
|
||||
|
||||
let themeObservable;
|
||||
|
||||
if (!theme) {
|
||||
themeObservable = of(defaultTheme);
|
||||
} else {
|
||||
themeObservable = theme.theme$;
|
||||
}
|
||||
|
||||
return useObservable(themeObservable, defaultTheme);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue