1import { DateTimePicker } from "@expo/ui/community/datetime-picker";
2import {
3 localSnoozeDate,
4 localSnoozeTime,
5 resolveCustomSnooze,
6 type CustomSnoozeInput,
7} from "@t3tools/client-runtime/state/thread-settled";
8import { useState } from "react";
9import {
10 KeyboardAvoidingView,
11 Modal,
12 Platform,
13 Pressable,
14 ScrollView,
15 TextInput,
16 View,
17} from "react-native";
18import { AppText } from "../../components/AppText";
19import { SegmentedControl } from "../../components/SegmentedControl";
20
21export function CustomSnoozeSheet(props: {
22 readonly onClose: () => void;
23 readonly onSnooze: (snoozedUntil: string) => void;
24}) {
25 const [mode, setMode] = useState<CustomSnoozeInput["mode"]>("date");
26 const [date, setDate] = useState(() => new Date(Date.now() + 3_600_000));
27 const [picker, setPicker] = useState<"date" | "time" | null>(null);
28 const [amount, setAmount] = useState("2");
29 const [unit, setUnit] = useState<"minutes" | "hours" | "days">("hours");
30 const [error, setError] = useState<string | null>(null);
31 return (
32 <Modal visible transparent animationType="fade" onRequestClose={props.onClose}>
33 <KeyboardAvoidingView
34 className="flex-1 items-center justify-center bg-backdrop px-6"
35 behavior={Platform.OS === "ios" ? "padding" : undefined}
36 >
37 <ScrollView
38 className="max-h-[80%] w-full max-w-md grow-0 rounded-3xl bg-screen"
39 keyboardShouldPersistTaps="handled"
40 contentInsetAdjustmentBehavior="automatic"
41 contentContainerStyle={{ padding: 24, paddingBottom: 24, gap: 20 }}
42 >
43 <AppText accessibilityRole="header" className="text-xl font-t3-semibold">
44 Custom snooze
45 </AppText>
46 <AppText className="text-base text-foreground-secondary">
47 Choose when snoozed threads return to your inbox.
48 </AppText>
49 <SegmentedControl
50 options={
51 [
52 { value: "date", label: "Date and time" },
53 { value: "duration", label: "Duration" },
54 ] as const
55 }
56 selected={mode}
57 onSelect={(value) => {
58 setMode(value);
59 setPicker(null);
60 setError(null);
61 }}
62 role="tab"
63 />
64 {mode === "date" ? (
65 <View className="gap-3">
66 {(["date", "time"] as const).map((value) => (
67 <Pressable
68 key={value}
69 accessibilityRole="button"
70 accessibilityLabel={value === "date" ? "Choose date" : "Choose time"}
71 className="min-h-12 flex-row items-center justify-between rounded-xl bg-subtle px-3"
72 onPress={() => setPicker(value)}
73 >
74 <AppText>{value === "date" ? "Date" : "Time"}</AppText>
75 <AppText>
76 {value === "date"
77 ? date.toLocaleDateString()
78 : date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })}
79 </AppText>
80 </Pressable>
81 ))}
82 {picker && (
83 <DateTimePicker
84 value={date}
85 mode={picker}
86 display={Platform.OS === "ios" ? "spinner" : "default"}
87 onDismiss={() => setPicker(null)}
88 onValueChange={(_, selected) => {
89 const next = new Date(date);
90 if (picker === "date")
91 next.setFullYear(
92 selected.getFullYear(),
93 selected.getMonth(),
94 selected.getDate(),
95 );
96 else next.setHours(selected.getHours(), selected.getMinutes(), 0, 0);
97 setDate(next);
98 setError(null);
99 }}
100 />
101 )}
102 </View>
103 ) : (
104 <View className="gap-3">
105 <AppText>Snooze for</AppText>
106 <TextInput
107 accessibilityLabel="Duration"
108 className="min-h-12 rounded-xl bg-subtle px-3 text-base text-foreground"
109 keyboardType="decimal-pad"
110 value={amount}
111 onChangeText={(value) => {
112 setAmount(value);
113 setError(null);
114 }}
115 />
116 <View className="flex-row gap-2">
117 {(["minutes", "hours", "days"] as const).map((value) => (
118 <Pressable
119 key={value}
120 accessibilityRole="radio"
121 accessibilityState={{ checked: unit === value }}
122 className={
123 unit === value
124 ? "min-h-12 flex-1 items-center justify-center rounded-xl bg-subtle"
125 : "min-h-12 flex-1 items-center justify-center rounded-xl"
126 }
127 onPress={() => {
128 setUnit(value);
129 setError(null);
130 }}
131 >
132 <AppText>
133 {value === "minutes" ? "Minutes" : value === "hours" ? "Hours" : "Days"}
134 </AppText>
135 </Pressable>
136 ))}
137 </View>
138 </View>
139 )}
140 {error && (
141 <AppText accessibilityRole="alert" className="text-danger-foreground">
142 {error}
143 </AppText>
144 )}
145 <View className="flex-row justify-end gap-3">
146 <Pressable
147 accessibilityRole="button"
148 className="min-h-12 justify-center px-3"
149 onPress={props.onClose}
150 >
151 <AppText>Cancel</AppText>
152 </Pressable>
153 <Pressable
154 accessibilityRole="button"
155 className="min-h-12 justify-center rounded-xl bg-subtle px-3"
156 onPress={() => {
157 const input: CustomSnoozeInput =
158 mode === "date"
159 ? { mode, date: localSnoozeDate(date), time: localSnoozeTime(date) }
160 : { mode, amount: amount.replace(",", "."), unit };
161 const snoozedUntil = resolveCustomSnooze(input, new Date());
162 if (!snoozedUntil) {
163 setError(
164 mode === "date"
165 ? "Choose a date and time in the future."
166 : "Enter a positive duration.",
167 );
168 return;
169 }
170 props.onSnooze(snoozedUntil);
171 props.onClose();
172 }}
173 >
174 <AppText className="text-foreground">Snooze</AppText>
175 </Pressable>
176 </View>
177 </ScrollView>
178 </KeyboardAvoidingView>
179 </Modal>
180 );
181}