mirror of
https://github.com/block/buzz.git
synced 2026-11-03 07:51:22 +00:00
refactor(desktop): centralize auxiliary panel header controls
- Move auxiliary panel header padding into shared layout constants so profile, activity, thread, and channel management panels share the same right inset. - Add shared auxiliary header back-button rendering with a scoped negative margin so leading icon placement is consistent without per-panel padding variants. - Add a fixed auxiliary header actions wrapper to standardize icon-button spacing in header action clusters. - Move the Activity raw feed toggle into a settings dropdown next to the close action, matching the profile panel settings ingress pattern. - Normalize related Tailwind shorthand classes while touching the auxiliary panel headers. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { ArrowLeft, CircleDot, Octagon, TerminalSquare, X } from "lucide-react";
|
||||
import { CircleDot, Octagon, Settings, TerminalSquare, X } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { ManagedAgentSessionPanel } from "@/features/agents/ui/ManagedAgentSessionPanel";
|
||||
@@ -12,9 +12,11 @@ import { useStickToBottom } from "@/shared/hooks/useStickToBottom";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import {
|
||||
AuxiliaryPanelHeader,
|
||||
AuxiliaryPanelHeaderActions,
|
||||
AuxiliaryPanelHeaderGroup,
|
||||
AuxiliaryPanelTitle,
|
||||
auxiliaryPanelContentPaddingClass,
|
||||
auxiliaryPanelHeaderPaddingClass,
|
||||
} from "@/shared/layout/AuxiliaryPanelHeader";
|
||||
import { Badge } from "@/shared/ui/badge";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
@@ -28,6 +30,12 @@ import {
|
||||
import { THREAD_PANEL_MIN_WIDTH_PX } from "@/shared/hooks/useThreadPanelWidth";
|
||||
import { Switch } from "@/shared/ui/switch";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/ui/dropdown-menu";
|
||||
import type { ChannelAgentSessionAgent } from "./useChannelAgentSessions";
|
||||
|
||||
type AgentSessionThreadPanelProps = {
|
||||
@@ -63,6 +71,7 @@ export function AgentSessionThreadPanel({
|
||||
|
||||
const { ref: scrollRef, onScroll } = useStickToBottom<HTMLDivElement>();
|
||||
const rawFeedScopeKey = `${agent.pubkey}:${channel?.id ?? "all"}`;
|
||||
const rawFeedSwitchId = React.useId();
|
||||
const [rawFeedState, setRawFeedState] = React.useState(() => ({
|
||||
scopeKey: rawFeedScopeKey,
|
||||
show: false,
|
||||
@@ -96,7 +105,7 @@ export function AgentSessionThreadPanel({
|
||||
}
|
||||
|
||||
const agentHeaderActions = (
|
||||
<div className="ml-auto flex shrink-0 items-center gap-2">
|
||||
<AuxiliaryPanelHeaderActions>
|
||||
{isLive && isWorking ? (
|
||||
<Badge className="shrink-0 gap-1 px-2 py-0 text-2xs" variant="default">
|
||||
<CircleDot className="h-2.5 w-2.5" />
|
||||
@@ -104,32 +113,57 @@ export function AgentSessionThreadPanel({
|
||||
</Badge>
|
||||
) : null}
|
||||
{isLive ? (
|
||||
<div
|
||||
className="flex min-w-19 shrink-0 items-center justify-end gap-2"
|
||||
title={
|
||||
showRawFeed
|
||||
? "Hide raw JSON-RPC payloads."
|
||||
: channel
|
||||
? "Show raw JSON-RPC payloads for this channel."
|
||||
: "Show raw JSON-RPC payloads for this agent."
|
||||
}
|
||||
>
|
||||
<label
|
||||
className="flex shrink-0 items-center gap-1.5 text-xs font-medium text-muted-foreground"
|
||||
htmlFor="agent-session-raw-feed-switch"
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-label="Open activity settings"
|
||||
data-testid="agent-session-settings-menu-trigger"
|
||||
size="icon"
|
||||
title="Activity settings"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Settings />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="min-w-56"
|
||||
onCloseAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<TerminalSquare className="h-3 w-3" />
|
||||
Raw
|
||||
</label>
|
||||
<Switch
|
||||
aria-label={showRawFeed ? "Hide raw feed" : "Show raw feed"}
|
||||
checked={showRawFeed}
|
||||
className="shrink-0 data-[state=unchecked]:bg-muted-foreground/45 [&>span]:bg-white"
|
||||
data-testid="agent-session-toggle-raw-feed"
|
||||
id="agent-session-raw-feed-switch"
|
||||
onCheckedChange={handleRawFeedChange}
|
||||
/>
|
||||
</div>
|
||||
<DropdownMenuItem
|
||||
className="gap-3 pr-2"
|
||||
onSelect={(event) => {
|
||||
event.preventDefault();
|
||||
handleRawFeedChange(!showRawFeed);
|
||||
}}
|
||||
title={
|
||||
showRawFeed
|
||||
? "Hide raw JSON-RPC payloads."
|
||||
: channel
|
||||
? "Show raw JSON-RPC payloads for this channel."
|
||||
: "Show raw JSON-RPC payloads for this agent."
|
||||
}
|
||||
>
|
||||
<TerminalSquare className="h-4 w-4 text-muted-foreground" />
|
||||
<label
|
||||
className="min-w-0 flex-1 text-sm font-medium"
|
||||
htmlFor={rawFeedSwitchId}
|
||||
>
|
||||
Raw activity
|
||||
</label>
|
||||
<Switch
|
||||
aria-label={showRawFeed ? "Hide raw feed" : "Show raw feed"}
|
||||
checked={showRawFeed}
|
||||
className="shrink-0 data-[state=unchecked]:bg-muted-foreground/45 [&>span]:bg-white"
|
||||
data-testid="agent-session-toggle-raw-feed"
|
||||
id={rawFeedSwitchId}
|
||||
onCheckedChange={handleRawFeedChange}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
{isLive && isWorking ? (
|
||||
<Tooltip>
|
||||
@@ -167,23 +201,16 @@ export function AgentSessionThreadPanel({
|
||||
>
|
||||
<X />
|
||||
</Button>
|
||||
</div>
|
||||
</AuxiliaryPanelHeaderActions>
|
||||
);
|
||||
|
||||
const agentHeaderContent = (
|
||||
<>
|
||||
<AuxiliaryPanelHeaderGroup>
|
||||
<Button
|
||||
aria-label="Back from activity"
|
||||
className="shrink-0"
|
||||
data-testid="agent-session-back"
|
||||
onClick={onBackToProfile}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
<AuxiliaryPanelHeaderGroup
|
||||
backButtonAriaLabel="Back from activity"
|
||||
backButtonTestId="agent-session-back"
|
||||
onBack={onBackToProfile}
|
||||
>
|
||||
<AuxiliaryPanelTitle>
|
||||
{showRawFeed ? "Raw ACP Activity" : "Activity"}
|
||||
</AuxiliaryPanelTitle>
|
||||
@@ -199,7 +226,7 @@ export function AgentSessionThreadPanel({
|
||||
className={cn(
|
||||
"min-h-0 flex-1 overflow-y-auto px-3 pb-4",
|
||||
isSplitLayout && auxiliaryPanelContentPaddingClass,
|
||||
!isSplitLayout && (isFloatingOverlay ? "pt-4" : "pt-[3.25rem]"),
|
||||
!isSplitLayout && (isFloatingOverlay ? "pt-4" : "pt-13"),
|
||||
)}
|
||||
>
|
||||
<ManagedAgentSessionPanel
|
||||
@@ -248,7 +275,7 @@ export function AgentSessionThreadPanel({
|
||||
{!isOverlay ? (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[3.25rem] bg-background/75 backdrop-blur-md supports-[backdrop-filter]:bg-background/65 dark:bg-background/45 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/35"
|
||||
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-13 bg-background/75 backdrop-blur-md supports-backdrop-filter:bg-background/65 dark:bg-background/45 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/35"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -256,8 +283,8 @@ export function AgentSessionThreadPanel({
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center",
|
||||
isSinglePanelView
|
||||
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-[3.25rem] min-h-[3.25rem] shrink-0 gap-2.5 bg-background/80 px-4 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 sm:pl-6 sm:pr-3 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55`
|
||||
: "relative z-50 min-h-[3.25rem] shrink-0 gap-3 bg-background/80 px-5 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
|
||||
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-13 min-h-13 shrink-0 gap-2.5 bg-background/80 ${auxiliaryPanelHeaderPaddingClass} backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55`
|
||||
: `relative z-50 min-h-13 shrink-0 gap-3 bg-background/80 ${auxiliaryPanelHeaderPaddingClass} backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55`,
|
||||
)}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
|
||||
@@ -54,6 +54,7 @@ import { Input } from "@/shared/ui/input";
|
||||
import { Textarea } from "@/shared/ui/textarea";
|
||||
import {
|
||||
AuxiliaryPanelHeader,
|
||||
AuxiliaryPanelHeaderActions,
|
||||
AuxiliaryPanelHeaderGroup,
|
||||
AuxiliaryPanelTitle,
|
||||
auxiliaryPanelContentPaddingClass,
|
||||
@@ -320,7 +321,7 @@ export function ChannelManagementSheet({
|
||||
"h-full w-full cursor-default overflow-hidden border-l-0 p-0",
|
||||
animateSplitEnter && PANEL_ENTER_MOTION_CLASS,
|
||||
isDark
|
||||
? "bg-background/85 backdrop-blur-xl supports-[backdrop-filter]:bg-background/75"
|
||||
? "bg-background/85 backdrop-blur-xl supports-backdrop-filter:bg-background/75"
|
||||
: "bg-background",
|
||||
)}
|
||||
data-testid="channel-management-sheet"
|
||||
@@ -368,7 +369,7 @@ export function ChannelManagementSheet({
|
||||
PANEL_ENTER_MOTION_CLASS,
|
||||
"w-[380px] cursor-default overflow-hidden p-0 data-[state=closed]:animate-out data-[state=closed]:slide-out-to-right data-[state=closed]:duration-200",
|
||||
isDark
|
||||
? "bg-background/85 backdrop-blur-xl supports-[backdrop-filter]:bg-background/75"
|
||||
? "bg-background/85 backdrop-blur-xl supports-backdrop-filter:bg-background/75"
|
||||
: "bg-background",
|
||||
)}
|
||||
data-testid="channel-management-sheet"
|
||||
@@ -681,30 +682,25 @@ function ChannelManagementPanelContent({
|
||||
<>
|
||||
{isSplitLayout ? (
|
||||
<AuxiliaryPanelHeader>
|
||||
<AuxiliaryPanelHeaderGroup>
|
||||
{activeView === "canvas" ? (
|
||||
<Button
|
||||
aria-label="Back to channel"
|
||||
className="shrink-0"
|
||||
data-testid="channel-management-back"
|
||||
onClick={() => setActiveView("summary")}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<ChevronLeft />
|
||||
</Button>
|
||||
) : null}
|
||||
<AuxiliaryPanelHeaderGroup
|
||||
backButtonAriaLabel="Back to channel"
|
||||
backButtonTestId="channel-management-back"
|
||||
onBack={
|
||||
activeView === "canvas"
|
||||
? () => setActiveView("summary")
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<DialogPrimitive.Title asChild>
|
||||
<AuxiliaryPanelTitle>
|
||||
{activeView === "canvas" ? "Canvas" : "Channel"}
|
||||
</AuxiliaryPanelTitle>
|
||||
</DialogPrimitive.Title>
|
||||
</AuxiliaryPanelHeaderGroup>
|
||||
<div className="ml-auto flex shrink-0 items-center gap-2">
|
||||
<AuxiliaryPanelHeaderActions>
|
||||
<Button
|
||||
aria-label="Close channel management"
|
||||
className="relative z-[60]"
|
||||
className="relative z-60"
|
||||
data-testid="channel-management-close"
|
||||
onClick={() => onOpenChange(false)}
|
||||
onPointerDown={(event) => {
|
||||
@@ -718,7 +714,7 @@ function ChannelManagementPanelContent({
|
||||
>
|
||||
<X />
|
||||
</Button>
|
||||
</div>
|
||||
</AuxiliaryPanelHeaderActions>
|
||||
<DialogPrimitive.Description className="sr-only">
|
||||
Channel settings
|
||||
</DialogPrimitive.Description>
|
||||
@@ -728,8 +724,8 @@ function ChannelManagementPanelContent({
|
||||
className={cn(
|
||||
"relative z-10 flex min-h-11 flex-row items-center gap-3 space-y-0 border-b border-border/35 px-3 py-1.5 text-left shadow-none",
|
||||
isDark
|
||||
? "bg-background/70 backdrop-blur-xl supports-[backdrop-filter]:bg-background/55"
|
||||
: "bg-background/80 backdrop-blur-md supports-[backdrop-filter]:bg-background/70",
|
||||
? "bg-background/70 backdrop-blur-xl supports-backdrop-filter:bg-background/55"
|
||||
: "bg-background/80 backdrop-blur-md supports-backdrop-filter:bg-background/70",
|
||||
)}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-1.5">
|
||||
@@ -751,7 +747,7 @@ function ChannelManagementPanelContent({
|
||||
</div>
|
||||
<Button
|
||||
aria-label="Close channel management"
|
||||
className="relative z-[60]"
|
||||
className="relative z-60"
|
||||
data-testid="channel-management-close"
|
||||
onClick={() => onOpenChange(false)}
|
||||
onPointerDown={(event) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { ArrowDown, ArrowLeft, X } from "lucide-react";
|
||||
import { ArrowDown, X } from "lucide-react";
|
||||
|
||||
import {
|
||||
buildThreadSummaryFromVisibleEntries,
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
AuxiliaryPanelHeaderGroup,
|
||||
AuxiliaryPanelTitle,
|
||||
auxiliaryPanelContentPaddingClass,
|
||||
auxiliaryPanelHeaderPaddingClass,
|
||||
} from "@/shared/layout/AuxiliaryPanelHeader";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
@@ -233,19 +234,10 @@ export function MessageThreadPanelSkeleton({
|
||||
|
||||
const threadHeaderContent = (
|
||||
<>
|
||||
<AuxiliaryPanelHeaderGroup>
|
||||
{isSinglePanelView ? (
|
||||
<Button
|
||||
aria-label="Back to conversation"
|
||||
className="shrink-0"
|
||||
onClick={onClose}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
) : null}
|
||||
<AuxiliaryPanelHeaderGroup
|
||||
backButtonAriaLabel="Back to conversation"
|
||||
onBack={isSinglePanelView ? onClose : undefined}
|
||||
>
|
||||
<AuxiliaryPanelTitle>Thread</AuxiliaryPanelTitle>
|
||||
</AuxiliaryPanelHeaderGroup>
|
||||
<Button
|
||||
@@ -266,7 +258,7 @@ export function MessageThreadPanelSkeleton({
|
||||
className={cn(
|
||||
"min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain pb-24 [overflow-anchor:none]",
|
||||
isSplitLayout && auxiliaryPanelContentPaddingClass,
|
||||
!isSplitLayout && !isFloatingOverlay && "pt-[3.25rem]",
|
||||
!isSplitLayout && !isFloatingOverlay && "pt-13",
|
||||
)}
|
||||
data-testid="message-thread-loading"
|
||||
>
|
||||
@@ -323,8 +315,8 @@ export function MessageThreadPanelSkeleton({
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center",
|
||||
isSinglePanelView
|
||||
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-[3.25rem] min-h-[3.25rem] shrink-0 gap-2.5 bg-background/80 px-4 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 sm:pr-3 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55`
|
||||
: "relative z-50 min-h-[3.25rem] shrink-0 gap-3 bg-background/80 px-5 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
|
||||
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-13 min-h-13 shrink-0 gap-2.5 bg-background/80 ${auxiliaryPanelHeaderPaddingClass} backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55`
|
||||
: `relative z-50 min-h-13 shrink-0 gap-3 bg-background/80 ${auxiliaryPanelHeaderPaddingClass} backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55`,
|
||||
)}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
@@ -614,7 +606,7 @@ export function MessageThreadPanel({
|
||||
className={cn(
|
||||
"min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain pb-24 [overflow-anchor:none]",
|
||||
isSplitLayout && auxiliaryPanelContentPaddingClass,
|
||||
!isSplitLayout && !isFloatingOverlay && "pt-[3.25rem]",
|
||||
!isSplitLayout && !isFloatingOverlay && "pt-13",
|
||||
)}
|
||||
data-testid="message-thread-body"
|
||||
onScroll={onScroll}
|
||||
@@ -910,20 +902,11 @@ export function MessageThreadPanel({
|
||||
|
||||
const threadHeaderContent = (
|
||||
<>
|
||||
<AuxiliaryPanelHeaderGroup>
|
||||
{isSinglePanelView ? (
|
||||
<Button
|
||||
aria-label="Back to conversation"
|
||||
className="shrink-0"
|
||||
data-testid="message-thread-back"
|
||||
onClick={onClose}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
) : null}
|
||||
<AuxiliaryPanelHeaderGroup
|
||||
backButtonAriaLabel="Back to conversation"
|
||||
backButtonTestId="message-thread-back"
|
||||
onBack={isSinglePanelView ? onClose : undefined}
|
||||
>
|
||||
<AuxiliaryPanelTitle>Thread</AuxiliaryPanelTitle>
|
||||
</AuxiliaryPanelHeaderGroup>
|
||||
<Button
|
||||
@@ -970,8 +953,8 @@ export function MessageThreadPanel({
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center",
|
||||
isSinglePanelView
|
||||
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-[3.25rem] min-h-[3.25rem] shrink-0 gap-2.5 bg-background/80 px-4 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 sm:pr-3 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55`
|
||||
: "relative z-50 min-h-[3.25rem] shrink-0 gap-3 bg-background/80 px-5 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
|
||||
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-13 min-h-13 shrink-0 gap-2.5 bg-background/80 ${auxiliaryPanelHeaderPaddingClass} backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55`
|
||||
: `relative z-50 min-h-13 shrink-0 gap-3 bg-background/80 ${auxiliaryPanelHeaderPaddingClass} backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55`,
|
||||
)}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type * as React from "react";
|
||||
|
||||
import { THREAD_PANEL_MIN_WIDTH_PX } from "@/shared/hooks/useThreadPanelWidth";
|
||||
import { AuxiliaryPanelHeader } from "@/shared/layout/AuxiliaryPanelHeader";
|
||||
import {
|
||||
AuxiliaryPanelHeader,
|
||||
auxiliaryPanelHeaderPaddingClass,
|
||||
} from "@/shared/layout/AuxiliaryPanelHeader";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import {
|
||||
OverlayPanelBackdrop,
|
||||
@@ -111,10 +114,10 @@ export function UserProfilePanelFrame({
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center",
|
||||
isSinglePanelView
|
||||
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-13 min-h-13 shrink-0 gap-2.5 bg-transparent px-4 py-2 sm:pl-6 sm:pr-3`
|
||||
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-13 min-h-13 shrink-0 gap-2.5 bg-transparent ${auxiliaryPanelHeaderPaddingClass}`
|
||||
: isOverlay
|
||||
? "relative z-50 min-h-13 shrink-0 gap-3 bg-background/80 px-5 py-2 backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55"
|
||||
: "absolute inset-x-0 top-0 z-50 min-h-13 gap-3 bg-transparent px-3 py-2 after:absolute after:bottom-0 after:-left-px after:top-0 after:w-px after:bg-border/45 after:transition-colors peer-hover/profile-resize:after:bg-border/80 peer-focus-visible/profile-resize:after:bg-border/80",
|
||||
? `relative z-50 min-h-13 shrink-0 gap-3 bg-background/80 ${auxiliaryPanelHeaderPaddingClass} backdrop-blur-md supports-backdrop-filter:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-backdrop-filter:bg-background/55`
|
||||
: `absolute inset-x-0 top-0 z-50 min-h-13 gap-3 bg-transparent ${auxiliaryPanelHeaderPaddingClass} after:absolute after:bottom-0 after:-left-px after:top-0 after:w-px after:bg-border/45 after:transition-colors peer-hover/profile-resize:after:bg-border/80 peer-focus-visible/profile-resize:after:bg-border/80`,
|
||||
)}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { ArrowLeft, X } from "lucide-react";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { CopyButton } from "@/features/agents/ui/CopyButton";
|
||||
import { MemoryRefreshButton } from "@/features/agent-memory/ui/MemorySection";
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type ProfilePanelView,
|
||||
} from "@/features/profile/ui/UserProfilePanelUtils";
|
||||
import {
|
||||
AuxiliaryPanelHeaderActions,
|
||||
AuxiliaryPanelHeaderGroup,
|
||||
AuxiliaryPanelTitle,
|
||||
} from "@/shared/layout/AuxiliaryPanelHeader";
|
||||
@@ -37,21 +38,11 @@ export function getUserProfilePanelHeaderContent({
|
||||
(view === "diagnostics" || view === "logs") && Boolean(logSubtitle);
|
||||
const headerLeftContent = (
|
||||
<AuxiliaryPanelHeaderGroup
|
||||
backButtonAriaLabel="Back to profile"
|
||||
backButtonTestId="user-profile-panel-back"
|
||||
className={shouldShowLogDetails ? "items-start" : undefined}
|
||||
onBack={view !== "summary" ? onBack : undefined}
|
||||
>
|
||||
{view !== "summary" ? (
|
||||
<Button
|
||||
aria-label="Back to profile"
|
||||
className={shouldShowLogDetails ? "mt-0.5 shrink-0" : "shrink-0"}
|
||||
data-testid="user-profile-panel-back"
|
||||
onClick={onBack}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
) : null}
|
||||
{shouldShowLogDetails ? (
|
||||
<div className="min-w-0 flex-1">
|
||||
<AuxiliaryPanelTitle className="translate-y-0 leading-5">
|
||||
@@ -70,7 +61,7 @@ export function getUserProfilePanelHeaderContent({
|
||||
</AuxiliaryPanelHeaderGroup>
|
||||
);
|
||||
const headerActions = (
|
||||
<div className="ml-auto flex shrink-0 items-center gap-2">
|
||||
<AuxiliaryPanelHeaderActions>
|
||||
{view === "memories" && viewerIsOwner && effectivePubkey ? (
|
||||
<MemoryRefreshButton
|
||||
agentPubkey={effectivePubkey}
|
||||
@@ -99,7 +90,7 @@ export function getUserProfilePanelHeaderContent({
|
||||
>
|
||||
<X />
|
||||
</Button>
|
||||
</div>
|
||||
</AuxiliaryPanelHeaderActions>
|
||||
);
|
||||
|
||||
return { headerActions, headerLeftContent };
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import type * as React from "react";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
||||
import { channelChrome } from "@/shared/layout/chromeLayout";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
|
||||
type AuxiliaryPanelHeaderProps = React.ComponentProps<"div">;
|
||||
type AuxiliaryPanelHeaderGroupProps = React.ComponentProps<"div">;
|
||||
type AuxiliaryPanelHeaderGroupProps = React.ComponentProps<"div"> & {
|
||||
backButtonAriaLabel?: string;
|
||||
backButtonTestId?: string;
|
||||
onBack?: () => void;
|
||||
};
|
||||
type AuxiliaryPanelHeaderActionsProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
type AuxiliaryPanelTitleProps = React.ComponentProps<"h2">;
|
||||
|
||||
export const auxiliaryPanelHeaderRightPaddingClass = "pr-2";
|
||||
export const auxiliaryPanelHeaderPaddingClass = `pl-5 ${auxiliaryPanelHeaderRightPaddingClass} py-2`;
|
||||
|
||||
/** Compact title/action row for right auxiliary panels in split layouts. */
|
||||
export function AuxiliaryPanelHeader({
|
||||
className,
|
||||
@@ -23,7 +35,10 @@ export function AuxiliaryPanelHeader({
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className="pointer-events-auto relative z-30 shrink-0 cursor-default select-none px-5 py-2"
|
||||
className={cn(
|
||||
"pointer-events-auto relative z-30 shrink-0 cursor-default select-none",
|
||||
auxiliaryPanelHeaderPaddingClass,
|
||||
)}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div className="flex h-9 min-w-0 items-center gap-2.5">{children}</div>
|
||||
@@ -35,8 +50,11 @@ export function AuxiliaryPanelHeader({
|
||||
export const auxiliaryPanelContentPaddingClass = channelChrome.contentPadding;
|
||||
|
||||
export function AuxiliaryPanelHeaderGroup({
|
||||
backButtonAriaLabel = "Back",
|
||||
backButtonTestId,
|
||||
className,
|
||||
children,
|
||||
onBack,
|
||||
...props
|
||||
}: AuxiliaryPanelHeaderGroupProps) {
|
||||
return (
|
||||
@@ -44,11 +62,35 @@ export function AuxiliaryPanelHeaderGroup({
|
||||
className={cn("flex min-w-0 flex-1 items-center gap-1.5", className)}
|
||||
{...props}
|
||||
>
|
||||
{onBack ? (
|
||||
<Button
|
||||
aria-label={backButtonAriaLabel}
|
||||
// Header text needs a comfortable left inset, but a leading icon
|
||||
// should visually sit closer to the panel edge. Keep the padding
|
||||
// centralized on the header and pull only this shared button back.
|
||||
className="-ml-2 shrink-0"
|
||||
data-testid={backButtonTestId}
|
||||
onClick={onBack}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
) : null}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AuxiliaryPanelHeaderActions({
|
||||
children,
|
||||
}: AuxiliaryPanelHeaderActionsProps) {
|
||||
return (
|
||||
<div className="ml-auto flex shrink-0 items-center gap-0.5">{children}</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AuxiliaryPanelTitle({
|
||||
className,
|
||||
children,
|
||||
|
||||
Reference in New Issue
Block a user