You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
12 KiB
376 lines
12 KiB
import type { CSSObject } from '@ant-design/cssinjs';
|
|
import { Keyframes } from '@ant-design/cssinjs';
|
|
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
|
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
|
import { genPresetColor, resetComponent } from '../../style';
|
|
|
|
interface BadgeToken extends FullToken<'Badge'> {
|
|
badgeFontHeight: number;
|
|
badgeZIndex: number | string;
|
|
badgeHeight: number;
|
|
badgeHeightSm: number;
|
|
badgeTextColor: string;
|
|
badgeFontWeight: string;
|
|
badgeFontSize: number;
|
|
badgeColor: string;
|
|
badgeColorHover: string;
|
|
badgeDotSize: number;
|
|
badgeFontSizeSm: number;
|
|
badgeStatusSize: number;
|
|
badgeShadowSize: number;
|
|
badgeShadowColor: string;
|
|
badgeProcessingDuration: string;
|
|
badgeRibbonOffset: number;
|
|
badgeRibbonCornerTransform: string;
|
|
badgeRibbonCornerFilter: string;
|
|
}
|
|
|
|
const antStatusProcessing = new Keyframes('antStatusProcessing', {
|
|
'0%': { transform: 'scale(0.8)', opacity: 0.5 },
|
|
'100%': { transform: 'scale(2.4)', opacity: 0 },
|
|
});
|
|
|
|
const antZoomBadgeIn = new Keyframes('antZoomBadgeIn', {
|
|
'0%': { transform: 'scale(0) translate(50%, -50%)', opacity: 0 },
|
|
'100%': { transform: 'scale(1) translate(50%, -50%)' },
|
|
});
|
|
|
|
const antZoomBadgeOut = new Keyframes('antZoomBadgeOut', {
|
|
'0%': { transform: 'scale(1) translate(50%, -50%)' },
|
|
'100%': { transform: 'scale(0) translate(50%, -50%)', opacity: 0 },
|
|
});
|
|
|
|
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%': {
|
|
transform: 'translate(50%, -50%) rotate(360deg)',
|
|
transformOrigin: '50%',
|
|
},
|
|
});
|
|
|
|
const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSObject => {
|
|
const {
|
|
componentCls,
|
|
iconCls,
|
|
antCls,
|
|
badgeFontHeight,
|
|
badgeShadowSize,
|
|
badgeHeightSm,
|
|
motionDurationSlow,
|
|
badgeStatusSize,
|
|
marginXS,
|
|
badgeRibbonOffset,
|
|
} = token;
|
|
const numberPrefixCls = `${antCls}-scroll-number`;
|
|
const ribbonPrefixCls = `${antCls}-ribbon`;
|
|
const ribbonWrapperPrefixCls = `${antCls}-ribbon-wrapper`;
|
|
|
|
const statusPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
|
|
[`${componentCls}-status-${colorKey}`]: {
|
|
background: darkColor,
|
|
},
|
|
}));
|
|
|
|
const statusRibbonPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
|
|
[`&${ribbonPrefixCls}-color-${colorKey}`]: {
|
|
background: darkColor,
|
|
color: darkColor,
|
|
},
|
|
}));
|
|
|
|
return {
|
|
[componentCls]: {
|
|
...resetComponent(token),
|
|
position: 'relative',
|
|
display: 'inline-block',
|
|
width: 'fit-content',
|
|
lineHeight: 1,
|
|
|
|
[`${componentCls}-count`]: {
|
|
zIndex: token.badgeZIndex,
|
|
minWidth: token.badgeHeight,
|
|
height: token.badgeHeight,
|
|
color: token.badgeTextColor,
|
|
fontWeight: token.badgeFontWeight,
|
|
fontSize: token.badgeFontSize,
|
|
lineHeight: `${token.badgeHeight}px`,
|
|
whiteSpace: 'nowrap',
|
|
textAlign: 'center',
|
|
background: token.badgeColor,
|
|
borderRadius: token.badgeHeight / 2,
|
|
boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`,
|
|
transition: `background ${token.motionDurationMid}`,
|
|
|
|
a: {
|
|
color: token.badgeTextColor,
|
|
},
|
|
'a:hover': {
|
|
color: token.badgeTextColor,
|
|
},
|
|
|
|
'a:hover &': {
|
|
background: token.badgeColorHover,
|
|
},
|
|
},
|
|
[`${componentCls}-count-sm`]: {
|
|
minWidth: badgeHeightSm,
|
|
height: badgeHeightSm,
|
|
fontSize: token.badgeFontSizeSm,
|
|
lineHeight: `${badgeHeightSm}px`,
|
|
borderRadius: badgeHeightSm / 2,
|
|
},
|
|
|
|
[`${componentCls}-multiple-words`]: {
|
|
padding: `0 ${token.paddingXS}px`,
|
|
},
|
|
|
|
[`${componentCls}-dot`]: {
|
|
zIndex: token.badgeZIndex,
|
|
width: token.badgeDotSize,
|
|
minWidth: token.badgeDotSize,
|
|
height: token.badgeDotSize,
|
|
background: token.badgeColor,
|
|
borderRadius: '100%',
|
|
boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`,
|
|
},
|
|
[`${componentCls}-dot${numberPrefixCls}`]: {
|
|
transition: `background ${motionDurationSlow}`,
|
|
},
|
|
[`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
insetInlineEnd: 0,
|
|
transform: 'translate(50%, -50%)',
|
|
transformOrigin: '100% 0%',
|
|
[`${iconCls}-spin`]: {
|
|
animationName: antBadgeLoadingCircle,
|
|
animationDuration: token.motionDurationMid,
|
|
animationIterationCount: 'infinite',
|
|
animationTimingFunction: 'linear',
|
|
},
|
|
},
|
|
[`&${componentCls}-status`]: {
|
|
lineHeight: 'inherit',
|
|
verticalAlign: 'baseline',
|
|
|
|
[`${componentCls}-status-dot`]: {
|
|
position: 'relative',
|
|
top: -1, // Magic number, but seems better experience
|
|
display: 'inline-block',
|
|
width: badgeStatusSize,
|
|
height: badgeStatusSize,
|
|
verticalAlign: 'middle',
|
|
borderRadius: '50%',
|
|
},
|
|
|
|
[`${componentCls}-status-success`]: {
|
|
backgroundColor: token.colorSuccess,
|
|
},
|
|
[`${componentCls}-status-processing`]: {
|
|
position: 'relative',
|
|
color: token.colorPrimary,
|
|
backgroundColor: token.colorPrimary,
|
|
|
|
'&::after': {
|
|
position: 'absolute',
|
|
top: 0,
|
|
insetInlineStart: 0,
|
|
width: '100%',
|
|
height: '100%',
|
|
borderWidth: badgeShadowSize,
|
|
borderStyle: 'solid',
|
|
borderColor: 'inherit',
|
|
borderRadius: '50%',
|
|
animationName: antStatusProcessing,
|
|
animationDuration: token.badgeProcessingDuration,
|
|
animationIterationCount: 'infinite',
|
|
animationTimingFunction: 'ease-in-out',
|
|
content: '""',
|
|
},
|
|
},
|
|
[`${componentCls}-status-default`]: {
|
|
backgroundColor: token.colorTextPlaceholder,
|
|
},
|
|
|
|
[`${componentCls}-status-error`]: {
|
|
backgroundColor: token.colorError,
|
|
},
|
|
|
|
[`${componentCls}-status-warning`]: {
|
|
backgroundColor: token.colorWarning,
|
|
},
|
|
...statusPreset,
|
|
[`${componentCls}-status-text`]: {
|
|
marginInlineStart: marginXS,
|
|
color: token.colorText,
|
|
fontSize: token.fontSize,
|
|
},
|
|
},
|
|
[`${componentCls}-zoom-appear, ${componentCls}-zoom-enter`]: {
|
|
animationName: antZoomBadgeIn,
|
|
animationDuration: token.motionDurationSlow,
|
|
animationTimingFunction: token.motionEaseOutBack,
|
|
animationFillMode: 'both',
|
|
},
|
|
[`${componentCls}-zoom-leave`]: {
|
|
animationName: antZoomBadgeOut,
|
|
animationDuration: token.motionDurationSlow,
|
|
animationTimingFunction: token.motionEaseOutBack,
|
|
animationFillMode: 'both',
|
|
},
|
|
[`&${componentCls}-not-a-wrapper`]: {
|
|
[`${componentCls}-zoom-appear, ${componentCls}-zoom-enter`]: {
|
|
animationName: antNoWrapperZoomBadgeIn,
|
|
animationDuration: token.motionDurationSlow,
|
|
animationTimingFunction: token.motionEaseOutBack,
|
|
},
|
|
|
|
[`${componentCls}-zoom-leave`]: {
|
|
animationName: antNoWrapperZoomBadgeOut,
|
|
animationDuration: token.motionDurationSlow,
|
|
animationTimingFunction: token.motionEaseOutBack,
|
|
},
|
|
[`&:not(${componentCls}-status)`]: {
|
|
verticalAlign: 'middle',
|
|
},
|
|
[`${numberPrefixCls}-custom-component, ${componentCls}-count`]: {
|
|
transform: 'none',
|
|
},
|
|
[`${numberPrefixCls}-custom-component, ${numberPrefixCls}`]: {
|
|
position: 'relative',
|
|
top: 'auto',
|
|
display: 'block',
|
|
transformOrigin: '50% 50%',
|
|
},
|
|
},
|
|
[`${numberPrefixCls}`]: {
|
|
overflow: 'hidden',
|
|
[`${numberPrefixCls}-only`]: {
|
|
position: 'relative',
|
|
display: 'inline-block',
|
|
height: token.badgeHeight,
|
|
transition: `all ${token.motionDurationSlow} ${token.motionEaseOutBack}`,
|
|
WebkitTransformStyle: 'preserve-3d',
|
|
WebkitBackfaceVisibility: 'hidden',
|
|
[`> p${numberPrefixCls}-only-unit`]: {
|
|
height: token.badgeHeight,
|
|
margin: 0,
|
|
WebkitTransformStyle: 'preserve-3d',
|
|
WebkitBackfaceVisibility: 'hidden',
|
|
},
|
|
},
|
|
[`${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,
|
|
height: badgeFontHeight,
|
|
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;
|
|
|
|
const badgeFontHeight = Math.round(fontSize * lineHeight);
|
|
const badgeShadowSize = lineWidth;
|
|
const badgeZIndex = 'auto';
|
|
const badgeHeight = badgeFontHeight - 2 * badgeShadowSize;
|
|
const badgeTextColor = token.colorBgContainer;
|
|
const badgeFontWeight = 'normal';
|
|
const badgeFontSize = fontSizeSM;
|
|
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<BadgeToken>(token, {
|
|
badgeFontHeight,
|
|
badgeShadowSize,
|
|
badgeZIndex,
|
|
badgeHeight,
|
|
badgeTextColor,
|
|
badgeFontWeight,
|
|
badgeFontSize,
|
|
badgeColor,
|
|
badgeColorHover,
|
|
badgeShadowColor: colorBorderBg,
|
|
badgeHeightSm,
|
|
badgeDotSize,
|
|
badgeFontSizeSm,
|
|
badgeStatusSize,
|
|
badgeProcessingDuration: '1.2s',
|
|
badgeRibbonOffset: marginXS,
|
|
|
|
// Follow token just by Design. Not related with token
|
|
badgeRibbonCornerTransform: 'scaleY(0.75)',
|
|
badgeRibbonCornerFilter: `brightness(75%)`,
|
|
});
|
|
|
|
return [genSharedBadgeStyle(badgeToken)];
|
|
});
|
|
|