389 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			389 lines
		
	
	
		
			11 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';
 | |
| 
 | |
| export type ComponentToken = {
 | |
|   color: string;
 | |
|   colorGradientEnd: string;
 | |
| };
 | |
| 
 | |
| const skeletonClsLoading = new Keyframes(`ant-skeleton-loading`, {
 | |
|   '0%': {
 | |
|     transform: 'translateX(-37.5%)',
 | |
|   },
 | |
|   '100%': {
 | |
|     transform: 'translateX(37.5%)',
 | |
|   },
 | |
| });
 | |
| 
 | |
| interface SkeletonToken extends FullToken<'Skeleton'> {
 | |
|   skeletonAvatarCls: string;
 | |
|   skeletonTitleCls: string;
 | |
|   skeletonParagraphCls: string;
 | |
|   skeletonButtonCls: string;
 | |
|   skeletonInputCls: string;
 | |
|   skeletonImageCls: string;
 | |
|   imageSizeBase: number;
 | |
|   skeletonTitleHeight: number;
 | |
|   skeletonBlockRadius: number;
 | |
|   skeletonParagraphLineHeight: number;
 | |
|   skeletonParagraphMarginTop: number;
 | |
|   skeletonLoadingBackground: string;
 | |
|   skeletonLoadingMotionDuration: string;
 | |
|   borderRadius: number;
 | |
| }
 | |
| 
 | |
| const genSkeletonElementCommonSize = (size: number): CSSObject => ({
 | |
|   height: size,
 | |
|   lineHeight: `${size}px`,
 | |
| });
 | |
| 
 | |
| const genSkeletonElementAvatarSize = (size: number): CSSObject => ({
 | |
|   width: size,
 | |
|   ...genSkeletonElementCommonSize(size),
 | |
| });
 | |
| 
 | |
| const genSkeletonColor = (token: SkeletonToken): CSSObject => ({
 | |
|   position: 'relative',
 | |
|   // fix https://github.com/ant-design/ant-design/issues/36444
 | |
|   // https://monshin.github.io/202109/css/safari-border-radius-overflow-hidden/
 | |
|   /* stylelint-disable-next-line property-no-vendor-prefix,value-no-vendor-prefix */
 | |
|   zIndex: 0,
 | |
|   overflow: 'hidden',
 | |
|   background: 'transparent',
 | |
|   '&::after': {
 | |
|     position: 'absolute',
 | |
|     top: 0,
 | |
|     insetInlineEnd: '-150%',
 | |
|     bottom: 0,
 | |
|     insetInlineStart: '-150%',
 | |
|     background: token.skeletonLoadingBackground,
 | |
|     animationName: skeletonClsLoading,
 | |
|     animationDuration: token.skeletonLoadingMotionDuration,
 | |
|     animationTimingFunction: 'ease',
 | |
|     animationIterationCount: 'infinite',
 | |
|     content: '""',
 | |
|   },
 | |
| });
 | |
| 
 | |
| const genSkeletonElementInputSize = (size: number): CSSObject => ({
 | |
|   width: size * 5,
 | |
|   minWidth: size * 5,
 | |
|   ...genSkeletonElementCommonSize(size),
 | |
| });
 | |
| 
 | |
| const genSkeletonElementAvatar = (token: SkeletonToken): CSSObject => {
 | |
|   const { skeletonAvatarCls, color, controlHeight, controlHeightLG, controlHeightSM } = token;
 | |
|   return {
 | |
|     [`${skeletonAvatarCls}`]: {
 | |
|       display: 'inline-block',
 | |
|       verticalAlign: 'top',
 | |
|       background: color,
 | |
|       ...genSkeletonElementAvatarSize(controlHeight),
 | |
|     },
 | |
|     [`${skeletonAvatarCls}${skeletonAvatarCls}-circle`]: {
 | |
|       borderRadius: '50%',
 | |
|     },
 | |
|     [`${skeletonAvatarCls}${skeletonAvatarCls}-lg`]: {
 | |
|       ...genSkeletonElementAvatarSize(controlHeightLG),
 | |
|     },
 | |
|     [`${skeletonAvatarCls}${skeletonAvatarCls}-sm`]: {
 | |
|       ...genSkeletonElementAvatarSize(controlHeightSM),
 | |
|     },
 | |
|   };
 | |
| };
 | |
