Skip to content

Commit

Permalink
Add new state/context for settings theme CSS variables
Browse files Browse the repository at this point in the history
  • Loading branch information
cee-chen committed Aug 28, 2023
1 parent 6ce9cfd commit bded85f
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 17 deletions.
2 changes: 2 additions & 0 deletions src/services/theme/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ export const EuiNestedThemeContext = createContext<EuiThemeNested>({
hasDifferentColorFromGlobalTheme: false,
bodyColor: '',
colorClassName: '',
setGlobalCSSVariables: () => {},
setNearestThemeCSSVariables: () => {},
});
82 changes: 65 additions & 17 deletions src/services/theme/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import React, {
useRef,
useMemo,
useState,
useCallback,
PropsWithChildren,
HTMLAttributes,
} from 'react';
import classNames from 'classnames';
import { css } from '@emotion/css';
import { Global, type CSSObject } from '@emotion/react';
import isEqual from 'lodash/isEqual';

import type { CommonProps } from '../../components/common';
import { cloneElementWithCss } from '../emotion';

import {
EuiSystemContext,
Expand Down Expand Up @@ -63,7 +66,12 @@ export const EuiThemeProvider = <T extends {} = {}>({
children,
wrapperProps,
}: EuiThemeProviderProps<T>) => {
const { isGlobalTheme, bodyColor } = useContext(EuiNestedThemeContext);
const {
isGlobalTheme,
bodyColor,
globalCSSVariables,
setGlobalCSSVariables,
} = useContext(EuiNestedThemeContext);
const parentSystem = useContext(EuiSystemContext);
const parentModifications = useContext(EuiModificationsContext);
const parentColorMode = useContext(EuiColorModeContext);
Expand Down Expand Up @@ -137,6 +145,13 @@ export const EuiThemeProvider = <T extends {} = {}>({
}
}, [colorMode, system, modifications]);

const [themeCSSVariables, _setThemeCSSVariables] = useState<CSSObject>();
const setThemeCSSVariables = useCallback(
(variables: CSSObject) =>
_setThemeCSSVariables((previous) => ({ ...previous, ...variables })),
[]
);

const nestedThemeContext = useMemo(() => {
return {
isGlobalTheme: false, // The theme that determines the global body styles
Expand All @@ -148,8 +163,25 @@ export const EuiThemeProvider = <T extends {} = {}>({
label: euiColorMode-${_colorMode};
color: ${theme.colors.text};
`,
setGlobalCSSVariables: isGlobalTheme
? setThemeCSSVariables
: setGlobalCSSVariables,
globalCSSVariables: isGlobalTheme
? themeCSSVariables
: globalCSSVariables,
setNearestThemeCSSVariables: setThemeCSSVariables,
themeCSSVariables: themeCSSVariables,
};
}, [theme, isGlobalTheme, bodyColor, _colorMode]);
}, [
theme,
isGlobalTheme,
bodyColor,
_colorMode,
setGlobalCSSVariables,
globalCSSVariables,
setThemeCSSVariables,
themeCSSVariables,
]);

const renderedChildren = useMemo(() => {
if (isGlobalTheme) {
Expand All @@ -161,9 +193,14 @@ export const EuiThemeProvider = <T extends {} = {}>({
...rest,
className: classNames(className, nestedThemeContext.colorClassName),
};
// Condition avoids rendering an empty Emotion selector if no
// theme-specific CSS variables have been set by child components
if (themeCSSVariables) {
props.css = { label: 'euiCSSVariables', ...themeCSSVariables };
}

if (cloneElement) {
return React.cloneElement(children, {
return cloneElementWithCss(children, {
...props,
className: classNames(children.props.className, props.className),
});
Expand All @@ -177,21 +214,32 @@ export const EuiThemeProvider = <T extends {} = {}>({
</span>
);
}
}, [isGlobalTheme, nestedThemeContext, wrapperProps, children]);
}, [
isGlobalTheme,
themeCSSVariables,
nestedThemeContext,
wrapperProps,
children,
]);

return (
<EuiColorModeContext.Provider value={colorMode}>
<EuiSystemContext.Provider value={system}>
<EuiModificationsContext.Provider value={modifications}>
<EuiThemeContext.Provider value={theme}>
<EuiNestedThemeContext.Provider value={nestedThemeContext}>
<EuiEmotionThemeProvider>
{renderedChildren}
</EuiEmotionThemeProvider>
</EuiNestedThemeContext.Provider>
</EuiThemeContext.Provider>
</EuiModificationsContext.Provider>
</EuiSystemContext.Provider>
</EuiColorModeContext.Provider>
<>
{isGlobalTheme && themeCSSVariables && (
<Global styles={{ ':root': themeCSSVariables }} />
)}
<EuiColorModeContext.Provider value={colorMode}>
<EuiSystemContext.Provider value={system}>
<EuiModificationsContext.Provider value={modifications}>
<EuiThemeContext.Provider value={theme}>
<EuiNestedThemeContext.Provider value={nestedThemeContext}>
<EuiEmotionThemeProvider>
{renderedChildren}
</EuiEmotionThemeProvider>
</EuiNestedThemeContext.Provider>
</EuiThemeContext.Provider>
</EuiModificationsContext.Provider>
</EuiSystemContext.Provider>
</EuiColorModeContext.Provider>
</>
);
};
6 changes: 6 additions & 0 deletions src/services/theme/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Side Public License, v 1.
*/

import type { CSSObject } from '@emotion/react';

import { RecursivePartial, ValueOf } from '../../components/common';
import { _EuiThemeAnimation } from '../../global_styling/variables/animations';
import { _EuiThemeBreakpoints } from '../../global_styling/variables/breakpoint';
Expand Down Expand Up @@ -99,4 +101,8 @@ export type EuiThemeNested = {
hasDifferentColorFromGlobalTheme: boolean;
bodyColor: string;
colorClassName: string;
setGlobalCSSVariables: Function;
globalCSSVariables?: CSSObject;
setNearestThemeCSSVariables: Function;
themeCSSVariables?: CSSObject;
};

0 comments on commit bded85f

Please sign in to comment.