Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/cold-coats-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@td-design/react-native-picker': major
---

重构picker模块
1 change: 1 addition & 0 deletions packages/react-native-picker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"react-native-builder-bob": "^0.21.3",
"react-native-gesture-handler": "^2.12.0",
"react-native-reanimated": "^3.3.0",
"react-native-safe-area-context": "^4.7.1",
"typescript": "^5.1.6"
},
"react-native-builder-bob": {
Expand Down
88 changes: 88 additions & 0 deletions packages/react-native-picker/src/cascade-picker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { ForwardedRef } from 'react';
import { Modal, StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { Box, Flex, helpers, Pressable, Text } from '@td-design/react-native';

import WheelPicker from '../components/WheelPicker';
import { CascaderProps, PickerRef } from '../type';
import useCascader from './useCascader';

const { ONE_PIXEL, px } = helpers;

function Cascader(
{
data,
cols = 3,
activeOpacity = 0.6,
title,
cancelText = '取消',
okText = '确定',
value,
onChange,
...restProps
}: CascaderProps,
ref: ForwardedRef<PickerRef>
) {
const { childrenTree, stateValue, handleValueChange, handleOk, handleClose, visible } = useCascader({
data,
cols,
value,
onChange,
ref,
});

const { bottom } = useSafeAreaInsets();

if (childrenTree.length === 0) return null;

return (
<Modal visible={visible} statusBarTranslucent animationType="none" transparent>
<GestureHandlerRootView style={{ flex: 1 }}>
<Box flex={1} justifyContent={'flex-end'} style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}>
<Flex
borderBottomWidth={ONE_PIXEL}
borderBottomColor="border"
backgroundColor="white"
paddingVertical="x3"
paddingHorizontal="x3"
>
<Pressable activeOpacity={activeOpacity} onPress={handleClose} style={styles.cancel}>
<Text variant="p0" color="primary200">
{cancelText}
</Text>
</Pressable>
<Flex.Item alignItems="center">
<Text variant="p0" color="text">
{title}
</Text>
</Flex.Item>
<Pressable activeOpacity={activeOpacity} onPress={handleOk} style={styles.submit}>
<Text variant="p0" color="primary200">
{okText}
</Text>
</Pressable>
</Flex>
<Flex backgroundColor={'white'} height={px(200)} style={{ paddingBottom: bottom }}>
{childrenTree.map((item, index) => (
<WheelPicker
key={index}
{...restProps}
{...{ data: item.map(el => ({ ...el, value: el.value })), value: stateValue[index] }}
onChange={value => handleValueChange(value, index)}
/>
))}
</Flex>
</Box>
</GestureHandlerRootView>
</Modal>
);
}

const styles = StyleSheet.create({
cancel: { justifyContent: 'center', alignItems: 'flex-start' },
submit: { justifyContent: 'center', alignItems: 'flex-end' },
});

export default React.forwardRef(Cascader);
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import { useMemo } from 'react';
import { ForwardedRef, useImperativeHandle, useMemo } from 'react';

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

import { CascadePickerItemProps, PickerData } from '../../../components/WheelPicker/type';
import { CascaderProps } from '../../type';
import { CascadePickerItemProps, PickerData } from '../components/WheelPicker/type';
import { CascaderProps, PickerRef } from '../type';

export default function useCascader<T>({
export default function useCascader({
data,
cols = 3,
value,
onChange,
closeModal,
}: ImperativeModalChildrenProps<Pick<CascaderProps<T>, 'data' | 'cols' | 'value' | 'onChange'>>) {
const [stateValue, setStateValue] = useSafeState<T[]>(generateNextValue(data, value, cols));

const handleValueChange = (value: PickerData<T>, index: number) => {
ref,
}: Pick<CascaderProps, 'data' | 'cols' | 'value' | 'onChange'> & {
ref: ForwardedRef<PickerRef>;
}) {
const [stateValue, setStateValue] = useSafeState<(string | number)[]>(generateNextValue(data, value, cols));
const [visible, setVisible] = useSafeState(false);

useImperativeHandle(ref, () => ({
show: () => {
setVisible(true);
},
hide: () => {
setVisible(false);
},
}));

const handleValueChange = (value: PickerData<string | number>, index: number) => {
const newValue = [...stateValue];
// 修改当前的值,然后把后面的值都清掉
newValue[index] = value.value;
Expand All @@ -27,13 +38,13 @@ export default function useCascader<T>({

const handleOk = () => {
onChange?.(stateValue);
closeModal?.();
setVisible(false);
};

const handleClose = () => {
const nextValue = generateNextValue(data, value, cols);
setStateValue(nextValue);
closeModal?.();
setVisible(false);
};

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

return childrenTree;
return childrenTree as CascadePickerItemProps<string | number>[][];
}, [data, stateValue, cols]);

return {
stateValue,
childrenTree,

visible,
handleValueChange: useMemoizedFn(handleValueChange),
handleOk: useMemoizedFn(handleOk),
handleClose: useMemoizedFn(handleClose),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { FC } from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

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

const { bottom } = useSafeAreaInsets();
const { values, cols } = getValueCols();

return (
<Flex>
<Flex backgroundColor="white" style={{ paddingBottom: bottom }}>
{cols.map((col, index) => {
return (
<WheelPicker
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SyntheticEvent } from 'react';

import { ModalPickerProps } from '../../picker/type';
import { ModalPickerProps } from '../../type';
import { CascadePickerItemProps, WheelPickerPropsBase } from '../WheelPicker/type';

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

export { CascadePickerItemProps, ModalPickerProps, DateUnit, DateRef };
24 changes: 16 additions & 8 deletions packages/react-native-picker/src/date-period-input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Box, Brief, Flex, helpers, Label, Pressable, SvgIcon, Text, useTheme }
import dayjs from 'dayjs';

import { DatePickerPropsBase, ModalPickerProps } from '../components/DatePicker/type';
import DatePicker from '../date-picker';
import useDatePeriodInput from './useDatePeriodInput';

export interface DatePeriodInputProps
Expand Down Expand Up @@ -51,12 +52,11 @@ const DatePeriodInput: FC<DatePeriodInputProps> = ({
...restProps
}) => {
const theme = useTheme();
const { dates, handleStartPress, handleEndPress, clearStartDate, clearEndDate } = useDatePeriodInput({
value,
onChange,
format,
...restProps,
});
const { dates, order, datePickerRef, handleChange, handleStartPress, handleEndPress, clearStartDate, clearEndDate } =
useDatePeriodInput({
value,
onChange,
});

const styles = StyleSheet.create({
content: {
Expand Down Expand Up @@ -84,7 +84,7 @@ const DatePeriodInput: FC<DatePeriodInputProps> = ({
<Pressable disabled={disabled} onPress={handleStartPress} activeOpacity={activeOpacity} style={styles.content}>
<Flex>
<SvgIcon name="date" color={theme.colors.icon} />
<Text variant="p1" color={disabled ? 'disabled' : dates[0] ? 'text' : 'gray300'} marginLeft="x2">
<Text variant="p2" color={disabled ? 'disabled' : dates[0] ? 'text' : 'gray300'} marginLeft="x2">
{dates[0] ? dayjs(dates[0]).format(format) : placeholders[0]}
</Text>
</Flex>
Expand All @@ -102,7 +102,7 @@ const DatePeriodInput: FC<DatePeriodInputProps> = ({
<Pressable disabled={disabled} onPress={handleEndPress} activeOpacity={activeOpacity} style={styles.content}>
<Flex>
<SvgIcon name="date" color={theme.colors.icon} />
<Text variant="p1" color={disabled ? 'disabled' : dates[1] ? 'text' : 'gray300'} marginLeft="x2">
<Text variant="p2" color={disabled ? 'disabled' : dates[1] ? 'text' : 'gray300'} marginLeft="x2">
{dates[1] ? dayjs(dates[1]).format(format) : placeholders[1]}
</Text>
</Flex>
Expand Down Expand Up @@ -132,6 +132,14 @@ const DatePeriodInput: FC<DatePeriodInputProps> = ({
<Brief brief={brief} />
</Box>
)}
<DatePicker
ref={datePickerRef}
value={order === 'start' ? dates[0] : dates[1]}
onChange={handleChange}
{...restProps}
minDate={order === 'end' ? dates[0] : undefined}
maxDate={order === 'start' ? dates[1] : undefined}
/>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
import React from 'react';
import { useRef } from 'react';
import { Keyboard } from 'react-native';

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

import type { DatePeriodInputProps } from '.';
import DatePicker from '../date-picker';
import { DatePickerRef } from '../type';

export default function useDatePeriodInput({
value,
onChange,
format,
...restProps
}: ImperativeModalChildrenProps<Pick<DatePeriodInputProps, 'value' | 'onChange' | 'format'>>) {
export default function useDatePeriodInput({ value, onChange }: Pick<DatePeriodInputProps, 'value' | 'onChange'>) {
const datePickerRef = useRef<DatePickerRef>(null);
const [order, setOrder] = useSafeState<'start' | 'end'>('start');
const [dates, setDates] = useSafeState(value ?? [undefined, undefined]);

const handleChange = useMemoizedFn((date: Date | undefined, index: number) => {
const handleChange = useMemoizedFn((date: Date | undefined) => {
const [startDate, endDate] = dates;
if (onChange) {
onChange(index === 0 ? [date!, endDate] : [startDate, date!]);
onChange(order === 'start' ? [date!, endDate] : [startDate, date!]);
} else {
setDates((draft: any[]) => {
const nextDates = [...draft];
nextDates[index] = date;
nextDates[order === 'start' ? 0 : 1] = date;
return nextDates;
});
}
Expand All @@ -33,41 +27,15 @@ export default function useDatePeriodInput({
/** 点开开始时间选择器 */
const handleStartPress = () => {
Keyboard.dismiss();
Modal.show(
<DatePicker
{...restProps}
{...{
format,
onChange: date => handleChange(date, 0),
value: dates[0],
}}
minDate={undefined}
maxDate={dates[1] ? dayjs(dates[1]).format(format) : undefined}
/>,
{
position: 'bottom',
}
);
setOrder('start');
datePickerRef.current?.show();
};

/** 点开结束时间选择器 */
const handleEndPress = () => {
Keyboard.dismiss();
Modal.show(
<DatePicker
{...restProps}
{...{
format,
onChange: date => handleChange(date, 1),
value: dates[1],
}}
minDate={dates[0] ? dayjs(dates[0]).format(format) : undefined}
maxDate={undefined}
/>,
{
position: 'bottom',
}
);
setOrder('end');
datePickerRef.current?.show();
};

/**
Expand Down Expand Up @@ -97,6 +65,9 @@ export default function useDatePeriodInput({

return {
dates,
order,
datePickerRef,
handleChange: useMemoizedFn(handleChange),
handleStartPress: useMemoizedFn(handleStartPress),
handleEndPress: useMemoizedFn(handleEndPress),
clearStartDate: useMemoizedFn(clearStartDate),
Expand Down
Loading