feat: Add skeleton button (#19699)

* #18237 add skeleton button

* update snapshot

* refactoring demo code

* lint code

* update test snapshots

* add test

* update snapshot

* fix deps lint issue

* set avatar siz and update test snapshots

* fix lint issue

* remove button and just keep skeleton

* add active switch for button demo

* add size and shape radio for skeleton button demo

* add button tests

* add skeleton tests

* update doc

* update test snapshots

* omit avatar and button props

* refactoring skeleton and update test snapshots
This commit is contained in:
Rustin 2019-11-21 20:23:56 +08:00 committed by 二货机器人
parent f32235d074
commit 060b7ded20
12 changed files with 746 additions and 198 deletions

View File

@ -1,47 +1,18 @@
import * as React from 'react';
import classNames from 'classnames';
import SkeletonElement, { SkeletonElementProps } from './SkeletonElement';
export interface SkeletonAvatarProps {
prefixCls?: string;
className?: string;
style?: object;
size?: 'large' | 'small' | 'default' | number;
export interface AvatarProps extends Omit<SkeletonElementProps, 'shape'> {
shape?: 'circle' | 'square';
}
// eslint-disable-next-line react/prefer-stateless-function
class SkeletonAvatar extends React.Component<SkeletonAvatarProps, any> {
static defaultProps: Partial<SkeletonAvatarProps> = {
class SkeletonAvatar extends React.Component<AvatarProps, any> {
static defaultProps: Partial<SkeletonElementProps> = {
size: 'large',
};
render() {
const { prefixCls, className, style, size, shape } = this.props;
const sizeCls = classNames({
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
});
const shapeCls = classNames({
[`${prefixCls}-circle`]: shape === 'circle',
[`${prefixCls}-square`]: shape === 'square',
});
const sizeStyle: React.CSSProperties =
typeof size === 'number'
? {
width: size,
height: size,
lineHeight: `${size}px`,
}
: {};
return (
<span
className={classNames(prefixCls, className, sizeCls, shapeCls)}
style={{ ...sizeStyle, ...style }}
/>
);
return <SkeletonElement {...this.props} />;
}
}

View File

@ -0,0 +1,37 @@
import * as React from 'react';
import omit from 'omit.js';
import classNames from 'classnames';
import SkeletonElement, { SkeletonElementProps } from './SkeletonElement';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
interface SkeletonButtonProps extends Omit<SkeletonElementProps, 'size'> {
active?: boolean;
size?: 'large' | 'small' | 'default';
}
// eslint-disable-next-line react/prefer-stateless-function
class SkeletonButton extends React.Component<SkeletonButtonProps, any> {
static defaultProps: Partial<SkeletonButtonProps> = {
size: 'default',
};
renderSkeletonButton = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className, active } = this.props;
const prefixCls = getPrefixCls('skeleton', customizePrefixCls);
const otherProps = omit(this.props, ['prefixCls']);
const cls = classNames(prefixCls, className, `${prefixCls}-element`, {
[`${prefixCls}-active`]: active,
});
return (
<div className={cls}>
<SkeletonElement prefixCls={`${prefixCls}-button`} {...otherProps} />
</div>
);
};
render() {
return <ConfigConsumer>{this.renderSkeletonButton}</ConfigConsumer>;
}
}
export default SkeletonButton;

View File

@ -0,0 +1,164 @@
import * as React from 'react';
import classNames from 'classnames';
import Avatar, { AvatarProps } from './Avatar';
import Title, { SkeletonTitleProps } from './Title';
import Paragraph, { SkeletonParagraphProps } from './Paragraph';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import SkeletonButton from './Button';
export interface SkeletonProps {
active?: boolean;
loading?: boolean;
prefixCls?: string;
className?: string;
children?: React.ReactNode;
avatar?: AvatarProps | boolean;
title?: SkeletonTitleProps | boolean;
paragraph?: SkeletonParagraphProps | boolean;
}
function getComponentProps<T>(prop: T | boolean | undefined): T | {} {
if (prop && typeof prop === 'object') {
return prop;
}
return {};
}
function getAvatarBasicProps(hasTitle: boolean, hasParagraph: boolean): AvatarProps {
if (hasTitle && !hasParagraph) {
return { shape: 'square' };
}
return { shape: 'circle' };
}
function getTitleBasicProps(hasAvatar: boolean, hasParagraph: boolean): SkeletonTitleProps {
if (!hasAvatar && hasParagraph) {
return { width: '38%' };
}
if (hasAvatar && hasParagraph) {
return { width: '50%' };
}
return {};
}
function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): SkeletonParagraphProps {
const basicProps: SkeletonParagraphProps = {};
// Width
if (!hasAvatar || !hasTitle) {
basicProps.width = '61%';
}
// Rows
if (!hasAvatar && hasTitle) {
basicProps.rows = 3;
} else {
basicProps.rows = 2;
}
return basicProps;
}
class Skeleton extends React.Component<SkeletonProps, any> {
static Button: typeof SkeletonButton;
static defaultProps: Partial<SkeletonProps> = {
avatar: false,
title: true,
paragraph: true,
};
renderSkeleton = ({ getPrefixCls }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
loading,
className,
children,
avatar,
title,
paragraph,
active,
} = this.props;
const prefixCls = getPrefixCls('skeleton', customizePrefixCls);
if (loading || !('loading' in this.props)) {
const hasAvatar = !!avatar;
const hasTitle = !!title;
const hasParagraph = !!paragraph;
// Avatar
let avatarNode;
if (hasAvatar) {
const avatarProps: AvatarProps = {
prefixCls: `${prefixCls}-avatar`,
...getAvatarBasicProps(hasTitle, hasParagraph),
...getComponentProps(avatar),
};
avatarNode = (
<div className={`${prefixCls}-header`}>
<Avatar {...avatarProps} />
</div>
);
}
let contentNode;
if (hasTitle || hasParagraph) {
// Title
let $title;
if (hasTitle) {
const titleProps: SkeletonTitleProps = {
prefixCls: `${prefixCls}-title`,
...getTitleBasicProps(hasAvatar, hasParagraph),
...getComponentProps(title),
};
$title = <Title {...titleProps} />;
}
// Paragraph
let paragraphNode;
if (hasParagraph) {
const paragraphProps: SkeletonParagraphProps = {
prefixCls: `${prefixCls}-paragraph`,
...getParagraphBasicProps(hasAvatar, hasTitle),
...getComponentProps(paragraph),
};
paragraphNode = <Paragraph {...paragraphProps} />;
}
contentNode = (
<div className={`${prefixCls}-content`}>
{$title}
{paragraphNode}
</div>
);
}
const cls = classNames(prefixCls, className, {
[`${prefixCls}-with-avatar`]: hasAvatar,
[`${prefixCls}-active`]: active,
});
return (
<div className={cls}>
{avatarNode}
{contentNode}
</div>
);
}
return children;
};
render() {
return <ConfigConsumer>{this.renderSkeleton}</ConfigConsumer>;
}
}
export default Skeleton;

