121 lines
3.1 KiB
TypeScript
121 lines
3.1 KiB
TypeScript
import DateTimePicker from "react-native-dates-picker";
|
|
import dayjs from "dayjs";
|
|
import { Button, Spinner, useThemeColor } from "heroui-native";
|
|
import BottomSheet from "@/components/BottomSheet";
|
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
import { View } from "react-native";
|
|
import { CheckIcon } from "lucide-react-native";
|
|
import { FadeIn } from "react-native-reanimated";
|
|
|
|
export type DatePickerMode = "year" | "month" | "date" | "time" | "datetime";
|
|
|
|
export type DateFormatType = "YYYY" | "YYYY-MM" | "YYYY-MM-DD" | "HH:mm:ss" | "YYYY-MM-DD HH:mm";
|
|
|
|
interface DatePickerProps {
|
|
value: Date | string;
|
|
show?: boolean;
|
|
onClose?: () => void;
|
|
onConfirm?: (value: string) => void | Promise<void>;
|
|
mode?: DatePickerMode;
|
|
format?: DateFormatType;
|
|
}
|
|
|
|
const defaultFormatMap: Record<DatePickerMode, DateFormatType> = {
|
|
year: "YYYY",
|
|
month: "YYYY-MM",
|
|
date: "YYYY-MM-DD",
|
|
time: "HH:mm:ss",
|
|
datetime: "YYYY-MM-DD HH:mm",
|
|
};
|
|
|
|
const modeViewMap: Record<DatePickerMode, "day" | "month" | "year"> = {
|
|
year: "year",
|
|
month: "month",
|
|
date: "day",
|
|
time: "day",
|
|
datetime: "day",
|
|
};
|
|
|
|
export default function DateTime({
|
|
value,
|
|
show = false,
|
|
onClose,
|
|
onConfirm,
|
|
mode = "date",
|
|
format,
|
|
}: DatePickerProps) {
|
|
const finalFormat = format ?? defaultFormatMap[mode];
|
|
const accentColor = useThemeColor("accent");
|
|
|
|
const [open, setOpen] = useState(false);
|
|
const [tempDate, setTempDate] = useState<dayjs.Dayjs | undefined>(dayjs());
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const onCloseRef = useRef(onClose);
|
|
onCloseRef.current = onClose;
|
|
|
|
// 打开
|
|
useEffect(() => {
|
|
if (show) {
|
|
const d = dayjs(value);
|
|
setTempDate(d.isValid() ? d : dayjs());
|
|
setOpen(true);
|
|
}
|
|
}, [show, value]);
|
|
|
|
// 关闭
|
|
useEffect(() => {
|
|
if (!open && show) {
|
|
onCloseRef.current?.();
|
|
}
|
|
}, [open, show]);
|
|
|
|
const handleConfirm = useCallback(async () => {
|
|
if (loading || !tempDate) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
const result = tempDate.format(finalFormat);
|
|
await onConfirm?.(result);
|
|
setOpen(false);
|
|
} catch (e) {
|
|
console.log(e);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [loading, tempDate, finalFormat, onConfirm]);
|
|
|
|
return (
|
|
<BottomSheet
|
|
isOpen={open}
|
|
onOpenChange={setOpen}
|
|
>
|
|
<BottomSheet.Content>
|
|
<View className="w-full items-center">
|
|
<DateTimePicker
|
|
mode="single"
|
|
date={tempDate}
|
|
initialView={modeViewMap[mode]}
|
|
timePicker={mode === "time" || mode === "datetime"}
|
|
selectedItemColor={accentColor}
|
|
onChange={({ date }) => {
|
|
if (date) setTempDate(dayjs(date));
|
|
}}
|
|
/>
|
|
|
|
<Button className="w-full" onPress={handleConfirm} isDisabled={loading}>
|
|
{loading ? (
|
|
<Spinner entering={FadeIn.delay(50)} color="white" />
|
|
) : (
|
|
<>
|
|
<CheckIcon color="#fff" />
|
|
<Button.Label>确认</Button.Label>
|
|
</>
|
|
)}
|
|
</Button>
|
|
</View>
|
|
</BottomSheet.Content>
|
|
</BottomSheet>
|
|
);
|
|
}
|