| 
 | |
| const genSkeletonElementInput = (token: SkeletonToken): CSSObject => {
 | |
|   const {
 | |
|     controlHeight,
 | |
|     borderRadiusSM,
 | |
|     skeletonInputCls,
 | |
|     controlHeightLG,
 | |
|     controlHeightSM,
 | |
|     color,
 | |
|   } = token;
 | |
|   return {
 | |
|     [`${skeletonInputCls}`]: {
 | |
|       display: 'inline-block',
 | |
|       verticalAlign: 'top',
 | |
|       background: color,
 | |
|       borderRadius: borderRadiusSM,
 | |
|       ...genSkeletonElementInputSize(controlHeight),
 | |
|     },
 | |
| 
 | |
|     [`${skeletonInputCls}-lg`]: {
 | |
|       ...genSkeletonElementInputSize(controlHeightLG),
 | |
|     },
 | |
| 
 | |
|     [`${skeletonInputCls}-sm`]: {
 | |
|       ...genSkeletonElementInputSize(controlHeightSM),
 | |
|     },
 | |
|   };
 | |
| };
 | |
| 
 | |
| const genSkeletonElementImageSize = (size: number): CSSObject => ({
 | |
|   width: size,
 | |
|   ...genSkeletonElementCommonSize(size),
 | |
| });
 | |
| 
 | |
| const genSkeletonElementImage = (token: SkeletonToken): CSSObject => {
 | |
|   const { skeletonImageCls, imageSizeBase, color, borderRadiusSM } = token;
 | |
|   return {
 | |
|     [`${skeletonImageCls}`]: {
 | |
|       display: 'flex',
 | |
|       alignItems: 'center',
 | |
|       justifyContent: 'center',
 | |
|       verticalAlign: 'top',
 | |
|       background: color,
 | |
|       borderRadius: borderRadiusSM,
 | |
|       ...genSkeletonElementImageSize(imageSizeBase * 2),
 | |
|       [`${skeletonImageCls}-path`]: {
 | |
|         fill: '#bfbfbf',
 | |
|       },
 | |
|       [`${skeletonImageCls}-svg`]: {
 | |
|         ...genSkeletonElementImageSize(imageSizeBase),
 | |
|         maxWidth: imageSizeBase * 4,
 | |
|         maxHeight: imageSizeBase * 4,
 | |
|       },
 | |
|       [`${skeletonImageCls}-svg${skeletonImageCls}-svg-circle`]: {
 | |
|         borderRadius: '50%',
 | |
|       },
 | |
|     },
 | |
|     [`${skeletonImageCls}${skeletonImageCls}-circle`]: {
 | |
|       borderRadius: '50%',
 | |
|     },
 | |
|   };
 | |
| };
 | |
| const genSkeletonElementButtonShape = (
 | |
|   token: SkeletonToken,
 | |
|   size: number,
 | |
|   buttonCls: string,
 | |
| ): CSSObject => {
 | |
|   const { skeletonButtonCls } = token;
 | |
|   return {
 | |
|     [`${buttonCls}${skeletonButtonCls}-circle`]: {
 | |
|       width: size,
 | |
|       minWidth: size,
 | |
|       borderRadius: '50%',
 | |
|     },
 | |
|     [`${buttonCls}${skeletonButtonCls}-round`]: {
 | |
|       borderRadius: size,
 | |
|     },
 | |
|   };
 | |
| };
 | |
| 
 | |
| const genSkeletonElementButtonSize = (size: number): CSSObject => ({
 | |
|   width: size * 2,
 | |
|   minWidth: size * 2,
 | |
|   ...genSkeletonElementCommonSize(size),
 | |
| });
 | |
| 
 | |
