From 9c35fc5ad47c7af8a2416611ff43cb6bbef22d1d Mon Sep 17 00:00:00 2001 From: nishu Date: Mon, 28 Apr 2025 19:00:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20QIANXING1-787=20Avatar=20&=20Badge?= =?UTF-8?q?=20=E7=BB=84=E4=BB=B6=E6=94=AF=E6=8C=81=20css=20var?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/avatar/Avatar.tsx | 6 +- components/avatar/style/index.ts | 100 +++--- components/badge/Badge.tsx | 6 +- components/badge/Ribbon.tsx | 11 +- components/badge/style/index.ts | 305 ++++++++++-------- components/badge/style/ribbon.ts | 86 +++++ .../theme/interface/cssinjs-utils/index.ts | 36 +++ .../interface/cssinjs-utils/interface.ts | 56 ++++ components/theme/interface/index.ts | 2 + components/theme/internal.ts | 2 + 10 files changed, 422 insertions(+), 188 deletions(-) create mode 100644 components/badge/style/ribbon.ts create mode 100644 components/theme/interface/cssinjs-utils/index.ts create mode 100644 components/theme/interface/cssinjs-utils/interface.ts diff --git a/components/avatar/Avatar.tsx b/components/avatar/Avatar.tsx index 5bd4f17e0e..89ea6adf37 100644 --- a/components/avatar/Avatar.tsx +++ b/components/avatar/Avatar.tsx @@ -12,6 +12,7 @@ import ResizeObserver from '../vc-resize-observer'; import eagerComputed from '../_util/eagerComputed'; import useStyle from './style'; import { useAvatarInjectContext } from './AvatarContext'; +import useCSSVarCls from '../config-provider/hooks/useCssVarCls'; export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap; @@ -55,7 +56,8 @@ const Avatar = defineComponent({ const avatarNodeRef = shallowRef(null); 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 size = computed(() => { return props.size === 'default' ? avatarCtx.size : props.size; @@ -146,6 +148,8 @@ const Avatar = defineComponent({ [`${pre}-${mergeShape}`]: true, [`${pre}-image`]: src && isImgExist.value, [`${pre}-icon`]: icon, + [cssVarCls.value]: true, + [rootCls.value]: true, [hashId.value]: true, }; diff --git a/components/avatar/style/index.ts b/components/avatar/style/index.ts index 6d8201add1..5bcd696853 100644 --- a/components/avatar/style/index.ts +++ b/components/avatar/style/index.ts @@ -1,12 +1,14 @@ +import { unit } 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 type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; +import { genStyleHooks, mergeToken } from '../../theme/internal'; export interface ComponentToken { /** - * @desc 头像背景色 - * @descEN Background color of Avatar + * @desc 头像尺寸 + * @descEN Size of Avatar */ containerSize: number; /** @@ -51,10 +53,14 @@ export interface ComponentToken { groupBorderColor: string; } +/** + * @desc Avatar 组件的 Token + * @descEN Token for Avatar component + */ type AvatarToken = FullToken<'Avatar'> & { + avatarBgColor: string; avatarBg: string; avatarColor: string; - avatarBgColor: string; }; const genBaseStyle: GenerateStyle = token => { @@ -81,22 +87,12 @@ const genBaseStyle: GenerateStyle = token => { const avatarSizeStyle = (size: number, fontSize: number, radius: number): CSSObject => ({ width: size, height: size, - lineHeight: `${size - lineWidth * 2}px`, borderRadius: '50%', [`&${componentCls}-square`]: { borderRadius: radius, }, - [`${componentCls}-string`]: { - position: 'absolute', - left: { - _skip_check_: true, - value: '50%', - }, - transformOrigin: '0 center', - }, - [`&${componentCls}-icon`]: { fontSize, [`> ${iconCls}`]: { @@ -109,16 +105,18 @@ const genBaseStyle: GenerateStyle = token => { [componentCls]: { ...resetComponent(token), position: 'relative', - display: 'inline-block', + display: 'inline-flex', + justifyContent: 'center', + alignItems: 'center', overflow: 'hidden', color: avatarColor, whiteSpace: 'nowrap', textAlign: 'center', verticalAlign: 'middle', background: avatarBg, - border: `${lineWidth}px ${lineType} transparent`, + border: `${unit(lineWidth)} ${lineType} transparent`, - [`&-image`]: { + '&-image': { background: 'transparent', }, @@ -128,11 +126,11 @@ const genBaseStyle: GenerateStyle = token => { ...avatarSizeStyle(containerSize, textFontSize, borderRadius), - [`&-lg`]: { + '&-lg': { ...avatarSizeStyle(containerSizeLG, textFontSizeLG, borderRadiusLG), }, - [`&-sm`]: { + '&-sm': { ...avatarSizeStyle(containerSizeSM, textFontSizeSM, borderRadiusSM), }, @@ -153,11 +151,11 @@ const genGroupStyle: GenerateStyle = token => { [`${componentCls}-group`]: { display: 'inline-flex', - [`${componentCls}`]: { + [componentCls]: { borderColor: groupBorderColor, }, - [`> *:not(:first-child)`]: { + '> *:not(:first-child)': { marginInlineStart: groupOverlapping, }, }, @@ -169,7 +167,33 @@ const genGroupStyle: GenerateStyle = 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', token => { const { colorTextLightSolid, colorTextPlaceholder } = token; @@ -179,33 +203,5 @@ export default genComponentStyleHook( }); return [genBaseStyle(avatarToken), genGroupStyle(avatarToken)]; }, - 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, - }; - }, + prepareComponentToken, ); diff --git a/components/badge/Badge.tsx b/components/badge/Badge.tsx index a0363732f4..a2577564b2 100644 --- a/components/badge/Badge.tsx +++ b/components/badge/Badge.tsx @@ -9,6 +9,7 @@ import { defineComponent, computed, ref, watch, Transition } from 'vue'; import Ribbon from './Ribbon'; import useConfigInject from '../config-provider/hooks/useConfigInject'; import isNumeric from '../_util/isNumeric'; +import useCSSVarCls from '../config-provider/hooks/useCssVarCls'; import useStyle from './style'; import type { PresetColorKey } from '../theme/interface'; import type { LiteralUnion, CustomSlotsType } from '../_util/type'; @@ -49,7 +50,8 @@ export default defineComponent({ }>, setup(props, { slots, attrs }) { const { prefixCls, direction } = useConfigInject('badge', props); - const [wrapSSR, hashId] = useStyle(prefixCls); + const rootCls = useCSSVarCls(prefixCls); + const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls); // ================================ Misc ================================ const numberedDisplayCount = computed(() => { @@ -189,6 +191,8 @@ export default defineComponent({ [`${pre}-rtl`]: direction.value === 'rtl', }, attrs.class, + cssVarCls.value, + rootCls.value, hashId.value, ); diff --git a/components/badge/Ribbon.tsx b/components/badge/Ribbon.tsx index 6725d833b2..6426f612da 100644 --- a/components/badge/Ribbon.tsx +++ b/components/badge/Ribbon.tsx @@ -1,6 +1,7 @@ import type { CustomSlotsType, LiteralUnion } from '../_util/type'; 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 type { CSSProperties, PropType, ExtractPropTypes } from 'vue'; import { defineComponent, computed } from 'vue'; @@ -27,7 +28,8 @@ export default defineComponent({ }>, setup(props, { attrs, slots }) { 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 ribbonCls = computed(() => [ prefixCls.value, @@ -46,7 +48,10 @@ export default defineComponent({ cornerColorStyle.color = props.color; } return wrapSSR( -
+
{slots.default?.()}
{ +/** 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; - badgeZIndex: number | string; - badgeHeight: number; - badgeHeightSm: number; + /** + * @desc 徽标文本颜色 + * @descEN Text color of badge + */ badgeTextColor: string; - badgeFontWeight: string; - badgeFontSize: number; + /** + * @desc 徽标颜色 + * @descEN Color of badge + */ badgeColor: string; + /** + * @desc 徽标悬停颜色 + * @descEN Hover color of badge + */ badgeColorHover: string; - badgeDotSize: number; - badgeFontSizeSm: number; - badgeStatusSize: number; + /** + * @desc 徽标阴影尺寸 + * @descEN Shadow size of badge + */ badgeShadowSize: number; + /** + * @desc 徽标阴影颜色 + * @descEN Shadow color of badge + */ badgeShadowColor: string; + /** + * @desc 徽标处理持续时间 + * @descEN Processing duration of badge + */ badgeProcessingDuration: string; + /** + * @desc 徽标丝带偏移量 + * @descEN Ribbon offset of badge + */ badgeRibbonOffset: number; + /** + * @desc 徽标丝带角变换 + * @descEN Ribbon corner transform of badge + */ badgeRibbonCornerTransform: string; + /** + * @desc 徽标丝带角滤镜 + * @descEN Ribbon corner filter of badge + */ badgeRibbonCornerFilter: string; } @@ -46,10 +125,12 @@ const antNoWrapperZoomBadgeIn = new Keyframes('antNoWrapperZoomBadgeIn', { '0%': { transform: 'scale(0)', opacity: 0 }, '100%': { transform: 'scale(1)' }, }); + const antNoWrapperZoomBadgeOut = new Keyframes('antNoWrapperZoomBadgeOut', { '0%': { transform: 'scale(1)' }, '100%': { transform: 'scale(0)', opacity: 0 }, }); + const antBadgeLoadingCircle = new Keyframes('antBadgeLoadingCircle', { '0%': { transformOrigin: '50%' }, '100%': { @@ -58,22 +139,23 @@ const antBadgeLoadingCircle = new Keyframes('antBadgeLoadingCircle', { }, }); -const genSharedBadgeStyle: GenerateStyle = (token: BadgeToken): CSSObject => { +const genSharedBadgeStyle: GenerateStyle = token => { const { componentCls, iconCls, antCls, - badgeFontHeight, badgeShadowSize, - badgeHeightSm, - motionDurationSlow, - badgeStatusSize, + textFontSize, + textFontSizeSM, + statusSize, + dotSize, + textFontWeight, + indicatorHeight, + indicatorHeightSM, marginXS, - badgeRibbonOffset, + calc, } = token; const numberPrefixCls = `${antCls}-scroll-number`; - const ribbonPrefixCls = `${antCls}-ribbon`; - const ribbonWrapperPrefixCls = `${antCls}-ribbon-wrapper`; const colorPreset = genPresetColor(token, (colorKey, { darkColor }) => ({ [`&${componentCls} ${componentCls}-color-${colorKey}`]: { @@ -81,13 +163,9 @@ const genSharedBadgeStyle: GenerateStyle = (token: BadgeToken): CSSO [`&:not(${componentCls}-count)`]: { color: darkColor, }, - }, - })); - - const statusRibbonPreset = genPresetColor(token, (colorKey, { darkColor }) => ({ - [`&${ribbonPrefixCls}-color-${colorKey}`]: { - background: darkColor, - color: darkColor, + 'a:hover &': { + background: darkColor, + }, }, })); @@ -100,18 +178,20 @@ const genSharedBadgeStyle: GenerateStyle = (token: BadgeToken): CSSO lineHeight: 1, [`${componentCls}-count`]: { - zIndex: token.badgeZIndex, - minWidth: token.badgeHeight, - height: token.badgeHeight, + display: 'inline-flex', + justifyContent: 'center', + zIndex: token.indicatorZIndex, + minWidth: indicatorHeight, + height: indicatorHeight, color: token.badgeTextColor, - fontWeight: token.badgeFontWeight, - fontSize: token.badgeFontSize, - lineHeight: `${token.badgeHeight}px`, + fontWeight: textFontWeight, + fontSize: textFontSize, + lineHeight: unit(indicatorHeight), whiteSpace: 'nowrap', textAlign: 'center', background: token.badgeColor, - borderRadius: token.badgeHeight / 2, - boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`, + borderRadius: calc(indicatorHeight).div(2).equal(), + boxShadow: `0 0 0 ${unit(badgeShadowSize)} ${token.badgeShadowColor}`, transition: `background ${token.motionDurationMid}`, a: { @@ -126,28 +206,29 @@ const genSharedBadgeStyle: GenerateStyle = (token: BadgeToken): CSSO }, }, [`${componentCls}-count-sm`]: { - minWidth: badgeHeightSm, - height: badgeHeightSm, - fontSize: token.badgeFontSizeSm, - lineHeight: `${badgeHeightSm}px`, - borderRadius: badgeHeightSm / 2, + minWidth: indicatorHeightSM, + height: indicatorHeightSM, + fontSize: textFontSizeSM, + lineHeight: unit(indicatorHeightSM), + borderRadius: calc(indicatorHeightSM).div(2).equal(), }, [`${componentCls}-multiple-words`]: { - padding: `0 ${token.paddingXS}px`, + padding: `0 ${unit(token.paddingXS)}`, + + bdi: { + unicodeBidi: 'plaintext', + }, }, [`${componentCls}-dot`]: { - zIndex: token.badgeZIndex, - width: token.badgeDotSize, - minWidth: token.badgeDotSize, - height: token.badgeDotSize, + zIndex: token.indicatorZIndex, + width: dotSize, + minWidth: dotSize, + height: dotSize, background: token.badgeColor, borderRadius: '100%', - boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`, - }, - [`${componentCls}-dot${numberPrefixCls}`]: { - transition: `background ${motionDurationSlow}`, + boxShadow: `0 0 0 ${unit(badgeShadowSize)} ${token.badgeShadowColor}`, }, [`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: { position: 'absolute', @@ -170,8 +251,8 @@ const genSharedBadgeStyle: GenerateStyle = (token: BadgeToken): CSSO position: 'relative', top: -1, // Magic number, but seems better experience display: 'inline-block', - width: badgeStatusSize, - height: badgeStatusSize, + width: statusSize, + height: statusSize, verticalAlign: 'middle', borderRadius: '50%', }, @@ -181,8 +262,9 @@ const genSharedBadgeStyle: GenerateStyle = (token: BadgeToken): CSSO }, [`${componentCls}-status-processing`]: { overflow: 'visible', - color: token.colorPrimary, - backgroundColor: token.colorPrimary, + color: token.colorInfo, + backgroundColor: token.colorInfo, + borderColor: 'currentcolor', '&::after': { position: 'absolute', @@ -256,118 +338,56 @@ const genSharedBadgeStyle: GenerateStyle = (token: BadgeToken): CSSO transformOrigin: '50% 50%', }, }, - [`${numberPrefixCls}`]: { + [numberPrefixCls]: { overflow: 'hidden', + transition: `all ${token.motionDurationMid} ${token.motionEaseOutBack}`, [`${numberPrefixCls}-only`]: { position: 'relative', display: 'inline-block', - height: token.badgeHeight, + height: indicatorHeight, transition: `all ${token.motionDurationSlow} ${token.motionEaseOutBack}`, WebkitTransformStyle: 'preserve-3d', WebkitBackfaceVisibility: 'hidden', [`> p${numberPrefixCls}-only-unit`]: { - height: token.badgeHeight, + height: indicatorHeight, margin: 0, WebkitTransformStyle: 'preserve-3d', WebkitBackfaceVisibility: 'hidden', }, }, - [`${numberPrefixCls}-symbol`]: { verticalAlign: 'top' }, + [`${numberPrefixCls}-symbol`]: { + verticalAlign: 'top', + }, }, // ====================== RTL ======================= '&-rtl': { direction: 'rtl', - [`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: { 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 default genComponentStyleHook('Badge', token => { - const { fontSize, lineHeight, fontSizeSM, lineWidth, marginXS, colorBorderBg } = token; +export const prepareToken: (token: Parameters>[0]) => BadgeToken = token => { + const { fontHeight, lineWidth, marginXS, colorBorderBg } = token; - const badgeFontHeight = Math.round(fontSize * lineHeight); + const badgeFontHeight = fontHeight; const badgeShadowSize = lineWidth; - const badgeZIndex = 'auto'; - const badgeHeight = badgeFontHeight - 2 * badgeShadowSize; - const badgeTextColor = token.colorBgContainer; - const badgeFontWeight = 'normal'; - const badgeFontSize = fontSizeSM; + const badgeTextColor = token.colorTextLightSolid; const badgeColor = token.colorError; const badgeColorHover = token.colorErrorHover; - const badgeHeightSm = fontSize; - const badgeDotSize = fontSizeSM / 2; - const badgeFontSizeSm = fontSizeSM; - const badgeStatusSize = fontSizeSM / 2; const badgeToken = mergeToken(token, { badgeFontHeight, badgeShadowSize, - badgeZIndex, - badgeHeight, badgeTextColor, - badgeFontWeight, - badgeFontSize, badgeColor, badgeColorHover, badgeShadowColor: colorBorderBg, - badgeHeightSm, - badgeDotSize, - badgeFontSizeSm, - badgeStatusSize, badgeProcessingDuration: '1.2s', badgeRibbonOffset: marginXS, @@ -376,5 +396,28 @@ export default genComponentStyleHook('Badge', token => { 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, +); diff --git a/components/badge/style/ribbon.ts b/components/badge/style/ribbon.ts new file mode 100644 index 0000000000..91698fb71b --- /dev/null +++ b/components/badge/style/ribbon.ts @@ -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 = 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, +); diff --git a/components/theme/interface/cssinjs-utils/index.ts b/components/theme/interface/cssinjs-utils/index.ts new file mode 100644 index 0000000000..f6c7483c25 --- /dev/null +++ b/components/theme/interface/cssinjs-utils/index.ts @@ -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; + +export type OverrideToken = OverrideTokenTypeUtil; + +export type OverrideComponent = TokenMapKey; + +export type FullToken> = FullTokenTypeUtil< + ComponentTokenMap, + AliasToken, + C +>; + +export type GetDefaultToken> = GetDefaultTokenTypeUtil< + ComponentTokenMap, + AliasToken, + C +>; + +export type GenStyleFn> = GenStyleFnTypeUtil< + ComponentTokenMap, + AliasToken, + C +>; diff --git a/components/theme/interface/cssinjs-utils/interface.ts b/components/theme/interface/cssinjs-utils/interface.ts new file mode 100644 index 0000000000..0d07fa32c1 --- /dev/null +++ b/components/theme/interface/cssinjs-utils/interface.ts @@ -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; + +export type TokenMapKey = Extract; + +export type GlobalToken = AliasToken & + CompTokenMap; + +export type OverrideTokenMap = { + [key in keyof CompTokenMap]: Partial & Partial; +}; + +export type GlobalTokenWithComponent< + CompTokenMap extends TokenMap, + AliasToken extends TokenType, + C extends TokenMapKey, +> = GlobalToken & CompTokenMap[C]; + +export type ComponentToken< + CompTokenMap extends TokenMap, + AliasToken extends TokenType, + C extends TokenMapKey, +> = Exclude[C], undefined>; + +export type ComponentTokenKey< + CompTokenMap extends TokenMap, + AliasToken extends TokenType, + C extends TokenMapKey, +> = keyof ComponentToken; + +export type FullToken< + CompTokenMap extends TokenMap, + AliasToken extends TokenType, + C extends TokenMapKey, +> = TokenWithCommonCls>; + +export type GenStyleFn< + CompTokenMap extends TokenMap, + AliasToken extends TokenType, + C extends TokenMapKey, +> = (token: FullToken, info: StyleInfo) => CSSInterpolation; + +export type GetDefaultTokenFn< + CompTokenMap extends TokenMap, + AliasToken extends TokenType, + C extends TokenMapKey, +> = (token: AliasToken & Partial) => CompTokenMap[C]; + +export type GetDefaultToken< + CompTokenMap extends TokenMap, + AliasToken extends TokenType, + C extends TokenMapKey, +> = null | CompTokenMap[C] | GetDefaultTokenFn; diff --git a/components/theme/interface/index.ts b/components/theme/interface/index.ts index bd0f9942eb..2c5a66bcb5 100644 --- a/components/theme/interface/index.ts +++ b/components/theme/interface/index.ts @@ -6,6 +6,8 @@ import type { SeedToken } from './seeds'; import type { VueNode } from '../..//_util/type'; import type { Ref } from 'vue'; +export type { FullToken, OverrideComponent, GetDefaultToken, GenStyleFn } from './cssinjs-utils'; + export type MappingAlgorithm = DerivativeFunc; export type OverrideToken = { diff --git a/components/theme/internal.ts b/components/theme/internal.ts index 27f81de86d..44d4381d06 100644 --- a/components/theme/internal.ts +++ b/components/theme/internal.ts @@ -7,6 +7,7 @@ import type { PresetColorType, SeedToken, UseComponentStyleResult, + GenStyleFn, } from './interface'; import { PresetColors } from './interface'; import useToken from './useToken'; @@ -48,4 +49,5 @@ export type { SeedToken, UseComponentStyleResult, GetDefaultToken, + GenStyleFn, }; From ae2e3bce2c69437774217fbd40eff686c244cc3c Mon Sep 17 00:00:00 2001 From: nishu Date: Mon, 28 Apr 2025 19:41:39 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20QIANXING1-787=20Avatar=20&=20Badge?= =?UTF-8?q?=20=E7=BB=84=E4=BB=B6=E6=94=AF=E6=8C=81=20css=20var?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/avatar/Avatar.tsx | 2 +- components/avatar/Group.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/avatar/Avatar.tsx b/components/avatar/Avatar.tsx index 89ea6adf37..7cba88f3ad 100644 --- a/components/avatar/Avatar.tsx +++ b/components/avatar/Avatar.tsx @@ -179,7 +179,7 @@ const Avatar = defineComponent({ } else if (icon) { childrenToRender = icon; } else if (isMounted.value || scale.value !== 1) { - const transformString = `scale(${scale.value}) translateX(-50%)`; + const transformString = `scale(${scale.value})`; const childrenStyle: CSSProperties = { msTransform: transformString, WebkitTransform: transformString, diff --git a/components/avatar/Group.tsx b/components/avatar/Group.tsx index c60f6411b4..75971b9dae 100644 --- a/components/avatar/Group.tsx +++ b/components/avatar/Group.tsx @@ -6,6 +6,7 @@ import type { PropType, ExtractPropTypes, CSSProperties } from 'vue'; import { computed, defineComponent, watchEffect } from 'vue'; import { flattenChildren, getPropsSlot } from '../_util/props-util'; import useConfigInject from '../config-provider/hooks/useConfigInject'; +import useCSSVarCls from '../config-provider/hooks/useCssVarCls'; import useStyle from './style'; import { useAvatarProviderContext } from './AvatarContext'; @@ -36,7 +37,8 @@ const Group = defineComponent({ setup(props, { slots, attrs }) { const { prefixCls, direction } = useConfigInject('avatar', props); const groupPrefixCls = computed(() => `${prefixCls.value}-group`); - const [wrapSSR, hashId] = useStyle(prefixCls); + const rootCls = useCSSVarCls(prefixCls); + const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls); watchEffect(() => { const context = { size: props.size, shape: props.shape }; useAvatarProviderContext(context); @@ -54,6 +56,8 @@ const Group = defineComponent({ [groupPrefixCls.value]: true, [`${groupPrefixCls.value}-rtl`]: direction.value === 'rtl', [`${attrs.class}`]: !!attrs.class, + [cssVarCls.value]: true, + [rootCls.value]: true, [hashId.value]: true, };