Show every bot of a chatroom in its rail icon

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-28 04:54:55 +01:00
co-authored by Claude Opus 5.5
parent d8193d08b7
commit d3ce787087
2 changed files with 53 additions and 6 deletions
@@ -0,0 +1,48 @@
import Row from "@moduletrace/twui/layout/Row";
import Stack from "@moduletrace/twui/layout/Stack";
import type { ChatroomBot } from "@/src/types";
import ChatroomStone from "./chatroom-stone";
type Props = {
bots: ChatroomBot[];
};
/**
* The square the cluster fills (`w-7 h-7`), the size of a rail avatar.
*/
const ClusterSize = 28;
/**
* A chatroom's bots packed into one avatar-sized square: the asking bot
* on top, the two below it in a triangle (four make a square, the rest
* are left out). Stones stay small and apart: never overlapping, in a
* badge or outlined. One bot fills the square alone.
*/
export default function ChatroomStoneCluster({ bots }: Props) {
const shown = bots.slice(0, 4);
const stone = Math.floor((ClusterSize - 2) / 2);
if (shown.length <= 1) {
return shown[0] ? <ChatroomStone bot={shown[0]} size={ClusterSize} /> : null;
}
const top = shown.length == 3 ? shown.slice(0, 1) : shown.slice(0, 2);
const bottom = shown.length == 2 ? [] : shown.slice(top.length);
return (
<Stack className="shrink-0 gap-0.5! items-center justify-center w-7 h-7">
<Row className="gap-0.5! flex-nowrap justify-center">
{top.map((bot) => (
<ChatroomStone key={bot.id} bot={bot} size={stone} />
))}
</Row>
{bottom.length ? (
<Row className="gap-0.5! flex-nowrap justify-center">
{bottom.map((bot) => (
<ChatroomStone key={bot.id} bot={bot} size={stone} />
))}
</Row>
) : null}
</Stack>
);
}
@@ -2,7 +2,7 @@ import Link from "@moduletrace/twui/layout/Link";
import Span from "@moduletrace/twui/layout/Span";
import Stack from "@moduletrace/twui/layout/Stack";
import { twMerge } from "tailwind-merge";
import ChatroomStone from "@/src/components/general/chatroom-stone";
import ChatroomStoneCluster from "@/src/components/general/chatroom-stone-cluster";
import type { ChatroomSummary } from "@/src/types";
import chatroomStatusLine from "@/src/utils/chatroom-status-line";
@@ -13,12 +13,11 @@ type Props = {
};
/**
* A chatroom in the rail, the same 48px row as a bot: the asking bot's
* stone, the request and where it stands. Unread is said in words.
* A chatroom in the rail, the same 48px row as a bot: its bots' stones
* in one avatar-sized cluster, the request and where it stands. Unread
* is said in words.
*/
export default function RailChatroomRow({ chatroom, href, selected }: Props) {
const requester = chatroom.bots[0];
return (
<Link
href={href}
@@ -32,7 +31,7 @@ export default function RailChatroomRow({ chatroom, href, selected }: Props) {
)}
>
<Span className="mx-auto @rail:mx-0 inline-flex shrink-0">
{requester ? <ChatroomStone bot={requester} size={28} /> : null}
<ChatroomStoneCluster bots={chatroom.bots} />
</Span>
<Stack className="hidden @rail:flex grow min-w-0 gap-0!">
<Span className="text-sm font-bold leading-5 text-ink truncate w-full">