feat(mobile): ReactionStrip quick-reactor + ReactionPills display

This commit is contained in:
byGalax
2026-05-14 00:19:15 +02:00
parent 153e40bdcb
commit 8d7ed592b8
2 changed files with 112 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import type { MessageReaction } from '@chat-app/shared/chat';
import { useMemo } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { colors } from '../theme/colors';
interface Props {
reactions: MessageReaction[];
myUserId: string | null;
onToggle: (emoji: string) => void;
}
// Renders the per-message reaction badges underneath a bubble. Pills
// the current user has reacted to get a tinted background so they know
// which ones to tap to un-react.
export function ReactionPills({ reactions, myUserId, onToggle }: Props) {
const grouped = useMemo(() => groupByEmoji(reactions, myUserId), [reactions, myUserId]);
if (grouped.length === 0) return null;
return (
<View style={styles.row}>
{grouped.map((g) => (
<Pressable
key={g.emoji}
onPress={() => onToggle(g.emoji)}
style={[styles.pill, g.mine && styles.pillMine]}
>
<Text style={styles.emoji}>{g.emoji}</Text>
<Text style={[styles.count, g.mine && styles.countMine]}>{g.count}</Text>
</Pressable>
))}
</View>
);
}
function groupByEmoji(
rows: MessageReaction[],
myUserId: string | null,
): Array<{ emoji: string; count: number; mine: boolean }> {
const m = new Map<string, { count: number; mine: boolean }>();
for (const r of rows) {
const prev = m.get(r.emoji) ?? { count: 0, mine: false };
m.set(r.emoji, {
count: prev.count + 1,
mine: prev.mine || (myUserId !== null && r.userId === myUserId),
});
}
return Array.from(m.entries()).map(([emoji, v]) => ({ emoji, ...v }));
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', flexWrap: 'wrap', gap: 4, marginTop: 4 },
pill: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 12,
backgroundColor: colors.surface,
borderColor: colors.border,
borderWidth: 1,
gap: 4,
},
pillMine: { backgroundColor: colors.accentMuted, borderColor: colors.accent },
emoji: { fontSize: 13 },
count: { color: colors.textMuted, fontSize: 12, fontWeight: '600' },
countMine: { color: colors.accent },
});