feat: timeline3.0 (#3072)

Co-authored-by: yanglu19 <yanglu19@baidu.com>
Co-authored-by: liaoxuezhi <liaoxuezhi@baidu.com>
This commit is contained in:
Dora 2021-12-27 15:46:47 +08:00 committed by GitHub
parent 3e8c73225f
commit 13846fb4e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1044 additions and 5 deletions

View File

@ -0,0 +1,46 @@
import React = require('react');
import {render, cleanup} from '@testing-library/react';
import '../../src/themes/default';
import {render as amisRender} from '../../src/index';
import {makeEnv} from '../helper';
import {clearStoresCache} from '../../src/factory';
afterEach(() => {
cleanup();
clearStoresCache();
});
test('Renderer:timeline', async () => {
const {container} = render(
amisRender(
{
type: 'timeline',
items: [
{
time: "2019-02-07",
title: "节点数据",
color: "#ffb200",
},
{
time: "2019-02-08",
title: "节点数据",
color: "#4F86F4",
},
{
time: "2019-02-09",
title: "节点数据",
color: "success",
},
{
time: "2019-02-09",
title: "节点数据",
color: "warning",
}
]
},
{},
makeEnv()
)
);
expect(container).toMatchSnapshot();
});

View File

@ -0,0 +1,390 @@
---
title: Timeline 时间轴
description:
type: 0
group: ⚙ 组件
menuName: Timeline
icon:
order: 73
---
时间轴组件
## 基本用法
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
"items": [
{
"time": "2019-02-07",
"title": "节点数据",
"detail": "error",
},
{
"time": "2019-02-08",
"title": "节点数据",
"detail": "success",
},
{
"time": "2019-02-09",
"title": "节点数据",
"detail": "error",
}
]
}
]
}
```
## 时间轴节点颜色设置
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
"items": [
{
"time": "2019-02-07",
"title": "节点数据",
"color": "#ffb200",
},
{
"time": "2019-02-08",
"title": "节点数据",
"color": "#4F86F4",
},
{
"time": "2019-02-09",
"title": "节点数据",
"color": "success",
},
{
"time": "2019-02-09",
"title": "节点数据",
"color": "warning",
}
]
}
]
}
```
## 时间轴节点图标设置
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
"items": [
{
"time": "2019-02-07",
"title": "节点数据error",
"detail": "error",
"icon": "status-fail"
},
{
"time": "2019-02-08",
"title": "节点数据success",
"detail": "success",
"icon": "status-success"
},
{
"time": "2019-02-09",
"title": "节点数据warning",
"detail": "warning",
"icon": "status-warning"
}
]
}
]
}
```
## 节点标题自定义
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
"items": [
{
"time": "2019-02-07",
"title": [
{
"type": "text",
"value": "2019年02月7日"
},
{
"type": "button",
"label": "查看",
"actionType": "dialog",
"level": "link",
"dialog": {
"title": "查看详情",
"body": "这是详细内容。"
}
},
{
"type": "button",
"label": "删除",
"level": "link",
"actionType": "dialog",
"dialog": {
"title": "删除",
"body": "确认删除吗?"
}
}
]
},
{
"time": "2019-02-10",
"title": [
{
"type": "text",
"value": "2019年02月10日"
},
{
"type": "button",
"label": "查看",
"actionType": "dialog",
"level": "link",
"dialog": {
"title": "查看详情",
"body": "这是详细内容。"
}
},
{
"type": "button",
"label": "删除",
"level": "link",
"actionType": "dialog",
"dialog": {
"title": "删除",
"body": "确认删除吗?"
}
}
]
},
]
}
]
}
```
## 设置节点数据倒序
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
direction: "vertical",
reverse: true,
"items": [
{
"time": "2019-02-07",
"title": "节点数据",
},
{
"time": "2019-02-08",
"title": "节点数据",
},
{
"time": "2019-02-09",
"title": "节点数据",
},
{
"time": "2019-02-10",
"title": "节点数据",
},
]
}
]
}
```
## 设置时间轴方向
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
direction: "horizontal",
"items": [
{
"time": "2019-02-07",
"title": "节点数据",
},
{
"time": "2019-02-08",
"title": "节点数据",
},
{
"time": "2019-02-09",
"title": "节点数据",
},
{
"time": "2019-02-10",
"title": "节点数据",
},
]
}
]
}
```
## 设置文字相对时间轴方向(时间轴横向时不支持)
### 文字位于时间轴左侧
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
"mode": "left",
"items": [
{
"time": "2019-02-07",
"title": "节点数据",
"detail": "error",
},
{
"time": "2019-02-08",
"title": "节点数据",
"detail": "success",
}
]
},
]
}
```
### 文字交替位于时间轴两侧
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
"mode": "alternate",
"items": [
{
"time": "2019-02-07",
"title": "节点数据",
"detail": "error",
},
{
"time": "2019-02-08",
"title": "节点数据",
"detail": "success",
}
]
}
]
}
```
### 文字位于时间轴右侧
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
"mode": "right",
"items": [
{
"time": "2019-02-07",
"title": "节点数据",
"detail": "error",
},
{
"time": "2019-02-08",
"title": "节点数据",
"detail": "success",
}
]
},
]
}
```
## 远程数据
```schema
{
"type": "page",
"body": [
{
"type": "timeline",
"source": {
"method": "get",
"url": "/api/mock2/timeline/timelineItems"
}
}
]
}
```
"source": "/api/mock2/timeline/timelineItems",
远程拉取接口时,返回的数据结构除了需要满足 amis 接口要求的基本数据结构 以外,必须用"items"作为时间轴数据的 key 值,如下:
```json
{
"status": 0,
"msg": "",
"data": {
"items": [
{"time": "2019-02-07", "title": "数据开发", "detail": "2019-02-07detail", "color":"#ffb200", "icon": "close"},
{"time": "2019-02-08", "title": "管理中心", "detail": "2019-02-08detail" },
{"time": "2019-02-09", "title": "SQL语句", "detail": "2019-02-09detail", "color":"warning"},
{"time": "2019-02-10", "title": "一键部署", "detail": "2019-02-10detail", "icon": "compress-alt"},
{"time": "2019-02-10", "title": "一键部署", "detail": "2019-02-11detail"},
{"time": "2019-02-10", "title": "一键部署", "detail": "2019-02-12detail", "icon": "close"},
{"time": "2019-02-10", "title": "一键部署", "detail": "2019-02-13detail"}
]
}
}
```
## 属性表
| 属性名 | 类型 | 默认值 | 说明 |
| --------- | --------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------- |
| type | `string` | | `"timeline"` 指定为 时间轴 渲染器 |
| items | Array<[timelineItem](#timeline.item)> | [] | 配置节点数据 |
| source | [API](../../../docs/types/api) | | 数据源,可通过数据映射获取当前数据域变量、或者配置 API 对象 |
| mode | `left` \| `right` \| `alternate` | `right` | 指定文字相对于时间轴的位置,仅 direction=vertical时支持 |
| direction | `vertical` \| `horizontal` | `vertical` | 时间轴方向 | |
| reverse | `boolean` | `false` | 根据时间倒序显示
### timeline.item
| 属性名 | 类型 | 默认值 | 说明 |
| ----------- | ----------------------------------------------------- | ------ | --------------------------------------- |
| time | `string ` | | 节点时间 |
| title | `string \| [SchemaNode](../../docs/types/schemanode)` | | 节点标题 |
| detail | `string` | | 节点详细描述(折叠) |
| detailCollapsedText | `string` | `展开` | 详细内容折叠时按钮文案 |
| detailExpandedText | `string` | `折叠` | 详细内容展开时按钮文案 |
| color | `string \| level样式info、success、warning、danger` | `#DADBDD` | 时间轴节点颜色 |
| icon | `string` | | icon 名,支持 fontawesome v4 或使用 url优先级高于color |

