Skip to content

Commit

Permalink
feat(cdk:theme): add cdk theme support (IDuxFE#1739)
Browse files Browse the repository at this point in the history
  • Loading branch information
sallerli1 committed Nov 23, 2023
1 parent ed6462a commit f34f0e5
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/cdk/index.ts
Expand Up @@ -54,5 +54,6 @@ export * from '@idux/cdk/popper'
export * from '@idux/cdk/portal'
export * from '@idux/cdk/resize'
export * from '@idux/cdk/scroll'
export * from '@idux/cdk/theme'
export * from '@idux/cdk/utils'
export * from '@idux/cdk/version'
12 changes: 12 additions & 0 deletions packages/cdk/theme/demo/Basic.md
@@ -0,0 +1,12 @@
---
order: 0
title:
zh: 基本使用
en: Basic usage
---

## zh

主题切换

## en
22 changes: 22 additions & 0 deletions packages/cdk/theme/demo/Basic.vue
@@ -0,0 +1,22 @@
<template>
<div>
<h2>Platform:</h2>
<p>Is Browser: {{ isBrowser }}</p>
<p>Is Blink: {{ isBlink }}</p>
<p>Is Webkit: {{ isWebKit }}</p>
<p>Is iOS: {{ isIOS }}</p>
<p>Is Android: {{ isAndroid }}</p>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { isAndroid, isBlink, isBrowser, isIOS, isWebKit } from '@idux/cdk/platform'
export default defineComponent({
setup() {
return { isBrowser, isBlink, isWebKit, isIOS, isAndroid }
},
})
</script>
1 change: 1 addition & 0 deletions packages/cdk/theme/docs/Api.zh.md
@@ -0,0 +1 @@
## API
11 changes: 11 additions & 0 deletions packages/cdk/theme/docs/Index.zh.md
@@ -0,0 +1,11 @@
---
category: cdk
type:
title: Theme
subtitle: 主题
cover:
---

`@idux-vue2/cdk/theme` 提供了一组用于获取、更新主题的响应式工具

需要对不同主题做兼容性处理时使用
8 changes: 8 additions & 0 deletions packages/cdk/theme/index.ts
@@ -0,0 +1,8 @@
/**
* @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
*/

export * from './src/useTheme'
179 changes: 179 additions & 0 deletions packages/cdk/theme/src/useTheme.ts
@@ -0,0 +1,179 @@
/**
* @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 { type ComputedRef, onMounted } from 'vue'

import { debounce } from 'lodash-es'

import { Logger, createSharedComposable, tryOnScopeDispose, useState } from '@idux/cdk/utils'

export interface UseThemeParams<Theme extends string> {
/**
* CSS Selector for the target element applying to
*
* @default 'body''
*/
selector?: string | Element | null | undefined
/**
* HTML attribute applying the target element
*
* @default 'color-schema''
*/
attribute?: string

/**
* The initial value of the theme
*/
defaultTheme?: Theme

/**
* The map of the theme value and the corresponding attribute value
*/
themes?: Theme[]

/**
* Debounce time for updating the theme value
*/
debounceTime?: number
}

export interface UseThemeReturns<Theme extends string> {
theme: ComputedRef<Theme>
changeTheme: (theme: Theme) => void
}

/**
* reactivy theme hook
*/
export const useTheme = createSharedComposable(
<Theme extends string>(options: UseThemeParams<Theme> = {}): UseThemeReturns<Theme> => {
const {
selector = 'body',
attribute = 'color-schema',
defaultTheme = 'default' as Theme,
themes = [],
debounceTime = 300,
} = options

const mergedThemes = [...themes]

const requiredOptions: Required<UseThemeParams<Theme>> = {
selector,
attribute,
themes: mergedThemes,
defaultTheme,
debounceTime,
}

const [theme, setTheme] = useState<Theme>(getHTMLAttrs(requiredOptions))
const updateThemeAttrs = () => {
updateHTMLAttrs(
Object.assign({}, requiredOptions, {
defaultTheme: theme.value,
}),
)
}

updateThemeAttrs()

const changeTheme = (newTheme: Theme) => {
setTheme(newTheme)
updateThemeAttrs()
}

const onDomChange = debounce(() => {
const oldTheme = theme.value
const newTheme = getHTMLAttrs(
Object.assign({}, requiredOptions, {
defaultTheme: theme.value,
}),
)
// 这里需要手动节流一下,否则会重复更新 theme 的值
if (oldTheme !== newTheme) {
changeTheme(newTheme)
}
}, debounceTime)

let observer: MutationObserver
onMounted(() => {
if (observer) {
return
}
observer = new MutationObserver(onDomChange)
const el = getEl(selector)
if (el) {
observer.observe(el, {
attributes: true,
attributeFilter: [attribute],
})
}
})
tryOnScopeDispose(() => {
observer?.disconnect
})

return {
theme,
changeTheme,
}
},
)

function getEl(selector: UseThemeParams<string>['selector']) {
const el = typeof selector === 'string' ? window?.document.querySelector(selector) : selector

if (!el && __DEV__) {
Logger.warn('cdk/theme', `The element holding the theme cannot be found.`)
}

return el
}

/**
* Update the HTML attribute of the target element
*/
function updateHTMLAttrs<Theme extends string>(options: Required<UseThemeParams<Theme>>) {
const { selector, attribute, themes, defaultTheme } = options

const el = getEl(selector)
if (!el) {
return
}

if (attribute !== 'class') {
el.setAttribute(attribute, defaultTheme)
return
}

const currentCls = defaultTheme.split(/\s+/g)
Object.values(themes)
.flatMap(i => String(i || '').split(/\s/g))
.filter(Boolean)
.forEach(v => {
if (currentCls.includes(v)) {
el.classList.add(v)
} else {
el.classList.remove(v)
}
})
}

function getHTMLAttrs<Theme extends string>(options: Required<UseThemeParams<Theme>>): Theme {
const { selector, attribute, themes, defaultTheme } = options
const el = getEl(selector)
if (!el) {
return defaultTheme
}

const val = el.getAttribute(attribute) ?? defaultTheme
if (attribute !== 'class') {
return val as Theme
}

const currentCls = val.split(/\s+/g)
return (themes.filter(targetValue => currentCls.includes(targetValue)).at(0) ?? defaultTheme) as Theme
}

0 comments on commit f34f0e5

Please sign in to comment.