ux improvements
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { useMemo } from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { Plus } from "lucide-react-native";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import { REACTION_EMOJIS, type Reactions } from "@/api/types";
|
||||
import type { Human } from "@/api/types";
|
||||
import { resolveHumanDisplay } from "@/lib/humans";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const EMOJI_SET = new Set<string>(REACTION_EMOJIS);
|
||||
|
||||
interface ReactionStackProps {
|
||||
reactions: Reactions;
|
||||
currentHumanId: string;
|
||||
humans: Human[] | undefined;
|
||||
/** Toggle a reaction (emoji or text) — same contract as ReactionSheet's onToggle. */
|
||||
onToggle: (key: string) => void;
|
||||
/** Open the full reaction sheet for emoji + custom-text picking. */
|
||||
onOpenSheet: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Right-edge reaction stack — mobile counterpart of desktop's ReactionBar.
|
||||
* Sits vertically centered on the right side of the canvas so the user can
|
||||
* see existing reactions at a glance and tap to toggle their own. The "+"
|
||||
* affordance opens the ReactionSheet for the full picker (emoji or text).
|
||||
*/
|
||||
export function ReactionStack({
|
||||
reactions,
|
||||
currentHumanId,
|
||||
humans,
|
||||
onToggle,
|
||||
onOpenSheet,
|
||||
}: ReactionStackProps) {
|
||||
const activeEmojis = REACTION_EMOJIS.filter(
|
||||
(emoji) => reactions?.[emoji] && (reactions[emoji]?.length ?? 0) > 0,
|
||||
);
|
||||
const activeTextKeys = useMemo(
|
||||
() =>
|
||||
Object.keys(reactions ?? {}).filter(
|
||||
(k) => !EMOJI_SET.has(k) && (reactions?.[k]?.length ?? 0) > 0,
|
||||
),
|
||||
[reactions],
|
||||
);
|
||||
|
||||
const handleToggle = (key: string) => {
|
||||
void Haptics.selectionAsync();
|
||||
onToggle(key);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="items-end gap-1.5">
|
||||
{activeEmojis.map((emoji) => {
|
||||
const reactors = reactions?.[emoji] ?? [];
|
||||
const isMine = reactors.includes(currentHumanId);
|
||||
return (
|
||||
<Pressable
|
||||
key={emoji}
|
||||
onPress={() => handleToggle(emoji)}
|
||||
className={cn(
|
||||
"flex-row items-center gap-1 rounded-full px-2 py-1",
|
||||
isMine ? "bg-white/25" : "bg-black/45",
|
||||
)}
|
||||
style={
|
||||
isMine
|
||||
? { borderWidth: 1, borderColor: "rgba(255,255,255,0.4)" }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Text className="text-sm">{emoji}</Text>
|
||||
<Text className="text-white/85 text-xs font-medium">
|
||||
{reactors.length}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
|
||||
{activeTextKeys.map((text) => {
|
||||
const reactors = reactions?.[text] ?? [];
|
||||
const isMine = reactors.includes(currentHumanId);
|
||||
const firstReactor = resolveHumanDisplay(reactors[0], humans);
|
||||
return (
|
||||
<Pressable
|
||||
key={text}
|
||||
onPress={() => handleToggle(text)}
|
||||
className={cn(
|
||||
"flex-row items-center gap-1.5 rounded-full py-1 pl-1 pr-2.5",
|
||||
isMine ? "bg-white/25" : "bg-black/45",
|
||||
)}
|
||||
style={[
|
||||
{ maxWidth: 200 },
|
||||
isMine
|
||||
? { borderWidth: 1, borderColor: "rgba(255,255,255,0.4)" }
|
||||
: null,
|
||||
]}
|
||||
>
|
||||
<View className="bg-white/15 h-5 w-5 items-center justify-center rounded-full">
|
||||
<Text className="text-white text-[9px] font-semibold">
|
||||
{firstReactor.initials}
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
className="text-white/90 text-xs"
|
||||
numberOfLines={1}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
{reactors.length > 1 ? (
|
||||
<Text className="text-white/60 text-xs">{reactors.length}</Text>
|
||||
) : null}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
|
||||
<Pressable
|
||||
onPress={onOpenSheet}
|
||||
accessibilityLabel="Add reaction"
|
||||
className="h-8 w-8 items-center justify-center rounded-full bg-black/45"
|
||||
>
|
||||
<Plus color="rgba(255,255,255,0.85)" size={16} strokeWidth={2} />
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user