View File

@ -999,7 +999,15 @@ export const components = [
import('../../docs/zh-CN/components/video.md').then(
makeMarkdownRenderer
)
}
},
{
label: 'Timeline 时间轴',
path: '/zh-CN/components/timeline',
getComponent: () =>
import('../../docs/zh-CN/components/timeline.md').then(
makeMarkdownRenderer
)
},
]
},

View File

@ -0,0 +1,15 @@
{
"status": 0,
"msg": "",
"data": {
"items": [
{"time": "2019-02-07", "title": "数据开发", "detail": "2019-02-07detail", "color":"#ffb200", "icon": "close"},
{"time": "2019-02-08", "title": "管理中心", "detail": "2019-02-08detail" },
{"time": "2019-02-09", "title": "SQL语句", "detail": "2019-02-09detail", "color":"warning"},
{"time": "2019-02-10", "title": "一键部署", "detail": "2019-02-10detail", "icon": "compress-alt"},
{"time": "2019-02-10", "title": "一键部署", "detail": "2019-02-11detail"},
{"time": "2019-02-10", "title": "一键部署", "detail": "2019-02-12detail", "icon": "close"},
{"time": "2019-02-10", "title": "一键部署", "detail": "2019-02-13detail"}
]
}
}

View File

@ -6,7 +6,7 @@
"scripts": {
"test": "jest",
"coverage": "jest --coverage",
"serve": "fis3 server start --www ./public --port 8888 --no-daemon --no-browse",
"serve": "fis3 server start --www ./public --port 8000 --no-daemon --no-browse",
"start": "concurrently --restart-tries -1 npm:serve npm:dev",
"update-snapshot": "jest --updateSnapshot",
"stop": "fis3 server stop",

View File

@ -0,0 +1,198 @@
.#{$ns}Timeline-vertical {
display: flex;
flex-flow: column;
.#{$ns}TimelineItem {
display: flex;
flex: 1;
flex-direction: row;
&:last-of-type {
.#{$ns}TimelineItem-axle .#{$ns}TimelineItem-line {
display: none;
}
}
&-axle {
position: relative;
flex: var(--TimelineItem--axle-flex);
.#{$ns}TimelineItem-line {
position: absolute;
height: calc(100% - var(--TimelineItem--left-line-top));
width: var(--TimelineItem--left-line-width);
left: var(--TimelineItem--left-line-left);
top: var(--TimelineItem--left-line-top);
background-color: var(--TimelineItem--line-bg);
}
.#{$ns}TimelineItem-round {
position: absolute;
width: var(--TimelineItem--round-width);
height: var(--TimelineItem--round-height);
left: var(--TimelineItem--round-left);
top: var(--TimelineItem--round-top);
background: var(--TimelineItem-round-bg);
border-radius: var(--TimelineItem--round-radius);
&--danger {
background: var(--Timeline--danger-bg);
}
&--info {
background: var(--Timeline--info-bg);
}
&--success {
background: var(--Timeline--success-bg);
}
&--warning {
background: var(--Timeline--warning-bg);
}
}
.#{$ns}TimelineItem-icon {
position: absolute;
width: var(--TimelineItem--icon-width);
height: var(--TimelineItem--icon-height);
left: var(--TimelineItem--icon-left);
border-radius: var(--TimelineItem--icon-radius);
}
}
&-content {
padding-bottom: var(--TimelineItem--content-padding-bottom);
margin-left: var(--TimelineItem--content-margin-left);
.#{$ns}TimelineItem-time {
color: var(--TimelineItem--text-secondary-color);
font-size: var(--Timeline--font-size);
margin-bottom: var(--TimelineItem--content-time-margin-bottom);
}
.#{$ns}TimelineItem-title {
color: var(--TimelineItem--text-primary-color);
font-size: var(--Timeline--font-size);
margin-bottom: var(--TimelineItem--content-title-margin-bottom);
}
.#{$ns}TimelineItem-detail {
.#{$ns}TimelineItem-detail-button {
display: flex;
cursor: pointer;
align-items: center;
font-size: var(--Timeline--font-size);
color: var(--TimelineItem--detail-button-color);
margin-bottom: var(--TimelineItem--detail-button-margin-bottom);
}
.#{$ns}TimelineItem-detail-arrow {
width: var(--TimelineItem-detail-arrow-width);
height: var(--TimelineItem-detail-arrow-width);
}
.#{$ns}TimelineItem-detail-arrow-top {
transform: rotateX(180deg);
}
.#{$ns}TimelineItem-detail-visible {
display: block;
max-width: var(--TimelineItem-detail-visible-max-width);
font-size: var(--Timeline--font-size);
padding: var(--TimelineItem-detail-visible-padding);
box-shadow: var(--TimelineItem-detail-visible-shadow);
}
.#{$ns}TimelineItem-detail-invisible {
display: none;
}
}
}
}
&.#{$ns}Timeline-left {
.#{$ns}TimelineItem {
flex-direction: row-reverse;
}
}
&.#{$ns}Timeline-alternate {
.#{$ns}TimelineItem:nth-child(odd) {
flex-direction: row-reverse;
max-width: 50%;
}
.#{$ns}TimelineItem:nth-child(even) {
margin-left: calc(50% - var(--Timeline-alternate-margin-left));
max-width: calc(50% + var(--Timeline-alternate-margin-left));
}
}
}
.#{$ns}Timeline-horizontal {
display: flex;
flex-flow: row;
margin-left: 50%;
transform: translateX(-50%);
.#{$ns}TimelineItem {
display: flex;
width: -webkit-fill-available;
flex-flow: column;
&:last-of-type {
.#{$ns}TimelineItem-axle .#{$ns}TimelineItem-line {
display: none;
}
}
&-axle {
position: relative;
flex: var(--TimelineItem--axle-flex);
.#{$ns}TimelineItem-line {
position: absolute;
height: var(--TimelineItem--left-line-width);
width: calc(100% - var(--TimelineItem--left-line-left));
left: var(--TimelineItem--left-line-top);
top: var(--TimelineItem--left-line-left);
background-color: var(--TimelineItem--line-bg);
}
.#{$ns}TimelineItem-round {
position: absolute;
width: var(--TimelineItem--round-width);
height: var(--TimelineItem--round-height);
left: var(--TimelineItem--round-top);
top: var(--TimelineItem--round-left);
background: var(--TimelineItem-round-bg);
border-radius: var(--TimelineItem--round-radius);
&--danger {
background: var(--Timeline--danger-bg);
}
&--info {
background: var(--Timeline--info-bg);
}
&--success {
background: var(--Timeline--success-bg);
}
&--warning {
background: var(--Timeline--warning-bg);
}
}
.#{$ns}TimelineItem-icon {
position: absolute;
width: var(--TimelineItem--icon-width);
height: var(--TimelineItem--icon-height);
left: var(--TimelineItem--icon-left);
border-radius: var(--TimelineItem--icon-radius);
}
}
}
}

