mirror of
				https://github.com/antd-tiny-vue/antd-tiny-vue.git
				synced 2025-10-31 08:41:45 +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 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 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' | ||||
|  | ||||
| const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => { | ||||
| @@ -10,6 +38,45 @@ const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => | ||||
|  | ||||
|   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 getPrefixCls = defaultGetPrefixCls | ||||
|   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