managarten/memoro/apps/mobile/components/molecules/StableMemoTitle.tsx
Till-JS e7f5f942f3 chore: initial commit - consolidate 4 projects into monorepo
Projects included:
- maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing)
- manacore (Expo mobile + SvelteKit web + Astro landing)
- manadeck (NestJS backend + Expo mobile + SvelteKit web)
- memoro (Expo mobile + SvelteKit web + Astro landing)

This commit preserves the current state before monorepo restructuring.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 23:38:24 +01:00

107 lines
3 KiB
TypeScript

import React, { useEffect, useState, useRef } from 'react';
import { Text, TextProps, Animated } from 'react-native';
import { useMemoProcessing } from '~/features/memos/contexts/MemoProcessingContext';
interface StableMemoTitleProps extends TextProps {
memoId: string;
titleClasses?: string;
numberOfLines?: number;
ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
}
/**
* A specialized component that renders ONLY the memo title
* and updates it without causing parent re-renders
*/
const StableMemoTitle: React.FC<StableMemoTitleProps> = ({
memoId,
titleClasses,
numberOfLines = 2,
ellipsizeMode = 'tail',
style,
...rest
}) => {
// Get the display title from context
const { getDisplayTitle, registerForUpdates } = useMemoProcessing();
// Local state for the title (will only update this component)
const [title, setTitle] = useState(() => getDisplayTitle(memoId) || 'New Recording');
// Animation value for smooth transitions
const fadeAnim = useRef(new Animated.Value(1)).current;
// Direct polling for title updates to ensure we don't miss any
useEffect(() => {
// Poll for title updates every 500ms
const pollInterval = setInterval(() => {
const newTitle = getDisplayTitle(memoId);
if (newTitle && newTitle !== title) {
console.debug(`🔄 StableMemoTitle: Updating title from "${title}" to "${newTitle}"`);
// Fade out
Animated.timing(fadeAnim, {
toValue: 0,
duration: 100,
useNativeDriver: true,
}).start(() => {
// Update title
setTitle(newTitle);
// Fade in
Animated.timing(fadeAnim, {
toValue: 1,
duration: 100,
useNativeDriver: true,
}).start();
});
}
}, 500);
// Also register for immediate updates from the context
const unregister = registerForUpdates(() => {
const newTitle = getDisplayTitle(memoId);
if (newTitle && newTitle !== title) {
console.debug(`🔔 StableMemoTitle: Context update - title from "${title}" to "${newTitle}"`);
// Fade out
Animated.timing(fadeAnim, {
toValue: 0,
duration: 100,
useNativeDriver: true,
}).start(() => {
// Update title
setTitle(newTitle);
// Fade in
Animated.timing(fadeAnim, {
toValue: 1,
duration: 100,
useNativeDriver: true,
}).start();
});
}
});
// Cleanup
return () => {
clearInterval(pollInterval);
unregister();
};
}, [memoId, getDisplayTitle, registerForUpdates, title, fadeAnim]);
return (
<Animated.View style={{ opacity: fadeAnim }}>
<Text
className={titleClasses}
numberOfLines={numberOfLines}
ellipsizeMode={ellipsizeMode}
style={style}
{...rest}
>
{title}
</Text>
</Animated.View>
);
};
export default React.memo(StableMemoTitle);