View File

@ -166,4 +166,46 @@ $link-color: $info;
--Table-onChecked-borderColor: var(--Table-borderColor);
--Switch-bgColor: #bfbfbf;
// timeline
--TimelineItem--axle-flex: 0 0 #{px2rem(24px)};
--TimelineItem--left-line-width: #{px2rem(2px)};
--TimelineItem--left-line-left: #{px2rem(13px)};
--TimelineItem--left-line-top: #{px2rem(20px)};
--TimelineItem--round-width: #{px2rem(8px)};
--TimelineItem--round-height: #{px2rem(8px)};
--TimelineItem--round-left: #{px2rem(10px)};
--TimelineItem--round-top: #{px2rem(8px)};
--TimelineItem--icon-width: #{px2rem(16px)};
--TimelineItem--icon-height: #{px2rem(16px)};
--TimelineItem--icon-left: #{px2rem(6px)};
--TimelineItem--content-padding-bottom: #{px2rem(16px)};
--TimelineItem--content-margin-left: #{px2rem(8px)};
--TimelineItem--content-time-margin-bottom: #{px2rem(4px)};
--TimelineItem--content-title-margin-bottom: #{px2rem(4px)};
--TimelineItem--detail-button-margin-bottom: #{px2rem(8px)};
--TimelineItem-detail-arrow-width: #{px2rem(16px)};
--TimelineItem-detail-visible-padding: #{px2rem(10px)};
--TimelineItem-detail-visible-max-width: #{px2rem(300px)};
--Timeline-alternate-margin-left: #{px2rem(24px)};
--TimelineItem--icon-radius: 50%;
--TimelineItem--round-radius: 50%;
--TimelineItem--content-radius: #{px2rem(2px)};
--TimelineItem-detail-visible-shadow: 0 #{px2rem(1px)} #{px2rem(10px)} 0 rgba(0 0 0 / 10%);
--TimelineItem--font-size: #{px2rem(12px)};
--TimelineItem--text-primary-color: #151a26;
--TimelineItem--text-secondary-color: #83868c;
--TimelineItem--detail-button-color: var(--primary);
--TimelineItem--line-bg: #e6e6e8;
--TimelineItem--content-bg: #f2f2f4;
--TimelineItem-round-bg: #dadbdd;
--Timeline--success-bg: var(--success);
--Timeline--info-bg: var(--info);
--Timeline--warning-bg: var(--warning);
--Timeline--danger-bg: var(--danger);
}

