feat: add theme and style

This commit is contained in:
2023-03-02 07:16:36 +08:00
parent 7b58fb84eb
commit 8ab55b28fe
52 changed files with 3836 additions and 5 deletions

View File

@ -0,0 +1,196 @@
import { TinyColor } from '@ctrl/tinycolor'
import type { AliasToken, MapToken, OverrideToken, SeedToken } from '../interface'
import seedToken from '../themes/seed'
import getAlphaColor from './getAlphaColor'
/** Raw merge of `@antd-tiny-vue/cssinjs` token. Which need additional process */
type RawMergedToken = MapToken & OverrideToken & { override: Partial<AliasToken> }
/**
* Seed (designer) > Derivative (designer) > Alias (developer).
*
* Merge seed & derivative & override token and generate alias token for developer.
*/
export default function formatToken(derivativeToken: RawMergedToken): AliasToken {
const { override, ...restToken } = derivativeToken
const overrideTokens = { ...override }
Object.keys(seedToken).forEach(token => {
delete overrideTokens[token as keyof SeedToken]
})
const mergedToken = {
...restToken,
...overrideTokens
}
const screenXS = 480
const screenSM = 576
const screenMD = 768
const screenLG = 992
const screenXL = 1200
const screenXXL = 1600
// Generate alias token
const aliasToken: AliasToken = {
...mergedToken,
colorLink: mergedToken.colorInfoText,
colorLinkHover: mergedToken.colorInfoHover,
colorLinkActive: mergedToken.colorInfoActive,
// ============== Background ============== //
colorFillContent: mergedToken.colorFillSecondary,
colorFillContentHover: mergedToken.colorFill,
colorFillAlter: mergedToken.colorFillQuaternary,
colorBgContainerDisabled: mergedToken.colorFillTertiary,
// ============== Split ============== //
colorBorderBg: mergedToken.colorBgContainer,
colorSplit: getAlphaColor(mergedToken.colorBorderSecondary, mergedToken.colorBgContainer),
// ============== Text ============== //
colorTextPlaceholder: mergedToken.colorTextQuaternary,
colorTextDisabled: mergedToken.colorTextQuaternary,
colorTextHeading: mergedToken.colorText,
colorTextLabel: mergedToken.colorTextSecondary,
colorTextDescription: mergedToken.colorTextTertiary,
colorTextLightSolid: mergedToken.colorWhite,
colorHighlight: mergedToken.colorError,
colorBgTextHover: mergedToken.colorFillSecondary,
colorBgTextActive: mergedToken.colorFill,
colorIcon: mergedToken.colorTextTertiary,
colorIconHover: mergedToken.colorText,
colorErrorOutline: getAlphaColor(mergedToken.colorErrorBg, mergedToken.colorBgContainer),
colorWarningOutline: getAlphaColor(mergedToken.colorWarningBg, mergedToken.colorBgContainer),
// Font
fontSizeIcon: mergedToken.fontSizeSM,
// Control
lineWidth: mergedToken.lineWidth,
controlOutlineWidth: mergedToken.lineWidth * 2,
// Checkbox size and expand icon size
controlInteractiveSize: mergedToken.controlHeight / 2,
controlItemBgHover: mergedToken.colorFillTertiary,
controlItemBgActive: mergedToken.colorPrimaryBg,
controlItemBgActiveHover: mergedToken.colorPrimaryBgHover,
controlItemBgActiveDisabled: mergedToken.colorFill,
controlTmpOutline: mergedToken.colorFillQuaternary,
controlOutline: getAlphaColor(mergedToken.colorPrimaryBg, mergedToken.colorBgContainer),
lineType: mergedToken.lineType,
borderRadius: mergedToken.borderRadius,
borderRadiusXS: mergedToken.borderRadiusXS,
borderRadiusSM: mergedToken.borderRadiusSM,
borderRadiusLG: mergedToken.borderRadiusLG,
fontWeightStrong: 600,
opacityLoading: 0.65,
linkDecoration: 'none',
linkHoverDecoration: 'none',
linkFocusDecoration: 'none',
controlPaddingHorizontal: 12,
controlPaddingHorizontalSM: 8,
paddingXXS: mergedToken.sizeXXS,
paddingXS: mergedToken.sizeXS,
paddingSM: mergedToken.sizeSM,
padding: mergedToken.size,
paddingMD: mergedToken.sizeMD,
paddingLG: mergedToken.sizeLG,
paddingXL: mergedToken.sizeXL,
paddingContentHorizontalLG: mergedToken.sizeLG,
paddingContentVerticalLG: mergedToken.sizeMS,
paddingContentHorizontal: mergedToken.sizeMS,
paddingContentVertical: mergedToken.sizeSM,
paddingContentHorizontalSM: mergedToken.size,
paddingContentVerticalSM: mergedToken.sizeXS,
marginXXS: mergedToken.sizeXXS,
marginXS: mergedToken.sizeXS,
marginSM: mergedToken.sizeSM,
margin: mergedToken.size,
marginMD: mergedToken.sizeMD,
marginLG: mergedToken.sizeLG,
marginXL: mergedToken.sizeXL,
marginXXL: mergedToken.sizeXXL,
boxShadow: `
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowSecondary: `
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowTertiary: `
0 1px 2px 0 rgba(0, 0, 0, 0.03),
0 1px 6px -1px rgba(0, 0, 0, 0.02),
0 2px 4px 0 rgba(0, 0, 0, 0.02)
`,
screenXS,
screenXSMin: screenXS,
screenXSMax: screenSM - 1,
screenSM,
screenSMMin: screenSM,
screenSMMax: screenMD - 1,
screenMD,
screenMDMin: screenMD,
screenMDMax: screenLG - 1,
screenLG,
screenLGMin: screenLG,
screenLGMax: screenXL - 1,
screenXL,
screenXLMin: screenXL,
screenXLMax: screenXXL - 1,
screenXXL,
screenXXLMin: screenXXL,
boxShadowPopoverArrow: '2px 2px 5px rgba(0, 0, 0, 0.05)',
boxShadowCard: `
0 1px 2px -2px ${new TinyColor('rgba(0, 0, 0, 0.16)').toRgbString()},
0 3px 6px 0 ${new TinyColor('rgba(0, 0, 0, 0.12)').toRgbString()},
0 5px 12px 4px ${new TinyColor('rgba(0, 0, 0, 0.09)').toRgbString()}
`,
boxShadowDrawerRight: `
-6px 0 16px 0 rgba(0, 0, 0, 0.08),
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
-9px 0 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerLeft: `
6px 0 16px 0 rgba(0, 0, 0, 0.08),
3px 0 6px -4px rgba(0, 0, 0, 0.12),
9px 0 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerUp: `
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerDown: `
0 -6px 16px 0 rgba(0, 0, 0, 0.08),
0 -3px 6px -4px rgba(0, 0, 0, 0.12),
0 -9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowTabsOverflowLeft: 'inset 10px 0 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowRight: 'inset -10px 0 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowTop: 'inset 0 10px 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowBottom: 'inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08)',
// Override AliasToken
...overrideTokens
}
return aliasToken
}

