381 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			381 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
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 { genPresetColor, resetComponent } from '../../style';
 | 
						|
 | 
						|
export interface ComponentToken {}
 | 
						|
 | 
						|
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 colorPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
 | 
						|
    [`&${componentCls} ${componentCls}-color-${colorKey}`]: {
 | 
						|
      background: darkColor,
 | 
						|
      [`&:not(${componentCls}-count)`]: {
 | 
						|
        color: 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: '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: badgeStatusSize,
 | 
						|
          height: badgeStatusSize,
 | 
						|
          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: 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,
 | 
						|
      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)];
 | 
						|
});
 |