Files
grok-glance/web/src/components/SessionsCard.tsx
T
2026-08-09 04:00:13 +00:00

80 lines
2.3 KiB
TypeScript

import type { ReactNode } from "react";
import { Card } from "@heroui/react";
import { StateChip } from "@/components/StatusChip";
import { relTime } from "@/lib/format";
import type { SessionView } from "@/protocol";
/**
* Only rendered when more than one session is live. With a single workspace the Now card
* already says everything, and an extra list is just noise on a small screen.
*/
export function SessionsCard({
sessions,
selectedId,
onSelect,
now,
}: {
sessions: SessionView[];
selectedId: string | null;
onSelect: (id: string | null) => void;
now: number;
}) {
return (
<Card>
<Card.Header>
<Card.Title className="text-base">Sessions</Card.Title>
<Card.Description className="text-xs">
Tap one to filter the activity list.
</Card.Description>
</Card.Header>
<Card.Content className="px-0">
<ul className="flex flex-col">
<li className="border-b border-separator">
<Row active={selectedId === null} onPress={() => onSelect(null)}>
<span className="text-sm">All sessions</span>
<span className="text-xs text-muted">{sessions.length}</span>
</Row>
</li>
{sessions.map((session) => (
<li key={session.id} className="border-b border-separator last:border-b-0">
<Row
active={selectedId === session.id}
onPress={() => onSelect(session.id === selectedId ? null : session.id)}
>
<span className="min-w-0 flex-1 truncate text-sm">{session.label}</span>
<span className="shrink-0 text-[11px] text-muted">
{relTime(session.lastActivity, now)}
</span>
<StateChip state={session.state} />
</Row>
</li>
))}
</ul>
</Card.Content>
</Card>
);
}
function Row({
active,
onPress,
children,
}: {
active: boolean;
onPress: () => void;
children: ReactNode;
}) {
return (
<button
type="button"
onClick={onPress}
aria-pressed={active}
className={`flex w-full items-center gap-2 px-4 py-3 text-left transition-colors ${
active ? "bg-surface-secondary" : "hover:bg-surface-hover"
}`}
>
{children}
</button>
);
}