View File

@ -119,5 +119,6 @@
@import '../components/link';
@import '../components/mapping';
@import '../components/formula';
@import '../components/timeline';
@import '../utilities';

View File

@ -613,6 +613,49 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06),
--Rating-inactive-color: #{$G9};
// timeline
--TimelineItem--axle-flex: 0 0 #{px2rem(24px)};
--TimelineItem--left-line-width: #{px2rem(2px)};
--TimelineItem--left-line-left: #{px2rem(13px)};
--TimelineItem--left-line-top: #{px2rem(20px)};
--TimelineItem--round-width: #{px2rem(8px)};
--TimelineItem--round-height: #{px2rem(8px)};
--TimelineItem--round-left: #{px2rem(10px)};
--TimelineItem--round-top: #{px2rem(8px)};
--TimelineItem--icon-width: #{px2rem(16px)};
--TimelineItem--icon-height: #{px2rem(16px)};
--TimelineItem--icon-left: #{px2rem(6px)};
--TimelineItem--content-padding-bottom: #{px2rem(16px)};
--TimelineItem--content-margin-left: #{px2rem(8px)};
--TimelineItem--content-time-margin-bottom: #{px2rem(4px)};
--TimelineItem--content-title-margin-bottom: #{px2rem(4px)};
--TimelineItem--detail-button-margin-bottom: #{px2rem(8px)};
--TimelineItem-detail-arrow-width: #{px2rem(16px)};
--TimelineItem-detail-visible-padding: #{px2rem(10px)};
--TimelineItem-detail-visible-max-width: #{px2rem(300px)};
--Timeline-alternate-margin-left: #{px2rem(24px)};
--TimelineItem--icon-radius: #{$R8};
--TimelineItem--round-radius: #{$R8};
--TimelineItem--content-radius: #{$R2};
--TimelineItem-detail-visible-shadow: 0 #{px2rem(1px)} #{px2rem(10px)} 0 rgba(0 0 0 / 10%);
--TimelineItem--font-size: #{$T2};
--TimelineItem--text-primary-color: #{$text-color};
--TimelineItem--text-secondary-color: #{$G5};
--TimelineItem--detail-button-color: var(--primary);
--TimelineItem--line-bg: #{$G9};
--TimelineItem--content-bg: #{$G10};
--TimelineItem-round-bg: #{$G8};
--Timeline--success-bg: var(--success);
--Timeline--info-bg: var(--info);
--Timeline--warning-bg: var(--warning);
--Timeline--danger-bg: var(--danger);
// Formula
--Formula-header-bgColor: #{$G10};
--Formula-funcItem-bgColor-onActive: #{$light};

