mirror of
				https://github.com/antd-tiny-vue/antd-tiny-vue.git
				synced 2025-11-01 01:01:44 +08:00 
			
		
		
		
	feat: add config provider
This commit is contained in:
		| @@ -1,8 +1,36 @@ | |||||||
| import { createInjectionState } from '@v-c/utils' | import { booleanType, createInjectionState, functionType, objectType, someType, stringType } from '@v-c/utils' | ||||||
|  | import type { ExtractPropTypes } from 'vue' | ||||||
| import { computed } from 'vue' | import { computed } from 'vue' | ||||||
|  | import type { DerivativeFunc } from '@antd-tiny-vue/cssinjs' | ||||||
|  | import type { AliasToken, MapToken, OverrideToken, SeedToken } from '../theme/interface' | ||||||
|  | import type { RenderEmptyHandler } from './default-render-empty' | ||||||
|  |  | ||||||
| export type SizeType = 'small' | 'middle' | 'large' | undefined | export type SizeType = 'small' | 'middle' | 'large' | undefined | ||||||
|  |  | ||||||
|  | export interface Theme { | ||||||
|  |   primaryColor?: string | ||||||
|  |   infoColor?: string | ||||||
|  |   successColor?: string | ||||||
|  |   processingColor?: string | ||||||
|  |   errorColor?: string | ||||||
|  |   warningColor?: string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface CSPConfig { | ||||||
|  |   nonce?: string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export type DirectionType = 'ltr' | 'rtl' | undefined | ||||||
|  |  | ||||||
|  | export type MappingAlgorithm = DerivativeFunc<SeedToken, MapToken> | ||||||
|  |  | ||||||
|  | export interface ThemeConfig { | ||||||
|  |   token?: Partial<AliasToken> | ||||||
|  |   components?: OverrideToken | ||||||
|  |   algorithm?: MappingAlgorithm | MappingAlgorithm[] | ||||||
|  |   hashed?: boolean | ||||||
|  |   inherit?: boolean | ||||||
|  | } | ||||||
| export const defaultIconPrefixCls = 'anticon' | export const defaultIconPrefixCls = 'anticon' | ||||||
|  |  | ||||||
| const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => { | const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => { | ||||||
| @@ -10,6 +38,45 @@ const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => | |||||||
|  |  | ||||||
|   return suffixCls ? `ant-${suffixCls}` : 'ant' |   return suffixCls ? `ant-${suffixCls}` : 'ant' | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export const configConsumerProps = { | ||||||
|  |   getTargetContainer: functionType<() => HTMLElement>(), | ||||||
|  |   getPopupContainer: functionType<(triggerNode?: HTMLElement) => HTMLElement>(), | ||||||
|  |   rootPrefixCls: stringType(), | ||||||
|  |   iconPrefixCls: stringType(defaultIconPrefixCls), | ||||||
|  |   getPrefixCls: functionType(defaultGetPrefixCls), | ||||||
|  |   renderEmpty: functionType<RenderEmptyHandler>(), | ||||||
|  |   csp: objectType<CSPConfig>(), | ||||||
|  |   autoInsertSpaceInButton: booleanType(), | ||||||
|  |   input: objectType<{ | ||||||
|  |     autoComplete?: string | ||||||
|  |   }>(), | ||||||
|  |   pagination: objectType<{ | ||||||
|  |     showSizeChanger?: boolean | ||||||
|  |   }>(), | ||||||
|  |   locale: objectType(), | ||||||
|  |   pageHeader: objectType<{ | ||||||
|  |     ghost: boolean | ||||||
|  |   }>(), | ||||||
|  |   direction: someType<DirectionType>([String]), | ||||||
|  |   space: objectType<{ | ||||||
|  |     size?: SizeType | number | ||||||
|  |   }>(), | ||||||
|  |   virtual: booleanType(), | ||||||
|  |   dropdownMatchSelectWidth: booleanType(), | ||||||
|  |   form: objectType<{ | ||||||
|  |     // requiredMark?: RequiredMark | ||||||
|  |     colon?: boolean | ||||||
|  |     // scrollToFirstError?: Options | boolean | ||||||
|  |   }>(), | ||||||
|  |   theme: objectType<ThemeConfig>(), | ||||||
|  |   select: objectType<{ | ||||||
|  |     showSearch?: boolean | ||||||
|  |   }>() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export type ConfigConsumerProps = ExtractPropTypes<typeof configConsumerProps> | ||||||
|  |  | ||||||
| const [useProviderConfigProvide, useProviderConfigInject] = createInjectionState(() => { | const [useProviderConfigProvide, useProviderConfigInject] = createInjectionState(() => { | ||||||
|   const getPrefixCls = defaultGetPrefixCls |   const getPrefixCls = defaultGetPrefixCls | ||||||
|   const iconPrefixCls = computed(() => defaultIconPrefixCls) |   const iconPrefixCls = computed(() => defaultIconPrefixCls) | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								components/config-provider/css-variables.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								components/config-provider/css-variables.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | /* eslint-disable import/prefer-default-export, prefer-destructuring */ | ||||||
|  |  | ||||||
|  | import { generate } from '@ant-design/colors' | ||||||
|  | import { TinyColor } from '@ctrl/tinycolor' | ||||||
|  | import { canUseDom, updateCSS, warning } from '@v-c/utils' | ||||||
|  | import type { Theme } from './context' | ||||||
|  |  | ||||||
|  | const dynamicStyleMark = `-ant-${Date.now()}-${Math.random()}` | ||||||
|  |  | ||||||
|  | export function getStyle(globalPrefixCls: string, theme: Theme) { | ||||||
|  |   const variables: Record<string, string> = {} | ||||||
|  |  | ||||||
|  |   const formatColor = (color: TinyColor, updater?: (cloneColor: TinyColor) => TinyColor) => { | ||||||
|  |     let clone = color.clone() | ||||||
|  |     clone = updater?.(clone) || clone | ||||||
|  |     return clone.toRgbString() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const fillColor = (colorVal: string, type: string) => { | ||||||
|  |     const baseColor = new TinyColor(colorVal) | ||||||
|  |     const colorPalettes = generate(baseColor.toRgbString()) | ||||||
|  |  | ||||||
|  |     variables[`${type}-color`] = formatColor(baseColor) | ||||||
|  |     variables[`${type}-color-disabled`] = colorPalettes[1] | ||||||
|  |     variables[`${type}-color-hover`] = colorPalettes[4] | ||||||
|  |     variables[`${type}-color-active`] = colorPalettes[6] | ||||||
|  |     variables[`${type}-color-outline`] = baseColor.clone().setAlpha(0.2).toRgbString() | ||||||
|  |     variables[`${type}-color-deprecated-bg`] = colorPalettes[0] | ||||||
|  |     variables[`${type}-color-deprecated-border`] = colorPalettes[2] | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // ================ Primary Color ================ | ||||||
|  |   if (theme.primaryColor) { | ||||||
|  |     fillColor(theme.primaryColor, 'primary') | ||||||
|  |  | ||||||
|  |     const primaryColor = new TinyColor(theme.primaryColor) | ||||||
|  |     const primaryColors = generate(primaryColor.toRgbString()) | ||||||
|  |  | ||||||
|  |     // Legacy - We should use semantic naming standard | ||||||
|  |     primaryColors.forEach((color, index) => { | ||||||
|  |       variables[`primary-${index + 1}`] = color | ||||||
|  |     }) | ||||||
|  |     // Deprecated | ||||||
|  |     variables['primary-color-deprecated-l-35'] = formatColor(primaryColor, c => c.lighten(35)) | ||||||
|  |     variables['primary-color-deprecated-l-20'] = formatColor(primaryColor, c => c.lighten(20)) | ||||||
|  |     variables['primary-color-deprecated-t-20'] = formatColor(primaryColor, c => c.tint(20)) | ||||||
|  |     variables['primary-color-deprecated-t-50'] = formatColor(primaryColor, c => c.tint(50)) | ||||||
|  |     variables['primary-color-deprecated-f-12'] = formatColor(primaryColor, c => c.setAlpha(c.getAlpha() * 0.12)) | ||||||
|  |  | ||||||
|  |     const primaryActiveColor = new TinyColor(primaryColors[0]) | ||||||
|  |     variables['primary-color-active-deprecated-f-30'] = formatColor(primaryActiveColor, c => c.setAlpha(c.getAlpha() * 0.3)) | ||||||
|  |     variables['primary-color-active-deprecated-d-02'] = formatColor(primaryActiveColor, c => c.darken(2)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // ================ Success Color ================ | ||||||
|  |   if (theme.successColor) { | ||||||
|  |     fillColor(theme.successColor, 'success') | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // ================ Warning Color ================ | ||||||
|  |   if (theme.warningColor) { | ||||||
|  |     fillColor(theme.warningColor, 'warning') | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // ================= Error Color ================= | ||||||
|  |   if (theme.errorColor) { | ||||||
|  |     fillColor(theme.errorColor, 'error') | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // ================= Info Color ================== | ||||||
|  |   if (theme.infoColor) { | ||||||
|  |     fillColor(theme.infoColor, 'info') | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Convert to css variables | ||||||
|  |   const cssList = Object.keys(variables).map(key => `--${globalPrefixCls}-${key}: ${variables[key]};`) | ||||||
|  |  | ||||||
|  |   return ` | ||||||
|  |   :root { | ||||||
|  |     ${cssList.join('\n')} | ||||||
|  |   } | ||||||
|  |   `.trim() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function registerTheme(globalPrefixCls: string, theme: Theme) { | ||||||
|  |   const style = getStyle(globalPrefixCls, theme) | ||||||
|  |  | ||||||
|  |   if (canUseDom()) { | ||||||
|  |     updateCSS(style, `${dynamicStyleMark}-dynamic-theme`) | ||||||
|  |   } else { | ||||||
|  |     // @ts-expect-error this is ssr | ||||||
|  |     warning(false, 'ConfigProvider', 'SSR do not support dynamic theme with css variables.') | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								components/config-provider/default-render-empty.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								components/config-provider/default-render-empty.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | import type { VNodeChild } from 'vue' | ||||||
|  |  | ||||||
|  | export type RenderEmptyHandler = (componentName?: string) => VNodeChild | ||||||
							
								
								
									
										56
									
								
								components/config-provider/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								components/config-provider/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | import { booleanType, someType, stringType } from '@v-c/utils' | ||||||
|  | import type { ExtractPropTypes } from 'vue' | ||||||
|  | import type { SizeType, Theme } from './context' | ||||||
|  | import { configConsumerProps, defaultIconPrefixCls } from './context' | ||||||
|  | import { registerTheme } from './css-variables' | ||||||
|  |  | ||||||
|  | export const configProviderProps = { | ||||||
|  |   ...configConsumerProps, | ||||||
|  |   prefixCls: stringType(), | ||||||
|  |   componentSize: someType<SizeType>([String]), | ||||||
|  |   componentDisabled: booleanType() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export type ConfigProviderProps = Partial<ExtractPropTypes<typeof configProviderProps>> | ||||||
|  |  | ||||||
|  | export const defaultPrefixCls = 'ant' | ||||||
|  | let globalPrefixCls: string | ||||||
|  | let globalIconPrefixCls: string | ||||||
|  |  | ||||||
|  | function getGlobalPrefixCls() { | ||||||
|  |   return globalPrefixCls || defaultPrefixCls | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getGlobalIconPrefixCls() { | ||||||
|  |   return globalIconPrefixCls || defaultIconPrefixCls | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const setGlobalConfig = ({ prefixCls, iconPrefixCls, theme }: Pick<ConfigProviderProps, 'prefixCls' | 'iconPrefixCls'> & { theme?: Theme }) => { | ||||||
|  |   if (prefixCls !== undefined) { | ||||||
|  |     globalPrefixCls = prefixCls | ||||||
|  |   } | ||||||
|  |   if (iconPrefixCls !== undefined) { | ||||||
|  |     globalIconPrefixCls = iconPrefixCls | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (theme) { | ||||||
|  |     registerTheme(getGlobalPrefixCls(), theme) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const globalConfig = () => ({ | ||||||
|  |   getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => { | ||||||
|  |     if (customizePrefixCls) return customizePrefixCls | ||||||
|  |     return suffixCls ? `${getGlobalPrefixCls()}-${suffixCls}` : getGlobalPrefixCls() | ||||||
|  |   }, | ||||||
|  |   getIconPrefixCls: getGlobalIconPrefixCls, | ||||||
|  |   getRootPrefixCls: () => { | ||||||
|  |     // If Global prefixCls provided, use this | ||||||
|  |     if (globalPrefixCls) { | ||||||
|  |       return globalPrefixCls | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Fallback to default prefixCls | ||||||
|  |     return getGlobalPrefixCls() | ||||||
|  |   } | ||||||
|  | }) | ||||||
		Reference in New Issue
	
	Block a user