Files
birthday/apk/components/birthdate-item.tsx

204 lines
4.9 KiB
TypeScript

import { useState } from "react";
import {
Modal,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
interface BirthdayItemProps {
id: string;
name: string;
date: string;
onDelete: (id: string) => void;
}
export function BirthdayItem({ id, name, date, onDelete }: BirthdayItemProps) {
const [menuOpen, setMenuOpen] = useState(false);
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
const daysUntilNext = (birthdayDate: string) => {
const today = new Date();
const parsedDate = new Date(birthdayDate);
const month = parsedDate.getMonth();
const day = parsedDate.getDate();
const currentYear = today.getFullYear();
let target = new Date(currentYear, month, day);
if (target < today.setHours(0, 0, 0, 0)) {
target = new Date(currentYear + 1, month, day);
}
const msPerDay = 1000 * 60 * 60 * 24;
const diff = target.getTime() - new Date(today.setHours(0, 0, 0, 0)).getTime();
return Math.ceil(diff / msPerDay);
};
const computeNewAge = (birthdayDate: string) => {
const parsedDate = new Date(birthdayDate);
const today = new Date();
let age = today.getFullYear() - parsedDate.getFullYear();
const monthDiff = today.getMonth() - parsedDate.getMonth();
const dayDiff = today.getDate() - parsedDate.getDate();
if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
age--;
}
return age;
};
const days = daysUntilNext(date);
const handleOpenMenu = (event: {
nativeEvent: { pageX: number; pageY: number };
}) => {
setMenuPosition({
top: event.nativeEvent.pageY + 8,
left: event.nativeEvent.pageX - 136,
});
setMenuOpen(true);
};
const handleDelete = () => {
setMenuOpen(false);
onDelete(id);
};
return (
<>
<View style={styles.itemContainer}>
<View style={styles.contentRow}>
<View style={styles.textContainer}>
<Text style={styles.name}>{name}</Text>
{days === 0 ? (
<Text style={styles.age}>
{name} makes {computeNewAge(date)} years today!
</Text>
) : (
date && <Text style={styles.age}>Days remaining: {days}</Text>
)}
</View>
<Pressable
accessibilityLabel={`Open actions for ${name}`}
hitSlop={8}
onPress={handleOpenMenu}
style={styles.menuTrigger}
>
<Text style={styles.menuTriggerText}></Text>
</Pressable>
</View>
</View>
<Modal
animationType="fade"
onRequestClose={() => setMenuOpen(false)}
transparent
visible={menuOpen}
>
<View style={styles.menuBackdrop}>
<Pressable
onPress={() => setMenuOpen(false)}
style={styles.menuBackdropPressable}
/>
<View
style={[
styles.menu,
{
top: Math.max(menuPosition.top, 12),
left: Math.max(menuPosition.left, 12),
},
]}
>
<Pressable disabled style={[styles.menuItem, styles.menuItemDisabled]}>
<Text style={[styles.menuItemText, styles.menuItemTextDisabled]}>
Edit
</Text>
</Pressable>
<Pressable onPress={handleDelete} style={styles.menuItem}>
<Text style={[styles.menuItemText, styles.deleteText]}>Delete</Text>
</Pressable>
</View>
</View>
</Modal>
</>
);
}
const styles = StyleSheet.create({
itemContainer: {
padding: 15,
borderRadius: 8,
backgroundColor: "#f0f0f0",
elevation: 2,
marginBottom: 10,
},
contentRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: 12,
},
textContainer: {
flex: 1,
},
menuTrigger: {
alignItems: "center",
borderRadius: 999,
height: 36,
justifyContent: "center",
width: 36,
},
menuTriggerText: {
color: "#444",
fontSize: 22,
lineHeight: 22,
},
name: {
fontSize: 16,
fontWeight: "bold",
},
date: {
fontSize: 14,
},
age: {
fontSize: 12,
marginTop: 5,
},
menuBackdrop: {
...StyleSheet.absoluteFillObject,
},
menuBackdropPressable: {
...StyleSheet.absoluteFillObject,
},
menu: {
backgroundColor: "#fff",
borderRadius: 8,
elevation: 8,
minWidth: 140,
overflow: "hidden",
position: "absolute",
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15,
shadowRadius: 12,
},
menuItem: {
paddingHorizontal: 14,
paddingVertical: 12,
},
menuItemDisabled: {
opacity: 0.45,
},
menuItemText: {
color: "#222",
fontSize: 14,
fontWeight: "500",
},
menuItemTextDisabled: {
color: "#666",
},
deleteText: {
color: "#c53a3a",
},
});