Skip to content

Commit dce305b

Browse files
authored
refactor(TimeLine): 重构时间轴 & 新增自定义渲染 (#629)
1 parent 585f7f0 commit dce305b

File tree

11 files changed

+908
-195
lines changed

11 files changed

+908
-195
lines changed

example/base/ios/Podfile.lock

+574
Large diffs are not rendered by default.

example/base/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"@babel/core": "~7.20.7",
2626
"@babel/runtime": "~7.20.7",
2727
"@react-native-community/eslint-config": "^2.0.0",
28-
"@uimjs/metro-config": "^1.0.2",
28+
"@uimjs/metro-config": "2.0.1",
2929
"babel-jest": "^26.6.3",
3030
"eslint": "^7.32.0",
3131
"jest": "^26.6.3",

packages/core/src/MaskLayer/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useMemo } from 'react';
1+
import React, { useState, useMemo, useEffect } from 'react';
22
import { Modal, ModalProps as RNModalProps, Animated, TouchableOpacity, StyleSheet } from 'react-native';
33
import { usePrevious } from '../utils';
44
import { Theme } from '../theme';
@@ -76,7 +76,7 @@ const MaskLayer = (props: MaskLayerProps = {}) => {
7676
const preVisible = usePrevious<boolean | undefined>(props.visible);
7777
const [visibleModal, setVisibleModal] = useState(false);
7878
const [bgOpacity] = useState(new Animated.Value(0));
79-
useMemo(() => {
79+
useEffect(() => {
8080
if (preVisible !== props.visible && props.visible) {
8181
setVisible(!!props.visible);
8282
setVisibleModal(false);

packages/core/src/Timeline/Desc.tsx

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { View, Text, StyleSheet } from 'react-native';
2+
import { useTheme } from '@shopify/restyle';
3+
import { Theme } from '../theme';
4+
5+
interface DescProps {
6+
desc?: string | string[];
7+
theme: Theme;
8+
}
9+
10+
export const Desc = ({ desc, theme }: DescProps) => {
11+
const styles = createStyles({
12+
desc: theme.colors.text,
13+
});
14+
15+
if (Array.isArray(desc) && desc.length) {
16+
const descs: string[] = desc as string[];
17+
return (
18+
<View>
19+
{descs.map((item, index) => (
20+
<Text style={styles.desc} key={index}>
21+
{item}
22+
</Text>
23+
))}
24+
</View>
25+
);
26+
}
27+
return <Text style={styles.desc}>{desc}</Text>;
28+
};
29+
30+
function createStyles({ desc = '#5e6d82' }) {
31+
return StyleSheet.create({
32+
desc: {
33+
color: desc,
34+
fontSize: 14,
35+
marginTop: 10,
36+
lineHeight: 20,
37+
},
38+
});
39+
}

packages/core/src/Timeline/Icons.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Fragment } from 'react';
2+
import Icon, { IconsName } from '../Icon';
3+
import { Theme } from '../theme';
4+
import type { TimelineItemsIcons } from './types';
5+
6+
interface IconCustomProps {
7+
icon?: TimelineItemsIcons;
8+
size?: number;
9+
color?: string;
10+
theme: Theme;
11+
}
12+
13+
export const IconCustom = (props: IconCustomProps) => {
14+
const { icon, size, color, theme } = props;
15+
16+
if (icon) {
17+
if (typeof icon === 'string') {
18+
return <Icon name={icon as IconsName} size={size ? size : 15} color={color ? color : 'red'} />;
19+
}
20+
return <Fragment>{icon}</Fragment>;
21+
}
22+
return (
23+
<Icon
24+
name="circle-o"
25+
size={size ? size : 15}
26+
color={color ? color : theme.colors.primary_background || '#3578e5'}
27+
/>
28+
);
29+
};

packages/core/src/Timeline/Items.tsx

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { Fragment } from 'react';
2+
import { View, Text, StyleSheet, ViewProps } from 'react-native';
3+
import { IconCustom } from './Icons';
4+
import { Desc } from './Desc';
5+
import { useTheme } from '@shopify/restyle';
6+
import { Theme } from '../theme';
7+
import type { TimelineItemsProps, TimelineItemsMode, TimelineProps } from './types';
8+
9+
interface LineItemProps {
10+
item: TimelineItemsProps;
11+
mode?: TimelineItemsMode;
12+
end?: boolean;
13+
index: number;
14+
renderItem?: TimelineProps['renderItem'];
15+
}
16+
17+
export const Items = (props: LineItemProps) => {
18+
const theme = useTheme<Theme>();
19+
const { item, end, mode, index, renderItem } = props;
20+
21+
const styles = createStyles({
22+
backgroundColor: theme.colors.mask,
23+
title: theme.colors.primary_text,
24+
line: theme.colors.primary_text,
25+
tips: theme.colors.primary_text,
26+
});
27+
28+
const render = () => {
29+
if (item.renderItem) {
30+
return item.renderItem(item, index);
31+
} else if (renderItem) {
32+
return renderItem(item, index);
33+
}
34+
return (
35+
<Fragment>
36+
<View>
37+
<Text style={styles.title}>{item.title}</Text>
38+
</View>
39+
{item.tips && <Text style={styles.tips}>{item.tips}</Text>}
40+
{item.desc && <Desc theme={theme} desc={item.desc} />}
41+
</Fragment>
42+
);
43+
};
44+
45+
return (
46+
<View
47+
style={[
48+
styles.item,
49+
mode === 'left' && styles.itemLeft,
50+
mode === 'alternate' && index % 2 === 0 && styles.itemEnd,
51+
]}
52+
>
53+
<View style={[styles.left, mode === 'alternate' && styles.leftAlternate]}>
54+
{!end && <View style={styles.line} />}
55+
<View style={styles.icons}>
56+
<IconCustom theme={theme} icon={item.icon} size={item.size} color={item.color} />
57+
</View>
58+
</View>
59+
60+
<View
61+
style={[
62+
!mode && styles.content,
63+
mode === 'left' && styles.contentLeft,
64+
mode === 'alternate' && styles.contentAlternate,
65+
mode === 'alternate' && index % 2 !== 0 && styles.contentAlternateLeft,
66+
mode === 'alternate' && index % 2 === 0 && styles.contentAlternateRight,
67+
]}
68+
>
69+
{render()}
70+
</View>
71+
</View>
72+
);
73+
};
74+
75+
function createStyles({ backgroundColor = '', title = '#666', tips = '#666', line = '#e4e7ed' }) {
76+
return StyleSheet.create({
77+
item: {
78+
position: 'relative',
79+
paddingBottom: 20,
80+
display: 'flex',
81+
flexDirection: 'row',
82+
},
83+
itemLeft: {
84+
flexDirection: 'row-reverse',
85+
},
86+
itemEnd: {
87+
justifyContent: 'flex-end',
88+
},
89+
90+
left: {
91+
width: 24,
92+
position: 'relative',
93+
},
94+
leftAlternate: {
95+
position: 'absolute',
96+
left: '50%',
97+
marginLeft: -12,
98+
top: 5,
99+
bottom: 10,
100+
},
101+
content: {
102+
paddingLeft: 12,
103+
paddingRight: 12,
104+
flex: 1,
105+
},
106+
contentLeft: {
107+
flex: 1,
108+
paddingLeft: 12,
109+
paddingRight: 12,
110+
alignItems: 'flex-end',
111+
},
112+
contentAlternate: {
113+
paddingLeft: 12,
114+
paddingRight: 12,
115+
width: '47%',
116+
},
117+
contentAlternateLeft: {
118+
alignItems: 'flex-end',
119+
textAlign: 'right',
120+
},
121+
contentAlternateRight: {},
122+
icons: {
123+
width: 24,
124+
display: 'flex',
125+
alignItems: 'center',
126+
},
127+
128+
line: {
129+
position: 'absolute',
130+
left: 12,
131+
top: 22,
132+
bottom: -17,
133+
width: 1,
134+
backgroundColor: line,
135+
},
136+
wrapper: {
137+
paddingLeft: 20,
138+
},
139+
top: {},
140+
tips: {
141+
color: tips,
142+
marginTop: 8,
143+
},
144+
title: {
145+
fontSize: 15,
146+
lineHeight: 20,
147+
color: title,
148+
},
149+
});
150+
}

packages/core/src/Timeline/README.md

+57
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,68 @@ function Demo() {
147147
export default Demo
148148
```
149149

150+
### 自定义渲染
151+
152+
```jsx mdx:preview&background=#bebebe29
153+
import React from 'react'
154+
import { View } from 'react-native'
155+
import { Card, Icon, WingBlank, Timeline, List } from '@uiw/react-native';
156+
157+
158+
function Demo() {
159+
const item = [
160+
{
161+
title: '声明式声明式',
162+
tips: '2021-08-07 12:00:00',
163+
desc: 'React 使创建交互式',
164+
icon: <Icon name="smile" fill="red" size={18} />
165+
},
166+
{
167+
icon: 'qq',
168+
renderItem: () => {
169+
return (
170+
<List>
171+
<List.Item style={{ height: 50 }}>首曝海报特写诡异人脸</List.Item>
172+
<List.Item>六大变五大?传迪士尼600亿收购福斯</List.Item>
173+
<List.Item>快跑!《侏罗纪世界2》正式预告要来了</List.Item>
174+
</List>
175+
)
176+
}
177+
},
178+
{
179+
title: '随处编写',
180+
tips: '2021-08-09 12:00:00',
181+
desc: '服务器渲染。',
182+
},
183+
{
184+
title: '一次学习,随处编写',
185+
tips: '2021-08-10 12:00:00',
186+
desc: '开发新功能。',
187+
},
188+
];
189+
return (
190+
<Card title="展示在左边">
191+
<WingBlank>
192+
<Timeline
193+
style={{ backgroundColor: '#fff' }}
194+
items={item}
195+
/>
196+
</WingBlank>
197+
</Card>
198+
);
199+
}
200+
export default Demo
201+
```
202+
203+
150204
### Props
205+
151206
| 参数 | 说明 | 类型 | 默认值 |
152207
|------|------|-----|------|
153208
| items | 步骤条数据列表 | `TimelineItemsProps[]` | - |
154209
| isReverse | 是否倒序 | `boolean` | - |
155210
| mode | 改变时间轴和内容的相对位置 | `'left' \| 'alternate'` | - |
211+
| renderItem | 自定义渲染 | `(item: TimelineItemsProps, index: number) => React.ReactNode;` | - |
156212

157213

158214
### TimelineItemsProps
@@ -164,3 +220,4 @@ export default Demo
164220
| icon | 自定义图标 | `IconsName \| React.ReactElement \| React.ReactNode` | - |
165221
| color | 自定义图标颜色 | `string` | - |
166222
| size | 自定义图标尺寸 | `number` | - |
223+
| renderItem | 自定义渲染(优先级大于`props.renderItem`| `(item: TimelineItemsProps, index: number) => React.ReactNode;` | - |

0 commit comments

Comments
 (0)