mirror of
				https://github.com/antd-tiny-vue/antd-tiny-vue.git
				synced 2025-10-31 16:51:45 +08:00 
			
		
		
		
	feat: add theme and style
This commit is contained in:
		
							
								
								
									
										196
									
								
								components/theme/util/alias.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								components/theme/util/alias.ts
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										98
									
								
								components/theme/util/genComponentStyleHook.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								components/theme/util/genComponentStyleHook.ts
									
									
									
									
									
										Normal 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 | ||||
|     ] | ||||
|   } | ||||
| } | ||||
							
								
								
									
										29
									
								
								components/theme/util/getAlphaColor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								components/theme/util/getAlphaColor.ts
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										70
									
								
								components/theme/util/statistic.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								components/theme/util/statistic.ts
									
									
									
									
									
										Normal 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 } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user