mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 07:29:39 +02:00
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
182 lines
4.9 KiB
TypeScript
182 lines
4.9 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { View, Pressable, PressableProps, Platform } from 'react-native';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import * as Haptics from 'expo-haptics';
|
|
import { useTheme } from '~/features/theme/ThemeProvider';
|
|
import { getIconMapping } from '~/features/icons/iconMapping';
|
|
|
|
// Conditionally import SymbolView only on iOS
|
|
let SymbolView: any = null;
|
|
if (Platform.OS === 'ios') {
|
|
try {
|
|
SymbolView = require('expo-symbols').SymbolView;
|
|
} catch (e) {
|
|
// expo-symbols not available, will use Ionicons
|
|
}
|
|
}
|
|
|
|
// Unterstützte Icon-Typen
|
|
type IconType = 'ionicons';
|
|
|
|
// Props für die Icon-Komponente
|
|
type IconProps = {
|
|
// Icon-Name (aus Ionicons)
|
|
name: string;
|
|
// Größe des Icons
|
|
size?: number;
|
|
// Farbe des Icons (überschreibt die Theme-Farbe)
|
|
color?: string;
|
|
// Automatisch die Theme-Farbe verwenden
|
|
useThemeColor?: boolean;
|
|
// Zusätzliche Tailwind-Klassen
|
|
className?: string;
|
|
// Icon-Typ (für zukünftige Erweiterungen)
|
|
type?: IconType;
|
|
// Als Button anzeigen
|
|
asButton?: boolean;
|
|
// Hintergrundfarbe für Button-Variante
|
|
buttonBackgroundColor?: string;
|
|
// Button-Eigenschaften, wenn asButton=true
|
|
onPress?: PressableProps['onPress'];
|
|
// Zusätzliche Tailwind-Klassen für den Button-Container
|
|
buttonClassName?: string;
|
|
// Deaktiviert den Button
|
|
disabled?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Icon-Komponente
|
|
*
|
|
* Ein Wrapper für verschiedene Icon-Bibliotheken, der automatisch die Theme-Farben verwendet.
|
|
* Standardmäßig wird Ionicons verwendet.
|
|
*
|
|
* Beispiel:
|
|
* ```tsx
|
|
* <Icon name="heart" size={24} />
|
|
* <Icon name="settings-outline" size={20} color="red" />
|
|
* <Icon name="home" className="ml-2" useThemeColor />
|
|
* ```
|
|
*/
|
|
/**
|
|
* Icon-Komponente mit Memoization für bessere Performance
|
|
*
|
|
* Kann auch als Button verwendet werden, indem asButton=true gesetzt wird
|
|
* und onPress übergeben wird.
|
|
*/
|
|
export const Icon: React.FC<IconProps> = React.memo(
|
|
({
|
|
name,
|
|
size = 24,
|
|
color,
|
|
useThemeColor = false,
|
|
className = '',
|
|
type = 'ionicons',
|
|
asButton = false,
|
|
buttonBackgroundColor,
|
|
onPress,
|
|
buttonClassName = '',
|
|
disabled = false,
|
|
}: IconProps) => {
|
|
const { tw, themeVariant, isDark, themeVersion } = useTheme();
|
|
|
|
// Bestimme die Farbe basierend auf den Props und dem Theme
|
|
// Verwende useMemo, um die Farbe bei Theme-Änderungen neu zu berechnen
|
|
const iconColor = useMemo(() => {
|
|
if (useThemeColor) {
|
|
// Verwende die primäre Themefarbe, wenn keine Farbe angegeben ist
|
|
return isDark
|
|
? `var(--color-dark-${themeVariant}-primary)`
|
|
: `var(--color-${themeVariant}-primary)`;
|
|
} else if (!color && className) {
|
|
// Versuche, die Farbe aus den Tailwind-Klassen zu extrahieren
|
|
const colorClass = className.match(/text-([a-z0-9-]+)/)?.[0];
|
|
if (colorClass) {
|
|
const twClass = tw(colorClass);
|
|
// Extrahiere die Farbe aus der generierten Klasse
|
|
return twClass.includes('text-dark-')
|
|
? `var(--color-${twClass.replace('text-dark-', '')})`
|
|
: `var(--color-${twClass.replace('text-', '')})`;
|
|
}
|
|
}
|
|
return color;
|
|
}, [color, className, useThemeColor, themeVariant, isDark, tw, themeVersion]);
|
|
|
|
// Rendere das Icon basierend auf dem Typ
|
|
const renderIcon = () => {
|
|
// Check if we should use SF Symbols on iOS
|
|
if (Platform.OS === 'ios' && SymbolView) {
|
|
const mapping = getIconMapping(name);
|
|
if (mapping?.sfSymbol) {
|
|
// Calculate proper scale for SF Symbols based on size
|
|
let symbolScale = 'medium';
|
|
if (size <= 16) {
|
|
symbolScale = 'small';
|
|
} else if (size >= 32) {
|
|
symbolScale = 'large';
|
|
}
|
|
|
|
return (
|
|
<SymbolView
|
|
name={mapping.sfSymbol}
|
|
scale={symbolScale}
|
|
tintColor={iconColor}
|
|
style={{ width: size, height: size }}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
// Default to Ionicons
|
|
const mapping = getIconMapping(name);
|
|
const ioniconName = mapping?.ionicon || name;
|
|
|
|
return <Ionicons name={ioniconName as any} size={size} color={iconColor} />;
|
|
};
|
|
|
|
// Handler für Button-Press mit Haptic Feedback
|
|
const handlePress = async () => {
|
|
if (!disabled && onPress) {
|
|
try {
|
|
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
} catch (error) {
|
|
console.debug('Haptic feedback error:', error);
|
|
}
|
|
onPress();
|
|
}
|
|
};
|
|
|
|
// Wenn als Button angezeigt werden soll
|
|
if (asButton) {
|
|
return (
|
|
<Pressable
|
|
onPress={handlePress}
|
|
disabled={disabled}
|
|
style={({ pressed }) => [
|
|
{
|
|
padding: 8,
|
|
opacity: pressed ? 0.7 : 1,
|
|
},
|
|
]}
|
|
>
|
|
<View
|
|
style={{
|
|
width: 36,
|
|
height: 36,
|
|
borderRadius: 8,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: buttonBackgroundColor,
|
|
}}
|
|
>
|
|
{renderIcon()}
|
|
</View>
|
|
</Pressable>
|
|
);
|
|
}
|
|
|
|
// Standard-Icon ohne Button
|
|
return <View style={className ? tw(className) : undefined}>{renderIcon()}</View>;
|
|
}
|
|
);
|
|
|
|
export default Icon as React.FC<IconProps>;
|