Skip to content

Commit dbdce4f

Browse files
NevilleQingNYclaude
andcommitted
refactor(ui): comprehensive UI craft review — sidebar, headers, detail panels
Sidebar: - Pinned items: StatusIcon for issues, emoji for projects, sm size, mask gradient text fade - Pinned items: inline X close button (hidden → flex on hover, desktop tab pattern) - Pinned section: collapsible with chevron + hover count - Remove unused canvas token Global components: - PageHeader: shared component with built-in mobile SidebarTrigger (md:hidden) - Replace header divs in all 11 dashboard pages with PageHeader - Remove standalone mobile trigger bar from DashboardLayout - Tooltip: 200ms delay, remove arrow, popover/border style - Search dialog: add finalFocus={false} - SidebarInset: remove shadow-sm - Button sizing: icon-xs → icon-sm across all non-editor contexts Issue Detail: - Simplify breadcrumb to workspace > identifier - Extract sidebarContent variable shared between ResizablePanel and mobile Sheet - All sidebar sections collapsible (Properties, Parent issue, Details, Token usage) - Auto-close sidebar on mobile breakpoint - Collapsible section headers: text before chevron, !size-3 stroke-[2.5], hover bg Project Detail: - Match Issue Detail layout pattern (header inside left ResizablePanel) - Extract sidebarContent, add mobile Sheet support - All sidebar sections collapsible (Properties, Progress, Description) - Header: move three-dot menu to right button group, unified breadcrumb layout - Auto-close sidebar on mobile breakpoint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ce94c80 commit dbdce4f

File tree

21 files changed

+598
-595
lines changed

21 files changed

+598
-595
lines changed

packages/ui/components/ui/sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
353353
<main
354354
data-slot="sidebar-inset"
355355
className={cn(
356-
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
356+
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
357357
className
358358
)}
359359
{...props}

packages/ui/components/ui/tooltip.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
55
import { cn } from "@multica/ui/lib/utils"
66

