chahinebrini 692abc07e1 fix(ui): Teilen-Button ohne Icon h:44, Dropdown-Width 170→210pt
ComposeCard:
- paper-plane-Icon entfernt, h:52→44, px-6→5, layout flex-row gap-2 →
  items-center justify-center, only Text. Saubere Pill ohne Icon-Gewicht.
- i18n-Key community.share war schon da — kein neuer Key.

HeaderDropdownMenu:
- minWidth 170→210, numberOfLines={1} auf Item-Labels entfernt (Breite
  reicht jetzt fuer alle Labels). numberOfLines bei SOS-Block-Labels
  bleibt (zwei separate Text-Nodes, sinnvoll).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:36:37 +02:00

245 lines
7.2 KiB
TypeScript

import { View, Text, Pressable, Modal } from 'react-native';
import { useRouter, type RelativePathString } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../../stores/auth';
// Controlled-Modal-Pattern. Trigger ist NICHT in dieser Komponente — der
// Avatar im AppHeader öffnet das Modal via `visible`-Prop (User-Anweisung
// 2026-05-07: kein separates 3-Punkte-Icon).
//
// Card-Style mit:
// - SOS prominent oben (nur Wort "SOS" rot, Tagline neutral; ernste Sache,
// nicht mit Gaming/Profile in eine Liste werfen)
// - Profile · Settings · Games · [Debug DEV] in der Mitte
// - Abmelden unten, neutral (nicht rot — Recovery-tonal, kein Alarm)
type ItemKey = 'profile' | 'settings' | 'games' | 'debug';
type Item = {
key: ItemKey;
label: string;
icon: React.ComponentProps<typeof Ionicons>['name'];
onSelect: () => void | Promise<void>;
};
type Props = {
visible: boolean;
onClose: () => void;
topOffset?: number;
};
export function HeaderDropdownMenu({ visible, onClose, topOffset = 80 }: Props) {
const router = useRouter();
const { t } = useTranslation();
const { signOut } = useAuthStore();
function nav(path: RelativePathString) {
onClose();
router.push(path);
}
async function handleLogout() {
onClose();
await signOut();
router.replace('/' as RelativePathString);
}
const items: Item[] = [
{
key: 'profile',
label: t('headerMenu.profile'),
icon: 'person-outline',
onSelect: () => nav('/profile' as RelativePathString),
},
{
key: 'settings',
label: t('headerMenu.settings'),
icon: 'settings-outline',
onSelect: () => nav('/settings' as RelativePathString),
},
{
key: 'games',
label: t('headerMenu.games'),
icon: 'game-controller-outline',
onSelect: () => nav('/games' as RelativePathString),
},
];
if (__DEV__) {
items.push({
key: 'debug',
label: t('headerMenu.debug'),
icon: 'bug-outline',
onSelect: () => nav('/debug' as RelativePathString),
});
}
return (
<Modal
visible={visible}
transparent
animationType="fade"
statusBarTranslucent
onRequestClose={onClose}
>
<Pressable
onPress={onClose}
style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.18)' }}
>
<View
onStartShouldSetResponder={() => true}
style={{
position: 'absolute',
top: topOffset,
right: 12,
backgroundColor: '#ffffff',
borderRadius: 18,
shadowColor: '#000',
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.18,
shadowRadius: 20,
elevation: 12,
minWidth: 210,
overflow: 'hidden',
}}
>
{/* SOS prominent — separat, ernst-tonal, nur "SOS" rot */}
<Pressable
onPress={() => {
onClose();
router.push('/urge' as RelativePathString);
}}
android_ripple={{ color: '#fee2e2' }}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 18,
paddingVertical: 16,
}}
>
<View
style={{
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#fee2e2',
alignItems: 'center',
justifyContent: 'center',
marginRight: 14,
}}
>
<Ionicons name="heart" size={18} color="#dc2626" />
</View>
<View style={{ flex: 1 }}>
<Text
style={{
fontSize: 15,
fontFamily: 'Nunito_700Bold',
color: '#dc2626',
}}
numberOfLines={1}
>
{t('appHeader.sosLabel')}
</Text>
<Text
style={{
fontSize: 12,
fontFamily: 'Nunito_400Regular',
color: '#a3a3a3',
marginTop: 1,
}}
numberOfLines={1}
>
{t('appHeader.sosTagline')}
</Text>
</View>
<Ionicons name="chevron-forward" size={16} color="#d4d4d8" />
</View>
</Pressable>
<View style={{ height: 1, backgroundColor: '#f0f0f0' }} />
{/* Profile · Settings · Games · [Debug DEV] */}
{items.map((item) => (
<Pressable
key={item.key}
onPress={() => {
onClose();
void item.onSelect();
}}
android_ripple={{ color: '#e5e7eb' }}
style={({ pressed }) => ({
backgroundColor: pressed ? '#f5f5f5' : 'transparent',
})}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 18,
paddingVertical: 14,
}}
>
<Ionicons
name={item.icon}
size={18}
color="#737373"
style={{ marginRight: 14 }}
/>
<Text
style={{
fontSize: 14,
fontFamily: 'Nunito_600SemiBold',
color: '#0a0a0a',
}}
>
{item.label}
</Text>
</View>
</Pressable>
))}
<View style={{ height: 1, backgroundColor: '#f0f0f0' }} />
{/* Abmelden — neutral, nicht rot */}
<Pressable
onPress={handleLogout}
android_ripple={{ color: '#e5e7eb' }}
style={({ pressed }) => ({
backgroundColor: pressed ? '#f5f5f5' : 'transparent',
})}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 18,
paddingVertical: 14,
}}
>
<Ionicons
name="log-out-outline"
size={18}
color="#737373"
style={{ marginRight: 14 }}
/>
<Text
style={{
fontSize: 14,
fontFamily: 'Nunito_600SemiBold',
color: '#0a0a0a',
}}
>
{t('headerMenu.logout')}
</Text>
</View>
</Pressable>
</View>
</Pressable>
</Modal>
);
}