Skip to content

Commit

Permalink
refactor(TimeLine): 重构时间轴 & 新增自定义渲染 (#629)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaob421123 committed Oct 26, 2023
1 parent 585f7f0 commit dce305b
Show file tree
Hide file tree
Showing 11 changed files with 908 additions and 195 deletions.
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

0 comments on commit dce305b

Please sign in to comment.