import type { ReactNode } from 'react' import { createContext, useContext } from 'react' import deepmerge from 'deepmerge' import { jsxs, EmotionContext } from '@vtex/admin-jsxs' import packageInfo from '@emotion/react/package.json' import type { Theme } from '@vtex/admin-styles' const EMOTION_VERSION = packageInfo.version export interface ContextValue { EMOTION_VERSION: string theme: Theme } export const Context = createContext({ EMOTION_VERSION, theme: {}, }) export const useThemeContext = () => useContext(Context) const canUseSymbol = typeof Symbol === 'function' && Symbol.for const REACT_ELEMENT = canUseSymbol ? Symbol.for('react.element') : 0xeac7 const FORWARD_REF = canUseSymbol ? Symbol.for('react.forward_ref') : 0xeac7 const deepmergeOptions: deepmerge.Options = { isMergeableObject: (n) => { return ( !!n && typeof n === 'object' && (n as React.ExoticComponent).$$typeof !== REACT_ELEMENT && (n as React.ExoticComponent).$$typeof !== FORWARD_REF ) }, arrayMerge: (_leftArray, rightArray) => rightArray, } /** * Deeply merge themes */ export const mergeThemes = (a: Theme, b: Theme): Theme => deepmerge(a, b, deepmergeOptions) function mergeAll(a: A, B: B): A & B function mergeAll(a: A, B: B, c: C): A & B & C function mergeAll(a: A, B: B, c: C, d: D): A & B & C & D function mergeAll(...args: Array>) { return deepmerge.all(args, deepmergeOptions) } mergeThemes.all = mergeAll interface BaseProviderProps { context: ContextValue children?: ReactNode } function BaseProvider(props: BaseProviderProps) { const { context, children } = props return jsxs( EmotionContext.Provider, { value: context.theme }, jsxs(Context.Provider, { value: context }, children) ) } export interface ThemeProviderProps { theme: Theme | ((outerTheme: Theme) => Theme) children?: ReactNode } export function ThemeProvider({ theme, children }: ThemeProviderProps) { const outer = useThemeContext() if (process.env.NODE_ENV !== 'production') { if (outer.EMOTION_VERSION !== EMOTION_VERSION) { console.warn( 'Multiple versions of Emotion detected,', 'and theming might not work as expected.', 'Please ensure there is only one copy of @emotion/react installed in your application.' ) } } const context = typeof theme === 'function' ? { ...outer, theme: theme(outer.theme) } : mergeThemes.all({}, outer, { theme }) return jsxs(BaseProvider, { context }, children) }