Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(comp:theme): root level IxThemeProvider is no longer needed #1765

Merged
merged 1 commit into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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