Files
zopu-code/repos/web/components/work-os/work-os-header.tsx
-Puter cc47007fa9 Slice 1 polish: archive dormant code, merge work-os into primitives, add futures
- Archive dormant apps (daemon, desktop, native, tui) and superseded
  packages/server into repos/ for reference
- Archive 21 dead web components (chat page shell, work-os cluster, strays)
  into repos/web/; keep reused message-rendering primitives in components/chat
- Merge @code/work-os into @code/primitives; work.ts absorbed via ./work
  subpath and barrel export; update all four importers
- Add docs/futures.md tracking audio/video (blocked at Flue + provider),
  web layout cleanup, and message rendering quality
- Repoint dev:zopu script to @code/agents (the live Flue server)
- Note repos/ reference-code convention in AGENTS.md
2026-07-27 16:34:17 +05:30

113 lines
3.6 KiB
TypeScript

import { Button } from "@code/ui/components/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@code/ui/components/dropdown-menu";
import { ChevronDown, Circle, FolderGit2, Zap } from "lucide-react";
import UserMenu from "@/components/user-menu";
interface ProjectOption {
readonly id: string;
readonly name: string;
readonly host?: string;
}
interface WorkOsHeaderProps {
readonly projects: readonly ProjectOption[] | undefined;
readonly selectedProject: ProjectOption | null;
readonly onSelectProject: (id: string) => void;
readonly connected: boolean;
readonly onBack?: () => void;
readonly showBack: boolean;
}
const StatusDot = ({ connected }: { readonly connected: boolean }) => (
<span className="inline-flex items-center gap-1.5 text-[11px] text-muted-foreground">
<Circle
className={`size-2 ${connected ? "fill-emerald-500 text-emerald-500" : "fill-amber-500 text-amber-500"}`}
/>
{connected ? "Connected" : "Connecting"}
</span>
);
export const WorkOsHeader = ({
connected,
onBack,
onSelectProject,
projects,
selectedProject,
showBack,
}: WorkOsHeaderProps) => (
<header className="flex items-center justify-between gap-3 border-b border-border/40 bg-background/80 px-4 py-3 backdrop-blur-xl sm:px-6">
<div className="flex min-w-0 items-center gap-3">
{showBack && onBack ? (
<Button
className="shrink-0"
onClick={onBack}
size="sm"
type="button"
variant="ghost"
>
<ChevronDown className="size-4 rotate-90" />
</Button>
) : null}
<div className="grid size-9 shrink-0 place-items-center rounded-xl bg-foreground text-background">
<Zap className="size-4 fill-current" />
</div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<p className="text-sm font-semibold tracking-tight">Zopu</p>
<StatusDot connected={connected} />
</div>
{selectedProject ? (
<p className="truncate text-[11px] text-muted-foreground">
{selectedProject.name}
</p>
) : (
<p className="text-[11px] text-muted-foreground">No project</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
{projects && projects.length > 1 ? (
<DropdownMenu>
<DropdownMenuTrigger>
<Button size="sm" type="button" variant="ghost">
<FolderGit2 className="size-3.5" />
<span className="max-w-32 truncate">
{selectedProject?.name ?? "Select"}
</span>
<ChevronDown className="size-3.5 opacity-50" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Projects</DropdownMenuLabel>
<DropdownMenuSeparator />
{projects.map((project) => (
<DropdownMenuItem
key={project.id}
onClick={() => onSelectProject(project.id)}
>
<FolderGit2 className="size-3.5" />
<span className="truncate">{project.name}</span>
{project.host ? (
<span className="ml-auto text-[10px] text-muted-foreground">
{project.host}
</span>
) : null}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
) : null}
<UserMenu />
</div>
</header>
);