| const genSkeletonElementButton = (token: SkeletonToken): CSSObject => {
 | |
|   const {
 | |
|     borderRadiusSM,
 | |
|     skeletonButtonCls,
 | |
|     controlHeight,
 | |
|     controlHeightLG,
 | |
|     controlHeightSM,
 | |
|     color,
 | |
|   } = token;
 | |
|   return {
 | |
|     [`${skeletonButtonCls}`]: {
 | |
|       display: 'inline-block',
 | |
|       verticalAlign: 'top',
 | |
|       background: color,
 | |
|       borderRadius: borderRadiusSM,
 | |
|       width: controlHeight * 2,
 | |
|       minWidth: controlHeight * 2,
 | |
|       ...genSkeletonElementButtonSize(controlHeight),
 | |
|     },
 | |
|     ...genSkeletonElementButtonShape(token, controlHeight, skeletonButtonCls),
 | |
| 
 | |
|     [`${skeletonButtonCls}-lg`]: {
 | |
|       ...genSkeletonElementButtonSize(controlHeightLG),
 | |
|     },
 | |
|     ...genSkeletonElementButtonShape(token, controlHeightLG, `${skeletonButtonCls}-lg`),
 | |
| 
 | |
|     [`${skeletonButtonCls}-sm`]: {
 | |
|       ...genSkeletonElementButtonSize(controlHeightSM),
 | |
|     },
 | |
|     ...genSkeletonElementButtonShape(token, controlHeightSM, `${skeletonButtonCls}-sm`),
 | |
|   };
 | |
| };
 | |
| 
 | |
| // =============================== Base ===============================
 | |
| const genBaseStyle: GenerateStyle<SkeletonToken> = (token: SkeletonToken) => {
 | |
|   const {
 | |
|     componentCls,
 | |
|     skeletonAvatarCls,
 | |
|     skeletonTitleCls,
 | |
|     skeletonParagraphCls,
 | |
|     skeletonButtonCls,
 | |
|     skeletonInputCls,
 | |
|     skeletonImageCls,
 | |
|     controlHeight,
 | |
|     controlHeightLG,
 | |
|     controlHeightSM,
 | |
|     color,
 | |
|     padding,
 | |
|     marginSM,
 | |
|     borderRadius,
 | |
|     skeletonTitleHeight,
 | |
|     skeletonBlockRadius,
 | |
|     skeletonParagraphLineHeight,
 | |
|     controlHeightXS,
 | |
|     skeletonParagraphMarginTop,
 | |
|   } = token;
 | |
| 
 | |
|   return {
 | |
|     [`${componentCls}`]: {
 | |
|       display: 'table',
 | |
|       width: '100%',
 | |
| 
 | |
|       [`${componentCls}-header`]: {
 | |
|         display: 'table-cell',
 | |
|         paddingInlineEnd: padding,
 | |
|         verticalAlign: 'top',
 | |
| 
 | |
|         // Avatar
 | |
|         [`${skeletonAvatarCls}`]: {
 | |
|           display: 'inline-block',
 | |
|           verticalAlign: 'top',
 | |
|           background: color,
 | |
|           ...genSkeletonElementAvatarSize(controlHeight),
 | |
|         },
 | |
|         [`${skeletonAvatarCls}-circle`]: {
 | |
|           borderRadius: '50%',
 | |
|         },
 | |
|         [`${skeletonAvatarCls}-lg`]: {
 | |
|           ...genSkeletonElementAvatarSize(controlHeightLG),
 | |
|         },
 | |
|         [`${skeletonAvatarCls}-sm`]: {
 | |
|           ...genSkeletonElementAvatarSize(controlHeightSM),
 | |
|         },
 | |
|       },
 | |
|       [`${componentCls}-content`]: {
 | |
|         display: 'table-cell',
 | |
|         width: '100%',
 | |
|         verticalAlign: 'top',
 | |
| 
 | |
|         // Title
 | |
|         [`${skeletonTitleCls}`]: {
 | |
|           width: '100%',
 | |
|           height: skeletonTitleHeight,
 | |
|           background: color,
 | |
|           borderRadius: skeletonBlockRadius,
 | |
|           [`+ ${skeletonParagraphCls}`]: {
 | |
|             marginBlockStart: controlHeightSM,
 | |
|           },
 | |
|         },
 | |
| 
 | |
|         // paragraph
 | |
|         [`${skeletonParagraphCls}`]: {
 | |
|           padding: 0,
 | |
|           '> li': {
 | |
|             width: '100%',
 | |
|             height: skeletonParagraphLineHeight,
 | |
|             listStyle: 'none',
 | |
|             background: color,
 | |
|             borderRadius: skeletonBlockRadius,
 | |
|             '+ li': {
 | |
|               marginBlockStart: controlHeightXS,
 | |
|             },
 | |
|           },
 | |
|         },
 | |
| 
 | |
|         [`${skeletonParagraphCls}> li:last-child:not(:first-child):not(:nth-child(2))`]: {
 | |
|           width: '61%',
 | |
|         },
 | |
|       },
 | |
| 
 | |
|       [`&-round ${componentCls}-content`]: {
 | |
|         [`${skeletonTitleCls}, ${skeletonParagraphCls} > li`]: {
 | |
|           borderRadius,
 | |
|         },
 | |
|       },
 | |
|     },
 | |
|     [`${componentCls}-with-avatar ${componentCls}-content`]: {
 | |
|       // Title
 | |
|       [`${skeletonTitleCls}`]: {
 | |
|         marginBlockStart: marginSM,
 | |
| 
 | |
|         [`+ ${skeletonParagraphCls}`]: {
 | |
|           marginBlockStart: skeletonParagraphMarginTop,
 | |
|         },
 | |
|       },
 | |
|     },
 | |
|     // Skeleton element
 | |
|     [`${componentCls}${componentCls}-element`]: {
 | |
|       display: 'inline-block',
 | |
|       width: 'auto',
 | |
| 
 | |
|       ...genSkeletonElementButton(token),
 | |
|       ...genSkeletonElementAvatar(token),
 | |
|       ...genSkeletonElementInput(token),
 | |
|       ...genSkeletonElementImage(token),
 | |
|     },
 | |
|     // Skeleton Block Button, Input
 | |
|     [`${componentCls}${componentCls}-block`]: {
 | |
|       width: '100%',
 | |
| 
 | |
|       [`${skeletonButtonCls}`]: {
 | |
|         width: '100%',
 | |
|       },
 | |
| 
 | |
|       [`${skeletonInputCls}`]: {
 | |
|         width: '100%',
 | |
|       },
 | |
|     },
 | |
|     // With active animation
 | |
|     [`${componentCls}${componentCls}-active`]: {
 | |
|       [`
 | |
|         ${skeletonTitleCls},
 | |
|         ${skeletonParagraphCls} > li,
 | |
|         ${skeletonAvatarCls},
 | |
|         ${skeletonButtonCls},
 | |
|         ${skeletonInputCls},
 | |
|         ${skeletonImageCls}
 | |
|       `]: {
 | |
|         ...genSkeletonColor(token),
 | |
|       },
 | |
|     },
 | |
|   };
 | |
| };
 | |
