mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 11:08:45 +08:00
Feature 3.5.0 time line reverse api (#10057)
* Add reverse api for TimeLine * Update TimeLine test snapshot * Add specifications for reverse api and pending api
This commit is contained in:
parent
754c94c447
commit
d016471638
@ -10,28 +10,30 @@ export interface TimelineProps {
|
||||
pending?: React.ReactNode;
|
||||
pendingDot?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
reverse?: boolean;
|
||||
}
|
||||
|
||||
export default class Timeline extends React.Component<TimelineProps, any> {
|
||||
static Item = TimelineItem as React.ClassicComponentClass<TimeLineItemProps>;
|
||||
static defaultProps = {
|
||||
prefixCls: 'ant-timeline',
|
||||
reverse: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { prefixCls, children, pending, pendingDot, className, ...restProps } = this.props;
|
||||
const {
|
||||
prefixCls,
|
||||
pending = null, pendingDot,
|
||||
children, className, reverse,
|
||||
...restProps,
|
||||
} = this.props;
|
||||
const pendingNode = typeof pending === 'boolean' ? null : pending;
|
||||
const classString = classNames(prefixCls, {
|
||||
[`${prefixCls}-pending`]: !!pending,
|
||||
[`${prefixCls}-reverse`]: !!reverse,
|
||||
}, className);
|
||||
// Remove falsy items
|
||||
const falsylessItems = React.Children.toArray(children).filter(item => !!item);
|
||||
const items = React.Children.map(falsylessItems, (ele: React.ReactElement<any>, idx) =>
|
||||
React.cloneElement(ele, {
|
||||
last: idx === (React.Children.count(falsylessItems) - 1),
|
||||
}),
|
||||
);
|
||||
const pendingItem = (!!pending) ? (
|
||||
|
||||
const pendingItem = !!pending ? (
|
||||
<TimelineItem
|
||||
pending={!!pending}
|
||||
dot={pendingDot || <Icon type="loading" />}
|
||||
@ -39,10 +41,29 @@ export default class Timeline extends React.Component<TimelineProps, any> {
|
||||
{pendingNode}
|
||||
</TimelineItem>
|
||||
) : null;
|
||||
|
||||
const timeLineItems = !!reverse
|
||||
? [pendingItem, ...React.Children.toArray(children).reverse()]
|
||||
: [...React.Children.toArray(children), pendingItem];
|
||||
|
||||
// Remove falsy items
|
||||
const falsylessItems = timeLineItems.filter(item => !!item);
|
||||
const itemsCount = React.Children.count(falsylessItems);
|
||||
const lastCls = `${prefixCls}-item-last`;
|
||||
const items = React.Children.map(falsylessItems, (ele: React.ReactElement<any>, idx) =>
|
||||
React.cloneElement(ele, {
|
||||
className: classNames([
|
||||
ele.props.className,
|
||||
(!reverse && !!pending)
|
||||
? (idx === itemsCount - 2) ? lastCls : ''
|
||||
: (idx === itemsCount - 1) ? lastCls : '',
|
||||
]),
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<ul {...restProps} className={classString}>
|
||||
{items}
|
||||
{pendingItem}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ export interface TimeLineItemProps {
|
||||
color?: string;
|
||||
dot?: React.ReactNode;
|
||||
pending?: boolean;
|
||||
last?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
@ -15,16 +14,14 @@ export default class TimelineItem extends React.Component<TimeLineItemProps, any
|
||||
static defaultProps = {
|
||||
prefixCls: 'ant-timeline',
|
||||
color: 'blue',
|
||||
last: false,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { prefixCls, className, color = '', last, children, pending, dot, ...restProps } = this.props;
|
||||
const { prefixCls, className, color = '', children, pending, dot, ...restProps } = this.props;
|
||||
|
||||
const itemClassName = classNames({
|
||||
[`${prefixCls}-item`]: true,
|
||||
[`${prefixCls}-item-last`]: last,
|
||||
[`${prefixCls}-item-pending`]: pending,
|
||||
}, className);
|
||||
|
||||
|
@ -223,72 +223,83 @@ exports[`renders ./components/timeline/demo/custom.md correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders ./components/timeline/demo/pending.md correctly 1`] = `
|
||||
<ul
|
||||
class="ant-timeline ant-timeline-pending"
|
||||
>
|
||||
<li
|
||||
class="ant-timeline-item"
|
||||
<div>
|
||||
<ul
|
||||
class="ant-timeline ant-timeline-pending"
|
||||
>
|
||||
<div
|
||||
class="ant-timeline-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-head ant-timeline-item-head-blue"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-content"
|
||||
<li
|
||||
class="ant-timeline-item"
|
||||
>
|
||||
Create a services site 2015-09-01
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="ant-timeline-item"
|
||||
>
|
||||
<div
|
||||
class="ant-timeline-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-head ant-timeline-item-head-blue"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-content"
|
||||
>
|
||||
Solve initial network problems 2015-09-01
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="ant-timeline-item ant-timeline-item-last"
|
||||
>
|
||||
<div
|
||||
class="ant-timeline-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-head ant-timeline-item-head-blue"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-content"
|
||||
>
|
||||
Technical testing 2015-09-01
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="ant-timeline-item ant-timeline-item-pending"
|
||||
>
|
||||
<div
|
||||
class="ant-timeline-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-head ant-timeline-item-head-custom ant-timeline-item-head-blue"
|
||||
>
|
||||
<i
|
||||
class="anticon anticon-spin anticon-loading"
|
||||
<div
|
||||
class="ant-timeline-item-tail"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-timeline-item-content"
|
||||
<div
|
||||
class="ant-timeline-item-head ant-timeline-item-head-blue"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-content"
|
||||
>
|
||||
Create a services site 2015-09-01
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="ant-timeline-item"
|
||||
>
|
||||
Recording...
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
class="ant-timeline-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-head ant-timeline-item-head-blue"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-content"
|
||||
>
|
||||
Solve initial network problems 2015-09-01
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="ant-timeline-item ant-timeline-item-last"
|
||||
>
|
||||
<div
|
||||
class="ant-timeline-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-head ant-timeline-item-head-blue"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-content"
|
||||
>
|
||||
Technical testing 2015-09-01
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="ant-timeline-item ant-timeline-item-pending"
|
||||
>
|
||||
<div
|
||||
class="ant-timeline-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-timeline-item-head ant-timeline-item-head-custom ant-timeline-item-head-blue"
|
||||
>
|
||||
<i
|
||||
class="anticon anticon-spin anticon-loading"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-timeline-item-content"
|
||||
>
|
||||
Recording...
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
style="margin-top:16px"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Toggle Reverse
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
103
components/timeline/__tests__/index.test.js
Normal file
103
components/timeline/__tests__/index.test.js
Normal file
@ -0,0 +1,103 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import TimeLine from '..';
|
||||
|
||||
const { Item } = TimeLine;
|
||||
|
||||
const wrapperFactory = (timeLineProps = {}) => mount(
|
||||
<TimeLine type="editable-card" {...timeLineProps}>
|
||||
<Item key="1">foo</Item>
|
||||
<Item key="2">bar</Item>
|
||||
<Item key="3">baz</Item>
|
||||
</TimeLine>
|
||||
);
|
||||
|
||||
describe('TimeLine', () => {
|
||||
describe('renders items without passing any props correctly', () => {
|
||||
const wrapper = wrapperFactory();
|
||||
|
||||
it('has 3 timeline item', () => {
|
||||
expect(wrapper.find('li.ant-timeline-item')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('has only 1 timeline item is marked as the last item', () => {
|
||||
expect(wrapper.find('li.ant-timeline-item-last')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('its last item is marked as the last item', () => {
|
||||
expect(wrapper.find('li.ant-timeline-item').last().hasClass('ant-timeline-item-last')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renders pending item', () => {
|
||||
const pending = <div>pending...</div>;
|
||||
const pendingDot = <i>dot</i>;
|
||||
|
||||
it('has one extra timeline item', () => {
|
||||
const wrapper = wrapperFactory({ pending });
|
||||
expect(wrapper.find('li.ant-timeline-item')).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('has extra pending timeline item', () => {
|
||||
const wrapper = wrapperFactory({ pending });
|
||||
expect(wrapper.find('li.ant-timeline-item-pending')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders the pending timeline item as long as it receive a truthy prop value to \'pending\'', () => {
|
||||
const wrapper = wrapperFactory({ pending: true });
|
||||
expect(wrapper.find('li.ant-timeline-item-pending')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('its last item is marked as the pending item', () => {
|
||||
const wrapper = wrapperFactory({ pending });
|
||||
expect(wrapper.find('li.ant-timeline-item').last().hasClass('ant-timeline-item-pending')).toBe(true);
|
||||
});
|
||||
|
||||
it('its second to last item is marked as the last item', () => {
|
||||
const wrapper = wrapperFactory({ pending });
|
||||
const items = wrapper.find('li.ant-timeline-item');
|
||||
expect(items.at(items.length - 2).hasClass('ant-timeline-item-last')).toBe(true);
|
||||
});
|
||||
|
||||
it('has the correct pending node', () => {
|
||||
const wrapper = wrapperFactory({ pending });
|
||||
expect(wrapper.find('li.ant-timeline-item-pending').contains(pending)).toBe(true);
|
||||
});
|
||||
|
||||
it('has the correct pending dot node', () => {
|
||||
const wrapper = wrapperFactory({ pending, pendingDot });
|
||||
expect(wrapper.find('li.ant-timeline-item-pending').contains(pendingDot)).toBe(true);
|
||||
});
|
||||
|
||||
it('has no pending dot if without passing a truthy \'pending\' prop', () => {
|
||||
const wrapper = wrapperFactory({ pendingDot });
|
||||
expect(wrapper.find('li.ant-timeline-item-pending').contains(pendingDot)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the item rendering sequence is controlled by reverse', () => {
|
||||
it('items is in order when prop reverse is false', () => {
|
||||
const wrapper = wrapperFactory({ reverse: false });
|
||||
expect(wrapper.find('.ant-timeline-item-content').map(w => w.text())).toEqual(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
it('items is reversed when prop reverse is true', () => {
|
||||
const wrapper = wrapperFactory({ reverse: true });
|
||||
expect(wrapper.find('.ant-timeline-item-content').map(w => w.text())).toEqual(['baz', 'bar', 'foo']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renders items reversely and with pending item', () => {
|
||||
const pending = <div>pending...</div>;
|
||||
|
||||
it('its last item is marked as the last item', () => {
|
||||
const wrapper = wrapperFactory({ pending, reverse: true });
|
||||
expect(wrapper.find('li.ant-timeline-item').last().hasClass('ant-timeline-item-last')).toBe(true);
|
||||
});
|
||||
|
||||
it('its first item is marked as the pending item', () => {
|
||||
const wrapper = wrapperFactory({ pending, reverse: true });
|
||||
expect(wrapper.find('li.ant-timeline-item').first().hasClass('ant-timeline-item-pending')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,26 +1,44 @@
|
||||
---
|
||||
order: 2
|
||||
title:
|
||||
zh-CN: 最后一个
|
||||
en-US: Last node
|
||||
zh-CN: 最后一个及排序
|
||||
en-US: Last node and Reversing
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
当任务状态正在发生,还在记录过程中,可用幽灵节点来表示当前的时间节点(用于时间正序排列)。当 pending 值为 false ,可用定制元件替换默认时间图点。
|
||||
当任务状态正在发生,还在记录过程中,可用幽灵节点来表示当前的时间节点,当 pending 为真值时展示幽灵节点,如果 pending 是 React 元素可用于定制该节点内容,同时 pendingDot 将可以用于定制其轴点。reverse 属性用于控制节点排序,为 false 时按正序排列,为 true 时按倒序排列。
|
||||
|
||||
## en-US
|
||||
|
||||
When the timeline is incomplete and ongoing, put a ghost node at last. set `pending={true}` or `pending={a React Element}`. Used in ascend chronological order. When `pending` is not false, set `pendingDot={a React Element}` to replace the default pending dot.
|
||||
When the timeline is incomplete and ongoing, put a ghost node at last. Set `pending` as truthy value to enable displaying pending item. You can customize the pending content by passing a React Element. Meanwhile, `pendingDot={a React Element}` is used to customize the dot of the pending item.
|
||||
`reverse={true}` is used for reversing nodes.
|
||||
|
||||
````jsx
|
||||
import { Timeline } from 'antd';
|
||||
import { Timeline, Button } from 'antd';
|
||||
|
||||
ReactDOM.render(
|
||||
<Timeline pending="Recording...">
|
||||
<Timeline.Item>Create a services site 2015-09-01</Timeline.Item>
|
||||
<Timeline.Item>Solve initial network problems 2015-09-01</Timeline.Item>
|
||||
<Timeline.Item>Technical testing 2015-09-01</Timeline.Item>
|
||||
</Timeline>
|
||||
, mountNode);
|
||||
class PendingTimeLine extends React.Component {
|
||||
state = {
|
||||
reverse: false,
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.setState({ reverse: !this.state.reverse });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Timeline pending="Recording..." reverse={this.state.reverse}>
|
||||
<Timeline.Item>Create a services site 2015-09-01</Timeline.Item>
|
||||
<Timeline.Item>Solve initial network problems 2015-09-01</Timeline.Item>
|
||||
<Timeline.Item>Technical testing 2015-09-01</Timeline.Item>
|
||||
</Timeline>
|
||||
<Button type="primary" style={{ marginTop: 16 }} onClick={this.handleClick}>Toggle Reverse</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<PendingTimeLine />, mountNode);
|
||||
````
|
||||
|
@ -30,6 +30,7 @@ Timeline
|
||||
| -------- | ----------- | ---- | ------- |
|
||||
| pending | Set the last ghost node's existence or its content | boolean\|string\|ReactNode | `false` |
|
||||
| pendingDot | Set the dot of the last ghost node when pending is true | \|string\|ReactNode | `<Icon type="loading" />` |
|
||||
| reverse | reverse nodes or not | boolean | false |
|
||||
|
||||
### Timeline.Item
|
||||
|
||||
|
@ -31,6 +31,7 @@ title: Timeline
|
||||
| --- | --- | --- | --- |
|
||||
| pending | 指定最后一个幽灵节点是否存在或内容 | boolean\|string\|ReactNode | false |
|
||||
| pendingDot | 当最后一个幽灵节点存在時,指定其时间图点 | \|string\|ReactNode | `<Icon type="loading" />` |
|
||||
| reverse | 节点排序 | boolean | false |
|
||||
|
||||
### Timeline.Item
|
||||
|
||||
|
@ -78,7 +78,6 @@
|
||||
|
||||
&-last {
|
||||
.@{timeline-prefix-cls}-item-tail {
|
||||
border-left: 2px dotted @timeline-color;
|
||||
display: none;
|
||||
}
|
||||
.@{timeline-prefix-cls}-item-content {
|
||||
@ -88,6 +87,21 @@
|
||||
}
|
||||
|
||||
&&-pending &-item-last &-item-tail {
|
||||
border-left: 2px dotted @timeline-color;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&&-reverse &-item-last &-item-tail {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&&-reverse &-item-pending {
|
||||
.@{timeline-prefix-cls}-item-tail {
|
||||
border-left: 2px dotted @timeline-color;
|
||||
display: block;
|
||||
}
|
||||
.@{timeline-prefix-cls}-item-content {
|
||||
min-height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user