View File

@ -56,6 +56,7 @@ import {PaginationSchema} from './renderers/Pagination';
import {AnchorNavSchema} from './renderers/AnchorNav';
import {AvatarSchema} from './renderers/Avatar';
import {StepsSchema} from './renderers/Steps';
import {TimelineSchema} from './renderers/Timeline';
import {ArrayControlSchema} from './renderers/Form/InputArray';
import {ButtonGroupControlSchema} from './renderers/Form/ButtonGroupSelect';
import {ChainedSelectControlSchema} from './renderers/Form/ChainedSelect';
@ -201,6 +202,7 @@ export type SchemaType =
| 'web-component'
| 'anchor-nav'
| 'steps'
| 'timeline'
| 'control'
| 'input-array'
| 'button'
@ -387,6 +389,7 @@ export type SchemaObject =
| AnchorNavSchema
| StepsSchema
| PortletSchema
| TimelineSchema
// 表单项
| FormControlSchema

View File

@ -0,0 +1,31 @@
import React from 'react';
import {themeable, ThemeProps} from '../theme';
import TimelineItem, {TimelineItemProps} from './TimelineItem';
export interface TimelineProps extends ThemeProps {
items: Array<TimelineItemProps>;
direction?: 'vertical' | 'horizontal';
reverse?: boolean;
mode?: 'left' | 'right' | 'alternate';
}
export function Timeline(props: TimelineProps) {
const {
items,
classnames: cx,
direction = 'vertical',
reverse = false,
mode = 'right',
} = props;
const timelineDatasource = items?.slice();
reverse && timelineDatasource?.reverse();
return (
<div className={cx('Timeline', `Timeline-${direction}`, `Timeline-${mode}`)}>
{timelineDatasource?.map((item: TimelineItemProps) => <TimelineItem {...item} />)}
</div>)
}
export default themeable(Timeline);

