Skip to content

Commit df42ff4

Browse files
authored
Merge pull request #934 from thundersdata-frontend/rn-issue
refactor: 重构picker模块
2 parents 9ecb198 + a7f8412 commit df42ff4

File tree

26 files changed

+695
-854
lines changed

26 files changed

+695
-854
lines changed

.changeset/cold-coats-reply.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@td-design/react-native-picker': major
3+
---
4+
5+
重构picker模块

packages/react-native-picker/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"react-native-builder-bob": "^0.21.3",
3838
"react-native-gesture-handler": "^2.12.0",
3939
"react-native-reanimated": "^3.3.0",
40+
"react-native-safe-area-context": "^4.7.1",
4041
"typescript": "^5.1.6"
4142
},
4243
"react-native-builder-bob": {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { ForwardedRef } from 'react';
2+
import { Modal, StyleSheet } from 'react-native';
3+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
4+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
5+
6+
import { Box, Flex, helpers, Pressable, Text } from '@td-design/react-native';
7+
8+
import WheelPicker from '../components/WheelPicker';
9+
import { CascaderProps, PickerRef } from '../type';
10+
import useCascader from './useCascader';
11+
12+
const { ONE_PIXEL, px } = helpers;
13+
14+
function Cascader(
15+
{
16+
data,
17+
cols = 3,
18+
activeOpacity = 0.6,
19+
title,
20+
cancelText = '取消',
21+
okText = '确定',
22+
value,
23+
onChange,
24+
...restProps
25+
}: CascaderProps,
26+
ref: ForwardedRef<PickerRef>
27+
) {
28+
const { childrenTree, stateValue, handleValueChange, handleOk, handleClose, visible } = useCascader({
29+
data,
30+
cols,
31+
value,
32+
onChange,
33+
ref,
34+
});
35+
36+
const { bottom } = useSafeAreaInsets();
37+
38+
if (childrenTree.length === 0) return null;
39+
40+
return (
41+
<Modal visible={visible} statusBarTranslucent animationType="none" transparent>
42+
<GestureHandlerRootView style={{ flex: 1 }}>
43+
<Box flex={1} justifyContent={'flex-end'} style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}>
44+
<Flex
45+
borderBottomWidth={ONE_PIXEL}
46+
borderBottomColor="border"
47+
backgroundColor="white"
48+
paddingVertical="x3"
49+
paddingHorizontal="x3"
50+
>
51+
<Pressable activeOpacity={activeOpacity} onPress={handleClose} style={styles.cancel}>
52+
<Text variant="p0" color="primary200">
53+
{cancelText}
54+
</Text>
55+
</Pressable>
56+
<Flex.Item alignItems="center">
57+
<Text variant="p0" color="text">
58+
{title}
59+
</Text>
60+
</Flex.Item>
61+
<Pressable activeOpacity={activeOpacity} onPress={handleOk} style={styles.submit}>
62+
<Text variant="p0" color="primary200">
63+
{okText}
64+
</Text>
65+
</Pressable>
66+
</Flex>
67+
<Flex backgroundColor={'white'} height={px(200)} style={{ paddingBottom: bottom }}>
68+
{childrenTree.map((item, index) => (
69+
<WheelPicker
70+
key={index}
71+
{...restProps}
72+
{...{ data: item.map(el => ({ ...el, value: el.value })), value: stateValue[index] }}
73+
onChange={value => handleValueChange(value, index)}
74+
/>
75+
))}
76+
</Flex>
77+
</Box>
78+
</GestureHandlerRootView>
79+
</Modal>
80+
);
81+
}
82+
83+
const styles = StyleSheet.create({
84+
cancel: { justifyContent: 'center', alignItems: 'flex-start' },
85+
submit: { justifyContent: 'center', alignItems: 'flex-end' },
86+
});
87+
88+
export default React.forwardRef(Cascader);

packages/react-native-picker/src/picker/components/Cascade/useCascader.ts renamed to packages/react-native-picker/src/cascade-picker/useCascader.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
1-
import { useMemo } from 'react';
1+
import { ForwardedRef, useImperativeHandle, useMemo } from 'react';
22

3-
import { ImperativeModalChildrenProps } from '@td-design/react-native/lib/typescript/modal/type';
43
import { useMemoizedFn, useSafeState } from '@td-design/rn-hooks';
54
import arrayTreeFilter from 'array-tree-filter';
65

7-
import { CascadePickerItemProps, PickerData } from '../../../components/WheelPicker/type';
8-
import { CascaderProps } from '../../type';
6+
import { CascadePickerItemProps, PickerData } from '../components/WheelPicker/type';
7+
import { CascaderProps, PickerRef } from '../type';
98

