mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 23:21:24 +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>
83 lines
2.1 KiB
TypeScript
83 lines
2.1 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { View, Animated, Pressable } from 'react-native';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { Text } from '~/components/Text';
|
|
import { useTheme } from '~/contexts/ThemeContext';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
|
interface GenerationToastProps {
|
|
message: string;
|
|
duration?: number; // ms
|
|
onDismiss?: () => void;
|
|
}
|
|
|
|
export function GenerationToast({ message, duration = 3000, onDismiss }: GenerationToastProps) {
|
|
const { theme } = useTheme();
|
|
const insets = useSafeAreaInsets();
|
|
const [slideAnim] = useState(new Animated.Value(400)); // Start off-screen (right)
|
|
|
|
useEffect(() => {
|
|
// Slide in
|
|
Animated.spring(slideAnim, {
|
|
toValue: 0,
|
|
useNativeDriver: true,
|
|
tension: 50,
|
|
friction: 8,
|
|
}).start();
|
|
|
|
// Auto dismiss after duration
|
|
const timer = setTimeout(() => {
|
|
handleDismiss();
|
|
}, duration);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, []);
|
|
|
|
const handleDismiss = () => {
|
|
// Slide out
|
|
Animated.timing(slideAnim, {
|
|
toValue: 400,
|
|
duration: 200,
|
|
useNativeDriver: true,
|
|
}).start(() => {
|
|
onDismiss?.();
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Animated.View
|
|
style={{
|
|
position: 'absolute',
|
|
top: insets.top + 60, // Below header
|
|
right: 16,
|
|
zIndex: 9999,
|
|
transform: [{ translateX: slideAnim }],
|
|
}}
|
|
>
|
|
<Pressable onPress={handleDismiss}>
|
|
<View
|
|
style={{
|
|
backgroundColor: theme.colors.success,
|
|
borderRadius: 12,
|
|
padding: 12,
|
|
paddingHorizontal: 16,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
...theme.shadows.md,
|
|
minWidth: 200,
|
|
}}
|
|
>
|
|
<Ionicons name="checkmark-circle" size={20} color="#fff" />
|
|
<Text
|
|
variant="body"
|
|
weight="semibold"
|
|
style={{ color: '#fff', flex: 1 }}
|
|
>
|
|
{message}
|
|
</Text>
|
|
</View>
|
|
</Pressable>
|
|
</Animated.View>
|
|
);
|
|
}
|