Skip to content

Commit

Permalink
feat(comp:theme): root level IxThemeProvider is no longer needed (#1765)
Browse files Browse the repository at this point in the history
  • Loading branch information
sallerli1 committed Jan 2, 2024
1 parent 33b3d4d commit bd5603e
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 150 deletions.
1 change: 1 addition & 0 deletions packages/components/config/src/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const defaultConfig: GlobalConfig = {
},
locale: zhCN,
theme: {
injectThemeStyle: true,
presetTheme: 'default',
hashed: true,
},
Expand Down
1 change: 1 addition & 0 deletions packages/components/config/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export interface CommonConfig {
}
export interface ThemeConfig extends DeepPartialThemeTokens {
presetTheme: PresetTheme
injectThemeStyle: boolean
hashed: boolean
attachTo?: ThemeProviderAttachTo
}
Expand Down
1 change: 1 addition & 0 deletions packages/components/theme/docs/Api.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `presetTheme` | 预设的主题 | `PresetTheme` | `'default'` || |
| `injectThemeStyle` | 是否注入主题变量样式 | `boolean` | `true` || |
| `hashed` | 是否开始 `hash` 功能 | `boolean` | `true` || |
| `tag` | 配置 `IxThemeProvider` 渲染时使用的标签 | `string` | - | - | - |
| `inherit` | 是否继承上层Provider的token和配置 | `boolean \| 'all'` | `true` | - | 配置为true仅继承,配置为`'all'`则必须有上层的provider才会启用主题功能,用于组件封装时覆盖变量的场景 |
Expand Down
136 changes: 17 additions & 119 deletions packages/components/theme/src/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,135 +5,33 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import { computed, defineComponent, inject, provide, watch } from 'vue'
import { defineComponent, inject, provide } from 'vue'

import { isFunction } from 'lodash-es'
import { Logger } from '@idux/cdk/utils'

import { useGlobalConfig } from '@idux/components/config'

import { useTokenMerge } from './composables/useTokenMerge'
import { useTokenRegister } from './composables/useTokenRegister'
import { getResetTokens } from './themeTokens'
import { createThemeProviderContext, useSharedThemeProvider } from './composables/useThemeProvider'
import { THEME_PROVIDER_TOKEN } from './token'
import {
type ThemeKeys,
type UsetThemeProviderStates,
globalTokenKey,
resetTokenKey,
themeProviderProps,
} from './types'
import { themeProviderProps } from './types'

export default defineComponent({
name: 'IxThemeProvider',
props: themeProviderProps,
setup(props, { slots, attrs }) {
const supperContext = inject(THEME_PROVIDER_TOKEN, null)

if (props.inherit != 'all' || supperContext) {
const themeConfig = useGlobalConfig('theme')
const mergedPresetTheme = computed(
() =>
(props.inherit && !props.presetTheme ? supperContext?.presetTheme.value : props.presetTheme) ??
themeConfig.presetTheme,
)
const mergedHashed = computed(
() => (props.inherit ? supperContext?.hashed.value : undefined) ?? props.hashed ?? themeConfig.hashed,
)

const mergedAttachTo = computed(() => {
const attachTo =
(props.inherit ? supperContext?.attachTo.value : undefined) ?? props.attachTo ?? themeConfig.attachTo
if (!attachTo) {
return
}

if (attachTo instanceof Element) {
return attachTo
}

if (isFunction(attachTo)) {
return attachTo()
}

return document.querySelector(attachTo) ?? undefined
})

const { mergedAlgorithms, mergedTokens, getMergedTokens } = useTokenMerge(
props,
themeConfig,
supperContext,
mergedPresetTheme,
)
const { globalHashId, registerToken, updateToken, getThemeTokens, getThemeHashId, isTokensRegistered } =
useTokenRegister(mergedPresetTheme, mergedAlgorithms, mergedAttachTo, mergedHashed, getMergedTokens)

watch(
() => mergedTokens.value.global,
() => {
const useSupper = props.inherit && !props.tokens?.global && !!supperContext
if (!isTokensRegistered(globalTokenKey)) {
registerToken(
globalTokenKey,
() => (useSupper ? supperContext.getThemeTokens(globalTokenKey) : mergedTokens.value.global),
undefined,
undefined,
useSupper ? supperContext!.getThemeHashId(globalTokenKey) : undefined,
)
} else {
updateToken(globalTokenKey, useSupper ? supperContext!.getThemeHashId(globalTokenKey) : undefined)
}

// sub providers don't register reset styles
if (props.inherit && !!supperContext) {
return
}

if (!isTokensRegistered(resetTokenKey)) {
registerToken(
resetTokenKey,
globalTokens => (useSupper ? supperContext!.getThemeTokens(resetTokenKey) : getResetTokens(globalTokens)),
undefined,
false,
useSupper ? supperContext.getThemeHashId(resetTokenKey) : undefined,
)
} else {
updateToken(resetTokenKey, useSupper ? supperContext.getThemeHashId(resetTokenKey) : undefined)
}
},
{
immediate: true,
deep: true,
},
)
watch(
() => mergedTokens.value.components,
components => {
Object.keys(components).forEach(key => {
updateToken(key)
})
},
{
deep: true,
},
)

const useThemeTokenContextMap = new Map<ThemeKeys, UsetThemeProviderStates>()

provide(THEME_PROVIDER_TOKEN, {
globalHashId,
hashed: mergedHashed,
presetTheme: mergedPresetTheme,
attachTo: mergedAttachTo,
mergedTokens,
useThemeTokenContextMap,
getThemeHashId,
registerToken,
updateToken,
getThemeTokens,
isTokensRegistered,
})
let supperContext = inject(THEME_PROVIDER_TOKEN, null)

if (props.inherit === 'all' && !supperContext) {
if (__DEV__) {
Logger.warn(
'components/theme',
`parent IxThemeProvider not found when using inherit 'all', this may cause unexpected theme errors`,
)
}
supperContext = useSharedThemeProvider()
}

const context = createThemeProviderContext(supperContext, props)
provide(THEME_PROVIDER_TOKEN, context)

return () => (props.tag ? <props.tag {...attrs}>{slots.default?.()}</props.tag> : <>{slots.default?.()}</>)
},
})
150 changes: 150 additions & 0 deletions packages/components/theme/src/composables/useThemeProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import { computed, watch } from 'vue'

import { isFunction } from 'lodash-es'

import { createSharedComposable } from '@idux/cdk/utils'
import { useGlobalConfig } from '@idux/components/config'

import { useTokenMerge } from './useTokenMerge'
import { useTokenRegister } from './useTokenRegister'
import { getResetTokens } from '../themeTokens'
import { type ThemeProviderContext } from '../token'
import {
type ThemeKeys,
type ThemeProviderProps,
type UsetThemeProviderStates,
globalTokenKey,
resetTokenKey,
} from '../types'

export function createThemeProviderContext(
supperContext: ThemeProviderContext | null,
props?: ThemeProviderProps,
): ThemeProviderContext {
const themeConfig = useGlobalConfig('theme')
const injectThemeStyle = computed(() => props?.injectThemeStyle ?? themeConfig.injectThemeStyle)
const mergedPresetTheme = computed(
() =>
(props?.inherit && !props.presetTheme ? supperContext?.presetTheme.value : props?.presetTheme) ??
themeConfig.presetTheme,
)
const mergedHashed = computed(
() => (props?.inherit ? supperContext?.hashed.value : undefined) ?? props?.hashed ?? themeConfig.hashed,
)

const mergedAttachTo = computed(() => {
const attachTo =
(props?.inherit ? supperContext?.attachTo.value : undefined) ?? props?.attachTo ?? themeConfig.attachTo
if (!attachTo) {
return
}

if (attachTo instanceof Element) {
return attachTo
}

if (isFunction(attachTo)) {
return attachTo()
}

return document.querySelector(attachTo) ?? undefined
})

const { mergedAlgorithms, mergedTokens, getMergedTokens } = useTokenMerge(
props,
themeConfig,
supperContext,
mergedPresetTheme,
)
const { globalHashId, registerToken, updateToken, getThemeTokens, getThemeHashId, isTokensRegistered } =
useTokenRegister(
injectThemeStyle,
mergedPresetTheme,
mergedAlgorithms,
mergedAttachTo,
mergedHashed,
getMergedTokens,
)

watch(
() => mergedTokens.value.global,
() => {
const useSupper = props?.inherit && !props.tokens?.global && !!supperContext
if (!isTokensRegistered(globalTokenKey)) {
registerToken(
globalTokenKey,
() => (useSupper ? supperContext!.getThemeTokens(globalTokenKey) : mergedTokens.value.global),
undefined,
undefined,
useSupper ? supperContext!.getThemeHashId(globalTokenKey) : undefined,
)
} else {
updateToken(globalTokenKey, useSupper ? supperContext!.getThemeHashId(globalTokenKey) : undefined)
}

// sub providers don't register reset styles
if (props?.inherit && !!supperContext) {
return
}

if (!isTokensRegistered(resetTokenKey)) {
registerToken(
resetTokenKey,
globalTokens => (useSupper ? supperContext!.getThemeTokens(resetTokenKey) : getResetTokens(globalTokens)),
undefined,
false,
useSupper ? supperContext!.getThemeHashId(resetTokenKey) : undefined,
)
} else {
updateToken(resetTokenKey, useSupper ? supperContext!.getThemeHashId(resetTokenKey) : undefined)
}
},
{
immediate: true,
deep: true,
},
)
watch(
() => mergedTokens.value.components,
components => {
Object.keys(components).forEach(key => {
updateToken(key)
})
},
{
deep: true,
},
)

const useThemeTokenContextMap = new Map<ThemeKeys, UsetThemeProviderStates>()

return {
globalHashId,
hashed: mergedHashed,
presetTheme: mergedPresetTheme,
attachTo: mergedAttachTo,
mergedTokens,
useThemeTokenContextMap,
getThemeHashId,
registerToken,
updateToken,
getThemeTokens,
isTokensRegistered,
}
}

// export function useThemeProvider(props?: ThemeProviderProps) {
// let supperContext = inject(THEME_PROVIDER_TOKEN, null)
// supperContext = supperContext ?? useSharedThemeProvider()

// return createThemeProviderContext(supperContext, props)
// }

export const useSharedThemeProvider = createSharedComposable(() => createThemeProviderContext(null))
12 changes: 6 additions & 6 deletions packages/components/theme/src/composables/useTokenMerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export interface TokenMergeContext {
}

export function useTokenMerge(
props: ThemeProviderProps,
props: ThemeProviderProps | undefined,
config: ThemeConfig,
supperContext: ThemeProviderContext | null,
mergedPresetTheme: ComputedRef<PresetTheme>,
): TokenMergeContext {
const mergedAlgorithms = computed(() => {
const presetAlgorithms = getPresetAlgorithms(mergedPresetTheme.value)
const { getBaseColors, getColorPalette, getGreyColors } = props.algorithm ?? {}
const { getBaseColors, getColorPalette, getGreyColors } = props?.algorithm ?? {}

return {
getBaseColors: getBaseColors ?? presetAlgorithms.getBaseColors,
Expand All @@ -53,17 +53,17 @@ export function useTokenMerge(
const configComponentTokens = config.components

const overwrittenTokens = merge(
{ ...(props.inherit && !props.presetTheme ? supperContext?.mergedTokens.value.global ?? {} : {}) },
{ ...(props?.inherit && !props.presetTheme ? supperContext?.mergedTokens.value.global ?? {} : {}) },
{ ...configGlobalTokens },
props.tokens?.global,
props?.tokens?.global,
) as GlobalThemeTokens

const mergedGlobalTokens = getThemeTokens(mergedPresetTheme.value, overwrittenTokens, mergedAlgorithms.value)

const mergedComponentTokens = merge(
{ ...(props.inherit ? supperContext?.mergedTokens.value.components ?? {} : {}) },
{ ...(props?.inherit ? supperContext?.mergedTokens.value.components ?? {} : {}) },
{ ...configComponentTokens },
props.tokens?.components,
props?.tokens?.components,
)

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface TokenRegisterContext<Ext extends object = object> {
}

export function useTokenRegister(
injectThemeStyle: ComputedRef<boolean>,
mergedPresetTheme: ComputedRef<PresetTheme>,
mergedAlgorithms: ComputedRef<ThemeTokenAlgorithms>,
mergedAttachTo: ComputedRef<Element | undefined>,
Expand Down Expand Up @@ -121,7 +122,7 @@ export function useTokenRegister(
tokenHashedMap.set(key, hashed)

// if hashId is already provided, we consider the style injected already, no need to inject it again
if (!existedHashId) {
if (injectThemeStyle.value && !existedHashId) {
const cssContent = tokenToCss(
{ ...record, hashId: hashed ?? mergedHashed.value ? record.hashId : '' } as TokenRecord<string>,
transforms,
Expand Down
4 changes: 4 additions & 0 deletions packages/components/theme/src/types/themeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const themeProviderProps = {
type: String as PropType<PresetTheme>,
default: undefined,
},
injectThemeStyle: {
type: Boolean,
default: undefined,
},
hashed: {
type: Boolean,
default: undefined,
Expand Down
Loading

0 comments on commit bd5603e

Please sign in to comment.