View File

@ -0,0 +1,107 @@
import React, {ReactNode, useState} from 'react';
import {localeable, LocaleProps} from '../locale';
import {SchemaExpression} from '../Schema';
import {themeable, ThemeProps} from '../theme';
import {Icon} from './icons';
export interface TimelineItemProps {
/**
*
*/
time: string;
/**
*
*/
title?: string| ReactNode;
/**
*
*/
detail?: string;
/**
* detail折叠时文案
*/
detailCollapsedText?: string;
/**
* detail展开时文案
*/
detailExpandedText?: string;
/**
* ,//level样式infosuccesswarningdanger
*/
color?: SchemaExpression;
/**
*
*/
icon?: string | ReactNode;
}
export interface TimelineItem extends ThemeProps, LocaleProps, TimelineItemProps {}
export function TimelineItem(props: TimelineItem) {
const {
time,
title,
detail,
detailCollapsedText,
detailExpandedText,
color,
icon,
classnames: cx,
translate: __,
} = props;
const [detailVisible, setDetailVisible] = useState<boolean>(false);
const renderDetail = (detail: string, detailCollapsedText: string = __('Timeline.collapseText'), detailExpandedText: string = __('Timeline.expandText')) : ReactNode => {
return (
<>
<div className={cx('TimelineItem-detail-button')} onClick={() => setDetailVisible(!detailVisible)}>
{detailVisible ? detailExpandedText : detailCollapsedText}
<div className={cx('TimelineItem-detail-arrow', `${detailVisible && 'TimelineItem-detail-arrow-top'}`)}>
<Icon icon="tree-down"/>
</div>
</div>
<div className={cx(`${detailVisible ? 'TimelineItem-detail-visible' : 'TimelineItem-detail-invisible'}`)}>
{detail}
</div>
</>);
}
// 判断是否为颜色值
const isColorVal = color && /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/.test(color);
// 取level级颜色
const levelColor = !isColorVal && color;
return (
<div className={cx('TimelineItem')}>
<div className={cx('TimelineItem-axle')}>
<div className={cx('TimelineItem-line')}></div>
{icon
? <div className={cx('TimelineItem-icon')}>
<Icon icon={icon} className="icon"/>
</div>
: <div
className={cx('TimelineItem-round',
levelColor && `TimelineItem-round--${levelColor}`)}
style={isColorVal ? {backgroundColor: color} : undefined}
></div>
}
</div>
<div className={cx('TimelineItem-content')}>
<div className={cx('TimelineItem-time')}>{time}</div>
<div className={cx('TimelineItem-title')}>{title}</div>
{detail
&& <div className={cx('TimelineItem-detail')}>{renderDetail(detail, detailCollapsedText, detailExpandedText)}</div>}
</div>
</div>)
}
export default themeable(localeable(TimelineItem));

View File

