Initial commit for Greenlens

This commit is contained in:
Timo Knuth
2026-03-16 21:31:46 +01:00
parent 307135671f
commit 05d4f6e78b
573 changed files with 54233 additions and 1891 deletions

View File

@@ -1,37 +1,79 @@
import React, { useEffect, useState } from 'react';
import { CheckCircle2 } from 'lucide-react';
import React, { useEffect, useRef } from 'react';
import { Animated, Text, StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useColors } from '../constants/Colors';
import { ColorPalette } from '../types';
interface ToastProps {
message: string;
isVisible: boolean;
onClose: () => void;
isDark?: boolean;
colorPalette?: ColorPalette;
}
export const Toast: React.FC<ToastProps> = ({ message, isVisible, onClose }) => {
const [show, setShow] = useState(false);
export const Toast: React.FC<ToastProps> = ({
message,
isVisible,
onClose,
isDark = false,
colorPalette = 'forest',
}) => {
const colors = useColors(isDark, colorPalette);
const opacity = useRef(new Animated.Value(0)).current;
const translateY = useRef(new Animated.Value(20)).current;
useEffect(() => {
if (isVisible) {
setShow(true);
const timer = setTimeout(() => {
setShow(false);
setTimeout(onClose, 300); // Wait for animation
}, 3000);
return () => clearTimeout(timer);
} else {
setShow(false);
}
}, [isVisible, onClose]);
Animated.parallel([
Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true }),
Animated.timing(translateY, { toValue: 0, duration: 300, useNativeDriver: true }),
]).start();
if (!isVisible && !show) return null;
const timer = setTimeout(() => {
Animated.parallel([
Animated.timing(opacity, { toValue: 0, duration: 300, useNativeDriver: true }),
Animated.timing(translateY, { toValue: 20, duration: 300, useNativeDriver: true }),
]).start(() => onClose());
}, 3000);
return () => clearTimeout(timer);
}
}, [isVisible]);
if (!isVisible) return null;
return (
<div className={`fixed bottom-20 left-0 right-0 z-[70] flex justify-center pointer-events-none transition-all duration-300 transform ${show ? 'translate-y-0 opacity-100' : 'translate-y-4 opacity-0'}`}>
<div className="bg-stone-900 dark:bg-white text-white dark:text-stone-900 px-4 py-3 rounded-full shadow-lg flex items-center space-x-2">
<CheckCircle2 size={18} className="text-green-500" />
<span className="text-sm font-medium">{message}</span>
</div>
</div>
<Animated.View style={[styles.container, { opacity, transform: [{ translateY }] }]}>
<View style={[styles.toast, { backgroundColor: colors.surface, shadowColor: colors.overlayStrong }]}>
<Ionicons name="checkmark-circle" size={18} color={colors.success} />
<Text style={[styles.text, { color: colors.text }]}>{message}</Text>
</View>
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
position: 'absolute',
bottom: 100,
left: 0,
right: 0,
alignItems: 'center',
zIndex: 70,
pointerEvents: 'none',
},
toast: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
paddingHorizontal: 16,
paddingVertical: 12,
borderRadius: 24,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 8,
},
text: { fontSize: 13, fontWeight: '500' },
});