77
function TooltipProvider({
8-
delay = 0,
8+
delay = 200,
99
...props
1010
}: TooltipPrimitive.Provider.Props) {
1111
return (
@@ -50,13 +50,12 @@ function TooltipContent({
5050
<TooltipPrimitive.Popup
5151
data-slot="tooltip-content"
5252
className={cn(
53-
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
53+
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-lg border border-border bg-popover px-2.5 py-1 text-xs text-popover-foreground has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
5454
className
5555
)}
5656
{...props}
5757
>
5858
{children}
59-
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
6059
</TooltipPrimitive.Popup>
6160
</TooltipPrimitive.Positioner>
6261
</TooltipPrimitive.Portal>

packages/ui/styles/tokens.css

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
--color-brand: var(--brand);
2828
--color-brand-foreground: var(--brand-foreground);
2929
--color-priority: var(--priority);
30-
--color-canvas: var(--canvas);
3130
--color-accent-foreground: var(--accent-foreground);
3231
--color-accent: var(--accent);
3332
--color-muted-foreground: var(--muted-foreground);
@@ -86,7 +85,6 @@
8685
--sidebar-ring: oklch(0.705 0.015 286.067);
8786
--brand: oklch(0.55 0.16 255);
8887
--brand-foreground: oklch(0.985 0 0);
89-
--canvas: oklch(0.95 0.002 286);
9088
--success: oklch(0.55 0.16 145);
9189
--warning: oklch(0.75 0.16 85);
9290
--info: oklch(0.55 0.18 250);
@@ -130,7 +128,6 @@
130128
--sidebar-ring: oklch(0.552 0.016 285.938);
131129
--brand: oklch(0.65 0.16 255);
132130
--brand-foreground: oklch(0.985 0 0);
133-
--canvas: oklch(0.2 0.005 286);
134131
--success: oklch(0.65 0.15 145);
135132
--warning: oklch(0.70 0.16 85);
136133
--info: oklch(0.65 0.18 250);

packages/views/agents/components/agents-page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { runtimeListOptions } from "@multica/core/runtimes/queries";
1818
import { useQuery, useQueryClient } from "@tanstack/react-query";
1919
import { useWorkspaceId } from "@multica/core/hooks";
2020
import { agentListOptions, memberListOptions, workspaceKeys } from "@multica/core/workspace/queries";
21+
import { PageHeader } from "../../layout/page-header";
2122
import { CreateAgentDialog } from "./create-agent-dialog";
2223
import { AgentListItem } from "./agent-list-item";
2324
import { AgentDetail } from "./agent-detail";
@@ -140,28 +141,28 @@ export function AgentsPage() {
140141
<ResizablePanel id="list" defaultSize={280} minSize={240} maxSize={400} groupResizeBehavior="preserve-pixel-size">
141142
{/* Left column — agent list */}
142143
<div className="overflow-y-auto h-full border-r">
143-
<div className="flex h-12 items-center justify-between border-b px-4">
144+
<PageHeader className="justify-between">
144145
<h1 className="text-sm font-semibold">Agents</h1>
145146
<div className="flex items-center gap-1">
146147
{archivedCount > 0 && (
147148
<Button
148149
variant={showArchived ? "secondary" : "ghost"}
149-
size="icon-xs"
150+
size="icon-sm"
150151
onClick={() => setShowArchived(!showArchived)}
151152
title={showArchived ? "Show active agents" : "Show archived agents"}
152153
>
153-
<Archive className="h-4 w-4 text-muted-foreground" />
154+
<Archive className="text-muted-foreground" />
154155
</Button>
155156
)}
156157
<Button
157158
variant="ghost"
158-
size="icon-xs"
159+
size="icon-sm"
159160
onClick={() => setShowCreate(true)}
160161
>
161-
<Plus className="h-4 w-4 text-muted-foreground" />
162+
<Plus className="text-muted-foreground" />
162163
</Button>
163164
</div>
164-
</div>
165+
</PageHeader>
165166
{filteredAgents.length === 0 ? (
166167
<div className="flex flex-col items-center justify-center px-4 py-12">
167168
<Bot className="h-8 w-8 text-muted-foreground/40" />

packages/views/agents/components/tabs/skills-tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export function SkillsTab({
125125
</div>
126126
<Button
127127
variant="ghost"
128-
size="icon-xs"
128+
size="icon-sm"
129129
onClick={() => handleRemove(skill.id)}
130130
disabled={saving}
131131
className="text-muted-foreground hover:text-destructive"

packages/views/autopilots/components/autopilot-detail-page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { agentListOptions } from "@multica/core/workspace/queries";
1515
import { useWorkspaceId } from "@multica/core/hooks";
1616
import { useActorName } from "@multica/core/workspace/hooks";
1717
import { useNavigation, AppLink } from "../../navigation";
18+
import { PageHeader } from "../../layout/page-header";
1819
import { ActorAvatar } from "../../common/actor-avatar";
1920
import { Skeleton } from "@multica/ui/components/ui/skeleton";
2021
import { Button } from "@multica/ui/components/ui/button";
@@ -414,7 +415,7 @@ export function AutopilotDetailPage({ autopilotId }: { autopilotId: string }) {
414415
return (
415416
<div className="flex h-full flex-col">
416417
{/* Header */}
417-
<div className="flex h-12 shrink-0 items-center justify-between border-b px-5">
418+
<PageHeader className="justify-between px-5">
418419
<div className="flex items-center gap-2">
419420
<AppLink href="/autopilots" className="text-muted-foreground hover:text-foreground transition-colors">
420421
<Zap className="h-4 w-4" />
@@ -447,7 +448,7 @@ export function AutopilotDetailPage({ autopilotId }: { autopilotId: string }) {
447448
{triggerAutopilot.isPending ? "Running..." : "Run now"}
448449
</Button>
449450
</div>
450-
</div>
451+
</PageHeader>
451452

452453
<div className="flex-1 overflow-y-auto">
453454
<div className="max-w-4xl mx-auto p-6 space-y-8">

packages/views/autopilots/components/autopilots-page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useWorkspaceId } from "@multica/core/hooks";
1010
import { useActorName } from "@multica/core/workspace/hooks";
1111
import { AppLink } from "../../navigation";
1212
import { ActorAvatar } from "../../common/actor-avatar";
13+
import { PageHeader } from "../../layout/page-header";
1314
import { Skeleton } from "@multica/ui/components/ui/skeleton";
1415
import { Button } from "@multica/ui/components/ui/button";
1516
import { cn } from "@multica/ui/lib/utils";
@@ -346,7 +347,7 @@ export function AutopilotsPage() {
346347
return (
347348
<div className="flex h-full flex-col">
348349
{/* Header */}
349-
<div className="flex h-12 shrink-0 items-center justify-between border-b px-5">
350+
<PageHeader className="justify-between px-5">
350351
<div className="flex items-center gap-2">
351352
<Zap className="h-4 w-4 text-muted-foreground" />
352353
<h1 className="text-sm font-medium">Autopilot</h1>
@@ -358,7 +359,7 @@ export function AutopilotsPage() {
358359
<Plus className="h-3.5 w-3.5 mr-1" />
359360
New autopilot
360361
</Button>
361-
</div>
362+
</PageHeader>
362363

363364
{/* Table */}
364365
<div className="flex-1 overflow-y-auto">

packages/views/inbox/components/inbox-page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
DropdownMenuSeparator,
4545
} from "@multica/ui/components/ui/dropdown-menu";
4646
import { useIsMobile } from "@multica/ui/hooks/use-mobile";
47+
import { PageHeader } from "../../layout/page-header";
4748
import { InboxListItem, timeAgo } from "./inbox-list-item";
4849
import { typeLabels } from "./inbox-detail-label";
4950

@@ -133,7 +134,7 @@ export function InboxPage() {
133134
// -- Shared sub-components --------------------------------------------------
134135

135136
const listHeader = (
136-
<div className="flex h-12 shrink-0 items-center justify-between border-b px-4">
137+
<PageHeader className="justify-between">
137138
<div className="flex items-center gap-2">
138139
<h1 className="text-sm font-semibold">Inbox</h1>
139140
{unreadCount > 0 && (
@@ -147,7 +148,7 @@ export function InboxPage() {
147148
render={
148149
<Button
149150
variant="ghost"
150-
size="icon-xs"
151+
size="icon-sm"
151152
className="text-muted-foreground"
152153
/>
153154
}
@@ -174,7 +175,7 @@ export function InboxPage() {
174175
</DropdownMenuItem>
175176
</DropdownMenuContent>
176177
</DropdownMenu>
177-
</div>
178+
</PageHeader>
178179
);
179180

180181
const listBody = items.length === 0 ? (

packages/views/issues/components/comment-card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ function CommentRow({
223223
<DropdownMenu>
224224
<DropdownMenuTrigger
225225
render={
226-
<Button variant="ghost" size="icon-xs" className="text-muted-foreground">
226+
<Button variant="ghost" size="icon-sm" className="text-muted-foreground">
227227
<MoreHorizontal className="h-4 w-4" />
228228
</Button>
229229
}
@@ -434,7 +434,7 @@ function CommentCard({
434434
<DropdownMenu>
435435
<DropdownMenuTrigger
436436
render={
437-
<Button variant="ghost" size="icon-xs" className="text-muted-foreground">
437+
<Button variant="ghost" size="icon-sm" className="text-muted-foreground">
438438
<MoreHorizontal className="h-4 w-4" />
439439
</Button>
440440
}

0 commit comments

Comments
 (0)