ux improvements
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { EllipsisVertical, Globe, Maximize2, Minimize2 } from "lucide-react-native";
|
||||
import type { Human, Particle } from "@/api/types";
|
||||
import { parseVisibleTo } from "@/lib/stream-visibility";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Avatar } from "@/components/Avatar";
|
||||
import { useStreamPresence } from "./stream-presence-context";
|
||||
|
||||
interface StreamTopActionsProps {
|
||||
networkId: string;
|
||||
streamParticle: Particle & { type: "stream" };
|
||||
humans: Human[];
|
||||
videoFit: "cover" | "contain";
|
||||
onToggleVideoFit: () => void;
|
||||
onOpenMembers: () => void;
|
||||
onOpenActions: () => void;
|
||||
/** True when current particle is a video — fit toggle hidden otherwise. */
|
||||
showFitToggle: boolean;
|
||||
}
|
||||
|
||||
const MAX_AVATARS = 3;
|
||||
|
||||
/**
|
||||
* Top-right cluster on StreamView: visibility avatars (with presence ring),
|
||||
* fit/fill toggle, and actions menu trigger. Mirrors desktop's stream-top-bar
|
||||
* but compact for the mobile chrome.
|
||||
*/
|
||||
export function StreamTopActions({
|
||||
networkId,
|
||||
streamParticle,
|
||||
humans,
|
||||
videoFit,
|
||||
onToggleVideoFit,
|
||||
onOpenMembers,
|
||||
onOpenActions,
|
||||
showFitToggle,
|
||||
}: StreamTopActionsProps) {
|
||||
const { onlineHumanIds } = useStreamPresence();
|
||||
const visibility = parseVisibleTo(streamParticle.visible_to, networkId);
|
||||
const memberIds =
|
||||
visibility.mode === "network"
|
||||
? humans.map((h) => h.id)
|
||||
: visibility.humanIds;
|
||||
const shown = memberIds.slice(0, MAX_AVATARS);
|
||||
const overflow = memberIds.length - shown.length;
|
||||
|
||||
return (
|
||||
<View className="flex-row items-center gap-1.5">
|
||||
<Pressable
|
||||
onPress={onOpenMembers}
|
||||
accessibilityLabel="Stream members"
|
||||
className="bg-white/10 active:bg-white/20 rounded-full px-2 py-1 flex-row items-center gap-1"
|
||||
>
|
||||
{visibility.mode === "network" && memberIds.length === 0 ? (
|
||||
<Globe color="rgba(255,255,255,0.85)" size={14} />
|
||||
) : (
|
||||
<View className="flex-row">
|
||||
{shown.map((id, idx) => (
|
||||
<View
|
||||
key={id}
|
||||
style={{ marginLeft: idx === 0 ? 0 : -8 }}
|
||||
>
|
||||
{/* The stack ring matches the chrome's translucent bg so it
|
||||
reads as a separator without painting hard black halos. */}
|
||||
<Avatar
|
||||
humanId={id}
|
||||
humans={humans}
|
||||
size="xs"
|
||||
online={onlineHumanIds.has(id)}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
{overflow > 0 ? (
|
||||
<Text className="text-white/70 text-[10px] font-medium ml-0.5">
|
||||
+{overflow}
|
||||
</Text>
|
||||
) : null}
|
||||
</Pressable>
|
||||
|
||||
{showFitToggle ? (
|
||||
<Pressable
|
||||
onPress={onToggleVideoFit}
|
||||
accessibilityLabel={
|
||||
videoFit === "cover" ? "Fit video to screen" : "Fill screen with video"
|
||||
}
|
||||
className={cn(
|
||||
"h-8 w-8 items-center justify-center rounded-full",
|
||||
"bg-white/10 active:bg-white/20",
|
||||
)}
|
||||
>
|
||||
{videoFit === "cover" ? (
|
||||
<Minimize2 color="white" size={15} strokeWidth={1.8} />
|
||||
) : (
|
||||
<Maximize2 color="white" size={15} strokeWidth={1.8} />
|
||||
)}
|
||||
</Pressable>
|
||||
) : null}
|
||||
|
||||
<Pressable
|
||||
onPress={onOpenActions}
|
||||
accessibilityLabel="More actions"
|
||||
className="h-8 w-8 items-center justify-center rounded-full bg-white/10 active:bg-white/20"
|
||||
>
|
||||
<EllipsisVertical color="white" size={16} strokeWidth={1.8} />
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user