WellNuo/components/ui/BeneficiaryMenu.tsx
Sergei 2e72398818 Fix dropdown menu - make full row clickable
- BeneficiaryMenu: dropdownItem now has width: 100%
- Increased minWidth to 180 and added overflow: hidden
- Users can now tap anywhere on the menu row, not just the text

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-09 18:41:35 -08:00

187 lines
5.2 KiB
TypeScript

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Modal, Pressable } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { router } from 'expo-router';
import { AppColors, BorderRadius, FontSizes, Spacing, Shadows } from '@/constants/theme';
export type MenuItemId = 'edit' | 'access' | 'subscription' | 'equipment' | 'remove';
interface MenuItem {
id: MenuItemId;
icon: keyof typeof Ionicons.glyphMap;
label: string;
danger?: boolean;
}
const ALL_MENU_ITEMS: MenuItem[] = [
{ id: 'edit', icon: 'create-outline', label: 'Edit' },
{ id: 'access', icon: 'share-outline', label: 'Access' },
{ id: 'subscription', icon: 'diamond-outline', label: 'Subscription' },
{ id: 'equipment', icon: 'hardware-chip-outline', label: 'Equipment' },
{ id: 'remove', icon: 'trash-outline', label: 'Remove', danger: true },
];
interface BeneficiaryMenuProps {
beneficiaryId: string | number;
/** Which menu items to show. If not provided, shows all except current page */
visibleItems?: MenuItemId[];
/** Which menu item represents the current page (will be hidden) */
currentPage?: MenuItemId;
/** Custom handler for Edit action */
onEdit?: () => void;
/** Custom handler for Remove action */
onRemove?: () => void;
}
export function BeneficiaryMenu({
beneficiaryId,
visibleItems,
currentPage,
onEdit,
onRemove,
}: BeneficiaryMenuProps) {
const [isVisible, setIsVisible] = useState(false);
const handleMenuAction = (itemId: MenuItemId) => {
setIsVisible(false);
switch (itemId) {
case 'edit':
if (onEdit) {
onEdit();
} else {
// Navigate to main page with edit intent
router.push(`/(tabs)/beneficiaries/${beneficiaryId}`);
}
break;
case 'access':
router.push(`/(tabs)/beneficiaries/${beneficiaryId}/share`);
break;
case 'subscription':
router.push(`/(tabs)/beneficiaries/${beneficiaryId}/subscription`);
break;
case 'equipment':
router.push(`/(tabs)/beneficiaries/${beneficiaryId}/equipment`);
break;
case 'remove':
if (onRemove) {
onRemove();
} else {
// Navigate to main page
router.push(`/(tabs)/beneficiaries/${beneficiaryId}`);
}
break;
}
};
// Filter menu items - only hide current page
let menuItems = ALL_MENU_ITEMS;
if (visibleItems) {
menuItems = ALL_MENU_ITEMS.filter(item => visibleItems.includes(item.id));
} else if (currentPage) {
menuItems = ALL_MENU_ITEMS.filter(item => item.id !== currentPage);
}
return (
<View>
<TouchableOpacity
style={styles.menuButton}
onPress={() => setIsVisible(!isVisible)}
>
<Ionicons name="ellipsis-vertical" size={22} color={AppColors.textPrimary} />
</TouchableOpacity>
<Modal
visible={isVisible}
transparent={true}
animationType="fade"
onRequestClose={() => setIsVisible(false)}
>
{/* Full screen backdrop */}
<Pressable
style={styles.modalBackdrop}
onPress={() => setIsVisible(false)}
>
{/* Menu positioned at top right */}
<Pressable
style={styles.dropdownMenuContainer}
onPress={(e) => e.stopPropagation()}
>
<View style={styles.dropdownMenu}>
{menuItems.map((item) => (
<TouchableOpacity
key={item.id}
style={[
styles.dropdownItem,
item.danger && styles.dropdownItemDanger,
]}
onPress={() => handleMenuAction(item.id)}
>
<Ionicons
name={item.icon}
size={20}
color={item.danger ? AppColors.error : AppColors.textPrimary}
/>
<Text
style={[
styles.dropdownItemText,
item.danger && styles.dropdownItemTextDanger,
]}
>
{item.label}
</Text>
</TouchableOpacity>
))}
</View>
</Pressable>
</Pressable>
</Modal>
</View>
);
}
const styles = StyleSheet.create({
menuButton: {
width: 32,
height: 32,
alignItems: 'center',
justifyContent: 'center',
},
modalBackdrop: {
flex: 1,
backgroundColor: 'transparent',
},
dropdownMenuContainer: {
position: 'absolute',
top: 100, // Below status bar and header
right: Spacing.md,
},
dropdownMenu: {
backgroundColor: AppColors.surface,
borderRadius: BorderRadius.lg,
minWidth: 180,
overflow: 'hidden',
...Shadows.lg,
},
dropdownItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: Spacing.md,
paddingHorizontal: Spacing.md,
gap: Spacing.sm,
width: '100%',
},
dropdownItemText: {
fontSize: FontSizes.base,
color: AppColors.textPrimary,
},
dropdownItemDanger: {
borderTopWidth: 1,
borderTopColor: AppColors.border,
},
dropdownItemTextDanger: {
color: AppColors.error,
},
});