feat(bb-ui2): Social and Swarm views with detail panels integrated

This commit is contained in:
zenchantlive 2026-02-16 00:26:31 -08:00
parent 976fd0c361
commit 8dd2d01686
11 changed files with 622 additions and 64 deletions

View file

@ -0,0 +1,116 @@
'use client';
import type { SocialCard as SocialCardData, AgentStatus } from '../../lib/social-cards';
import { StatusBadge } from '../shared/status-badge';
import { AgentAvatar } from '../shared/agent-avatar';
import { Plus } from 'lucide-react';
interface SocialDetailProps {
data: SocialCardData;
}
function formatRelativeTime(date: Date): string {
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffSecs = Math.floor(diffMs / 1000);
const diffMins = Math.floor(diffSecs / 60);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffSecs < 60) return 'just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
}
export function SocialDetail({ data }: SocialDetailProps) {
return (
<div className="space-y-4">
<div className="space-y-2">
<span className="text-teal-400 font-mono text-sm font-medium">
{data.id}
</span>
<h2 className="text-text-primary font-semibold text-base leading-tight">
{data.title}
</h2>
<StatusBadge status={data.status} size="sm" />
</div>
<div className="space-y-1">
<h3 className="text-text-muted text-xs font-semibold uppercase tracking-wider">
Thread
</h3>
<p className="text-text-muted text-sm italic">
Thread placeholder (bb-ui2.13)
</p>
</div>
{data.blocks.length > 0 && (
<div className="space-y-2">
<h3 className="text-amber-400 text-xs font-semibold uppercase tracking-wider">
Blocks
</h3>
<ul className="space-y-1">
{data.blocks.map((id) => (
<li key={id} className="text-text-secondary text-sm font-mono">
{id}
</li>
))}
</ul>
</div>
)}
{data.unlocks.length > 0 && (
<div className="space-y-2">
<h3 className="text-emerald-400 text-xs font-semibold uppercase tracking-wider">
Unlocks
</h3>
<ul className="space-y-1">
{data.unlocks.map((id) => (
<li key={id} className="text-text-secondary text-sm font-mono">
{id}
</li>
))}
</ul>
</div>
)}
<div className="space-y-2">
<h3 className="text-text-muted text-xs font-semibold uppercase tracking-wider">
Assigned
</h3>
<div className="flex items-center gap-2 flex-wrap">
{data.agents.length > 0 ? (
data.agents.map((agent) => (
<AgentAvatar
key={agent.name}
name={agent.name}
status={agent.status as AgentStatus}
size="sm"
/>
))
) : (
<span className="text-text-muted text-sm">No agents</span>
)}
<button
type="button"
className="p-1.5 rounded-md border border-dashed border-white/20 hover:border-white/40 hover:bg-white/5 transition-colors"
aria-label="Add agent"
>
<Plus size={14} className="text-text-muted" />
</button>
</div>
</div>
<div className="space-y-1 pt-2 border-t border-white/10">
<h3 className="text-text-muted text-xs font-semibold uppercase tracking-wider">
Last Activity
</h3>
<p className="text-text-secondary text-sm">
{formatRelativeTime(data.lastActivity)}
</p>
</div>
</div>
);
}

View file

@ -0,0 +1,65 @@
'use client';
import { useMemo, useState } from 'react';
import type { BeadIssue } from '../../lib/types';
import { buildSocialCards } from '../../lib/social-cards';
import { SocialCard } from './social-card';
import { Button } from '@/components/ui/button';
import { ChevronDown } from 'lucide-react';
const INITIAL_LIMIT = 16; // 4x4 grid
interface SocialPageProps {
issues: BeadIssue[];
selectedId?: string;
onSelect: (id: string) => void;
}
export function SocialPage({ issues, selectedId, onSelect }: SocialPageProps) {
const [expanded, setExpanded] = useState(false);
const cards = useMemo(() => buildSocialCards(issues), [issues]);
const visibleCards = expanded ? cards : cards.slice(0, INITIAL_LIMIT);
const hasMore = cards.length > INITIAL_LIMIT;
return (
<div className="p-4">
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: '1rem',
maxWidth: '1200px',
margin: '0 auto',
}}
>
{visibleCards.map((card) => (
<SocialCard
key={card.id}
data={card}
selected={selectedId === card.id}
onClick={() => onSelect(card.id)}
/>
))}
</div>
{hasMore && (
<div className="flex justify-center mt-4">
<Button
variant="outline"
onClick={() => setExpanded(true)}
className="gap-2 border-white/10 bg-white/5 hover:bg-white/10"
>
Show {cards.length - INITIAL_LIMIT} more
<ChevronDown className="h-4 w-4" />
</Button>
</div>
)}
{cards.length === 0 && (
<div className="text-center py-12" style={{ color: 'var(--color-text-muted)' }}>
No tasks found.
</div>
)}
</div>
);
}