feat[cssvar]: Avatar & Badge support cssvar (#8327)

feat-4.3
Shuhari 2025-08-26 00:06:58 +08:00 committed by GitHub
parent 2d3f40aa3f
commit 437fdda197
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 428 additions and 190 deletions

View File

@ -12,6 +12,7 @@ import ResizeObserver from '../vc-resize-observer';
import eagerComputed from '../_util/eagerComputed'; import eagerComputed from '../_util/eagerComputed';
import useStyle from './style'; import useStyle from './style';
import { useAvatarInjectContext } from './AvatarContext'; import { useAvatarInjectContext } from './AvatarContext';
import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap; export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap;
@ -55,7 +56,8 @@ const Avatar = defineComponent({
const avatarNodeRef = shallowRef<HTMLElement>(null); const avatarNodeRef = shallowRef<HTMLElement>(null);
const { prefixCls } = useConfigInject('avatar', props); const { prefixCls } = useConfigInject('avatar', props);
const [wrapSSR, hashId] = useStyle(prefixCls); const rootCls = useCSSVarCls(prefixCls);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const avatarCtx = useAvatarInjectContext(); const avatarCtx = useAvatarInjectContext();
const size = computed(() => { const size = computed(() => {
return props.size === 'default' ? avatarCtx.size : props.size; return props.size === 'default' ? avatarCtx.size : props.size;
@ -146,6 +148,8 @@ const Avatar = defineComponent({
[`${pre}-${mergeShape}`]: true, [`${pre}-${mergeShape}`]: true,
[`${pre}-image`]: src && isImgExist.value, [`${pre}-image`]: src && isImgExist.value,
[`${pre}-icon`]: icon, [`${pre}-icon`]: icon,
[cssVarCls.value]: true,
[rootCls.value]: true,
[hashId.value]: true, [hashId.value]: true,
}; };
@ -175,7 +179,7 @@ const Avatar = defineComponent({
} else if (icon) { } else if (icon) {
childrenToRender = icon; childrenToRender = icon;
} else if (isMounted.value || scale.value !== 1) { } else if (isMounted.value || scale.value !== 1) {
const transformString = `scale(${scale.value}) translateX(-50%)`; const transformString = `scale(${scale.value})`;
const childrenStyle: CSSProperties = { const childrenStyle: CSSProperties = {
msTransform: transformString, msTransform: transformString,
WebkitTransform: transformString, WebkitTransform: transformString,

View File

@ -6,6 +6,7 @@ import type { PropType, ExtractPropTypes, CSSProperties } from 'vue';
import { computed, defineComponent, watchEffect } from 'vue'; import { computed, defineComponent, watchEffect } from 'vue';
import { flattenChildren, getPropsSlot } from '../_util/props-util'; import { flattenChildren, getPropsSlot } from '../_util/props-util';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
import useStyle from './style'; import useStyle from './style';
import { useAvatarProviderContext } from './AvatarContext'; import { useAvatarProviderContext } from './AvatarContext';
@ -36,7 +37,8 @@ const Group = defineComponent({
setup(props, { slots, attrs }) { setup(props, { slots, attrs }) {
const { prefixCls, direction } = useConfigInject('avatar', props); const { prefixCls, direction } = useConfigInject('avatar', props);
const groupPrefixCls = computed(() => `${prefixCls.value}-group`); const groupPrefixCls = computed(() => `${prefixCls.value}-group`);
const [wrapSSR, hashId] = useStyle(prefixCls); const rootCls = useCSSVarCls(prefixCls);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
watchEffect(() => { watchEffect(() => {
const context = { size: props.size, shape: props.shape }; const context = { size: props.size, shape: props.shape };
useAvatarProviderContext(context); useAvatarProviderContext(context);
@ -54,6 +56,8 @@ const Group = defineComponent({
[groupPrefixCls.value]: true, [groupPrefixCls.value]: true,
[`${groupPrefixCls.value}-rtl`]: direction.value === 'rtl', [`${groupPrefixCls.value}-rtl`]: direction.value === 'rtl',
[`${attrs.class}`]: !!attrs.class, [`${attrs.class}`]: !!attrs.class,
[cssVarCls.value]: true,
[rootCls.value]: true,
[hashId.value]: true, [hashId.value]: true,
}; };

View File

@ -1,12 +1,14 @@
import { unit } from '../../_util/cssinjs';
import type { CSSObject } from '../../_util/cssinjs'; import type { CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../style'; import { resetComponent } from '../../style';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
export interface ComponentToken { export interface ComponentToken {
/** /**
* @desc * @desc
* @descEN Background color of Avatar * @descEN Size of Avatar
*/ */
containerSize: number; containerSize: number;
/** /**
@ -51,10 +53,14 @@ export interface ComponentToken {
groupBorderColor: string; groupBorderColor: string;
} }
/**
* @desc Avatar Token
* @descEN Token for Avatar component
*/
type AvatarToken = FullToken<'Avatar'> & { type AvatarToken = FullToken<'Avatar'> & {
avatarBgColor: string;
avatarBg: string; avatarBg: string;
avatarColor: string; avatarColor: string;
avatarBgColor: string;
}; };
const genBaseStyle: GenerateStyle<AvatarToken> = token => { const genBaseStyle: GenerateStyle<AvatarToken> = token => {
@ -81,22 +87,12 @@ const genBaseStyle: GenerateStyle<AvatarToken> = token => {
const avatarSizeStyle = (size: number, fontSize: number, radius: number): CSSObject => ({ const avatarSizeStyle = (size: number, fontSize: number, radius: number): CSSObject => ({
width: size, width: size,
height: size, height: size,
lineHeight: `${size - lineWidth * 2}px`,
borderRadius: '50%', borderRadius: '50%',
[`&${componentCls}-square`]: { [`&${componentCls}-square`]: {
borderRadius: radius, borderRadius: radius,
}, },
[`${componentCls}-string`]: {
position: 'absolute',
left: {
_skip_check_: true,
value: '50%',
},
transformOrigin: '0 center',
},
[`&${componentCls}-icon`]: { [`&${componentCls}-icon`]: {
fontSize, fontSize,
[`> ${iconCls}`]: { [`> ${iconCls}`]: {
@ -109,16 +105,18 @@ const genBaseStyle: GenerateStyle<AvatarToken> = token => {
[componentCls]: { [componentCls]: {
...resetComponent(token), ...resetComponent(token),
position: 'relative', position: 'relative',
display: 'inline-block', display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden', overflow: 'hidden',
color: avatarColor, color: avatarColor,
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textAlign: 'center', textAlign: 'center',
verticalAlign: 'middle', verticalAlign: 'middle',
background: avatarBg, background: avatarBg,
border: `${lineWidth}px ${lineType} transparent`, border: `${unit(lineWidth)} ${lineType} transparent`,
[`&-image`]: { '&-image': {
background: 'transparent', background: 'transparent',
}, },
@ -128,11 +126,11 @@ const genBaseStyle: GenerateStyle<AvatarToken> = token => {
...avatarSizeStyle(containerSize, textFontSize, borderRadius), ...avatarSizeStyle(containerSize, textFontSize, borderRadius),
[`&-lg`]: { '&-lg': {
...avatarSizeStyle(containerSizeLG, textFontSizeLG, borderRadiusLG), ...avatarSizeStyle(containerSizeLG, textFontSizeLG, borderRadiusLG),
}, },
[`&-sm`]: { '&-sm': {
...avatarSizeStyle(containerSizeSM, textFontSizeSM, borderRadiusSM), ...avatarSizeStyle(containerSizeSM, textFontSizeSM, borderRadiusSM),
}, },
@ -153,11 +151,11 @@ const genGroupStyle: GenerateStyle<AvatarToken> = token => {
[`${componentCls}-group`]: { [`${componentCls}-group`]: {
display: 'inline-flex', display: 'inline-flex',
[`${componentCls}`]: { [componentCls]: {
borderColor: groupBorderColor, borderColor: groupBorderColor,
}, },
[`> *:not(:first-child)`]: { '> *:not(:first-child)': {
marginInlineStart: groupOverlapping, marginInlineStart: groupOverlapping,
}, },
}, },
@ -169,7 +167,33 @@ const genGroupStyle: GenerateStyle<AvatarToken> = token => {
}; };
}; };
export default genComponentStyleHook( export const prepareComponentToken: GetDefaultToken<'Avatar'> = token => {
const {
controlHeight,
controlHeightLG,
controlHeightSM,
fontSize,
fontSizeLG,
fontSizeXL,
fontSizeHeading3,
marginXS,
marginXXS,
colorBorderBg,
} = token;
return {
containerSize: controlHeight,
containerSizeLG: controlHeightLG,
containerSizeSM: controlHeightSM,
textFontSize: Math.round((fontSizeLG + fontSizeXL) / 2),
textFontSizeLG: fontSizeHeading3,
textFontSizeSM: fontSize,
groupSpace: marginXXS,
groupOverlapping: -marginXS,
groupBorderColor: colorBorderBg,
};
};
export default genStyleHooks(
'Avatar', 'Avatar',
token => { token => {
const { colorTextLightSolid, colorTextPlaceholder } = token; const { colorTextLightSolid, colorTextPlaceholder } = token;
@ -179,33 +203,5 @@ export default genComponentStyleHook(
}); });
return [genBaseStyle(avatarToken), genGroupStyle(avatarToken)]; return [genBaseStyle(avatarToken), genGroupStyle(avatarToken)];
}, },
token => { prepareComponentToken,
const {
controlHeight,
controlHeightLG,
controlHeightSM,
fontSize,
fontSizeLG,
fontSizeXL,
fontSizeHeading3,
marginXS,
marginXXS,
colorBorderBg,
} = token;
return {
containerSize: controlHeight,
containerSizeLG: controlHeightLG,
containerSizeSM: controlHeightSM,
textFontSize: Math.round((fontSizeLG + fontSizeXL) / 2),
textFontSizeLG: fontSizeHeading3,
textFontSizeSM: fontSize,
groupSpace: marginXXS,
groupOverlapping: -marginXS,
groupBorderColor: colorBorderBg,
};
},
); );

View File

@ -9,6 +9,7 @@ import { defineComponent, computed, ref, watch, Transition } from 'vue';
import Ribbon from './Ribbon'; import Ribbon from './Ribbon';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import isNumeric from '../_util/isNumeric'; import isNumeric from '../_util/isNumeric';
import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
import useStyle from './style'; import useStyle from './style';
import type { PresetColorKey } from '../theme/interface'; import type { PresetColorKey } from '../theme/interface';
import type { LiteralUnion, CustomSlotsType } from '../_util/type'; import type { LiteralUnion, CustomSlotsType } from '../_util/type';
@ -49,7 +50,8 @@ export default defineComponent({
}>, }>,
setup(props, { slots, attrs }) { setup(props, { slots, attrs }) {
const { prefixCls, direction } = useConfigInject('badge', props); const { prefixCls, direction } = useConfigInject('badge', props);
const [wrapSSR, hashId] = useStyle(prefixCls); const rootCls = useCSSVarCls(prefixCls);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
// ================================ Misc ================================ // ================================ Misc ================================
const numberedDisplayCount = computed(() => { const numberedDisplayCount = computed(() => {
@ -189,6 +191,8 @@ export default defineComponent({
[`${pre}-rtl`]: direction.value === 'rtl', [`${pre}-rtl`]: direction.value === 'rtl',
}, },
attrs.class, attrs.class,
cssVarCls.value,
rootCls.value,
hashId.value, hashId.value,
); );

View File

@ -1,6 +1,7 @@
import type { CustomSlotsType, LiteralUnion } from '../_util/type'; import type { CustomSlotsType, LiteralUnion } from '../_util/type';
import type { PresetColorType } from '../_util/colors'; import type { PresetColorType } from '../_util/colors';
import useStyle from './style'; import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
import useStyle from './style/ribbon';
import { isPresetColor } from '../_util/colors'; import { isPresetColor } from '../_util/colors';
import type { CSSProperties, PropType, ExtractPropTypes } from 'vue'; import type { CSSProperties, PropType, ExtractPropTypes } from 'vue';
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue';
@ -27,7 +28,8 @@ export default defineComponent({
}>, }>,
setup(props, { attrs, slots }) { setup(props, { attrs, slots }) {
const { prefixCls, direction } = useConfigInject('ribbon', props); const { prefixCls, direction } = useConfigInject('ribbon', props);
const [wrapSSR, hashId] = useStyle(prefixCls); const rootCls = useCSSVarCls(prefixCls);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const colorInPreset = computed(() => isPresetColor(props.color, false)); const colorInPreset = computed(() => isPresetColor(props.color, false));
const ribbonCls = computed(() => [ const ribbonCls = computed(() => [
prefixCls.value, prefixCls.value,
@ -46,7 +48,10 @@ export default defineComponent({
cornerColorStyle.color = props.color; cornerColorStyle.color = props.color;
} }
return wrapSSR( return wrapSSR(
<div class={`${prefixCls.value}-wrapper ${hashId.value}`} {...restAttrs}> <div
class={`${prefixCls.value}-wrapper ${cssVarCls.value} ${rootCls.value} ${hashId.value}`}
{...restAttrs}
>
{slots.default?.()} {slots.default?.()}
<div <div
class={[ribbonCls.value, className, hashId.value]} class={[ribbonCls.value, className, hashId.value]}

View File

@ -1,29 +1,108 @@
import type { CSSObject } from '../../_util/cssinjs'; import { Keyframes, unit } from '../../_util/cssinjs';
import { Keyframes } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genPresetColor, resetComponent } from '../../style';
export interface ComponentToken {} import { resetComponent } from '../../style';
import type { FullToken, GenerateStyle, GenStyleFn, GetDefaultToken } from '../../theme/internal';
import { genPresetColor, genStyleHooks, mergeToken } from '../../theme/internal';
interface BadgeToken extends FullToken<'Badge'> { /** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
// Component token here
/**
* @desc z-index
* @descEN z-index of badge
*/
indicatorZIndex: number | string;
/**
* @desc
* @descEN Height of badge
*/
indicatorHeight: number | string;
/**
* @desc
* @descEN Height of small badge
*/
indicatorHeightSM: number | string;
/**
* @desc
* @descEN Size of dot badge
*/
dotSize: number;
/**
* @desc
* @descEN Font size of badge text
*/
textFontSize: number;
/**
* @desc
* @descEN Font size of small badge text
*/
textFontSizeSM: number;
/**
* @desc
* @descEN Font weight of badge text
*/
textFontWeight: number | string;
/**
* @desc
* @descEN Size of status badge
*/
statusSize: number;
}
/**
* @desc Badge Token
* @descEN Token for Badge component
*/
export interface BadgeToken extends FullToken<'Badge'> {
/**
* @desc
* @descEN Font height of badge
*/
badgeFontHeight: number; badgeFontHeight: number;
badgeZIndex: number | string; /**
badgeHeight: number; * @desc
badgeHeightSm: number; * @descEN Text color of badge
*/
badgeTextColor: string; badgeTextColor: string;
badgeFontWeight: string; /**
badgeFontSize: number; * @desc
* @descEN Color of badge
*/
badgeColor: string; badgeColor: string;
/**
* @desc
* @descEN Hover color of badge
*/
badgeColorHover: string; badgeColorHover: string;
badgeDotSize: number; /**
badgeFontSizeSm: number; * @desc
badgeStatusSize: number; * @descEN Shadow size of badge
*/
badgeShadowSize: number; badgeShadowSize: number;
/**
* @desc
* @descEN Shadow color of badge
*/
badgeShadowColor: string; badgeShadowColor: string;
/**
* @desc
* @descEN Processing duration of badge
*/
badgeProcessingDuration: string; badgeProcessingDuration: string;
/**
* @desc
* @descEN Ribbon offset of badge
*/
badgeRibbonOffset: number; badgeRibbonOffset: number;
/**
* @desc
* @descEN Ribbon corner transform of badge
*/
badgeRibbonCornerTransform: string; badgeRibbonCornerTransform: string;
/**
* @desc
* @descEN Ribbon corner filter of badge
*/
badgeRibbonCornerFilter: string; badgeRibbonCornerFilter: string;
} }
@ -46,10 +125,12 @@ const antNoWrapperZoomBadgeIn = new Keyframes('antNoWrapperZoomBadgeIn', {
'0%': { transform: 'scale(0)', opacity: 0 }, '0%': { transform: 'scale(0)', opacity: 0 },
'100%': { transform: 'scale(1)' }, '100%': { transform: 'scale(1)' },
}); });
const antNoWrapperZoomBadgeOut = new Keyframes('antNoWrapperZoomBadgeOut', { const antNoWrapperZoomBadgeOut = new Keyframes('antNoWrapperZoomBadgeOut', {
'0%': { transform: 'scale(1)' }, '0%': { transform: 'scale(1)' },
'100%': { transform: 'scale(0)', opacity: 0 }, '100%': { transform: 'scale(0)', opacity: 0 },
}); });
const antBadgeLoadingCircle = new Keyframes('antBadgeLoadingCircle', { const antBadgeLoadingCircle = new Keyframes('antBadgeLoadingCircle', {
'0%': { transformOrigin: '50%' }, '0%': { transformOrigin: '50%' },
'100%': { '100%': {
@ -58,22 +139,23 @@ const antBadgeLoadingCircle = new Keyframes('antBadgeLoadingCircle', {
}, },
}); });
const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSObject => { const genSharedBadgeStyle: GenerateStyle<BadgeToken> = token => {
const { const {
componentCls, componentCls,
iconCls, iconCls,
antCls, antCls,
badgeFontHeight,
badgeShadowSize, badgeShadowSize,
badgeHeightSm, textFontSize,
motionDurationSlow, textFontSizeSM,
badgeStatusSize, statusSize,
dotSize,
textFontWeight,
indicatorHeight,
indicatorHeightSM,
marginXS, marginXS,
badgeRibbonOffset, calc,
} = token; } = token;
const numberPrefixCls = `${antCls}-scroll-number`; const numberPrefixCls = `${antCls}-scroll-number`;
const ribbonPrefixCls = `${antCls}-ribbon`;
const ribbonWrapperPrefixCls = `${antCls}-ribbon-wrapper`;
const colorPreset = genPresetColor(token, (colorKey, { darkColor }) => ({ const colorPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`&${componentCls} ${componentCls}-color-${colorKey}`]: { [`&${componentCls} ${componentCls}-color-${colorKey}`]: {
@ -81,13 +163,9 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
[`&:not(${componentCls}-count)`]: { [`&:not(${componentCls}-count)`]: {
color: darkColor, color: darkColor,
}, },
}, 'a:hover &': {
}));
const statusRibbonPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`&${ribbonPrefixCls}-color-${colorKey}`]: {
background: darkColor, background: darkColor,
color: darkColor, },
}, },
})); }));
@ -100,18 +178,20 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
lineHeight: 1, lineHeight: 1,
[`${componentCls}-count`]: { [`${componentCls}-count`]: {
zIndex: token.badgeZIndex, display: 'inline-flex',
minWidth: token.badgeHeight, justifyContent: 'center',
height: token.badgeHeight, zIndex: token.indicatorZIndex,
minWidth: indicatorHeight,
height: indicatorHeight,
color: token.badgeTextColor, color: token.badgeTextColor,
fontWeight: token.badgeFontWeight, fontWeight: textFontWeight,
fontSize: token.badgeFontSize, fontSize: textFontSize,
lineHeight: `${token.badgeHeight}px`, lineHeight: unit(indicatorHeight),
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textAlign: 'center', textAlign: 'center',
background: token.badgeColor, background: token.badgeColor,
borderRadius: token.badgeHeight / 2, borderRadius: calc(indicatorHeight).div(2).equal(),
boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`, boxShadow: `0 0 0 ${unit(badgeShadowSize)} ${token.badgeShadowColor}`,
transition: `background ${token.motionDurationMid}`, transition: `background ${token.motionDurationMid}`,
a: { a: {
@ -126,28 +206,29 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
}, },
}, },
[`${componentCls}-count-sm`]: { [`${componentCls}-count-sm`]: {
minWidth: badgeHeightSm, minWidth: indicatorHeightSM,
height: badgeHeightSm, height: indicatorHeightSM,
fontSize: token.badgeFontSizeSm, fontSize: textFontSizeSM,
lineHeight: `${badgeHeightSm}px`, lineHeight: unit(indicatorHeightSM),
borderRadius: badgeHeightSm / 2, borderRadius: calc(indicatorHeightSM).div(2).equal(),
}, },
[`${componentCls}-multiple-words`]: { [`${componentCls}-multiple-words`]: {
padding: `0 ${token.paddingXS}px`, padding: `0 ${unit(token.paddingXS)}`,
bdi: {
unicodeBidi: 'plaintext',
},
}, },
[`${componentCls}-dot`]: { [`${componentCls}-dot`]: {
zIndex: token.badgeZIndex, zIndex: token.indicatorZIndex,
width: token.badgeDotSize, width: dotSize,
minWidth: token.badgeDotSize, minWidth: dotSize,
height: token.badgeDotSize, height: dotSize,
background: token.badgeColor, background: token.badgeColor,
borderRadius: '100%', borderRadius: '100%',
boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`, boxShadow: `0 0 0 ${unit(badgeShadowSize)} ${token.badgeShadowColor}`,
},
[`${componentCls}-dot${numberPrefixCls}`]: {
transition: `background ${motionDurationSlow}`,
}, },
[`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: { [`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: {
position: 'absolute', position: 'absolute',
@ -170,8 +251,8 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
position: 'relative', position: 'relative',
top: -1, // Magic number, but seems better experience top: -1, // Magic number, but seems better experience
display: 'inline-block', display: 'inline-block',
width: badgeStatusSize, width: statusSize,
height: badgeStatusSize, height: statusSize,
verticalAlign: 'middle', verticalAlign: 'middle',
borderRadius: '50%', borderRadius: '50%',
}, },
@ -181,8 +262,9 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
}, },
[`${componentCls}-status-processing`]: { [`${componentCls}-status-processing`]: {
overflow: 'visible', overflow: 'visible',
color: token.colorPrimary, color: token.colorInfo,
backgroundColor: token.colorPrimary, backgroundColor: token.colorInfo,
borderColor: 'currentcolor',
'&::after': { '&::after': {
position: 'absolute', position: 'absolute',
@ -256,118 +338,56 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
transformOrigin: '50% 50%', transformOrigin: '50% 50%',
}, },
}, },
[`${numberPrefixCls}`]: { [numberPrefixCls]: {
overflow: 'hidden', overflow: 'hidden',
transition: `all ${token.motionDurationMid} ${token.motionEaseOutBack}`,
[`${numberPrefixCls}-only`]: { [`${numberPrefixCls}-only`]: {
position: 'relative', position: 'relative',
display: 'inline-block', display: 'inline-block',
height: token.badgeHeight, height: indicatorHeight,
transition: `all ${token.motionDurationSlow} ${token.motionEaseOutBack}`, transition: `all ${token.motionDurationSlow} ${token.motionEaseOutBack}`,
WebkitTransformStyle: 'preserve-3d', WebkitTransformStyle: 'preserve-3d',
WebkitBackfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden',
[`> p${numberPrefixCls}-only-unit`]: { [`> p${numberPrefixCls}-only-unit`]: {
height: token.badgeHeight, height: indicatorHeight,
margin: 0, margin: 0,
WebkitTransformStyle: 'preserve-3d', WebkitTransformStyle: 'preserve-3d',
WebkitBackfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden',
}, },
}, },
[`${numberPrefixCls}-symbol`]: { verticalAlign: 'top' }, [`${numberPrefixCls}-symbol`]: {
verticalAlign: 'top',
},
}, },
// ====================== RTL ======================= // ====================== RTL =======================
'&-rtl': { '&-rtl': {
direction: 'rtl', direction: 'rtl',
[`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: { [`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: {
transform: 'translate(-50%, -50%)', transform: 'translate(-50%, -50%)',
}, },
}, },
}, },
[`${ribbonWrapperPrefixCls}`]: { position: 'relative' },
[`${ribbonPrefixCls}`]: {
...resetComponent(token),
position: 'absolute',
top: marginXS,
padding: `0 ${token.paddingXS}px`,
color: token.colorPrimary,
lineHeight: `${badgeFontHeight}px`,
whiteSpace: 'nowrap',
backgroundColor: token.colorPrimary,
borderRadius: token.borderRadiusSM,
[`${ribbonPrefixCls}-text`]: { color: token.colorTextLightSolid },
[`${ribbonPrefixCls}-corner`]: {
position: 'absolute',
top: '100%',
width: badgeRibbonOffset,
height: badgeRibbonOffset,
color: 'currentcolor',
border: `${badgeRibbonOffset / 2}px solid`,
transform: token.badgeRibbonCornerTransform,
transformOrigin: 'top',
filter: token.badgeRibbonCornerFilter,
},
...statusRibbonPreset,
[`&${ribbonPrefixCls}-placement-end`]: {
insetInlineEnd: -badgeRibbonOffset,
borderEndEndRadius: 0,
[`${ribbonPrefixCls}-corner`]: {
insetInlineEnd: 0,
borderInlineEndColor: 'transparent',
borderBlockEndColor: 'transparent',
},
},
[`&${ribbonPrefixCls}-placement-start`]: {
insetInlineStart: -badgeRibbonOffset,
borderEndStartRadius: 0,
[`${ribbonPrefixCls}-corner`]: {
insetInlineStart: 0,
borderBlockEndColor: 'transparent',
borderInlineStartColor: 'transparent',
},
},
// ====================== RTL =======================
'&-rtl': {
direction: 'rtl',
},
},
}; };
}; };
// ============================== Export ============================== // ============================== Export ==============================
export default genComponentStyleHook('Badge', token => { export const prepareToken: (token: Parameters<GenStyleFn<'Badge'>>[0]) => BadgeToken = token => {
const { fontSize, lineHeight, fontSizeSM, lineWidth, marginXS, colorBorderBg } = token; const { fontHeight, lineWidth, marginXS, colorBorderBg } = token;
const badgeFontHeight = Math.round(fontSize * lineHeight); const badgeFontHeight = fontHeight;
const badgeShadowSize = lineWidth; const badgeShadowSize = lineWidth;
const badgeZIndex = 'auto'; const badgeTextColor = token.colorTextLightSolid;
const badgeHeight = badgeFontHeight - 2 * badgeShadowSize;
const badgeTextColor = token.colorBgContainer;
const badgeFontWeight = 'normal';
const badgeFontSize = fontSizeSM;
const badgeColor = token.colorError; const badgeColor = token.colorError;
const badgeColorHover = token.colorErrorHover; const badgeColorHover = token.colorErrorHover;
const badgeHeightSm = fontSize;
const badgeDotSize = fontSizeSM / 2;
const badgeFontSizeSm = fontSizeSM;
const badgeStatusSize = fontSizeSM / 2;
const badgeToken = mergeToken<BadgeToken>(token, { const badgeToken = mergeToken<BadgeToken>(token, {
badgeFontHeight, badgeFontHeight,
badgeShadowSize, badgeShadowSize,
badgeZIndex,
badgeHeight,
badgeTextColor, badgeTextColor,
badgeFontWeight,
badgeFontSize,
badgeColor, badgeColor,
badgeColorHover, badgeColorHover,
badgeShadowColor: colorBorderBg, badgeShadowColor: colorBorderBg,
badgeHeightSm,
badgeDotSize,
badgeFontSizeSm,
badgeStatusSize,
badgeProcessingDuration: '1.2s', badgeProcessingDuration: '1.2s',
badgeRibbonOffset: marginXS, badgeRibbonOffset: marginXS,
@ -376,5 +396,28 @@ export default genComponentStyleHook('Badge', token => {
badgeRibbonCornerFilter: `brightness(75%)`, badgeRibbonCornerFilter: `brightness(75%)`,
}); });
return [genSharedBadgeStyle(badgeToken)]; return badgeToken;
}); };
export const prepareComponentToken: GetDefaultToken<'Badge'> = token => {
const { fontSize, lineHeight, fontSizeSM, lineWidth } = token;
return {
indicatorZIndex: 'auto',
indicatorHeight: Math.round(fontSize * lineHeight) - 2 * lineWidth,
indicatorHeightSM: fontSize,
dotSize: fontSizeSM / 2,
textFontSize: fontSizeSM,
textFontSizeSM: fontSizeSM,
textFontWeight: 'normal',
statusSize: fontSizeSM / 2,
};
};
export default genStyleHooks(
'Badge',
token => {
const badgeToken = prepareToken(token);
return genSharedBadgeStyle(badgeToken);
},
prepareComponentToken,
);

View File

@ -0,0 +1,86 @@
import { unit } from '../../_util/cssinjs';
import { prepareComponentToken, prepareToken } from '.';
import type { BadgeToken } from '.';
import { resetComponent } from '../../style';
import type { GenerateStyle } from '../../theme/internal';
import { genPresetColor, genStyleHooks } from '../../theme/internal';
// ============================== Ribbon ==============================
const genRibbonStyle: GenerateStyle<BadgeToken> = token => {
const { antCls, badgeFontHeight, marginXS, badgeRibbonOffset, calc } = token;
const ribbonPrefixCls = `${antCls}-ribbon`;
const ribbonWrapperPrefixCls = `${antCls}-ribbon-wrapper`;
const statusRibbonPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`&${ribbonPrefixCls}-color-${colorKey}`]: {
background: darkColor,
color: darkColor,
},
}));
return {
[ribbonWrapperPrefixCls]: {
position: 'relative',
},
[ribbonPrefixCls]: {
...resetComponent(token),
position: 'absolute',
top: marginXS,
padding: `0 ${unit(token.paddingXS)}`,
color: token.colorPrimary,
lineHeight: unit(badgeFontHeight),
whiteSpace: 'nowrap',
backgroundColor: token.colorPrimary,
borderRadius: token.borderRadiusSM,
[`${ribbonPrefixCls}-text`]: {
color: token.badgeTextColor,
},
[`${ribbonPrefixCls}-corner`]: {
position: 'absolute',
top: '100%',
width: badgeRibbonOffset,
height: badgeRibbonOffset,
color: 'currentcolor',
border: `${unit(calc(badgeRibbonOffset).div(2).equal())} solid`,
transform: token.badgeRibbonCornerTransform,
transformOrigin: 'top',
filter: token.badgeRibbonCornerFilter,
},
...statusRibbonPreset,
[`&${ribbonPrefixCls}-placement-end`]: {
insetInlineEnd: calc(badgeRibbonOffset).mul(-1).equal(),
borderEndEndRadius: 0,
[`${ribbonPrefixCls}-corner`]: {
insetInlineEnd: 0,
borderInlineEndColor: 'transparent',
borderBlockEndColor: 'transparent',
},
},
[`&${ribbonPrefixCls}-placement-start`]: {
insetInlineStart: calc(badgeRibbonOffset).mul(-1).equal(),
borderEndStartRadius: 0,
[`${ribbonPrefixCls}-corner`]: {
insetInlineStart: 0,
borderBlockEndColor: 'transparent',
borderInlineStartColor: 'transparent',
},
},
// ====================== RTL =======================
'&-rtl': {
direction: 'rtl',
},
},
};
};
// ============================== Export ==============================
export default genStyleHooks(
['Badge', 'Ribbon'],
token => {
const badgeToken = prepareToken(token);
return genRibbonStyle(badgeToken);
},
prepareComponentToken,
);

View File

@ -0,0 +1,36 @@
import type {
GlobalToken as GlobalTokenTypeUtil,
OverrideTokenMap as OverrideTokenTypeUtil,
FullToken as FullTokenTypeUtil,
GetDefaultToken as GetDefaultTokenTypeUtil,
GenStyleFn as GenStyleFnTypeUtil,
TokenMapKey,
} from './interface';
import type { AliasToken } from '../alias';
import type { ComponentTokenMap } from '../components';
/** Final token which contains the components level override */
export type GlobalToken = GlobalTokenTypeUtil<ComponentTokenMap, AliasToken>;
export type OverrideToken = OverrideTokenTypeUtil<ComponentTokenMap, AliasToken>;
export type OverrideComponent = TokenMapKey<ComponentTokenMap>;
export type FullToken<C extends TokenMapKey<ComponentTokenMap>> = FullTokenTypeUtil<
ComponentTokenMap,
AliasToken,
C
>;
export type GetDefaultToken<C extends TokenMapKey<ComponentTokenMap>> = GetDefaultTokenTypeUtil<
ComponentTokenMap,
AliasToken,
C
>;
export type GenStyleFn<C extends TokenMapKey<ComponentTokenMap>> = GenStyleFnTypeUtil<
ComponentTokenMap,
AliasToken,
C
>;

View File

@ -0,0 +1,56 @@
import type { CSSInterpolation, TokenType } from '../../../_util/cssinjs';
import type { StyleInfo, TokenWithCommonCls } from '../../util/genComponentStyleHook';
/** Override the some definition of the @ant-design/cssinjs-utils https://github.com/ant-design/cssinjs-utils */
export type TokenMap = Record<PropertyKey, any>;
export type TokenMapKey<CompTokenMap extends TokenMap> = Extract<keyof CompTokenMap, string>;
export type GlobalToken<CompTokenMap extends TokenMap, AliasToken extends TokenType> = AliasToken &
CompTokenMap;
export type OverrideTokenMap<CompTokenMap extends TokenMap, AliasToken extends TokenType> = {
[key in keyof CompTokenMap]: Partial<CompTokenMap[key]> & Partial<AliasToken>;
};
export type GlobalTokenWithComponent<
CompTokenMap extends TokenMap,
AliasToken extends TokenType,
C extends TokenMapKey<CompTokenMap>,
> = GlobalToken<CompTokenMap, AliasToken> & CompTokenMap[C];
export type ComponentToken<
CompTokenMap extends TokenMap,
AliasToken extends TokenType,
C extends TokenMapKey<CompTokenMap>,
> = Exclude<OverrideTokenMap<CompTokenMap, AliasToken>[C], undefined>;
export type ComponentTokenKey<
CompTokenMap extends TokenMap,
AliasToken extends TokenType,
C extends TokenMapKey<CompTokenMap>,
> = keyof ComponentToken<CompTokenMap, AliasToken, C>;
export type FullToken<
CompTokenMap extends TokenMap,
AliasToken extends TokenType,
C extends TokenMapKey<CompTokenMap>,
> = TokenWithCommonCls<GlobalTokenWithComponent<CompTokenMap, AliasToken, C>>;
export type GenStyleFn<
CompTokenMap extends TokenMap,
AliasToken extends TokenType,
C extends TokenMapKey<CompTokenMap>,
> = (token: FullToken<CompTokenMap, AliasToken, C>, info: StyleInfo) => CSSInterpolation;
export type GetDefaultTokenFn<
CompTokenMap extends TokenMap,
AliasToken extends TokenType,
C extends TokenMapKey<CompTokenMap>,
> = (token: AliasToken & Partial<CompTokenMap[C]>) => CompTokenMap[C];
export type GetDefaultToken<
CompTokenMap extends TokenMap,
AliasToken extends TokenType,
C extends TokenMapKey<CompTokenMap>,
> = null | CompTokenMap[C] | GetDefaultTokenFn<CompTokenMap, AliasToken, C>;

View File

@ -6,6 +6,8 @@ import type { SeedToken } from './seeds';
import type { VueNode } from '../..//_util/type'; import type { VueNode } from '../..//_util/type';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
export type { FullToken, OverrideComponent, GetDefaultToken, GenStyleFn } from './cssinjs-utils';
export type MappingAlgorithm = DerivativeFunc<SeedToken, MapToken>; export type MappingAlgorithm = DerivativeFunc<SeedToken, MapToken>;
export type OverrideToken = { export type OverrideToken = {

View File

@ -7,6 +7,7 @@ import type {
PresetColorType, PresetColorType,
SeedToken, SeedToken,
UseComponentStyleResult, UseComponentStyleResult,
GenStyleFn,
} from './interface'; } from './interface';
import { PresetColors } from './interface'; import { PresetColors } from './interface';
import useToken from './useToken'; import useToken from './useToken';
@ -48,4 +49,5 @@ export type {
SeedToken, SeedToken,
UseComponentStyleResult, UseComponentStyleResult,
GetDefaultToken, GetDefaultToken,
GenStyleFn,
}; };