Files
beenvoice/apps/mobile/components/ui/CompactDateField.tsx
T
soconnor d057ba208d Add 'apps/mobile/' from commit '5fa30f365f21531094cd4d2045042bb4f1370ac3'
git-subtree-dir: apps/mobile
git-subtree-mainline: 86f8987dff
git-subtree-split: 5fa30f365f
2026-08-16 21:42:59 -04:00

175 lines
4.6 KiB
TypeScript

import DateTimePicker, {
type DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import { Ionicons } from "@expo/vector-icons";
import { useState } from "react";
import {
Modal,
Platform,
Pressable,
StyleSheet,
Text,
View,
type StyleProp,
type ViewStyle,
} from "react-native";
import { fonts, radii } from "@/constants/theme";
import { useAppTheme } from "@/contexts/ThemeContext";
import { formatShortDate } from "@/lib/format";
type CompactDateFieldProps = {
value: Date;
onChange: (date: Date) => void;
style?: StyleProp<ViewStyle>;
maximumDate?: Date;
minimumDate?: Date;
};
export function CompactDateField({
value,
onChange,
style,
maximumDate = new Date(2100, 0, 1),
minimumDate,
}: CompactDateFieldProps) {
const { colors, isDark } = useAppTheme();
const [open, setOpen] = useState(false);
const [draft, setDraft] = useState(value);
function applyDate(next: Date) {
const clamped =
next.getTime() > maximumDate.getTime()
? maximumDate
: minimumDate && next.getTime() < minimumDate.getTime()
? minimumDate
: next;
onChange(clamped);
}
function handleChange(event: DateTimePickerEvent, selected?: Date) {
if (Platform.OS === "android") {
setOpen(false);
if (event.type === "set" && selected) applyDate(selected);
return;
}
if (selected) setDraft(selected);
}
return (
<>
<Pressable
accessibilityRole="button"
accessibilityLabel="Change date"
onPress={() => {
setDraft(value);
setOpen(true);
}}
style={({ pressed }) => [
styles.trigger,
{
borderColor: colors.border,
backgroundColor: colors.cardGlass,
},
pressed && styles.pressed,
style,
]}
>
<Text style={[styles.value, { color: colors.foreground }]} numberOfLines={1}>
{formatShortDate(value)}
</Text>
<Ionicons name="chevron-down" size={12} color={colors.mutedForeground} />
</Pressable>
{Platform.OS === "ios" ? (
<Modal visible={open} transparent animationType="slide" onRequestClose={() => setOpen(false)}>
<Pressable style={styles.backdrop} onPress={() => setOpen(false)}>
<Pressable
style={[styles.sheet, { backgroundColor: colors.card }]}
onPress={(event) => event.stopPropagation()}
>
<View style={[styles.sheetHeader, { borderBottomColor: colors.border }]}>
<Pressable onPress={() => setOpen(false)}>
<Text style={[styles.action, { color: colors.mutedForeground }]}>Cancel</Text>
</Pressable>
<Text style={[styles.sheetTitle, { color: colors.foreground }]}>Date</Text>
<Pressable
onPress={() => {
applyDate(draft);
setOpen(false);
}}
>
<Text style={[styles.action, { color: colors.primary }]}>Done</Text>
</Pressable>
</View>
<DateTimePicker
value={draft}
mode="date"
display="spinner"
maximumDate={maximumDate}
minimumDate={minimumDate}
themeVariant={isDark ? "dark" : "light"}
onChange={handleChange}
/>
</Pressable>
</Pressable>
</Modal>
) : open ? (
<DateTimePicker
value={draft}
mode="date"
maximumDate={maximumDate}
minimumDate={minimumDate}
onChange={handleChange}
/>
) : null}
</>
);
}
const styles = StyleSheet.create({
trigger: {
minHeight: 36,
borderWidth: 1,
borderRadius: radii.md,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 8,
gap: 2,
},
pressed: {
opacity: 0.9,
},
value: {
flex: 1,
fontFamily: fonts.body,
fontSize: 12,
},
backdrop: {
flex: 1,
justifyContent: "flex-end",
backgroundColor: "rgba(0,0,0,0.45)",
},
sheet: {
borderTopLeftRadius: radii.lg,
borderTopRightRadius: radii.lg,
},
sheetHeader: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
},
sheetTitle: {
fontFamily: fonts.bodySemiBold,
fontSize: 15,
},
action: {
fontFamily: fonts.bodyMedium,
fontSize: 15,
},
});