View File

@ -0,0 +1,98 @@
/* eslint-disable no-redeclare */
import type { CSSInterpolation } from '@antd-tiny-vue/cssinjs'
import { useStyleRegister } from '@antd-tiny-vue/cssinjs'
import type { Ref } from 'vue'
import { computed } from 'vue'
import { genCommonStyle, genLinkStyle } from '../../style'
import { useProviderConfigState } from '../../config-provider/context'
import type { UseComponentStyleResult } from '../internal'
import { mergeToken, statisticToken, useToken } from '../internal'
import type { ComponentTokenMap, GlobalToken } from '../interface'
export type OverrideTokenWithoutDerivative = ComponentTokenMap
export type OverrideComponent = keyof OverrideTokenWithoutDerivative
export type GlobalTokenWithComponent<ComponentName extends OverrideComponent> = GlobalToken & ComponentTokenMap[ComponentName]
export interface StyleInfo<ComponentName extends OverrideComponent> {
hashId: string
prefixCls: string
rootPrefixCls: string
iconPrefixCls: string
overrideComponentToken: ComponentTokenMap[ComponentName]
}
export type TokenWithCommonCls<T> = T & {
/** Wrap component class with `.` prefix */
componentCls: string
/** Origin prefix which do not have `.` prefix */
prefixCls: string
/** Wrap icon class with `.` prefix */
iconCls: string
/** Wrap ant prefixCls class with `.` prefix */
antCls: string
}
export type FullToken<ComponentName extends OverrideComponent> = TokenWithCommonCls<GlobalTokenWithComponent<ComponentName>>
export default function genComponentStyleHook<ComponentName extends OverrideComponent>(
component: ComponentName,
styleFn: (token: FullToken<ComponentName>, info: StyleInfo<ComponentName>) => CSSInterpolation,
getDefaultToken?: OverrideTokenWithoutDerivative[ComponentName] | ((token: GlobalToken) => OverrideTokenWithoutDerivative[ComponentName])
) {
return (prefixCls: Ref<string>): UseComponentStyleResult => {
const [theme, token, hashId] = useToken()
const { getPrefixCls, iconPrefixCls } = useProviderConfigState()
const rootPrefixCls = computed(() => getPrefixCls())
const sharedInfo = computed(() => {
return {
theme: theme.value,
token: token.value,
hashId: hashId.value,
path: ['Shared', rootPrefixCls.value]
}
})
// Generate style for all a tags in antd component.
useStyleRegister(sharedInfo, () => [
{
// Link
'&': genLinkStyle(token.value)
}
])
const componentInfo = computed(() => ({
theme: theme.value,
token: token.value,
hashId: hashId.value,
path: [component, prefixCls.value, iconPrefixCls.value]
}))
return [
useStyleRegister(componentInfo, () => {
const { token: proxyToken, flush } = statisticToken(token.value)
const defaultComponentToken = typeof getDefaultToken === 'function' ? getDefaultToken(proxyToken) : getDefaultToken
const mergedComponentToken = { ...defaultComponentToken, ...token.value[component] }
const componentCls = `.${prefixCls.value}`
const mergedToken = mergeToken<TokenWithCommonCls<GlobalTokenWithComponent<OverrideComponent>>>(
proxyToken,
{
componentCls,
prefixCls: prefixCls.value,
iconCls: `.${iconPrefixCls.value}`,
antCls: `.${rootPrefixCls.value}`
},
mergedComponentToken
)
const styleInterpolation = styleFn(mergedToken as unknown as FullToken<ComponentName>, {
hashId: hashId.value,
prefixCls: prefixCls.value,
rootPrefixCls: rootPrefixCls.value,
iconPrefixCls: iconPrefixCls.value,
overrideComponentToken: token.value[component]
})
flush(component, mergedComponentToken)
return [genCommonStyle(token.value, prefixCls.value), styleInterpolation]
}),
hashId.value
]
}
}