| 
 | |
| // ============================== Export ==============================
 | |
| export default genComponentStyleHook(
 | |
|   'Skeleton',
 | |
|   token => {
 | |
|     const { componentCls } = token;
 | |
| 
 | |
|     const skeletonToken = mergeToken<SkeletonToken>(token, {
 | |
|       skeletonAvatarCls: `${componentCls}-avatar`,
 | |
|       skeletonTitleCls: `${componentCls}-title`,
 | |
|       skeletonParagraphCls: `${componentCls}-paragraph`,
 | |
|       skeletonButtonCls: `${componentCls}-button`,
 | |
|       skeletonInputCls: `${componentCls}-input`,
 | |
|       skeletonImageCls: `${componentCls}-image`,
 | |
|       imageSizeBase: token.controlHeight * 1.5,
 | |
|       skeletonTitleHeight: token.controlHeight / 2,
 | |
|       skeletonBlockRadius: token.borderRadiusSM,
 | |
|       skeletonParagraphLineHeight: token.controlHeight / 2,
 | |
|       skeletonParagraphMarginTop: token.marginLG + token.marginXXS,
 | |
|       borderRadius: 100, // Large number to make capsule shape
 | |
|       skeletonLoadingBackground: `linear-gradient(90deg, ${token.color} 25%, ${token.colorGradientEnd} 37%, ${token.color} 63%)`,
 | |
|       skeletonLoadingMotionDuration: '1.4s',
 | |
|     });
 | |
|     return [genBaseStyle(skeletonToken)];
 | |
|   },
 | |
|   token => {
 | |
|     const { colorFillContent, colorFill } = token;
 | |
| 
 | |
|     return {
 | |
|       color: colorFillContent,
 | |
|       colorGradientEnd: colorFill,
 | |
|     };
 | |
|   },
 | |
| );
 |