Button和Spin实现
ts
import type { ComputedRef, InjectionKey, Ref } from 'vue'
import { computed, inject } from 'vue'
import { TinyColor } from '@ctrl/tinycolor'
import type { CSSObject, Theme } from '../../../src'
import { createTheme, useCacheToken } from '../../../src'
import type { MaybeComputedRef } from '../../../src/util'
export type GetStyle = (prefixCls: string, token: DerivativeToken) => CSSObject
export interface DesignToken {
primaryColor: string
textColor: string
reverseTextColor: string
componentBackgroundColor: string
borderRadius: number
borderColor: string
borderWidth: number
}
export interface DerivativeToken extends DesignToken {
primaryColorDisabled: string
}
export const defaultDesignToken: DesignToken = {
primaryColor: '#1890ff',
textColor: '#333333',
reverseTextColor: '#FFFFFF',
componentBackgroundColor: '#FFFFFF',
borderRadius: 2,
borderColor: 'black',
borderWidth: 1,
}
// 模拟推导过程
function derivative(designToken: DesignToken): DerivativeToken {
return {
...designToken,
primaryColorDisabled: new TinyColor(designToken.primaryColor)
.setAlpha(0.5).toString(),
}
}
const defaultTheme = createTheme(derivative)
export const DesignTokenContextKey: InjectionKey<{
token?: Partial<DesignToken>
hashed?: string | boolean | undefined
theme?: Theme<any, any>
}> = Symbol('DesignTokenContext')
export const defaultConfig = {
token: defaultDesignToken,
hashed: true,
}
export function useToken(): [MaybeComputedRef<Theme<any, any>>, ComputedRef<DerivativeToken>, ComputedRef<string>] {
const designTokenContext = inject(DesignTokenContextKey, defaultConfig)
const salt = computed(() => `${designTokenContext.hashed || ''}`)
const mergedTheme = computed(() => designTokenContext.theme || defaultTheme)
const cacheToken = useCacheToken<DesignToken, DerivativeToken>(
mergedTheme,
computed(() => {
return [defaultDesignToken, designTokenContext.token]
}) as Ref<DesignToken[]>,
computed(() => ({
salt: salt.value,
})),
)
return [
mergedTheme,
computed(() => cacheToken.value[0]) as any,
computed(() => (designTokenContext.hashed ? cacheToken.value[1] : '')),
]
}
vue
<script lang="ts" setup>
import type { PropType } from 'vue'
import { provide } from 'vue'
import type { DesignToken } from '../theme'
import { DesignTokenContextKey, defaultDesignToken } from '../theme'
const props = defineProps({
token: {
type: Object as PropType<Partial<DesignToken>>,
default: () => defaultDesignToken,
},
hashed: {
type: [Boolean, String] as PropType<boolean | string>,
default: undefined,
},
})
provide(DesignTokenContextKey, props)
</script>
<template>
<slot />
</template>
ts
// 通用框架
import type { DerivativeToken } from '../theme'
import type { CSSInterpolation, CSSObject } from '../../../src'
export const genSharedButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation => ({
[`.${prefixCls}`]: {
borderColor: token.borderColor,
borderWidth: token.borderWidth,
borderRadius: token.borderRadius,
cursor: 'pointer',
transition: 'background 0.3s',
},
})
// 实心底色样式
export const genSolidButtonStyle = (
prefixCls: string,
token: DerivativeToken,
postFn: () => CSSObject,
): CSSInterpolation => [
genSharedButtonStyle(prefixCls, token),
{
[`.${prefixCls}`]: {
...postFn(),
},
},
]
// 默认样式
export const genDefaultButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation =>
genSolidButtonStyle(prefixCls, token, () => ({
'backgroundColor': token.componentBackgroundColor,
'color': token.textColor,
'&:hover': {
borderColor: token.primaryColor,
color: token.primaryColor,
},
}))
// 主色样式
export const genPrimaryButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation =>
genSolidButtonStyle(prefixCls, token, () => ({
'backgroundColor': token.primaryColor,
'border': `${token.borderWidth}px solid ${token.primaryColor}`,
'color': token.reverseTextColor,
'&:hover': {
backgroundColor: token.primaryColorDisabled,
},
}))
// 透明按钮
export const genGhostButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation => [
genSharedButtonStyle(prefixCls, token),
{
[`.${prefixCls}`]: {
'backgroundColor': 'transparent',
'color': token.primaryColor,
'border': `${token.borderWidth}px solid ${token.primaryColor}`,
'&:hover': {
borderColor: token.primaryColor,
color: token.primaryColor,
},
},
},
]
tsx
import { computed, defineComponent } from 'vue'
import classNames from 'classnames'
import { useStyleRegister } from '../../../src'
import { useToken } from '../theme'
import { genDefaultButtonStyle, genGhostButtonStyle, genPrimaryButtonStyle } from './button.style'
export default defineComponent({
name: 'AButton',
props: {
type: {
type: String,
default: 'default',
},
},
setup(props, { slots, attrs }) {
const prefixCls = 'ant-btn'
const [theme, token, hashId] = useToken()
const registerParam = computed(() => {
return {
theme: theme.value,
token: token.value,
hashId: hashId.value,
path: [prefixCls],
}
})
const defaultCls = `${prefixCls}-default`
const primaryCls = `${prefixCls}-primary`
const ghostCls = `${prefixCls}-ghost`
const wrapSSR = useStyleRegister(registerParam, () => {
return [
genDefaultButtonStyle(defaultCls, token.value),
genPrimaryButtonStyle(primaryCls, token.value),
genGhostButtonStyle(ghostCls, token.value),
]
})
return () => {
const typeCls: any = {
[defaultCls]: props.type === 'default',
[primaryCls]: props.type === 'primary',
[ghostCls]: props.type === 'ghost',
}
const className = slots?.class
return (wrapSSR(<button class={classNames(prefixCls, typeCls, hashId.value, className)} {...attrs}>
{slots?.default?.()}
</button>))
}
},
})
tsx
import { computed, defineComponent } from 'vue'
import classNames from 'classnames'
import type { CSSInterpolation } from '../../../src'
import { Keyframes, useStyleRegister } from '../../../src'
import { useToken } from '../theme'
import type { DerivativeToken } from '../theme'
const animation = new Keyframes('loadingCircle', {
to: {
transform: 'rotate(360deg)',
},
})
// 通用框架
const genSpinStyle = (
prefixCls: string,
token: DerivativeToken,
hashId: string,
): CSSInterpolation => [
{
[`.${prefixCls}`]: {
width: 20,
height: 20,
backgroundColor: token.primaryColor,
animationName: animation,
animationDuration: '1.2s',
animationIterationCount: 'infinite',
animationTimingFunction: 'linear',
// animation: `${animation.getName(hashId)} 1s infinite linear`,
},
},
// animation,
]
const Spin = defineComponent({
name: 'ASpin',
setup(props, { attrs }) {
const prefixCls = 'ant-spin'
const [theme, token, hashId] = useToken()
const registerInfo = computed(() => {
return { theme: theme.value, token: token.value, hashId: hashId.value, path: [prefixCls] }
})
const wrapSSR = useStyleRegister(
registerInfo,
() => [genSpinStyle(prefixCls, token.value, hashId.value)],
)
return () => {
return wrapSSR(
<div {...attrs} class={classNames(prefixCls, hashId.value)} />,
)
}
},
})
export default Spin