View File

@ -0,0 +1,29 @@
import { TinyColor } from '@ctrl/tinycolor'
function isStableColor(color: number): boolean {
return color >= 0 && color <= 255
}
function getAlphaColor(frontColor: string, backgroundColor: string): string {
const { r: fR, g: fG, b: fB, a: originAlpha } = new TinyColor(frontColor).toRgb()
if (originAlpha < 1) {
return frontColor
}
const { r: bR, g: bG, b: bB } = new TinyColor(backgroundColor).toRgb()
for (let fA = 0.01; fA <= 1; fA += 0.01) {
const r = Math.round((fR - bR * (1 - fA)) / fA)
const g = Math.round((fG - bG * (1 - fA)) / fA)
const b = Math.round((fB - bB * (1 - fA)) / fA)
if (isStableColor(r) && isStableColor(g) && isStableColor(b)) {
return new TinyColor({ r, g, b, a: Math.round(fA * 100) / 100 }).toRgbString()
}
}
// fallback
/* istanbul ignore next */
return new TinyColor({ r: fR, g: fG, b: fB, a: 1 }).toRgbString()
}
export default getAlphaColor

View File

@ -0,0 +1,70 @@
declare const CSSINJS_STATISTIC: any
const enableStatistic = process.env.NODE_ENV !== 'production' || typeof CSSINJS_STATISTIC !== 'undefined'
let recording = true
/**
* This function will do as `Object.assign` in production. But will use Object.defineProperty:get to
* pass all value access in development. To support statistic field usage with alias token.
*/
export function merge<T extends object>(...objs: Partial<T>[]): T {
/* istanbul ignore next */
if (!enableStatistic) {
return Object.assign({}, ...objs)
}
recording = false
const ret = {} as T
objs.forEach(obj => {
const keys = Object.keys(obj)
keys.forEach(key => {
Object.defineProperty(ret, key, {
configurable: true,
enumerable: true,
get: () => (obj as any)[key]
})
})
})
recording = true
return ret
}
/** @private Internal Usage. Not use in your production. */
export const statistic: Record<string, { global: string[]; component: Record<string, string | number> }> = {}
/** @private Internal Usage. Not use in your production. */
// eslint-disable-next-line camelcase
export const _statistic_build_: typeof statistic = {}
/* istanbul ignore next */
function noop() {}
/** Statistic token usage case. Should use `merge` function if you do not want spread record. */
export default function statisticToken<T extends object>(token: T) {
let tokenKeys: Set<string> | undefined
let proxy = token
let flush: (componentName: string, componentToken: Record<string, string | number>) => void = noop
if (enableStatistic) {
tokenKeys = new Set<string>()
proxy = new Proxy(token, {
get(obj: any, prop: any) {
if (recording) {
tokenKeys!.add(prop)
}
return obj[prop]
}
})
flush = (componentName, componentToken) => {
statistic[componentName] = { global: Array.from(tokenKeys!), component: componentToken }
}
}
return { token: proxy, keys: tokenKeys, flush }
}