Added main functionality
This commit is contained in:
@@ -1,18 +1,126 @@
|
||||
// BirthdayItem.tsx
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Modal,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
interface BirthdayItemProps {
|
||||
id: string;
|
||||
name: string;
|
||||
date: string;
|
||||
age?: number;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
export function BirthdayItem({ name, date, age }: BirthdayItemProps) {
|
||||
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}>
|
||||
<Text style={styles.name}>{name}</Text>
|
||||
{age && <Text style={styles.age}>Age: {age}</Text>}
|
||||
</View>
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,6 +132,27 @@ const styles = StyleSheet.create({
|
||||
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",
|
||||
@@ -35,4 +164,40 @@ const styles = StyleSheet.create({
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user