View File

@ -0,0 +1,45 @@
import * as React from 'react';
import classNames from 'classnames';
export interface SkeletonElementProps {
prefixCls?: string;
className?: string;
style?: object;
size?: 'large' | 'small' | 'default' | number;
shape?: 'circle' | 'square' | 'round';
}
// eslint-disable-next-line react/prefer-stateless-function
class SkeletonElement extends React.Component<SkeletonElementProps, any> {
render() {
const { prefixCls, className, style, size, shape } = this.props;
const sizeCls = classNames({
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
});
const shapeCls = classNames({
[`${prefixCls}-circle`]: shape === 'circle',
[`${prefixCls}-square`]: shape === 'square',
[`${prefixCls}-round`]: shape === 'round',
});
const sizeStyle: React.CSSProperties =
typeof size === 'number'
? {
width: size,
height: size,
lineHeight: `${size}px`,
}
: {};
return (
<span
className={classNames(prefixCls, className, sizeCls, shapeCls)}
style={{ ...sizeStyle, ...style }}
/>
);
}
}
export default SkeletonElement;

View File

@ -48,6 +48,223 @@ exports[`renders ./components/skeleton/demo/basic.md correctly 1`] = `
</div>
`;
exports[`renders ./components/skeleton/demo/button.md correctly 1`] = `
<div>
<form
class="ant-form ant-form-inline"
style="margin-bottom:16px"
>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-label"
>
<label
class=""
title="Active"
>
Active
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<button
aria-checked="false"
class="ant-switch"
role="switch"
type="button"
>
<span
class="ant-switch-inner"
/>
</button>
</div>
</div>
</div>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-label"
>
<label
class=""
title="Size"
>
Size
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
>
<span
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-button-input"
type="radio"
value="default"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Default
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="large"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Large
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="small"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Small
</span>
</label>
</div>
</div>
</div>
</div>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-label"
>
<label
class=""
title="Shape"
>
Shape
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
>
<span
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-button-input"
type="radio"
value="default"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Default
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="round"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Round
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="circle"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Circle
</span>
</label>
</div>
</div>
</div>
</div>
</form>
<div
class="ant-skeleton ant-skeleton-element"
>
<span
class="ant-skeleton-button"
/>
</div>
</div>
`;
exports[`renders ./components/skeleton/demo/children.md correctly 1`] = `
<div
class="article"

View File

@ -169,6 +169,76 @@ exports[`Skeleton avatar size 4`] = `
</div>
`;
exports[`Skeleton button active 1`] = `
<div
class="ant-skeleton ant-skeleton-element ant-skeleton-active"
>
<span
class="ant-skeleton-button"
/>
</div>
`;
exports[`Skeleton button shape 1`] = `
<div
class="ant-skeleton ant-skeleton-element"
>
<span
class="ant-skeleton-button"
/>
</div>
`;
exports[`Skeleton button shape 2`] = `
<div
class="ant-skeleton ant-skeleton-element"
>
<span
class="ant-skeleton-button ant-skeleton-button-round"
/>
</div>
`;
exports[`Skeleton button shape 3`] = `
<div
class="ant-skeleton ant-skeleton-element"
>
<span
class="ant-skeleton-button ant-skeleton-button-circle"
/>
</div>
`;
exports[`Skeleton button size 1`] = `
<div
class="ant-skeleton ant-skeleton-element"
>
<span
class="ant-skeleton-button"
/>
</div>
`;
exports[`Skeleton button size 2`] = `
<div
class="ant-skeleton ant-skeleton-element"
>
<span
class="ant-skeleton-button ant-skeleton-button-lg"
/>
</div>
`;
exports[`Skeleton button size 3`] = `
<div
class="ant-skeleton ant-skeleton-element"
>
<span
class="ant-skeleton-button ant-skeleton-button-sm"
/>
</div>
`;
exports[`Skeleton paragraph rows 1`] = `
<div
class="ant-skeleton"
@ -245,6 +315,41 @@ exports[`Skeleton paragraph width 2`] = `
</div>
`;
exports[`Skeleton should square avatar 1`] = `
<div
class="ant-skeleton ant-skeleton-with-avatar"
>
<div
class="ant-skeleton-header"
>
<span
class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-square"
/>
</div>
<div
class="ant-skeleton-content"
>
<h3
class="ant-skeleton-title"
/>
</div>
</div>
`;
exports[`Skeleton should without avatar and paragraph 1`] = `
<div
class="ant-skeleton"
>
<div
class="ant-skeleton-content"
>
<h3
class="ant-skeleton-title"
/>
</div>
</div>
`;
exports[`Skeleton title width 1`] = `
<div
class="ant-skeleton"

View File

@ -10,9 +10,20 @@ describe('Skeleton', () => {
Bamboo
</Skeleton>,
);
const genSkeletonButton = props => mount(<Skeleton.Button {...props} />);
mountTest(Skeleton);
it('should without avatar and paragraph', () => {
const wrapperSmall = genSkeleton({ avatar: false, paragraph: false });
expect(wrapperSmall.render()).toMatchSnapshot();
});
it('should square avatar', () => {
const wrapperSmall = genSkeleton({ avatar: true, paragraph: false });
expect(wrapperSmall.render()).toMatchSnapshot();
});
describe('avatar', () => {
it('size', () => {
const wrapperSmall = genSkeleton({ avatar: { size: 'small' } });
@ -53,4 +64,27 @@ describe('Skeleton', () => {
expect(wrapperList.render()).toMatchSnapshot();
});
});
describe('button', () => {
it('active', () => {
const wrapper = genSkeletonButton({ active: true });
expect(wrapper.render()).toMatchSnapshot();
});
it('size', () => {
const wrapperDefault = genSkeletonButton({ size: 'default' });
expect(wrapperDefault.render()).toMatchSnapshot();
const wrapperLarge = genSkeletonButton({ size: 'large' });
expect(wrapperLarge.render()).toMatchSnapshot();
const wrapperSmall = genSkeletonButton({ size: 'small' });
expect(wrapperSmall.render()).toMatchSnapshot();
});
it('shape', () => {
const wrapperDefault = genSkeletonButton({ shape: 'default' });
expect(wrapperDefault.render()).toMatchSnapshot();
const wrapperRound = genSkeletonButton({ shape: 'round' });
expect(wrapperRound.render()).toMatchSnapshot();
const wrapperCircle = genSkeletonButton({ shape: 'circle' });
expect(wrapperCircle.render()).toMatchSnapshot();
});
});
});

View File

@ -0,0 +1,68 @@
---
order: 2
title:
zh-CN: 骨架按钮
en-US: Skeleton Button
---
## zh-CN
骨架按钮。
## en-US
Skeleton Button.
```jsx
import { Skeleton, Switch, Form, Radio } from 'antd';
class Demo extends React.Component {
state = {
active: false,
size: 'default',
shape: 'default',
};
handleActiveChange = checked => {
this.setState({ active: checked });
};
handleSizeChange = e => {
this.setState({ size: e.target.value });
};
handleShapeChange = e => {
this.setState({ shape: e.target.value });
};
render() {
const { active, size, shape } = this.state;
return (
<div>
<Form layout="inline" style={{ marginBottom: 16 }}>
<Form.Item label="Active">
<Switch checked={active} onChange={this.handleActiveChange} />
</Form.Item>
<Form.Item label="Size">
<Radio.Group value={size} onChange={this.handleSizeChange}>
<Radio.Button value="default">Default</Radio.Button>
<Radio.Button value="large">Large</Radio.Button>
<Radio.Button value="small">Small</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="Shape">
<Radio.Group value={shape} onChange={this.handleShapeChange}>
<Radio.Button value="default">Default</Radio.Button>
<Radio.Button value="round">Round</Radio.Button>
<Radio.Button value="circle">Circle</Radio.Button>
</Radio.Group>
</Form.Item>
</Form>
<Skeleton.Button {...this.state} />
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -45,3 +45,11 @@ Provide a placeholder while you wait for content to load, or to visualise conten
| --- | --- | --- | --- |
| rows | Set the row count of paragraph | number | - |
| width | Set the width of paragraph. When width is an Array, it can set the width of each row. Otherwise only set the last row width | number \| string \| Array<number \| string> | - |
### SkeletonButtonProps
| Property | Description | Type | Default |
| -------- | ----------------------- | ------------------------------------ | ------- |
| active | Show animation effect | boolean | false |
| size | Set the size of button | Enum{ 'large', 'small', 'default' } | - |
| shape | Set the shape of button | Enum{ 'circle', 'round', 'default' } | - |

View File

@ -1,161 +1,7 @@
import * as React from 'react';
import classNames from 'classnames';
import Avatar, { SkeletonAvatarProps } from './Avatar';
import Title, { SkeletonTitleProps } from './Title';
import Paragraph, { SkeletonParagraphProps } from './Paragraph';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import Skeleton from './Skeleton';
import SkeletonButton from './Button';
export interface SkeletonProps {
active?: boolean;
loading?: boolean;
prefixCls?: string;
className?: string;
children?: React.ReactNode;
avatar?: SkeletonAvatarProps | boolean;
title?: SkeletonTitleProps | boolean;
paragraph?: SkeletonParagraphProps | boolean;
}
function getComponentProps<T>(prop: T | boolean | undefined): T | {} {
if (prop && typeof prop === 'object') {
return prop;
}
return {};
}
function getAvatarBasicProps(hasTitle: boolean, hasParagraph: boolean): SkeletonAvatarProps {
if (hasTitle && !hasParagraph) {
return { shape: 'square' };
}
return { shape: 'circle' };
}
function getTitleBasicProps(hasAvatar: boolean, hasParagraph: boolean): SkeletonTitleProps {
if (!hasAvatar && hasParagraph) {
return { width: '38%' };
}
if (hasAvatar && hasParagraph) {
return { width: '50%' };
}
return {};
}
function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): SkeletonParagraphProps {
const basicProps: SkeletonParagraphProps = {};
// Width
if (!hasAvatar || !hasTitle) {
basicProps.width = '61%';
}
// Rows
if (!hasAvatar && hasTitle) {
basicProps.rows = 3;
} else {
basicProps.rows = 2;
}
return basicProps;
}
class Skeleton extends React.Component<SkeletonProps, any> {
static defaultProps: Partial<SkeletonProps> = {
avatar: false,
title: true,
paragraph: true,
};
renderSkeleton = ({ getPrefixCls }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
loading,
className,
children,
avatar,
title,
paragraph,
active,
} = this.props;
const prefixCls = getPrefixCls('skeleton', customizePrefixCls);
if (loading || !('loading' in this.props)) {
const hasAvatar = !!avatar;
const hasTitle = !!title;
const hasParagraph = !!paragraph;
// Avatar
let avatarNode;
if (hasAvatar) {
const avatarProps: SkeletonAvatarProps = {
prefixCls: `${prefixCls}-avatar`,
...getAvatarBasicProps(hasTitle, hasParagraph),
...getComponentProps(avatar),
};
avatarNode = (
<div className={`${prefixCls}-header`}>
<Avatar {...avatarProps} />
</div>
);
}
let contentNode;
if (hasTitle || hasParagraph) {
// Title
let $title;
if (hasTitle) {
const titleProps: SkeletonTitleProps = {
prefixCls: `${prefixCls}-title`,
...getTitleBasicProps(hasAvatar, hasParagraph),
...getComponentProps(title),
};
$title = <Title {...titleProps} />;
}
// Paragraph
let paragraphNode;
if (hasParagraph) {
const paragraphProps: SkeletonParagraphProps = {
prefixCls: `${prefixCls}-paragraph`,
...getParagraphBasicProps(hasAvatar, hasTitle),
...getComponentProps(paragraph),
};
paragraphNode = <Paragraph {...paragraphProps} />;
}
contentNode = (
<div className={`${prefixCls}-content`}>
{$title}
{paragraphNode}
</div>
);
}
const cls = classNames(prefixCls, className, {
[`${prefixCls}-with-avatar`]: hasAvatar,
[`${prefixCls}-active`]: active,
});
return (
<div className={cls}>
{avatarNode}
{contentNode}
</div>
);
}
return children;
};
render() {
return <ConfigConsumer>{this.renderSkeleton}</ConfigConsumer>;
}
}
export { SkeletonProps } from './Skeleton';
Skeleton.Button = SkeletonButton;
export default Skeleton;

View File

@ -46,3 +46,11 @@ cols: 1
| --- | --- | --- | --- |
| rows | 设置段落占位图的行数 | number | - |
| width | 设置段落占位图的宽度,若为数组时则为对应的每行宽度,反之则是最后一行的宽度 | number \| string \| Array<number \| string> | - |
### SkeletonButtonProps
| 属性 | 说明 | 类型 | 默认值 |
| ------ | ---------------- | ------------------------------------ | ------ |
| active | 是否展示动画效果 | boolean | false |
| size | 设置按钮的大小 | Enum{ 'large', 'small', 'default' } | - |
| shape | 指定按钮的形状 | Enum{ 'circle', 'round', 'default' } | - |

View File

@ -5,6 +5,7 @@
@skeleton-avatar-prefix-cls: ~'@{skeleton-prefix-cls}-avatar';
@skeleton-title-prefix-cls: ~'@{skeleton-prefix-cls}-title';
@skeleton-paragraph-prefix-cls: ~'@{skeleton-prefix-cls}-paragraph';
@skeleton-button-prefix-cls: ~'@{skeleton-prefix-cls}-button';
@skeleton-to-color: shade(@skeleton-color, 5%);
@ -23,14 +24,14 @@
vertical-align: top;
background: @skeleton-color;
.avatar-size(@avatar-size-base);
.skeleton-element-avatar-size(@avatar-size-base);
&-lg {
.avatar-size(@avatar-size-lg);
.skeleton-element-avatar-size(@avatar-size-lg);
}
&-sm {
.avatar-size(@avatar-size-sm);
.skeleton-element-avatar-size(@avatar-size-sm);
}
}
}
@ -96,19 +97,63 @@
.@{skeleton-avatar-prefix-cls} {
.skeleton-color();
}
.@{skeleton-button-prefix-cls} {
.skeleton-color();
}
}
// Skeleton element
&-element {
display: inline-block;
// Button
.@{skeleton-button-prefix-cls} {
display: inline-block;
vertical-align: top;
background: @skeleton-color;
border-radius: @border-radius-base;
.skeleton-element-button-size(@btn-height-base);
&-lg {
.skeleton-element-button-size(@btn-height-lg);
}
&-sm {
.skeleton-element-button-size(@btn-height-sm);
}
}
}
}
.avatar-size(@size) {
.skeleton-element-avatar-size(@size) {
width: @size;
height: @size;
line-height: @size;
.skeleton-element-common-size(@size);
&.@{skeleton-avatar-prefix-cls}-circle {
border-radius: 50%;
}
}
.skeleton-element-button-size(@size) {
width: @size * 2;
.skeleton-element-common-size(@size);
&.@{skeleton-button-prefix-cls}-circle {
width: @size;
border-radius: 50%;
}
&.@{skeleton-button-prefix-cls}-round {
border-radius: @size;
}
}
.skeleton-element-common-size(@size) {
height: @size;
line-height: @size;
}
.skeleton-color() {
background: linear-gradient(
90deg,