@ -43,6 +43,7 @@ import ExchangeIcon from '../icons/exchange.svg';
import ColmunsIcon from '../icons/columns.svg';
import CalendarIcon from '../icons/calendar.svg';
import ClockIcon from '../icons/clock.svg';
import TreeDownIcon from '../icons/tree-down.svg';
import CopyIcon from '../icons/copy.svg';
import FilterIcon from '../icons/filter.svg';
@ -176,6 +177,7 @@ registerIcon('alert-success', AlertSuccess);
registerIcon('alert-info', AlertInfo);
registerIcon('alert-warning', AlertWarning);
registerIcon('alert-danger', AlertDanger);
registerIcon('tree-down', TreeDownIcon);
export function Icon({
icon,

5
src/icons/tree-down.svg Normal file
View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1024 1024" width="30"
fill="currentColor">
<path d="M512 704L256 384h512z"></path>
</svg>

After

Width:  |  Height:  |  Size: 155 B

View File

@ -165,6 +165,7 @@ import './renderers/Icon';
import './renderers/Carousel';
import './renderers/AnchorNav';
import './renderers/Steps';
import './renderers/Timeline';
import './renderers/Markdown';
import './renderers/TableView';
import './renderers/Code';

View File

@ -254,5 +254,7 @@ register('de-DE', {
'Condition.cond_placeholder': 'Bedingung auswählen',
'Condition.field_placeholder': 'Feld auswählen',
'Condition.blank': 'leer',
'InputTable.uniqueError': 'Column `{{label}}` unique validate failed'
'InputTable.uniqueError': 'Column `{{label}}` unique validate failed',
'Timeline.collapseText': 'Entfalten',
'Timeline.expandText': 'Falten',
});

View File

@ -256,5 +256,7 @@ register('en-US', {
'Condition.cond_placeholder': 'select condition',
'Condition.field_placeholder': 'select field',
'Condition.blank': 'blank',
'InputTable.uniqueError': 'Column `{{label}}` unique validate failed'
'InputTable.uniqueError': 'Column `{{label}}` unique validate failed',
'Timeline.collapseText': 'Unfold',
'Timeline.expandText': 'Fold',
});

View File

@ -260,5 +260,7 @@ register('zh-CN', {
'Condition.cond_placeholder': '请选择操作',
'Condition.field_placeholder': '请选择字段',
'Condition.blank': '空',
'InputTable.uniqueError': '列`{{label}}`没有通过唯一验证'
'InputTable.uniqueError': '列`{{label}}`没有通过唯一验证',
'Timeline.collapseText': '展开',
'Timeline.expandText': '折叠',
});

141
src/renderers/Timeline.tsx Normal file
View File

@ -0,0 +1,141 @@
import React from 'react';
import {Renderer, RendererProps} from '../factory';
import {BaseSchema, SchemaApi, SchemaCollection, SchemaTokenizeableString} from '../Schema';
import {resolveVariable} from '../utils/tpl-builtin';
import Timeline from '../components/Timeline';
import {filter} from '../utils/tpl';
import { RemoteOptionsProps, withRemoteConfig } from '../components/WithRemoteConfig';
export interface TimelineItemSchema extends Omit<BaseSchema, 'type'> {
/**
*
*/
time: string;
/**
*
*/
title?: SchemaCollection;
/**
*
*/
detail?: string;
/**
* detail折叠时文案
*/
detailCollapsedText?: string;
/**
* detail展开时文案
*/
detailExpandedText?: string;
/**
*
*/
color?: string;
/**
*
*/
icon?: SchemaCollection;
}
export interface TimelineSchema extends BaseSchema {
/**
* Timeline
*/
type: 'timeline';
/**
*
*/
items?: Array<TimelineItemSchema>;
/**
* API
*/
source?: SchemaApi  | SchemaTokenizeableString;
/**
*
*/
mode?: 'left' | 'right' | 'alternate';
/**
*
*/
direction?: 'horizontal' | 'vertical'
/**
*
*/
reverse?: boolean,
}
export interface TimelineProps
extends RendererProps,
Omit<TimelineSchema, 'className'> {}
export function TimelineCmpt(props: TimelineProps) {
const {
items,
mode,
direction,
reverse,
data,
config,
source,
render
} = props;
// 获取源数据
const timelineItemsRow: Array<TimelineItemSchema> = config || items || [];
// 渲染内容
const resolveRender = (region: string, val?: SchemaCollection) => typeof val === 'string'
? filter(val, data)
: (val && render(region, val));
// 处理源数据
const resolveTimelineItems =
timelineItemsRow?.map((timelineItem: TimelineItemSchema) => {
return {
...timelineItem,
icon: resolveRender('icon', timelineItem.icon),
title: resolveRender('title', timelineItem.title),
}
});
return (<Timeline
items = {resolveTimelineItems}
direction = {direction}
reverse = {reverse}
mode = {mode}
/>)
}
const TimelineWithRemoteConfig = withRemoteConfig({
adaptor: data => data.items || data
})(
class extends React.Component<
RemoteOptionsProps & React.ComponentProps<typeof TimelineCmpt>
> {
render() {
const {config, deferLoad, loading, updateConfig, ...rest} = this.props;
return <TimelineCmpt config={config} {...rest} />;
}
}
);
@Renderer({
type: 'timeline'
})
export class TimelineRenderer extends React.Component<TimelineProps> {
render() {
return <TimelineWithRemoteConfig {...this.props} />;
}
}