mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 16:29:40 +02:00
- Add uload project with apps/web structure
- Reorganize from flat to monorepo structure
- Remove PocketBase binary and local data
- Update to pnpm and @uload/web namespace
- Add picture project to monorepo
- Remove embedded git repository
- Unify all package names to @{project}/{app} schema:
- @maerchenzauber/* (was @storyteller/*)
- @manacore/* (was manacore-*, manacore)
- @manadeck/* (was web, backend, manadeck)
- @memoro/* (was memoro-web, landing, memoro)
- @picture/* (already unified)
- @uload/web
- Add convenient dev scripts for all apps:
- pnpm dev:{project}:web
- pnpm dev:{project}:landing
- pnpm dev:{project}:mobile
- pnpm dev:{project}:backend
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
252 lines
No EOL
7.3 KiB
TypeScript
252 lines
No EOL
7.3 KiB
TypeScript
import { View, Pressable, Platform, Modal, ViewStyle, TextStyle } from 'react-native';
|
|
import { useState } from 'react';
|
|
import { useRouter, useSegments } from 'expo-router';
|
|
import { useAuth } from '~/contexts/AuthContext';
|
|
import { useTheme } from '~/contexts/ThemeContext';
|
|
import { Icon } from './Icon';
|
|
import { Text } from './Text';
|
|
|
|
// Only import ContextMenu on native platforms
|
|
let ContextMenu: any = null;
|
|
if (Platform.OS !== 'web') {
|
|
ContextMenu = require('react-native-context-menu-view').default;
|
|
}
|
|
|
|
type HeaderProps = {
|
|
title?: string;
|
|
showBackButton?: boolean;
|
|
rightContent?: React.ReactNode;
|
|
};
|
|
|
|
export function Header({ title, showBackButton = false, rightContent }: HeaderProps) {
|
|
const router = useRouter();
|
|
const segments = useSegments();
|
|
const { user, signOut } = useAuth();
|
|
const { theme } = useTheme();
|
|
const [webMenuVisible, setWebMenuVisible] = useState(false);
|
|
|
|
// Determine current route for menu highlighting
|
|
const currentRoute = segments.join('/');
|
|
|
|
// Define menu items based on auth status
|
|
const menuItems = user ? [
|
|
{
|
|
title: 'Galerie',
|
|
systemIcon: Platform.OS === 'ios' ? 'house' : undefined,
|
|
route: '/(tabs)',
|
|
destructive: false,
|
|
},
|
|
{
|
|
title: 'Generieren',
|
|
systemIcon: Platform.OS === 'ios' ? 'paintbrush' : undefined,
|
|
route: '/(tabs)/generate',
|
|
destructive: false,
|
|
},
|
|
{
|
|
title: 'Entdecken',
|
|
systemIcon: Platform.OS === 'ios' ? 'sparkles' : undefined,
|
|
route: '/(tabs)/explore',
|
|
destructive: false,
|
|
},
|
|
{
|
|
title: 'Tags verwalten',
|
|
systemIcon: Platform.OS === 'ios' ? 'tag' : undefined,
|
|
route: '/tags',
|
|
destructive: false,
|
|
},
|
|
{
|
|
title: 'Profil',
|
|
systemIcon: Platform.OS === 'ios' ? 'person' : undefined,
|
|
route: '/(tabs)/profile',
|
|
destructive: false,
|
|
},
|
|
] : [
|
|
{
|
|
title: 'Anmelden',
|
|
systemIcon: Platform.OS === 'ios' ? 'lock' : undefined,
|
|
route: '/(auth)/login',
|
|
destructive: false,
|
|
},
|
|
{
|
|
title: 'Registrieren',
|
|
systemIcon: Platform.OS === 'ios' ? 'person.badge.plus' : undefined,
|
|
route: '/(auth)/register',
|
|
destructive: false,
|
|
},
|
|
];
|
|
|
|
const handleMenuPress = (index: number) => {
|
|
const item = menuItems[index];
|
|
if (!item || item.disabled) return;
|
|
|
|
if (item.route === 'signout') {
|
|
signOut();
|
|
} else if (item.route) {
|
|
router.push(item.route as any);
|
|
}
|
|
|
|
// Close web menu if open
|
|
if (Platform.OS === 'web') {
|
|
setWebMenuVisible(false);
|
|
}
|
|
};
|
|
|
|
// Web menu component - using absolute positioning for better web compatibility
|
|
const WebMenu = () => {
|
|
if (!webMenuVisible) return null;
|
|
|
|
const backdropStyle: ViewStyle = {
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
zIndex: 999,
|
|
backgroundColor: 'transparent',
|
|
} as any;
|
|
|
|
const menuStyle: ViewStyle = {
|
|
position: 'absolute',
|
|
right: 0,
|
|
top: 50,
|
|
zIndex: 1000,
|
|
minWidth: 200,
|
|
backgroundColor: theme.colors.surface,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
borderRadius: 8,
|
|
...theme.shadows.md,
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* Backdrop */}
|
|
<Pressable
|
|
style={backdropStyle}
|
|
onPress={() => {
|
|
console.log('Backdrop clicked - closing menu');
|
|
setWebMenuVisible(false);
|
|
}}
|
|
/>
|
|
{/* Menu */}
|
|
<View style={menuStyle}>
|
|
{menuItems.filter(item => !item.disabled).map((item, index) => {
|
|
const actualIndex = menuItems.findIndex(mi => mi === item);
|
|
|
|
const menuItemStyle: ViewStyle = {
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
borderTopWidth: item.destructive ? 1 : 0,
|
|
borderTopColor: theme.colors.border,
|
|
};
|
|
|
|
return (
|
|
<Pressable
|
|
key={index}
|
|
onPress={() => {
|
|
console.log('Menu item clicked:', item.title, item.route);
|
|
handleMenuPress(actualIndex);
|
|
}}
|
|
style={({ pressed }) => [
|
|
menuItemStyle,
|
|
pressed && { opacity: theme.opacity.pressed },
|
|
]}
|
|
>
|
|
<Text
|
|
variant="body"
|
|
color={item.destructive ? 'error' : 'primary'}
|
|
>
|
|
{item.title}
|
|
</Text>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</View>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const headerContainerStyle: ViewStyle = {
|
|
backgroundColor: theme.colors.surface,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: theme.colors.border,
|
|
};
|
|
|
|
return (
|
|
<View style={headerContainerStyle}>
|
|
<View className="flex-row items-center justify-between px-4 py-3">
|
|
{/* Left side - Back button or title */}
|
|
<View className="flex-row items-center flex-1">
|
|
{showBackButton ? (
|
|
<Pressable
|
|
onPress={() => router.back()}
|
|
className="mr-3 p-2 -ml-2"
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
style={({ pressed }) => [
|
|
pressed && { opacity: theme.opacity.pressed },
|
|
]}
|
|
>
|
|
<Icon name="chevron-back" size={24} color={theme.colors.primary.default} />
|
|
</Pressable>
|
|
) : null}
|
|
|
|
{/* Title */}
|
|
{title && (
|
|
<Text variant="h4" color="primary">
|
|
{title}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
|
|
{/* Right side - Menu and custom content */}
|
|
<View className="flex-row items-center">
|
|
{rightContent && (
|
|
<View className="flex-row items-center mr-2">
|
|
{rightContent}
|
|
</View>
|
|
)}
|
|
{!showBackButton && (
|
|
Platform.OS === 'web' ? (
|
|
<>
|
|
<Pressable
|
|
className="p-2 -mr-2"
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
onPress={() => {
|
|
console.log('Menu button clicked');
|
|
setWebMenuVisible(true);
|
|
}}
|
|
style={({ pressed }) => [
|
|
pressed && { opacity: theme.opacity.pressed },
|
|
]}
|
|
>
|
|
<Icon name="menu" size={24} color={theme.colors.primary.default} />
|
|
</Pressable>
|
|
<WebMenu />
|
|
</>
|
|
) : ContextMenu ? (
|
|
<ContextMenu
|
|
actions={menuItems.map(item => ({
|
|
title: item.title,
|
|
systemIcon: item.systemIcon,
|
|
destructive: item.destructive,
|
|
disabled: item.disabled,
|
|
}))}
|
|
onPress={(e: any) => {
|
|
handleMenuPress(e.nativeEvent.index);
|
|
}}
|
|
dropdownMenuMode={true}
|
|
>
|
|
<Pressable
|
|
className="p-2 -mr-2"
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
>
|
|
<Icon name="menu" size={24} color={theme.colors.primary.default} />
|
|
</Pressable>
|
|
</ContextMenu>
|
|
) : null
|
|
)}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
} |