| import { useState } from "react"; |
| import { |
| BookOpen, |
| Bot, |
| Check, |
| ChevronDown, |
| CircleDot, |
| Command as CommandIcon, |
| DollarSign, |
| Hexagon, |
| History, |
| Inbox, |
| LayoutDashboard, |
| ListTodo, |
| Mail, |
| Plus, |
| Search, |
| Settings, |
| Target, |
| Trash2, |
| Upload, |
| User, |
| Zap, |
| } from "lucide-react"; |
| import { Button } from "@/components/ui/button"; |
| import { Badge } from "@/components/ui/badge"; |
| import { Input } from "@/components/ui/input"; |
| import { Textarea } from "@/components/ui/textarea"; |
| import { Checkbox } from "@/components/ui/checkbox"; |
| import { Label } from "@/components/ui/label"; |
| import { Separator } from "@/components/ui/separator"; |
| import { Skeleton } from "@/components/ui/skeleton"; |
| import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; |
| import { |
| Card, |
| CardHeader, |
| CardTitle, |
| CardDescription, |
| CardContent, |
| CardFooter, |
| } from "@/components/ui/card"; |
| import { |
| Dialog, |
| DialogTrigger, |
| DialogContent, |
| DialogHeader, |
| DialogTitle, |
| DialogDescription, |
| DialogFooter, |
| } from "@/components/ui/dialog"; |
| import { |
| Tooltip, |
| TooltipTrigger, |
| TooltipContent, |
| } from "@/components/ui/tooltip"; |
| import { |
| Select, |
| SelectTrigger, |
| SelectValue, |
| SelectContent, |
| SelectItem, |
| } from "@/components/ui/select"; |
| import { |
| DropdownMenu, |
| DropdownMenuTrigger, |
| DropdownMenuContent, |
| DropdownMenuItem, |
| DropdownMenuSeparator, |
| DropdownMenuCheckboxItem, |
| DropdownMenuShortcut, |
| } from "@/components/ui/dropdown-menu"; |
| import { |
| Popover, |
| PopoverTrigger, |
| PopoverContent, |
| } from "@/components/ui/popover"; |
| import { |
| Sheet, |
| SheetTrigger, |
| SheetContent, |
| SheetHeader, |
| SheetTitle, |
| SheetDescription, |
| SheetFooter, |
| } from "@/components/ui/sheet"; |
| import { |
| Collapsible, |
| CollapsibleTrigger, |
| CollapsibleContent, |
| } from "@/components/ui/collapsible"; |
| import { ScrollArea } from "@/components/ui/scroll-area"; |
| import { |
| Command, |
| CommandInput, |
| CommandList, |
| CommandGroup, |
| CommandItem, |
| CommandEmpty, |
| CommandSeparator, |
| } from "@/components/ui/command"; |
| import { |
| Breadcrumb, |
| BreadcrumbItem, |
| BreadcrumbLink, |
| BreadcrumbList, |
| BreadcrumbPage, |
| BreadcrumbSeparator, |
| } from "@/components/ui/breadcrumb"; |
| import { |
| Avatar, |
| AvatarFallback, |
| AvatarGroup, |
| AvatarGroupCount, |
| } from "@/components/ui/avatar"; |
| import { StatusBadge } from "@/components/StatusBadge"; |
| import { StatusIcon } from "@/components/StatusIcon"; |
| import { PriorityIcon } from "@/components/PriorityIcon"; |
| import { agentStatusDot, agentStatusDotDefault } from "@/lib/status-colors"; |
| import { EntityRow } from "@/components/EntityRow"; |
| import { EmptyState } from "@/components/EmptyState"; |
| import { MetricCard } from "@/components/MetricCard"; |
| import { FilterBar, type FilterValue } from "@/components/FilterBar"; |
| import { InlineEditor } from "@/components/InlineEditor"; |
| import { PageSkeleton } from "@/components/PageSkeleton"; |
| import { Identity } from "@/components/Identity"; |
|
|
| |
| |
| |
|
|
| function Section({ title, children }: { title: string; children: React.ReactNode }) { |
| return ( |
| <section className="space-y-4"> |
| <h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide"> |
| {title} |
| </h3> |
| <Separator /> |
| {children} |
| </section> |
| ); |
| } |
|
|
| function SubSection({ title, children }: { title: string; children: React.ReactNode }) { |
| return ( |
| <div className="space-y-3"> |
| <h4 className="text-sm font-medium">{title}</h4> |
| {children} |
| </div> |
| ); |
| } |
|
|
| |
| |
| |
|
|
| function Swatch({ name, cssVar }: { name: string; cssVar: string }) { |
| return ( |
| <div className="flex items-center gap-3"> |
| <div |
| className="h-8 w-8 rounded-md border border-border shrink-0" |
| style={{ backgroundColor: `var(${cssVar})` }} |
| /> |
| <div> |
| <p className="text-xs font-mono">{cssVar}</p> |
| <p className="text-xs text-muted-foreground">{name}</p> |
| </div> |
| </div> |
| ); |
| } |
|
|
| |
| |
| |
|
|
| export function DesignGuide() { |
| const [status, setStatus] = useState("todo"); |
| const [priority, setPriority] = useState("medium"); |
| const [selectValue, setSelectValue] = useState("in_progress"); |
| const [menuChecked, setMenuChecked] = useState(true); |
| const [collapsibleOpen, setCollapsibleOpen] = useState(false); |
| const [inlineText, setInlineText] = useState("Click to edit this text"); |
| const [inlineTitle, setInlineTitle] = useState("Editable Title"); |
| const [inlineDesc, setInlineDesc] = useState( |
| "This is an editable description. Click to edit it — the textarea auto-sizes to fit the content without layout shift." |
| ); |
| const [filters, setFilters] = useState<FilterValue[]>([ |
| { key: "status", label: "Status", value: "Active" }, |
| { key: "priority", label: "Priority", value: "High" }, |
| ]); |
|
|
| return ( |
| <div className="space-y-10 max-w-4xl"> |
| {/* Page header */} |
| <div> |
| <h2 className="text-xl font-bold">Design Guide</h2> |
| <p className="text-sm text-muted-foreground mt-1"> |
| Every component, style, and pattern used across Paperclip. |
| </p> |
| </div> |
| |
| {/* ============================================================ */} |
| {/* COVERAGE */} |
| {/* ============================================================ */} |
| <Section title="Component Coverage"> |
| <p className="text-sm text-muted-foreground"> |
| This page should be updated when new UI primitives or app-level patterns ship. |
| </p> |
| <div className="grid gap-6 md:grid-cols-2"> |
| <SubSection title="UI primitives"> |
| <div className="flex flex-wrap gap-2"> |
| {[ |
| "avatar", "badge", "breadcrumb", "button", "card", "checkbox", "collapsible", |
| "command", "dialog", "dropdown-menu", "input", "label", "popover", "scroll-area", |
| "select", "separator", "sheet", "skeleton", "tabs", "textarea", "tooltip", |
| ].map((name) => ( |
| <Badge key={name} variant="outline" className="font-mono text-[10px]"> |
| {name} |
| </Badge> |
| ))} |
| </div> |
| </SubSection> |
| <SubSection title="App components"> |
| <div className="flex flex-wrap gap-2"> |
| {[ |
| "StatusBadge", "StatusIcon", "PriorityIcon", "EntityRow", "EmptyState", "MetricCard", |
| "FilterBar", "InlineEditor", "PageSkeleton", "Identity", "CommentThread", "MarkdownEditor", |
| "PropertiesPanel", "Sidebar", "CommandPalette", |
| ].map((name) => ( |
| <Badge key={name} variant="ghost" className="font-mono text-[10px]"> |
| {name} |
| </Badge> |
| ))} |
| </div> |
| </SubSection> |
| </div> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* COLORS */} |
| {/* ============================================================ */} |
| <Section title="Colors"> |
| <SubSection title="Core"> |
| <div className="grid grid-cols-2 md:grid-cols-3 gap-4"> |
| <Swatch name="Background" cssVar="--background" /> |
| <Swatch name="Foreground" cssVar="--foreground" /> |
| <Swatch name="Card" cssVar="--card" /> |
| <Swatch name="Primary" cssVar="--primary" /> |
| <Swatch name="Primary foreground" cssVar="--primary-foreground" /> |
| <Swatch name="Secondary" cssVar="--secondary" /> |
| <Swatch name="Muted" cssVar="--muted" /> |
| <Swatch name="Muted foreground" cssVar="--muted-foreground" /> |
| <Swatch name="Accent" cssVar="--accent" /> |
| <Swatch name="Destructive" cssVar="--destructive" /> |
| <Swatch name="Border" cssVar="--border" /> |
| <Swatch name="Ring" cssVar="--ring" /> |
| </div> |
| </SubSection> |
| |
| <SubSection title="Sidebar"> |
| <div className="grid grid-cols-2 md:grid-cols-3 gap-4"> |
| <Swatch name="Sidebar" cssVar="--sidebar" /> |
| <Swatch name="Sidebar border" cssVar="--sidebar-border" /> |
| </div> |
| </SubSection> |
| |
| <SubSection title="Chart"> |
| <div className="grid grid-cols-2 md:grid-cols-3 gap-4"> |
| <Swatch name="Chart 1" cssVar="--chart-1" /> |
| <Swatch name="Chart 2" cssVar="--chart-2" /> |
| <Swatch name="Chart 3" cssVar="--chart-3" /> |
| <Swatch name="Chart 4" cssVar="--chart-4" /> |
| <Swatch name="Chart 5" cssVar="--chart-5" /> |
| </div> |
| </SubSection> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* TYPOGRAPHY */} |
| {/* ============================================================ */} |
| <Section title="Typography"> |
| <div className="space-y-3"> |
| <h2 className="text-xl font-bold">Page Title — text-xl font-bold</h2> |
| <h2 className="text-lg font-semibold">Section Title — text-lg font-semibold</h2> |
| <h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide"> |
| Section Heading — text-sm font-semibold uppercase tracking-wide |
| </h3> |
| <p className="text-sm font-medium">Card Title — text-sm font-medium</p> |
| <p className="text-sm font-semibold">Card Title Alt — text-sm font-semibold</p> |
| <p className="text-sm">Body text — text-sm</p> |
| <p className="text-sm text-muted-foreground"> |
| Muted description — text-sm text-muted-foreground |
| </p> |
| <p className="text-xs text-muted-foreground"> |
| Tiny label — text-xs text-muted-foreground |
| </p> |
| <p className="text-sm font-mono text-muted-foreground"> |
| Mono identifier — text-sm font-mono text-muted-foreground |
| </p> |
| <p className="text-2xl font-bold">Large stat — text-2xl font-bold</p> |
| <p className="font-mono text-xs">Log/code text — font-mono text-xs</p> |
| </div> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* SPACING & RADIUS */} |
| {/* ============================================================ */} |
| <Section title="Radius"> |
| <div className="flex items-end gap-4 flex-wrap"> |
| {[ |
| ["sm", "var(--radius-sm)"], |
| ["md", "var(--radius-md)"], |
| ["lg", "var(--radius-lg)"], |
| ["xl", "var(--radius-xl)"], |
| ["full", "9999px"], |
| ].map(([label, radius]) => ( |
| <div key={label} className="flex flex-col items-center gap-1"> |
| <div |
| className="h-12 w-12 bg-primary" |
| style={{ borderRadius: radius }} |
| /> |
| <span className="text-xs text-muted-foreground">{label}</span> |
| </div> |
| ))} |
| </div> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* BUTTONS */} |
| {/* ============================================================ */} |
| <Section title="Buttons"> |
| <SubSection title="Variants"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| <Button variant="default">Default</Button> |
| <Button variant="secondary">Secondary</Button> |
| <Button variant="outline">Outline</Button> |
| <Button variant="ghost">Ghost</Button> |
| <Button variant="destructive">Destructive</Button> |
| <Button variant="link">Link</Button> |
| </div> |
| </SubSection> |
| |
| <SubSection title="Sizes"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| <Button size="xs">Extra Small</Button> |
| <Button size="sm">Small</Button> |
| <Button size="default">Default</Button> |
| <Button size="lg">Large</Button> |
| </div> |
| </SubSection> |
| |
| <SubSection title="Icon buttons"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| <Button variant="ghost" size="icon-xs"><Search /></Button> |
| <Button variant="ghost" size="icon-sm"><Search /></Button> |
| <Button variant="outline" size="icon"><Search /></Button> |
| <Button variant="outline" size="icon-lg"><Search /></Button> |
| </div> |
| </SubSection> |
| |
| <SubSection title="With icons"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| <Button><Plus /> New Issue</Button> |
| <Button variant="outline"><Upload /> Upload</Button> |
| <Button variant="destructive"><Trash2 /> Delete</Button> |
| <Button size="sm"><Plus /> Add</Button> |
| </div> |
| </SubSection> |
| |
| <SubSection title="States"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| <Button disabled>Disabled</Button> |
| <Button variant="outline" disabled>Disabled Outline</Button> |
| </div> |
| </SubSection> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* BADGES */} |
| {/* ============================================================ */} |
| <Section title="Badges"> |
| <SubSection title="Variants"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| <Badge variant="default">Default</Badge> |
| <Badge variant="secondary">Secondary</Badge> |
| <Badge variant="outline">Outline</Badge> |
| <Badge variant="destructive">Destructive</Badge> |
| <Badge variant="ghost">Ghost</Badge> |
| </div> |
| </SubSection> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* STATUS BADGES & ICONS */} |
| {/* ============================================================ */} |
| <Section title="Status System"> |
| <SubSection title="StatusBadge (all statuses)"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| {[ |
| "active", "running", "paused", "idle", "archived", "planned", |
| "achieved", "completed", "failed", "timed_out", "succeeded", "error", |
| "pending_approval", "backlog", "todo", "in_progress", "in_review", "blocked", |
| "done", "terminated", "cancelled", "pending", "revision_requested", |
| "approved", "rejected", |
| ].map((s) => ( |
| <StatusBadge key={s} status={s} /> |
| ))} |
| </div> |
| </SubSection> |
| |
| <SubSection title="StatusIcon (interactive)"> |
| <div className="flex items-center gap-3 flex-wrap"> |
| {["backlog", "todo", "in_progress", "in_review", "done", "cancelled", "blocked"].map( |
| (s) => ( |
| <div key={s} className="flex items-center gap-1.5"> |
| <StatusIcon status={s} /> |
| <span className="text-xs text-muted-foreground">{s}</span> |
| </div> |
| ) |
| )} |
| </div> |
| <div className="flex items-center gap-2 mt-2"> |
| <StatusIcon status={status} onChange={setStatus} /> |
| <span className="text-sm">Click the icon to change status (current: {status})</span> |
| </div> |
| </SubSection> |
| |
| <SubSection title="PriorityIcon (interactive)"> |
| <div className="flex items-center gap-3 flex-wrap"> |
| {["critical", "high", "medium", "low"].map((p) => ( |
| <div key={p} className="flex items-center gap-1.5"> |
| <PriorityIcon priority={p} /> |
| <span className="text-xs text-muted-foreground">{p}</span> |
| </div> |
| ))} |
| </div> |
| <div className="flex items-center gap-2 mt-2"> |
| <PriorityIcon priority={priority} onChange={setPriority} /> |
| <span className="text-sm">Click the icon to change (current: {priority})</span> |
| </div> |
| </SubSection> |
| |
| <SubSection title="Agent status dots"> |
| <div className="flex items-center gap-4 flex-wrap"> |
| {(["running", "active", "paused", "error", "archived"] as const).map((label) => ( |
| <div key={label} className="flex items-center gap-2"> |
| <span className="relative flex h-2.5 w-2.5"> |
| <span className={`inline-flex h-full w-full rounded-full ${agentStatusDot[label] ?? agentStatusDotDefault}`} /> |
| </span> |
| <span className="text-xs text-muted-foreground">{label}</span> |
| </div> |
| ))} |
| </div> |
| </SubSection> |
| |
| <SubSection title="Run invocation badges"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| {[ |
| ["timer", "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"], |
| ["assignment", "bg-violet-100 text-violet-700 dark:bg-violet-900/50 dark:text-violet-300"], |
| ["on_demand", "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300"], |
| ["automation", "bg-muted text-muted-foreground"], |
| ].map(([label, cls]) => ( |
| <span key={label} className={`rounded-full px-1.5 py-0.5 text-[10px] font-medium ${cls}`}> |
| {label} |
| </span> |
| ))} |
| </div> |
| </SubSection> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* FORM ELEMENTS */} |
| {/* ============================================================ */} |
| <Section title="Form Elements"> |
| <div className="grid gap-6 md:grid-cols-2"> |
| <SubSection title="Input"> |
| <Input placeholder="Default input" /> |
| <Input placeholder="Disabled input" disabled className="mt-2" /> |
| </SubSection> |
| |
| <SubSection title="Textarea"> |
| <Textarea placeholder="Write something..." /> |
| </SubSection> |
| |
| <SubSection title="Checkbox & Label"> |
| <div className="space-y-3"> |
| <div className="flex items-center gap-2"> |
| <Checkbox id="check1" defaultChecked /> |
| <Label htmlFor="check1">Checked item</Label> |
| </div> |
| <div className="flex items-center gap-2"> |
| <Checkbox id="check2" /> |
| <Label htmlFor="check2">Unchecked item</Label> |
| </div> |
| <div className="flex items-center gap-2"> |
| <Checkbox id="check3" disabled /> |
| <Label htmlFor="check3">Disabled item</Label> |
| </div> |
| </div> |
| </SubSection> |
| |
| <SubSection title="Inline Editor"> |
| <div className="space-y-4"> |
| <div> |
| <p className="text-xs text-muted-foreground mb-1">Title (single-line)</p> |
| <InlineEditor |
| value={inlineTitle} |
| onSave={setInlineTitle} |
| as="h2" |
| className="text-xl font-bold" |
| /> |
| </div> |
| <div> |
| <p className="text-xs text-muted-foreground mb-1">Body text (single-line)</p> |
| <InlineEditor |
| value={inlineText} |
| onSave={setInlineText} |
| as="p" |
| className="text-sm" |
| /> |
| </div> |
| <div> |
| <p className="text-xs text-muted-foreground mb-1">Description (multiline, auto-sizing)</p> |
| <InlineEditor |
| value={inlineDesc} |
| onSave={setInlineDesc} |
| as="p" |
| className="text-sm text-muted-foreground" |
| placeholder="Add a description..." |
| multiline |
| /> |
| </div> |
| </div> |
| </SubSection> |
| </div> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* SELECT */} |
| {/* ============================================================ */} |
| <Section title="Select"> |
| <div className="grid gap-6 md:grid-cols-2"> |
| <SubSection title="Default size"> |
| <Select value={selectValue} onValueChange={setSelectValue}> |
| <SelectTrigger className="w-full"> |
| <SelectValue placeholder="Select status" /> |
| </SelectTrigger> |
| <SelectContent> |
| <SelectItem value="backlog">Backlog</SelectItem> |
| <SelectItem value="todo">Todo</SelectItem> |
| <SelectItem value="in_progress">In Progress</SelectItem> |
| <SelectItem value="in_review">In Review</SelectItem> |
| <SelectItem value="done">Done</SelectItem> |
| </SelectContent> |
| </Select> |
| <p className="text-xs text-muted-foreground">Current value: {selectValue}</p> |
| </SubSection> |
| <SubSection title="Small trigger"> |
| <Select defaultValue="high"> |
| <SelectTrigger size="sm" className="w-full"> |
| <SelectValue /> |
| </SelectTrigger> |
| <SelectContent> |
| <SelectItem value="critical">Critical</SelectItem> |
| <SelectItem value="high">High</SelectItem> |
| <SelectItem value="medium">Medium</SelectItem> |
| <SelectItem value="low">Low</SelectItem> |
| </SelectContent> |
| </Select> |
| </SubSection> |
| </div> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* DROPDOWN MENU */} |
| {/* ============================================================ */} |
| <Section title="Dropdown Menu"> |
| <DropdownMenu> |
| <DropdownMenuTrigger asChild> |
| <Button variant="outline" size="sm"> |
| Quick Actions |
| <ChevronDown className="h-4 w-4" /> |
| </Button> |
| </DropdownMenuTrigger> |
| <DropdownMenuContent align="start" className="w-56"> |
| <DropdownMenuItem> |
| <Check className="h-4 w-4" /> |
| Mark as done |
| <DropdownMenuShortcut>⌘D</DropdownMenuShortcut> |
| </DropdownMenuItem> |
| <DropdownMenuItem> |
| <BookOpen className="h-4 w-4" /> |
| Open docs |
| </DropdownMenuItem> |
| <DropdownMenuSeparator /> |
| <DropdownMenuCheckboxItem |
| checked={menuChecked} |
| onCheckedChange={(value) => setMenuChecked(value === true)} |
| > |
| Watch issue |
| </DropdownMenuCheckboxItem> |
| <DropdownMenuItem variant="destructive"> |
| <Trash2 className="h-4 w-4" /> |
| Delete issue |
| </DropdownMenuItem> |
| </DropdownMenuContent> |
| </DropdownMenu> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* POPOVER */} |
| {/* ============================================================ */} |
| <Section title="Popover"> |
| <Popover> |
| <PopoverTrigger asChild> |
| <Button variant="outline" size="sm">Open Popover</Button> |
| </PopoverTrigger> |
| <PopoverContent className="space-y-2"> |
| <p className="text-sm font-medium">Agent heartbeat</p> |
| <p className="text-xs text-muted-foreground"> |
| Last run succeeded 24s ago. Next timer run in 9m. |
| </p> |
| <Button size="xs">Wake now</Button> |
| </PopoverContent> |
| </Popover> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* COLLAPSIBLE */} |
| {/* ============================================================ */} |
| <Section title="Collapsible"> |
| <Collapsible open={collapsibleOpen} onOpenChange={setCollapsibleOpen} className="space-y-2"> |
| <CollapsibleTrigger asChild> |
| <Button variant="outline" size="sm"> |
| {collapsibleOpen ? "Hide" : "Show"} advanced filters |
| </Button> |
| </CollapsibleTrigger> |
| <CollapsibleContent className="rounded-md border border-border p-3"> |
| <div className="space-y-2"> |
| <Label htmlFor="owner-filter">Owner</Label> |
| <Input id="owner-filter" placeholder="Filter by agent name" /> |
| </div> |
| </CollapsibleContent> |
| </Collapsible> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* SHEET */} |
| {/* ============================================================ */} |
| <Section title="Sheet"> |
| <Sheet> |
| <SheetTrigger asChild> |
| <Button variant="outline" size="sm">Open Side Panel</Button> |
| </SheetTrigger> |
| <SheetContent side="right"> |
| <SheetHeader> |
| <SheetTitle>Issue Properties</SheetTitle> |
| <SheetDescription>Edit metadata without leaving the current page.</SheetDescription> |
| </SheetHeader> |
| <div className="space-y-4 px-4"> |
| <div className="space-y-1"> |
| <Label htmlFor="sheet-title">Title</Label> |
| <Input id="sheet-title" defaultValue="Improve onboarding docs" /> |
| </div> |
| <div className="space-y-1"> |
| <Label htmlFor="sheet-description">Description</Label> |
| <Textarea id="sheet-description" defaultValue="Capture setup pitfalls and screenshots." /> |
| </div> |
| </div> |
| <SheetFooter> |
| <Button variant="outline">Cancel</Button> |
| <Button>Save</Button> |
| </SheetFooter> |
| </SheetContent> |
| </Sheet> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* SCROLL AREA */} |
| {/* ============================================================ */} |
| <Section title="Scroll Area"> |
| <ScrollArea className="h-36 rounded-md border border-border"> |
| <div className="space-y-2 p-3"> |
| {Array.from({ length: 12 }).map((_, i) => ( |
| <div key={i} className="rounded-md border border-border p-2 text-sm"> |
| Heartbeat run #{i + 1}: completed successfully |
| </div> |
| ))} |
| </div> |
| </ScrollArea> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* COMMAND */} |
| {/* ============================================================ */} |
| <Section title="Command (CMDK)"> |
| <div className="rounded-md border border-border"> |
| <Command> |
| <CommandInput placeholder="Type a command or search..." /> |
| <CommandList> |
| <CommandEmpty>No results found.</CommandEmpty> |
| <CommandGroup heading="Pages"> |
| <CommandItem> |
| <LayoutDashboard className="h-4 w-4" /> |
| Dashboard |
| </CommandItem> |
| <CommandItem> |
| <CircleDot className="h-4 w-4" /> |
| Issues |
| </CommandItem> |
| </CommandGroup> |
| <CommandSeparator /> |
| <CommandGroup heading="Actions"> |
| <CommandItem> |
| <CommandIcon className="h-4 w-4" /> |
| Open command palette |
| </CommandItem> |
| <CommandItem> |
| <Plus className="h-4 w-4" /> |
| Create new issue |
| </CommandItem> |
| </CommandGroup> |
| </CommandList> |
| </Command> |
| </div> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* BREADCRUMB */} |
| {/* ============================================================ */} |
| <Section title="Breadcrumb"> |
| <Breadcrumb> |
| <BreadcrumbList> |
| <BreadcrumbItem> |
| <BreadcrumbLink href="#">Projects</BreadcrumbLink> |
| </BreadcrumbItem> |
| <BreadcrumbSeparator /> |
| <BreadcrumbItem> |
| <BreadcrumbLink href="#">Paperclip App</BreadcrumbLink> |
| </BreadcrumbItem> |
| <BreadcrumbSeparator /> |
| <BreadcrumbItem> |
| <BreadcrumbPage>Issue List</BreadcrumbPage> |
| </BreadcrumbItem> |
| </BreadcrumbList> |
| </Breadcrumb> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* CARDS */} |
| {/* ============================================================ */} |
| <Section title="Cards"> |
| <SubSection title="Standard Card"> |
| <Card> |
| <CardHeader> |
| <CardTitle>Card Title</CardTitle> |
| <CardDescription>Card description with supporting text.</CardDescription> |
| </CardHeader> |
| <CardContent> |
| <p className="text-sm">Card content goes here. This is the main body area.</p> |
| </CardContent> |
| <CardFooter className="gap-2"> |
| <Button size="sm">Action</Button> |
| <Button variant="outline" size="sm">Cancel</Button> |
| </CardFooter> |
| </Card> |
| </SubSection> |
| |
| <SubSection title="Metric Cards"> |
| <div className="grid md:grid-cols-2 xl:grid-cols-4 gap-4"> |
| <MetricCard icon={Bot} value={12} label="Active Agents" description="+3 this week" /> |
| <MetricCard icon={CircleDot} value={48} label="Open Issues" /> |
| <MetricCard icon={DollarSign} value="$1,234" label="Monthly Cost" description="Under budget" /> |
| <MetricCard icon={Zap} value="99.9%" label="Uptime" /> |
| </div> |
| </SubSection> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* TABS */} |
| {/* ============================================================ */} |
| <Section title="Tabs"> |
| <SubSection title="Default (pill) variant"> |
| <Tabs defaultValue="overview"> |
| <TabsList> |
| <TabsTrigger value="overview">Overview</TabsTrigger> |
| <TabsTrigger value="runs">Runs</TabsTrigger> |
| <TabsTrigger value="config">Config</TabsTrigger> |
| <TabsTrigger value="costs">Costs</TabsTrigger> |
| </TabsList> |
| <TabsContent value="overview"> |
| <p className="text-sm text-muted-foreground py-4">Overview tab content.</p> |
| </TabsContent> |
| <TabsContent value="runs"> |
| <p className="text-sm text-muted-foreground py-4">Runs tab content.</p> |
| </TabsContent> |
| <TabsContent value="config"> |
| <p className="text-sm text-muted-foreground py-4">Config tab content.</p> |
| </TabsContent> |
| <TabsContent value="costs"> |
| <p className="text-sm text-muted-foreground py-4">Costs tab content.</p> |
| </TabsContent> |
| </Tabs> |
| </SubSection> |
| |
| <SubSection title="Line variant"> |
| <Tabs defaultValue="summary"> |
| <TabsList variant="line"> |
| <TabsTrigger value="summary">Summary</TabsTrigger> |
| <TabsTrigger value="details">Details</TabsTrigger> |
| <TabsTrigger value="comments">Comments</TabsTrigger> |
| </TabsList> |
| <TabsContent value="summary"> |
| <p className="text-sm text-muted-foreground py-4">Summary content with underline tabs.</p> |
| </TabsContent> |
| <TabsContent value="details"> |
| <p className="text-sm text-muted-foreground py-4">Details content.</p> |
| </TabsContent> |
| <TabsContent value="comments"> |
| <p className="text-sm text-muted-foreground py-4">Comments content.</p> |
| </TabsContent> |
| </Tabs> |
| </SubSection> |
| </Section> |
| |
| {/* ============================================================ */} |
| {/* ENTITY ROWS */} |
| {/* ============================================================ */} |
| <Section title="Entity Rows"> |
| <div className="border border-border rounded-md"> |
| <EntityRow |
| leading={ |
| <> |
| <StatusIcon status="in_progress" /> |
| <PriorityIcon priority="high" /> |
| </> |
| } |
| identifier="PAP-001" |
| title="Implement authentication flow" |
| subtitle="Assigned to Agent Alpha" |
| trailing={<StatusBadge status="in_progress" />} |
| onClick={() => {}} |
| /> |
| <EntityRow |
| leading={ |
| <> |
| <StatusIcon status="done" /> |
| <PriorityIcon priority="medium" /> |
| </> |
| } |
| identifier="PAP-002" |
| title="Set up CI/CD pipeline" |
| subtitle="Completed 2 days ago" |
| trailing={<StatusBadge status="done" />} |
| onClick={() => {}} |
| /> |
| <EntityRow |
| leading={ |
| <> |
| <StatusIcon status="todo" /> |
| <PriorityIcon priority="low" /> |
| </> |
| } |
| identifier="PAP-003" |
| title="Write API documentation" |
| trailing={<StatusBadge status="todo" />} |
| onClick={() => {}} |
| /> |
| <EntityRow |
| leading={ |
| <> |
| <StatusIcon status="blocked" /> |
| <PriorityIcon priority="critical" /> |
| </> |
| } |
| identifier="PAP-004" |
| title="Deploy to production" |
| subtitle="Blocked by PAP-001" |
| trailing={<StatusBadge status="blocked" />} |
| selected |
| /> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Filter Bar"> |
| <FilterBar |
| filters={filters} |
| onRemove={(key) => setFilters((f) => f.filter((x) => x.key !== key))} |
| onClear={() => setFilters([])} |
| /> |
| {filters.length === 0 && ( |
| <Button |
| variant="outline" |
| size="sm" |
| onClick={() => |
| setFilters([ |
| { key: "status", label: "Status", value: "Active" }, |
| { key: "priority", label: "Priority", value: "High" }, |
| ]) |
| } |
| > |
| Reset filters |
| </Button> |
| )} |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Avatars"> |
| <SubSection title="Sizes"> |
| <div className="flex items-center gap-3"> |
| <Avatar size="sm"><AvatarFallback>SM</AvatarFallback></Avatar> |
| <Avatar><AvatarFallback>DF</AvatarFallback></Avatar> |
| <Avatar size="lg"><AvatarFallback>LG</AvatarFallback></Avatar> |
| </div> |
| </SubSection> |
|
|
| <SubSection title="Group"> |
| <AvatarGroup> |
| <Avatar><AvatarFallback>A1</AvatarFallback></Avatar> |
| <Avatar><AvatarFallback>A2</AvatarFallback></Avatar> |
| <Avatar><AvatarFallback>A3</AvatarFallback></Avatar> |
| <AvatarGroupCount>+5</AvatarGroupCount> |
| </AvatarGroup> |
| </SubSection> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Identity"> |
| <SubSection title="Sizes"> |
| <div className="flex items-center gap-6"> |
| <Identity name="Agent Alpha" size="sm" /> |
| <Identity name="Agent Alpha" /> |
| <Identity name="Agent Alpha" size="lg" /> |
| </div> |
| </SubSection> |
|
|
| <SubSection title="Initials derivation"> |
| <div className="flex flex-col gap-2"> |
| <Identity name="CEO Agent" size="sm" /> |
| <Identity name="Alpha" size="sm" /> |
| <Identity name="Quality Assurance Lead" size="sm" /> |
| </div> |
| </SubSection> |
|
|
| <SubSection title="Custom initials"> |
| <Identity name="Backend Service" initials="BS" size="sm" /> |
| </SubSection> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Tooltips"> |
| <div className="flex items-center gap-4"> |
| <Tooltip> |
| <TooltipTrigger asChild> |
| <Button variant="outline" size="sm">Hover me</Button> |
| </TooltipTrigger> |
| <TooltipContent>This is a tooltip</TooltipContent> |
| </Tooltip> |
| <Tooltip> |
| <TooltipTrigger asChild> |
| <Button variant="ghost" size="icon-sm"><Settings /></Button> |
| </TooltipTrigger> |
| <TooltipContent>Settings</TooltipContent> |
| </Tooltip> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Dialog"> |
| <Dialog> |
| <DialogTrigger asChild> |
| <Button variant="outline">Open Dialog</Button> |
| </DialogTrigger> |
| <DialogContent> |
| <DialogHeader> |
| <DialogTitle>Dialog Title</DialogTitle> |
| <DialogDescription> |
| This is a sample dialog showing the standard layout with header, content, and footer. |
| </DialogDescription> |
| </DialogHeader> |
| <div className="space-y-3"> |
| <div> |
| <Label>Name</Label> |
| <Input placeholder="Enter a name" className="mt-1.5" /> |
| </div> |
| <div> |
| <Label>Description</Label> |
| <Textarea placeholder="Describe..." className="mt-1.5" /> |
| </div> |
| </div> |
| <DialogFooter> |
| <Button variant="outline">Cancel</Button> |
| <Button>Save</Button> |
| </DialogFooter> |
| </DialogContent> |
| </Dialog> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Empty State"> |
| <div className="border border-border rounded-md"> |
| <EmptyState |
| icon={Inbox} |
| message="No items to show. Create your first one to get started." |
| action="Create Item" |
| onAction={() => {}} |
| /> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Progress Bars (Budget)"> |
| <div className="space-y-3"> |
| {[ |
| { label: "Under budget (40%)", pct: 40, color: "bg-green-400" }, |
| { label: "Warning (75%)", pct: 75, color: "bg-yellow-400" }, |
| { label: "Over budget (95%)", pct: 95, color: "bg-red-400" }, |
| ].map(({ label, pct, color }) => ( |
| <div key={label} className="space-y-1"> |
| <div className="flex items-center justify-between"> |
| <span className="text-xs text-muted-foreground">{label}</span> |
| <span className="text-xs font-mono">{pct}%</span> |
| </div> |
| <div className="w-full h-2 bg-muted rounded-full overflow-hidden"> |
| <div |
| className={`h-full rounded-full transition-[width,background-color] duration-150 ${color}`} |
| style={{ width: `${pct}%` }} |
| /> |
| </div> |
| </div> |
| ))} |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Log Viewer"> |
| <div className="bg-neutral-950 rounded-lg p-3 font-mono text-xs max-h-80 overflow-y-auto"> |
| <div className="text-foreground">[12:00:01] INFO Agent started successfully</div> |
| <div className="text-foreground">[12:00:02] INFO Processing task PAP-001</div> |
| <div className="text-yellow-400">[12:00:05] WARN Rate limit approaching (80%)</div> |
| <div className="text-foreground">[12:00:08] INFO Task PAP-001 completed</div> |
| <div className="text-red-400">[12:00:12] ERROR Connection timeout to upstream service</div> |
| <div className="text-blue-300">[12:00:12] SYS Retrying connection in 5s...</div> |
| <div className="text-foreground">[12:00:17] INFO Reconnected successfully</div> |
| <div className="flex items-center gap-1.5"> |
| <span className="relative flex h-1.5 w-1.5"> |
| <span className="absolute inline-flex h-full w-full rounded-full bg-cyan-400 animate-pulse" /> |
| <span className="inline-flex h-full w-full rounded-full bg-cyan-400" /> |
| </span> |
| <span className="text-cyan-400">Live</span> |
| </div> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Property Row Pattern"> |
| <div className="border border-border rounded-md p-4 space-y-1 max-w-sm"> |
| <div className="flex items-center justify-between py-1.5"> |
| <span className="text-xs text-muted-foreground">Status</span> |
| <StatusBadge status="active" /> |
| </div> |
| <div className="flex items-center justify-between py-1.5"> |
| <span className="text-xs text-muted-foreground">Priority</span> |
| <PriorityIcon priority="high" /> |
| </div> |
| <div className="flex items-center justify-between py-1.5"> |
| <span className="text-xs text-muted-foreground">Assignee</span> |
| <div className="flex items-center gap-1.5"> |
| <Avatar size="sm"><AvatarFallback>A</AvatarFallback></Avatar> |
| <span className="text-xs">Agent Alpha</span> |
| </div> |
| </div> |
| <div className="flex items-center justify-between py-1.5"> |
| <span className="text-xs text-muted-foreground">Created</span> |
| <span className="text-xs">Jan 15, 2025</span> |
| </div> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Navigation Patterns"> |
| <SubSection title="Sidebar nav items"> |
| <div className="w-60 border border-border rounded-md p-3 space-y-0.5 bg-card"> |
| <div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium bg-accent text-accent-foreground"> |
| <LayoutDashboard className="h-4 w-4" /> |
| Dashboard |
| </div> |
| <div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground cursor-pointer"> |
| <CircleDot className="h-4 w-4" /> |
| Issues |
| <span className="ml-auto text-xs bg-primary text-primary-foreground rounded-full px-1.5 py-0.5"> |
| 12 |
| </span> |
| </div> |
| <div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground cursor-pointer"> |
| <Bot className="h-4 w-4" /> |
| Agents |
| </div> |
| <div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground cursor-pointer"> |
| <Hexagon className="h-4 w-4" /> |
| Projects |
| </div> |
| </div> |
| </SubSection> |
|
|
| <SubSection title="View toggle"> |
| <div className="flex items-center border border-border rounded-md w-fit"> |
| <button className="px-3 py-1.5 text-xs font-medium bg-accent text-foreground rounded-l-md"> |
| <ListTodo className="h-3.5 w-3.5 inline mr-1" /> |
| List |
| </button> |
| <button className="px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent/50 rounded-r-md"> |
| <Target className="h-3.5 w-3.5 inline mr-1" /> |
| Org |
| </button> |
| </div> |
| </SubSection> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Grouped List (Issues pattern)"> |
| <div> |
| <div className="flex items-center gap-2 px-4 py-2 bg-muted/50 rounded-t-md"> |
| <StatusIcon status="in_progress" /> |
| <span className="text-sm font-medium">In Progress</span> |
| <span className="text-xs text-muted-foreground ml-1">2</span> |
| </div> |
| <div className="border border-border rounded-b-md"> |
| <EntityRow |
| leading={<PriorityIcon priority="high" />} |
| identifier="PAP-101" |
| title="Build agent heartbeat system" |
| onClick={() => {}} |
| /> |
| <EntityRow |
| leading={<PriorityIcon priority="medium" />} |
| identifier="PAP-102" |
| title="Add cost tracking dashboard" |
| onClick={() => {}} |
| /> |
| </div> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Comment Thread Pattern"> |
| <div className="space-y-3 max-w-2xl"> |
| <h3 className="text-sm font-semibold">Comments (2)</h3> |
| <div className="space-y-3"> |
| <div className="rounded-md border border-border p-3"> |
| <div className="flex items-center justify-between mb-1"> |
| <span className="text-xs font-medium text-muted-foreground">Agent</span> |
| <span className="text-xs text-muted-foreground">Jan 15, 2025</span> |
| </div> |
| <p className="text-sm">Started working on the authentication module. Will need API keys configured.</p> |
| </div> |
| <div className="rounded-md border border-border p-3"> |
| <div className="flex items-center justify-between mb-1"> |
| <span className="text-xs font-medium text-muted-foreground">Human</span> |
| <span className="text-xs text-muted-foreground">Jan 16, 2025</span> |
| </div> |
| <p className="text-sm">API keys have been added to the vault. Please proceed.</p> |
| </div> |
| </div> |
| <div className="space-y-2"> |
| <Textarea placeholder="Leave a comment..." rows={3} /> |
| <Button size="sm">Comment</Button> |
| </div> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Cost Table Pattern"> |
| <div className="border border-border rounded-lg overflow-hidden"> |
| <table className="w-full text-xs"> |
| <thead className="border-b border-border bg-accent/20"> |
| <tr> |
| <th className="text-left px-3 py-2 font-medium text-muted-foreground">Model</th> |
| <th className="text-left px-3 py-2 font-medium text-muted-foreground">Tokens</th> |
| <th className="text-left px-3 py-2 font-medium text-muted-foreground">Cost</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr className="border-b border-border"> |
| <td className="px-3 py-2">claude-sonnet-4-20250514</td> |
| <td className="px-3 py-2 font-mono">1.2M</td> |
| <td className="px-3 py-2 font-mono">$18.00</td> |
| </tr> |
| <tr className="border-b border-border"> |
| <td className="px-3 py-2">claude-haiku-4-20250506</td> |
| <td className="px-3 py-2 font-mono">500k</td> |
| <td className="px-3 py-2 font-mono">$1.25</td> |
| </tr> |
| <tr> |
| <td className="px-3 py-2 font-medium">Total</td> |
| <td className="px-3 py-2 font-mono">1.7M</td> |
| <td className="px-3 py-2 font-mono font-medium">$19.25</td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Skeletons"> |
| <SubSection title="Individual"> |
| <div className="space-y-2"> |
| <Skeleton className="h-4 w-48" /> |
| <Skeleton className="h-8 w-full max-w-sm" /> |
| <Skeleton className="h-20 w-full" /> |
| </div> |
| </SubSection> |
|
|
| <SubSection title="Page Skeleton (list)"> |
| <div className="border border-border rounded-md p-4"> |
| <PageSkeleton variant="list" /> |
| </div> |
| </SubSection> |
|
|
| <SubSection title="Page Skeleton (detail)"> |
| <div className="border border-border rounded-md p-4"> |
| <PageSkeleton variant="detail" /> |
| </div> |
| </SubSection> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Separator"> |
| <div className="space-y-4"> |
| <p className="text-sm text-muted-foreground">Horizontal</p> |
| <Separator /> |
| <div className="flex items-center gap-4 h-8"> |
| <span className="text-sm">Left</span> |
| <Separator orientation="vertical" /> |
| <span className="text-sm">Right</span> |
| </div> |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Common Icons (Lucide)"> |
| <div className="grid grid-cols-4 md:grid-cols-6 gap-4"> |
| {[ |
| ["Inbox", Inbox], |
| ["ListTodo", ListTodo], |
| ["CircleDot", CircleDot], |
| ["Hexagon", Hexagon], |
| ["Target", Target], |
| ["LayoutDashboard", LayoutDashboard], |
| ["Bot", Bot], |
| ["DollarSign", DollarSign], |
| ["History", History], |
| ["Search", Search], |
| ["Plus", Plus], |
| ["Trash2", Trash2], |
| ["Settings", Settings], |
| ["User", User], |
| ["Mail", Mail], |
| ["Upload", Upload], |
| ["Zap", Zap], |
| ].map(([name, Icon]) => { |
| const LucideIcon = Icon as React.FC<{ className?: string }>; |
| return ( |
| <div key={name as string} className="flex flex-col items-center gap-1.5 p-2"> |
| <LucideIcon className="h-4 w-4 text-muted-foreground" /> |
| <span className="text-[10px] text-muted-foreground font-mono">{name as string}</span> |
| </div> |
| ); |
| })} |
| </div> |
| </Section> |
|
|
| {} |
| {} |
| {} |
| <Section title="Keyboard Shortcuts"> |
| <div className="border border-border rounded-md divide-y divide-border text-sm"> |
| {[ |
| ["Cmd+K / Ctrl+K", "Open Command Palette"], |
| ["C", "New Issue (outside inputs)"], |
| ["[", "Toggle Sidebar"], |
| ["]", "Toggle Properties Panel"], |
| |
| ["Cmd+Enter / Ctrl+Enter", "Submit markdown comment"], |
| ].map(([key, desc]) => ( |
| <div key={key} className="flex items-center justify-between px-4 py-2"> |
| <span className="text-muted-foreground">{desc}</span> |
| <kbd className="px-2 py-0.5 text-xs font-mono bg-muted rounded border border-border"> |
| {key} |
| </kbd> |
| </div> |
| ))} |
| </div> |
| </Section> |
| </div> |
| ); |
| } |
|
|