mirror of
https://github.com/ant-design/ant-design.git
synced 2025-07-26 08:47:29 +08:00
Merge branch 'feature-3.9.0' of https://github.com/ant-design/ant-design into feature/svg-icon
# Conflicts: # package.json
This commit is contained in:
commit
d9b38aee4b
@ -41,6 +41,7 @@ Array [
|
|||||||
"Rate",
|
"Rate",
|
||||||
"Row",
|
"Row",
|
||||||
"Select",
|
"Select",
|
||||||
|
"Skeleton",
|
||||||
"Slider",
|
"Slider",
|
||||||
"Spin",
|
"Spin",
|
||||||
"Steps",
|
"Steps",
|
||||||
|
@ -7,6 +7,8 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
|||||||
cancel: () => void;
|
cancel: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private extraNode: HTMLDivElement;
|
||||||
|
private clickWaveTimeoutId: number;
|
||||||
private styleForPesudo: HTMLStyleElement | null;
|
private styleForPesudo: HTMLStyleElement | null;
|
||||||
|
|
||||||
isNotGrey(color: string) {
|
isNotGrey(color: string) {
|
||||||
@ -17,22 +19,17 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = (node: HTMLElement) => {
|
onClick = (node: HTMLElement, waveColor: string) => {
|
||||||
if (node.className.indexOf('-leave') >= 0) {
|
if (node.className.indexOf('-leave') >= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.removeExtraStyleNode();
|
|
||||||
const { insertExtraNode } = this.props;
|
const { insertExtraNode } = this.props;
|
||||||
const extraNode = document.createElement('div');
|
this.extraNode = document.createElement('div');
|
||||||
|
const extraNode = this.extraNode;
|
||||||
extraNode.className = 'ant-click-animating-node';
|
extraNode.className = 'ant-click-animating-node';
|
||||||
const attributeName = insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
|
const attributeName = this.getAttributeName();
|
||||||
node.removeAttribute(attributeName);
|
node.removeAttribute(attributeName);
|
||||||
node.setAttribute(attributeName, 'true');
|
node.setAttribute(attributeName, 'true');
|
||||||
// Get wave color from target
|
|
||||||
const waveColor =
|
|
||||||
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
|
|
||||||
getComputedStyle(node).getPropertyValue('border-color') ||
|
|
||||||
getComputedStyle(node).getPropertyValue('background-color');
|
|
||||||
// Not white or transparnt or grey
|
// Not white or transparnt or grey
|
||||||
if (waveColor &&
|
if (waveColor &&
|
||||||
waveColor !== '#ffffff' &&
|
waveColor !== '#ffffff' &&
|
||||||
@ -49,19 +46,13 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
|||||||
if (insertExtraNode) {
|
if (insertExtraNode) {
|
||||||
node.appendChild(extraNode);
|
node.appendChild(extraNode);
|
||||||
}
|
}
|
||||||
const transitionEnd = () => {
|
TransitionEvents.addEndEventListener(node, this.onTransitionEnd);
|
||||||
node.removeAttribute(attributeName);
|
|
||||||
this.removeExtraStyleNode();
|
|
||||||
if (insertExtraNode) {
|
|
||||||
node.removeChild(extraNode);
|
|
||||||
}
|
|
||||||
TransitionEvents.removeEndEventListener(node, transitionEnd);
|
|
||||||
};
|
|
||||||
TransitionEvents.addEndEventListener(node, transitionEnd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bindAnimationEvent = (node: HTMLElement) => {
|
bindAnimationEvent = (node: HTMLElement) => {
|
||||||
if (node.getAttribute('disabled') ||
|
if (!node ||
|
||||||
|
!node.getAttribute ||
|
||||||
|
node.getAttribute('disabled') ||
|
||||||
node.className.indexOf('disabled') >= 0) {
|
node.className.indexOf('disabled') >= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -70,7 +61,13 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
|||||||
if ((e.target as HTMLElement).tagName === 'INPUT') {
|
if ((e.target as HTMLElement).tagName === 'INPUT') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTimeout(() => this.onClick(node), 0);
|
this.resetEffect(node);
|
||||||
|
// Get wave color from target
|
||||||
|
const waveColor =
|
||||||
|
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
|
||||||
|
getComputedStyle(node).getPropertyValue('border-color') ||
|
||||||
|
getComputedStyle(node).getPropertyValue('background-color');
|
||||||
|
this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0);
|
||||||
};
|
};
|
||||||
node.addEventListener('click', onClick, true);
|
node.addEventListener('click', onClick, true);
|
||||||
return {
|
return {
|
||||||
@ -80,6 +77,29 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAttributeName() {
|
||||||
|
const { insertExtraNode } = this.props;
|
||||||
|
return insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
|
||||||
|
}
|
||||||
|
|
||||||
|
resetEffect(node: HTMLElement) {
|
||||||
|
const { insertExtraNode } = this.props;
|
||||||
|
const attributeName = this.getAttributeName();
|
||||||
|
node.removeAttribute(attributeName);
|
||||||
|
this.removeExtraStyleNode();
|
||||||
|
if (insertExtraNode) {
|
||||||
|
node.removeChild(this.extraNode);
|
||||||
|
}
|
||||||
|
TransitionEvents.removeEndEventListener(node, this.onTransitionEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTransitionEnd = (e: AnimationEvent) => {
|
||||||
|
if (!e || e.animationName !== 'fadeEffect') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.resetEffect(e.target as HTMLElement);
|
||||||
|
}
|
||||||
|
|
||||||
removeExtraStyleNode() {
|
removeExtraStyleNode() {
|
||||||
if (this.styleForPesudo && document.body.contains(this.styleForPesudo)) {
|
if (this.styleForPesudo && document.body.contains(this.styleForPesudo)) {
|
||||||
document.body.removeChild(this.styleForPesudo);
|
document.body.removeChild(this.styleForPesudo);
|
||||||
@ -95,6 +115,9 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
|||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
this.instance.cancel();
|
this.instance.cancel();
|
||||||
}
|
}
|
||||||
|
if (this.clickWaveTimeoutId) {
|
||||||
|
clearTimeout(this.clickWaveTimeoutId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { render, mount } from 'enzyme';
|
import { render, mount } from 'enzyme';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
import Button from '..';
|
import Button from '..';
|
||||||
import Icon from '../../icon';
|
import Icon from '../../icon';
|
||||||
|
|
||||||
@ -11,6 +12,16 @@ describe('Button', () => {
|
|||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('mount correctly', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Button>Follow</Button>
|
||||||
|
);
|
||||||
|
if (process.env.REACT === '15') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(() => renderer.create(wrapper).toJSON()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders Chinese characters correctly', () => {
|
it('renders Chinese characters correctly', () => {
|
||||||
const wrapper = render(
|
const wrapper = render(
|
||||||
<Button>按钮</Button>
|
<Button>按钮</Button>
|
||||||
|
@ -7,7 +7,7 @@ title:
|
|||||||
|
|
||||||
## zh-CN
|
## zh-CN
|
||||||
|
|
||||||
幽灵按钮将其他按钮的内容反色,背景变为透明,常用在有色背景上。
|
幽灵按钮将按钮的内容反色,背景变为透明,常用在有色背景上。
|
||||||
|
|
||||||
## en-US
|
## en-US
|
||||||
|
|
||||||
|
@ -372,174 +372,83 @@ exports[`renders ./components/card/demo/inner.md correctly 1`] = `
|
|||||||
|
|
||||||
exports[`renders ./components/card/demo/loading.md correctly 1`] = `
|
exports[`renders ./components/card/demo/loading.md correctly 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<span
|
||||||
class="ant-card ant-card-loading ant-card-bordered"
|
class="ant-switch"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-switch-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="ant-card ant-card-bordered"
|
||||||
|
style="width:300px;margin-top:16px"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="ant-card-head"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-head-wrapper"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-head-title"
|
|
||||||
>
|
|
||||||
Card title
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="ant-card-body"
|
class="ant-card-body"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-card-loading-content"
|
class="ant-skeleton ant-skeleton-with-avatar ant-skeleton-active"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-row"
|
class="ant-skeleton-header"
|
||||||
style="margin-left:-4px;margin-right:-4px"
|
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="ant-col-22"
|
class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"
|
||||||
style="padding-left:4px;padding-right:4px"
|
/>
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-row"
|
class="ant-skeleton-content"
|
||||||
style="margin-left:-4px;margin-right:-4px"
|
|
||||||
>
|
>
|
||||||
<div
|
<h3
|
||||||
class="ant-col-8"
|
class="ant-skeleton-title"
|
||||||
style="padding-left:4px;padding-right:4px"
|
style="width:50%"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
>
|
>
|
||||||
<div
|
<li
|
||||||
class="ant-card-loading-block"
|
style="width:100%"
|
||||||
/>
|
/>
|
||||||
</div>
|
<li
|
||||||
<div
|
style="width:100%"
|
||||||
class="ant-col-15"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-row"
|
|
||||||
style="margin-left:-4px;margin-right:-4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-col-6"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-col-18"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-row"
|
|
||||||
style="margin-left:-4px;margin-right:-4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-col-13"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-col-9"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-row"
|
|
||||||
style="margin-left:-4px;margin-right:-4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-col-4"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-col-3"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-col-16"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-row"
|
|
||||||
style="margin-left:-4px;margin-right:-4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-col-8"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-col-6"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-col-8"
|
|
||||||
style="padding-left:4px;padding-right:4px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-card-loading-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ul
|
||||||
|
class="ant-card-actions"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width:33.333333333333336%"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i
|
||||||
|
class="anticon anticon-setting"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
style="width:33.333333333333336%"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i
|
||||||
|
class="anticon anticon-edit"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
style="width:33.333333333333336%"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i
|
||||||
|
class="anticon anticon-ellipsis"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
class="ant-btn"
|
|
||||||
style="margin-top:16px"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Toggle loading
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -14,30 +14,42 @@ title:
|
|||||||
Shows a loading indicator while the contents of the card is being fetched.
|
Shows a loading indicator while the contents of the card is being fetched.
|
||||||
|
|
||||||
````jsx
|
````jsx
|
||||||
import { Card, Button } from 'antd';
|
import { Skeleton, Switch, Card, Icon, Avatar } from 'antd';
|
||||||
|
|
||||||
class LoadingCard extends React.Component {
|
const { Meta } = Card;
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = () => {
|
onChange = (checked) => {
|
||||||
this.setState({ loading: !this.state.loading });
|
this.setState({ loading: !checked });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { loading } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Card loading={this.state.loading} title="Card title">
|
<Switch checked={!loading} onChange={this.onChange} />
|
||||||
Whatever content
|
|
||||||
|
<Card
|
||||||
|
style={{ width: 300, marginTop: 16 }}
|
||||||
|
actions={[<Icon type="setting" />, <Icon type="edit" />, <Icon type="ellipsis" />]}
|
||||||
|
>
|
||||||
|
<Skeleton loading={loading} avatar active>
|
||||||
|
<Meta
|
||||||
|
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
||||||
|
title="Card title"
|
||||||
|
description="This is the description"
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
</Card>
|
</Card>
|
||||||
<Button onClick={this.handleClick} style={{ marginTop: 16 }}>Toggle loading</Button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(<App />, mountNode);
|
||||||
<LoadingCard />,
|
|
||||||
mountNode);
|
|
||||||
````
|
````
|
||||||
|
@ -87,7 +87,7 @@ class WeekPicker extends React.Component<any, any> {
|
|||||||
const {
|
const {
|
||||||
prefixCls, className, disabled, pickerClass, popupStyle,
|
prefixCls, className, disabled, pickerClass, popupStyle,
|
||||||
pickerInputClass, format, allowClear, locale, localeCode, disabledDate,
|
pickerInputClass, format, allowClear, locale, localeCode, disabledDate,
|
||||||
style, onFocus, onBlur,
|
style, onFocus, onBlur, id,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const pickerValue = this.state.value;
|
const pickerValue = this.state.value;
|
||||||
@ -129,7 +129,6 @@ class WeekPicker extends React.Component<any, any> {
|
|||||||
className={pickerInputClass}
|
className={pickerInputClass}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
style={style}
|
|
||||||
/>
|
/>
|
||||||
{clearIcon}
|
{clearIcon}
|
||||||
<Icon type="calendar" className={`${prefixCls}-picker-icon`}/>
|
<Icon type="calendar" className={`${prefixCls}-picker-icon`}/>
|
||||||
@ -137,7 +136,11 @@ class WeekPicker extends React.Component<any, any> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<span className={classNames(className, pickerClass)} id={this.props.id}>
|
<span
|
||||||
|
className={classNames(className, pickerClass)}
|
||||||
|
style={style}
|
||||||
|
id={id}
|
||||||
|
>
|
||||||
<RcDatePicker
|
<RcDatePicker
|
||||||
{...this.props}
|
{...this.props}
|
||||||
calendar={calendar}
|
calendar={calendar}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
exports[`WeekPicker should support style prop 1`] = `
|
exports[`WeekPicker should support style prop 1`] = `
|
||||||
<span
|
<span
|
||||||
class="ant-calendar-picker"
|
class="ant-calendar-picker"
|
||||||
|
style="width: 400px;"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<input
|
<input
|
||||||
class="ant-calendar-picker-input ant-input"
|
class="ant-calendar-picker-input ant-input"
|
||||||
placeholder="Select date"
|
placeholder="Select date"
|
||||||
readonly=""
|
readonly=""
|
||||||
style="width: 400px;"
|
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
|
@ -16,6 +16,20 @@ describe('Drawer', () => {
|
|||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('render top drawer', () => {
|
||||||
|
const wrapper = render(
|
||||||
|
<Drawer
|
||||||
|
visible
|
||||||
|
height={400}
|
||||||
|
placement="top"
|
||||||
|
getContainer={false}
|
||||||
|
>
|
||||||
|
Here is content of Drawer
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('have a title', () => {
|
it('have a title', () => {
|
||||||
const wrapper = render(
|
const wrapper = render(
|
||||||
<Drawer
|
<Drawer
|
||||||
|
@ -42,7 +42,6 @@ class MultiDrawer extends React.Component {
|
|||||||
title="Multi-level drawer"
|
title="Multi-level drawer"
|
||||||
className="test_drawer"
|
className="test_drawer"
|
||||||
width={520}
|
width={520}
|
||||||
closable={false}
|
|
||||||
onClose={this.onClose}
|
onClose={this.onClose}
|
||||||
getContainer={false}
|
getContainer={false}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
@ -54,7 +53,7 @@ class MultiDrawer extends React.Component {
|
|||||||
<Drawer
|
<Drawer
|
||||||
title="Two-level Drawer"
|
title="Two-level Drawer"
|
||||||
width={320}
|
width={320}
|
||||||
closable={false}
|
className="Two-level"
|
||||||
getContainer={false}
|
getContainer={false}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
onClose={this.onChildrenDrawerClose}
|
onClose={this.onChildrenDrawerClose}
|
||||||
@ -105,12 +104,23 @@ describe('Drawer', () => {
|
|||||||
expect(wrapper.find('#two_drawer_text').exists()).toBe(true);
|
expect(wrapper.find('#two_drawer_text').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render right MultiDrawer', () => {
|
it('render left MultiDrawer', () => {
|
||||||
const wrapper = mount(<MultiDrawer placement="left" />);
|
const wrapper = mount(<MultiDrawer placement="left" />);
|
||||||
wrapper.find('button#open_drawer').simulate('click');
|
wrapper.find('button#open_drawer').simulate('click');
|
||||||
wrapper.find('button#open_two_drawer').simulate('click');
|
wrapper.find('button#open_two_drawer').simulate('click');
|
||||||
const translateX = wrapper.find('.ant-drawer.test_drawer').get(0).props.style.transform;
|
const translateX = wrapper.find('.ant-drawer.test_drawer').get(0).props.style.transform;
|
||||||
expect(translateX).toEqual('translateX(180px)');
|
expect(translateX).toEqual('translateX(180px)');
|
||||||
expect(wrapper.find('#two_drawer_text').exists()).toBe(true);
|
expect(wrapper.find('#two_drawer_text').exists()).toBe(true);
|
||||||
|
wrapper.find('.Two-level .ant-drawer-close').simulate('click');
|
||||||
|
expect(wrapper.state().childrenDrawer).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('render left MultiDrawer', () => {
|
||||||
|
const wrapper = mount(<MultiDrawer placement="top" />);
|
||||||
|
wrapper.find('button#open_drawer').simulate('click');
|
||||||
|
wrapper.find('button#open_two_drawer').simulate('click');
|
||||||
|
const translateX = wrapper.find('.ant-drawer.test_drawer').get(0).props.style.transform;
|
||||||
|
expect(translateX).toEqual('translateY(180px)');
|
||||||
|
expect(wrapper.find('#two_drawer_text').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -277,3 +277,43 @@ exports[`Drawer render correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Drawer render top drawer 1`] = `
|
||||||
|
<div
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-drawer ant-drawer-top"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-drawer-mask"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ant-drawer-content-wrapper"
|
||||||
|
style="transform:translateY(-100%);-ms-transform:translateY(-100%);height:400px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-drawer-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-drawer-wrapper-body"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Close"
|
||||||
|
class="ant-drawer-close"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-drawer-close-x"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="ant-drawer-body"
|
||||||
|
>
|
||||||
|
Here is content of Drawer
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
---
|
|
||||||
order: 1
|
|
||||||
title:
|
|
||||||
zh-CN: 左侧滑出
|
|
||||||
en-US: Left Slider
|
|
||||||
---
|
|
||||||
|
|
||||||
## zh-CN
|
|
||||||
|
|
||||||
基础抽屉,点击触发按钮抽屉从左滑出,点击遮罩区关闭
|
|
||||||
|
|
||||||
## en-US
|
|
||||||
|
|
||||||
Basic drawer.
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
import { Drawer, Button } from 'antd';
|
|
||||||
|
|
||||||
class App extends React.Component {
|
|
||||||
state = { visible: false };
|
|
||||||
|
|
||||||
showDrawer = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onClose = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button type="primary" onClick={this.showDrawer}>
|
|
||||||
Open
|
|
||||||
</Button>
|
|
||||||
<Drawer
|
|
||||||
title="Basic Drawer"
|
|
||||||
placement="left"
|
|
||||||
closable={false}
|
|
||||||
onClose={this.onClose}
|
|
||||||
visible={this.state.visible}
|
|
||||||
>
|
|
||||||
<p>Some contents...</p>
|
|
||||||
<p>Some contents...</p>
|
|
||||||
<p>Some contents...</p>
|
|
||||||
</Drawer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.render(<App />, mountNode);
|
|
||||||
```
|
|
75
components/drawer/demo/placement.md
Normal file
75
components/drawer/demo/placement.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
order: 1
|
||||||
|
title:
|
||||||
|
zh-CN: 自定义位置
|
||||||
|
en-US: Custom Placement
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
自定义位置,点击触发按钮抽屉从相应的位置滑出,点击遮罩区关闭
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Basic drawer.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Drawer, Button, Radio } from 'antd';
|
||||||
|
|
||||||
|
const RadioGroup = Radio.Group;
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
state = { visible: false, placement: 'left' };
|
||||||
|
|
||||||
|
showDrawer = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onChange = (e) => {
|
||||||
|
this.setState({
|
||||||
|
placement: e.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<RadioGroup
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
defaultValue={this.state.placement}
|
||||||
|
onChange={this.onChange}
|
||||||
|
>
|
||||||
|
<Radio value="top">top</Radio>
|
||||||
|
<Radio value="right">right</Radio>
|
||||||
|
<Radio value="bottom">bottom</Radio>
|
||||||
|
<Radio value="left">left</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
<Button type="primary" onClick={this.showDrawer}>
|
||||||
|
Open
|
||||||
|
</Button>
|
||||||
|
<Drawer
|
||||||
|
title="Basic Drawer"
|
||||||
|
placement={this.state.placement}
|
||||||
|
closable={false}
|
||||||
|
onClose={this.onClose}
|
||||||
|
visible={this.state.visible}
|
||||||
|
>
|
||||||
|
<p>Some contents...</p>
|
||||||
|
<p>Some contents...</p>
|
||||||
|
<p>Some contents...</p>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, mountNode);
|
||||||
|
```
|
@ -27,9 +27,10 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
|
|||||||
| title | The title for Drawer. | string\|ReactNode | - |
|
| title | The title for Drawer. | string\|ReactNode | - |
|
||||||
| visible | Whether the Drawer dialog is visible or not. | boolean | false |
|
| visible | Whether the Drawer dialog is visible or not. | boolean | false |
|
||||||
| width | Width of the Drawer dialog. | string\|number | 256 |
|
| width | Width of the Drawer dialog. | string\|number | 256 |
|
||||||
|
| height | placement is `top` or `bottom`, height of the Drawer dialog. | string\|number | - |
|
||||||
| className | The class name of the container of the Drawer dialog. | string | - |
|
| className | The class name of the container of the Drawer dialog. | string | - |
|
||||||
| zIndex | The `z-index` of the Drawer. | Number | 1000 |
|
| zIndex | The `z-index` of the Drawer. | Number | 1000 |
|
||||||
| placement | The placement of the Drawer. | 'left' \| 'right' | 'right'
|
| placement | The placement of the Drawer. | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' |
|
||||||
| onClose | Specify a callback that will be called when a user clicks mask, close button or Cancel button. | function(e) | - |
|
| onClose | Specify a callback that will be called when a user clicks mask, close button or Cancel button. | function(e) | - |
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RcDrawer from 'rc-drawer';
|
|
||||||
import * as PropTypes from 'prop-types';
|
import * as PropTypes from 'prop-types';
|
||||||
|
import RcDrawer from 'rc-drawer';
|
||||||
import createReactContext, { Context } from 'create-react-context';
|
import createReactContext, { Context } from 'create-react-context';
|
||||||
import warning from 'warning';
|
import warning from 'warning';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -14,6 +14,7 @@ type EventType =
|
|||||||
|
|
||||||
type getContainerfunc = () => HTMLElement;
|
type getContainerfunc = () => HTMLElement;
|
||||||
|
|
||||||
|
type placementType = 'top' | 'right' | 'bottom' | 'left';
|
||||||
export interface DrawerProps {
|
export interface DrawerProps {
|
||||||
closable?: boolean;
|
closable?: boolean;
|
||||||
destroyOnClose?: boolean;
|
destroyOnClose?: boolean;
|
||||||
@ -25,12 +26,13 @@ export interface DrawerProps {
|
|||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
|
height?: number | string;
|
||||||
/* deprecated, use className instead */
|
/* deprecated, use className instead */
|
||||||
wrapClassName?: string;
|
wrapClassName?: string;
|
||||||
zIndex?: number;
|
zIndex?: number;
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
push?: boolean;
|
push?: boolean;
|
||||||
placement?: 'left' | 'right';
|
placement?: placementType;
|
||||||
onClose?: (e: EventType) => void;
|
onClose?: (e: EventType) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
@ -66,6 +68,7 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
|||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
prefixCls: 'ant-drawer',
|
prefixCls: 'ant-drawer',
|
||||||
width: 256,
|
width: 256,
|
||||||
|
height: 256,
|
||||||
closable: true,
|
closable: true,
|
||||||
placement: 'right',
|
placement: 'right',
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
@ -76,14 +79,14 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
|||||||
push: false,
|
push: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
praentDrawer: Drawer;
|
parentDrawer: Drawer;
|
||||||
destoryClose: boolean;
|
destoryClose: boolean;
|
||||||
public componentDidUpdate(preProps: DrawerProps) {
|
public componentDidUpdate(preProps: DrawerProps) {
|
||||||
if (preProps.visible !== this.props.visible && this.praentDrawer) {
|
if (preProps.visible !== this.props.visible && this.parentDrawer) {
|
||||||
if (this.props.visible) {
|
if (this.props.visible) {
|
||||||
this.praentDrawer.push();
|
this.parentDrawer.push();
|
||||||
} else {
|
} else {
|
||||||
this.praentDrawer.pull();
|
this.parentDrawer.pull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,6 +127,16 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
|||||||
|
|
||||||
getDestoryOnClose = () => (this.props.destroyOnClose && !this.props.visible);
|
getDestoryOnClose = () => (this.props.destroyOnClose && !this.props.visible);
|
||||||
|
|
||||||
|
// get drawar push width or height
|
||||||
|
getPushTransform = (placement?: placementType) => {
|
||||||
|
if (placement === 'left' || placement === 'right') {
|
||||||
|
return `translateX(${placement === 'left' ? 180 : -180}px)`;
|
||||||
|
}
|
||||||
|
if (placement === 'top' || placement === 'bottom') {
|
||||||
|
return `translateY(${placement === 'top' ? 180 : -180}px)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// render drawer body dom
|
||||||
renderBody = () => {
|
renderBody = () => {
|
||||||
if (this.destoryClose && !this.props.visible) {
|
if (this.destoryClose && !this.props.visible) {
|
||||||
return null;
|
return null;
|
||||||
@ -138,12 +151,15 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
|||||||
} : {};
|
} : {};
|
||||||
|
|
||||||
const isDestroyOnClose = this.getDestoryOnClose();
|
const isDestroyOnClose = this.getDestoryOnClose();
|
||||||
|
|
||||||
if (isDestroyOnClose) {
|
if (isDestroyOnClose) {
|
||||||
// Increase the opacity transition, delete children after closing.
|
// Increase the opacity transition, delete children after closing.
|
||||||
containerStyle.opacity = 0;
|
containerStyle.opacity = 0;
|
||||||
containerStyle.transition = 'opacity .3s';
|
containerStyle.transition = 'opacity .3s';
|
||||||
}
|
}
|
||||||
const { prefixCls, title, closable } = this.props;
|
const { prefixCls, title, closable } = this.props;
|
||||||
|
|
||||||
|
// is have header dom
|
||||||
let header;
|
let header;
|
||||||
if (title) {
|
if (title) {
|
||||||
header = (
|
header = (
|
||||||
@ -152,6 +168,7 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// is have closer button
|
||||||
let closer;
|
let closer;
|
||||||
if (closable) {
|
if (closable) {
|
||||||
closer = (
|
closer = (
|
||||||
@ -181,26 +198,40 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRcDrawerStyle = () => {
|
||||||
|
const { zIndex, placement } = this.props;
|
||||||
|
return this.state.push
|
||||||
|
? {
|
||||||
|
zIndex,
|
||||||
|
transform: this.getPushTransform(placement),
|
||||||
|
}
|
||||||
|
: { zIndex };
|
||||||
|
}
|
||||||
|
|
||||||
|
// render Provider for Multi-level drawe
|
||||||
renderProvider = (value: Drawer) => {
|
renderProvider = (value: Drawer) => {
|
||||||
let { zIndex, style, placement, className, wrapClassName, ...rest } = this.props;
|
let { zIndex, style, placement, className, wrapClassName, width, height, ...rest } = this.props;
|
||||||
warning(wrapClassName === undefined, 'wrapClassName is deprecated, please use className instead.');
|
warning(wrapClassName === undefined, 'wrapClassName is deprecated, please use className instead.');
|
||||||
const RcDrawerStyle = this.state.push
|
|
||||||
? {
|
this.parentDrawer = value;
|
||||||
zIndex,
|
const offsetStyle: any = {};
|
||||||
transform: `translateX(${placement === 'left' ? 180 : -180}px)`,
|
if (placement === 'left' || placement === 'right') {
|
||||||
}
|
offsetStyle.width = width;
|
||||||
: { zIndex };
|
} else {
|
||||||
this.praentDrawer = value;
|
offsetStyle.height = height;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<DrawerContext.Provider value={this}>
|
<DrawerContext.Provider value={this}>
|
||||||
<RcDrawer
|
<RcDrawer
|
||||||
{...rest}
|
|
||||||
handler={false}
|
handler={false}
|
||||||
|
{...rest}
|
||||||
|
{...offsetStyle}
|
||||||
open={this.props.visible}
|
open={this.props.visible}
|
||||||
onMaskClick={this.onMaskClick}
|
onMaskClick={this.onMaskClick}
|
||||||
showMask={this.props.mask}
|
showMask={this.props.mask}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
style={RcDrawerStyle}
|
style={this.getRcDrawerStyle()}
|
||||||
className={classNames(wrapClassName, className)}
|
className={classNames(wrapClassName, className)}
|
||||||
>
|
>
|
||||||
{this.renderBody()}
|
{this.renderBody()}
|
||||||
@ -208,6 +239,7 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
|||||||
</DrawerContext.Provider>
|
</DrawerContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<DrawerContext.Consumer>{this.renderProvider}</DrawerContext.Consumer>
|
<DrawerContext.Consumer>{this.renderProvider}</DrawerContext.Consumer>
|
||||||
|
@ -28,9 +28,10 @@ title: Drawer
|
|||||||
| title | 标题 | string \| ReactNode | - |
|
| title | 标题 | string \| ReactNode | - |
|
||||||
| visible | Drawer 是否可见 | boolean | - |
|
| visible | Drawer 是否可见 | boolean | - |
|
||||||
| width | 宽度 | string \| number | 256 |
|
| width | 宽度 | string \| number | 256 |
|
||||||
|
| height | 高度, 在 `placement` 为 `top` 或 `bottom` 时使用 | string \| number | 256 |
|
||||||
| className | 对话框外层容器的类名 | string | - |
|
| className | 对话框外层容器的类名 | string | - |
|
||||||
| zIndex | 设置 Drawer 的 `z-index` | Number | 1000 |
|
| zIndex | 设置 Drawer 的 `z-index` | Number | 1000 |
|
||||||
| placement | 抽屉的方向 | 'left' \| 'right' | 'right'
|
| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right'
|
||||||
| onClose | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 |
|
| onClose | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 |
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 0%;
|
width: 0%;
|
||||||
|
height: 100%;
|
||||||
z-index: @zindex-modal;
|
z-index: @zindex-modal;
|
||||||
transition: transform @animation-duration-slow @ease-base-in;
|
|
||||||
> * {
|
> * {
|
||||||
transition: transform @animation-duration-slow @ease-base-in;
|
transition: transform @animation-duration-slow @ease-base-in;
|
||||||
}
|
}
|
||||||
@ -18,8 +18,6 @@
|
|||||||
.@{dawer-prefix-cls}-content {
|
.@{dawer-prefix-cls}-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
opacity: 0;
|
|
||||||
transition: opacity @animation-duration-slow linear;
|
|
||||||
}
|
}
|
||||||
&-left,
|
&-left,
|
||||||
&-right {
|
&-right {
|
||||||
@ -54,10 +52,14 @@
|
|||||||
|
|
||||||
&-top,
|
&-top,
|
||||||
&-bottom {
|
&-bottom {
|
||||||
.@{dawer-prefix-cls}-content-wrapper,
|
width: 100%;
|
||||||
.@{dawer-prefix-cls}-content {
|
height: 0%;
|
||||||
|
.@{dawer-prefix-cls}-content-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
&.@{dawer-prefix-cls}-open {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&-top {
|
&-top {
|
||||||
&.@{dawer-prefix-cls}-open {
|
&.@{dawer-prefix-cls}-open {
|
||||||
@ -81,13 +83,10 @@
|
|||||||
|
|
||||||
&.@{dawer-prefix-cls}-open {
|
&.@{dawer-prefix-cls}-open {
|
||||||
.@{dawer-prefix-cls} {
|
.@{dawer-prefix-cls} {
|
||||||
&-content {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
&-mask {
|
&-mask {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out;
|
animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out;
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,9 +169,6 @@
|
|||||||
}
|
}
|
||||||
&-open {
|
&-open {
|
||||||
transition: transform @animation-duration-slow @ease-base-out;
|
transition: transform @animation-duration-slow @ease-base-out;
|
||||||
> * {
|
|
||||||
transition: transform @animation-duration-slow @ease-base-out;
|
|
||||||
}
|
|
||||||
&-content {
|
&-content {
|
||||||
box-shadow: @shadow-2;
|
box-shadow: @shadow-2;
|
||||||
}
|
}
|
||||||
|
@ -117,8 +117,8 @@ export type WrappedFormUtils = {
|
|||||||
isFieldsTouched(names?: Array<string>): boolean;
|
isFieldsTouched(names?: Array<string>): boolean;
|
||||||
/** 重置一组输入控件的值与状态,如不传入参数,则重置所有组件 */
|
/** 重置一组输入控件的值与状态,如不传入参数,则重置所有组件 */
|
||||||
resetFields(names?: Array<string>): void;
|
resetFields(names?: Array<string>): void;
|
||||||
|
// tslint:disable-next-line:max-line-length
|
||||||
getFieldDecorator(id: string, options?: GetFieldDecoratorOptions): (node: React.ReactNode) => React.ReactNode;
|
getFieldDecorator<T extends Object = {}>(id: keyof T, options?: GetFieldDecoratorOptions): (node: React.ReactNode) => React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FormComponentProps {
|
export interface FormComponentProps {
|
||||||
|
@ -91,6 +91,8 @@ export { default as Row } from './row';
|
|||||||
|
|
||||||
export { default as Select } from './select';
|
export { default as Select } from './select';
|
||||||
|
|
||||||
|
export { default as Skeleton } from './skeleton';
|
||||||
|
|
||||||
export { default as Slider } from './slider';
|
export { default as Slider } from './slider';
|
||||||
|
|
||||||
export { default as Spin } from './spin';
|
export { default as Spin } from './spin';
|
||||||
|
@ -99,7 +99,6 @@ The sidebar.
|
|||||||
| trigger | specify the customized trigger, set to null to hide the trigger | string\|ReactNode | - |
|
| trigger | specify the customized trigger, set to null to hide the trigger | string\|ReactNode | - |
|
||||||
| width | width of the sidebar | number\|string | 200 |
|
| width | width of the sidebar | number\|string | 200 |
|
||||||
| onCollapse | the callback function, executed by clicking the trigger or activating the responsive layout | (collapsed, type) => {} | - |
|
| onCollapse | the callback function, executed by clicking the trigger or activating the responsive layout | (collapsed, type) => {} | - |
|
||||||
| theme | color theme of the sidebar | string: `light` `dark` | `dark` |
|
|
||||||
| onBreakpoint | the callback function, executed when [breakpoints](/components/grid#api) changed | (broken) => {} | - |
|
| onBreakpoint | the callback function, executed when [breakpoints](/components/grid#api) changed | (broken) => {} | - |
|
||||||
|
|
||||||
#### breakpoint width
|
#### breakpoint width
|
||||||
|
@ -365,7 +365,7 @@ exports[`renders ./components/list/demo/infinite-virtualized-load.md correctly 1
|
|||||||
|
|
||||||
exports[`renders ./components/list/demo/loadmore.md correctly 1`] = `
|
exports[`renders ./components/list/demo/loadmore.md correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-list demo-loadmore-list ant-list-split ant-list-loading ant-list-something-after-last-item"
|
class="ant-list demo-loadmore-list ant-list-split ant-list-loading"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-spin-nested-loading"
|
class="ant-spin-nested-loading"
|
||||||
@ -392,18 +392,6 @@ exports[`renders ./components/list/demo/loadmore.md correctly 1`] = `
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
style="text-align:center;margin-top:12px;height:32px;line-height:32px"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="ant-btn"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
loading more
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -14,25 +14,27 @@ title:
|
|||||||
Load more list with `loadMore` property.
|
Load more list with `loadMore` property.
|
||||||
|
|
||||||
````jsx
|
````jsx
|
||||||
import { List, Avatar, Button, Spin } from 'antd';
|
import { List, Avatar, Button, Skeleton } from 'antd';
|
||||||
|
|
||||||
import reqwest from 'reqwest';
|
import reqwest from 'reqwest';
|
||||||
|
|
||||||
const fakeDataUrl = 'https://randomuser.me/api/?results=5&inc=name,gender,email,nat&noinfo';
|
const count = 3;
|
||||||
|
const fakeDataUrl = `https://randomuser.me/api/?results=${count}&inc=name,gender,email,nat&noinfo`;
|
||||||
|
|
||||||
class LoadMoreList extends React.Component {
|
class LoadMoreList extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
loading: true,
|
initLoading: true,
|
||||||
loadingMore: false,
|
loading: false,
|
||||||
showLoadingMore: true,
|
|
||||||
data: [],
|
data: [],
|
||||||
|
list: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getData((res) => {
|
this.getData((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
initLoading: false,
|
||||||
data: res.results,
|
data: res.results,
|
||||||
|
list: res.results,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -51,13 +53,15 @@ class LoadMoreList extends React.Component {
|
|||||||
|
|
||||||
onLoadMore = () => {
|
onLoadMore = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingMore: true,
|
loading: true,
|
||||||
|
list: this.state.data.concat([...new Array(count)].map(() => ({ loading: true, name: {} }))),
|
||||||
});
|
});
|
||||||
this.getData((res) => {
|
this.getData((res) => {
|
||||||
const data = this.state.data.concat(res.results);
|
const data = this.state.data.concat(res.results);
|
||||||
this.setState({
|
this.setState({
|
||||||
data,
|
data,
|
||||||
loadingMore: false,
|
list: data,
|
||||||
|
loading: false,
|
||||||
}, () => {
|
}, () => {
|
||||||
// Resetting window's offsetTop so as to display react-virtualized demo underfloor.
|
// Resetting window's offsetTop so as to display react-virtualized demo underfloor.
|
||||||
// In real scene, you can using public method of react-virtualized:
|
// In real scene, you can using public method of react-virtualized:
|
||||||
@ -68,28 +72,30 @@ class LoadMoreList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, loadingMore, showLoadingMore, data } = this.state;
|
const { initLoading, loading, list } = this.state;
|
||||||
const loadMore = showLoadingMore ? (
|
const loadMore = !initLoading && !loading ? (
|
||||||
<div style={{ textAlign: 'center', marginTop: 12, height: 32, lineHeight: '32px' }}>
|
<div style={{ textAlign: 'center', marginTop: 12, height: 32, lineHeight: '32px' }}>
|
||||||
{loadingMore && <Spin />}
|
<Button onClick={this.onLoadMore}>loading more</Button>
|
||||||
{!loadingMore && <Button onClick={this.onLoadMore}>loading more</Button>}
|
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
className="demo-loadmore-list"
|
className="demo-loadmore-list"
|
||||||
loading={loading}
|
loading={initLoading}
|
||||||
itemLayout="horizontal"
|
itemLayout="horizontal"
|
||||||
loadMore={loadMore}
|
loadMore={loadMore}
|
||||||
dataSource={data}
|
dataSource={list}
|
||||||
renderItem={item => (
|
renderItem={item => (
|
||||||
<List.Item actions={[<a>edit</a>, <a>more</a>]}>
|
<List.Item actions={[<a>edit</a>, <a>more</a>]}>
|
||||||
<List.Item.Meta
|
<Skeleton avatar title={false} loading={item.loading} active>
|
||||||
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
<List.Item.Meta
|
||||||
title={<a href="https://ant.design">{item.name.last}</a>}
|
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
||||||
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
|
title={<a href="https://ant.design">{item.name.last}</a>}
|
||||||
/>
|
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
|
||||||
<div>content</div>
|
/>
|
||||||
|
<div>content</div>
|
||||||
|
</Skeleton>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -179,6 +179,16 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: @border-radius-base;
|
border-radius: @border-radius-base;
|
||||||
z-index: @zindex-dropdown;
|
z-index: @zindex-dropdown;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
top: -7px;
|
||||||
|
left: -6px;
|
||||||
|
right: -6px;
|
||||||
|
bottom: 0;
|
||||||
|
content: ' ';
|
||||||
|
opacity: .0001;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .@{menu-prefix-cls} {
|
> .@{menu-prefix-cls} {
|
||||||
@ -375,6 +385,7 @@
|
|||||||
width: @menu-collapsed-width;
|
width: @menu-collapsed-width;
|
||||||
> .@{menu-prefix-cls}-item,
|
> .@{menu-prefix-cls}-item,
|
||||||
> .@{menu-prefix-cls}-item-group > .@{menu-prefix-cls}-item-group-list > .@{menu-prefix-cls}-item,
|
> .@{menu-prefix-cls}-item-group > .@{menu-prefix-cls}-item-group-list > .@{menu-prefix-cls}-item,
|
||||||
|
> .@{menu-prefix-cls}-item-group > .@{menu-prefix-cls}-item-group-list > .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title,
|
||||||
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
|
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
|
||||||
left: 0;
|
left: 0;
|
||||||
text-overflow: clip;
|
text-overflow: clip;
|
||||||
|
37
components/skeleton/Avatar.tsx
Normal file
37
components/skeleton/Avatar.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
export interface SkeletonAvatarProps {
|
||||||
|
prefixCls?: string;
|
||||||
|
className?: string;
|
||||||
|
style?: object;
|
||||||
|
size?: 'large' | 'small' | 'default';
|
||||||
|
shape?: 'circle'| 'square';
|
||||||
|
}
|
||||||
|
|
||||||
|
class Title extends React.Component<SkeletonAvatarProps, any> {
|
||||||
|
static defaultProps: Partial<SkeletonAvatarProps> = {
|
||||||
|
prefixCls: 'ant-skeleton-avatar',
|
||||||
|
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',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={classNames(prefixCls, className, sizeCls, shapeCls)} style={style} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Title;
|
78
components/skeleton/Paragraph.tsx
Normal file
78
components/skeleton/Paragraph.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { polyfill } from 'react-lifecycles-compat';
|
||||||
|
|
||||||
|
type widthUnit = number | string;
|
||||||
|
|
||||||
|
export interface SkeletonParagraphProps {
|
||||||
|
prefixCls?: string;
|
||||||
|
className?: string;
|
||||||
|
style?: object;
|
||||||
|
width?: widthUnit | Array<widthUnit>;
|
||||||
|
rows?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SkeletonParagraphState {
|
||||||
|
prevProps: SkeletonParagraphProps;
|
||||||
|
widthList: Array<widthUnit>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Paragraph extends React.Component<SkeletonParagraphProps, SkeletonParagraphState> {
|
||||||
|
static defaultProps: Partial<SkeletonParagraphProps> = {
|
||||||
|
prefixCls: 'ant-skeleton-paragraph',
|
||||||
|
};
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(
|
||||||
|
props: SkeletonParagraphProps,
|
||||||
|
state: SkeletonParagraphState,
|
||||||
|
): Partial<SkeletonParagraphState> {
|
||||||
|
const { prevProps } = state;
|
||||||
|
const { width, rows = 2 } = props;
|
||||||
|
|
||||||
|
const newState: Partial<SkeletonParagraphState> = {
|
||||||
|
prevProps: props,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rows !== prevProps.rows || width !== prevProps.width) {
|
||||||
|
// Parse width list
|
||||||
|
let widthList = [];
|
||||||
|
if (width && Array.isArray(width)) {
|
||||||
|
widthList = width;
|
||||||
|
} else if (width && !Array.isArray(width)) {
|
||||||
|
widthList = [];
|
||||||
|
widthList[rows - 1] = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
newState.widthList = widthList;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
state: SkeletonParagraphState = {
|
||||||
|
prevProps: {},
|
||||||
|
widthList: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { widthList } = this.state;
|
||||||
|
const { prefixCls, className, style, rows } = this.props;
|
||||||
|
|
||||||
|
const rowList = [...Array(rows)].map((_, index) => (
|
||||||
|
<li key={index} style={{ width: widthList[index] || '100%' }} />
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
className={classNames(prefixCls, className)}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{rowList}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
polyfill(Paragraph);
|
||||||
|
|
||||||
|
export default Paragraph;
|
28
components/skeleton/Title.tsx
Normal file
28
components/skeleton/Title.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
export interface SkeletonTitleProps {
|
||||||
|
prefixCls?: string;
|
||||||
|
className?: string;
|
||||||
|
style?: object;
|
||||||
|
width?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Title extends React.Component<SkeletonTitleProps, any> {
|
||||||
|
static defaultProps: Partial<SkeletonTitleProps> = {
|
||||||
|
prefixCls: 'ant-skeleton-title',
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { prefixCls, className, width, style } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<h3
|
||||||
|
className={classNames(prefixCls, className)}
|
||||||
|
style={{ width, ...style }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Title;
|
204
components/skeleton/__tests__/__snapshots__/demo.test.js.snap
Normal file
204
components/skeleton/__tests__/__snapshots__/demo.test.js.snap
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`renders ./components/skeleton/demo/active.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-skeleton ant-skeleton-active"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width:38%"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:61%"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/skeleton/demo/basic.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-skeleton"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width:38%"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:61%"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/skeleton/demo/children.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="article"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
Ant Design, a design language
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="ant-btn"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Show Skeleton
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/skeleton/demo/list.md correctly 1`] = `
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="ant-switch"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-switch-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="ant-list ant-list-vertical ant-list-lg ant-list-split"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-spin-nested-loading"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-spin-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-list-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-list-item-content ant-list-item-content-single"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton ant-skeleton-active"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width:38%"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:61%"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-list-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-list-item-content ant-list-item-content-single"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton ant-skeleton-active"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width:38%"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:61%"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-list-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-list-item-content ant-list-item-content-single"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton ant-skeleton-active"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width:38%"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:100%"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width:61%"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
279
components/skeleton/__tests__/__snapshots__/index.test.js.snap
Normal file
279
components/skeleton/__tests__/__snapshots__/index.test.js.snap
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Skeleton avatar shape 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-circle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width: 50%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Skeleton avatar shape 2`] = `
|
||||||
|
<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"
|
||||||
|
style="width: 50%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Skeleton avatar size 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-skeleton ant-skeleton-with-avatar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-header"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-skeleton-avatar ant-skeleton-avatar-sm ant-skeleton-avatar-circle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width: 50%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Skeleton avatar size 2`] = `
|
||||||
|
<div
|
||||||
|
class="ant-skeleton ant-skeleton-with-avatar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-header"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-skeleton-avatar ant-skeleton-avatar-circle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width: 50%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Skeleton avatar size 3`] = `
|
||||||
|
<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-circle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width: 50%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Skeleton paragraph rows 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-skeleton"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width: 38%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 61%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Skeleton paragraph width 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-skeleton"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width: 38%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 93%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Skeleton paragraph width 2`] = `
|
||||||
|
<div
|
||||||
|
class="ant-skeleton"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width: 38%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 28%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 93%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Skeleton title width 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-skeleton"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-skeleton-content"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="ant-skeleton-title"
|
||||||
|
style="width: 93%;"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
class="ant-skeleton-paragraph"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
style="width: 61%;"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
3
components/skeleton/__tests__/demo.test.js
Normal file
3
components/skeleton/__tests__/demo.test.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import demoTest from '../../../tests/shared/demoTest';
|
||||||
|
|
||||||
|
demoTest('skeleton');
|
50
components/skeleton/__tests__/index.test.js
Normal file
50
components/skeleton/__tests__/index.test.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import Skeleton from '..';
|
||||||
|
|
||||||
|
describe('Skeleton', () => {
|
||||||
|
const genSkeleton = props => mount(
|
||||||
|
<Skeleton loading {...props}>
|
||||||
|
Bamboo
|
||||||
|
</Skeleton>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('avatar', () => {
|
||||||
|
it('size', () => {
|
||||||
|
const wrapperSmall = genSkeleton({ avatar: { size: 'small' } });
|
||||||
|
expect(wrapperSmall.render()).toMatchSnapshot();
|
||||||
|
const wrapperDefault = genSkeleton({ avatar: { size: 'default' } });
|
||||||
|
expect(wrapperDefault.render()).toMatchSnapshot();
|
||||||
|
const wrapperLarge = genSkeleton({ avatar: { size: 'large' } });
|
||||||
|
expect(wrapperLarge.render()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shape', () => {
|
||||||
|
const wrapperCircle = genSkeleton({ avatar: { shape: 'circle' } });
|
||||||
|
expect(wrapperCircle.render()).toMatchSnapshot();
|
||||||
|
const wrapperSquare = genSkeleton({ avatar: { shape: 'square' } });
|
||||||
|
expect(wrapperSquare.render()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('title', () => {
|
||||||
|
it('width', () => {
|
||||||
|
const wrapper = genSkeleton({ title: { width: '93%' } });
|
||||||
|
expect(wrapper.render()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('paragraph', () => {
|
||||||
|
it('rows', () => {
|
||||||
|
const wrapper = genSkeleton({ paragraph: { rows: 5 } });
|
||||||
|
expect(wrapper.render()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('width', () => {
|
||||||
|
const wrapperPure = genSkeleton({ paragraph: { width: '93%' } });
|
||||||
|
expect(wrapperPure.render()).toMatchSnapshot();
|
||||||
|
const wrapperList = genSkeleton({ paragraph: { width: ['28%', '93%'] } });
|
||||||
|
expect(wrapperList.render()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
22
components/skeleton/demo/active.md
Normal file
22
components/skeleton/demo/active.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
order: 1
|
||||||
|
title:
|
||||||
|
zh-CN: 动画效果
|
||||||
|
en-US: Active Animation
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
显示动画效果。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Display active animation.
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { Skeleton } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Skeleton active />,
|
||||||
|
mountNode);
|
||||||
|
````
|
22
components/skeleton/demo/basic.md
Normal file
22
components/skeleton/demo/basic.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
order: 0
|
||||||
|
title:
|
||||||
|
zh-CN: 基本
|
||||||
|
en-US: Basic
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
最简单的用法。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Basic usage.
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { Skeleton } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Skeleton />,
|
||||||
|
mountNode);
|
||||||
|
````
|
58
components/skeleton/demo/children.md
Normal file
58
components/skeleton/demo/children.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
order: 2
|
||||||
|
title:
|
||||||
|
zh-CN: 包含子组件
|
||||||
|
en-US: Contains sub component
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
加载占位图包含子组件。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Skeleton contains sub component.
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { Skeleton, Button } from 'antd';
|
||||||
|
|
||||||
|
class Demo extends React.Component {
|
||||||
|
state = {
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
showSkeleton = () => {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({ loading: false });
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="article">
|
||||||
|
<Skeleton loading={this.state.loading}>
|
||||||
|
<div>
|
||||||
|
<h4>Ant Design, a design language</h4>
|
||||||
|
<p>We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.</p>
|
||||||
|
</div>
|
||||||
|
</Skeleton>
|
||||||
|
<Button onClick={this.showSkeleton} disabled={this.state.loading}>
|
||||||
|
Show Skeleton
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
|
````
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.article h4 {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.article button {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
86
components/skeleton/demo/list.md
Normal file
86
components/skeleton/demo/list.md
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
order: 3
|
||||||
|
title:
|
||||||
|
zh-CN: 列表
|
||||||
|
en-US: List
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
在列表组件中使用加载占位符。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Use skeleton in list component.
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { Skeleton, Switch, List, Avatar, Icon } from 'antd';
|
||||||
|
|
||||||
|
const listData = [];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
listData.push({
|
||||||
|
href: 'http://ant.design',
|
||||||
|
title: `ant design part ${i}`,
|
||||||
|
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
|
||||||
|
description: 'Ant Design, a design language for background applications, is refined by Ant UED Team.',
|
||||||
|
content: 'We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const IconText = ({ type, text }) => (
|
||||||
|
<span>
|
||||||
|
<Icon type={type} style={{ marginRight: 8 }} />
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
state = {
|
||||||
|
loading: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (checked) => {
|
||||||
|
this.setState({ loading: !checked });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Switch checked={!loading} onChange={this.onChange} />
|
||||||
|
|
||||||
|
<List
|
||||||
|
itemLayout="vertical"
|
||||||
|
size="large"
|
||||||
|
dataSource={listData}
|
||||||
|
renderItem={item => (
|
||||||
|
<List.Item
|
||||||
|
key={item.title}
|
||||||
|
actions={!loading && [<IconText type="star-o" text="156" />, <IconText type="like-o" text="156" />, <IconText type="message" text="2" />]}
|
||||||
|
extra={!loading && <img width={272} alt="logo" src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png" />}
|
||||||
|
>
|
||||||
|
<Skeleton loading={loading} active>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={<Avatar src={item.avatar} />}
|
||||||
|
title={<a href={item.href}>{item.title}</a>}
|
||||||
|
description={item.description}
|
||||||
|
/>
|
||||||
|
{item.content}
|
||||||
|
</Skeleton>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, mountNode);
|
||||||
|
````
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.skeleton-demo {
|
||||||
|
border: 1px solid #f4f4f4;
|
||||||
|
}
|
||||||
|
</style>
|
44
components/skeleton/index.en-US.md
Normal file
44
components/skeleton/index.en-US.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
category: Components
|
||||||
|
type: Data Entry
|
||||||
|
title: Skeleton
|
||||||
|
cols: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
Provide a placeholder at the place which need waiting for loading.
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
- When resource needs long time to load, like low network speed.
|
||||||
|
- The component contains much information. Such as List or Card.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Skeleton
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| active | Show animation effect | boolean | false |
|
||||||
|
| avatar | Show avatar placeholder | boolean \| [SkeletonAvatarProps](#SkeletonAvatarProps) | false |
|
||||||
|
| loading | Display the skeleton when `true` | boolean | - |
|
||||||
|
| paragraph | Show paragraph placeholder | boolean \| [SkeletonParagraphProps](#SkeletonParagraphProps) | true |
|
||||||
|
| title | Show title placeholder | boolean \| [SkeletonTitleProps](#SkeletonTitleProps) | true |
|
||||||
|
|
||||||
|
### SkeletonAvatarProps
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| size | Set the size of avatar | Enum{ 'large', 'small', 'default' } | - |
|
||||||
|
| shape | Set the shape of avatar | Enum{ 'circle', 'square' } | - |
|
||||||
|
|
||||||
|
### SkeletonTitleProps
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| width | Set the width of title | number \| string | - |
|
||||||
|
|
||||||
|
### SkeletonParagraphProps
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 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> | - |
|
155
components/skeleton/index.tsx
Normal file
155
components/skeleton/index.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Avatar, { SkeletonAvatarProps } from './Avatar';
|
||||||
|
import Title, { SkeletonTitleProps } from './Title';
|
||||||
|
import Paragraph, { SkeletonParagraphProps } from './Paragraph';
|
||||||
|
|
||||||
|
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 { width: '100%' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): SkeletonParagraphProps {
|
||||||
|
const basicProps: SkeletonParagraphProps = {};
|
||||||
|
|
||||||
|
// Width
|
||||||
|
if (hasAvatar && hasTitle) {
|
||||||
|
basicProps.width = '100%';
|
||||||
|
} else {
|
||||||
|
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> = {
|
||||||
|
prefixCls: 'ant-skeleton',
|
||||||
|
avatar: false,
|
||||||
|
title: true,
|
||||||
|
paragraph: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
loading, prefixCls, className, children,
|
||||||
|
avatar, title, paragraph, active,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (loading || !('loading' in this.props)) {
|
||||||
|
const hasAvatar = !!avatar;
|
||||||
|
const hasTitle = !!title;
|
||||||
|
const hasParagraph = !!paragraph;
|
||||||
|
|
||||||
|
// Avatar
|
||||||
|
let avatarNode;
|
||||||
|
if (hasAvatar) {
|
||||||
|
const avatarProps: SkeletonAvatarProps = {
|
||||||
|
...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 = {
|
||||||
|
...getTitleBasicProps(hasAvatar, hasParagraph),
|
||||||
|
...getComponentProps(title),
|
||||||
|
};
|
||||||
|
|
||||||
|
$title = (
|
||||||
|
<Title {...titleProps} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paragraph
|
||||||
|
let paragraphNode;
|
||||||
|
if (hasParagraph) {
|
||||||
|
const paragraphProps: SkeletonParagraphProps = {
|
||||||
|
...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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Skeleton;
|
46
components/skeleton/index.zh-CN.md
Normal file
46
components/skeleton/index.zh-CN.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
category: Components
|
||||||
|
subtitle: 加载占位图
|
||||||
|
type: Data Entry
|
||||||
|
title: Skeleton
|
||||||
|
cols: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
在需要等待加载内容的位置提供一个占位图。
|
||||||
|
|
||||||
|
## 何时使用
|
||||||
|
|
||||||
|
- 网络较慢,需要长时间等待加载处理的情况下。
|
||||||
|
- 图文信息内容较多的列表/卡片中。
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Skeleton
|
||||||
|
|
||||||
|
| 属性 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| active | 是否展示动画效果 | boolean | false |
|
||||||
|
| avatar | 是否显示头像占位图 | boolean \| [SkeletonAvatarProps](#SkeletonAvatarProps) | false |
|
||||||
|
| loading | 为 `true` 时,显示占位图。反之则直接展示子组件 | boolean | - |
|
||||||
|
| paragraph | 是否显示段落占位图 | boolean \| [SkeletonParagraphProps](#SkeletonParagraphProps) | true |
|
||||||
|
| title | 是否显示标题占位图 | boolean \| [SkeletonTitleProps](#SkeletonTitleProps) | true |
|
||||||
|
|
||||||
|
### SkeletonAvatarProps
|
||||||
|
|
||||||
|
| 属性 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| size | 设置头像占位图的大小 | Enum{ 'large', 'small', 'default' } | - |
|
||||||
|
| shape | 指定头像的形状 | Enum{ 'circle', 'square' } | - |
|
||||||
|
|
||||||
|
### SkeletonTitleProps
|
||||||
|
|
||||||
|
| 属性 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| width | 设置标题占位图的宽度 | number \| string | - |
|
||||||
|
|
||||||
|
### SkeletonParagraphProps
|
||||||
|
|
||||||
|
| 属性 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| rows | 设置段落占位图的行数 | number | - |
|
||||||
|
| width | 设置标题占位图的宽度,若为数组时则为对应的每行宽度,反之则是最后一行的宽度 | number \| string \| Array<number \| string> | - |
|
113
components/skeleton/style/index.less
Normal file
113
components/skeleton/style/index.less
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
@import "../../style/themes/default";
|
||||||
|
@import "../../style/mixins/index";
|
||||||
|
|
||||||
|
@skeleton-prefix-cls: ~"@{ant-prefix}-skeleton";
|
||||||
|
@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-to-color: shade(@skeleton-color, 5%);
|
||||||
|
|
||||||
|
.@{skeleton-prefix-cls} {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: top;
|
||||||
|
padding-right: 16px;
|
||||||
|
|
||||||
|
// Avatar
|
||||||
|
.@{skeleton-avatar-prefix-cls} {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
background: @skeleton-color;
|
||||||
|
|
||||||
|
.avatar-size(@avatar-size-base);
|
||||||
|
|
||||||
|
&-lg {
|
||||||
|
.avatar-size(@avatar-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-sm {
|
||||||
|
.avatar-size(@avatar-size-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
// Title
|
||||||
|
.@{skeleton-title-prefix-cls} {
|
||||||
|
margin-top: 16px;
|
||||||
|
height: 16px;
|
||||||
|
width: 100%;
|
||||||
|
background: @skeleton-color;
|
||||||
|
|
||||||
|
+ .@{skeleton-paragraph-prefix-cls} {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// paragraph
|
||||||
|
.@{skeleton-paragraph-prefix-cls} {
|
||||||
|
> li {
|
||||||
|
height: 16px;
|
||||||
|
background: @skeleton-color;
|
||||||
|
|
||||||
|
+ li {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-avatar &-content {
|
||||||
|
// Title
|
||||||
|
.@{skeleton-title-prefix-cls} {
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
+ .@{skeleton-paragraph-prefix-cls} {
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With active animation
|
||||||
|
&.@{skeleton-prefix-cls}-active {
|
||||||
|
& .@{skeleton-prefix-cls}-content {
|
||||||
|
.@{skeleton-title-prefix-cls},
|
||||||
|
.@{skeleton-paragraph-prefix-cls} > li {
|
||||||
|
.skeleton-color();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-size(@size) {
|
||||||
|
width: @size;
|
||||||
|
height: @size;
|
||||||
|
line-height: @size;
|
||||||
|
|
||||||
|
&.@{skeleton-avatar-prefix-cls}-circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-color() {
|
||||||
|
background: linear-gradient(90deg, @skeleton-color 25%, @skeleton-to-color 37%, @skeleton-color 63%);
|
||||||
|
animation: ~"@{skeleton-prefix-cls}-loading" 1.4s ease infinite;
|
||||||
|
background-size: 400% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ~"@{skeleton-prefix-cls}-loading" {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 50%;
|
||||||
|
}
|
||||||
|
}
|
2
components/skeleton/style/index.tsx
Normal file
2
components/skeleton/style/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import '../../style/index.less';
|
||||||
|
import './index.less';
|
@ -18,7 +18,7 @@ subtitle: 加载中
|
|||||||
| delay | 延迟显示加载效果的时间(防止闪烁) | number (毫秒) | - |
|
| delay | 延迟显示加载效果的时间(防止闪烁) | number (毫秒) | - |
|
||||||
| indicator | 加载指示符 | ReactElement | - |
|
| indicator | 加载指示符 | ReactElement | - |
|
||||||
| size | 组件大小,可选值为 `small` `default` `large` | string | 'default' |
|
| size | 组件大小,可选值为 `small` `default` `large` | string | 'default' |
|
||||||
| spinning | 是否旋转 | boolean | true |
|
| spinning | 是否为加载中状态 | boolean | true |
|
||||||
| tip | 当作为包裹元素时,可以自定义描述文案 | string | - |
|
| tip | 当作为包裹元素时,可以自定义描述文案 | string | - |
|
||||||
| wrapperClassName | 包装器的类属性 | string | - |
|
| wrapperClassName | 包装器的类属性 | string | - |
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ The whole of the step bar.
|
|||||||
| progressDot | Steps with progress dot style, customize the progress dot by setting it to a function. labelPlacement will be `vertical` | Boolean or (iconDot, {index, status, title, description}) => ReactNode | false |
|
| progressDot | Steps with progress dot style, customize the progress dot by setting it to a function. labelPlacement will be `vertical` | Boolean or (iconDot, {index, status, title, description}) => ReactNode | false |
|
||||||
| size | to specify the size of the step bar, `default` and `small` are currently supported | string | `default` |
|
| size | to specify the size of the step bar, `default` and `small` are currently supported | string | `default` |
|
||||||
| status | to specify the status of current step, can be set to one of the following values: `wait` `process` `finish` `error` | string | `process` |
|
| status | to specify the status of current step, can be set to one of the following values: `wait` `process` `finish` `error` | string | `process` |
|
||||||
|
| initial | set the initial step, counting from 0 | number | 0 |
|
||||||
|
|
||||||
### Steps.Step
|
### Steps.Step
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ export interface StepsProps {
|
|||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
iconPrefix?: string;
|
iconPrefix?: string;
|
||||||
current?: number;
|
current?: number;
|
||||||
|
initial?: number;
|
||||||
status?: 'wait' | 'process' | 'finish' | 'error';
|
status?: 'wait' | 'process' | 'finish' | 'error';
|
||||||
size?: 'default' | 'small';
|
size?: 'default' | 'small';
|
||||||
direction?: 'horizontal' | 'vertical';
|
direction?: 'horizontal' | 'vertical';
|
||||||
|
@ -34,6 +34,7 @@ title: Steps
|
|||||||
| progressDot | 点状步骤条,可以设置为一个 function,labelPlacement 将强制为`vertical` | Boolean or (iconDot, {index, status, title, description}) => ReactNode | false |
|
| progressDot | 点状步骤条,可以设置为一个 function,labelPlacement 将强制为`vertical` | Boolean or (iconDot, {index, status, title, description}) => ReactNode | false |
|
||||||
| size | 指定大小,目前支持普通(`default`)和迷你(`small`) | string | default |
|
| size | 指定大小,目前支持普通(`default`)和迷你(`small`) | string | default |
|
||||||
| status | 指定当前步骤的状态,可选 `wait` `process` `finish` `error` | string | process |
|
| status | 指定当前步骤的状态,可选 `wait` `process` `finish` `error` | string | process |
|
||||||
|
| initial | 起始序号,从 0 开始记数 | number | 0 |
|
||||||
|
|
||||||
### Steps.Step
|
### Steps.Step
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@
|
|||||||
right: -1px;
|
right: -1px;
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
border: 0 solid @primary-color;
|
border: 0 solid @primary-color;
|
||||||
opacity: 0.4;
|
opacity: 0.2;
|
||||||
animation: waveEffect .4s cubic-bezier(.25, .8, .25, 1);
|
animation: fadeEffect 2.4s @ease-out-circ, waveEffect .48s @ease-out-circ;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes waveEffect {
|
@keyframes waveEffect {
|
||||||
to {
|
100% {
|
||||||
opacity: 0;
|
|
||||||
top: -@wave-animation-width;
|
top: -@wave-animation-width;
|
||||||
left: -@wave-animation-width;
|
left: -@wave-animation-width;
|
||||||
bottom: -@wave-animation-width;
|
bottom: -@wave-animation-width;
|
||||||
@ -34,3 +34,9 @@
|
|||||||
border-width: @wave-animation-width;
|
border-width: @wave-animation-width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fadeEffect {
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -484,6 +484,10 @@
|
|||||||
@collapse-content-padding: @padding-md;
|
@collapse-content-padding: @padding-md;
|
||||||
@collapse-content-bg: @component-background;
|
@collapse-content-bg: @component-background;
|
||||||
|
|
||||||
|
// Skeleton
|
||||||
|
// ---
|
||||||
|
@skeleton-color: #f2f2f2;
|
||||||
|
|
||||||
// Message
|
// Message
|
||||||
// ---
|
// ---
|
||||||
@message-notice-content-padding: 10px 16px;
|
@message-notice-content-padding: 10px 16px;
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
border-radius: @border-radius-base;
|
border-radius: @border-radius-base;
|
||||||
box-shadow: @box-shadow-base;
|
box-shadow: @box-shadow-base;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
word-break: break-all;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrows
|
// Arrows
|
||||||
|
@ -3,7 +3,7 @@ import { AbstractSelectProps } from '../select';
|
|||||||
|
|
||||||
export type TreeNode = TreeNodeNormal | TreeNodeSimpleMode;
|
export type TreeNode = TreeNodeNormal | TreeNodeSimpleMode;
|
||||||
|
|
||||||
interface TreeNodeNormal {
|
export interface TreeNodeNormal {
|
||||||
value: string;
|
value: string;
|
||||||
/**
|
/**
|
||||||
* @deprecated Please use `title` instead.
|
* @deprecated Please use `title` instead.
|
||||||
@ -18,12 +18,12 @@ interface TreeNodeNormal {
|
|||||||
children?: TreeNodeNormal[];
|
children?: TreeNodeNormal[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeNodeSimpleMode {
|
export interface TreeNodeSimpleMode {
|
||||||
/* It is possible to change `id` and `pId` prop keys using TreeDataSimpleMode so those keys can be anything */
|
/* It is possible to change `id` and `pId` prop keys using TreeDataSimpleMode so those keys can be anything */
|
||||||
[key: string]: string | boolean | React.ReactNode;
|
[key: string]: string | boolean | React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeDataSimpleMode {
|
export interface TreeDataSimpleMode {
|
||||||
id?: string;
|
id?: string;
|
||||||
pId?: string;
|
pId?: string;
|
||||||
rootPId?: string;
|
rootPId?: string;
|
||||||
|
@ -39,6 +39,8 @@ export interface AntTreeNodeProps {
|
|||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
icon?: ((treeNode: AntdTreeNodeAttribute) => React.ReactNode) | React.ReactNode;
|
icon?: ((treeNode: AntdTreeNodeAttribute) => React.ReactNode) | React.ReactNode;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
||||||
|
[customProp: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AntTreeNode extends React.Component<AntTreeNodeProps, {}> { }
|
export interface AntTreeNode extends React.Component<AntTreeNodeProps, {}> { }
|
||||||
|
@ -22,7 +22,6 @@ export interface UploadFile {
|
|||||||
status?: UploadFileStatus;
|
status?: UploadFileStatus;
|
||||||
percent?: number;
|
percent?: number;
|
||||||
thumbUrl?: string;
|
thumbUrl?: string;
|
||||||
isNotImage?: boolean;
|
|
||||||
originFileObj?: File;
|
originFileObj?: File;
|
||||||
response?: any;
|
response?: any;
|
||||||
error?: any;
|
error?: any;
|
||||||
|
@ -7,57 +7,106 @@ Ant Design allows you to customize some basic design aspects in order to meet th
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Less variables
|
## Ant Design Less variables
|
||||||
|
|
||||||
We are using [Less](http://lesscss.org/) as the development language for styling. A set of less variables are defined for each design aspect that can be customized to your needs.
|
We are using [Less](http://lesscss.org/) as the development language for styling. A set of less variables are defined for each design aspect that can be customized to your needs.
|
||||||
|
|
||||||
- [Default Variables](https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less)
|
There are some major variables below, all less variables could be found in [Default Variables](https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less).
|
||||||
|
|
||||||
|
```less
|
||||||
|
@primary-color: #1890ff; // primary color for all components
|
||||||
|
@link-color: #1890ff; // link color
|
||||||
|
@success-color: #52c41a; // success state color
|
||||||
|
@warning-color: #faad14; // warning state color
|
||||||
|
@error-color: #f5222d; // error state color
|
||||||
|
@font-size-base: 14px; // major text font size
|
||||||
|
@heading-color: rgba(0, 0, 0, .85); // heading text color
|
||||||
|
@text-color: rgba(0, 0, 0, .65); // major text color
|
||||||
|
@text-color-secondary : rgba(0, 0, 0, .45); // secondary text color
|
||||||
|
@disabled-color : rgba(0, 0, 0, .25); // disable state color
|
||||||
|
@border-radius-base: 4px; // major border radius
|
||||||
|
@border-color-base: #d9d9d9; // major border color
|
||||||
|
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, .15); // major shadow for layers
|
||||||
|
```
|
||||||
|
|
||||||
Please report an issue if the existing list of variables is not enough for you.
|
Please report an issue if the existing list of variables is not enough for you.
|
||||||
|
|
||||||
## How to do it
|
## How to do it
|
||||||
|
|
||||||
We recommend [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) to override the default values of the variables. There are two ways to achieve it in practice.
|
We will use [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) provided by less.js to override the default values of the variables, You can use this [example](https://github.com/ant-design/create-react-app-antd) as a live playground. We now introduce some popular way to do it depends on different workflow.
|
||||||
|
|
||||||
You can use this [example](https://github.com/ant-design/antd-init/tree/master/examples/customize-antd-theme) as a playground.
|
### Customize in webpack
|
||||||
|
|
||||||
### 1) Using `theme` property (recommended way)
|
We take a typical `webpack.config.js` file as example to customize it's [less-loader](https://github.com/webpack-contrib/less-loader) options.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
// webpack.config.js
|
||||||
|
module.exports = {
|
||||||
|
rules: [{
|
||||||
|
test: /\.less$/,
|
||||||
|
use: [{
|
||||||
|
loader: 'style-loader',
|
||||||
|
}, {
|
||||||
|
loader: 'css-loader', // translates CSS into CommonJS
|
||||||
|
}, {
|
||||||
|
loader: 'less-loader', // compiles Less to CSS
|
||||||
|
+ options: {
|
||||||
|
+ modifyVars: {
|
||||||
|
+ 'primary-color': '#1DA57A',
|
||||||
|
+ 'link-color': '#1DA57A',
|
||||||
|
+ 'border-radius-base': '2px',
|
||||||
|
+ },
|
||||||
|
+ javascriptEnabled: true,
|
||||||
|
+ },
|
||||||
|
}],
|
||||||
|
// ...other rules
|
||||||
|
}],
|
||||||
|
// ...other config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that do not exclude antd package in node_modules when using less-loader.
|
||||||
|
|
||||||
|
### Customize in roadhog or Umi
|
||||||
|
|
||||||
|
You can easily use `theme` field in `.webpackrc` file of your project root directory if you are using [roadhog](https://github.com/sorrycc/roadhog) or [Umi](http://umijs.org/),which could be a object or a javascript file path.
|
||||||
|
|
||||||
Specify the `theme` property in the `package.json` or `.webpackrc` file, whose value can be either an object or the path to a JS file that contains the custom values of specific variables:
|
|
||||||
- example of directly specifying the custom values as an object:
|
|
||||||
```js
|
```js
|
||||||
"theme": {
|
"theme": {
|
||||||
"primary-color": "#1DA57A",
|
"primary-color": "#1DA57A",
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
- example of specifying a [file path](https://github.com/ant-design/antd-init/blob/master/examples/customize-antd-theme/theme.js) to a JS file:
|
|
||||||
|
Or just [a javascript file path](https://github.com/ant-design/ant-design-pro/blob/3c2a056ef0dac06ce3b4389192691bb1f5c448e2/.webpackrc.js#L19):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
"theme": "./theme.js",
|
"theme": "./theme.js",
|
||||||
```
|
```
|
||||||
|
|
||||||
This approach is available only when using [antd-init](https://github.com/ant-design/antd-init) or [dva-cli](https://github.com/dvajs/dva-cli). If you choose other boilerplates, you can write a webpack config about [less-loader modifyVars](https://github.com/webpack/less-loader#less-options) like [atool-build ](https://github.com/ant-tool/atool-build/blob/a4b3e3eec4ffc09b0e2352d7f9d279c4c28fdb99/src/getWebpackCommonConfig.js#L131-L138) does.
|
### Customize in create-react-app
|
||||||
|
|
||||||
Note:
|
Follow [Use in create-react-app](/docs/react/create-react-app).
|
||||||
|
|
||||||
- Importing styles from less files is necessary.
|
### Customize in less file
|
||||||
- If you import styles by specifying the `style` option of [babel-plugin-import](https://github.com/ant-design/babel-plugin-import), change it from `'css'` to `true`, which will import the `less` version of antd.
|
|
||||||
- If you import styles from `'antd/dist/antd.css'`, change it to `antd/dist/antd.less`.
|
|
||||||
- When using `dva-cli@0.7.0+`, you should add the `theme` block to [.roadhogrc](https://github.com/dvajs/dva-example-user-dashboard/commit/d6da33b3a6e18eb7f003752a4b00b5a660747c31) instead of `package.json`.
|
|
||||||
- If you want to override `@icon-url`, the value must be contained in quotes like `"@icon-url": "'your-icon-font-path'"` ([A fix sample](https://github.com/visvadw/dvajs-user-dashboard/pull/2)).
|
|
||||||
|
|
||||||
### 2) Overriding Less variables (alternative way)
|
Another approach to customize theme is creating a `less` file within variables to override `antd.less`.
|
||||||
|
|
||||||
Override variables via less definition files.
|
```css
|
||||||
|
@import "~antd/dist/antd.less"; // Import Ant Design styles by less entry
|
||||||
Create a standalone less file like the one below, and import it in your project.
|
@import "your-theme-file.less"; // variables to override above
|
||||||
|
```
|
||||||
```css
|
|
||||||
@import "~antd/dist/antd.less"; // import official less entry file
|
|
||||||
@import "your-theme-file.less"; // override variables here
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: This way will load the styles of all components, regardless of your demand, which cause `style` option of `babel-plugin-import` not working.
|
Note: This way will load the styles of all components, regardless of your demand, which cause `style` option of `babel-plugin-import` not working.
|
||||||
|
|
||||||
|
## Not working?
|
||||||
|
|
||||||
|
You must import styles as less format. A common mistake would be importing multiple copied of styles that some of them are css format to override the less styles.
|
||||||
|
|
||||||
|
- If you import styles by specifying the `style` option of [babel-plugin-import](https://github.com/ant-design/babel-plugin-import), change it from `'css'` to `true`, which will import the `less` version of antd.
|
||||||
|
- If you import styles from `'antd/dist/antd.css'`, change it to `antd/dist/antd.less`.
|
||||||
|
|
||||||
|
If you want to override `@icon-url`, the value must be contained in quotes like `"@icon-url": "'your-icon-font-path'"` ([A fix sample](https://github.com/visvadw/dvajs-user-dashboard/pull/2)).
|
||||||
|
|
||||||
## Related Articles
|
## Related Articles
|
||||||
|
|
||||||
- [Using Ant Design in Sass-Styled Webpack Projects with `antd-scss-theme-plugin`](https://intoli.com/blog/antd-scss-theme-plugin/)
|
- [Using Ant Design in Sass-Styled Webpack Projects with `antd-scss-theme-plugin`](https://intoli.com/blog/antd-scss-theme-plugin/)
|
||||||
|
@ -7,24 +7,69 @@ Ant Design 设计规范上支持一定程度的样式定制,以满足业务和
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 样式变量
|
## Ant Design 的样式变量
|
||||||
|
|
||||||
antd 的样式使用了 [Less](http://lesscss.org/) 作为开发语言,并定义了一系列全局/组件的样式变量,你可以根据需求进行相应调整。
|
antd 的样式使用了 [Less](http://lesscss.org/) 作为开发语言,并定义了一系列全局/组件的样式变量,你可以根据需求进行相应调整。
|
||||||
|
|
||||||
- [默认样式变量](https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less)
|
以下是一些最常用的通用变量,所有样式变量可以在 [这里](https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less) 找到。
|
||||||
|
|
||||||
|
```less
|
||||||
|
@primary-color: #1890ff; // 全局主色
|
||||||
|
@link-color: #1890ff; // 链接色
|
||||||
|
@success-color: #52c41a; // 成功色
|
||||||
|
@warning-color: #faad14; // 警告色
|
||||||
|
@error-color: #f5222d; // 错误色
|
||||||
|
@font-size-base: 14px; // 主字号
|
||||||
|
@heading-color: rgba(0, 0, 0, .85); // 标题色
|
||||||
|
@text-color: rgba(0, 0, 0, .65); // 主文本色
|
||||||
|
@text-color-secondary : rgba(0, 0, 0, .45); // 次文本色
|
||||||
|
@disabled-color : rgba(0, 0, 0, .25); // 失效色
|
||||||
|
@border-radius-base: 4px; // 组件/浮层圆角
|
||||||
|
@border-color-base: #d9d9d9; // 边框色
|
||||||
|
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, .15); // 浮层阴影
|
||||||
|
```
|
||||||
|
|
||||||
如果以上变量不能满足你的定制需求,可以给我们提 issue。
|
如果以上变量不能满足你的定制需求,可以给我们提 issue。
|
||||||
|
|
||||||
## 定制方式
|
## 定制方式
|
||||||
|
|
||||||
我们使用 [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) 的方式来覆盖变量。
|
原理上是使用 less 提供的 [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) 的方式进行覆盖变量,可以在本地运行 [例子](https://github.com/ant-design/create-react-app-antd) 查看定制效果。下面将针对不同的场景提供一些常用的定制方式。
|
||||||
在具体工程实践中,有 `package.theme` 和 `less` 两种方案,选择一种即可。
|
|
||||||
|
|
||||||
可以在本地运行 [例子](https://github.com/ant-design/antd-init/tree/master/examples/customize-antd-theme) 查看定制效果。
|
### 在 webpack 中定制主题
|
||||||
|
|
||||||
### 1) theme 属性(推荐)
|
我们以 webpack@4 为例进行说明,以下是一个 `webpack.config.js` 的典型例子,对 [less-loader](https://github.com/webpack-contrib/less-loader) 的 options 属性进行相应配置。
|
||||||
|
|
||||||
配置在 `package.json` 或 `.webpackrc` 下的 `theme` 字段。theme 可以配置为一个对象或文件路径。
|
```diff
|
||||||
|
// webpack.config.js
|
||||||
|
module.exports = {
|
||||||
|
rules: [{
|
||||||
|
test: /\.less$/,
|
||||||
|
use: [{
|
||||||
|
loader: 'style-loader',
|
||||||
|
}, {
|
||||||
|
loader: 'css-loader', // translates CSS into CommonJS
|
||||||
|
}, {
|
||||||
|
loader: 'less-loader', // compiles Less to CSS
|
||||||
|
+ options: {
|
||||||
|
+ modifyVars: {
|
||||||
|
+ 'primary-color': '#1DA57A',
|
||||||
|
+ 'link-color': '#1DA57A',
|
||||||
|
+ 'border-radius-base': '2px',
|
||||||
|
+ },
|
||||||
|
+ javascriptEnabled: true,
|
||||||
|
+ },
|
||||||
|
}],
|
||||||
|
// ...other rules
|
||||||
|
}],
|
||||||
|
// ...other config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意 less-loader 的处理范围不要过滤掉 `node_modules` 下的 antd 包。
|
||||||
|
|
||||||
|
### 在 roadhog 或 Umi 里配置主题
|
||||||
|
|
||||||
|
如果你在使用 [roadhog](https://github.com/sorrycc/roadhog) 或者 [Umi](http://umijs.org/),那么可以很方便地在项目根目录的 `.webpackrc` 文件中 `theme` 字段进行主题配置。`theme` 可以配置为一个对象或文件路径。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
"theme": {
|
"theme": {
|
||||||
@ -32,34 +77,35 @@ antd 的样式使用了 [Less](http://lesscss.org/) 作为开发语言,并定
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
或者 [一个 js 文件](https://github.com/ant-design/antd-init/blob/master/examples/customize-antd-theme/theme.js):
|
或者 [一个 js 文件](https://github.com/ant-design/ant-design-pro/blob/3c2a056ef0dac06ce3b4389192691bb1f5c448e2/.webpackrc.js#L19):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
"theme": "./theme.js",
|
"theme": "./theme.js",
|
||||||
```
|
```
|
||||||
|
|
||||||
定义 `theme` 属性时,需要配合使用([antd-init](https://github.com/ant-design/antd-init) 或 [dva-cli](https://github.com/dvajs/dva-cli)。如果你使用的是其他脚手架,可以参考 [atool-build 中 less-loader 的 webpack 相关配置 ](https://github.com/ant-tool/atool-build/blob/a4b3e3eec4ffc09b0e2352d7f9d279c4c28fdb99/src/getWebpackCommonConfig.js#L131-L138),利用 [less-loader](https://github.com/webpack/less-loader#less-options) 的 `modifyVars` 配置来覆盖原来的样式变量。
|
### 在 create-react-app 中定制主题
|
||||||
|
|
||||||
注意:
|
参考 [在 create-react-app 中使用](/docs/react/create-react-app) 进行配置即可。
|
||||||
|
|
||||||
- 样式必须加载 less 格式。
|
### 配置 less 变量文件
|
||||||
- 如果你在使用 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) 的 `style` 配置来引入样式,需要将配置值从 `'css'` 改为 `true`,这样会引入 less 文件。
|
|
||||||
- 如果你是通过 `'antd/dist/antd.css'` 引入样式的,改为 `antd/dist/antd.less`。
|
|
||||||
- `dva-cli@0.7.0+` 的 `theme` 属性需要写在 [.roadhogrc](https://github.com/dvajs/dva-example-user-dashboard/commit/d6da33b3a6e18eb7f003752a4b00b5a660747c31) 文件里。
|
|
||||||
- 如果要覆盖 `@icon-url` 变量,内容需要包括引号 `"@icon-url": "'your-icon-font-path'"`([修正示例](https://github.com/visvadw/dvajs-user-dashboard/pull/2))。
|
|
||||||
|
|
||||||
### 2) less
|
另外一种方式是建立一个单独的 `less` 变量文件,引入这个文件覆盖 `antd.less` 里的变量。
|
||||||
|
|
||||||
用 less 文件进行变量覆盖。
|
```css
|
||||||
|
@import "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件
|
||||||
|
@import "your-theme-file.less"; // 用于覆盖上面定义的变量
|
||||||
|
```
|
||||||
|
|
||||||
建立一个单独的 `less` 文件如下,再引入这个文件。
|
注意,这种方式已经载入了所有组件的样式,不需要也无法和按需加载插件 `babel-plugin-import` 的 `style` 属性一起使用。
|
||||||
|
|
||||||
```css
|
## 没有生效?
|
||||||
@import "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件
|
|
||||||
@import "your-theme-file.less"; // 用于覆盖上面定义的变量
|
|
||||||
```
|
|
||||||
|
|
||||||
注意:这种方式已经载入了所有组件的样式,不需要也无法和按需加载插件 `babel-plugin-import` 的 `style` 属性一起使用。
|
注意样式必须加载 less 格式,一个常见的问题就是引入了多份样式,less 的样式被 css 的样式覆盖了。
|
||||||
|
|
||||||
|
- 如果你在使用 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) 的 `style` 配置来引入样式,需要将配置值从 `'css'` 改为 `true`,这样会引入 less 文件。
|
||||||
|
- 如果你是通过 `'antd/dist/antd.css'` 引入样式的,改为 `antd/dist/antd.less`。
|
||||||
|
|
||||||
|
如果要覆盖 `@icon-url` 变量,内容需要包括引号 `"@icon-url": "'your-icon-font-path'"`([修正示例](https://github.com/visvadw/dvajs-user-dashboard/pull/2))。
|
||||||
|
|
||||||
## 社区教程
|
## 社区教程
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ module.exports = {
|
|||||||
'app.footer.company': 'AFX',
|
'app.footer.company': 'AFX',
|
||||||
'app.footer.ant-design': '蚂蚁 UI 体系',
|
'app.footer.ant-design': '蚂蚁 UI 体系',
|
||||||
'app.footer.yuque': '语雀',
|
'app.footer.yuque': '语雀',
|
||||||
'app.footer.yuque.slogan': '企业文档管理平台',
|
'app.footer.yuque.slogan': '知识创作·协作平台',
|
||||||
'app.footer.fengdie': '云凤蝶',
|
'app.footer.fengdie': '云凤蝶',
|
||||||
'app.footer.fengdie.slogan': '移动建站平台',
|
'app.footer.fengdie.slogan': '移动建站平台',
|
||||||
'app.footer.zhihu': '知乎专栏',
|
'app.footer.zhihu': '知乎专栏',
|
||||||
|
@ -41,6 +41,7 @@ Array [
|
|||||||
"Rate",
|
"Rate",
|
||||||
"Row",
|
"Row",
|
||||||
"Select",
|
"Select",
|
||||||
|
"Skeleton",
|
||||||
"Slider",
|
"Slider",
|
||||||
"Spin",
|
"Spin",
|
||||||
"Steps",
|
"Steps",
|
||||||
|
Loading…
Reference in New Issue
Block a user