import { memo, useEffect, useState } from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withRepeat,
withSequence,
withTiming,
cancelAnimation,
} from 'react-native-reanimated';
import { Text } from '@/aesthetics/Text';
import { radius, spacing, useColors } from '@/aesthetics/styles';
import { Plus, X } from 'lucide-react-native';
import type { MemoLogger } from './types';
const GAP = spacing.lg;
const MIN_TILE = 60;
const MAX_TILE = 76;
const ICON_SIZE = 30;
const LABEL_SIZE = 11;
const WIGGLE_DEG = 2;
const WIGGLE_MS = 160;
interface LoggerTileGridProps {
memoLoggers: MemoLogger[];
isLoading: boolean;
isEditMode: boolean;
onEnterEditMode: () => void;
onExitEditMode: () => void;
onSelect: (id: string) => void;
onEdit?: (id: string) => void;
onDelete: (id: string) => void;
onAddNew: () => void;
getLabel: (logger: MemoLogger) => string;
}
export function LoggerTileGrid({
memoLoggers,
isEditMode,
onEnterEditMode,
onExitEditMode,
onSelect,
onEdit,
onDelete,
onAddNew,
getLabel,
}: LoggerTileGridProps) {
const [tileSize, setTileSize] = useState(0);
const handleLayout = (e: { nativeEvent: { layout: { width: number } } }) => {
const width = e.nativeEvent.layout.width;
const cols = Math.max(1, Math.floor((width + GAP) / (MIN_TILE + GAP)));
const size = (width - GAP * (cols - 1)) / cols;
setTileSize(Math.min(size, MAX_TILE));
};
const handleTilePress = (id: string) => {
if (isEditMode && onEdit) {
onEdit(id);
} else {
onSelect(id);
}
};
return (
{tileSize > 0 && (
<>
{memoLoggers.map((logger, i) => (
handleTilePress(logger.id)}
onLongPress={onEnterEditMode}
onDelete={() => onDelete(logger.id)}
/>
))}
>
)}
);
}
interface LoggerTileProps {
logger: MemoLogger;
label: string;
size: number;
isEditMode: boolean;
index: number;
onPress: () => void;
onLongPress: () => void;
onDelete: () => void;
}
const LoggerTile = memo(function LoggerTile({
logger,
label,
size,
isEditMode,
index,
onPress,
onLongPress,
onDelete,
}: LoggerTileProps) {
const c = useColors();
const rotation = useSharedValue(0);
useEffect(() => {
if (isEditMode) {
const offset = index % 2 === 0 ? WIGGLE_DEG : -WIGGLE_DEG;
rotation.value = withRepeat(
withSequence(
withTiming(offset, { duration: WIGGLE_MS }),
withTiming(-offset, { duration: WIGGLE_MS })
),
-1,
true
);
} else {
cancelAnimation(rotation);
rotation.value = withTiming(0, { duration: WIGGLE_MS });
}
}, [isEditMode, index, rotation]);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${rotation.value}deg` }],
}));
return (
{isEditMode && }
{label}
);
});
function TileIcon({ icon, label }: { icon?: string; label: string }) {
const c = useColors();
if (icon) {
return {icon};
}
return {label.charAt(0)};
}
function DeleteButton({ onPress }: { onPress: () => void }) {
const c = useColors();
return (
);
}
interface EditGridButtonProps {
size: number;
isEditMode: boolean;
onAdd: () => void;
onDone: () => void;
}
function EditGridButton({ size, isEditMode, onAdd, onDone }: EditGridButtonProps) {
const c = useColors();
return (
{isEditMode ? (
Done
) : (
)}
);
}
const styles = StyleSheet.create({
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: GAP,
},
tileWrapper: {
alignItems: 'center',
gap: spacing.xs,
},
tile: {
aspectRatio: 1,
width: '100%',
borderRadius: radius.md,
alignItems: 'center',
justifyContent: 'center',
},
editButton: {
borderWidth: 1.5,
borderStyle: 'dashed',
},
emoji: {
fontSize: ICON_SIZE,
},
iconFallback: {
fontSize: ICON_SIZE,
fontWeight: '600',
},
label: {
fontSize: LABEL_SIZE,
textAlign: 'center',
},
doneLabel: {
fontSize: 13,
fontWeight: '600',
},
deleteBtn: {
position: 'absolute',
top: -6,
left: -6,
width: 18,
height: 18,
borderRadius: 9,
alignItems: 'center',
justifyContent: 'center',
},
});