pull/8213/merge
JoeXu727 2025-06-17 09:03:54 +07:00 committed by GitHub
commit fe80c0aed8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
113 changed files with 4540 additions and 2341 deletions

View File

@ -1,11 +1,14 @@
export type KeyType = string | number;
type ValueType = [number, any]; // [times, realValue]
const SPLIT = '%';
class Entity {
instanceId: string;
constructor(instanceId: string) {
this.instanceId = instanceId;
}
/** @private Internal cache map. Do not access this directly */
cache = new Map<string, ValueType>();

View File

@ -31,7 +31,6 @@ export function createCache() {
Array.from(styles).forEach(style => {
(style as any)[CSS_IN_JS_INSTANCE] = (style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId;
// Not force move if no head
// Not force move if no head
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
document.head.insertBefore(style, firstChild);

View File

@ -0,0 +1,82 @@
import type Cache from './Cache';
import { extract as tokenExtractStyle, TOKEN_PREFIX } from './hooks/useCacheToken';
import { CSS_VAR_PREFIX, extract as cssVarExtractStyle } from './hooks/useCSSVarRegister';
import { extract as styleExtractStyle, STYLE_PREFIX } from './hooks/useStyleRegister';
import { toStyleStr } from './util';
import { ATTR_CACHE_MAP, serialize as serializeCacheMap } from './util/cacheMapUtil';
const ExtractStyleFns = {
[STYLE_PREFIX]: styleExtractStyle,
[TOKEN_PREFIX]: tokenExtractStyle,
[CSS_VAR_PREFIX]: cssVarExtractStyle,
};
type ExtractStyleType = keyof typeof ExtractStyleFns;
function isNotNull<T>(value: T | null): value is T {
return value !== null;
}
export default function extractStyle(
cache: Cache,
options?:
| boolean
| {
plain?: boolean;
types?: ExtractStyleType | ExtractStyleType[];
},
) {
const { plain = false, types = ['style', 'token', 'cssVar'] } =
typeof options === 'boolean' ? { plain: options } : options || {};
const matchPrefixRegexp = new RegExp(
`^(${(typeof types === 'string' ? [types] : types).join('|')})%`,
);
// prefix with `style` is used for `useStyleRegister` to cache style context
const styleKeys = Array.from(cache.cache.keys()).filter(key => matchPrefixRegexp.test(key));
// Common effect styles like animation
const effectStyles: Record<string, boolean> = {};
// Mapping of cachePath to style hash
const cachePathMap: Record<string, string> = {};
let styleText = '';
styleKeys
.map<[number, string] | null>(key => {
const cachePath = key.replace(matchPrefixRegexp, '').replace(/%/g, '|');
const [prefix] = key.split('%');
const extractFn = ExtractStyleFns[prefix as keyof typeof ExtractStyleFns];
const extractedStyle = extractFn(cache.cache.get(key)![1], effectStyles, {
plain,
});
if (!extractedStyle) {
return null;
}
const [order, styleId, styleStr] = extractedStyle;
if (key.startsWith('style')) {
cachePathMap[cachePath] = styleId;
}
return [order, styleStr];
})
.filter(isNotNull)
.sort(([o1], [o2]) => o1 - o2)
.forEach(([, style]) => {
styleText += style;
});
// ==================== Fill Cache Path ====================
styleText += toStyleStr(
`.${ATTR_CACHE_MAP}{content:"${serializeCacheMap(cachePathMap)}";}`,
undefined,
undefined,
{
[ATTR_CACHE_MAP]: ATTR_CACHE_MAP,
},
plain,
);
return styleText;
}

View File

@ -0,0 +1,108 @@
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
import { ATTR_MARK, ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
import { isClientSide, toStyleStr } from '../util';
import type { TokenWithCSSVar } from '../util/css-variables';
import { transformToken } from '../util/css-variables';
import type { ExtractStyle } from './useGlobalCache';
import useGlobalCache from './useGlobalCache';
import { uniqueHash } from './useStyleRegister';
import type { ComputedRef } from 'vue';
import { computed } from 'vue';
export const CSS_VAR_PREFIX = 'cssVar';
type CSSVarCacheValue<V, T extends Record<string, V> = Record<string, V>> = [
cssVarToken: TokenWithCSSVar<V, T>,
cssVarStr: string,
styleId: string,
cssVarKey: string,
];
const useCSSVarRegister = <V, T extends Record<string, V>>(
config: ComputedRef<{
path: string[];
key: string;
prefix?: string;
unitless?: Record<string, boolean>;
ignore?: Record<string, boolean>;
scope?: string;
token: any;
}>,
fn: () => T,
) => {
const styleContext = useStyleInject();
const stylePath = computed(() => {
return [
...config.value.path,
config.value.key,
config.value.scope || '',
config.value.token?._tokenKey,
];
});
const cache = useGlobalCache<CSSVarCacheValue<V, T>>(
CSS_VAR_PREFIX,
stylePath,
() => {
const originToken = fn();
const [mergedToken, cssVarsStr] = transformToken<V, T>(originToken, config.value.key, {
prefix: config.value.prefix,
unitless: config.value.unitless,
ignore: config.value.ignore,
scope: config.value.scope || '',
});
const styleId = uniqueHash(stylePath.value, cssVarsStr);
return [mergedToken, cssVarsStr, styleId, config.value.key];
},
([, , styleId]) => {
if (isClientSide) {
removeCSS(styleId, { mark: ATTR_MARK });
}
},
([, cssVarsStr, styleId]) => {
if (!cssVarsStr) {
return;
}
const style = updateCSS(cssVarsStr, styleId, {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: styleContext.value.container,
priority: -999,
});
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value.cache?.instanceId;
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, config.value.key);
},
);
return cache;
};
export const extract: ExtractStyle<CSSVarCacheValue<any>> = (cache, _effectStyles, options) => {
const [, styleStr, styleId, cssVarKey] = cache;
const { plain } = options || {};
if (!styleStr) {
return null;
}
const order = -999;
// ====================== Style ======================
// Used for rc-util
const sharedAttrs = {
'data-vc-order': 'prependQueue',
'data-vc-priority': `${order}`,
};
const styleText = toStyleStr(styleStr, cssVarKey, styleId, sharedAttrs, plain);
return [order, styleId, styleText];
};
export default useCSSVarRegister;

View File

@ -1,20 +1,19 @@
import hash from '@emotion/hash';
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
import { updateCSS } from '../../../vc-util/Dom/dynamicCSS';
import { ATTR_MARK, ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
import type Theme from '../theme/Theme';
import { flattenToken, memoResult, token2key, toStyleStr } from '../util';
import { transformToken } from '../util/css-variables';
import type { ExtractStyle } from './useGlobalCache';
import useGlobalCache from './useGlobalCache';
import { flattenToken, token2key } from '../util';
import type { Ref } from 'vue';
import { ref, computed } from 'vue';
const EMPTY_OVERRIDE = {};
const isProduction = process.env.NODE_ENV === 'production';
// nuxt generate when NODE_ENV is prerender
const isPrerender = process.env.NODE_ENV === 'prerender';
// Generate different prefix to make user selector break in production env.
// This helps developer not to do style override directly on the hash id.
const hashPrefix = !isProduction && !isPrerender ? 'css-dev-only-do-not-override' : 'css';
const hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css';
export interface Option<DerivativeToken, DesignToken> {
/**
@ -46,6 +45,22 @@ export interface Option<DerivativeToken, DesignToken> {
override: object,
theme: Theme<any, any>,
) => DerivativeToken;
/**
* Transform token to css variables.
*/
cssVar?: {
/** Prefix for css variables */
prefix?: string;
/** Tokens that should not be appended with unit */
unitless?: Record<string, boolean>;
/** Tokens that should not be transformed to css variables */
ignore?: Record<string, boolean>;
/** Tokens that preserves origin value */
preserve?: Record<string, boolean>;
/** Key for current theme. Useful for customizing and should be unique */
key?: string;
};
}
const tokenKeys = new Map<string, number>();
@ -94,6 +109,7 @@ export const getComputedToken = <DerivativeToken = object, DesignToken = Derivat
format?: (token: DesignToken) => DerivativeToken,
) => {
const derivativeToken = theme.getDerivativeToken(originToken);
// Merge with override
let mergedDerivativeToken = {
...derivativeToken,
@ -108,6 +124,16 @@ export const getComputedToken = <DerivativeToken = object, DesignToken = Derivat
return mergedDerivativeToken;
};
export const TOKEN_PREFIX = 'token';
type TokenCacheValue<DerivativeToken> = [
token: DerivativeToken & { _tokenKey: string; _themeKey: string },
hashId: string,
realToken: DerivativeToken & { _tokenKey: string },
cssVarStr: string,
cssVarKey: string,
];
/**
* Cache theme derivative token as global shared one
* @param theme Theme entity
@ -119,21 +145,27 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
theme: Ref<Theme<any, any>>,
tokens: Ref<Partial<DesignToken>[]>,
option: Ref<Option<DerivativeToken, DesignToken>> = ref({}),
) {
const style = useStyleInject();
): Ref<TokenCacheValue<DerivativeToken>> {
const styleContext = useStyleInject();
// Basic - We do basic cache here
const mergedToken = computed(() => Object.assign({}, ...tokens.value));
const tokenStr = computed(() => flattenToken(mergedToken.value));
const overrideTokenStr = computed(() => flattenToken(option.value.override || EMPTY_OVERRIDE));
const mergedToken = computed(() =>
memoResult(() => Object.assign({}, ...tokens.value), tokens.value),
);
const cachedToken = useGlobalCache<[DerivativeToken & { _tokenKey: string }, string]>(
'token',
const tokenStr = computed(() => flattenToken(mergedToken.value));
const overrideTokenStr = computed(() => flattenToken(option.value.override ?? EMPTY_OVERRIDE));
const cssVarStr = computed(() => (option.value.cssVar ? flattenToken(option.value.cssVar) : ''));
const cachedToken = useGlobalCache<TokenCacheValue<DerivativeToken>>(
TOKEN_PREFIX,
computed(() => [
option.value.salt || '',
theme.value.id,
option.value.salt ?? '',
theme.value?.id,
tokenStr.value,
overrideTokenStr.value,
cssVarStr.value,
]),
() => {
const {
@ -141,25 +173,82 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
override = EMPTY_OVERRIDE,
formatToken,
getComputedToken: compute,
cssVar,
} = option.value;
const mergedDerivativeToken = compute
let mergedDerivativeToken = compute
? compute(mergedToken.value, override, theme.value)
: getComputedToken(mergedToken.value, override, theme.value, formatToken);
// Replace token value with css variables
const actualToken = { ...mergedDerivativeToken };
let cssVarsStr = '';
if (!!cssVar) {
[mergedDerivativeToken, cssVarsStr] = transformToken(mergedDerivativeToken, cssVar.key!, {
prefix: cssVar.prefix,
ignore: cssVar.ignore,
unitless: cssVar.unitless,
preserve: cssVar.preserve,
});
}
// Optimize for `useStyleRegister` performance
const tokenKey = token2key(mergedDerivativeToken, salt);
mergedDerivativeToken._tokenKey = tokenKey;
recordCleanToken(tokenKey);
actualToken._tokenKey = token2key(actualToken, salt);
const themeKey = cssVar?.key ?? tokenKey;
mergedDerivativeToken._themeKey = themeKey;
recordCleanToken(themeKey);
const hashId = `${hashPrefix}-${hash(tokenKey)}`;
mergedDerivativeToken._hashId = hashId; // Not used
return [mergedDerivativeToken, hashId];
return [mergedDerivativeToken, hashId, actualToken, cssVarsStr, cssVar?.key || ''];
},
cache => {
// Remove token will remove all related style
cleanTokenStyle(cache[0]._tokenKey, style.value?.cache.instanceId);
cleanTokenStyle(cache[0]._themeKey, styleContext.value?.cache?.instanceId);
},
([token, , , cssVarsStr]) => {
const { cssVar } = option.value;
if (cssVar && cssVarsStr) {
const style = updateCSS(cssVarsStr, hash(`css-variables-${token._themeKey}`), {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: styleContext.value?.container,
priority: -999,
});
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value?.cache?.instanceId;
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, token._themeKey);
}
},
);
return cachedToken;
}
export const extract: ExtractStyle<TokenCacheValue<any>> = (cache, _effectStyles, options) => {
const [, , realToken, styleStr, cssVarKey] = cache;
const { plain } = options || {};
if (!styleStr) {
return null;
}
const styleId = realToken._tokenKey;
const order = -999;
// ====================== Style ======================
// Used for rc-util
const sharedAttrs = {
'data-vc-order': 'prependQueue',
'data-vc-priority': `${order}`,
};
const styleText = toStyleStr(styleStr, cssVarKey, styleId, sharedAttrs, plain);
return [order, styleId, styleText];
};

View File

@ -0,0 +1,30 @@
// import canUseDom from 'rc-util/lib/Dom/canUseDom';
import useLayoutEffect from '../../../_util/hooks/useLayoutEffect';
import type { ShallowRef, WatchCallback } from 'vue';
import { watch } from 'vue';
type UseCompatibleInsertionEffect = (
renderEffect: WatchCallback,
effect: (polyfill?: boolean) => ReturnType<WatchCallback>,
deps: ShallowRef,
) => void;
/**
* Polyfill `useInsertionEffect` for React < 18
* @param renderEffect will be executed in `useMemo`, and do not have callback
* @param effect will be executed in `useLayoutEffect`
* @param deps
*/
const useInsertionEffectPolyfill: UseCompatibleInsertionEffect = (renderEffect, effect, deps) => {
watch(deps, renderEffect, { immediate: true });
useLayoutEffect(() => effect(true), deps);
};
/**
* Compatible `useInsertionEffect`
* will use `useInsertionEffect` if React version >= 18,
* otherwise use `useInsertionEffectPolyfill`.
*/
const useCompatibleInsertionEffect: UseCompatibleInsertionEffect = useInsertionEffectPolyfill;
export default useCompatibleInsertionEffect;

View File

@ -0,0 +1,8 @@
const useRun = () => {
return function (fn: () => void) {
fn();
};
};
const useEffectCleanupRegister = useRun;
export default useEffectCleanupRegister;

View File

@ -1,58 +1,115 @@
import { useStyleInject } from '../StyleContext';
import type { KeyType } from '../Cache';
import useCompatibleInsertionEffect from './useCompatibleInsertionEffect';
import useHMR from './useHMR';
import type { ShallowRef, Ref } from 'vue';
import { onBeforeUnmount, watch, watchEffect, shallowRef } from 'vue';
export default function useClientCache<CacheType>(
import { onBeforeUnmount, watch, computed } from 'vue';
export type ExtractStyle<CacheValue> = (
cache: CacheValue,
effectStyles: Record<string, boolean>,
options?: {
plain?: boolean;
},
) => [order: number, styleId: string, style: string] | null;
export default function useGlobalCache<CacheType>(
prefix: string,
keyPath: Ref<KeyType[]>,
cacheFn: () => CacheType,
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
// Add additional effect trigger by `useInsertionEffect`
onCacheEffect?: (cachedValue: CacheType) => void,
): ShallowRef<CacheType> {
const styleContext = useStyleInject();
const fullPathStr = shallowRef('');
const res = shallowRef<CacheType>();
watchEffect(() => {
fullPathStr.value = [prefix, ...keyPath.value].join('%');
});
const globalCache = computed(() => styleContext.value?.cache);
const deps = computed(() => [prefix, ...keyPath.value].join('%'));
const HMRUpdate = useHMR();
const clearCache = (pathStr: string) => {
styleContext.value.cache.update(pathStr, prevCache => {
const [times = 0, cache] = prevCache || [];
const nextCount = times - 1;
if (nextCount === 0) {
onCacheRemove?.(cache, false);
return null;
type UpdaterArgs = [times: number, cache: CacheType];
const buildCache = (updater?: (data: UpdaterArgs) => UpdaterArgs) => {
globalCache.value.update(deps.value, prevCache => {
const [times = 0, cache] = prevCache || [undefined, undefined];
// HMR should always ignore cache since developer may change it
let tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove?.(tmpCache, HMRUpdate);
tmpCache = null;
}
return [times - 1, cache];
const mergedCache = tmpCache || cacheFn();
const data: UpdaterArgs = [times, mergedCache];
// Call updater if need additional logic
return updater ? updater(data) : data;
});
};
watch(
fullPathStr,
(newStr, oldStr) => {
if (oldStr) clearCache(oldStr);
// Create cache
styleContext.value.cache.update(newStr, prevCache => {
const [times = 0, cache] = prevCache || [];
// HMR should always ignore cache since developer may change it
let tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove?.(tmpCache, HMRUpdate);
tmpCache = null;
}
const mergedCache = tmpCache || cacheFn();
return [times + 1, mergedCache];
});
res.value = styleContext.value.cache.get(fullPathStr.value)![1];
deps,
() => {
buildCache();
},
{ immediate: true },
);
let cacheEntity = globalCache.value.get(deps.value);
// HMR clean the cache but not trigger `useMemo` again
// Let's fallback of this
// ref https://github.com/ant-design/cssinjs/issues/127
if (process.env.NODE_ENV !== 'production' && !cacheEntity) {
buildCache();
cacheEntity = globalCache.value.get(deps.value);
}
const cacheContent = computed(
() =>
(globalCache.value.get(deps.value) && globalCache.value.get(deps.value)![1]) ||
cacheEntity![1],
);
// Remove if no need anymore
useCompatibleInsertionEffect(
() => {
onCacheEffect?.(cacheContent.value);
},
polyfill => {
// It's bad to call build again in effect.
// But we have to do this since StrictMode will call effect twice
// which will clear cache on the first time.
buildCache(([times, cache]) => {
if (polyfill && times === 0) {
onCacheEffect?.(cacheContent.value);
}
return [times + 1, cache];
});
return () => {
globalCache.value.update(deps.value, prevCache => {
const [times = 0, cache] = prevCache || [];
const nextCount = times - 1;
if (nextCount <= 0) {
if (polyfill || !globalCache.value.get(deps.value)) {
onCacheRemove?.(cache, false);
}
return null;
}
return [times - 1, cache];
});
};
},
deps,
);
onBeforeUnmount(() => {
clearCache(fullPathStr.value);
buildCache();
});
return res;
return cacheContent;
}

View File

@ -3,38 +3,30 @@ import type * as CSS from 'csstype';
// @ts-ignore
import unitless from '@emotion/unitless';
import { compile, serialize, stringify } from 'stylis';
import type { Theme, Transformer } from '../..';
import type Cache from '../../Cache';
import type Keyframes from '../../Keyframes';
import type { Linter } from '../../linters';
import { contentQuotesLinter, hashedAnimationLinter } from '../../linters';
import type { HashPriority } from '../../StyleContext';
import type { Theme, Transformer } from '..';
import type Keyframes from '../Keyframes';
import type { Linter } from '../linters';
import { contentQuotesLinter, hashedAnimationLinter } from '../linters';
import type { HashPriority } from '../StyleContext';
import {
useStyleInject,
ATTR_CACHE_PATH,
ATTR_MARK,
ATTR_TOKEN,
CSS_IN_JS_INSTANCE,
} from '../../StyleContext';
import { supportLayer } from '../../util';
import useGlobalCache from '../useGlobalCache';
import { removeCSS, updateCSS } from '../../../../vc-util/Dom/dynamicCSS';
} from '../StyleContext';
import { isClientSide, supportLayer, toStyleStr } from '../util';
import { CSS_FILE_STYLE, existPath, getStyleAndHash } from '../util/cacheMapUtil';
import type { ExtractStyle } from './useGlobalCache';
import useGlobalCache from './useGlobalCache';
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
import type { Ref } from 'vue';
import { computed } from 'vue';
import type { VueNode } from '../../../type';
import canUseDom from '../../../../_util/canUseDom';
import {
ATTR_CACHE_MAP,
existPath,
getStyleAndHash,
serialize as serializeCacheMap,
} from './cacheMapUtil';
const isClientSide = canUseDom();
import type { VueNode } from '../../type';
const SKIP_CHECK = '_skip_check_';
const MULTI_VALUE = '_multi_value_';
export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & {
animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes;
};
@ -60,6 +52,7 @@ export type CSSInterpolation = InterpolationPrimitive | ArrayCSSInterpolation |
export type CSSOthersObject = Record<string, CSSInterpolation>;
// @ts-ignore
export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSSOthersObject {}
// ============================================================================
@ -114,16 +107,6 @@ export interface ParseInfo {
parentSelectors: string[];
}
// Global effect style will mount once and not removed
// The effect will not save in SSR cache (e.g. keyframes)
const globalEffectStyleKeys = new Set();
/**
* @private Test only. Clear the global effect style keys.
*/
export const _cf =
process.env.NODE_ENV !== 'production' ? () => globalEffectStyleKeys.clear() : undefined;
// Parse CSSObject to style content
export const parseStyle = (
interpolation: CSSInterpolation,
@ -258,6 +241,7 @@ export const parseStyle = (
styleStr += `${styleName}:${formatValue};`;
}
const actualValue = (value as any)?.value ?? value;
if (
typeof value === 'object' &&
@ -295,7 +279,7 @@ export const parseStyle = (
// ============================================================================
// == Register ==
// ============================================================================
function uniqueHash(path: (string | number)[], styleStr: string) {
export function uniqueHash(path: (string | number)[], styleStr: string) {
return hash(`${path.join('%')}${styleStr}`);
}
@ -303,6 +287,17 @@ function uniqueHash(path: (string | number)[], styleStr: string) {
// return null;
// }
export const STYLE_PREFIX = 'style';
type StyleCacheValue = [
styleStr: string,
tokenKey: string,
styleId: string,
effectStyle: Record<string, string>,
clientOnly: boolean | undefined,
order: number,
];
/**
* Register a style to the global style sheet.
*/
@ -337,22 +332,14 @@ export default function useStyleRegister(
}
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
useGlobalCache<
[
styleStr: string,
tokenKey: string,
styleId: string,
effectStyle: Record<string, string>,
clientOnly: boolean | undefined,
order: number,
]
>(
'style',
useGlobalCache<StyleCacheValue>(
STYLE_PREFIX,
fullPath,
// Create cache if needed
() => {
const { path, hashId, layer, nonce, clientOnly, order = 0 } = info.value;
const { path, hashId, layer, clientOnly, order = 0 } = info.value;
const cachePath = fullPath.value.join('|');
// Get style from SSR inline style directly
if (existPath(cachePath)) {
const [inlineCacheStyleStr, styleHash] = getStyleAndHash(cachePath);
@ -360,8 +347,10 @@ export default function useStyleRegister(
return [inlineCacheStyleStr, tokenKey.value, styleHash, {}, clientOnly, order];
}
}
// Generate style
const styleObj = styleFn();
const { hashPriority, container, transformers, linters, cache } = styleContext.value;
const { hashPriority, transformers, linters } = styleContext.value;
const [parsedStyle, effectStyle] = parseStyle(styleObj, {
hashId,
@ -371,18 +360,32 @@ export default function useStyleRegister(
transformers,
linters,
});
const styleStr = normalizeStyle(parsedStyle);
const styleId = uniqueHash(fullPath.value, styleStr);
if (isMergedClientSide) {
return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order];
},
// Remove cache if no need
([, , styleId], fromHMR) => {
if ((fromHMR || styleContext.value.autoClear) && isClientSide) {
removeCSS(styleId, { mark: ATTR_MARK });
}
},
// Effect: Inject style here
([styleStr, , styleId, effectStyle]) => {
if (isMergedClientSide && styleStr !== CSS_FILE_STYLE) {
const mergedCSSConfig: Parameters<typeof updateCSS>[2] = {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
priority: order,
attachTo: styleContext.value.container,
priority: info.value.order,
};
const nonceStr = typeof nonce === 'function' ? nonce() : nonce;
const nonceStr =
typeof info.value.nonce === 'function' ? info.value.nonce() : info.value.nonce;
if (nonceStr) {
mergedCSSConfig.csp = { nonce: nonceStr };
@ -390,45 +393,33 @@ export default function useStyleRegister(
const style = updateCSS(styleStr, styleId, mergedCSSConfig);
(style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId;
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value.cache.instanceId;
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, tokenKey.value);
// Dev usage to find which cache path made this easily
// Debug usage. Dev only
if (process.env.NODE_ENV !== 'production') {
style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|'));
}
// Inject client side effect style
Object.keys(effectStyle).forEach(effectKey => {
if (!globalEffectStyleKeys.has(effectKey)) {
globalEffectStyleKeys.add(effectKey);
// Inject
updateCSS(normalizeStyle(effectStyle[effectKey]), `_effect-${effectKey}`, {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
});
}
updateCSS(
normalizeStyle(effectStyle[effectKey]),
`_effect-${effectKey}`,
mergedCSSConfig,
);
});
}
return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order];
},
// Remove cache if no need
([, , styleId], fromHMR) => {
if ((fromHMR || styleContext.value.autoClear) && isClientSide) {
removeCSS(styleId, { mark: ATTR_MARK });
}
},
);
return (node: VueNode) => {
return node;
// let styleNode: VueNode;
// if (!styleContext.ssrInline || isMergedClientSide || !styleContext.defaultCache) {
// if (!styleContext.value.ssrInline || isMergedClientSide || !styleContext.value.defaultCache) {
// styleNode = <Empty />;
// } else {
// styleNode = (
@ -451,116 +442,43 @@ export default function useStyleRegister(
};
}
// ============================================================================
// == SSR ==
// ============================================================================
export function extractStyle(cache: Cache, plain = false) {
const matchPrefix = `style%`;
export const extract: ExtractStyle<StyleCacheValue> = (cache, effectStyles, options) => {
const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order]: StyleCacheValue = cache;
const { plain } = options || {};
// prefix with `style` is used for `useStyleRegister` to cache style context
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith(matchPrefix));
// Common effect styles like animation
const effectStyles: Record<string, boolean> = {};
// Mapping of cachePath to style hash
const cachePathMap: Record<string, string> = {};
let styleText = '';
function toStyleStr(
style: string,
tokenKey?: string,
styleId?: string,
customizeAttrs: Record<string, string> = {},
) {
const attrs: Record<string, string | undefined> = {
...customizeAttrs,
[ATTR_TOKEN]: tokenKey,
[ATTR_MARK]: styleId,
};
const attrStr = Object.keys(attrs)
.map(attr => {
const val = attrs[attr];
return val ? `${attr}="${val}"` : null;
})
.filter(v => v)
.join(' ');
return plain ? style : `<style ${attrStr}>${style}</style>`;
// Skip client only style
if (clientOnly) {
return null;
}
// ====================== Fill Style ======================
type OrderStyle = [order: number, style: string];
let keyStyleText = styleStr;
const orderStyles: OrderStyle[] = styleKeys
.map(key => {
const cachePath = key.slice(matchPrefix.length).replace(/%/g, '|');
// ====================== Style ======================
// Used for rc-util
const sharedAttrs = {
'data-vc-order': 'prependQueue',
'data-vc-priority': `${order}`,
};
const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order]: [
string,
string,
string,
Record<string, string>,
boolean,
number,
] = cache.cache.get(key)![1];
keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs, plain);
// Skip client only style
if (clientOnly) {
return null! as OrderStyle;
// =============== Create effect style ===============
if (effectStyle) {
Object.keys(effectStyle).forEach(effectKey => {
// Effect style can be reused
if (!effectStyles[effectKey]) {
effectStyles[effectKey] = true;
const effectStyleStr = normalizeStyle(effectStyle[effectKey]);
keyStyleText += toStyleStr(
effectStyleStr,
tokenKey,
`_effect-${effectKey}`,
sharedAttrs,
plain,
);
}
// ====================== Style ======================
// Used for vc-util
const sharedAttrs = {
'data-vc-order': 'prependQueue',
'data-vc-priority': `${order}`,
};
let keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs);
// Save cache path with hash mapping
cachePathMap[cachePath] = styleId;
// =============== Create effect style ===============
if (effectStyle) {
Object.keys(effectStyle).forEach(effectKey => {
// Effect style can be reused
if (!effectStyles[effectKey]) {
effectStyles[effectKey] = true;
keyStyleText += toStyleStr(
normalizeStyle(effectStyle[effectKey]),
tokenKey,
`_effect-${effectKey}`,
sharedAttrs,
);
}
});
}
const ret: OrderStyle = [order, keyStyleText];
return ret;
})
.filter(o => o);
orderStyles
.sort((o1, o2) => o1[0] - o2[0])
.forEach(([, style]) => {
styleText += style;
});
}
// ==================== Fill Cache Path ====================
styleText += toStyleStr(
`.${ATTR_CACHE_MAP}{content:"${serializeCacheMap(cachePathMap)}";}`,
undefined,
undefined,
{
[ATTR_CACHE_MAP]: ATTR_CACHE_MAP,
},
);
return styleText;
}
return [order, styleId, keyStyleText];
};

View File

@ -1,51 +1,37 @@
import useCacheToken from './hooks/useCacheToken';
import extractStyle from './extractStyle';
import useCacheToken, { getComputedToken } from './hooks/useCacheToken';
import useCSSVarRegister from './hooks/useCSSVarRegister';
import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister';
import useStyleRegister, { extractStyle } from './hooks/useStyleRegister';
import useStyleRegister from './hooks/useStyleRegister';
import Keyframes from './Keyframes';
import type { Linter } from './linters';
import { legacyNotSelectorLinter, logicalPropertiesLinter, parentSelectorLinter } from './linters';
import type { StyleContextProps, StyleProviderProps } from './StyleContext';
import { createCache, useStyleInject, useStyleProvider, StyleProvider } from './StyleContext';
import {
legacyNotSelectorLinter,
logicalPropertiesLinter,
NaNLinter,
parentSelectorLinter,
} from './linters';
import type { StyleProviderProps } from './StyleContext';
import { createCache, StyleProvider } from './StyleContext';
import type { DerivativeFunc, TokenType } from './theme';
import { createTheme, Theme } from './theme';
import type { Transformer } from './transformers/interface';
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
import px2remTransformer from './transformers/px2rem';
import { supportLogicProps, supportWhere } from './util';
import { supportLogicProps, supportWhere, unit } from './util';
import { token2CSSVar } from './util/css-variables';
const cssinjs = {
Theme,
createTheme,
useStyleRegister,
useCacheToken,
createCache,
useStyleInject,
useStyleProvider,
Keyframes,
extractStyle,
// Transformer
legacyLogicalPropertiesTransformer,
px2remTransformer,
// Linters
logicalPropertiesLinter,
legacyNotSelectorLinter,
parentSelectorLinter,
// cssinjs
StyleProvider,
};
export {
Theme,
createTheme,
useStyleRegister,
useCSSVarRegister,
useCacheToken,
createCache,
useStyleInject,
useStyleProvider,
StyleProvider,
Keyframes,
extractStyle,
getComputedToken,
// Transformer
legacyLogicalPropertiesTransformer,
@ -55,9 +41,11 @@ export {
logicalPropertiesLinter,
legacyNotSelectorLinter,
parentSelectorLinter,
NaNLinter,
// cssinjs
StyleProvider,
// util
token2CSSVar,
unit,
};
export type {
TokenType,
@ -66,12 +54,9 @@ export type {
DerivativeFunc,
Transformer,
Linter,
StyleContextProps,
StyleProviderProps,
};
export const _experimental = {
supportModernCSS: () => supportWhere() && supportLogicProps(),
};
export default cssinjs;

View File

@ -0,0 +1,10 @@
import type { Linter } from './interface';
import { lintWarning } from './utils';
const linter: Linter = (key, value, info) => {
if ((typeof value === 'string' && /NaN/g.test(value)) || Number.isNaN(value)) {
lintWarning(`Unexpected 'NaN' in property '${key}: ${value}'.`, info);
}
};
export default linter;

View File

@ -3,4 +3,5 @@ export { default as hashedAnimationLinter } from './hashedAnimationLinter';
export type { Linter } from './interface';
export { default as legacyNotSelectorLinter } from './legacyNotSelectorLinter';
export { default as logicalPropertiesLinter } from './logicalPropertiesLinter';
export { default as NaNLinter } from './NaNLinter';
export { default as parentSelectorLinter } from './parentSelectorLinter';

View File

@ -6,8 +6,8 @@ export function lintWarning(message: string, info: LinterInfo) {
devWarning(
false,
`[Ant Design Vue CSS-in-JS] ${path ? `Error in '${path}': ` : ''}${message}${
parentSelectors.length ? ` Selector info: ${parentSelectors.join(' -> ')}` : ''
`[Ant Design Vue CSS-in-JS] ${path ? `Error in ${path}: ` : ''}${message}${
parentSelectors.length ? ` Selector: ${parentSelectors.join(' | ')}` : ''
}`,
);
}

View File

@ -1,34 +1,36 @@
import type { CSSObject } from '..';
import type { Transformer } from './interface';
function splitValues(value: string | number) {
function splitValues(value: string | number): [values: (string | number)[], important: boolean] {
if (typeof value === 'number') {
return [value];
return [[value], false];
}
const splitStyle = String(value).split(/\s+/);
const rawStyle = String(value).trim();
const importantCells = rawStyle.match(/(.*)(!important)/);
const splitStyle = (importantCells ? importantCells[1] : rawStyle).trim().split(/\s+/);
// Combine styles split in brackets, like `calc(1px + 2px)`
let temp = '';
let brackets = 0;
return splitStyle.reduce<string[]>((list, item) => {
if (item.includes('(')) {
temp += item;
brackets += item.split('(').length - 1;
} else if (item.includes(')')) {
temp += ` ${item}`;
brackets -= item.split(')').length - 1;
if (brackets === 0) {
list.push(temp);
temp = '';
return [
splitStyle.reduce<string[]>((list, item) => {
if (item.includes('(') || item.includes(')')) {
const left = item.split('(').length - 1;
const right = item.split(')').length - 1;
brackets += left - right;
}
} else if (brackets > 0) {
temp += ` ${item}`;
} else {
list.push(item);
}
return list;
}, []);
if (brackets === 0) {
list.push(temp + item);
temp = '';
} else if (brackets > 0) {
temp += item;
}
return list;
}, []),
!!importantCells,
];
}
type MatchValue = string[] & {
@ -105,8 +107,14 @@ const keyMap: Record<string, MatchValue> = {
borderEndEndRadius: ['borderBottomRightRadius'],
};
function skipCheck(value: string | number) {
return { _skip_check_: true, value };
function wrapImportantAndSkipCheck(value: string | number, important: boolean) {
let parsedValue = value;
if (important) {
parsedValue = `${parsedValue} !important`;
}
return { _skip_check_: true, value: parsedValue };
}
/**
@ -127,25 +135,28 @@ const transform: Transformer = {
const matchValue = keyMap[key];
if (matchValue && (typeof value === 'number' || typeof value === 'string')) {
const values = splitValues(value);
const [values, important] = splitValues(value);
if (matchValue.length && matchValue.notSplit) {
// not split means always give same value like border
matchValue.forEach(matchKey => {
clone[matchKey] = skipCheck(value);
clone[matchKey] = wrapImportantAndSkipCheck(value, important);
});
} else if (matchValue.length === 1) {
// Handle like `marginBlockStart` => `marginTop`
clone[matchValue[0]] = skipCheck(value);
clone[matchValue[0]] = wrapImportantAndSkipCheck(value, important);
} else if (matchValue.length === 2) {
// Handle like `marginBlock` => `marginTop` & `marginBottom`
matchValue.forEach((matchKey, index) => {
clone[matchKey] = skipCheck(values[index] ?? values[0]);
clone[matchKey] = wrapImportantAndSkipCheck(values[index] ?? values[0], important);
});
} else if (matchValue.length === 4) {
// Handle like `inset` => `top` & `right` & `bottom` & `left`
matchValue.forEach((matchKey, index) => {
clone[matchKey] = skipCheck(values[index] ?? values[index - 2] ?? values[0]);
clone[matchKey] = wrapImportantAndSkipCheck(
values[index] ?? values[index - 2] ?? values[0],
important,
);
});
} else {
clone[key] = value;

View File

@ -1,6 +1,7 @@
/**
* respect https://github.com/cuth/postcss-pxtorem
*/
// @ts-ignore
import unitless from '@emotion/unitless';
import type { CSSObject } from '..';
import type { Transformer } from './interface';

View File

@ -1,5 +1,5 @@
import canUseDom from '../../../../_util/canUseDom';
import { ATTR_MARK } from '../../StyleContext';
import canUseDom from '../../canUseDom';
import { ATTR_MARK } from '../StyleContext';
export const ATTR_CACHE_MAP = 'data-ant-cssinjs-cache-path';

View File

@ -0,0 +1,58 @@
export const token2CSSVar = (token: string, prefix = '') => {
return `--${prefix ? `${prefix}-` : ''}${token}`
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, '$1-$2')
.replace(/([a-z])([A-Z0-9])/g, '$1-$2')
.toLowerCase();
};
export const serializeCSSVar = <T extends Record<string, any>>(
cssVars: T,
hashId: string,
options?: {
scope?: string;
},
) => {
if (!Object.keys(cssVars).length) {
return '';
}
return `.${hashId}${options?.scope ? `.${options.scope}` : ''}{${Object.entries(cssVars)
.map(([key, value]) => `${key}:${value};`)
.join('')}}`;
};
export type TokenWithCSSVar<V, T extends Record<string, V> = Record<string, V>> = {
[key in keyof T]?: string | V;
};
export const transformToken = <V, T extends Record<string, V> = Record<string, V>>(
token: T,
themeKey: string,
config?: {
prefix?: string;
ignore?: {
[key in keyof T]?: boolean;
};
unitless?: {
[key in keyof T]?: boolean;
};
preserve?: {
[key in keyof T]?: boolean;
};
scope?: string;
},
): [TokenWithCSSVar<V, T>, string] => {
const cssVars: Record<string, string> = {};
const result: TokenWithCSSVar<V, T> = {};
Object.entries(token).forEach(([key, value]) => {
if (config?.preserve?.[key]) {
result[key as keyof T] = value;
} else if ((typeof value === 'string' || typeof value === 'number') && !config?.ignore?.[key]) {
const cssVar = token2CSSVar(key, config?.prefix);
cssVars[cssVar] =
typeof value === 'number' && !config?.unitless?.[key] ? `${value}px` : String(value);
result[key as keyof T] = `var(${cssVar})`;
}
});
return [result, serializeCSSVar(cssVars, themeKey, { scope: config?.scope })];
};

View File

@ -1,12 +1,37 @@
import hash from '@emotion/hash';
import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
import canUseDom from '../canUseDom';
import canUseDom from '../../canUseDom';
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
import { ATTR_MARK, ATTR_TOKEN } from '../StyleContext';
import { Theme } from '../theme';
import { Theme } from './theme';
// Create a cache for memo concat
type NestWeakMap<T> = WeakMap<object, NestWeakMap<T> | T>;
const resultCache: NestWeakMap<object> = new WeakMap();
const RESULT_VALUE = {};
export function memoResult<T extends object, R>(callback: () => R, deps: T[]): R {
let current: WeakMap<any, any> = resultCache;
for (let i = 0; i < deps.length; i += 1) {
const dep = deps[i];
if (!current.has(dep)) {
current.set(dep, new WeakMap());
}
current = current.get(dep)!;
}
if (!current.has(RESULT_VALUE)) {
current.set(RESULT_VALUE, callback());
}
return current.get(RESULT_VALUE);
}
// Create a cache here to avoid always loop generate
const flattenTokenCache = new WeakMap<any, string>();
/**
* Flatten token to string, this will auto cache the result when token not change
*/
export function flattenToken(token: any) {
let str = flattenTokenCache.get(token) || '';
@ -116,3 +141,39 @@ export function supportLogicProps(): boolean {
return canLogic!;
}
export const isClientSide = canUseDom();
export function unit(num: string | number) {
if (typeof num === 'number') {
return `${num}px`;
}
return num;
}
export function toStyleStr(
style: string,
tokenKey?: string,
styleId?: string,
customizeAttrs: Record<string, string> = {},
plain = false,
) {
if (plain) {
return style;
}
const attrs: Record<string, string | undefined> = {
...customizeAttrs,
[ATTR_TOKEN]: tokenKey,
[ATTR_MARK]: styleId,
};
const attrStr = Object.keys(attrs)
.map(attr => {
const val = attrs[attr];
return val ? `${attr}="${val}"` : null;
})
.filter(v => v)
.join(' ');
return `<style ${attrStr}>${style}</style>`;
}

View File

@ -2,32 +2,31 @@ export function isWindow(obj: any): obj is Window {
return obj !== null && obj !== undefined && obj === obj.window;
}
export default function getScroll(
target: HTMLElement | Window | Document | null,
top: boolean,
): number {
const getScroll = (target: HTMLElement | Window | Document | null): number => {
if (typeof window === 'undefined') {
return 0;
}
const method = top ? 'scrollTop' : 'scrollLeft';
let result = 0;
if (isWindow(target)) {
result = target[top ? 'scrollY' : 'scrollX'];
result = target.pageYOffset;
} else if (target instanceof Document) {
result = target.documentElement[method];
result = target.documentElement.scrollTop;
} else if (target instanceof HTMLElement) {
result = target[method];
result = target.scrollTop;
} else if (target) {
// According to the type inference, the `target` is `never` type.
// Since we configured the loose mode type checking, and supports mocking the target with such shape below::
// `{ documentElement: { scrollLeft: 200, scrollTop: 400 } }`,
// the program may falls into this branch.
// Check the corresponding tests for details. Don't sure what is the real scenario this happens.
result = target[method];
/* biome-ignore lint/complexity/useLiteralKeys: target is a never type */ /* eslint-disable-next-line dot-notation */
result = target['scrollTop'];
}
if (target && !isWindow(target) && typeof result !== 'number') {
result = ((target.ownerDocument ?? target) as any).documentElement?.[method];
result = (target.ownerDocument ?? (target as Document)).documentElement?.scrollTop;
}
return result;
}
};
export default getScroll;

View File

@ -0,0 +1,48 @@
import type { Ref, ShallowRef } from 'vue';
import { shallowRef, ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
function useLayoutEffect(
fn: (mount: boolean) => void | VoidFunction,
deps?: Ref<any> | Ref<any>[] | ShallowRef<any> | ShallowRef<any>[],
) {
const firstMount = shallowRef(true);
const cleanupFn = ref(null);
let stopWatch = null;
stopWatch = watch(
deps,
() => {
nextTick(() => {
if (cleanupFn.value) {
cleanupFn.value();
}
cleanupFn.value = fn(firstMount.value);
});
},
{ immediate: true, flush: 'post' },
);
onMounted(() => {
firstMount.value = false;
});
onUnmounted(() => {
if (cleanupFn.value) {
cleanupFn.value();
}
if (stopWatch) {
stopWatch();
}
});
}
export const useLayoutUpdateEffect = (callback, deps) => {
useLayoutEffect(firstMount => {
if (!firstMount) {
return callback();
}
}, deps);
};
export default useLayoutEffect;

View File

@ -14,7 +14,7 @@ interface ScrollToOptions {
export default function scrollTo(y: number, options: ScrollToOptions = {}) {
const { getContainer = () => window, callback, duration = 450 } = options;
const container = getContainer();
const scrollTop = getScroll(container, true);
const scrollTop = getScroll(container);
const startTime = Date.now();
const frameFunc = () => {

View File

@ -27,10 +27,11 @@ import useStyle from './style';
function getDefaultTarget() {
return typeof window !== 'undefined' ? window : null;
}
enum AffixStatus {
None,
Prepare,
}
const AFFIX_STATUS_NONE = 0;
const AFFIX_STATUS_PREPARE = 1;
type AffixStatus = typeof AFFIX_STATUS_NONE | typeof AFFIX_STATUS_PREPARE;
export interface AffixState {
affixStyle?: CSSProperties;
placeholderStyle?: CSSProperties;
@ -82,7 +83,7 @@ const Affix = defineComponent({
const state = reactive({
affixStyle: undefined,
placeholderStyle: undefined,
status: AffixStatus.None,
status: AFFIX_STATUS_NONE,
lastAffix: false,
prevTarget: null,
timeout: null,
@ -98,7 +99,12 @@ const Affix = defineComponent({
const measure = () => {
const { status, lastAffix } = state;
const { target } = props;
if (status !== AffixStatus.Prepare || !fixedNode.value || !placeholderNode.value || !target) {
if (
status !== AFFIX_STATUS_PREPARE ||
!fixedNode.value ||
!placeholderNode.value ||
!target
) {
return;
}
@ -108,7 +114,7 @@ const Affix = defineComponent({
}
const newState = {
status: AffixStatus.None,
status: AFFIX_STATUS_NONE,
} as AffixState;
const placeholderRect = getTargetRect(placeholderNode.value as HTMLElement);
@ -172,7 +178,7 @@ const Affix = defineComponent({
};
const prepareMeasure = () => {
Object.assign(state, {
status: AffixStatus.Prepare,
status: AFFIX_STATUS_PREPARE,
affixStyle: undefined,
placeholderStyle: undefined,
});
@ -253,12 +259,13 @@ const Affix = defineComponent({
});
const { prefixCls } = useConfigInject('affix', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls);
return () => {
const { affixStyle, placeholderStyle, status } = state;
const className = classNames({
[prefixCls.value]: affixStyle,
[hashId.value]: true,
[cssVarCls.value]: true,
});
const restProps = omit(props, [
'prefixCls',

View File

@ -1,15 +1,21 @@
import type { CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { FullToken, GenerateStyle, genStyleHooks, GetDefaultToken } from '../../theme/internal';
export interface ComponentToken {
/**
* @desc z-index
* @descEN z-index of popup
*/
zIndexPopup: number;
}
interface AffixToken extends FullToken<'Affix'> {
zIndexPopup: number;
//
}
// ============================== Shared ==============================
const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => {
const { componentCls } = token;
return {
[componentCls]: {
position: 'fixed',
@ -18,10 +24,9 @@ const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => {
};
};
// ============================== Export ==============================
export default genComponentStyleHook('Affix', token => {
const affixToken = mergeToken<AffixToken>(token, {
zIndexPopup: token.zIndexBase + 10,
});
return [genSharedAffixStyle(affixToken)];
export const prepareComponentToken: GetDefaultToken<'Affix'> = token => ({
zIndexPopup: token.zIndexBase + 10,
});
// ============================== Export ==============================
export default genStyleHooks('Affix', genSharedAffixStyle, prepareComponentToken);

View File

@ -9,8 +9,11 @@ export function getTargetRect(target: BindElement): DOMRect {
: ({ top: 0, bottom: window.innerHeight } as DOMRect);
}
export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offsetTop: number) {
if (offsetTop !== undefined && targetRect.top > placeholderRect.top - offsetTop) {
export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offsetTop?: number) {
if (
offsetTop !== undefined &&
Math.round(targetRect.top) > Math.round(placeholderRect.top) - offsetTop
) {
return `${offsetTop + targetRect.top}px`;
}
return undefined;
@ -19,9 +22,12 @@ export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offse
export function getFixedBottom(
placeholderRect: DOMRect,
targetRect: DOMRect,
offsetBottom: number,
offsetBottom?: number,
) {
if (offsetBottom !== undefined && targetRect.bottom < placeholderRect.bottom + offsetBottom) {
if (
offsetBottom !== undefined &&
Math.round(targetRect.bottom) < Math.round(placeholderRect.bottom) + offsetBottom
) {
const targetBottomOffset = window.innerHeight - targetRect.bottom;
return `${offsetBottom + targetBottomOffset}px`;
}
@ -29,7 +35,7 @@ export function getFixedBottom(
}
// ======================== Observer ========================
const TRIGGER_EVENTS = [
const TRIGGER_EVENTS: (keyof WindowEventMap)[] = [
'resize',
'scroll',
'touchstart',

View File

@ -70,7 +70,7 @@ const Alert = defineComponent({
props: alertProps(),
setup(props, { slots, emit, attrs, expose }) {
const { prefixCls, direction } = useConfigInject('alert', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls);
const closing = shallowRef(false);
const closed = shallowRef(false);
const alertNode = shallowRef();
@ -134,6 +134,7 @@ const Alert = defineComponent({
[`${prefixClsValue}-closable`]: closable,
[`${prefixClsValue}-rtl`]: direction.value === 'rtl',
[hashId.value]: true,
[cssVarCls.value]: true,
});
const closeIcon = closable ? (

View File

@ -1,13 +1,29 @@
import type { CSSInterpolation, CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { CSSObject, unit } from '../../_util/cssinjs';
import { FullToken, GenerateStyle, genStyleHooks, GetDefaultToken } from '../../theme/internal';
import { resetComponent } from '../../style';
import { CSSProperties } from 'vue';
export interface ComponentToken {}
export interface ComponentToken {
// Component token here
/**
* @desc
* @descEN Default padding
*/
defaultPadding: CSSProperties['padding'];
/**
* @desc
* @descEN Padding with description
*/
withDescriptionPadding: CSSProperties['padding'];
/**
* @desc
* @descEN Icon size with description
*/
withDescriptionIconSize: number;
}
type AlertToken = FullToken<'Alert'> & {
alertIconSizeLG: number;
alertPaddingHorizontal: number;
// Custom token here
};
const genAlertTypeStyle = (
@ -17,8 +33,8 @@ const genAlertTypeStyle = (
token: AlertToken,
alertCls: string,
): CSSObject => ({
backgroundColor: bgColor,
border: `${token.lineWidth}px ${token.lineType} ${borderColor}`,
background: bgColor,
border: `${unit(token.lineWidth)} ${token.lineType} ${borderColor}`,
[`${alertCls}-icon`]: {
color: iconColor,
},
@ -35,12 +51,11 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
lineHeight,
borderRadiusLG: borderRadius,
motionEaseInOutCirc,
alertIconSizeLG,
withDescriptionIconSize,
colorText,
paddingContentVerticalSM,
alertPaddingHorizontal,
paddingMD,
paddingContentHorizontalLG,
colorTextHeading,
withDescriptionPadding,
defaultPadding,
} = token;
return {
@ -49,7 +64,7 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
position: 'relative',
display: 'flex',
alignItems: 'center',
padding: `${paddingContentVerticalSM}px ${alertPaddingHorizontal}px`, // Fixed horizontal padding here.
padding: defaultPadding,
wordWrap: 'break-word',
borderRadius,
@ -67,14 +82,14 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
lineHeight: 0,
},
[`&-description`]: {
'&-description': {
display: 'none',
fontSize,
lineHeight,
},
'&-message': {
color: colorText,
color: colorTextHeading,
},
[`&${componentCls}-motion-leave`]: {
@ -96,24 +111,23 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
[`${componentCls}-with-description`]: {
alignItems: 'flex-start',
paddingInline: paddingContentHorizontalLG,
paddingBlock: paddingMD,
padding: withDescriptionPadding,
[`${componentCls}-icon`]: {
marginInlineEnd: marginSM,
fontSize: alertIconSizeLG,
fontSize: withDescriptionIconSize,
lineHeight: 0,
},
[`${componentCls}-message`]: {
display: 'block',
marginBottom: marginXS,
color: colorText,
color: colorTextHeading,
fontSize: fontSizeLG,
},
[`${componentCls}-description`]: {
display: 'block',
color: colorText,
},
},
@ -187,7 +201,7 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
return {
[componentCls]: {
[`&-action`]: {
'&-action': {
marginInlineStart: marginXS,
},
@ -196,7 +210,7 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
padding: 0,
overflow: 'hidden',
fontSize: fontSizeIcon,
lineHeight: `${fontSizeIcon}px`,
lineHeight: unit(fontSizeIcon),
backgroundColor: 'transparent',
border: 'none',
outline: 'none',
@ -222,19 +236,17 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
};
};
export const genAlertStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSInterpolation => [
genBaseStyle(token),
genTypeStyle(token),
genActionStyle(token),
];
export const prepareComponentToken: GetDefaultToken<'Alert'> = token => {
const paddingHorizontal = 12; // Fixed value here.
return {
withDescriptionIconSize: token.fontSizeHeading3,
defaultPadding: `${token.paddingContentVerticalSM}px ${paddingHorizontal}px`,
withDescriptionPadding: `${token.paddingMD}px ${token.paddingContentHorizontalLG}px`,
};
};
export default genComponentStyleHook('Alert', token => {
const { fontSizeHeading3 } = token;
const alertToken = mergeToken<AlertToken>(token, {
alertIconSizeLG: fontSizeHeading3,
alertPaddingHorizontal: 12, // Fixed value here.
});
return [genAlertStyle(alertToken)];
});
export default genStyleHooks(
'Alert',
token => [genBaseStyle(token), genTypeStyle(token), genActionStyle(token)],
prepareComponentToken,
);

View File

@ -23,6 +23,7 @@ import AnchorLink from './AnchorLink';
import PropTypes from '../_util/vue-types';
import devWarning from '../vc-util/devWarning';
import { arrayType } from '../_util/type';
import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
export type AnchorDirection = 'vertical' | 'horizontal';
@ -39,8 +40,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
if (rect.width || rect.height) {
if (container === window) {
container = element.ownerDocument!.documentElement!;
return rect.top - container.clientTop;
return rect.top - element.ownerDocument!.documentElement!.clientTop;
}
return rect.top - (container as HTMLElement).getBoundingClientRect().top;
}
@ -70,6 +70,7 @@ export const anchorProps = () => ({
targetOffset: Number,
items: arrayType<AnchorLinkItemProps[]>(),
direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'),
replace: Boolean,
onChange: Function as PropType<(currentActiveLink: string) => void>,
onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>,
});
@ -91,7 +92,7 @@ export default defineComponent({
setup(props, { emit, attrs, slots, expose }) {
const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props);
const anchorDirection = computed(() => props.direction ?? 'vertical');
const rootCls = useCSSVarCls(prefixCls);
if (process.env.NODE_ENV !== 'production') {
devWarning(
props.items && typeof slots.default !== 'function',
@ -133,7 +134,7 @@ export default defineComponent({
const target = document.getElementById(sharpLinkMatch[1]);
if (target) {
const top = getOffsetTop(target, container);
if (top < offsetTop + bounds) {
if (top <= offsetTop + bounds) {
linkSections.push({
link,
top,
@ -170,7 +171,7 @@ export default defineComponent({
}
const container = getContainer.value();
const scrollTop = getScroll(container, true);
const scrollTop = getScroll(container);
const eleOffsetTop = getOffsetTop(targetElement, container);
let y = scrollTop + eleOffsetTop;
y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
@ -277,6 +278,7 @@ export default defineComponent({
title={title}
customTitleProps={option}
v-slots={{ customTitle: slots.customTitle }}
replace={props.replace}
>
{anchorDirection.value === 'vertical' ? createNestedLink(children) : null}
</AnchorLink>
@ -284,7 +286,7 @@ export default defineComponent({
})
: null;
const [wrapSSR, hashId] = useStyle(prefixCls);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
return () => {
const { offsetTop, affix, showInkInFixed } = props;
@ -296,6 +298,8 @@ export default defineComponent({
const wrapperClass = classNames(hashId.value, props.wrapperClass, `${pre}-wrapper`, {
[`${pre}-wrapper-horizontal`]: anchorDirection.value === 'horizontal',
[`${pre}-rtl`]: direction.value === 'rtl',
[rootCls.value]: true,
[cssVarCls.value]: true,
});
const anchorClass = classNames(pre, {

View File

@ -13,6 +13,7 @@ export const anchorLinkProps = () => ({
href: String,
title: anyType<VueNode | ((item: any) => VueNode)>(),
target: String,
replace: Boolean,
/* private use */
customTitleProps: objectType<AnchorLinkItemProps>(),
});
@ -53,6 +54,10 @@ export default defineComponent({
const { href } = props;
contextHandleClick(e, { title: mergedTitle, href });
scrollTo(href);
if (props.replace) {
e.preventDefault();
window.location.replace(href);
}
};
watch(

View File

@ -1,21 +1,55 @@
import type { CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { unit } from '../../_util/cssinjs';
import {
FullToken,
GenerateStyle,
genStyleHooks,
GetDefaultToken,
mergeToken,
} from '../../theme/internal';
import { resetComponent, textEllipsis } from '../../style';
export interface ComponentToken {}
export interface ComponentToken {
/**
* @desc
* @descEN Vertical padding of link
*/
linkPaddingBlock: number;
/**
* @desc
* @descEN Horizontal padding of link
*/
linkPaddingInlineStart: number;
}
/**
* @desc Anchor Token
* @descEN Token for Anchor component
*/
interface AnchorToken extends FullToken<'Anchor'> {
/**
* @desc
* @descEN Holder block offset
*/
holderOffsetBlock: number;
anchorPaddingBlock: number;
anchorPaddingBlockSecondary: number;
anchorPaddingInline: number;
anchorBallSize: number;
anchorTitleBlock: number;
/**
* @desc
* @descEN Secondary anchor block padding
*/
anchorPaddingBlockSecondary: number | string;
/**
* @desc
* @descEN Anchor ball size
*/
anchorBallSize: number | string;
/**
* @desc
* @descEN Anchor title block
*/
anchorTitleBlock: number | string;
}
// ============================== Shared ==============================
const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
const genSharedAnchorStyle: GenerateStyle<AnchorToken> = token => {
const {
componentCls,
holderOffsetBlock,
@ -24,26 +58,25 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
colorPrimary,
lineType,
colorSplit,
calc,
} = token;
return {
[`${componentCls}-wrapper`]: {
marginBlockStart: -holderOffsetBlock,
marginBlockStart: calc(holderOffsetBlock).mul(-1).equal(),
paddingBlockStart: holderOffsetBlock,
// delete overflow: auto
// overflow: 'auto',
backgroundColor: 'transparent',
[componentCls]: {
...resetComponent(token),
position: 'relative',
paddingInlineStart: lineWidthBold,
[`${componentCls}-link`]: {
paddingBlock: token.anchorPaddingBlock,
paddingInline: `${token.anchorPaddingInline}px 0`,
paddingBlock: token.linkPaddingBlock,
paddingInline: `${unit(token.linkPaddingInlineStart)} 0`,
'&-title': {
...textEllipsis,
@ -73,28 +106,21 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
[componentCls]: {
'&::before': {
position: 'absolute',
left: {
_skip_check_: true,
value: 0,
},
insetInlineStart: 0,
top: 0,
height: '100%',
borderInlineStart: `${lineWidthBold}px ${lineType} ${colorSplit}`,
borderInlineStart: `${unit(lineWidthBold)} ${lineType} ${colorSplit}`,
content: '" "',
},
[`${componentCls}-ink`]: {
position: 'absolute',
left: {
_skip_check_: true,
value: 0,
},
insetInlineStart: 0,
display: 'none',
transform: 'translateY(-50%)',
transition: `top ${motionDurationSlow} ease-in-out`,
width: lineWidthBold,
backgroundColor: colorPrimary,
[`&${componentCls}-ink-visible`]: {
display: 'inline-block',
},
@ -109,7 +135,7 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
};
};
const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = token => {
const { componentCls, motionDurationSlow, lineWidthBold, colorPrimary } = token;
return {
@ -127,7 +153,7 @@ const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token): CSSO
value: 0,
},
bottom: 0,
borderBottom: `1px ${token.lineType} ${token.colorSplit}`,
borderBottom: `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}`,
content: '" "',
},
@ -157,17 +183,23 @@ const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token): CSSO
};
};
// ============================== Export ==============================
export default genComponentStyleHook('Anchor', token => {
const { fontSize, fontSizeLG, padding, paddingXXS } = token;
const anchorToken = mergeToken<AnchorToken>(token, {
holderOffsetBlock: paddingXXS,
anchorPaddingBlock: paddingXXS,
anchorPaddingBlockSecondary: paddingXXS / 2,
anchorPaddingInline: padding,
anchorTitleBlock: (fontSize / 14) * 3,
anchorBallSize: fontSizeLG / 2,
});
return [genSharedAnchorStyle(anchorToken), genSharedAnchorHorizontalStyle(anchorToken)];
export const prepareComponentToken: GetDefaultToken<'Anchor'> = token => ({
linkPaddingBlock: token.paddingXXS,
linkPaddingInlineStart: token.padding,
});
// ============================== Export ==============================
export default genStyleHooks(
'Anchor',
token => {
const { fontSize, fontSizeLG, paddingXXS, calc } = token;
const anchorToken = mergeToken<AnchorToken>(token, {
holderOffsetBlock: paddingXXS,
anchorPaddingBlockSecondary: calc(paddingXXS).div(2).equal(),
anchorTitleBlock: calc(fontSize).div(14).mul(3).equal(),
anchorBallSize: calc(fontSizeLG).div(2).equal(),
});
return [genSharedAnchorStyle(anchorToken), genSharedAnchorHorizontalStyle(anchorToken)];
},
prepareComponentToken,
);

View File

@ -4,6 +4,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genPresetColor, resetComponent } from '../../style';
export interface ComponentToken {}
interface BadgeToken extends FullToken<'Badge'> {
badgeFontHeight: number;
badgeZIndex: number | string;

View File

@ -3,6 +3,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genFocusStyle, resetComponent } from '../../style';
export interface ComponentToken {}
interface BreadcrumbToken extends FullToken<'Breadcrumb'> {
breadcrumbBaseColor: string;
breadcrumbFontSize: number;

View File

@ -45,7 +45,11 @@ export default defineComponent({
break;
default:
// eslint-disable-next-line no-console
devWarning(!size, 'Button.Group', 'Invalid prop `size`.');
devWarning(
!size || ['large', 'small', 'middle'].includes(size),
'Button.Group',
'Invalid prop `size`.',
);
}
return {
[`${prefixCls.value}`]: true,

View File

@ -45,7 +45,7 @@ export default defineComponent({
// emits: ['click', 'mousedown'],
setup(props, { slots, attrs, emit, expose }) {
const { prefixCls, autoInsertSpaceInButton, direction, size } = useConfigInject('btn', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
const groupSizeContext = GroupSizeContext.useInject();
const disabledContext = useInjectDisabled();
const mergedDisabled = computed(() => props.disabled ?? disabledContext.value);
@ -95,6 +95,7 @@ export default defineComponent({
compactItemClassnames.value,
{
[hashId.value]: true,
[cssVarCls.value]: true,
[`${pre}`]: true,
[`${pre}-${shape}`]: shape !== 'default' && shape,
[`${pre}-${type}`]: type,
@ -216,7 +217,7 @@ export default defineComponent({
);
if (href !== undefined) {
return wrapSSR(
return wrapCSSVar(
<a {...buttonProps} href={href} target={target} ref={buttonNodeRef}>
{iconNode}
{kids}
@ -239,7 +240,7 @@ export default defineComponent({
);
}
return wrapSSR(buttonNode);
return wrapCSSVar(buttonNode);
};
},
});

View File

@ -0,0 +1,72 @@
// Style as inline component
import type { ButtonToken } from './token';
import { prepareComponentToken, prepareToken } from './token';
import { genCompactItemStyle } from '../../style/compact-item';
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
import type { GenerateStyle } from '../../theme/internal';
import { genSubStyleComponent } from '../../theme/internal';
import type { CSSObject } from '../../_util/cssinjs';
import { unit } from '../../_util/cssinjs';
const genButtonCompactStyle: GenerateStyle<ButtonToken, CSSObject> = token => {
const { componentCls, calc } = token;
return {
[componentCls]: {
// Special styles for Primary Button
[`&-compact-item${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: calc(token.lineWidth).mul(-1).equal(),
insetInlineStart: calc(token.lineWidth).mul(-1).equal(),
display: 'inline-block',
width: token.lineWidth,
height: `calc(100% + ${unit(token.lineWidth)} * 2)`,
backgroundColor: token.colorPrimaryHover,
content: '""',
},
},
},
// Special styles for Primary Button
'&-compact-vertical-item': {
[`&${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: calc(token.lineWidth).mul(-1).equal(),
insetInlineStart: calc(token.lineWidth).mul(-1).equal(),
display: 'inline-block',
width: `calc(100% + ${unit(token.lineWidth)} * 2)`,
height: token.lineWidth,
backgroundColor: token.colorPrimaryHover,
content: '""',
},
},
},
},
},
};
};
// ============================== Export ==============================
export default genSubStyleComponent(
['Button', 'compact'],
token => {
const buttonToken = prepareToken(token);
return [
// Space Compact
genCompactItemStyle(buttonToken),
genCompactItemVerticalStyle(buttonToken),
genButtonCompactStyle(buttonToken),
] as CSSObject[];
},
prepareComponentToken,
);

View File

@ -1,4 +1,5 @@
import type { ButtonToken } from '.';
import type { CSSObject } from '../../_util/cssinjs';
import type { ButtonToken } from './token';
import type { GenerateStyle } from '../../theme/internal';
const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({
@ -22,8 +23,8 @@ const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({
},
});
const genGroupStyle: GenerateStyle<ButtonToken> = token => {
const { componentCls, fontSize, lineWidth, colorPrimaryHover, colorErrorHover } = token;
const genGroupStyle: GenerateStyle<ButtonToken, CSSObject> = token => {
const { componentCls, fontSize, lineWidth, groupBorderColor, colorErrorHover } = token;
return {
[`${componentCls}-group`]: [
@ -41,7 +42,7 @@ const genGroupStyle: GenerateStyle<ButtonToken> = token => {
},
'&:not(:first-child)': {
marginInlineStart: -lineWidth,
marginInlineStart: token.calc(lineWidth).mul(-1).equal(),
[`&, & > ${componentCls}`]: {
borderStartStartRadius: 0,
@ -71,7 +72,7 @@ const genGroupStyle: GenerateStyle<ButtonToken> = token => {
},
// Border Color
genButtonBorderStyle(`${componentCls}-primary`, colorPrimaryHover),
genButtonBorderStyle(`${componentCls}-primary`, groupBorderColor),
genButtonBorderStyle(`${componentCls}-danger`, colorErrorHover),
],
};

View File

@ -1,51 +1,59 @@
import type { CSSInterpolation, CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import genGroupStyle from './group';
import type { CSSObject } from '../../_util/cssinjs';
import { unit } from '../../_util/cssinjs';
import { genFocusStyle } from '../../style';
import { genCompactItemStyle } from '../../style/compact-item';
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
import type { GenerateStyle } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
import genGroupStyle from './group';
import type { ButtonToken, ComponentToken } from './token';
import { prepareComponentToken, prepareToken } from './token';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {}
export interface ButtonToken extends FullToken<'Button'> {
// FIXME: should be removed
colorOutlineDefault: string;
buttonPaddingHorizontal: number;
}
export type { ComponentToken };
// ============================== Shared ==============================
const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => {
const { componentCls, iconCls } = token;
const { componentCls, iconCls, fontWeight } = token;
return {
[componentCls]: {
outline: 'none',
position: 'relative',
display: 'inline-block',
fontWeight: 400,
fontWeight,
whiteSpace: 'nowrap',
textAlign: 'center',
backgroundImage: 'none',
backgroundColor: 'transparent',
border: `${token.lineWidth}px ${token.lineType} transparent`,
background: 'transparent',
border: `${unit(token.lineWidth)} ${token.lineType} transparent`,
cursor: 'pointer',
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
userSelect: 'none',
touchAction: 'manipulation',
lineHeight: token.lineHeight,
color: token.colorText,
'&:disabled > *': {
pointerEvents: 'none',
},
'> span': {
display: 'inline-block',
},
[`${componentCls}-icon`]: {
lineHeight: 0,
},
// Leave a space between icon and text.
[`> ${iconCls} + span, > span + ${iconCls}`]: {
marginInlineStart: token.marginXS,
},
[`&:not(${componentCls}-icon-only) > ${componentCls}-icon`]: {
[`&${componentCls}-loading-icon, &:not(:last-child)`]: {
marginInlineEnd: token.marginXS,
},
},
'> a': {
color: 'currentColor',
},
@ -54,54 +62,29 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
...genFocusStyle(token),
},
[`&${componentCls}-two-chinese-chars::first-letter`]: {
letterSpacing: '0.34em',
},
[`&${componentCls}-two-chinese-chars > *:not(${iconCls})`]: {
marginInlineEnd: '-0.34em',
letterSpacing: '0.34em',
},
// make `btn-icon-only` not too narrow
[`&-icon-only${componentCls}-compact-item`]: {
flex: 'none',
},
// Special styles for Primary Button
[`&-compact-item${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: -token.lineWidth,
insetInlineStart: -token.lineWidth,
display: 'inline-block',
width: token.lineWidth,
height: `calc(100% + ${token.lineWidth * 2}px)`,
backgroundColor: token.colorPrimaryHover,
content: '""',
},
},
},
// Special styles for Primary Button
'&-compact-vertical-item': {
[`&${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: -token.lineWidth,
insetInlineStart: -token.lineWidth,
display: 'inline-block',
width: `calc(100% + ${token.lineWidth * 2}px)`,
height: token.lineWidth,
backgroundColor: token.colorPrimaryHover,
content: '""',
},
},
},
},
},
};
} as CSSObject;
};
const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject): CSSObject => ({
'&:not(:disabled)': {
const genHoverActiveButtonStyle = (
btnCls: string,
hoverStyle: CSSObject,
activeStyle: CSSObject,
): CSSObject => ({
[`&:not(:disabled):not(${btnCls}-disabled)`]: {
'&:hover': hoverStyle,
'&:active': activeStyle,
},
@ -117,21 +100,22 @@ const genCircleButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
const genRoundButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
borderRadius: token.controlHeight,
paddingInlineStart: token.controlHeight / 2,
paddingInlineEnd: token.controlHeight / 2,
paddingInlineStart: token.calc(token.controlHeight).div(2).equal(),
paddingInlineEnd: token.calc(token.controlHeight).div(2).equal(),
});
// =============================== Type ===============================
const genDisabledStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
cursor: 'not-allowed',
borderColor: token.colorBorder,
borderColor: token.borderColorDisabled,
color: token.colorTextDisabled,
backgroundColor: token.colorBgContainerDisabled,
background: token.colorBgContainerDisabled,
boxShadow: 'none',
});
const genGhostButtonStyle = (
btnCls: string,
background: string,
textColor: string | false,
borderColor: string | false,
textColorDisabled: string | false,
@ -141,17 +125,18 @@ const genGhostButtonStyle = (
): CSSObject => ({
[`&${btnCls}-background-ghost`]: {
color: textColor || undefined,
backgroundColor: 'transparent',
background,
borderColor: borderColor || undefined,
boxShadow: 'none',
...genHoverActiveButtonStyle(
btnCls,
{
backgroundColor: 'transparent',
background,
...hoverStyle,
},
{
backgroundColor: 'transparent',
background,
...activeStyle,
},
),
@ -165,7 +150,7 @@ const genGhostButtonStyle = (
});
const genSolidDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
'&:disabled': {
[`&:disabled, &${token.componentCls}-disabled`]: {
...genDisabledStyle(token),
},
});
@ -175,7 +160,7 @@ const genSolidButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
});
const genPureDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
'&:disabled': {
[`&:disabled, &${token.componentCls}-disabled`]: {
cursor: 'not-allowed',
color: token.colorTextDisabled,
},
@ -185,12 +170,14 @@ const genPureDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token
const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genSolidButtonStyle(token),
backgroundColor: token.colorBgContainer,
borderColor: token.colorBorder,
background: token.defaultBg,
borderColor: token.defaultBorderColor,
color: token.defaultColor,
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
boxShadow: token.defaultShadow,
...genHoverActiveButtonStyle(
token.componentCls,
{
color: token.colorPrimaryHover,
borderColor: token.colorPrimaryHover,
@ -203,8 +190,9 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genGhostButtonStyle(
token.componentCls,
token.colorBgContainer,
token.colorBgContainer,
token.ghostBg,
token.defaultGhostColor,
token.defaultGhostBorderColor,
token.colorTextDisabled,
token.colorBorder,
),
@ -214,6 +202,7 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
borderColor: token.colorError,
...genHoverActiveButtonStyle(
token.componentCls,
{
color: token.colorErrorHover,
borderColor: token.colorErrorBorderHover,
@ -226,6 +215,7 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genGhostButtonStyle(
token.componentCls,
token.ghostBg,
token.colorError,
token.colorError,
token.colorTextDisabled,
@ -239,24 +229,26 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genSolidButtonStyle(token),
color: token.colorTextLightSolid,
backgroundColor: token.colorPrimary,
color: token.primaryColor,
background: token.colorPrimary,
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
boxShadow: token.primaryShadow,
...genHoverActiveButtonStyle(
token.componentCls,
{
color: token.colorTextLightSolid,
backgroundColor: token.colorPrimaryHover,
background: token.colorPrimaryHover,
},
{
color: token.colorTextLightSolid,
backgroundColor: token.colorPrimaryActive,
background: token.colorPrimaryActive,
},
),
...genGhostButtonStyle(
token.componentCls,
token.ghostBg,
token.colorPrimary,
token.colorPrimary,
token.colorTextDisabled,
@ -272,20 +264,23 @@ const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
),
[`&${token.componentCls}-dangerous`]: {
backgroundColor: token.colorError,
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
background: token.colorError,
boxShadow: token.dangerShadow,
color: token.dangerColor,
...genHoverActiveButtonStyle(
token.componentCls,
{
backgroundColor: token.colorErrorHover,
background: token.colorErrorHover,
},
{
backgroundColor: token.colorErrorActive,
background: token.colorErrorActive,
},
),
...genGhostButtonStyle(
token.componentCls,
token.ghostBg,
token.colorError,
token.colorError,
token.colorTextDisabled,
@ -314,8 +309,10 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
color: token.colorLink,
...genHoverActiveButtonStyle(
token.componentCls,
{
color: token.colorLinkHover,
background: token.linkHoverBg,
},
{
color: token.colorLinkActive,
@ -328,6 +325,7 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
color: token.colorError,
...genHoverActiveButtonStyle(
token.componentCls,
{
color: token.colorErrorHover,
},
@ -343,13 +341,14 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
// Type: Text
const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genHoverActiveButtonStyle(
token.componentCls,
{
color: token.colorText,
backgroundColor: token.colorBgTextHover,
background: token.textHoverBg,
},
{
color: token.colorText,
backgroundColor: token.colorBgTextActive,
background: token.colorBgTextActive,
},
),
@ -360,26 +359,19 @@ const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genPureDisabledButtonStyle(token),
...genHoverActiveButtonStyle(
token.componentCls,
{
color: token.colorErrorHover,
backgroundColor: token.colorErrorBg,
background: token.colorErrorBg,
},
{
color: token.colorErrorHover,
backgroundColor: token.colorErrorBg,
background: token.colorErrorBg,
},
),
},
});
// Href and Disabled
const genDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genDisabledStyle(token),
[`&${token.componentCls}:hover`]: {
...genDisabledStyle(token),
},
});
const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => {
const { componentCls } = token;
@ -389,26 +381,30 @@ const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => {
[`${componentCls}-dashed`]: genDashedButtonStyle(token),
[`${componentCls}-link`]: genLinkButtonStyle(token),
[`${componentCls}-text`]: genTextButtonStyle(token),
[`${componentCls}-disabled`]: genDisabledButtonStyle(token),
[`${componentCls}-ghost`]: genGhostButtonStyle(
token.componentCls,
token.ghostBg,
token.colorBgContainer,
token.colorBgContainer,
token.colorTextDisabled,
token.colorBorder,
),
};
};
// =============================== Size ===============================
const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSSInterpolation => {
const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = '') => {
const {
componentCls,
iconCls,
controlHeight,
fontSize,
lineHeight,
lineWidth,
borderRadius,
buttonPaddingHorizontal,
iconCls,
buttonPaddingVertical,
} = token;
const paddingVertical = Math.max(0, (controlHeight - fontSize * lineHeight) / 2 - lineWidth);
const paddingHorizontal = buttonPaddingHorizontal - lineWidth;
const iconOnlyCls = `${componentCls}-icon-only`;
return [
@ -416,8 +412,9 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
{
[`${componentCls}${sizePrefixCls}`]: {
fontSize,
lineHeight,
height: controlHeight,
padding: `${paddingVertical}px ${paddingHorizontal}px`,
padding: `${unit(buttonPaddingVertical!)} ${unit(buttonPaddingHorizontal!)}`,
borderRadius,
[`&${iconOnlyCls}`]: {
@ -427,8 +424,8 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
[`&${componentCls}-round`]: {
width: 'auto',
},
'> span': {
transform: 'scale(1.143)', // 14px -> 16px
[iconCls]: {
fontSize: token.buttonIconOnlyFontSize,
},
},
@ -441,10 +438,6 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
[`${componentCls}-loading-icon`]: {
transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`,
},
[`&:not(${iconOnlyCls}) ${componentCls}-loading-icon > ${iconCls}`]: {
marginInlineEnd: token.marginXS,
},
},
},
@ -458,14 +451,24 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
];
};
const genSizeBaseButtonStyle: GenerateStyle<ButtonToken> = token => genSizeButtonStyle(token);
const genSizeBaseButtonStyle: GenerateStyle<ButtonToken> = token =>
genSizeButtonStyle(
mergeToken<ButtonToken>(token, {
fontSize: token.contentFontSize,
lineHeight: token.contentLineHeight,
}),
);
const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = token => {
const smallToken = mergeToken<ButtonToken>(token, {
controlHeight: token.controlHeightSM,
fontSize: token.contentFontSizeSM,
lineHeight: token.contentLineHeightSM,
padding: token.paddingXS,
buttonPaddingHorizontal: 8, // Fixed padding
buttonPaddingHorizontal: token.paddingInlineSM,
buttonPaddingVertical: token.paddingBlockSM,
borderRadius: token.borderRadiusSM,
buttonIconOnlyFontSize: token.onlyIconSizeSM,
});
return genSizeButtonStyle(smallToken, `${token.componentCls}-sm`);
@ -474,8 +477,12 @@ const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = token => {
const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = token => {
const largeToken = mergeToken<ButtonToken>(token, {
controlHeight: token.controlHeightLG,
fontSize: token.fontSizeLG,
fontSize: token.contentFontSizeLG,
lineHeight: token.contentLineHeightLG,
buttonPaddingHorizontal: token.paddingInlineLG,
buttonPaddingVertical: token.paddingBlockLG,
borderRadius: token.borderRadiusLG,
buttonIconOnlyFontSize: token.onlyIconSizeLG,
});
return genSizeButtonStyle(largeToken, `${token.componentCls}-lg`);
@ -493,33 +500,37 @@ const genBlockButtonStyle: GenerateStyle<ButtonToken> = token => {
};
// ============================== Export ==============================
export default genComponentStyleHook('Button', token => {
const { controlTmpOutline, paddingContentHorizontal } = token;
const buttonToken = mergeToken<ButtonToken>(token, {
colorOutlineDefault: controlTmpOutline,
buttonPaddingHorizontal: paddingContentHorizontal,
});
export default genStyleHooks(
'Button',
token => {
const buttonToken = prepareToken(token);
return [
// Shared
genSharedButtonStyle(buttonToken),
return [
// Shared
genSharedButtonStyle(buttonToken),
// Size
genSizeSmallButtonStyle(buttonToken),
genSizeBaseButtonStyle(buttonToken),
genSizeLargeButtonStyle(buttonToken),
// Size
genSizeSmallButtonStyle(buttonToken),
genSizeBaseButtonStyle(buttonToken),
genSizeLargeButtonStyle(buttonToken),
// Block
genBlockButtonStyle(buttonToken),
// Block
genBlockButtonStyle(buttonToken),
// Group (type, ghost, danger, disabled, loading)
genTypeButtonStyle(buttonToken),
// Group (type, ghost, danger, loading)
genTypeButtonStyle(buttonToken),
// Button Group
genGroupStyle(buttonToken),
// Space Compact
genCompactItemStyle(token, { focus: false }),
genCompactItemVerticalStyle(token),
];
});
// Button Group
genGroupStyle(buttonToken),
];
},
prepareComponentToken,
{
unitless: {
fontWeight: true,
contentLineHeight: true,
contentLineHeightSM: true,
contentLineHeightLG: true,
},
},
);

View File

@ -0,0 +1,234 @@
import type { CSSProperties } from 'vue';
import type { FullToken, GetDefaultToken } from '../../theme/internal';
import { getLineHeight, mergeToken } from '../../theme/internal';
import type { GenStyleFn } from '../../theme/util/genComponentStyleHook';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
/**
* @desc
* @descEN Font weight of text
*/
fontWeight: CSSProperties['fontWeight'];
/**
* @desc
* @descEN Shadow of default button
*/
defaultShadow: string;
/**
* @desc
* @descEN Shadow of primary button
*/
primaryShadow: string;
/**
* @desc
* @descEN Shadow of danger button
*/
dangerShadow: string;
/**
* @desc
* @descEN Text color of primary button
*/
primaryColor: string;
/**
* @desc
* @descEN Text color of default button
*/
defaultColor: string;
/**
* @desc
* @descEN Background color of default button
*/
defaultBg: string;
/**
* @desc
* @descEN Border color of default button
*/
defaultBorderColor: string;
/**
* @desc
* @descEN Text color of danger button
*/
dangerColor: string;
/**
* @desc
* @descEN Border color of disabled button
*/
borderColorDisabled: string;
/**
* @desc
* @descEN Text color of default ghost button
*/
defaultGhostColor: string;
/**
* @desc
* @descEN Background color of ghost button
*/
ghostBg: string;
/**
* @desc
* @descEN Border color of default ghost button
*/
defaultGhostBorderColor: string;
/**
* @desc
* @descEN Horizontal padding of button
*/
paddingInline: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of large button
*/
paddingInlineLG: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of small button
*/
paddingInlineSM: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of button
*/
paddingBlock: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of large button
*/
paddingBlockLG: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of small button
*/
paddingBlockSM: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Icon size of button which only contains icon
*/
onlyIconSize: number;
/**
* @desc
* @descEN Icon size of large button which only contains icon
*/
onlyIconSizeLG: number;
/**
* @desc
* @descEN Icon size of small button which only contains icon
*/
onlyIconSizeSM: number;
/**
* @desc
* @descEN Border color of button group
*/
groupBorderColor: string;
/**
* @desc
* @descEN Background color of link button when hover
*/
linkHoverBg: string;
/**
* @desc
* @descEN Background color of text button when hover
*/
textHoverBg: string;
/**
* @desc
* @descEN Font size of button content
*/
contentFontSize: number;
/**
* @desc
* @descEN Font size of large button content
*/
contentFontSizeLG: number;
/**
* @desc
* @descEN Font size of small button content
*/
contentFontSizeSM: number;
/**
* @desc
* @descEN Line height of button content
*/
contentLineHeight: number;
/**
* @desc
* @descEN Line height of large button content
*/
contentLineHeightLG: number;
/**
* @desc
* @descEN Line height of small button content
*/
contentLineHeightSM: number;
}
export interface ButtonToken extends FullToken<'Button'> {
buttonPaddingHorizontal: CSSProperties['paddingInline'];
buttonPaddingVertical: CSSProperties['paddingBlock'];
buttonIconOnlyFontSize: number;
}
export const prepareToken: (token: Parameters<GenStyleFn<'Button'>>[0]) => ButtonToken = token => {
const { paddingInline, onlyIconSize, paddingBlock } = token;
const buttonToken = mergeToken<ButtonToken>(token, {
buttonPaddingHorizontal: paddingInline,
buttonPaddingVertical: paddingBlock,
buttonIconOnlyFontSize: onlyIconSize,
});
return buttonToken;
};
export const prepareComponentToken: GetDefaultToken<'Button'> = token => {
const contentFontSize = token.contentFontSize ?? token.fontSize;
const contentFontSizeSM = token.contentFontSizeSM ?? token.fontSize;
const contentFontSizeLG = token.contentFontSizeLG ?? token.fontSizeLG;
const contentLineHeight = token.contentLineHeight ?? getLineHeight(contentFontSize);
const contentLineHeightSM = token.contentLineHeightSM ?? getLineHeight(contentFontSizeSM);
const contentLineHeightLG = token.contentLineHeightLG ?? getLineHeight(contentFontSizeLG);
return {
fontWeight: 400,
defaultShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
primaryShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
dangerShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
primaryColor: token.colorTextLightSolid,
dangerColor: token.colorTextLightSolid,
borderColorDisabled: token.colorBorder,
defaultGhostColor: token.colorBgContainer,
ghostBg: 'transparent',
defaultGhostBorderColor: token.colorBgContainer,
paddingInline: token.paddingContentHorizontal - token.lineWidth,
paddingInlineLG: token.paddingContentHorizontal - token.lineWidth,
paddingInlineSM: 8 - token.lineWidth,
onlyIconSize: token.fontSizeLG,
onlyIconSizeSM: token.fontSizeLG - 2,
onlyIconSizeLG: token.fontSizeLG + 2,
groupBorderColor: token.colorPrimaryHover,
linkHoverBg: 'transparent',
textHoverBg: token.colorBgTextHover,
defaultColor: token.colorText,
defaultBg: token.colorBgContainer,
defaultBorderColor: token.colorBorder,
defaultBorderColorDisabled: token.colorBorder,
contentFontSize,
contentFontSizeSM,
contentFontSizeLG,
contentLineHeight,
contentLineHeightSM,
contentLineHeightLG,
paddingBlock: Math.max(
(token.controlHeight - contentFontSize * contentLineHeight) / 2 - token.lineWidth,
0,
),
paddingBlockSM: Math.max(
(token.controlHeightSM - contentFontSizeSM * contentLineHeightSM) / 2 - token.lineWidth,
0,
),
paddingBlockLG: Math.max(
(token.controlHeightLG - contentFontSizeLG * contentLineHeightLG) / 2 - token.lineWidth,
0,
),
};
};

View File

@ -57,6 +57,18 @@ export interface ThemeConfig {
algorithm?: MappingAlgorithm | MappingAlgorithm[];
hashed?: boolean;
inherit?: boolean;
cssVar?:
| {
/**
* Prefix for css variable, default to `antd`.
*/
prefix?: string;
/**
* Unique key for theme, should be set manually < react@18.
*/
key?: string;
}
| boolean;
}
export const configProviderProps = () => ({

View File

@ -0,0 +1,16 @@
import { useToken } from '../../theme/internal';
import type { Ref } from 'vue';
import { computed } from 'vue';
/**
* This hook is only for cssVar to add root className for components.
* If root ClassName is needed, this hook could be refactored with `-root`
* @param prefixCls
*/
const useCSSVarCls = (prefixCls: Ref<string>) => {
const [, , , , cssVar] = useToken();
return computed(() => (cssVar.value ? `${prefixCls.value}-css-var` : ''));
};
export default useCSSVarCls;

View File

@ -0,0 +1,32 @@
import type { SizeType } from '../SizeContext';
import { useInjectSize } from '../SizeContext';
import type { Ref } from 'vue';
import { computed, shallowRef, watch } from 'vue';
const useSize = <T>(customSize?: T | ((ctxSize: SizeType) => T)): Ref<T> => {
const size = useInjectSize();
const mergedSize = shallowRef(null);
watch(
computed(() => {
return [customSize, size.value];
}),
() => {
if (!customSize) {
mergedSize.value = size.value as T;
}
if (typeof customSize === 'string') {
mergedSize.value = customSize ?? (size.value as T);
}
if (customSize instanceof Function) {
mergedSize.value = customSize(size.value) as T;
}
},
{ immediate: true },
);
return mergedSize;
};
export default useSize;

View File

@ -2,13 +2,26 @@ import type { ThemeConfig } from '../context';
import { defaultConfig } from '../../theme/internal';
import type { Ref } from 'vue';
import { computed } from 'vue';
import devWarning from '../../vc-util/warning';
const themeKey = 'antdvtheme';
export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<ThemeConfig>) {
const themeConfig = computed(() => theme?.value || {});
const parentThemeConfig = computed<ThemeConfig>(() =>
themeConfig.value.inherit === false || !parentTheme?.value ? defaultConfig : parentTheme.value,
);
if (process.env.NODE_ENV !== 'production') {
const cssVarEnabled = themeConfig.value.cssVar || parentThemeConfig.value.cssVar;
const validKey = !!(
(typeof themeConfig.value.cssVar === 'object' && themeConfig.value.cssVar?.key) ||
themeKey
);
devWarning(
!cssVarEnabled || validKey,
'[Ant Design Vue ConfigProvider] Missing key in `cssVar` config. Please set `cssVar.key` manually in each ConfigProvider inside `cssVar` enabled ConfigProvider.',
);
}
const mergedTheme = computed(() => {
if (!theme?.value) {
return parentTheme?.value;
@ -26,6 +39,17 @@ export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<The
} as any;
});
const cssVarKey = `css-var-${themeKey.replace(/:/g, '')}`;
const mergedCssVar = (themeConfig.value.cssVar ?? parentThemeConfig.value.cssVar) && {
prefix: 'ant', // Default to ant
...(typeof parentThemeConfig.value.cssVar === 'object' ? parentThemeConfig.value.cssVar : {}),
...(typeof themeConfig.value.cssVar === 'object' ? themeConfig.value.cssVar : {}),
key:
(typeof themeConfig.value.cssVar === 'object' && themeConfig.value.cssVar?.key) ||
cssVarKey,
};
// Base token
return {
...parentThemeConfig.value,
@ -36,6 +60,7 @@ export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<The
...themeConfig.value.token,
},
components: mergedComponents,
cssVar: mergedCssVar,
};
});

View File

@ -0,0 +1,6 @@
let uid = 0;
const useThemeKey = () => {
return 'themekey' + uid++;
};
export default useThemeKey;

View File

@ -15,7 +15,7 @@ import type { ValidateMessages } from '../form/interface';
import useStyle from './style';
import useTheme from './hooks/useTheme';
import defaultSeedToken from '../theme/themes/seed';
import type { ConfigProviderInnerProps, ConfigProviderProps, Theme } from './context';
import type { ConfigProviderInnerProps, ConfigProviderProps, Theme, ThemeConfig } from './context';
import {
useConfigContextProvider,
useConfigContextInject,
@ -26,7 +26,7 @@ import {
import { useProviderSize } from './SizeContext';
import { useProviderDisabled } from './DisabledContext';
import { createTheme } from '../_util/cssinjs';
import { DesignTokenProvider } from '../theme/internal';
import { defaultTheme, DesignTokenProvider } from '../theme/context';
export type {
ConfigProviderProps,
@ -226,19 +226,47 @@ const ConfigProvider = defineComponent({
// ================================ Dynamic theme ================================
const memoTheme = computed(() => {
const { algorithm, token, ...rest } = mergedTheme.value || {};
const { algorithm, token, components, cssVar, ...rest } = mergedTheme.value || {};
const themeObj =
algorithm && (!Array.isArray(algorithm) || algorithm.length > 0)
? createTheme(algorithm)
: undefined;
: defaultTheme;
const parsedComponents: any = {};
Object.entries(components || {}).forEach(([componentName, componentToken]) => {
const parsedToken: typeof componentToken & { theme?: typeof defaultTheme } = {
...componentToken,
};
if ('algorithm' in parsedToken) {
if (parsedToken.algorithm === true) {
parsedToken.theme = themeObj;
} else if (
Array.isArray(parsedToken.algorithm) ||
typeof parsedToken.algorithm === 'function'
) {
parsedToken.theme = createTheme(parsedToken.algorithm as any);
}
delete parsedToken.algorithm;
}
parsedComponents[componentName] = parsedToken;
});
const mergedToken = {
...defaultSeedToken,
...token,
};
return {
...rest,
theme: themeObj,
token: {
...defaultSeedToken,
...token,
token: mergedToken,
components: parsedComponents,
override: {
override: mergedToken,
...parsedComponents,
},
cssVar: cssVar as Exclude<ThemeConfig['cssVar'], boolean>,
};
});
const validateMessagesRef = computed(() => {

View File

@ -1,3 +1,4 @@
import type { CSSObject } from '../../_util/cssinjs';
import { useStyleRegister } from '../../_util/cssinjs';
import { resetIcon } from '../../style';
import { useToken } from '../../theme/internal';
@ -13,16 +14,17 @@ const useStyle = (iconPrefixCls: Ref<string>) => {
hashId: '',
path: ['ant-design-icons', iconPrefixCls.value],
})),
() => [
{
[`.${iconPrefixCls.value}`]: {
...resetIcon(),
[`.${iconPrefixCls.value} .${iconPrefixCls.value}-icon`]: {
display: 'block',
() =>
[
{
[`.${iconPrefixCls.value}`]: {
...resetIcon(),
[`.${iconPrefixCls.value} .${iconPrefixCls.value}-icon`]: {
display: 'block',
},
},
},
},
],
] as CSSObject[],
);
};

View File

@ -22,6 +22,8 @@ import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'
import { resetComponent, roundedArrow, textEllipsis } from '../../style';
import { genCompactItemStyle } from '../../style/compact-item';
export interface ComponentToken {}
export interface ComponentToken {
presetsWidth: number;
presetsMaxWidth: number;

View File

@ -3,6 +3,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent, textEllipsis } from '../../style';
export interface ComponentToken {}
interface DescriptionsToken extends FullToken<'Descriptions'> {
descriptionsTitleMarginBottom: number;
descriptionsExtraColor: string;

View File

@ -60,7 +60,7 @@ const BackTop = defineComponent({
const handleScroll = throttleByAnimationFrame((e: Event | { target: any }) => {
const { visibilityHeight } = props;
const scrollTop = getScroll(e.target, true);
const scrollTop = getScroll(e.target);
state.visible = scrollTop >= visibilityHeight;
});

View File

@ -5,6 +5,8 @@ import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../style';
import genFormValidateMotionStyle from './explain';
export interface ComponentToken {}
export interface FormToken extends FullToken<'Form'> {
formItemCls: string;
rootPrefixCls: string;

View File

@ -2,6 +2,8 @@ import type { CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
export interface ComponentToken {}
interface GridRowToken extends FullToken<'Grid'> {}
interface GridColToken extends FullToken<'Grid'> {

View File

@ -2,7 +2,7 @@ import type { App } from 'vue';
import * as components from './components';
import { default as version } from './version';
import cssinjs from './_util/cssinjs';
import * as cssinjs from './_util/cssinjs';
export * from './components';
export * from './_util/cssinjs';

View File

@ -5,6 +5,8 @@ import type { GlobalToken } from '../../theme/interface';
import { clearFix, resetComponent } from '../../style';
import { genCompactItemStyle } from '../../style/compact-item';
export interface ComponentToken {}
export type InputToken<T extends GlobalToken = FullToken<'Input'>> = T & {
inputAffixPadding: number;
inputPaddingVertical: number;

View File

@ -4,6 +4,8 @@ import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent, textEllipsis } from '../../style';
import { operationUnit } from '../../style';
export interface ComponentToken {}
interface PageHeaderToken extends FullToken<'PageHeader'> {
pageHeaderPadding: number;
pageHeaderPaddingVertical: number;

View File

@ -9,6 +9,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genFocusOutline, genFocusStyle, resetComponent } from '../../style';
export interface ComponentToken {}
interface PaginationToken extends InputToken<FullToken<'Pagination'>> {
paginationItemSize: number;
paginationFontFamily: string;

View File

@ -0,0 +1,101 @@
import { defineComponent, ref, computed, watchEffect } from 'vue';
export interface ProgressProps {
prefixCls: string;
percent: number;
}
const viewSize = 100;
const borderWidth = viewSize / 5;
const radius = viewSize / 2 - borderWidth / 2;
const circumference = radius * 2 * Math.PI;
const position = 50;
const CustomCircle = defineComponent({
compatConfig: { MODE: 3 },
inheritAttrs: false,
props: {
dotClassName: String,
style: Object,
hasCircleCls: Boolean,
},
setup(props) {
const cStyle = computed(() => props.style || {});
return () => (
<circle
class={[
`${props.dotClassName}-circle`,
{
[`${props.dotClassName}-circle-bg`]: props.hasCircleCls,
},
]}
r={radius}
cx={position}
cy={position}
stroke-width={borderWidth}
style={cStyle.value}
/>
);
},
});
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'Progress',
inheritAttrs: false,
props: {
percent: Number,
prefixCls: String,
},
setup(props) {
const dotClassName = `${props.prefixCls}-dot`;
const holderClassName = `${dotClassName}-holder`;
const hideClassName = `${holderClassName}-hidden`;
const render = ref(false);
// ==================== Visible =====================
watchEffect(() => {
if (props.percent !== 0) {
render.value = true;
}
});
// ==================== Progress ====================
const safePtg = computed(() => Math.max(Math.min(props.percent, 100), 0));
const circleStyle = computed(() => ({
strokeDashoffset: `${circumference / 4}`,
strokeDasharray: `${(circumference * safePtg.value) / 100} ${
(circumference * (100 - safePtg.value)) / 100
}`,
}));
// ===================== Render =====================
return () => {
if (!render.value) {
return null;
}
return (
<span
class={[holderClassName, `${dotClassName}-progress`, safePtg.value <= 0 && hideClassName]}
>
<svg
viewBox={`0 0 ${viewSize} ${viewSize}`}
{...({
role: 'progressbar',
'aria-valuemin': 0,
'aria-valuemax': 100,
'aria-valuenow': safePtg.value,
} as any)}
>
<CustomCircle dotClassName={dotClassName} hasCircleCls={true} />
<CustomCircle dotClassName={dotClassName} style={circleStyle.value} />
</svg>
</span>
);
};
},
});

View File

@ -1,11 +1,22 @@
import type { VNode, ExtractPropTypes, PropType } from 'vue';
import { onBeforeUnmount, cloneVNode, isVNode, defineComponent, shallowRef, watch } from 'vue';
import {
onBeforeUnmount,
cloneVNode,
isVNode,
defineComponent,
shallowRef,
watch,
computed,
} from 'vue';
import { debounce } from 'throttle-debounce';
import PropTypes from '../_util/vue-types';
import { filterEmpty, getPropsSlot } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps';
import useStyle from './style';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
import Progress from './Progress';
import usePercent from './usePercent';
export type SpinSize = 'small' | 'default' | 'large';
export const spinProps = () => ({
@ -16,6 +27,8 @@ export const spinProps = () => ({
tip: PropTypes.any,
delay: Number,
indicator: PropTypes.any,
fullscreen: Boolean,
percent: [Number, String] as PropType<number | 'auto'>,
});
export type SpinProps = Partial<ExtractPropTypes<ReturnType<typeof spinProps>>>;
@ -40,11 +53,16 @@ export default defineComponent({
size: 'default',
spinning: true,
wrapperClassName: '',
fullscreen: false,
}),
setup(props, { attrs, slots }) {
const { prefixCls, size, direction } = useConfigInject('spin', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const sSpinning = shallowRef(props.spinning && !shouldDelay(props.spinning, props.delay));
const mergedPercent = computed(() => usePercent(sSpinning.value, props.percent));
let updateSpinning: any;
watch(
[() => props.spinning, () => props.delay],
@ -63,6 +81,7 @@ export default defineComponent({
onBeforeUnmount(() => {
updateSpinning?.cancel();
});
return () => {
const { class: cls, ...divProps } = attrs;
const { tip = slots.tip?.() } = props;
@ -78,8 +97,11 @@ export default defineComponent({
[cls as string]: !!cls,
};
function renderIndicator(prefixCls: string) {
function renderIndicator(prefixCls: string, percent: number) {
const dotClassName = `${prefixCls}-dot`;
const holderClassName = `${dotClassName}-holder`;
const hideClassName = `${holderClassName}-hidden`;
let indicator = getPropsSlot(slots, props, 'indicator');
// should not be render default indicator when indicator value is null
if (indicator === null) {
@ -89,43 +111,87 @@ export default defineComponent({
indicator = indicator.length === 1 ? indicator[0] : indicator;
}
if (isVNode(indicator)) {
return cloneVNode(indicator, { class: dotClassName });
return cloneVNode(indicator, { class: dotClassName, percent });
}
if (defaultIndicator && isVNode(defaultIndicator())) {
return cloneVNode(defaultIndicator(), { class: dotClassName });
return cloneVNode(defaultIndicator(), { class: dotClassName, percent });
}
return (
<span class={`${dotClassName} ${prefixCls}-dot-spin`}>
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
</span>
<>
<span class={[holderClassName, percent > 0 && hideClassName]}>
<span class={[dotClassName, `${prefixCls}-dot-spin`]}>
{[1, 2, 3, 4].map(i => (
<i class={`${prefixCls}-dot-item`} key={i} />
))}
</span>
</span>
{props.percent && <Progress prefixCls={prefixCls} percent={percent} />}
</>
);
}
const spinElement = (
<div {...divProps} class={spinClassName} aria-live="polite" aria-busy={sSpinning.value}>
{renderIndicator(prefixCls.value)}
{tip ? <div class={`${prefixCls.value}-text`}>{tip}</div> : null}
<div
{...divProps}
key="loading"
class={[spinClassName, rootCls.value, cssVarCls.value]}
aria-live="polite"
aria-busy={sSpinning.value}
>
{renderIndicator(prefixCls.value, mergedPercent.value.value)}
{tip ? (
<div class={[`${prefixCls.value}-text`, hashId.value, rootCls.value, cssVarCls.value]}>
{tip}
</div>
) : null}
</div>
);
if (children && filterEmpty(children).length) {
if (children && filterEmpty(children).length && !props.fullscreen) {
const containerClassName = {
[`${prefixCls.value}-container`]: true,
[`${prefixCls.value}-blur`]: sSpinning.value,
[rootCls.value]: true,
[cssVarCls.value]: true,
[hashId.value]: true,
};
return wrapSSR(
<div class={[`${prefixCls.value}-nested-loading`, props.wrapperClassName, hashId.value]}>
{sSpinning.value && <div key="loading">{spinElement}</div>}
return wrapCSSVar(
<div
class={[
`${prefixCls.value}-nested-loading`,
props.wrapperClassName,
hashId.value,
rootCls.value,
cssVarCls.value,
]}
>
{sSpinning.value && spinElement}
<div class={containerClassName} key="container">
{children}
</div>
</div>,
);
}
return wrapSSR(spinElement);
if (props.fullscreen) {
return wrapCSSVar(
<div
class={[
`${prefixCls.value}-fullscreen`,
{
[`${prefixCls.value}-fullscreen-show`]: sSpinning.value,
},
hashId.value,
rootCls.value,
cssVarCls.value,
]}
>
{spinElement}
</div>,
);
}
return wrapCSSVar(spinElement);
};
},
});

View File

@ -17,15 +17,38 @@ Use custom loading indicator.
</docs>
<template>
<a-spin :indicator="indicator" />
<a-flex align="center" gap="middle">
<a-spin :indicator="smallIndicator" />
<a-spin :indicator="indicator" />
<a-spin :indicator="largeIndicator" />
<a-spin :indicator="customIndicator" />
</a-flex>
</template>
<script lang="ts" setup>
import { LoadingOutlined } from '@ant-design/icons-vue';
import { h } from 'vue';
const smallIndicator = h(LoadingOutlined, {
style: {
fontSize: '16px',
},
spin: true,
});
const indicator = h(LoadingOutlined, {
style: {
fontSize: '24px',
},
spin: true,
});
const largeIndicator = h(LoadingOutlined, {
style: {
fontSize: '36px',
},
spin: true,
});
const customIndicator = h(LoadingOutlined, {
style: {
fontSize: '48px',
},
spin: true,
});
</script>

View File

@ -0,0 +1,51 @@
<docs>
---
order: 8
title:
zh-CN: 全屏
en-US: fullscreen
---
## zh-CN
`fullscreen` 属性非常适合创建流畅的页面加载器它添加了半透明覆盖层并在其中心放置了一个旋转加载符号
## en-US
The `fullscreen` mode is perfect for creating page loaders. It adds a dimmed overlay with a centered spinner.
</docs>
<template>
<a-button @click="showLoader">Show fullscreen</a-button>
<a-spin :spinning="spinning" :percent="percent" fullscreen />
</template>
<script setup>
import { ref, onUnmounted } from 'vue';
const spinning = ref(false);
const percent = ref(0);
let interval = null;
const showLoader = () => {
spinning.value = true;
let ptg = -10;
interval = setInterval(() => {
ptg += 5;
percent.value = ptg;
if (ptg > 120) {
if (interval) clearInterval(interval);
spinning.value = false;
percent.value = 0;
}
}, 100);
};
//
onUnmounted(() => {
if (interval) clearInterval(interval);
});
</script>

View File

@ -7,6 +7,8 @@
<tip />
<delay />
<custom-indicator />
<fullscreen />
<percent />
</demo-sort>
</template>
<script lang="ts">
@ -16,6 +18,8 @@ import Inside from './inside.vue';
import Nested from './nested.vue';
import Tip from './tip.vue';
import Delay from './delay.vue';
import Fullscreen from './fullscreen.vue';
import Percent from './percent.vue';
import CustomIndicator from './custom-indicator.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
@ -31,6 +35,8 @@ export default defineComponent({
Tip,
Delay,
CustomIndicator,
Fullscreen,
Percent,
},
setup() {
return {};

View File

@ -0,0 +1,62 @@
<docs>
---
order: 7
title:
zh-CN: 进度
en-US: progress
---
## zh-CN
展示进度当设置 `percent="auto"` 时会预估一个永远不会停止的进度条
## en-US
Show the progress. When `percent="auto"` is set, an indeterminate progress will be displayed.
</docs>
<template>
<div style="display: flex; align-items: center; gap: 16px">
<!-- 开关组件 -->
<a-switch
v-model:checked="auto"
checked-children="Auto"
un-checked-children="Auto"
@change="toggleAuto"
/>
<!-- 加载动画组件 -->
<a-spin :percent="mergedPercent" size="small" />
<a-spin :percent="mergedPercent" />
<a-spin :percent="mergedPercent" size="large" />
</div>
</template>
<script setup>
import { ref, computed, watch, onUnmounted, onMounted, getCurrentInstance } from 'vue';
const auto = ref(false); //
const percent = ref(-50); //
let interval = null;
const mergedPercent = computed(() => (auto.value ? 'auto' : percent.value));
const toggleAuto = checked => {
auto.value = checked;
};
onMounted(() => {
interval = setInterval(() => {
percent.value = percent.value + 5;
if (percent.value > 150) {
percent.value = -50;
}
}, 100);
});
onUnmounted(() => {
if (interval) clearInterval(interval);
});
</script>
<style scoped></style>

View File

@ -1,6 +1,6 @@
<docs>
---
order: 4
order: 4
title:
zh-CN: 自定义描述文案
en-US: Customized description
@ -17,10 +17,23 @@ Customized description content.
</docs>
<template>
<a-spin tip="Loading...">
<a-alert
message="Alert message title"
description="Further details about the context of this alert."
></a-alert>
</a-spin>
<a-flex gap="middle" vertical>
<a-flex gap="middle">
<a-spin tip="Loading" size="small">
<div style="padding: 50px; background: rgba(0, 0, 0, 0.05); border-radius: 4px"></div>
</a-spin>
<a-spin tip="Loading">
<div style="padding: 50px; background: rgba(0, 0, 0, 0.05); border-radius: 4px"></div>
</a-spin>
<a-spin tip="Loading" size="large">
<div style="padding: 50px; background: rgba(0, 0, 0, 0.05); border-radius: 4px"></div>
</a-spin>
</a-flex>
<a-spin tip="Loading...">
<a-alert
message="Alert message title"
description="Further details about the context of this alert."
></a-alert>
</a-spin>
</a-flex>
</template>

View File

@ -17,7 +17,9 @@ When part of the page is waiting for asynchronous data or during a rendering pro
| Property | Description | Type | Default Value | Version |
| --- | --- | --- | --- | --- |
| delay | specifies a delay in milliseconds for loading state (prevent flush) | number (milliseconds) | - | |
| fullscreen | Display a backdrop with the `Spin` component | boolean | false | |
| indicator | vue node of the spinning indicator | vNode \|slot | - | |
| percent | The progress percentage, when set to `auto`, it will be an indeterminate progress | number \| 'auto' | - | |
| size | size of Spin, options: `small`, `default` and `large` | string | `default` | |
| spinning | whether Spin is visible | boolean | true | |
| tip | customize description content when Spin has children | string \| slot | - | slot 3.0 |

View File

@ -18,7 +18,9 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*i43_ToFrL8YAAA
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| delay | 延迟显示加载效果的时间(防止闪烁) | number (毫秒) | - | |
| fullscreen | 显示带有`Spin`组件的背景 | boolean | false | |
| indicator | 加载指示符 | vNode \| slot | - | |
| percent | 展示进度,当设置`percent="auto"`时会预估一个永远不会停止的进度 | number \| 'auto' | - | |
| size | 组件大小,可选值为 `small` `default` `large` | string | `default` | |
| spinning | 是否为加载中状态 | boolean | true | |
| tip | 当作为包裹元素时,可以自定义描述文案 | string \| slot | - | slot 3.0 |

View File

@ -1,18 +1,34 @@
import type { CSSObject } from '../../_util/cssinjs';
import { Keyframes } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../style';
export interface ComponentToken {
contentHeight: number;
/**
* @desc
* @descEN Height of content area
*/
contentHeight: number | string;
/**
* @desc
* @descEN Loading icon size
*/
dotSize: number;
/**
* @desc
* @descEN Small loading icon size
*/
dotSizeSM: number;
/**
* @desc
* @descEN Large loading icon size
*/
dotSizeLG: number;
}
interface SpinToken extends FullToken<'Spin'> {
spinDotDefault: string;
spinDotSize: number;
spinDotSizeSM: number;
spinDotSizeLG: number;
}
const antSpinMove = new Keyframes('antSpinMove', {
@ -23,219 +39,309 @@ const antRotate = new Keyframes('antRotate', {
to: { transform: 'rotate(405deg)' },
});
const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject => ({
[`${token.componentCls}`]: {
...resetComponent(token),
position: 'absolute',
display: 'none',
color: token.colorPrimary,
textAlign: 'center',
verticalAlign: 'middle',
opacity: 0,
transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`,
const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject => {
const { componentCls, calc } = token;
return {
[componentCls]: {
...resetComponent(token),
position: 'absolute',
display: 'none',
color: token.colorPrimary,
fontSize: 0,
textAlign: 'center',
verticalAlign: 'middle',
opacity: 0,
transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`,
'&-spinning': {
position: 'static',
display: 'inline-block',
opacity: 1,
},
'&-spinning': {
position: 'relative',
display: 'inline-block',
opacity: 1,
},
'&-nested-loading': {
position: 'relative',
[`> div > ${token.componentCls}`]: {
position: 'absolute',
top: 0,
insetInlineStart: 0,
zIndex: 4,
display: 'block',
width: '100%',
height: '100%',
maxHeight: token.contentHeight,
[`${componentCls}-text`]: {
fontSize: token.fontSize,
paddingTop: calc(calc(token.dotSize).sub(token.fontSize)).div(2).add(2).equal(),
},
[`${token.componentCls}-dot`]: {
position: 'absolute',
top: '50%',
insetInlineStart: '50%',
margin: -token.spinDotSize / 2,
'&-fullscreen': {
position: 'fixed',
width: '100vw',
height: '100vh',
backgroundColor: token.colorBgMask,
zIndex: token.zIndexPopupBase,
inset: 0,
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'center',
opacity: 0,
visibility: 'hidden',
transition: `all ${token.motionDurationMid}`,
'&-show': {
opacity: 1,
visibility: 'visible',
},
[`${token.componentCls}-text`]: {
position: 'absolute',
top: '50%',
width: '100%',
paddingTop: (token.spinDotSize - token.fontSize) / 2 + 2,
textShadow: `0 1px 2px ${token.colorBgContainer}`, // FIXME: shadow
},
[`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
marginTop: -(token.spinDotSize / 2) - 10,
},
'&-sm': {
[`${token.componentCls}-dot`]: {
margin: -token.spinDotSizeSM / 2,
[componentCls]: {
[`${componentCls}-dot-holder`]: {
color: token.colorWhite,
},
[`${token.componentCls}-text`]: {
paddingTop: (token.spinDotSizeSM - token.fontSize) / 2 + 2,
},
[`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
marginTop: -(token.spinDotSizeSM / 2) - 10,
},
},
'&-lg': {
[`${token.componentCls}-dot`]: {
margin: -(token.spinDotSizeLG / 2),
},
[`${token.componentCls}-text`]: {
paddingTop: (token.spinDotSizeLG - token.fontSize) / 2 + 2,
},
[`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
marginTop: -(token.spinDotSizeLG / 2) - 10,
[`${componentCls}-text`]: {
color: token.colorTextLightSolid,
},
},
},
[`${token.componentCls}-container`]: {
'&-nested-loading': {
position: 'relative',
transition: `opacity ${token.motionDurationSlow}`,
'&::after': {
[`> ${componentCls}`]: {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
zIndex: 10,
zIndex: 4,
display: 'block',
width: '100%',
height: '100%',
background: token.colorBgContainer,
opacity: 0,
transition: `all ${token.motionDurationSlow}`,
content: '""',
maxHeight: token.contentHeight,
[`${componentCls}-dot`]: {
position: 'absolute',
top: '50%',
insetInlineStart: '50%',
margin: calc(token.dotSize).mul(-1).div(2).equal(),
},
[`${componentCls}-text`]: {
position: 'absolute',
top: '50%',
width: '100%',
// paddingTop: (token.spinDotSize - token.fontSize) / 2 + 2,
textShadow: `0 1px 2px ${token.colorBgContainer}`, // FIXME: shadow
},
[`&${componentCls}-show-text ${componentCls}-dot`]: {
marginTop: calc(token.dotSize).div(2).mul(-1).sub(10).equal(),
},
'&-sm': {
[`${componentCls}-dot`]: {
margin: calc(token.dotSizeSM).mul(-1).div(2).equal(),
},
[`${componentCls}-text`]: {
paddingTop: calc(calc(token.dotSizeSM).sub(token.fontSize)).div(2).add(2).equal(),
},
[`&${componentCls}-show-text ${componentCls}-dot`]: {
marginTop: calc(token.dotSizeSM).div(2).mul(-1).sub(10).equal(),
},
},
'&-lg': {
[`${componentCls}-dot`]: {
margin: calc(token.dotSizeLG).mul(-1).div(2).equal(),
},
[`${componentCls}-text`]: {
paddingTop: calc(calc(token.dotSizeLG).sub(token.fontSize)).div(2).add(2).equal(),
},
[`&${componentCls}-show-text ${componentCls}-dot`]: {
marginTop: calc(token.dotSizeLG).div(2).mul(-1).sub(10).equal(),
},
},
},
[`${componentCls}-container`]: {
position: 'relative',
transition: `opacity ${token.motionDurationSlow}`,
'&::after': {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
zIndex: 10,
width: '100%',
height: '100%',
background: token.colorBgContainer,
opacity: 0,
transition: `all ${token.motionDurationSlow}`,
content: '""',
pointerEvents: 'none',
},
},
[`${componentCls}-blur`]: {
clear: 'both',
opacity: 0.5,
userSelect: 'none',
pointerEvents: 'none',
[`&::after`]: {
opacity: 0.4,
pointerEvents: 'auto',
},
},
},
[`${token.componentCls}-blur`]: {
clear: 'both',
opacity: 0.5,
userSelect: 'none',
pointerEvents: 'none',
[`&::after`]: {
opacity: 0.4,
pointerEvents: 'auto',
},
// tip
// ------------------------------
[`&-tip`]: {
color: token.spinDotDefault,
},
},
// tip
// ------------------------------
[`&-tip`]: {
color: token.spinDotDefault,
},
// dots
// ------------------------------
[`${token.componentCls}-dot`]: {
position: 'relative',
display: 'inline-block',
fontSize: token.spinDotSize,
width: '1em',
height: '1em',
'&-item': {
position: 'absolute',
display: 'block',
width: (token.spinDotSize - token.marginXXS / 2) / 2,
height: (token.spinDotSize - token.marginXXS / 2) / 2,
backgroundColor: token.colorPrimary,
borderRadius: '100%',
transform: 'scale(0.75)',
// holder
// ------------------------------
[`${componentCls}-dot-holder`]: {
width: '1em',
height: '1em',
fontSize: token.dotSize,
display: 'inline-block',
transition: `transform ${token.motionDurationSlow} ease, opacity ${token.motionDurationSlow} ease`,
transformOrigin: '50% 50%',
opacity: 0.3,
animationName: antSpinMove,
animationDuration: '1s',
animationIterationCount: 'infinite',
animationTimingFunction: 'linear',
animationDirection: 'alternate',
lineHeight: 1,
color: token.colorPrimary,
'&:nth-child(1)': {
top: 0,
insetInlineStart: 0,
},
'&:nth-child(2)': {
top: 0,
insetInlineEnd: 0,
animationDelay: '0.4s',
},
'&:nth-child(3)': {
insetInlineEnd: 0,
bottom: 0,
animationDelay: '0.8s',
},
'&:nth-child(4)': {
bottom: 0,
insetInlineStart: 0,
animationDelay: '1.2s',
'&-hidden': {
transform: 'scale(0.3)',
opacity: 0,
},
},
'&-spin': {
transform: 'rotate(45deg)',
animationName: antRotate,
animationDuration: '1.2s',
animationIterationCount: 'infinite',
animationTimingFunction: 'linear',
// progress
// ------------------------------
[`${componentCls}-dot-progress`]: {
position: 'absolute',
inset: 0,
},
// dots
// ------------------------------
[`${componentCls}-dot`]: {
position: 'relative',
display: 'inline-block',
fontSize: token.dotSize,
width: '1em',
height: '1em',
'&-item': {
position: 'absolute',
display: 'block',
width: calc(token.dotSize).sub(calc(token.marginXXS).div(2)).div(2).equal(),
height: calc(token.dotSize).sub(calc(token.marginXXS).div(2)).div(2).equal(),
backgroundColor: token.colorPrimary,
borderRadius: '100%',
transform: 'scale(0.75)',
transformOrigin: '50% 50%',
opacity: 0.3,
animationName: antSpinMove,
animationDuration: '1s',
animationIterationCount: 'infinite',
animationTimingFunction: 'linear',
animationDirection: 'alternate',
'&:nth-child(1)': {
top: 0,
insetInlineStart: 0,
},
'&:nth-child(2)': {
top: 0,
insetInlineEnd: 0,
animationDelay: '0.4s',
},
'&:nth-child(3)': {
insetInlineEnd: 0,
bottom: 0,
animationDelay: '0.8s',
},
'&:nth-child(4)': {
bottom: 0,
insetInlineStart: 0,
animationDelay: '1.2s',
},
},
'&-spin': {
transform: 'rotate(45deg)',
animationName: antRotate,
animationDuration: '1.2s',
animationIterationCount: 'infinite',
animationTimingFunction: 'linear',
},
'&-circle': {
strokeLinecap: 'round',
transition: ['stroke-dashoffset', 'stroke-dasharray', 'stroke', 'stroke-width', 'opacity']
.map(item => `${item} ${token.motionDurationSlow} ease`)
.join(','),
fillOpacity: 0,
stroke: 'currentcolor',
},
'&-circle-bg': {
stroke: token.colorFillSecondary,
},
},
// Sizes
// ------------------------------
[`&-sm ${componentCls}-dot`]: {
'&, &-holder': {
fontSize: token.dotSizeSM,
},
},
// small
[`&-sm ${componentCls}-dot-holder`]: {
i: {
width: calc(calc(token.dotSizeSM).sub(calc(token.marginXXS).div(2)))
.div(2)
.equal(),
height: calc(calc(token.dotSizeSM).sub(calc(token.marginXXS).div(2)))
.div(2)
.equal(),
},
},
// large
[`&-lg ${componentCls}-dot`]: {
'&, &-holder': {
fontSize: token.dotSizeLG,
},
},
[`&-lg ${componentCls}-dot-holder`]: {
i: {
width: calc(calc(token.dotSizeLG).sub(token.marginXXS)).div(2).equal(),
height: calc(calc(token.dotSizeLG).sub(token.marginXXS)).div(2).equal(),
},
},
[`&${componentCls}-show-text ${componentCls}-text`]: {
display: 'block',
},
},
};
};
// Sizes
// ------------------------------
// small
[`&-sm ${token.componentCls}-dot`]: {
fontSize: token.spinDotSizeSM,
i: {
width: (token.spinDotSizeSM - token.marginXXS / 2) / 2,
height: (token.spinDotSizeSM - token.marginXXS / 2) / 2,
},
},
// large
[`&-lg ${token.componentCls}-dot`]: {
fontSize: token.spinDotSizeLG,
i: {
width: (token.spinDotSizeLG - token.marginXXS) / 2,
height: (token.spinDotSizeLG - token.marginXXS) / 2,
},
},
[`&${token.componentCls}-show-text ${token.componentCls}-text`]: {
display: 'block',
},
},
});
export const prepareComponentToken: GetDefaultToken<'Spin'> = token => {
const { controlHeightLG, controlHeight } = token;
return {
contentHeight: 400,
dotSize: controlHeightLG / 2,
dotSizeSM: controlHeightLG * 0.35,
dotSizeLG: controlHeight,
};
};
// ============================== Export ==============================
export default genComponentStyleHook(
export default genStyleHooks(
'Spin',
token => {
const spinToken = mergeToken<SpinToken>(token, {
spinDotDefault: token.colorTextDescription,
spinDotSize: token.controlHeightLG / 2,
spinDotSizeSM: token.controlHeightLG * 0.35,
spinDotSizeLG: token.controlHeight,
});
return [genSpinStyle(spinToken)];
},
{
contentHeight: 400,
},
prepareComponentToken,
);

View File

@ -0,0 +1,46 @@
import { ref, computed, watchEffect } from 'vue';
const AUTO_INTERVAL = 200;
const STEP_BUCKETS: [limit: number, stepPtg: number][] = [
[30, 0.05],
[70, 0.03],
[96, 0.01],
];
export default function usePercent(spinning: boolean, percent?: number | 'auto') {
const mockPercent = ref(0);
const mockIntervalRef = ref<ReturnType<typeof setInterval> | null>(null);
const isAuto = ref(percent === 'auto');
watchEffect(() => {
// 清除现有定时器
if (mockIntervalRef.value || !isAuto.value || !spinning) {
clearInterval(mockIntervalRef.value);
mockIntervalRef.value = null;
}
if (isAuto.value && spinning) {
mockPercent.value = 0;
mockIntervalRef.value = setInterval(() => {
mockPercent.value = calculateNextPercent(mockPercent.value);
}, AUTO_INTERVAL);
}
});
return computed(() => (isAuto.value ? mockPercent.value : +percent));
}
function calculateNextPercent(prev: number): number {
const restPTG = 100 - prev;
for (let i = 0; i < STEP_BUCKETS.length; i += 1) {
const [limit, stepPtg] = STEP_BUCKETS[i];
if (prev <= limit) {
return prev + restPTG * stepPtg;
}
}
return prev;
}

View File

@ -3,6 +3,8 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../style';
export interface ComponentToken {}
interface StatisticToken extends FullToken<'Statistic'> {
statisticTitleFontSize: number;
statisticContentFontSize: number;

View File

@ -19,7 +19,8 @@ The most basic usage.
<template>
<a-switch v-model:checked="checked" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const checked = ref<boolean>(false);
const checked = ref<boolean>(true);
</script>

View File

@ -27,6 +27,6 @@ title:
import { reactive } from 'vue';
const state = reactive({
checked1: true,
checked2: false,
checked2: true,
});
</script>

View File

@ -18,7 +18,7 @@ With text and icon.
<template>
<a-space direction="vertical">
<a-switch v-model:checked="state.checked1" checked-children="" un-checked-children="" />
<a-switch v-model:checked="state.checked1" checked-children="" un-checked-children="" />
<a-switch v-model:checked="state.checked2" checked-children="1" un-checked-children="0" />
<a-switch v-model:checked="state.checked3">
<template #checkedChildren><check-outlined /></template>
@ -32,6 +32,6 @@ import { CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
const state = reactive({
checked1: true,
checked2: false,
checked3: false,
checked3: true,
});
</script>

View File

@ -98,7 +98,7 @@ const Switch = defineComponent({
);
const { prefixCls, direction, size } = useConfigInject('switch', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
const refSwitchNode = ref();
const focus = () => {
refSwitchNode.value?.focus();
@ -159,10 +159,11 @@ const Switch = defineComponent({
[prefixCls.value]: true,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
[hashId.value]: true,
[cssVarCls.value]: true,
}));
return () =>
wrapSSR(
wrapCSSVar(
<Wave>
<button
{...omit(props, [

View File

@ -1,107 +1,172 @@
import type { CSSObject } from '../../_util/cssinjs';
import { unit } from '../../_util/cssinjs';
import { TinyColor } from '@ctrl/tinycolor';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genFocusStyle, resetComponent } from '../../style';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
export interface ComponentToken {
/**
* @desc
* @descEN Height of Switch
*/
trackHeight: number | string;
/**
* @desc
* @descEN Height of small Switch
*/
trackHeightSM: number | string;
/**
* @desc
* @descEN Minimum width of Switch
*/
trackMinWidth: number | string;
/**
* @desc
* @descEN Minimum width of small Switch
*/
trackMinWidthSM: number | string;
/**
* @desc
* @descEN Padding of Switch
*/
trackPadding: number;
/**
* @desc
* @descEN Background color of Switch handle
*/
handleBg: string;
/**
* @desc
* @descEN Shadow of Switch handle
*/
handleShadow: string;
/**
* @desc
* @descEN Size of Switch handle
*/
handleSize: number;
/**
* @desc
* @descEN Size of small Switch handle
*/
handleSizeSM: number;
/**
* @desc
* @descEN Minimum margin of content area
*/
innerMinMargin: number;
/**
* @desc
* @descEN Maximum margin of content area
*/
innerMaxMargin: number;
/**
* @desc
* @descEN Minimum margin of content area of small Switch
*/
innerMinMarginSM: number;
/**
* @desc
* @descEN Maximum margin of content area of small Switch
*/
innerMaxMarginSM: number;
}
interface SwitchToken extends FullToken<'Switch'> {
switchMinWidth: number;
switchHeight: number;
switchDuration: string;
switchColor: string;
switchDisabledOpacity: number;
switchInnerMarginMin: number;
switchInnerMarginMax: number;
switchPadding: number;
switchPinSize: number;
switchBg: string;
switchMinWidthSM: number;
switchHeightSM: number;
switchInnerMarginMinSM: number;
switchInnerMarginMaxSM: number;
switchPinSizeSM: number;
switchHandleShadow: string;
switchLoadingIconSize: number;
switchLoadingIconSize: number | string;
switchLoadingIconColor: string;
switchHandleActiveInset: string;
}
const genSwitchSmallStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
const { componentCls } = token;
const {
componentCls,
trackHeightSM,
trackPadding,
trackMinWidthSM,
innerMinMarginSM,
innerMaxMarginSM,
handleSizeSM,
calc,
} = token;
const switchInnerCls = `${componentCls}-inner`;
const trackPaddingCalc = unit(calc(handleSizeSM).add(calc(trackPadding).mul(2)).equal());
const innerMaxMarginCalc = unit(calc(innerMaxMarginSM).mul(2).equal());
return {
[componentCls]: {
[`&${componentCls}-small`]: {
minWidth: token.switchMinWidthSM,
height: token.switchHeightSM,
lineHeight: `${token.switchHeightSM}px`,
minWidth: trackMinWidthSM,
height: trackHeightSM,
lineHeight: unit(trackHeightSM),
[`${componentCls}-inner`]: {
paddingInlineStart: token.switchInnerMarginMaxSM,
paddingInlineEnd: token.switchInnerMarginMinSM,
paddingInlineStart: innerMaxMarginSM,
paddingInlineEnd: innerMinMarginSM,
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
minHeight: trackHeightSM,
},
[`${switchInnerCls}-checked`]: {
marginInlineStart: `calc(-100% + ${
token.switchPinSizeSM + token.switchPadding * 2
}px - ${token.switchInnerMarginMaxSM * 2}px)`,
marginInlineEnd: `calc(100% - ${token.switchPinSizeSM + token.switchPadding * 2}px + ${
token.switchInnerMarginMaxSM * 2
}px)`,
marginInlineStart: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
marginInlineEnd: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
},
[`${switchInnerCls}-unchecked`]: {
marginTop: -token.switchHeightSM,
marginTop: calc(trackHeightSM).mul(-1).equal(),
marginInlineStart: 0,
marginInlineEnd: 0,
},
},
[`${componentCls}-handle`]: {
width: token.switchPinSizeSM,
height: token.switchPinSizeSM,
width: handleSizeSM,
height: handleSizeSM,
},
[`${componentCls}-loading-icon`]: {
top: (token.switchPinSizeSM - token.switchLoadingIconSize) / 2,
top: calc(calc(handleSizeSM).sub(token.switchLoadingIconSize)).div(2).equal(),
fontSize: token.switchLoadingIconSize,
},
[`&${componentCls}-checked`]: {
[`${componentCls}-inner`]: {
paddingInlineStart: token.switchInnerMarginMinSM,
paddingInlineEnd: token.switchInnerMarginMaxSM,
paddingInlineStart: innerMinMarginSM,
paddingInlineEnd: innerMaxMarginSM,
[`${switchInnerCls}-checked`]: {
marginInlineStart: 0,
marginInlineEnd: 0,
},
[`${switchInnerCls}-unchecked`]: {
marginInlineStart: `calc(100% - ${
token.switchPinSizeSM + token.switchPadding * 2
}px + ${token.switchInnerMarginMaxSM * 2}px)`,
marginInlineEnd: `calc(-100% + ${
token.switchPinSizeSM + token.switchPadding * 2
}px - ${token.switchInnerMarginMaxSM * 2}px)`,
marginInlineStart: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
marginInlineEnd: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
},
},
[`${componentCls}-handle`]: {
insetInlineStart: `calc(100% - ${token.switchPinSizeSM + token.switchPadding}px)`,
insetInlineStart: `calc(100% - ${unit(calc(handleSizeSM).add(trackPadding).equal())})`,
},
},
[`&:not(${componentCls}-disabled):active`]: {
[`&:not(${componentCls}-checked) ${switchInnerCls}`]: {
[`${switchInnerCls}-unchecked`]: {
marginInlineStart: token.marginXXS / 2,
marginInlineEnd: -token.marginXXS / 2,
marginInlineStart: calc(token.marginXXS).div(2).equal(),
marginInlineEnd: calc(token.marginXXS).mul(-1).div(2).equal(),
},
},
[`&${componentCls}-checked ${switchInnerCls}`]: {
[`${switchInnerCls}-checked`]: {
marginInlineStart: -token.marginXXS / 2,
marginInlineEnd: token.marginXXS / 2,
marginInlineStart: calc(token.marginXXS).mul(-1).div(2).equal(),
marginInlineEnd: calc(token.marginXXS).div(2).equal(),
},
},
},
@ -111,13 +176,13 @@ const genSwitchSmallStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
};
const genSwitchLoadingStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
const { componentCls } = token;
const { componentCls, handleSize, calc } = token;
return {
[componentCls]: {
[`${componentCls}-loading-icon${token.iconCls}`]: {
position: 'relative',
top: (token.switchPinSize - token.fontSize) / 2,
top: calc(calc(handleSize).sub(token.fontSize)).div(2).equal(),
color: token.switchLoadingIconColor,
verticalAlign: 'top',
},
@ -130,17 +195,17 @@ const genSwitchLoadingStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
};
const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
const { componentCls } = token;
const { componentCls, trackPadding, handleBg, handleShadow, handleSize, calc } = token;
const switchHandleCls = `${componentCls}-handle`;
return {
[componentCls]: {
[switchHandleCls]: {
position: 'absolute',
top: token.switchPadding,
insetInlineStart: token.switchPadding,
width: token.switchPinSize,
height: token.switchPinSize,
top: trackPadding,
insetInlineStart: trackPadding,
width: handleSize,
height: handleSize,
transition: `all ${token.switchDuration} ease-in-out`,
'&::before': {
@ -149,16 +214,16 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
backgroundColor: token.colorWhite,
borderRadius: token.switchPinSize / 2,
boxShadow: token.switchHandleShadow,
backgroundColor: handleBg,
borderRadius: calc(handleSize).div(2).equal(),
boxShadow: handleShadow,
transition: `all ${token.switchDuration} ease-in-out`,
content: '""',
},
},
[`&${componentCls}-checked ${switchHandleCls}`]: {
insetInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding}px)`,
insetInlineStart: `calc(100% - ${unit(calc(handleSize).add(trackPadding).equal())})`,
},
[`&:not(${componentCls}-disabled):active`]: {
@ -177,9 +242,20 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
};
const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
const { componentCls } = token;
const {
componentCls,
trackHeight,
trackPadding,
innerMinMargin,
innerMaxMargin,
handleSize,
calc,
} = token;
const switchInnerCls = `${componentCls}-inner`;
const trackPaddingCalc = unit(calc(handleSize).add(calc(trackPadding).mul(2)).equal());
const innerMaxMarginCalc = unit(calc(innerMaxMargin).mul(2).equal());
return {
[componentCls]: {
[switchInnerCls]: {
@ -187,8 +263,8 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
overflow: 'hidden',
borderRadius: 100,
height: '100%',
paddingInlineStart: token.switchInnerMarginMax,
paddingInlineEnd: token.switchInnerMarginMin,
paddingInlineStart: innerMaxMargin,
paddingInlineEnd: innerMinMargin,
transition: `padding-inline-start ${token.switchDuration} ease-in-out, padding-inline-end ${token.switchDuration} ease-in-out`,
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
@ -197,54 +273,47 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
fontSize: token.fontSizeSM,
transition: `margin-inline-start ${token.switchDuration} ease-in-out, margin-inline-end ${token.switchDuration} ease-in-out`,
pointerEvents: 'none',
minHeight: trackHeight,
},
[`${switchInnerCls}-checked`]: {
marginInlineStart: `calc(-100% + ${token.switchPinSize + token.switchPadding * 2}px - ${
token.switchInnerMarginMax * 2
}px)`,
marginInlineEnd: `calc(100% - ${token.switchPinSize + token.switchPadding * 2}px + ${
token.switchInnerMarginMax * 2
}px)`,
marginInlineStart: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
marginInlineEnd: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
},
[`${switchInnerCls}-unchecked`]: {
marginTop: -token.switchHeight,
marginTop: calc(trackHeight).mul(-1).equal(),
marginInlineStart: 0,
marginInlineEnd: 0,
},
},
[`&${componentCls}-checked ${switchInnerCls}`]: {
paddingInlineStart: token.switchInnerMarginMin,
paddingInlineEnd: token.switchInnerMarginMax,
paddingInlineStart: innerMinMargin,
paddingInlineEnd: innerMaxMargin,
[`${switchInnerCls}-checked`]: {
marginInlineStart: 0,
marginInlineEnd: 0,
},
[`${switchInnerCls}-unchecked`]: {
marginInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding * 2}px + ${
token.switchInnerMarginMax * 2
}px)`,
marginInlineEnd: `calc(-100% + ${token.switchPinSize + token.switchPadding * 2}px - ${
token.switchInnerMarginMax * 2
}px)`,
marginInlineStart: `calc(100% - ${trackPaddingCalc} + ${innerMaxMarginCalc})`,
marginInlineEnd: `calc(-100% + ${trackPaddingCalc} - ${innerMaxMarginCalc})`,
},
},
[`&:not(${componentCls}-disabled):active`]: {
[`&:not(${componentCls}-checked) ${switchInnerCls}`]: {
[`${switchInnerCls}-unchecked`]: {
marginInlineStart: token.switchPadding * 2,
marginInlineEnd: -token.switchPadding * 2,
marginInlineStart: calc(trackPadding).mul(2).equal(),
marginInlineEnd: calc(trackPadding).mul(-1).mul(2).equal(),
},
},
[`&${componentCls}-checked ${switchInnerCls}`]: {
[`${switchInnerCls}-checked`]: {
marginInlineStart: -token.switchPadding * 2,
marginInlineEnd: token.switchPadding * 2,
marginInlineStart: calc(trackPadding).mul(-1).mul(2).equal(),
marginInlineEnd: calc(trackPadding).mul(2).equal(),
},
},
},
@ -253,7 +322,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = token => {
};
const genSwitchStyle = (token: SwitchToken): CSSObject => {
const { componentCls } = token;
const { componentCls, trackHeight, trackMinWidth } = token;
return {
[componentCls]: {
@ -262,9 +331,9 @@ const genSwitchStyle = (token: SwitchToken): CSSObject => {
position: 'relative',
display: 'inline-block',
boxSizing: 'border-box',
minWidth: token.switchMinWidth,
height: token.switchHeight,
lineHeight: `${token.switchHeight}px`,
minWidth: trackMinWidth,
height: trackHeight,
lineHeight: unit(trackHeight),
verticalAlign: 'middle',
background: token.colorTextQuaternary,
border: '0',
@ -306,48 +375,59 @@ const genSwitchStyle = (token: SwitchToken): CSSObject => {
};
// ============================== Export ==============================
export default genComponentStyleHook('Switch', token => {
const switchHeight = token.fontSize * token.lineHeight;
const switchHeightSM = token.controlHeight / 2;
const switchPadding = 2; // This is magic
const switchPinSize = switchHeight - switchPadding * 2;
const switchPinSizeSM = switchHeightSM - switchPadding * 2;
export const prepareComponentToken: GetDefaultToken<'Switch'> = token => {
const { fontSize, lineHeight, controlHeight, colorWhite } = token;
const switchToken = mergeToken<SwitchToken>(token, {
switchMinWidth: switchPinSize * 2 + switchPadding * 4,
switchHeight,
switchDuration: token.motionDurationMid,
switchColor: token.colorPrimary,
switchDisabledOpacity: token.opacityLoading,
switchInnerMarginMin: switchPinSize / 2,
switchInnerMarginMax: switchPinSize + switchPadding + switchPadding * 2,
switchPadding,
switchPinSize,
switchBg: token.colorBgContainer,
switchMinWidthSM: switchPinSizeSM * 2 + switchPadding * 2,
switchHeightSM,
switchInnerMarginMinSM: switchPinSizeSM / 2,
switchInnerMarginMaxSM: switchPinSizeSM + switchPadding + switchPadding * 2,
switchPinSizeSM,
switchHandleShadow: `0 2px 4px 0 ${new TinyColor('#00230b').setAlpha(0.2).toRgbString()}`,
switchLoadingIconSize: token.fontSizeIcon * 0.75,
switchLoadingIconColor: `rgba(0, 0, 0, ${token.opacityLoading})`,
switchHandleActiveInset: '-30%',
});
const height = fontSize * lineHeight;
const heightSM = controlHeight / 2;
const padding = 2; // Fixed value
const handleSize = height - padding * 2;
const handleSizeSM = heightSM - padding * 2;
return [
genSwitchStyle(switchToken),
return {
trackHeight: height,
trackHeightSM: heightSM,
trackMinWidth: handleSize * 2 + padding * 4,
trackMinWidthSM: handleSizeSM * 2 + padding * 2,
trackPadding: padding, // Fixed value
handleBg: colorWhite,
handleSize,
handleSizeSM,
handleShadow: `0 2px 4px 0 ${new TinyColor('#00230b').setAlpha(0.2).toRgbString()}`,
innerMinMargin: handleSize / 2,
innerMaxMargin: handleSize + padding + padding * 2,
innerMinMarginSM: handleSizeSM / 2,
innerMaxMarginSM: handleSizeSM + padding + padding * 2,
};
};
// inner style
genSwitchInnerStyle(switchToken),
export default genStyleHooks(
'Switch',
token => {
const switchToken = mergeToken<SwitchToken>(token, {
switchDuration: token.motionDurationMid,
switchColor: token.colorPrimary,
switchDisabledOpacity: token.opacityLoading,
switchLoadingIconSize: token.calc(token.fontSizeIcon).mul(0.75).equal(),
switchLoadingIconColor: `rgba(0, 0, 0, ${token.opacityLoading})`,
switchHandleActiveInset: '-30%',
});
// handle style
genSwitchHandleStyle(switchToken),
return [
genSwitchStyle(switchToken),
// loading style
genSwitchLoadingStyle(switchToken),
// inner style
genSwitchInnerStyle(switchToken),
// small style
genSwitchSmallStyle(switchToken),
];
});
// handle style
genSwitchHandleStyle(switchToken),
// loading style
genSwitchLoadingStyle(switchToken),
// small style
genSwitchSmallStyle(switchToken),
];
},
prepareComponentToken,
);

View File

@ -1,9 +0,0 @@
import getAlphaColor from '../util/getAlphaColor';
describe('util', () => {
describe('getAlphaColor', () => {
it('should not process color with alpha', () => {
expect(getAlphaColor('rgba(0, 0, 0, 0.5)', 'rgba(255, 255, 255)')).toBe('rgba(0, 0, 0, 0.5)');
});
});
});

View File

@ -0,0 +1,97 @@
import type { ShallowRef, InjectionKey, ExtractPropTypes, ComputedRef } from 'vue';
import {
defineComponent,
inject,
provide,
shallowRef,
unref,
triggerRef,
watch,
computed,
} from 'vue';
import type { Theme } from '../_util/cssinjs';
import { createTheme } from '../_util/cssinjs';
import { objectType, someType } from '../_util/type';
import type { AliasToken, MapToken, OverrideToken, SeedToken } from './interface';
import defaultDerivative from './themes/default';
import defaultSeedToken from './themes/seed';
export const defaultTheme = createTheme(defaultDerivative);
// ================================ Context =================================
// To ensure snapshot stable. We disable hashed in test env.
const DesignTokenContextKey: InjectionKey<ShallowRef<Partial<DesignTokenProviderProps>>> =
Symbol('DesignTokenContextKey');
export const globalDesignTokenApi = shallowRef<DesignTokenProviderProps>();
export const defaultConfig = {
token: defaultSeedToken,
override: { override: defaultSeedToken },
hashed: true,
};
export type ComponentsToken = {
[key in keyof OverrideToken]?: OverrideToken[key] & {
theme?: Theme<SeedToken, MapToken>;
};
};
export const styleProviderProps = () => ({
token: objectType<AliasToken>(),
theme: objectType<Theme<SeedToken, MapToken>>(),
components: objectType<ComponentsToken>(),
/** Just merge `token` & `override` at top to save perf */
override: objectType<{ override: Partial<AliasToken> } & ComponentsToken>(),
hashed: someType<string | boolean>(),
cssVar: someType<{
prefix?: string;
key?: string;
}>(),
});
export type StyleProviderProps = Partial<ExtractPropTypes<ReturnType<typeof styleProviderProps>>>;
export interface DesignTokenProviderProps {
token: Partial<AliasToken>;
theme?: Theme<SeedToken, MapToken>;
components?: ComponentsToken;
/** Just merge `token` & `override` at top to save perf */
override?: { override: Partial<AliasToken> } & ComponentsToken;
hashed?: string | boolean;
cssVar?: {
prefix?: string;
key?: string;
};
}
export const useDesignTokenProvider = (value: ComputedRef<DesignTokenProviderProps>) => {
provide(DesignTokenContextKey, value);
watch(
value,
() => {
globalDesignTokenApi.value = unref(value);
triggerRef(globalDesignTokenApi);
},
{ immediate: true, deep: true },
);
};
export const useDesignTokenInject = () => {
return inject(
DesignTokenContextKey,
computed(() => globalDesignTokenApi.value || defaultConfig),
);
};
export const DesignTokenProvider = defineComponent({
props: {
value: objectType<DesignTokenProviderProps>(),
},
setup(props, { slots }) {
useDesignTokenProvider(computed(() => props.value));
return () => {
return slots.default?.();
};
},
});
export default { useDesignTokenInject, useDesignTokenProvider, DesignTokenProvider };

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
import { createTheme, getComputedToken } from '../_util/cssinjs';
import type { ThemeConfig } from '../config-provider/context';
import type { AliasToken } from './interface';
import defaultDerivative from './themes/default';
import seedToken from './themes/seed';
import formatToken from './util/alias';
const getDesignToken = (config?: ThemeConfig): AliasToken => {
const theme = config?.algorithm ? createTheme(config.algorithm) : createTheme(defaultDerivative);
const mergedToken = {
...seedToken,
...config?.token,
};
return getComputedToken(mergedToken, { override: config?.token }, theme, formatToken);
};
export default getDesignToken;

View File

@ -1,9 +1,10 @@
/* eslint-disable import/prefer-default-export */
import getDesignToken from './getDesignToken';
import type { GlobalToken, MappingAlgorithm } from './interface';
import { defaultConfig, useToken as useInternalToken } from './internal';
import type { GlobalToken } from './interface';
import defaultAlgorithm from './themes/default';
import darkAlgorithm from './themes/dark';
import compactAlgorithm from './themes/compact';
import darkAlgorithm from './themes/dark';
import defaultAlgorithm from './themes/default';
// ZombieJ: We export as object to user but array in internal.
// This is used to minimize the bundle size for antd package but safe to refactor as object also.
@ -15,7 +16,7 @@ function useToken() {
return { theme, token, hashId };
}
export { type GlobalToken };
export type { GlobalToken, MappingAlgorithm };
export default {
/** @private Test Usage. Do not use in production. */
@ -28,4 +29,5 @@ export default {
defaultAlgorithm,
darkAlgorithm,
compactAlgorithm,
getDesignToken,
};

View File

@ -8,124 +8,604 @@ import type { MapToken } from './maps';
export interface AliasToken extends MapToken {
// Background
/**
* @nameZH
* @nameEN Background color of content area (hover)
* @desc
* @descEN Control the style of background color of content area when mouse hovers over it.
*/
colorFillContentHover: string;
/**
* @nameZH
* @nameEN Alternative background color
* @desc
* @descEN Control the alternative background color of element.
*/
colorFillAlter: string;
/**
* @nameZH
* @nameEN Background color of content area
* @desc
* @descEN Control the background color of content area.
*/
colorFillContent: string;
/**
* @nameZH
* @nameEN Disabled container background color
* @desc
* @descEN Control the background color of container in disabled state.
*/
colorBgContainerDisabled: string;
/**
* @nameZH
* @nameEN Text hover background color
* @desc
* @descEN Control the background color of text in hover state.
*/
colorBgTextHover: string;
/**
* @nameZH
* @nameEN Text active background color
* @desc
* @descEN Control the background color of text in active state.
*/
colorBgTextActive: string;
// Border
/**
* @nameZH
* @nameEN Background border color
* @desc
* @descEN Control the color of background border of element.
*/
colorBorderBg: string;
/**
* @nameZH 线
* @nameEN Separator Color
* @desc 线 colorBorderSecondary
* @descEN Used as the color of separator, this color is the same as colorBorderSecondary but with transparency.
*/
colorSplit: string;
// Text
/**
* @nameZH
* @nameEN Placeholder Text Color
* @desc
* @descEN Control the color of placeholder text.
*/
colorTextPlaceholder: string;
/**
* @nameZH
* @nameEN Disabled Text Color
* @desc
* @descEN Control the color of text in disabled state.
*/
colorTextDisabled: string;
/**
* @nameZH
* @nameEN Heading Text Color
* @desc
* @descEN Control the font color of heading.
*/
colorTextHeading: string;
/**
* @nameZH
* @nameEN Text label font color
* @desc
* @descEN Control the font color of text label.
*/
colorTextLabel: string;
/**
* @nameZH
* @nameEN Text description font color
* @desc
* @descEN Control the font color of text description.
*/
colorTextDescription: string;
/**
* @nameZH
* @nameEN Fixed text highlight color
* @desc Primary Button
* @descEN Control the highlight color of text with background color, such as the text in Primary Button components.
*/
colorTextLightSolid: string;
/** Weak action. Such as `allowClear` or Alert close button */
/**
/**
* @nameZH
* @nameEN Weak action icon color
* @desc allowClear Alert
* @descEN Weak action. Such as `allowClear` or Alert close button
*/
colorIcon: string;
/** Weak action hover color. Such as `allowClear` or Alert close button */
/** */
/**
* @nameZH
* @nameEN Weak action icon hover color
* @desc allowClear Alert
* @descEN Weak action hover color. Such as `allowClear` or Alert close button
*/
colorIconHover: string;
colorLink: string;
colorLinkHover: string;
colorLinkActive: string;
/**
* @nameZH
* @nameEN Highlight color
* @desc
* @descEN Control the color of page element when highlighted.
*/
colorHighlight: string;
/**
* @nameZH Outline
* @nameEN Input component outline color
* @desc 线
* @descEN Control the outline color of input component.
*/
controlOutline: string;
/**
* @nameZH Outline
* @nameEN Warning outline color
* @desc 线
* @descEN Control the outline color of input component in warning state.
*/
colorWarningOutline: string;
/**
* @nameZH Outline
* @nameEN Error outline color
* @desc 线
* @descEN Control the outline color of input component in error state.
*/
colorErrorOutline: string;
// Font
/** Operation icon in Select, Cascader, etc. icon fontSize. Normal is same as fontSizeSM */
/**
* @nameZH
* @nameEN Operation icon font size in Select, Cascader, etc.
* @desc fontSizeSM
* @descEN Control the font size of operation icon in Select, Cascader, etc. Normally same as fontSizeSM.
*/
fontSizeIcon: number;
/** For heading like h1, h2, h3 or option selected item */
/**
* @nameZH h1h2h3
* @nameEN Font weight for heading components (such as h1, h2, h3) or selected item
* @desc h1h2h3
* @descEN Control the font weight of heading components (such as h1, h2, h3) or selected item.
*/
fontWeightStrong: number;
// Control
/**
* @nameZH 线
* @nameEN Input component outline width
* @desc 线
* @descEN Control the outline width of input component.
*/
controlOutlineWidth: number;
/**
* @nameZH
* @nameEN Background color of control component item when hovering
* @desc
* @descEN Control the background color of control component item when hovering.
*/
controlItemBgHover: string; // Note. It also is a color
/**
* @nameZH
* @nameEN Background color of control component item when active
* @desc
* @descEN Control the background color of control component item when active.
*/
controlItemBgActive: string; // Note. It also is a color
/**
* @nameZH
* @nameEN Background color of control component item when hovering and active
* @desc
* @descEN Control the background color of control component item when hovering and active.
*/
controlItemBgActiveHover: string; // Note. It also is a color
/**
* @nameZH
* @nameEN Interactive size of control component
* @desc
* @descEN Control the interactive size of control component.
*/
controlInteractiveSize: number;
/**
* @nameZH
* @nameEN Background color of control component item when active and disabled
* @desc
* @descEN Control the background color of control component item when active and disabled.
*/
controlItemBgActiveDisabled: string; // Note. It also is a color
// Line
/**
* @nameZH 线()
* @nameEN Line width(focus state)
* @desc 线
* @descEN Control the width of the line when the component is in focus state.
*/
lineWidthFocus: number;
// Padding
/**
* @nameZH
* @nameEN Extra extra small padding
* @desc
* @descEN Control the extra extra small padding of the element.
*/
paddingXXS: number;
/**
* @nameZH
* @nameEN Extra small padding
* @desc
* @descEN Control the extra small padding of the element.
*/
paddingXS: number;
/**
* @nameZH
* @nameEN Small padding
* @desc
* @descEN Control the small padding of the element.
*/
paddingSM: number;
/**
* @nameZH
* @nameEN Padding
* @desc
* @descEN Control the padding of the element.
*/
padding: number;
/**
* @nameZH
* @nameEN Medium padding
* @desc
* @descEN Control the medium padding of the element.
*/
paddingMD: number;
/**
* @nameZH
* @nameEN Large padding
* @desc
* @descEN Control the large padding of the element.
*/
paddingLG: number;
/**
* @nameZH
* @nameEN Extra large padding
* @desc
* @descEN Control the extra large padding of the element.
*/
paddingXL: number;
// Padding Content
/**
* @nameZH LG
* @nameEN Content horizontal padding (LG)
* @desc
* @descEN Control the horizontal padding of content element, suitable for large screen devices.
*/
paddingContentHorizontalLG: number;
/**
* @nameZH
* @nameEN Content horizontal padding
* @desc
* @descEN Control the horizontal padding of content element.
*/
paddingContentHorizontal: number;
/**
* @nameZH SM
* @nameEN Content horizontal padding (SM)
* @desc
* @descEN Control the horizontal padding of content element, suitable for small screen devices.
*/
paddingContentHorizontalSM: number;
/**
* @nameZH LG
* @nameEN Content vertical padding (LG)
* @desc
* @descEN Control the vertical padding of content element, suitable for large screen devices.
*/
paddingContentVerticalLG: number;
/**
* @nameZH
* @nameEN Content vertical padding
* @desc
* @descEN Control the vertical padding of content element.
*/
paddingContentVertical: number;
/**
* @nameZH SM
* @nameEN Content vertical padding (SM)
* @desc
* @descEN Control the vertical padding of content element, suitable for small screen devices.
*/
paddingContentVerticalSM: number;
// Margin
/**
* @nameZH XXS
* @nameEN Margin XXS
* @desc
* @descEN Control the margin of an element, with the smallest size.
*/
marginXXS: number;
/**
* @nameZH XS
* @nameEN Margin XS
* @desc
* @descEN Control the margin of an element, with a small size.
*/
marginXS: number;
/**
* @nameZH SM
* @nameEN Margin SM
* @desc
* @descEN Control the margin of an element, with a medium-small size.
*/
marginSM: number;
/**
* @nameZH
* @nameEN Margin
* @desc
* @descEN Control the margin of an element, with a medium size.
*/
margin: number;
/**
* @nameZH MD
* @nameEN Margin MD
* @desc
* @descEN Control the margin of an element, with a medium-large size.
*/
marginMD: number;
/**
* @nameZH LG
* @nameEN Margin LG
* @desc
* @descEN Control the margin of an element, with a large size.
*/
marginLG: number;
/**
* @nameZH XL
* @nameEN Margin XL
* @desc
* @descEN Control the margin of an element, with an extra-large size.
*/
marginXL: number;
/**
* @nameZH XXL
* @nameEN Margin XXL
* @desc
* @descEN Control the margin of an element, with the largest size.
*/
marginXXL: number;
// =============== Legacy: should be remove ===============
/**
* @nameZH
* @nameEN Loading opacity
* @desc
* @descEN Control the opacity of the loading state.
*/
opacityLoading: number;
/**
* @nameZH
* @nameEN Box shadow
* @desc
* @descEN Control the box shadow style of an element.
*/
boxShadow: string;
/**
* @nameZH
* @nameEN Secondary box shadow
* @desc
* @descEN Control the secondary box shadow style of an element.
*/
boxShadowSecondary: string;
/**
* @nameZH
* @nameEN Tertiary box shadow
* @desc
* @descEN Control the tertiary box shadow style of an element.
*/
boxShadowTertiary: string;
/**
* @nameZH
* @nameEN Link text decoration
* @desc
* @descEN Control the text decoration style of a link.
*/
linkDecoration: CSSProperties['textDecoration'];
/**
* @nameZH
* @nameEN Link text decoration on mouse hover
* @desc
* @descEN Control the text decoration style of a link on mouse hover.
*/
linkHoverDecoration: CSSProperties['textDecoration'];
/**
* @nameZH
* @nameEN Link text decoration on focus
* @desc
* @descEN Control the text decoration style of a link on focus.
*/
linkFocusDecoration: CSSProperties['textDecoration'];
/**
* @nameZH
* @nameEN Control horizontal padding
* @desc
* @descEN Control the horizontal padding of an element.
*/
controlPaddingHorizontal: number;
/**
* @nameZH
* @nameEN Control horizontal padding with a small-medium size
* @desc
* @descEN Control the horizontal padding of an element with a small-medium size.
*/
controlPaddingHorizontalSM: number;
// Media queries breakpoints
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra small screens
* @desc
* @descEN Control the screen width of extra small screens.
*/
screenXS: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra small screens minimum value
* @desc
* @descEN Control the minimum width of extra small screens.
*/
screenXSMin: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra small screens maximum value
* @desc
* @descEN Control the maximum width of extra small screens.
*/
screenXSMax: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Small screens
* @desc
* @descEN Control the screen width of small screens.
*/
screenSM: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Small screens minimum value
* @desc
* @descEN Control the minimum width of small screens.
*/
screenSMMin: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Small screens maximum value
* @desc
* @descEN Control the maximum width of small screens.
*/
screenSMMax: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Medium screens
* @desc
* @descEN Control the screen width of medium screens.
*/
screenMD: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Medium screens minimum value
* @desc
* @descEN Control the minimum width of medium screens.
*/
screenMDMin: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Medium screens maximum value
* @desc
* @descEN Control the maximum width of medium screens.
*/
screenMDMax: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Large screens
* @desc
* @descEN Control the screen width of large screens.
*/
screenLG: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Large screens minimum value
* @desc
* @descEN Control the minimum width of large screens.
*/
screenLGMin: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Large screens maximum value
* @desc
* @descEN Control the maximum width of large screens.
*/
screenLGMax: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra large screens
* @desc
* @descEN Control the screen width of extra large screens.
*/
screenXL: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra large screens minimum value
* @desc
* @descEN Control the minimum width of extra large screens.
*/
screenXLMin: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra large screens maximum value
* @desc
* @descEN Control the maximum width of extra large screens.
*/
screenXLMax: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra extra large screens.
* @desc
* @descEN Control the screen width of extra extra large screens.
*/
screenXXL: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra extra large screens minimum value
* @desc
* @descEN Control the minimum width of extra extra large screens.
*/
screenXXLMin: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra extra large screens maximum value
* @desc
* @descEN Control the maximum width of extra extra large screens.
*/
screenXXLMax: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra extra extra large screens.
* @desc
* @descEN Control the screen width of extra extra extra large screens.
*/
screenXXXL: number;
/**
* @nameZH -
* @nameEN Screen width (pixels) - Extra extra extra large screens minimum value
* @desc
* @descEN Control the minimum width of extra extra extra large screens.
*/
screenXXXLMin: number;
/** Used for DefaultButton, Switch which has default outline */
/**
* @deprecated
* Used for DefaultButton, Switch which has default outline
* @desc Outline
* @descEN Default style outline color.
*/
controlTmpOutline: string;
// FIXME: component box-shadow, should be removed

View File

@ -1,21 +1,33 @@
import type { ComponentToken as WaveToken } from '../../_util/wave/style';
import type { ComponentToken as AffixComponentToken } from '../../affix/style';
import type { ComponentToken as AlertComponentToken } from '../../alert/style';
import type { ComponentToken as AnchorComponentToken } from '../../anchor/style';
import type { ComponentToken as AppComponentToken } from '../../app/style';
import type { ComponentToken as AvatarComponentToken } from '../../avatar/style';
import type { ComponentToken as BadgeComponentToken } from '../../badge/style';
import type { ComponentToken as BreadcrumbComponentToken } from '../../breadcrumb/style';
import type { ComponentToken as ButtonComponentToken } from '../../button/style';
import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style';
import type { ComponentToken as CalendarComponentToken } from '../../calendar/style';
import type { ComponentToken as CardComponentToken } from '../../card/style';
import type { ComponentToken as CarouselComponentToken } from '../../carousel/style';
import type { ComponentToken as CascaderComponentToken } from '../../cascader/style';
import type { ComponentToken as CheckboxComponentToken } from '../../checkbox/style';
import type { ComponentToken as CollapseComponentToken } from '../../collapse/style';
// import type { ComponentToken as ColorPickerComponentToken } from '../../color-picker/style';
import type { ComponentToken as CommentComponentToken } from '../../comment/style';
import type { ComponentToken as DatePickerComponentToken } from '../../date-picker/style';
import type { ComponentToken as DescriptionsComponentToken } from '../../descriptions/style';
import type { ComponentToken as DividerComponentToken } from '../../divider/style';
import type { ComponentToken as DropdownComponentToken } from '../../dropdown/style';
import type { ComponentToken as DrawerComponentToken } from '../../drawer/style';
import type { ComponentToken as DropdownComponentToken } from '../../dropdown/style';
import type { ComponentToken as EmptyComponentToken } from '../../empty/style';
import type { ComponentToken as FlexComponentToken } from '../../flex/style';
import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style';
import type { ComponentToken as FormComponentToken } from '../../form/style';
import type { ComponentToken as GridComponentToken } from '../../grid/style';
import type { ComponentToken as ImageComponentToken } from '../../image/style';
import type { ComponentToken as InputNumberComponentToken } from '../../input-number/style';
import type { ComponentToken as InputComponentToken } from '../../input/style';
import type { ComponentToken as LayoutComponentToken } from '../../layout/style';
import type { ComponentToken as ListComponentToken } from '../../list/style';
import type { ComponentToken as MentionsComponentToken } from '../../mentions/style';
@ -23,9 +35,12 @@ import type { ComponentToken as MenuComponentToken } from '../../menu/style';
import type { ComponentToken as MessageComponentToken } from '../../message/style';
import type { ComponentToken as ModalComponentToken } from '../../modal/style';
import type { ComponentToken as NotificationComponentToken } from '../../notification/style';
import type { ComponentToken as PageHeaderComponentToken } from '../../page-header/style';
import type { ComponentToken as PaginationComponentToken } from '../../pagination/style';
import type { ComponentToken as PopconfirmComponentToken } from '../../popconfirm/style';
import type { ComponentToken as PopoverComponentToken } from '../../popover/style';
import type { ComponentToken as ProgressComponentToken } from '../../progress/style';
import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style';
import type { ComponentToken as RadioComponentToken } from '../../radio/style';
import type { ComponentToken as RateComponentToken } from '../../rate/style';
import type { ComponentToken as ResultComponentToken } from '../../result/style';
@ -35,53 +50,55 @@ import type { ComponentToken as SkeletonComponentToken } from '../../skeleton/st
import type { ComponentToken as SliderComponentToken } from '../../slider/style';
import type { ComponentToken as SpaceComponentToken } from '../../space/style';
import type { ComponentToken as SpinComponentToken } from '../../spin/style';
import type { ComponentToken as StatisticComponentToken } from '../../statistic/style';
import type { ComponentToken as StepsComponentToken } from '../../steps/style';
import type { ComponentToken as SwitchComponentToken } from '../../switch/style';
import type { ComponentToken as TableComponentToken } from '../../table/style';
import type { ComponentToken as TabsComponentToken } from '../../tabs/style';
import type { ComponentToken as TagComponentToken } from '../../tag/style';
import type { ComponentToken as TimelineComponentToken } from '../../timeline/style';
import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style';
import type { ComponentToken as TourComponentToken } from '../../tour/style';
import type { ComponentToken as TransferComponentToken } from '../../transfer/style';
import type { ComponentToken as TreeSelectComponentToken } from '../../tree-select/style';
import type { ComponentToken as TreeComponentToken } from '../../tree/style';
import type { ComponentToken as TypographyComponentToken } from '../../typography/style';
import type { ComponentToken as UploadComponentToken } from '../../upload/style';
import type { ComponentToken as TourComponentToken } from '../../tour/style';
import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style';
import type { ComponentToken as AppComponentToken } from '../../app/style';
import type { ComponentToken as WaveToken } from '../../_util/wave/style';
import type { ComponentToken as FlexToken } from '../../flex/style';
export interface ComponentTokenMap {
Affix?: {};
Affix?: AffixComponentToken;
Alert?: AlertComponentToken;
Anchor?: AnchorComponentToken;
Avatar?: AvatarComponentToken;
Badge?: {};
Badge?: BadgeComponentToken;
Button?: ButtonComponentToken;
Breadcrumb?: {};
Breadcrumb?: BreadcrumbComponentToken;
Card?: CardComponentToken;
Carousel?: CarouselComponentToken;
Cascader?: CascaderComponentToken;
Checkbox?: CheckboxComponentToken;
// ColorPicker?: ColorPickerComponentToken;
Collapse?: CollapseComponentToken;
Comment?: {};
Comment?: CommentComponentToken;
DatePicker?: DatePickerComponentToken;
Descriptions?: {};
Descriptions?: DescriptionsComponentToken;
Divider?: DividerComponentToken;
Drawer?: DrawerComponentToken;
Dropdown?: DropdownComponentToken;
Empty?: EmptyComponentToken;
Flex?: FlexComponentToken;
FloatButton?: FloatButtonComponentToken;
Form?: {};
Grid?: {};
Form?: FormComponentToken;
Grid?: GridComponentToken;
Image?: ImageComponentToken;
Input?: {};
Input?: InputComponentToken;
InputNumber?: InputNumberComponentToken;
Layout?: LayoutComponentToken;
List?: ListComponentToken;
Mentions?: MentionsComponentToken;
Notification?: NotificationComponentToken;
PageHeader?: {};
Pagination?: {};
PageHeader?: PageHeaderComponentToken;
Pagination?: PaginationComponentToken;
Popover?: PopoverComponentToken;
Popconfirm?: PopconfirmComponentToken;
Rate?: RateComponentToken;
@ -92,11 +109,11 @@ export interface ComponentTokenMap {
Skeleton?: SkeletonComponentToken;
Slider?: SliderComponentToken;
Spin?: SpinComponentToken;
Statistic?: {};
Switch?: {};
Statistic?: StatisticComponentToken;
Switch?: SwitchComponentToken;
Tag?: TagComponentToken;
Tree?: {};
TreeSelect?: {};
Tree?: TreeComponentToken;
TreeSelect?: TreeSelectComponentToken;
Typography?: TypographyComponentToken;
Timeline?: TimelineComponentToken;
Transfer?: TransferComponentToken;
@ -114,8 +131,7 @@ export interface ComponentTokenMap {
Tour?: TourComponentToken;
QRCode?: QRCodeComponentToken;
App?: AppComponentToken;
Flex?: FlexToken;
// /** @private Internal TS definition. Do not use. */
/** @private Internal TS definition. Do not use. */
Wave?: WaveToken;
}

View File

@ -1,5 +1,12 @@
import type { ComponentTokenMap } from './components';
import type { CSSInterpolation, DerivativeFunc } from '../../_util/cssinjs';
import type { AliasToken } from './alias';
import type { ComponentTokenMap } from './components';
import type { MapToken } from './maps';
import type { SeedToken } from './seeds';
import type { VueNode } from '../..//_util/type';
import type { Ref } from 'vue';
export type MappingAlgorithm = DerivativeFunc<SeedToken, MapToken>;
export type OverrideToken = {
[key in keyof ComponentTokenMap]: Partial<ComponentTokenMap[key]> & Partial<AliasToken>;
@ -8,18 +15,30 @@ export type OverrideToken = {
/** Final token which contains the components level override */
export type GlobalToken = AliasToken & ComponentTokenMap;
export { PresetColors } from './presetColors';
export type { PresetColorType, ColorPalettes, PresetColorKey } from './presetColors';
export type { SeedToken } from './seeds';
export type { AliasToken } from './alias';
export type { ComponentTokenMap } from './components';
export type {
MapToken,
ColorMapToken,
ColorNeutralMapToken,
CommonMapToken,
HeightMapToken,
SizeMapToken,
FontMapToken,
HeightMapToken,
MapToken,
SizeMapToken,
StyleMapToken,
} from './maps';
export type { AliasToken } from './alias';
export type { ComponentTokenMap } from './components';
export { PresetColors } from './presetColors';
export type {
LegacyColorPalettes,
ColorPalettes,
PresetColorKey,
PresetColorType,
} from './presetColors';
export type { SeedToken } from './seeds';
export type UseComponentStyleResult = [(node: VueNode) => VueNode, Ref<string>];
export type GenerateStyle<
ComponentToken extends object = AliasToken,
ReturnType = CSSInterpolation,
> = (token: ComponentToken) => ReturnType;

View File

@ -13,25 +13,31 @@ export interface ColorNeutralMapToken {
/**
* @nameZH
* @nameEN Text Color
* @desc W3C使
* @descEN Default text color which comply with W3C standards, and this color is also the darkest neutral color.
*/
colorText: string;
/**
* @nameZH
* @nameEN Secondary Text Color
* @desc Label Menu
* @descEN The second level of text color is generally used in scenarios where text color is not emphasized, such as label text, menu text selection state, etc.
*/
colorTextSecondary: string;
/**
* @nameZH
* @desc
* @descEN The third level of text color is generally used for descriptive text, such as form supplementary explanation text, list descriptive text, etc.
*/
colorTextTertiary: string;
/**
* @nameZH
* @desc
* @descEN The fourth level of text color is the lightest text color, such as form input prompt text, disabled color text, etc.
*/
colorTextQuaternary: string;
@ -58,24 +64,28 @@ export interface ColorNeutralMapToken {
/**
* @nameZH
* @desc Slider hover
* @descEN The darkest fill color is used to distinguish between the second and third level of fill color, and is currently only used in the hover effect of Slider.
*/
colorFill: string;
/**
* @nameZH
* @desc RateSkeleton Hover Table
* @descEN The second level of fill color can outline the shape of the element more clearly, such as Rate, Skeleton, etc. It can also be used as the Hover state of the third level of fill color, such as Table, etc.
*/
colorFillSecondary: string;
/**
* @nameZH
* @desc SliderSegmented 使
* @descEN The third level of fill color is used to outline the shape of the element, such as Slider, Segmented, etc. If there is no emphasis requirement, it is recommended to use the third level of fill color as the default fill color.
*/
colorFillTertiary: string;
/**
* @nameZH
* @desc
* @descEN The weakest level of fill color is suitable for color blocks that are not easy to attract attention, such as zebra stripes, color blocks that distinguish boundaries, etc.
*/
colorFillQuaternary: string;
@ -83,27 +93,39 @@ export interface ColorNeutralMapToken {
/**
* @nameZH
* @nameEN Layout Background Color
* @desc B1 使 token
* @descEN This color is used for the background color of the overall layout of the page. This token will only be used when it is necessary to be at the B1 visual level in the page. Other usages are wrong.
*/
colorBgLayout: string;
/**
* @nameZH
* @desc `colorBgElevated`
* @descEN Container background color, e.g: default button, input box, etc. Be sure not to confuse this with `colorBgElevated`.
*/
colorBgContainer: string;
/**
* @nameZH
* @desc token `colorBgContainer`
* @descEN Container background color of the popup layer, in dark mode the color value of this token will be a little brighter than `colorBgContainer`. E.g: modal, pop-up, menu, etc.
*/
colorBgElevated: string;
/**
* @nameZH
* @desc Tooltip
* @descEN This color is used to draw the user's strong attention to the background color, and is currently only used in the background color of Tooltip.
*/
colorBgSpotlight: string;
/**
* @nameZH
* @nameEN Frosted glass container background color
* @desc
* @descEN Control the background color of frosted glass container, usually transparent.
*/
colorBgBlur: string;
}
/**
@ -112,12 +134,15 @@ export interface ColorNeutralMapToken {
interface ColorPrimaryMapToken {
/**
* @nameZH
* @desc */
* @nameEN Primary color of the brand
* @desc
* @descEN The brand color is one of the most intuitive visual elements that reflects product characteristics and communication concepts, and is used for the main color tone, main buttons, main icons, main text, etc. of the product.
*/
colorPrimary: string; // 6
/**
* @nameZH
* @nameEN Light Background Color of Primary Color
* @nameEN Light background color of primary color
* @desc
* @descEN Light background color of primary color, usually used for weak visual level selection state.
*/
@ -125,49 +150,65 @@ interface ColorPrimaryMapToken {
/**
* @nameZH
* @nameEN Hover state of light background color of primary color
* @desc
* @descEN The hover state color corresponding to the light background color of the primary color.
*/
colorPrimaryBgHover: string; // 2
/**
* @nameZH
* @desc Slider
* @nameEN Border color of primary color
* @desc Slider
* @descEN The stroke color under the main color gradient, used on the stroke of components such as Slider.
*/
colorPrimaryBorder: string; // 3
/**
* @nameZH
* @desc Slider Button Hover 使
* @nameEN Hover state of border color of primary color
* @desc Slider Button Hover 使
* @descEN The hover state of the stroke color under the main color gradient, which will be used when the stroke Hover of components such as Slider and Button.
*/
colorPrimaryBorderHover: string; // 4
/**
* @nameZH
* @desc 使
* @nameEN Hover state of primary color
* @desc
* @descEN Hover state under the main color gradient.
*/
colorPrimaryHover: string; // 5
/**
* @nameZH
* @desc
* @nameEN Active state of primary color
* @desc
* @descEN Dark active state under the main color gradient.
*/
colorPrimaryActive: string; // 7
/**
* @nameZH
* @desc
* @nameEN Hover state of text color of primary color
* @desc
* @descEN Hover state of text color under the main color gradient.
*/
colorPrimaryTextHover: string; // 8
/**
* @nameZH
* @desc
* @nameEN Text color of primary color
* @desc
* @descEN Text color under the main color gradient.
*/
colorPrimaryText: string; // 9
/**
* @nameZH
* @desc
* @nameZH
* @nameEN Active state of text color of primary color
* @desc
* @descEN Active state of text color under the main color gradient.
*/
colorPrimaryTextActive: string; // 10
}
@ -191,49 +232,65 @@ interface ColorSuccessMapToken {
/**
* @nameZH
* @nameEN Border Color of Success Color
* @desc Tag Alert
* @descEN Border color of success color, used for Tag and Alert success state border color
*/
colorSuccessBorder: string; // 3
/**
* @nameZH
* @nameEN Hover State Color of Success Border
* @desc
* @descEN Hover state color of success color border
*/
colorSuccessBorderHover: string; // 4
/**
* @nameZH
* @nameEN Hover State Color of Dark Success
* @desc
* @descEN Hover state color of dark success color
*/
colorSuccessHover: string; // 5
/**
* @nameZH
* @nameEN Success Color
* @desc ResultProgress 使
* @descEN Default success color, used in components such as Result and Progress
*/
colorSuccess: string; // 6
/**
* @nameZH
* @nameEN Active State Color of Dark Success
* @desc
* @descEN Active state color of dark success color
*/
colorSuccessActive: string; // 7
/**
* @nameZH
* @nameEN Hover State Color of Success Text
* @desc
* @descEN Hover state color of success color text
*/
colorSuccessTextHover: string; // 8
/**
* @nameZH
* @nameEN Default State Color of Success Text
* @desc
* @descEN Default state color of success color text
*/
colorSuccessText: string; // 9
/**
* @nameZH
* @nameEN Active State Color of Success Text
* @desc
* @descEN Active state color of success color text
*/
colorSuccessTextActive: string; // 10
}
@ -241,60 +298,81 @@ interface ColorSuccessMapToken {
interface ColorWarningMapToken {
/**
* @nameZH
* @nameEN Warning background color
* @desc
* @descEN The background color of the warning state.
*/
colorWarningBg: string; // 1
/**
* @nameZH
* @nameEN Warning background color hover state
* @desc
* @descEN The hover state background color of the warning state.
*/
colorWarningBgHover: string; // 2
/**
* @nameZH
* @nameEN Warning border color
* @desc
* @descEN The border color of the warning state.
*/
colorWarningBorder: string; // 3
/**
* @nameZH
* @nameEN Warning border color hover state
* @desc
* @descEN The hover state border color of the warning state.
*/
colorWarningBorderHover: string; // 4
/**
* @nameZH
* @nameEN Warning hover color
* @desc
* @descEN The hover state of the warning color.
*/
colorWarningHover: string; // 5
/**
* @nameZH
* @nameEN Warning color
* @desc Notification Alert Input 使
* @descEN The most commonly used warning color, used for warning components such as Notification, Alert, or input components.
*/
colorWarning: string; // 6
/**
* @nameZH
* @nameEN Warning active color
* @desc
* @descEN The active state of the warning color.
*/
colorWarningActive: string; // 7
/**
* @nameZH
* @nameEN Warning text hover state
* @desc
* @descEN The hover state of the text in the warning color.
*/
colorWarningTextHover: string; // 8
/**
* @nameZH
* @nameEN Warning text default state
* @desc
* @descEN The default state of the text in the warning color.
*/
colorWarningText: string; // 9
/**
* @nameZH
* @nameEN Warning text active state
* @desc
* @descEN The active state of the text in the warning color.
*/
colorWarningTextActive: string; // 10
}
@ -302,53 +380,81 @@ interface ColorWarningMapToken {
interface ColorInfoMapToken {
/**
* @nameZH
* @desc
* @nameEN Light background color of information color
* @desc
* @descEN Light background color of information color.
*/
colorInfoBg: string; // 1
/**
* @nameZH
* @desc
* @nameEN Hover state of light background color of information color
* @desc
* @descEN Hover state of light background color of information color.
*/
colorInfoBgHover: string; // 2
/**
* @nameZH
* @nameEN Border color of information color
* @desc
* @descEN Border color of information color.
*/
colorInfoBorder: string; // 3
/**
* @nameZH
* @nameEN Hover state of border color of information color
* @desc
* @descEN Hover state of border color of information color.
*/
colorInfoBorderHover: string; // 4
/**
* @nameZH
* @nameEN Hover state of dark color of information color
* @desc
* @descEN Hover state of dark color of information color.
*/
colorInfoHover: string; // 5
/**
* @nameZH
* @nameEN Information color
* @desc
* @descEN Information color.
*/
colorInfo: string; // 6
/**
* @nameZH
* @nameEN Active state of dark color of information color
* @desc
* @descEN Active state of dark color of information color.
*/
colorInfoActive: string; // 7
/**
* @nameZH
* @nameEN Hover state of text color of information color
* @desc
* @descEN Hover state of text color of information color.
*/
colorInfoTextHover: string; // 8
/**
* @nameZH
* @nameEN Default state of text color of information color
* @desc
* @descEN Default state of text color of information color.
*/
colorInfoText: string; // 9
/**
* @nameZH
* @nameEN Active state of text color of information color
* @desc
* @descEN Active state of text color of information color.
*/
colorInfoTextActive: string; // 10
}
@ -356,62 +462,117 @@ interface ColorInfoMapToken {
interface ColorErrorMapToken {
/**
* @nameZH
* @nameEN Error background color
* @desc
* @descEN The background color of the error state.
*/
colorErrorBg: string; // 1
/**
* @nameZH
* @nameEN Error background color hover state
* @desc
* @descEN The hover state background color of the error state.
*/
colorErrorBgHover: string; // 2
/**
* @nameZH
* @nameEN Error border color
* @desc
* @descEN The border color of the error state.
*/
colorErrorBorder: string; // 3
/**
* @nameZH
* @nameEN Error border color hover state
* @desc
* @descEN The hover state border color of the error state.
*/
colorErrorBorderHover: string; // 4
/**
* @nameZH
* @nameEN Error hover color
* @desc
* @descEN The hover state of the error color.
*/
colorErrorHover: string; // 5
/**
* @nameZH
* @nameEN Error color
* @desc
* @descEN The color of the error state.
*/
colorError: string; // 6
/**
* @nameZH
* @nameEN Error active color
* @desc
* @descEN The active state of the error color.
*/
colorErrorActive: string; // 7
/**
* @nameZH
* @nameEN Error text hover state
* @desc
* @descEN The hover state of the text in the error color.
*/
colorErrorTextHover: string; // 8
/**
* @nameZH
* @nameEN Error text default state
* @desc
* @descEN The default state of the text in the error color.
*/
colorErrorText: string; // 9
/**
* @nameZH
* @nameEN Error text active state
* @desc
* @descEN The active state of the text in the error color.
*/
colorErrorTextActive: string; // 10
}
export interface ColorLinkMapToken {
/**
* @nameZH
* @nameEN Hyperlink color
* @desc
* @descEN Control the color of hyperlink.
*/
colorLink: string;
/**
* @nameZH
* @nameEN Hyperlink hover color
* @desc
* @descEN Control the color of hyperlink when hovering.
*/
colorLinkHover: string;
/**
* @nameZH
* @nameEN Hyperlink active color
* @desc
* @descEN Control the color of hyperlink when clicked.
*/
colorLinkActive: string;
}
export interface ColorMapToken
extends ColorNeutralMapToken,
ColorPrimaryMapToken,
ColorSuccessMapToken,
ColorWarningMapToken,
ColorErrorMapToken,
ColorInfoMapToken {
ColorInfoMapToken,
ColorLinkMapToken {
/**
* @nameZH
* @desc

View File

@ -1,49 +1,139 @@
export interface FontMapToken {
// Font Size
/**
* @desc
* @descEN Small font size
*/
fontSizeSM: number;
/**
* @desc
* @descEN Standard font size
*/
fontSize: number;
/**
* @desc
* @descEN Large font size
*/
fontSizeLG: number;
/**
* @desc
* @descEN Super large font size
*/
fontSizeXL: number;
/**
* @nameZH
* @nameEN Font size of heading level 1
* @desc H1 使
* @descEN Font size of h1 tag.
* @default 38
*/
fontSizeHeading1: number;
/**
* @nameZH
* @nameEN Font size of heading level 2
* @desc h2 使
* @descEN Font size of h2 tag.
* @default 30
*/
fontSizeHeading2: number;
/**
* @nameZH
* @nameEN Font size of heading level 3
* @desc h3 使
* @descEN Font size of h3 tag.
* @default 24
*/
fontSizeHeading3: number;
/**
* @nameZH
* @nameEN Font size of heading level 4
* @desc h4 使
* @descEN Font size of h4 tag.
* @default 20
*/
fontSizeHeading4: number;
/**
* @nameZH
* @nameEN Font size of heading level 5
* @desc h5 使
* @descEN Font size of h5 tag.
* @default 16
*/
fontSizeHeading5: number;
// LineHeight
/**
* @desc
* @descEN Line height of text.
*/
lineHeight: number;
/**
* @desc
* @descEN Line height of large text.
*/
lineHeightLG: number;
/**
* @desc
* @descEN Line height of small text.
*/
lineHeightSM: number;
// TextHeight
/**
* Round of fontSize * lineHeight
* @internal
*/
fontHeight: number;
/**
* Round of fontSizeSM * lineHeightSM
* @internal
*/
fontHeightSM: number;
/**
* Round of fontSizeLG * lineHeightLG
* @internal
*/
fontHeightLG: number;
/**
* @nameZH
* @nameEN Line height of heading level 1
* @desc H1 使
* @descEN Line height of h1 tag.
* @default 1.4
*/
lineHeightHeading1: number;
/**
* @nameZH
* @nameEN Line height of heading level 2
* @desc h2 使
* @descEN Line height of h2 tag.
* @default 1.35
*/
lineHeightHeading2: number;
/**
* @nameZH
* @nameEN Line height of heading level 3
* @desc h3 使
* @descEN Line height of h3 tag.
* @default 1.3
*/
lineHeightHeading3: number;
/**
* @nameZH
* @nameEN Line height of heading level 4
* @desc h4 使
* @descEN Line height of h4 tag.
* @default 1.25
*/
lineHeightHeading4: number;
/**
* @nameZH
* @nameEN Line height of heading level 5
* @desc h5 使
* @descEN Line height of h5 tag.
* @default 1.2
*/
lineHeightHeading5: number;
}

View File

@ -1,19 +1,31 @@
import type { ColorPalettes } from '../presetColors';
import type { ColorPalettes, LegacyColorPalettes } from '../presetColors';
import type { SeedToken } from '../seeds';
import type { SizeMapToken, HeightMapToken } from './size';
import type { ColorMapToken } from './colors';
import type { StyleMapToken } from './style';
import type { FontMapToken } from './font';
import type { HeightMapToken, SizeMapToken } from './size';
import type { StyleMapToken } from './style';
export * from './colors';
export * from './style';
export * from './size';
export * from './font';
export * from './size';
export * from './style';
export interface CommonMapToken extends StyleMapToken {
// Motion
/**
* @desc
* @descEN Motion speed, fast speed. Used for small element animation interaction.
*/
motionDurationFast: string;
/**
* @desc
* @descEN Motion speed, medium speed. Used for medium element animation interaction.
*/
motionDurationMid: string;
/**
* @desc
* @descEN Motion speed, slow speed. Used for large element animation interaction.
*/
motionDurationSlow: string;
}
@ -25,6 +37,7 @@ export interface CommonMapToken extends StyleMapToken {
export interface MapToken
extends SeedToken,
ColorPalettes,
LegacyColorPalettes,
ColorMapToken,
SizeMapToken,
HeightMapToken,

View File

@ -51,18 +51,24 @@ export interface HeightMapToken {
/**
* @nameZH
* @nameEN XS component height
* @desc
* @descEN XS component height
*/
controlHeightXS: number;
/**
* @nameZH
* @nameEN SM component height
* @desc
* @descEN SM component height
*/
controlHeightSM: number;
/**
* @nameZH
* @nameEN LG component height
* @desc
* @descEN LG component height
*/
controlHeightLG: number;
}

View File

@ -10,8 +10,9 @@ export interface StyleMapToken {
/**
* @nameZH XS
* @nameEN XS Border Radius
* @desc XS Segmented Arrow
* @descEN XS size border radius, used in some small border radius components, such as Segmented, Arrow and other components.
* @descEN XS size border radius, used in some small border radius components, such as Segmented, Arrow and other components with small border radius.
* @default 2
*/
borderRadiusXS: number;
@ -32,7 +33,11 @@ export interface StyleMapToken {
*/
borderRadiusLG: number;
/**
* @nameZH
* @nameEN Outer Border Radius
* @default 4
* @desc
* @descEN Outer border radius
*/
borderRadiusOuter: number;
}

View File

@ -20,6 +20,13 @@ export type PresetColorType = Record<PresetColorKey, string>;
type ColorPaletteKeyIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
export type ColorPalettes = {
export type LegacyColorPalettes = {
/**
* @deprecated
*/
[key in `${keyof PresetColorType}-${ColorPaletteKeyIndex}`]: string;
};
export type ColorPalettes = {
[key in `${keyof PresetColorType}${ColorPaletteKeyIndex}`]: string;
};

View File

@ -50,7 +50,7 @@ export interface SeedToken extends PresetColorType {
/**
* @nameZH
* @nameEN Seed Text Color
* @desc v5 **使 Seed Token**
* @desc v5 使 Seed Token
* @descEN Used to derive the base variable of the text color gradient. In v5, we added a layer of text color derivation algorithm to produce gradient variables of text color gradient. But please do not use this Seed Token directly in the code!
*/
colorTextBase: string;
@ -58,24 +58,42 @@ export interface SeedToken extends PresetColorType {
/**
* @nameZH
* @nameEN Seed Background Color
* @desc v5 **使 Seed Token**
* @desc v5 使 Seed Token
* @descEN Used to derive the base variable of the background color gradient. In v5, we added a layer of background color derivation algorithm to produce map token of background color. But PLEASE DO NOT USE this Seed Token directly in the code!
*/
colorBgBase: string;
/**
* @nameZH
* @nameEN Hyperlink color
* @desc
* @descEN Control the color of hyperlink.
*/
colorLink: string;
// ---------- Font ---------- //
/**
* @nameZH
* @nameEN FontFamily
* @nameEN Font family for default text
* @desc Ant Design 使
* @descEN The font family of Ant Design prioritizes the default interface font of the system, and provides a set of alternative font libraries that are suitable for screen display to maintain the readability and readability of the font under different platforms and browsers, reflecting the friendly, stable and professional characteristics.
*/
fontFamily: string;
/**
* @nameZH
* @nameEN Font family for code text
* @desc Typography codepre kbd
* @descEN Code font, used for code, pre and kbd elements in Typography
*/
fontFamilyCode: string;
/**
* @nameZH
* @nameEN Default Font Size
* @desc 使广
* @descEN The most widely used font size in the design system, from which the text gradient will be derived.
* @default 14
*/
fontSize: number;
@ -130,6 +148,8 @@ export interface SeedToken extends PresetColorType {
/**
* @nameZH
* @desc
* @descEN The size of the component arrow
*/
sizePopupArrow: number;
@ -184,24 +204,57 @@ export interface SeedToken extends PresetColorType {
motionUnit: number;
/**
* @nameZH
* @nameZH
* @nameEN Animation Base Duration.
*/
motionBase: number;
/**
* @desc
* @descEN Preset motion curve.
*/
motionEaseOutCirc: string;
/**
* @desc
* @descEN Preset motion curve.
*/
motionEaseInOutCirc: string;
/**
* @desc
* @descEN Preset motion curve.
*/
motionEaseInOut: string;
/**
* @desc
* @descEN Preset motion curve.
*/
motionEaseOutBack: string;
/**
* @desc
* @descEN Preset motion curve.
*/
motionEaseInBack: string;
/**
* @desc
* @descEN Preset motion curve.
*/
motionEaseInQuint: string;
/**
* @desc
* @descEN Preset motion curve.
*/
motionEaseOutQuint: string;
/**
* @desc
* @descEN Preset motion curve.
*/
motionEaseOut: string;
// ---------- Style ---------- //
@ -210,7 +263,17 @@ export interface SeedToken extends PresetColorType {
* @nameZH 线
* @nameEN Wireframe Style
* @desc 线使 V4
* @descEN Used to change the visual effect of the component to wireframe, if you need to use the V4 effect, you need to enable the configuration item
* @default false
*/
wireframe: boolean;
/**
* @nameZH
* @nameEN Motion Style
* @desc `false`
* @descEN Used to configure the motion effect, when it is `false`, the motion is turned off
* @default false
*/
motion: boolean;
}

View File

@ -1,146 +1,51 @@
import type { CSSInterpolation, Theme } from '../_util/cssinjs';
import { createTheme, useCacheToken, useStyleRegister } from '../_util/cssinjs';
import { useStyleRegister } from '../_util/cssinjs';
import version from '../version';
import type {
AliasToken,
GlobalToken,
MapToken,
OverrideToken,
PresetColorType,
GenerateStyle,
PresetColorKey,
PresetColorType,
SeedToken,
UseComponentStyleResult,
} from './interface';
import { PresetColors } from './interface';
import defaultDerivative from './themes/default';
import defaultSeedToken from './themes/seed';
import formatToken from './util/alias';
import type { FullToken } from './util/genComponentStyleHook';
import genComponentStyleHook from './util/genComponentStyleHook';
import statisticToken, { merge as mergeToken, statistic } from './util/statistic';
import type { VueNode } from '../_util/type';
import { objectType } from '../_util/type';
import type { ComputedRef, InjectionKey, Ref } from 'vue';
import {
triggerRef,
unref,
defineComponent,
provide,
computed,
inject,
watch,
shallowRef,
} from 'vue';
const defaultTheme = createTheme(defaultDerivative);
import useToken from './useToken';
import type { FullToken, GetDefaultToken } from './util/genComponentStyleHook';
import genComponentStyleHook, {
genSubStyleComponent,
genStyleHooks,
} from './util/genComponentStyleHook';
import genPresetColor from './util/genPresetColor';
import statisticToken, { merge as mergeToken } from './util/statistic';
import useResetIconStyle from './util/useResetIconStyle';
import calc from './util/calc';
import { getLineHeight } from './themes/shared/genFontSizes';
export { defaultConfig, DesignTokenProvider } from './context';
export {
// colors
PresetColors,
// Statistic
statistic,
statisticToken,
mergeToken,
// hooks
useStyleRegister,
genComponentStyleHook,
genSubStyleComponent,
genPresetColor,
genStyleHooks,
mergeToken,
statisticToken,
calc,
getLineHeight,
// hooks
useResetIconStyle,
useStyleRegister,
useToken,
};
export type {
SeedToken,
AliasToken,
PresetColorType,
PresetColorKey,
// FIXME: Remove this type
AliasToken as DerivativeToken,
FullToken,
GenerateStyle,
PresetColorKey,
PresetColorType,
SeedToken,
UseComponentStyleResult,
GetDefaultToken,
};
// ================================ Context =================================
// To ensure snapshot stable. We disable hashed in test env.
export const defaultConfig = {
token: defaultSeedToken,
hashed: true,
};
export interface DesignTokenContext {
token: Partial<AliasToken>;
theme?: Theme<SeedToken, MapToken>;
components?: OverrideToken;
hashed?: string | boolean;
}
//defaultConfig
const DesignTokenContextKey: InjectionKey<ComputedRef<DesignTokenContext>> =
Symbol('DesignTokenContext');
export const globalDesignTokenApi = shallowRef<DesignTokenContext>();
export const useDesignTokenProvider = (value: ComputedRef<DesignTokenContext>) => {
provide(DesignTokenContextKey, value);
watch(
value,
() => {
globalDesignTokenApi.value = unref(value);
triggerRef(globalDesignTokenApi);
},
{ immediate: true, deep: true },
);
};
export const useDesignTokenInject = () => {
return inject(
DesignTokenContextKey,
computed(() => globalDesignTokenApi.value || defaultConfig),
);
};
export const DesignTokenProvider = defineComponent({
props: {
value: objectType<DesignTokenContext>(),
},
setup(props, { slots }) {
useDesignTokenProvider(computed(() => props.value));
return () => {
return slots.default?.();
};
},
});
// ================================== Hook ==================================
export function useToken(): [
ComputedRef<Theme<SeedToken, MapToken>>,
ComputedRef<GlobalToken>,
ComputedRef<string>,
] {
const designTokenContext = inject<ComputedRef<DesignTokenContext>>(
DesignTokenContextKey,
computed(() => globalDesignTokenApi.value || defaultConfig),
);
const salt = computed(() => `${version}-${designTokenContext.value.hashed || ''}`);
const mergedTheme = computed(() => designTokenContext.value.theme || defaultTheme);
const cacheToken = useCacheToken<GlobalToken, SeedToken>(
mergedTheme,
computed(() => [defaultSeedToken, designTokenContext.value.token]),
computed(() => ({
salt: salt.value,
override: {
override: designTokenContext.value.token,
...designTokenContext.value.components,
},
formatToken,
})),
);
return [
mergedTheme,
computed(() => cacheToken.value[0]),
computed(() => (designTokenContext.value.hashed ? cacheToken.value[1] : '')),
];
}
export type UseComponentStyleResult = [(node: VueNode) => VueNode, Ref<string>];
export type GenerateStyle<
ComponentToken extends object = AliasToken,
ReturnType = CSSInterpolation,
> = (token: ComponentToken) => ReturnType;

View File

@ -1,9 +1,9 @@
import type { DerivativeFunc } from '../../../_util/cssinjs';
import genControlHeight from '../shared/genControlHeight';
import type { MapToken, SeedToken } from '../../interface';
import defaultAlgorithm from '../default';
import genCompactSizeMapToken from './genCompactSizeMapToken';
import genFontMapToken from '../shared/genFontMapToken';
import type { DerivativeFunc } from '../../../_util/cssinjs';
const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
const mergedMapToken = mapToken ?? defaultAlgorithm(token);

View File

@ -46,6 +46,7 @@ export const generateNeutralColorPalettes: GenerateNeutralColorMap = (
colorBgContainer: getSolidColor(colorBgBase, 8),
colorBgLayout: getSolidColor(colorBgBase, 0),
colorBgSpotlight: getSolidColor(colorBgBase, 26),
colorBgBlur: getAlphaColor(colorTextBase, 0.04),
colorBorder: getSolidColor(colorBgBase, 26),
colorBorderSecondary: getSolidColor(colorBgBase, 19),

View File

@ -1,6 +1,12 @@
import { generate } from '@ant-design/colors';
import type { DerivativeFunc } from '../../../_util/cssinjs';
import type { ColorPalettes, MapToken, PresetColorType, SeedToken } from '../../interface';
import type {
ColorPalettes,
LegacyColorPalettes,
MapToken,
PresetColorType,
SeedToken,
} from '../../interface';
import { defaultPresetColors } from '../seed';
import genColorMapToken from '../shared/genColorMapToken';
import { generateColorPalettes, generateNeutralColorPalettes } from './colors';
@ -13,8 +19,9 @@ const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
return new Array(10).fill(1).reduce((prev, _, i) => {
prev[`${colorKey}-${i + 1}`] = colors[i];
prev[`${colorKey}${i + 1}`] = colors[i];
return prev;
}, {}) as ColorPalettes;
}, {}) as ColorPalettes & LegacyColorPalettes;
})
.reduce((prev, cur) => {
prev = {
@ -22,7 +29,7 @@ const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
...cur,
};
return prev;
}, {} as ColorPalettes);
}, {} as ColorPalettes & LegacyColorPalettes);
const mergedMapToken = mapToken ?? defaultAlgorithm(token);

View File

@ -46,6 +46,7 @@ export const generateNeutralColorPalettes: GenerateNeutralColorMap = (
colorBgContainer: getSolidColor(colorBgBase, 0),
colorBgElevated: getSolidColor(colorBgBase, 0),
colorBgSpotlight: getAlphaColor(colorTextBase, 0.85),
colorBgBlur: 'transparent',
colorBorder: getSolidColor(colorBgBase, 15),
colorBorderSecondary: getSolidColor(colorBgBase, 6),

View File

@ -1,7 +1,13 @@
import { generate } from '@ant-design/colors';
import genControlHeight from '../shared/genControlHeight';
import genSizeMapToken from '../shared/genSizeMapToken';
import type { ColorPalettes, MapToken, PresetColorType, SeedToken } from '../../interface';
import type {
ColorPalettes,
LegacyColorPalettes,
MapToken,
PresetColorType,
SeedToken,
} from '../../interface';
import { defaultPresetColors } from '../seed';
import genColorMapToken from '../shared/genColorMapToken';
import genCommonMapToken from '../shared/genCommonMapToken';
@ -15,8 +21,9 @@ export default function derivative(token: SeedToken): MapToken {
return new Array(10).fill(1).reduce((prev, _, i) => {
prev[`${colorKey}-${i + 1}`] = colors[i];
prev[`${colorKey}${i + 1}`] = colors[i];
return prev;
}, {}) as ColorPalettes;
}, {}) as ColorPalettes & LegacyColorPalettes;
})
.reduce((prev, cur) => {
prev = {
@ -24,7 +31,7 @@ export default function derivative(token: SeedToken): MapToken {
...cur,
};
return prev;
}, {} as ColorPalettes);
}, {} as ColorPalettes & LegacyColorPalettes);
return {
...token,

View File

@ -26,6 +26,7 @@ const seedToken: SeedToken = {
colorWarning: '#faad14',
colorError: '#ff4d4f',
colorInfo: '#1677ff',
colorLink: '',
colorTextBase: '',
colorBgBase: '',
@ -34,6 +35,7 @@ const seedToken: SeedToken = {
fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'`,
fontFamilyCode: `'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace`,
fontSize: 14,
// Line
@ -72,5 +74,8 @@ const seedToken: SeedToken = {
// Wireframe
wireframe: false,
// Motion
motion: true,
};
export default seedToken;

View File

@ -28,6 +28,10 @@ export default function genColorMapToken(
const infoColors = generateColorPalettes(colorInfoBase);
const neutralColors = generateNeutralColorPalettes(colorBgBase, colorTextBase);
// Color Link
const colorLink = seed.colorLink || seed.colorInfo;
const linkColors = generateColorPalettes(colorLink);
return {
...neutralColors,
@ -86,6 +90,10 @@ export default function genColorMapToken(
colorInfoText: infoColors[9],
colorInfoTextActive: infoColors[10],
colorLinkHover: linkColors[4],
colorLink: linkColors[6],
colorLinkActive: linkColors[7],
colorBgMask: new TinyColor('#000').setAlpha(0.45).toRgbString(),
colorWhite: '#fff',
};

View File

@ -6,10 +6,17 @@ const genFontMapToken = (fontSize: number): FontMapToken => {
const fontSizes = fontSizePairs.map(pair => pair.size);
const lineHeights = fontSizePairs.map(pair => pair.lineHeight);
const fontSizeMD = fontSizes[1];
const fontSizeSM = fontSizes[0];
const fontSizeLG = fontSizes[2];
const lineHeight = lineHeights[1];
const lineHeightSM = lineHeights[0];
const lineHeightLG = lineHeights[2];
return {
fontSizeSM: fontSizes[0],
fontSize: fontSizes[1],
fontSizeLG: fontSizes[2],
fontSizeSM,
fontSize: fontSizeMD,
fontSizeLG,
fontSizeXL: fontSizes[3],
fontSizeHeading1: fontSizes[6],
@ -18,9 +25,13 @@ const genFontMapToken = (fontSize: number): FontMapToken => {
fontSizeHeading4: fontSizes[3],
fontSizeHeading5: fontSizes[2],
lineHeight: lineHeights[1],
lineHeightLG: lineHeights[2],
lineHeightSM: lineHeights[0],
lineHeight,
lineHeightLG,
lineHeightSM,
fontHeight: Math.round(lineHeight * fontSizeMD),
fontHeightLG: Math.round(lineHeightLG * fontSizeLG),
fontHeightSM: Math.round(lineHeightSM * fontSizeSM),
lineHeightHeading1: lineHeights[6],
lineHeightHeading2: lineHeights[5],

View File

@ -1,3 +1,7 @@
export function getLineHeight(fontSize: number) {
return (fontSize + 8) / fontSize;
}
// https://zhuanlan.zhihu.com/p/32746810
export default function getFontSizes(base: number) {
const fontSizes = new Array(10).fill(null).map((_, index) => {
@ -11,12 +15,8 @@ export default function getFontSizes(base: number) {
fontSizes[1] = base;
return fontSizes.map(size => {
const height = size + 8;
return {
size,
lineHeight: height / size,
};
});
return fontSizes.map(size => ({
size,
lineHeight: getLineHeight(size),
}));
}

View File

@ -48,7 +48,7 @@ const genRadius = (
}
return {
borderRadius: radiusBase > 16 ? 16 : radiusBase,
borderRadius: radiusBase,
borderRadiusXS: radiusXS,
borderRadiusSM: radiusSM,
borderRadiusLG: radiusLG,

View File

@ -0,0 +1,160 @@
import type { Theme } from '../_util/cssinjs';
import { useCacheToken } from '../_util/cssinjs';
import version from '../version';
import type { DesignTokenProviderProps } from './context';
import { defaultTheme, useDesignTokenInject } from './context';
import type { AliasToken, GlobalToken, MapToken, SeedToken } from './interface';
import defaultSeedToken from './themes/seed';
import formatToken from './util/alias';
import { computed } from 'vue';
import type { ComputedRef } from 'vue';
export const unitless: {
[key in keyof AliasToken]?: boolean;
} = {
lineHeight: true,
lineHeightSM: true,
lineHeightLG: true,
lineHeightHeading1: true,
lineHeightHeading2: true,
lineHeightHeading3: true,
lineHeightHeading4: true,
lineHeightHeading5: true,
opacityLoading: true,
fontWeightStrong: true,
zIndexPopupBase: true,
zIndexBase: true,
};
export const ignore: {
[key in keyof AliasToken]?: boolean;
} = {
size: true,
sizeSM: true,
sizeLG: true,
sizeMD: true,
sizeXS: true,
sizeXXS: true,
sizeMS: true,
sizeXL: true,
sizeXXL: true,
sizeUnit: true,
sizeStep: true,
motionBase: true,
motionUnit: true,
};
const preserve: {
[key in keyof AliasToken]?: boolean;
} = {
screenXS: true,
screenXSMin: true,
screenXSMax: true,
screenSM: true,
screenSMMin: true,
screenSMMax: true,
screenMD: true,
screenMDMin: true,
screenMDMax: true,
screenLG: true,
screenLGMin: true,
screenLGMax: true,
screenXL: true,
screenXLMin: true,
screenXLMax: true,
screenXXL: true,
screenXXLMin: true,
screenXXLMax: true,
screenXXXL: true,
screenXXXLMin: true,
};
export const getComputedToken = (
originToken: SeedToken,
overrideToken: DesignTokenProviderProps['components'] & {
override?: Partial<AliasToken>;
},
theme: Theme<any, any>,
) => {
const derivativeToken = theme.getDerivativeToken(originToken);
const { override, ...components } = overrideToken;
// Merge with override
let mergedDerivativeToken = {
...derivativeToken,
override,
};
// Format if needed
mergedDerivativeToken = formatToken(mergedDerivativeToken);
if (components) {
Object.entries(components).forEach(([key, value]) => {
const { theme: componentTheme, ...componentTokens } = value;
let mergedComponentToken = componentTokens;
if (componentTheme) {
mergedComponentToken = getComputedToken(
{
...mergedDerivativeToken,
...componentTokens,
},
{
override: componentTokens,
},
componentTheme,
);
}
mergedDerivativeToken[key] = mergedComponentToken;
});
}
return mergedDerivativeToken;
};
// ================================== Hook ==================================
export default function useToken(): [
ComputedRef<Theme<SeedToken, MapToken>>,
ComputedRef<GlobalToken>,
ComputedRef<string>,
ComputedRef<GlobalToken>,
ComputedRef<DesignTokenProviderProps['cssVar']>,
] {
const designToken = useDesignTokenInject();
const salt = computed(() => `${version}-${designToken.value.hashed || ''}`);
const mergedTheme = computed(() => designToken.value.theme || defaultTheme);
const cacheToken = useCacheToken<GlobalToken, SeedToken>(
mergedTheme,
computed(() => [defaultSeedToken, designToken.value.token]),
computed(() => {
return {
salt: salt.value,
override: designToken.value.override,
getComputedToken,
// formatToken will not be consumed after 1.15.0 with getComputedToken.
// But token will break if @ant-design/cssinjs is under 1.15.0 without it
formatToken,
cssVar: designToken.value.cssVar && {
prefix: designToken.value.cssVar.prefix,
key: designToken.value.cssVar.key,
unitless,
ignore,
preserve,
},
};
}),
);
// cacheToken [token, hashId, realToken]
return [
mergedTheme,
computed(() => cacheToken.value[2]),
computed(() => cacheToken.value[1]),
computed(() => cacheToken.value[0]),
computed(() => designToken.value.cssVar),
];
}

View File

@ -1,7 +1,7 @@
import { TinyColor } from '@ctrl/tinycolor';
import type { AliasToken, MapToken, OverrideToken, SeedToken } from '../interface';
import getAlphaColor from './getAlphaColor';
import seedToken from '../themes/seed';
import getAlphaColor from './getAlphaColor';
/** Raw merge of `@ant-design/cssinjs` token. Which need additional process */
type RawMergedToken = MapToken & OverrideToken & { override: Partial<AliasToken> };
@ -32,14 +32,18 @@ export default function formatToken(derivativeToken: RawMergedToken): AliasToken
const screenXXL = 1600;
const screenXXXL = 2000;
// Motion
if (mergedToken.motion === false) {
const fastDuration = '0s';
mergedToken.motionDurationFast = fastDuration;
mergedToken.motionDurationMid = fastDuration;
mergedToken.motionDurationSlow = fastDuration;
}
// Generate alias token
const aliasToken: AliasToken = {
...mergedToken,
colorLink: mergedToken.colorInfoText,
colorLinkHover: mergedToken.colorInfoHover,
colorLinkActive: mergedToken.colorInfoActive,
// ============== Background ============== //
colorFillContent: mergedToken.colorFillSecondary,
colorFillContentHover: mergedToken.colorFill,
@ -70,6 +74,9 @@ export default function formatToken(derivativeToken: RawMergedToken): AliasToken
// Font
fontSizeIcon: mergedToken.fontSizeSM,
// Line
lineWidthFocus: mergedToken.lineWidth * 4,
// Control
lineWidth: mergedToken.lineWidth,
controlOutlineWidth: mergedToken.lineWidth * 2,
@ -125,9 +132,9 @@ export default function formatToken(derivativeToken: RawMergedToken): AliasToken
marginXXL: mergedToken.sizeXXL,
boxShadow: `
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)
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),
@ -161,8 +168,7 @@ export default function formatToken(derivativeToken: RawMergedToken): AliasToken
screenXXXL,
screenXXXLMin: screenXXXL,
// FIXME: component box-shadow, should be removed
boxShadowPopoverArrow: '3px 3px 7px rgba(0, 0, 0, 0.1)',
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()},

View File

@ -0,0 +1,87 @@
import AbstractCalculator from './calculator';
const CALC_UNIT = 'CALC_UNIT';
function unit(value: string | number) {
if (typeof value === 'number') {
return `${value}${CALC_UNIT}`;
}
return value;
}
export default class CSSCalculator extends AbstractCalculator {
result = '';
lowPriority?: boolean;
constructor(num: number | string | AbstractCalculator) {
super();
if (num instanceof CSSCalculator) {
this.result = `(${num.result})`;
} else if (typeof num === 'number') {
this.result = unit(num);
} else if (typeof num === 'string') {
this.result = num;
}
}
add(num: number | string | AbstractCalculator): this {
if (num instanceof CSSCalculator) {
this.result = `${this.result} + ${num.getResult()}`;
} else if (typeof num === 'number' || typeof num === 'string') {
this.result = `${this.result} + ${unit(num)}`;
}
this.lowPriority = true;
return this;
}
sub(num: number | string | AbstractCalculator): this {
if (num instanceof CSSCalculator) {
this.result = `${this.result} - ${num.getResult()}`;
} else if (typeof num === 'number' || typeof num === 'string') {
this.result = `${this.result} - ${unit(num)}`;
}
this.lowPriority = true;
return this;
}
mul(num: number | string | AbstractCalculator): this {
if (this.lowPriority) {
this.result = `(${this.result})`;
}
if (num instanceof CSSCalculator) {
this.result = `${this.result} * ${num.getResult(true)}`;
} else if (typeof num === 'number' || typeof num === 'string') {
this.result = `${this.result} * ${num}`;
}
this.lowPriority = false;
return this;
}
div(num: number | string | AbstractCalculator): this {
if (this.lowPriority) {
this.result = `(${this.result})`;
}
if (num instanceof CSSCalculator) {
this.result = `${this.result} / ${num.getResult(true)}`;
} else if (typeof num === 'number' || typeof num === 'string') {
this.result = `${this.result} / ${num}`;
}
this.lowPriority = false;
return this;
}
getResult(force?: boolean): string {
return this.lowPriority || force ? `(${this.result})` : this.result;
}
equal(options?: { unit?: boolean }): string {
const { unit: cssUnit = true } = options || {};
const regexp = new RegExp(`${CALC_UNIT}`, 'g');
this.result = this.result.replace(regexp, cssUnit ? 'px' : '');
if (typeof this.lowPriority !== 'undefined') {
return `calc(${this.result})`;
}
return this.result;
}
}

View File

@ -0,0 +1,54 @@
import AbstractCalculator from './calculator';
export default class NumCalculator extends AbstractCalculator {
result = 0;
constructor(num: number | string | AbstractCalculator) {
super();
if (num instanceof NumCalculator) {
this.result = num.result;
} else if (typeof num === 'number') {
this.result = num;
}
}
add(num: number | string | AbstractCalculator): this {
if (num instanceof NumCalculator) {
this.result += num.result;
} else if (typeof num === 'number') {
this.result += num;
}
return this;
}
sub(num: number | string | AbstractCalculator): this {
if (num instanceof NumCalculator) {
this.result -= num.result;
} else if (typeof num === 'number') {
this.result -= num;
}
return this;
}
mul(num: number | string | AbstractCalculator): this {
if (num instanceof NumCalculator) {
this.result *= num.result;
} else if (typeof num === 'number') {
this.result *= num;
}
return this;
}
div(num: number | string | AbstractCalculator): this {
if (num instanceof NumCalculator) {
this.result /= num.result;
} else if (typeof num === 'number') {
this.result /= num;
}
return this;
}
equal(): number {
return this.result;
}
}

Some files were not shown because too many files have changed in this diff Show More