ant-design/components/badge/style/index.ts
2023-08-28 21:56:37 +08:00

378 lines
11 KiB
TypeScript

import type { CSSObject } from '@ant-design/cssinjs';
import { Keyframes } from '@ant-design/cssinjs';
import { resetComponent } from '../../style';
import type { GlobalToken } from '../../theme';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, genPresetColor, 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 {
// Component token here
/**
* @desc 徽标 z-index
* @descEN z-index of badge
*/
indicatorZIndex: number | string;
/**
* @desc 徽标高度
* @descEN Height of badge
*/
indicatorHeight: number;
/**
* @desc 小号徽标高度
* @descEN Height of small badge
*/
indicatorHeightSM: number;
/**
* @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;
}
export interface BadgeToken extends FullToken<'Badge'> {
badgeFontHeight: number;
badgeTextColor: string;
badgeColor: string;
badgeColorHover: string;
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,
badgeShadowSize,
motionDurationSlow,
textFontSize,
textFontSizeSM,
statusSize,
dotSize,
textFontWeight,
indicatorHeight,
indicatorHeightSM,
marginXS,
} = token;
const numberPrefixCls = `${antCls}-scroll-number`;
const colorPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`&${componentCls} ${componentCls}-color-${colorKey}`]: {
background: darkColor,
[`&:not(${componentCls}-count)`]: {
color: darkColor,
},
},
}));
return {
[componentCls]: {
...resetComponent(token),
position: 'relative',
display: 'inline-block',
width: 'fit-content',
lineHeight: 1,
[`${componentCls}-count`]: {
zIndex: token.indicatorZIndex,
minWidth: indicatorHeight,
height: indicatorHeight,
color: token.badgeTextColor,
fontWeight: textFontWeight,
fontSize: textFontSize,
lineHeight: `${indicatorHeight}px`,
whiteSpace: 'nowrap',
textAlign: 'center',
background: token.badgeColor,
borderRadius: indicatorHeight / 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: indicatorHeightSM,
height: indicatorHeightSM,
fontSize: textFontSizeSM,
lineHeight: `${indicatorHeightSM}px`,
borderRadius: indicatorHeightSM / 2,
},
[`${componentCls}-multiple-words`]: {
padding: `0 ${token.paddingXS}px`,
bdi: {
unicodeBidi: 'plaintext',
},
},
[`${componentCls}-dot`]: {
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}`,
},
[`${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: '1s',
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: statusSize,
height: statusSize,
verticalAlign: 'middle',
borderRadius: '50%',
},
[`${componentCls}-status-success`]: {
backgroundColor: token.colorSuccess,
},
[`${componentCls}-status-processing`]: {
overflow: 'visible',
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,
},
[`${componentCls}-status-text`]: {
marginInlineStart: marginXS,
color: token.colorText,
fontSize: token.fontSize,
},
},
...colorPreset,
[`${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: indicatorHeight,
transition: `all ${token.motionDurationSlow} ${token.motionEaseOutBack}`,
WebkitTransformStyle: 'preserve-3d',
WebkitBackfaceVisibility: 'hidden',
[`> p${numberPrefixCls}-only-unit`]: {
height: indicatorHeight,
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%)',
},
},
},
};
};
// ============================== Export ==============================
export const prepareToken: (token: Parameters<GenStyleFn<'Badge'>>[0]) => BadgeToken = (token) => {
const { fontSize, lineHeight, lineWidth, marginXS, colorBorderBg } = token;
const badgeFontHeight = Math.round(fontSize * lineHeight);
const badgeShadowSize = lineWidth;
const badgeTextColor = token.colorBgContainer;
const badgeColor = token.colorError;
const badgeColorHover = token.colorErrorHover;
const badgeToken = mergeToken<BadgeToken>(token, {
badgeFontHeight,
badgeShadowSize,
badgeTextColor,
badgeColor,
badgeColorHover,
badgeShadowColor: colorBorderBg,
badgeProcessingDuration: '1.2s',
badgeRibbonOffset: marginXS,
// Follow token just by Design. Not related with token
badgeRibbonCornerTransform: 'scaleY(0.75)',
badgeRibbonCornerFilter: `brightness(75%)`,
});
return badgeToken;
};
export const prepareComponentToken = (token: GlobalToken) => {
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 genComponentStyleHook(
'Badge',
(token) => {
const badgeToken = prepareToken(token);
return [genSharedBadgeStyle(badgeToken)];
},
prepareComponentToken,
);