10-
export default function useCascader<T>({
9+
export default function useCascader({
1110
data,
1211
cols = 3,
1312
value,
1413
onChange,
15-
closeModal,
16-
}: ImperativeModalChildrenProps<Pick<CascaderProps<T>, 'data' | 'cols' | 'value' | 'onChange'>>) {
17-
const [stateValue, setStateValue] = useSafeState<T[]>(generateNextValue(data, value, cols));
18-
19-
const handleValueChange = (value: PickerData<T>, index: number) => {
14+
ref,
15+
}: Pick<CascaderProps, 'data' | 'cols' | 'value' | 'onChange'> & {
16+
ref: ForwardedRef<PickerRef>;
17+
}) {
18+
const [stateValue, setStateValue] = useSafeState<(string | number)[]>(generateNextValue(data, value, cols));
19+
const [visible, setVisible] = useSafeState(false);
20+
21+
useImperativeHandle(ref, () => ({
22+
show: () => {
23+
setVisible(true);
24+
},
25+
hide: () => {
26+
setVisible(false);
27+
},
28+
}));
29+
30+
const handleValueChange = (value: PickerData<string | number>, index: number) => {
2031
const newValue = [...stateValue];
2132
// 修改当前的值,然后把后面的值都清掉
2233
newValue[index] = value.value;
@@ -27,13 +38,13 @@ export default function useCascader<T>({
2738

2839
const handleOk = () => {
2940
onChange?.(stateValue);
30-
closeModal?.();
41+
setVisible(false);
3142
};
3243

3344
const handleClose = () => {
3445
const nextValue = generateNextValue(data, value, cols);
3546
setStateValue(nextValue);
36-
closeModal?.();
47+
setVisible(false);
3748
};
3849

3950
const childrenTree = useMemo(() => {
@@ -51,13 +62,13 @@ export default function useCascader<T>({
5162
childrenTree.length = cols! - 1;
5263
childrenTree.unshift(data);
5364

54-
return childrenTree;
65+
return childrenTree as CascadePickerItemProps<string | number>[][];
5566
}, [data, stateValue, cols]);
5667

5768
return {
5869
stateValue,
5970
childrenTree,
60-
71+
visible,
6172
handleValueChange: useMemoizedFn(handleValueChange),
6273
handleOk: useMemoizedFn(handleOk),
6374
handleClose: useMemoizedFn(handleClose),

packages/react-native-picker/src/components/DatePicker/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { FC } from 'react';
2+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
23

34
import { Flex } from '@td-design/react-native';
45
import dayjs from 'dayjs';
@@ -21,10 +22,11 @@ const DatePicker: FC<
2122
onChange,
2223
});
2324

25+
const { bottom } = useSafeAreaInsets();
2426
const { values, cols } = getValueCols();
2527

2628
return (
27-
<Flex>
29+
<Flex backgroundColor="white" style={{ paddingBottom: bottom }}>
2830
{cols.map((col, index) => {
2931
return (
3032
<WheelPicker

packages/react-native-picker/src/components/DatePicker/type.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { SyntheticEvent } from 'react';
22

3-
import { ModalPickerProps } from '../../picker/type';
3+
import { ModalPickerProps } from '../../type';
44
import { CascadePickerItemProps, WheelPickerPropsBase } from '../WheelPicker/type';
55

66
export type Event = SyntheticEvent<
@@ -26,9 +26,9 @@ export interface DatePickerPropsBase extends WheelPickerPropsBase {
2626
/** 日期修改事件 */
2727
onChange?: (date?: Date, formatDate?: string) => void;
2828
/** 最小日期 */
29-
minDate?: string;
29+
minDate?: Date | string;
3030
/** 最大日期 */
31-
maxDate?: string;
31+
maxDate?: Date | string;
3232
}
3333

3434
export { CascadePickerItemProps, ModalPickerProps, DateUnit, DateRef };

packages/react-native-picker/src/date-period-input/index.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Box, Brief, Flex, helpers, Label, Pressable, SvgIcon, Text, useTheme }
55
import dayjs from 'dayjs';
66

77
import { DatePickerPropsBase, ModalPickerProps } from '../components/DatePicker/type';
8+
import DatePicker from '../date-picker';
89
import useDatePeriodInput from './useDatePeriodInput';
910

1011
export interface DatePeriodInputProps
@@ -51,12 +52,11 @@ const DatePeriodInput: FC<DatePeriodInputProps> = ({
5152
...restProps
5253
}) => {
5354
const theme = useTheme();
54-
const { dates, handleStartPress, handleEndPress, clearStartDate, clearEndDate } = useDatePeriodInput({
55-
value,
56-
onChange,
57-
format,
58-
...restProps,
59-
});
55+
const { dates, order, datePickerRef, handleChange, handleStartPress, handleEndPress, clearStartDate, clearEndDate } =
56+
useDatePeriodInput({
57+
value,
58+
onChange,
59+
});
6060

6161
const styles = StyleSheet.create({
6262
content: {
@@ -84,7 +84,7 @@ const DatePeriodInput: FC<DatePeriodInputProps> = ({
8484
<Pressable disabled={disabled} onPress={handleStartPress} activeOpacity={activeOpacity} style={styles.content}>
8585
<Flex>
8686
<SvgIcon name="date" color={theme.colors.icon} />
87-
<Text variant="p1" color={disabled ? 'disabled' : dates[0] ? 'text' : 'gray300'} marginLeft="x2">
87+
<Text variant="p2" color={disabled ? 'disabled' : dates[0] ? 'text' : 'gray300'} marginLeft="x2">
8888
{dates[0] ? dayjs(dates[0]).format(format) : placeholders[0]}
8989
</Text>
9090
</Flex>
@@ -102,7 +102,7 @@ const DatePeriodInput: FC<DatePeriodInputProps> = ({
102102
<Pressable disabled={disabled} onPress={handleEndPress} activeOpacity={activeOpacity} style={styles.content}>
103103
<Flex>
104104
<SvgIcon name="date" color={theme.colors.icon} />
105-
<Text variant="p1" color={disabled ? 'disabled' : dates[1] ? 'text' : 'gray300'} marginLeft="x2">
105+
<Text variant="p2" color={disabled ? 'disabled' : dates[1] ? 'text' : 'gray300'} marginLeft="x2">
106106
{dates[1] ? dayjs(dates[1]).format(format) : placeholders[1]}
107107
</Text>
108108
</Flex>
@@ -132,6 +132,14 @@ const DatePeriodInput: FC<DatePeriodInputProps> = ({
132132
<Brief brief={brief} />
133133
</Box>
134134
)}
135+
<DatePicker
136+
ref={datePickerRef}
137+
value={order === 'start' ? dates[0] : dates[1]}
138+
onChange={handleChange}
139+
{...restProps}
140+
minDate={order === 'end' ? dates[0] : undefined}
141+
maxDate={order === 'start' ? dates[1] : undefined}
142+
/>
135143
</>
136144
);
137145
};

packages/react-native-picker/src/date-period-input/useDatePeriodInput.tsx

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
1-
import React from 'react';
1+
import { useRef } from 'react';
22
import { Keyboard } from 'react-native';
33

4-
import { Modal } from '@td-design/react-native';
5-
import { ImperativeModalChildrenProps } from '@td-design/react-native/lib/typescript/modal/type';
64
import { useMemoizedFn, useSafeState } from '@td-design/rn-hooks';
7-
import dayjs from 'dayjs';
85

96
import type { DatePeriodInputProps } from '.';
10-
import DatePicker from '../date-picker';
7+
import { DatePickerRef } from '../type';
118

12-
export default function useDatePeriodInput({
13-
value,
14-
onChange,
15-
format,
16-
...restProps
17-
}: ImperativeModalChildrenProps<Pick<DatePeriodInputProps, 'value' | 'onChange' | 'format'>>) {
9+
export default function useDatePeriodInput({ value, onChange }: Pick<DatePeriodInputProps, 'value' | 'onChange'>) {
10+
const datePickerRef = useRef<DatePickerRef>(null);
11+
const [order, setOrder] = useSafeState<'start' | 'end'>('start');
1812
const [dates, setDates] = useSafeState(value ?? [undefined, undefined]);
1913

20-
const handleChange = useMemoizedFn((date: Date | undefined, index: number) => {
14+
const handleChange = useMemoizedFn((date: Date | undefined) => {
2115
const [startDate, endDate] = dates;
2216
if (onChange) {
23-
onChange(index === 0 ? [date!, endDate] : [startDate, date!]);
17+
onChange(order === 'start' ? [date!, endDate] : [startDate, date!]);
2418
} else {
2519
setDates((draft: any[]) => {
2620
const nextDates = [...draft];
27-
nextDates[index] = date;
21+
nextDates[order === 'start' ? 0 : 1] = date;
2822
return nextDates;
2923
});
3024
}
@@ -33,41 +27,15 @@ export default function useDatePeriodInput({
3327
/** 点开开始时间选择器 */
3428
const handleStartPress = () => {
3529
Keyboard.dismiss();
36-
Modal.show(
37-
<DatePicker
38-
{...restProps}
39-
{...{
40-
format,
41-
onChange: date => handleChange(date, 0),
42-
value: dates[0],
43-
}}
44-
minDate={undefined}
45-
maxDate={dates[1] ? dayjs(dates[1]).format(format) : undefined}
46-
/>,
47-
{
48-
position: 'bottom',
49-
}
50-
);
30+
setOrder('start');
31+
datePickerRef.current?.show();
5132
};
5233

5334
/** 点开结束时间选择器 */
5435
const handleEndPress = () => {
5536
Keyboard.dismiss();
56-
Modal.show(
57-
<DatePicker
58-
{...restProps}
59-
{...{
60-
format,
61-
onChange: date => handleChange(date, 1),
62-
value: dates[1],
63-
}}
64-
minDate={dates[0] ? dayjs(dates[0]).format(format) : undefined}
65-
maxDate={undefined}
66-
/>,
67-
{
68-
position: 'bottom',
69-
}
70-
);
37+
setOrder('end');
38+
datePickerRef.current?.show();
7139
};
7240

7341
/**
@@ -97,6 +65,9 @@ export default function useDatePeriodInput({
9765

9866
return {
9967
dates,
68+
order,
69+
datePickerRef,
70+
handleChange: useMemoizedFn(handleChange),
10071
handleStartPress: useMemoizedFn(handleStartPress),
10172
handleEndPress: useMemoizedFn(handleEndPress),
10273
clearStartDate: useMemoizedFn(clearStartDate),

0 commit comments

Comments
 (0)