diff --git a/src-docs/src/views/theme/consuming_emotion_theme.tsx b/src-docs/src/views/theme/consuming_emotion_theme.tsx new file mode 100644 index 00000000000..88cefedf66b --- /dev/null +++ b/src-docs/src/views/theme/consuming_emotion_theme.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { ThemeProvider, css } from '@emotion/react'; +import { EuiIcon, EuiText } from '../../../../src'; + +export default () => { + return ( + +

css` + background-color: ${euiTheme.colors.lightestShade}; + padding: ${euiTheme.size.l}; + `} + > + ({ + color: + colorMode === 'LIGHT' + ? euiTheme.colors.primary + : euiTheme.colors.accent, + })} + />{' '} + This box sets its icon color, background color, and padding via Emotion + theme context +

+ + +

css` + color: ${theme.brandColor}; + background-color: ${theme.backgroundColor}; + padding: ${theme.padding}; + `} + > + This box sets its own Emotion ThemeProvider and theme variables +

+
+
+ ); +}; diff --git a/src-docs/src/views/theme/theme_example.js b/src-docs/src/views/theme/theme_example.js index 7b469e1406b..969a9f6c230 100644 --- a/src-docs/src/views/theme/theme_example.js +++ b/src-docs/src/views/theme/theme_example.js @@ -12,6 +12,9 @@ const consumingSource = require('!!raw-loader!./consuming'); import { ConsumingHOC } from './consuming_hoc'; const consumingHOCSource = require('!!raw-loader!./consuming_hoc'); +import ConsumingEmotionTheme from './consuming_emotion_theme'; +const consumingEmotionThemeSource = require('!!raw-loader!./consuming_emotion_theme'); + import OverrideSimple from './override_simple'; const overrideSimpleSource = require('!!raw-loader!./override_simple'); @@ -154,6 +157,36 @@ export const ThemeExample = { ), demo: , }, + { + title: "Consuming with Emotion's theming", + source: [ + { + type: GuideSectionTypes.TSX, + code: consumingEmotionThemeSource, + }, + ], + text: ( + <> +

+ EuiThemeProvider by default sets an{' '} + + Emotion theme context + {' '} + with the results of useEuiTheme(). This is a + syntactical sugar convenience that allows you to take advantage of + Emotion's styled syntax, or use a function in the{' '} + css prop. +

+

+ If you prefer to use or access your own custom Emotion theme, you + can completely override EUI's passed theme at any time with your own{' '} + ThemeProvider - see the second box below for an + example. +

+ + ), + demo: , + }, { title: 'Simple instance overrides', source: [ diff --git a/src/custom_typings/emotion.d.ts b/src/custom_typings/emotion.d.ts new file mode 100644 index 00000000000..53579effe97 --- /dev/null +++ b/src/custom_typings/emotion.d.ts @@ -0,0 +1,17 @@ +/* + * 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 '@emotion/react'; +import { UseEuiTheme } from '../services/theme'; + +/** + * @see https://emotion.sh/docs/typescript#define-a-theme + */ +declare module '@emotion/react' { + export interface Theme extends UseEuiTheme {} +} diff --git a/src/services/theme/emotion.test.tsx b/src/services/theme/emotion.test.tsx new file mode 100644 index 00000000000..b3c8cba2af1 --- /dev/null +++ b/src/services/theme/emotion.test.tsx @@ -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 React from 'react'; +import { ThemeProvider } from '@emotion/react'; +import { render } from '../../test/rtl'; + +import { EuiTextColor } from '../../components/text'; +import { EuiEmotionThemeProvider } from './emotion'; + +describe('EuiEmotionThemeProvider', () => { + it("allows consumers to use Emotion's theme context by default", () => { + const { container, getByTestSubject } = render( + +
({ color: euiTheme.colors.primary })} + data-test-subj="consumer" + > + hello world +
+
+ ); + + expect(getByTestSubject('consumer')).toHaveStyleRule('color', '#07C'); + + expect(container.firstChild).toMatchInlineSnapshot(` +
+ hello world +
+ `); + }); + + it("allows consumers to override EUI's ThemeProvider with their own theme", () => { + const customTheme = { + brandColor: 'pink', + }; + + const { container, getByTestSubject } = render( + + {/* @ts-ignore - consumers would set their own emotion.d.ts */} + +
({ color: theme.brandColor })} + data-test-subj="consumer" + > + hello +
+ {/* Custom Emotion themes should not break EUI's own Emotion styles */} + + world + +
+
+ ); + + expect(getByTestSubject('consumer')).toHaveStyleRule('color', 'pink'); + expect(getByTestSubject('eui')).toHaveStyleRule('color', '#ba3d76'); + + expect(container).toMatchInlineSnapshot(` +
+
+ hello +
+ + world + +
+ `); + }); +}); diff --git a/src/services/theme/emotion.tsx b/src/services/theme/emotion.tsx new file mode 100644 index 00000000000..a28788028ab --- /dev/null +++ b/src/services/theme/emotion.tsx @@ -0,0 +1,28 @@ +/* + * 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, { FunctionComponent, PropsWithChildren } from 'react'; +import { ThemeProvider } from '@emotion/react'; + +import { useEuiTheme } from './hooks'; + +/** + * @see https://emotion.sh/docs/theming + * This Emotion theme provider is added for *consumer usage* & convenience only. + * + * EUI should stick to using our own context/`useEuiTheme` internally + * instead of Emotion's shorthand `css={theme => {}}` API. If consumers + * set their own theme via ; EUI's styles should continue + * working as-is. + */ +export const EuiEmotionThemeProvider: FunctionComponent< + PropsWithChildren<{}> +> = ({ children }) => { + const euiThemeContext = useEuiTheme(); + return {children}; +}; diff --git a/src/services/theme/hooks.test.tsx b/src/services/theme/hooks.test.tsx index ca3a44c785d..ecbc819e855 100644 --- a/src/services/theme/hooks.test.tsx +++ b/src/services/theme/hooks.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import { render } from '@testing-library/react'; -import { setEuiDevProviderWarning } from './provider'; +import { setEuiDevProviderWarning } from './warning'; import { useEuiTheme, UseEuiTheme, diff --git a/src/services/theme/hooks.tsx b/src/services/theme/hooks.tsx index f23885726ed..ec74ac1b8ed 100644 --- a/src/services/theme/hooks.tsx +++ b/src/services/theme/hooks.tsx @@ -14,7 +14,7 @@ import { EuiColorModeContext, defaultComputedTheme, } from './context'; -import { getEuiDevProviderWarning } from './provider'; +import { getEuiDevProviderWarning } from './warning'; import { EuiThemeColorModeStandard, EuiThemeModifications, diff --git a/src/services/theme/index.ts b/src/services/theme/index.ts index 76e898cb700..7c46c4f81ff 100644 --- a/src/services/theme/index.ts +++ b/src/services/theme/index.ts @@ -16,11 +16,8 @@ export { export type { UseEuiTheme, WithEuiThemeProps } from './hooks'; export { useEuiTheme, withEuiTheme, RenderWithEuiTheme } from './hooks'; export type { EuiThemeProviderProps } from './provider'; -export { - EuiThemeProvider, - getEuiDevProviderWarning, - setEuiDevProviderWarning, -} from './provider'; +export { EuiThemeProvider } from './provider'; +export { getEuiDevProviderWarning, setEuiDevProviderWarning } from './warning'; export { buildTheme, computed, diff --git a/src/services/theme/provider.tsx b/src/services/theme/provider.tsx index 3d9195d7190..a2a7347d883 100644 --- a/src/services/theme/provider.tsx +++ b/src/services/theme/provider.tsx @@ -28,6 +28,7 @@ import { EuiModificationsContext, EuiColorModeContext, } from './context'; +import { EuiEmotionThemeProvider } from './emotion'; import { buildTheme, getColorMode, getComputed, mergeDeep } from './utils'; import { EuiThemeColorMode, @@ -36,12 +37,6 @@ import { EuiThemeModifications, } from './types'; -type LEVELS = 'log' | 'warn' | 'error'; -let providerWarning: LEVELS | undefined = undefined; -export const setEuiDevProviderWarning = (level: LEVELS | undefined) => - (providerWarning = level); -export const getEuiDevProviderWarning = () => providerWarning; - export interface EuiThemeProviderProps { theme?: EuiThemeSystem; colorMode?: EuiThemeColorMode; @@ -190,7 +185,9 @@ export const EuiThemeProvider = ({ - {renderedChildren} + + {renderedChildren} + diff --git a/src/services/theme/warning.ts b/src/services/theme/warning.ts new file mode 100644 index 00000000000..04311bc0d81 --- /dev/null +++ b/src/services/theme/warning.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +type LEVELS = 'log' | 'warn' | 'error'; + +let providerWarning: LEVELS | undefined = undefined; + +export const setEuiDevProviderWarning = (level: LEVELS | undefined) => + (providerWarning = level); + +export const getEuiDevProviderWarning = () => providerWarning; diff --git a/upcoming_changelogs/6913.md b/upcoming_changelogs/6913.md new file mode 100644 index 00000000000..6330e00f0f9 --- /dev/null +++ b/upcoming_changelogs/6913.md @@ -0,0 +1,2 @@ +- Updated `EuiThemeProvider` to set an Emotion theme context that returns the values of `useEuiTheme()` + diff --git a/wiki/contributing-to-eui/developing/writing-styles-with-emotion.md b/wiki/contributing-to-eui/developing/writing-styles-with-emotion.md index b5156ee51e9..b353b3b3e12 100644 --- a/wiki/contributing-to-eui/developing/writing-styles-with-emotion.md +++ b/wiki/contributing-to-eui/developing/writing-styles-with-emotion.md @@ -545,3 +545,11 @@ Emotion provides its own `createElement` function; existing uses of `import {cre Unfortunately, a limitation of the CSS-in-JS syntax parser we're using is that `//` comments throw this error (see https://github.com/hudochenkov/postcss-styled-syntax#known-issues). You must convert all `//` comments to standard CSS `/* */` comments instead. + +### Should I use Emotion's `css={theme => {}}` API? + +No. The [Emotion theme context](https://emotion.sh/docs/theming) that we include by default in `EuiThemeProvider` is intended for **consumer usage** and convenience, particularly with the goal of making adoption by Kibana devs easier. + +It is not intended for internal EUI usage, primarily because it can be too easily overridden by consumers who want to use their own custom Emotion theme vars and set their own ``. If this happens, and we're relying on Emotion's theme context, all of EUI's styles will break. + +When you're styling EUI components internally, you should use only EUI's theme context/`useEuiTheme()`, and not on Emotion's theme context (i.e., do not use the `css={theme => {}}` API).