mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 02:59:04 +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",
|
||||
"Row",
|
||||
"Select",
|
||||
"Skeleton",
|
||||
"Slider",
|
||||
"Spin",
|
||||
"Steps",
|
||||
|
@ -7,6 +7,8 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
||||
cancel: () => void;
|
||||
};
|
||||
|
||||
private extraNode: HTMLDivElement;
|
||||
private clickWaveTimeoutId: number;
|
||||
private styleForPesudo: HTMLStyleElement | null;
|
||||
|
||||
isNotGrey(color: string) {
|
||||
@ -17,22 +19,17 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
||||
return true;
|
||||
}
|
||||
|
||||
onClick = (node: HTMLElement) => {
|
||||
onClick = (node: HTMLElement, waveColor: string) => {
|
||||
if (node.className.indexOf('-leave') >= 0) {
|
||||
return;
|
||||
}
|
||||
this.removeExtraStyleNode();
|
||||
const { insertExtraNode } = this.props;
|
||||
const extraNode = document.createElement('div');
|
||||
this.extraNode = document.createElement('div');
|
||||
const extraNode = this.extraNode;
|
||||
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.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
|
||||
if (waveColor &&
|
||||
waveColor !== '#ffffff' &&
|
||||
@ -49,19 +46,13 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
||||
if (insertExtraNode) {
|
||||
node.appendChild(extraNode);
|
||||
}
|
||||
const transitionEnd = () => {
|
||||
node.removeAttribute(attributeName);
|
||||
this.removeExtraStyleNode();
|
||||
if (insertExtraNode) {
|
||||
node.removeChild(extraNode);
|
||||
}
|
||||
TransitionEvents.removeEndEventListener(node, transitionEnd);
|
||||
};
|
||||
TransitionEvents.addEndEventListener(node, transitionEnd);
|
||||
TransitionEvents.addEndEventListener(node, this.onTransitionEnd);
|
||||
}
|
||||
|
||||
bindAnimationEvent = (node: HTMLElement) => {
|
||||
if (node.getAttribute('disabled') ||
|
||||
if (!node ||
|
||||
!node.getAttribute ||
|
||||
node.getAttribute('disabled') ||
|
||||
node.className.indexOf('disabled') >= 0) {
|
||||
return;
|
||||
}
|
||||
@ -70,7 +61,13 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
||||
if ((e.target as HTMLElement).tagName === 'INPUT') {
|
||||
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);
|
||||
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() {
|
||||
if (this.styleForPesudo && document.body.contains(this.styleForPesudo)) {
|
||||
document.body.removeChild(this.styleForPesudo);
|
||||
@ -95,6 +115,9 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
|
||||
if (this.instance) {
|
||||
this.instance.cancel();
|
||||
}
|
||||
if (this.clickWaveTimeoutId) {
|
||||
clearTimeout(this.clickWaveTimeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { render, mount } from 'enzyme';
|
||||
import renderer from 'react-test-renderer';
|
||||
import Button from '..';
|
||||
import Icon from '../../icon';
|
||||
|
||||
@ -11,6 +12,16 @@ describe('Button', () => {
|
||||
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', () => {
|
||||
const wrapper = render(
|
||||
<Button>按钮</Button>
|
||||
|
@ -7,7 +7,7 @@ title:
|
||||
|
||||
## zh-CN
|
||||
|
||||
幽灵按钮将其他按钮的内容反色,背景变为透明,常用在有色背景上。
|
||||
幽灵按钮将按钮的内容反色,背景变为透明,常用在有色背景上。
|
||||
|
||||
## en-US
|
||||
|
||||
|
@ -372,174 +372,83 @@ exports[`renders ./components/card/demo/inner.md correctly 1`] = `
|
||||
|
||||
exports[`renders ./components/card/demo/loading.md correctly 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ant-card ant-card-loading ant-card-bordered"
|
||||
<span
|
||||
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
|
||||
class="ant-card-body"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-content"
|
||||
class="ant-skeleton ant-skeleton-with-avatar ant-skeleton-active"
|
||||
>
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-4px;margin-right:-4px"
|
||||
class="ant-skeleton-header"
|
||||
>
|
||||
<div
|
||||
class="ant-col-22"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-4px;margin-right:-4px"
|
||||
class="ant-skeleton-content"
|
||||
>
|
||||
<div
|
||||
class="ant-col-8"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
<h3
|
||||
class="ant-skeleton-title"
|
||||
style="width:50%"
|
||||
/>
|
||||
<ul
|
||||
class="ant-skeleton-paragraph"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
<li
|
||||
style="width:100%"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col-15"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
<li
|
||||
style="width:100%"
|
||||
/>
|
||||
</div>
|
||||
</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>
|
||||
</ul>
|
||||
</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>
|
||||
<button
|
||||
class="ant-btn"
|
||||
style="margin-top:16px"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Toggle loading
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -14,30 +14,42 @@ title:
|
||||
Shows a loading indicator while the contents of the card is being fetched.
|
||||
|
||||
````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 = {
|
||||
loading: true,
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.setState({ loading: !this.state.loading });
|
||||
onChange = (checked) => {
|
||||
this.setState({ loading: !checked });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card loading={this.state.loading} title="Card title">
|
||||
Whatever content
|
||||
<Switch checked={!loading} onChange={this.onChange} />
|
||||
|
||||
<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>
|
||||
<Button onClick={this.handleClick} style={{ marginTop: 16 }}>Toggle loading</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<LoadingCard />,
|
||||
mountNode);
|
||||
ReactDOM.render(<App />, mountNode);
|
||||
````
|
||||
|
@ -87,7 +87,7 @@ class WeekPicker extends React.Component<any, any> {
|
||||
const {
|
||||
prefixCls, className, disabled, pickerClass, popupStyle,
|
||||
pickerInputClass, format, allowClear, locale, localeCode, disabledDate,
|
||||
style, onFocus, onBlur,
|
||||
style, onFocus, onBlur, id,
|
||||
} = this.props;
|
||||
|
||||
const pickerValue = this.state.value;
|
||||
@ -129,7 +129,6 @@ class WeekPicker extends React.Component<any, any> {
|
||||
className={pickerInputClass}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
style={style}
|
||||
/>
|
||||
{clearIcon}
|
||||
<Icon type="calendar" className={`${prefixCls}-picker-icon`}/>
|
||||
@ -137,7 +136,11 @@ class WeekPicker extends React.Component<any, any> {
|
||||
);
|
||||
};
|
||||
return (
|
||||
<span className={classNames(className, pickerClass)} id={this.props.id}>
|
||||
<span
|
||||
className={classNames(className, pickerClass)}
|
||||
style={style}
|
||||
id={id}
|
||||
>
|
||||
<RcDatePicker
|
||||
{...this.props}
|
||||
calendar={calendar}
|
||||
|
@ -3,13 +3,13 @@
|
||||
exports[`WeekPicker should support style prop 1`] = `
|
||||
<span
|
||||
class="ant-calendar-picker"
|
||||
style="width: 400px;"
|
||||
>
|
||||
<span>
|
||||
<input
|
||||
class="ant-calendar-picker-input ant-input"
|
||||
placeholder="Select date"
|
||||
readonly=""
|
||||
style="width: 400px;"
|
||||
value=""
|
||||
/>
|
||||
<i
|
||||
|
@ -16,6 +16,20 @@ describe('Drawer', () => {
|
||||
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', () => {
|
||||
const wrapper = render(
|
||||
<Drawer
|
||||
|
@ -42,7 +42,6 @@ class MultiDrawer extends React.Component {
|
||||
title="Multi-level drawer"
|
||||
className="test_drawer"
|
||||
width={520}
|
||||
closable={false}
|
||||
onClose={this.onClose}
|
||||
getContainer={false}
|
||||
placement={placement}
|
||||
@ -54,7 +53,7 @@ class MultiDrawer extends React.Component {
|
||||
<Drawer
|
||||
title="Two-level Drawer"
|
||||
width={320}
|
||||
closable={false}
|
||||
className="Two-level"
|
||||
getContainer={false}
|
||||
placement={placement}
|
||||
onClose={this.onChildrenDrawerClose}
|
||||
@ -105,12 +104,23 @@ describe('Drawer', () => {
|
||||
expect(wrapper.find('#two_drawer_text').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('render right MultiDrawer', () => {
|
||||
it('render left MultiDrawer', () => {
|
||||
const wrapper = mount(<MultiDrawer placement="left" />);
|
||||
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('translateX(180px)');
|
||||
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>
|
||||
`;
|
||||
|
||||
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 | - |
|
||||
| visible | Whether the Drawer dialog is visible or not. | boolean | false |
|
||||
| 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 | - |
|
||||
| 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) | - |
|
||||
|
||||
<style>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import RcDrawer from 'rc-drawer';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import RcDrawer from 'rc-drawer';
|
||||
import createReactContext, { Context } from 'create-react-context';
|
||||
import warning from 'warning';
|
||||
import classNames from 'classnames';
|
||||
@ -14,6 +14,7 @@ type EventType =
|
||||
|
||||
type getContainerfunc = () => HTMLElement;
|
||||
|
||||
type placementType = 'top' | 'right' | 'bottom' | 'left';
|
||||
export interface DrawerProps {
|
||||
closable?: boolean;
|
||||
destroyOnClose?: boolean;
|
||||
@ -25,12 +26,13 @@ export interface DrawerProps {
|
||||
title?: React.ReactNode;
|
||||
visible?: boolean;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
/* deprecated, use className instead */
|
||||
wrapClassName?: string;
|
||||
zIndex?: number;
|
||||
prefixCls?: string;
|
||||
push?: boolean;
|
||||
placement?: 'left' | 'right';
|
||||
placement?: placementType;
|
||||
onClose?: (e: EventType) => void;
|
||||
className?: string;
|
||||
}
|
||||
@ -66,6 +68,7 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
||||
static defaultProps = {
|
||||
prefixCls: 'ant-drawer',
|
||||
width: 256,
|
||||
height: 256,
|
||||
closable: true,
|
||||
placement: 'right',
|
||||
maskClosable: true,
|
||||
@ -76,14 +79,14 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
||||
push: false,
|
||||
};
|
||||
|
||||
praentDrawer: Drawer;
|
||||
parentDrawer: Drawer;
|
||||
destoryClose: boolean;
|
||||
public componentDidUpdate(preProps: DrawerProps) {
|
||||
if (preProps.visible !== this.props.visible && this.praentDrawer) {
|
||||
if (preProps.visible !== this.props.visible && this.parentDrawer) {
|
||||
if (this.props.visible) {
|
||||
this.praentDrawer.push();
|
||||
this.parentDrawer.push();
|
||||
} 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);
|
||||
|
||||
// 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 = () => {
|
||||
if (this.destoryClose && !this.props.visible) {
|
||||
return null;
|
||||
@ -138,12 +151,15 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
||||
} : {};
|
||||
|
||||
const isDestroyOnClose = this.getDestoryOnClose();
|
||||
|
||||
if (isDestroyOnClose) {
|
||||
// Increase the opacity transition, delete children after closing.
|
||||
containerStyle.opacity = 0;
|
||||
containerStyle.transition = 'opacity .3s';
|
||||
}
|
||||
const { prefixCls, title, closable } = this.props;
|
||||
|
||||
// is have header dom
|
||||
let header;
|
||||
if (title) {
|
||||
header = (
|
||||
@ -152,6 +168,7 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// is have closer button
|
||||
let closer;
|
||||
if (closable) {
|
||||
closer = (
|
||||
@ -181,26 +198,40 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
||||
</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) => {
|
||||
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.');
|
||||
const RcDrawerStyle = this.state.push
|
||||
? {
|
||||
zIndex,
|
||||
transform: `translateX(${placement === 'left' ? 180 : -180}px)`,
|
||||
}
|
||||
: { zIndex };
|
||||
this.praentDrawer = value;
|
||||
|
||||
this.parentDrawer = value;
|
||||
const offsetStyle: any = {};
|
||||
if (placement === 'left' || placement === 'right') {
|
||||
offsetStyle.width = width;
|
||||
} else {
|
||||
offsetStyle.height = height;
|
||||
}
|
||||
return (
|
||||
<DrawerContext.Provider value={this}>
|
||||
<RcDrawer
|
||||
{...rest}
|
||||
handler={false}
|
||||
{...rest}
|
||||
{...offsetStyle}
|
||||
open={this.props.visible}
|
||||
onMaskClick={this.onMaskClick}
|
||||
showMask={this.props.mask}
|
||||
placement={placement}
|
||||
style={RcDrawerStyle}
|
||||
style={this.getRcDrawerStyle()}
|
||||
className={classNames(wrapClassName, className)}
|
||||
>
|
||||
{this.renderBody()}
|
||||
@ -208,6 +239,7 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
|
||||
</DrawerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DrawerContext.Consumer>{this.renderProvider}</DrawerContext.Consumer>
|
||||
|
@ -28,9 +28,10 @@ title: Drawer
|
||||
| title | 标题 | string \| ReactNode | - |
|
||||
| visible | Drawer 是否可见 | boolean | - |
|
||||
| width | 宽度 | string \| number | 256 |
|
||||
| height | 高度, 在 `placement` 为 `top` 或 `bottom` 时使用 | string \| number | 256 |
|
||||
| className | 对话框外层容器的类名 | string | - |
|
||||
| zIndex | 设置 Drawer 的 `z-index` | Number | 1000 |
|
||||
| placement | 抽屉的方向 | 'left' \| 'right' | 'right'
|
||||
| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right'
|
||||
| onClose | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 |
|
||||
|
||||
<style>
|
||||
|
@ -6,8 +6,8 @@
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
z-index: @zindex-modal;
|
||||
transition: transform @animation-duration-slow @ease-base-in;
|
||||
> * {
|
||||
transition: transform @animation-duration-slow @ease-base-in;
|
||||
}
|
||||
@ -18,8 +18,6 @@
|
||||
.@{dawer-prefix-cls}-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity @animation-duration-slow linear;
|
||||
}
|
||||
&-left,
|
||||
&-right {
|
||||
@ -54,10 +52,14 @@
|
||||
|
||||
&-top,
|
||||
&-bottom {
|
||||
.@{dawer-prefix-cls}-content-wrapper,
|
||||
.@{dawer-prefix-cls}-content {
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
.@{dawer-prefix-cls}-content-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
&.@{dawer-prefix-cls}-open {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
&-top {
|
||||
&.@{dawer-prefix-cls}-open {
|
||||
@ -81,13 +83,10 @@
|
||||
|
||||
&.@{dawer-prefix-cls}-open {
|
||||
.@{dawer-prefix-cls} {
|
||||
&-content {
|
||||
opacity: 1;
|
||||
}
|
||||
&-mask {
|
||||
opacity: 0.3;
|
||||
height: 100%;
|
||||
animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out;
|
||||
animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@ -170,9 +169,6 @@
|
||||
}
|
||||
&-open {
|
||||
transition: transform @animation-duration-slow @ease-base-out;
|
||||
> * {
|
||||
transition: transform @animation-duration-slow @ease-base-out;
|
||||
}
|
||||
&-content {
|
||||
box-shadow: @shadow-2;
|
||||
}
|
||||
|
@ -117,8 +117,8 @@ export type WrappedFormUtils = {
|
||||
isFieldsTouched(names?: Array<string>): boolean;
|
||||
/** 重置一组输入控件的值与状态,如不传入参数,则重置所有组件 */
|
||||
resetFields(names?: Array<string>): void;
|
||||
|
||||
getFieldDecorator(id: string, options?: GetFieldDecoratorOptions): (node: React.ReactNode) => React.ReactNode;
|
||||
// tslint:disable-next-line:max-line-length
|
||||
getFieldDecorator<T extends Object = {}>(id: keyof T, options?: GetFieldDecoratorOptions): (node: React.ReactNode) => React.ReactNode;
|
||||
};
|
||||
|
||||
export interface FormComponentProps {
|
||||
|
@ -91,6 +91,8 @@ export { default as Row } from './row';
|
||||
|
||||
export { default as Select } from './select';
|
||||
|
||||
export { default as Skeleton } from './skeleton';
|
||||
|
||||
export { default as Slider } from './slider';
|
||||
|
||||
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 | - |
|
||||
| width | width of the sidebar | number\|string | 200 |
|
||||
| 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) => {} | - |
|
||||
|
||||
#### 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`] = `
|
||||
<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
|
||||
class="ant-spin-nested-loading"
|
||||
@ -392,18 +392,6 @@ exports[`renders ./components/list/demo/loadmore.md correctly 1`] = `
|
||||
/>
|
||||
</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>
|
||||
`;
|
||||
|
||||
|
@ -14,25 +14,27 @@ title:
|
||||
Load more list with `loadMore` property.
|
||||
|
||||
````jsx
|
||||
import { List, Avatar, Button, Spin } from 'antd';
|
||||
import { List, Avatar, Button, Skeleton } from 'antd';
|
||||
|
||||
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 {
|
||||
state = {
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
showLoadingMore: true,
|
||||
initLoading: true,
|
||||
loading: false,
|
||||
data: [],
|
||||
list: [],
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getData((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
initLoading: false,
|
||||
data: res.results,
|
||||
list: res.results,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -51,13 +53,15 @@ class LoadMoreList extends React.Component {
|
||||
|
||||
onLoadMore = () => {
|
||||
this.setState({
|
||||
loadingMore: true,
|
||||
loading: true,
|
||||
list: this.state.data.concat([...new Array(count)].map(() => ({ loading: true, name: {} }))),
|
||||
});
|
||||
this.getData((res) => {
|
||||
const data = this.state.data.concat(res.results);
|
||||
this.setState({
|
||||
data,
|
||||
loadingMore: false,
|
||||
list: data,
|
||||
loading: false,
|
||||
}, () => {
|
||||
// Resetting window's offsetTop so as to display react-virtualized demo underfloor.
|
||||
// In real scene, you can using public method of react-virtualized:
|
||||
@ -68,28 +72,30 @@ class LoadMoreList extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, loadingMore, showLoadingMore, data } = this.state;
|
||||
const loadMore = showLoadingMore ? (
|
||||
const { initLoading, loading, list } = this.state;
|
||||
const loadMore = !initLoading && !loading ? (
|
||||
<div style={{ textAlign: 'center', marginTop: 12, height: 32, lineHeight: '32px' }}>
|
||||
{loadingMore && <Spin />}
|
||||
{!loadingMore && <Button onClick={this.onLoadMore}>loading more</Button>}
|
||||
<Button onClick={this.onLoadMore}>loading more</Button>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<List
|
||||
className="demo-loadmore-list"
|
||||
loading={loading}
|
||||
loading={initLoading}
|
||||
itemLayout="horizontal"
|
||||
loadMore={loadMore}
|
||||
dataSource={data}
|
||||
dataSource={list}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a>edit</a>, <a>more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
||||
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>
|
||||
<Skeleton avatar title={false} loading={item.loading} active>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
||||
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>
|
||||
</Skeleton>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
|
@ -179,6 +179,16 @@
|
||||
position: absolute;
|
||||
border-radius: @border-radius-base;
|
||||
z-index: @zindex-dropdown;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: -6px;
|
||||
right: -6px;
|
||||
bottom: 0;
|
||||
content: ' ';
|
||||
opacity: .0001;
|
||||
}
|
||||
}
|
||||
|
||||
> .@{menu-prefix-cls} {
|
||||
@ -375,6 +385,7 @@
|
||||
width: @menu-collapsed-width;
|
||||
> .@{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 {
|
||||
left: 0;
|
||||
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 (毫秒) | - |
|
||||
| indicator | 加载指示符 | ReactElement | - |
|
||||
| size | 组件大小,可选值为 `small` `default` `large` | string | 'default' |
|
||||
| spinning | 是否旋转 | boolean | true |
|
||||
| spinning | 是否为加载中状态 | boolean | true |
|
||||
| tip | 当作为包裹元素时,可以自定义描述文案 | 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 |
|
||||
| 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` |
|
||||
| initial | set the initial step, counting from 0 | number | 0 |
|
||||
|
||||
### Steps.Step
|
||||
|
||||
|
@ -6,6 +6,7 @@ export interface StepsProps {
|
||||
prefixCls?: string;
|
||||
iconPrefix?: string;
|
||||
current?: number;
|
||||
initial?: number;
|
||||
status?: 'wait' | 'process' | 'finish' | 'error';
|
||||
size?: 'default' | 'small';
|
||||
direction?: 'horizontal' | 'vertical';
|
||||
|
@ -34,6 +34,7 @@ title: Steps
|
||||
| progressDot | 点状步骤条,可以设置为一个 function,labelPlacement 将强制为`vertical` | Boolean or (iconDot, {index, status, title, description}) => ReactNode | false |
|
||||
| size | 指定大小,目前支持普通(`default`)和迷你(`small`) | string | default |
|
||||
| status | 指定当前步骤的状态,可选 `wait` `process` `finish` `error` | string | process |
|
||||
| initial | 起始序号,从 0 开始记数 | number | 0 |
|
||||
|
||||
### Steps.Step
|
||||
|
||||
|
@ -19,14 +19,14 @@
|
||||
right: -1px;
|
||||
border-radius: inherit;
|
||||
border: 0 solid @primary-color;
|
||||
opacity: 0.4;
|
||||
animation: waveEffect .4s cubic-bezier(.25, .8, .25, 1);
|
||||
opacity: 0.2;
|
||||
animation: fadeEffect 2.4s @ease-out-circ, waveEffect .48s @ease-out-circ;
|
||||
animation-fill-mode: forwards;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes waveEffect {
|
||||
to {
|
||||
opacity: 0;
|
||||
100% {
|
||||
top: -@wave-animation-width;
|
||||
left: -@wave-animation-width;
|
||||
bottom: -@wave-animation-width;
|
||||
@ -34,3 +34,9 @@
|
||||
border-width: @wave-animation-width;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeEffect {
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
@ -484,6 +484,10 @@
|
||||
@collapse-content-padding: @padding-md;
|
||||
@collapse-content-bg: @component-background;
|
||||
|
||||
// Skeleton
|
||||
// ---
|
||||
@skeleton-color: #f2f2f2;
|
||||
|
||||
// Message
|
||||
// ---
|
||||
@message-notice-content-padding: 10px 16px;
|
||||
|
@ -47,7 +47,7 @@
|
||||
border-radius: @border-radius-base;
|
||||
box-shadow: @box-shadow-base;
|
||||
min-height: 32px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
// Arrows
|
||||
|
@ -3,7 +3,7 @@ import { AbstractSelectProps } from '../select';
|
||||
|
||||
export type TreeNode = TreeNodeNormal | TreeNodeSimpleMode;
|
||||
|
||||
interface TreeNodeNormal {
|
||||
export interface TreeNodeNormal {
|
||||
value: string;
|
||||
/**
|
||||
* @deprecated Please use `title` instead.
|
||||
@ -18,12 +18,12 @@ interface 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 */
|
||||
[key: string]: string | boolean | React.ReactNode;
|
||||
}
|
||||
|
||||
interface TreeDataSimpleMode {
|
||||
export interface TreeDataSimpleMode {
|
||||
id?: string;
|
||||
pId?: string;
|
||||
rootPId?: string;
|
||||
|
@ -39,6 +39,8 @@ export interface AntTreeNodeProps {
|
||||
selectable?: boolean;
|
||||
icon?: ((treeNode: AntdTreeNodeAttribute) => React.ReactNode) | React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
|
||||
[customProp: string]: any;
|
||||
}
|
||||
|
||||
export interface AntTreeNode extends React.Component<AntTreeNodeProps, {}> { }
|
||||
|
@ -22,7 +22,6 @@ export interface UploadFile {
|
||||
status?: UploadFileStatus;
|
||||
percent?: number;
|
||||
thumbUrl?: string;
|
||||
isNotImage?: boolean;
|
||||
originFileObj?: File;
|
||||
response?: any;
|
||||
error?: any;
|
||||
|
@ -7,57 +7,106 @@ Ant Design allows you to customize some basic design aspects in order to meet th
|
||||
|
||||
![customized themes](https://zos.alipayobjects.com/rmsportal/zTFoszBtDODhXfLAazfSpYbSLSEeytoG.png)
|
||||
|
||||
## 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.
|
||||
|
||||
- [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.
|
||||
|
||||
## 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
|
||||
"theme": {
|
||||
"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
|
||||
"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.
|
||||
- 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)).
|
||||
### Customize in less file
|
||||
|
||||
### 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.
|
||||
|
||||
Create a standalone less file like the one below, and import it in your project.
|
||||
|
||||
```css
|
||||
@import "~antd/dist/antd.less"; // import official less entry file
|
||||
@import "your-theme-file.less"; // override variables here
|
||||
```
|
||||
```css
|
||||
@import "~antd/dist/antd.less"; // Import Ant Design styles by less entry
|
||||
@import "your-theme-file.less"; // variables to override above
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
- [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 设计规范上支持一定程度的样式定制,以满足业务和
|
||||
|
||||
![一些配置好的主题](https://zos.alipayobjects.com/rmsportal/zTFoszBtDODhXfLAazfSpYbSLSEeytoG.png)
|
||||
|
||||
## 样式变量
|
||||
## Ant Design 的样式变量
|
||||
|
||||
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。
|
||||
|
||||
## 定制方式
|
||||
|
||||
我们使用 [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) 的方式来覆盖变量。
|
||||
在具体工程实践中,有 `package.theme` 和 `less` 两种方案,选择一种即可。
|
||||
原理上是使用 less 提供的 [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) 的方式进行覆盖变量,可以在本地运行 [例子](https://github.com/ant-design/create-react-app-antd) 查看定制效果。下面将针对不同的场景提供一些常用的定制方式。
|
||||
|
||||
可以在本地运行 [例子](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
|
||||
"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
|
||||
"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 格式。
|
||||
- 如果你在使用 [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))。
|
||||
### 配置 less 变量文件
|
||||
|
||||
### 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.ant-design': '蚂蚁 UI 体系',
|
||||
'app.footer.yuque': '语雀',
|
||||
'app.footer.yuque.slogan': '企业文档管理平台',
|
||||
'app.footer.yuque.slogan': '知识创作·协作平台',
|
||||
'app.footer.fengdie': '云凤蝶',
|
||||
'app.footer.fengdie.slogan': '移动建站平台',
|
||||
'app.footer.zhihu': '知乎专栏',
|
||||
|
@ -41,6 +41,7 @@ Array [
|
||||
"Rate",
|
||||
"Row",
|
||||
"Select",
|
||||
"Skeleton",
|
||||
"Slider",
|
||||
"Spin",
|
||||
"Steps",
|
||||
|
Loading…
Reference in New Issue
Block a user