Skip to content

refactor(TimeLine): 重构时间轴 & 新增自定义渲染 #629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
574 changes: 574 additions & 0 deletions example/base/ios/Podfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@babel/core": "~7.20.7",
"@babel/runtime": "~7.20.7",
"@react-native-community/eslint-config": "^2.0.0",
"@uimjs/metro-config": "^1.0.2",
"@uimjs/metro-config": "2.0.1",
"babel-jest": "^26.6.3",
"eslint": "^7.32.0",
"jest": "^26.6.3",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/MaskLayer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useMemo } from 'react';
import React, { useState, useMemo, useEffect } from 'react';
import { Modal, ModalProps as RNModalProps, Animated, TouchableOpacity, StyleSheet } from 'react-native';
import { usePrevious } from '../utils';
import { Theme } from '../theme';
Expand Down Expand Up @@ -76,7 +76,7 @@ const MaskLayer = (props: MaskLayerProps = {}) => {
const preVisible = usePrevious<boolean | undefined>(props.visible);
const [visibleModal, setVisibleModal] = useState(false);
const [bgOpacity] = useState(new Animated.Value(0));
useMemo(() => {
useEffect(() => {
if (preVisible !== props.visible && props.visible) {
setVisible(!!props.visible);
setVisibleModal(false);
Expand Down
39 changes: 39 additions & 0 deletions packages/core/src/Timeline/Desc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { View, Text, StyleSheet } from 'react-native';
import { useTheme } from '@shopify/restyle';
import { Theme } from '../theme';

interface DescProps {
desc?: string | string[];
theme: Theme;
}

export const Desc = ({ desc, theme }: DescProps) => {
const styles = createStyles({
desc: theme.colors.text,
});

if (Array.isArray(desc) && desc.length) {
const descs: string[] = desc as string[];
return (
<View>
{descs.map((item, index) => (
<Text style={styles.desc} key={index}>
{item}
</Text>
))}
</View>
);
}
return <Text style={styles.desc}>{desc}</Text>;
};

function createStyles({ desc = '#5e6d82' }) {
return StyleSheet.create({
desc: {
color: desc,
fontSize: 14,
marginTop: 10,
lineHeight: 20,
},
});
}
29 changes: 29 additions & 0 deletions packages/core/src/Timeline/Icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Fragment } from 'react';
import Icon, { IconsName } from '../Icon';
import { Theme } from '../theme';
import type { TimelineItemsIcons } from './types';

interface IconCustomProps {
icon?: TimelineItemsIcons;
size?: number;
color?: string;
theme: Theme;
}

export const IconCustom = (props: IconCustomProps) => {
const { icon, size, color, theme } = props;

if (icon) {
if (typeof icon === 'string') {
return <Icon name={icon as IconsName} size={size ? size : 15} color={color ? color : 'red'} />;
}
return <Fragment>{icon}</Fragment>;
}
return (
<Icon
name="circle-o"
size={size ? size : 15}
color={color ? color : theme.colors.primary_background || '#3578e5'}
/>
);
};
150 changes: 150 additions & 0 deletions packages/core/src/Timeline/Items.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { Fragment } from 'react';
import { View, Text, StyleSheet, ViewProps } from 'react-native';
import { IconCustom } from './Icons';
import { Desc } from './Desc';
import { useTheme } from '@shopify/restyle';
import { Theme } from '../theme';
import type { TimelineItemsProps, TimelineItemsMode, TimelineProps } from './types';

interface LineItemProps {
item: TimelineItemsProps;
mode?: TimelineItemsMode;
end?: boolean;
index: number;
renderItem?: TimelineProps['renderItem'];
}

export const Items = (props: LineItemProps) => {
const theme = useTheme<Theme>();
const { item, end, mode, index, renderItem } = props;

const styles = createStyles({
backgroundColor: theme.colors.mask,
title: theme.colors.primary_text,
line: theme.colors.primary_text,
tips: theme.colors.primary_text,
});

const render = () => {
if (item.renderItem) {
return item.renderItem(item, index);
} else if (renderItem) {
return renderItem(item, index);
}
return (
<Fragment>
<View>
<Text style={styles.title}>{item.title}</Text>
</View>
{item.tips && <Text style={styles.tips}>{item.tips}</Text>}
{item.desc && <Desc theme={theme} desc={item.desc} />}
</Fragment>
);
};

return (
<View
style={[
styles.item,
mode === 'left' && styles.itemLeft,
mode === 'alternate' && index % 2 === 0 && styles.itemEnd,
]}
>
<View style={[styles.left, mode === 'alternate' && styles.leftAlternate]}>
{!end && <View style={styles.line} />}
<View style={styles.icons}>
<IconCustom theme={theme} icon={item.icon} size={item.size} color={item.color} />
</View>
</View>

<View
style={[
!mode && styles.content,
mode === 'left' && styles.contentLeft,
mode === 'alternate' && styles.contentAlternate,
mode === 'alternate' && index % 2 !== 0 && styles.contentAlternateLeft,
mode === 'alternate' && index % 2 === 0 && styles.contentAlternateRight,
]}
>
{render()}
</View>
</View>
);
};

function createStyles({ backgroundColor = '', title = '#666', tips = '#666', line = '#e4e7ed' }) {
return StyleSheet.create({
item: {
position: 'relative',
paddingBottom: 20,
display: 'flex',
flexDirection: 'row',
},
itemLeft: {
flexDirection: 'row-reverse',
},
itemEnd: {
justifyContent: 'flex-end',
},

left: {
width: 24,
position: 'relative',
},
leftAlternate: {
position: 'absolute',
left: '50%',
marginLeft: -12,
top: 5,
bottom: 10,
},
content: {
paddingLeft: 12,
paddingRight: 12,
flex: 1,
},
contentLeft: {
flex: 1,
paddingLeft: 12,
paddingRight: 12,
alignItems: 'flex-end',
},
contentAlternate: {
paddingLeft: 12,
paddingRight: 12,
width: '47%',
},
contentAlternateLeft: {
alignItems: 'flex-end',
textAlign: 'right',
},
contentAlternateRight: {},
icons: {
width: 24,
display: 'flex',
alignItems: 'center',
},

line: {
position: 'absolute',
left: 12,
top: 22,
bottom: -17,
width: 1,
backgroundColor: line,
},
wrapper: {
paddingLeft: 20,
},
top: {},
tips: {
color: tips,
marginTop: 8,
},
title: {
fontSize: 15,
lineHeight: 20,
color: title,
},
});
}
57 changes: 57 additions & 0 deletions packages/core/src/Timeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,68 @@ function Demo() {
export default Demo
```

### 自定义渲染

```jsx mdx:preview&background=#bebebe29
import React from 'react'
import { View } from 'react-native'
import { Card, Icon, WingBlank, Timeline, List } from '@uiw/react-native';


function Demo() {
const item = [
{
title: '声明式声明式',
tips: '2021-08-07 12:00:00',
desc: 'React 使创建交互式',
icon: <Icon name="smile" fill="red" size={18} />
},
{
icon: 'qq',
renderItem: () => {
return (
<List>
<List.Item style={{ height: 50 }}>首曝海报特写诡异人脸</List.Item>
<List.Item>六大变五大?传迪士尼600亿收购福斯</List.Item>
<List.Item>快跑!《侏罗纪世界2》正式预告要来了</List.Item>
</List>
)
}
},
{
title: '随处编写',
tips: '2021-08-09 12:00:00',
desc: '服务器渲染。',
},
{
title: '一次学习,随处编写',
tips: '2021-08-10 12:00:00',
desc: '开发新功能。',
},
];
return (
<Card title="展示在左边">
<WingBlank>
<Timeline
style={{ backgroundColor: '#fff' }}
items={item}
/>
</WingBlank>
</Card>
);
}
export default Demo
```


### Props

| 参数 | 说明 | 类型 | 默认值 |
|------|------|-----|------|
| items | 步骤条数据列表 | `TimelineItemsProps[]` | - |
| isReverse | 是否倒序 | `boolean` | - |
| mode | 改变时间轴和内容的相对位置 | `'left' \| 'alternate'` | - |
| renderItem | 自定义渲染 | `(item: TimelineItemsProps, index: number) => React.ReactNode;` | - |


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