Files
buzz/desktop/src/shared/layout/AuxiliaryPanelHeader.tsx
Taylor Ho e6738c5015 refactor(desktop): centralize auxiliary panel shell (#1343)
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
2026-06-29 16:19:48 -06:00

372 lines
10 KiB
TypeScript

import * as React from "react";
import { ArrowLeft, X } from "lucide-react";
import { channelChrome } from "@/shared/layout/chromeLayout";
import { AuxiliaryPanelContext } from "@/shared/layout/auxiliaryPanelContext";
import type { AuxiliaryPanelMode } from "@/shared/layout/auxiliaryPanelContext";
import { cn } from "@/shared/lib/cn";
import { Button } from "@/shared/ui/button";
export type { AuxiliaryPanelMode } from "@/shared/layout/auxiliaryPanelContext";
type AuxiliaryPanelHeaderProps = Omit<
React.ComponentProps<"div">,
"className"
> & {
backdrop?: boolean;
backdropSurface?: AuxiliaryPanelSurface;
bordered?: boolean;
density?: "comfortable" | "compact";
inset?: "default" | "wide";
mode?: AuxiliaryPanelMode;
resizeBorder?: boolean;
surface?: AuxiliaryPanelSurface;
/** Render header content without its own backdrop for a shared parent chrome. */
transparent?: boolean;
};
type AuxiliaryPanelHeaderGroupProps = Omit<
React.ComponentProps<"div">,
"className"
> & {
align?: "center" | "start";
backButtonAriaLabel?: string;
backButtonTestId?: string;
mode?: AuxiliaryPanelMode;
onBack?: () => void;
};
type AuxiliaryPanelHeaderActionsProps = {
children?: React.ReactNode;
includeCloseAction?: boolean;
};
type AuxiliaryPanelHeaderTitleBlockProps = {
subtitle?: React.ReactNode;
subtitleTitle?: string;
title: React.ReactNode;
};
type AuxiliaryPanelTitleProps = Omit<React.ComponentProps<"h2">, "className">;
type AuxiliaryPanelTitleContentProps = React.ComponentProps<"h2">;
type AuxiliaryPanelSurface = "default" | "soft" | "transparent";
const AUXILIARY_PANEL_HEADER_HEIGHT_CLASS = "pt-13";
const AUXILIARY_PANEL_CLOSE_LABEL = "Close panel";
const AUXILIARY_PANEL_CLOSE_TEST_ID = "auxiliary-panel-close";
const AUXILIARY_PANEL_RESIZE_BORDER_CLASS =
"after:absolute after:bottom-0 after:-left-px after:top-0 after:w-px after:bg-border/45 after:transition-colors peer-hover/auxiliary-panel-resize:after:bg-border/80 peer-focus-visible/auxiliary-panel-resize:after:bg-border/80";
export function getAuxiliaryPanelMode(
isSplitLayout: boolean,
isFloatingOverlay: boolean,
): AuxiliaryPanelMode {
if (isSplitLayout) {
return "docked";
}
return isFloatingOverlay ? "panel" : "single-panel";
}
function getAuxiliaryPanelSurfaceClass(surface: AuxiliaryPanelSurface) {
if (surface === "transparent") {
return "bg-transparent";
}
if (surface === "soft") {
return "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";
}
return "bg-background/80 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55";
}
type AuxiliaryPanelHeaderBackdropProps = {
surface: Exclude<AuxiliaryPanelSurface, "transparent">;
};
function AuxiliaryPanelHeaderBackdrop({
surface,
}: AuxiliaryPanelHeaderBackdropProps) {
return (
<div
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-x-0 top-0 z-40 h-13",
getAuxiliaryPanelSurfaceClass(surface),
)}
/>
);
}
/** Title/action row for right auxiliary panels across docked and standalone modes. */
export function AuxiliaryPanelHeader({
backdrop = false,
backdropSurface = "default",
bordered = false,
children,
density = "comfortable",
inset = "default",
mode,
resizeBorder = false,
surface = "default",
transparent,
...props
}: AuxiliaryPanelHeaderProps) {
const panelContext = React.useContext(AuxiliaryPanelContext);
const resolvedMode = mode ?? panelContext?.mode ?? "docked";
const resolvedTransparent =
transparent ?? panelContext?.transparentChrome ?? false;
if (resolvedMode !== "docked") {
const isSinglePanel = resolvedMode === "single-panel";
const effectiveSurface = resolvedTransparent ? "transparent" : surface;
return (
<>
{backdrop && backdropSurface !== "transparent" ? (
<AuxiliaryPanelHeaderBackdrop surface={backdropSurface} />
) : null}
<div
className={cn(
"flex cursor-default select-none items-center",
isSinglePanel
? cn(
"relative z-41 -mb-13 min-h-13 shrink-0 gap-2.5 px-4 py-2 sm:pr-3",
inset === "wide" && "sm:pl-6",
resizeBorder && AUXILIARY_PANEL_RESIZE_BORDER_CLASS,
getAuxiliaryPanelSurfaceClass(effectiveSurface),
)
: resizeBorder
? cn(
"absolute inset-x-0 top-0 z-50 min-h-13 gap-3 bg-transparent px-3 py-2",
AUXILIARY_PANEL_RESIZE_BORDER_CLASS,
)
: cn(
"relative z-50 shrink-0 gap-3",
density === "compact"
? "min-h-11 px-3 py-1.5 text-left shadow-none"
: "min-h-13 px-5 py-2",
inset === "wide" && "sm:pl-6",
bordered && "border-b border-border/35",
getAuxiliaryPanelSurfaceClass(effectiveSurface),
),
)}
data-tauri-drag-region
{...props}
>
{renderAuxiliaryPanelHeaderContent(children)}
</div>
</>
);
}
return (
<div
className={cn(
"pointer-events-none relative z-40 overflow-visible",
getAuxiliaryPanelSurfaceClass(
resolvedTransparent ? "transparent" : surface,
),
channelChrome.negativeMargin,
)}
{...props}
>
<div
className="pointer-events-auto relative z-40 shrink-0 cursor-default select-none py-2 pl-5 pr-3"
data-tauri-drag-region
>
<div className="flex h-9 min-w-0 items-center gap-2.5">
{renderAuxiliaryPanelHeaderContent(children)}
</div>
</div>
</div>
);
}
function renderAuxiliaryPanelHeaderContent(children: React.ReactNode) {
const { foundActions, content } = attachCloseActionToHeaderActions(children);
if (foundActions) {
return content;
}
return (
<>
{children}
<AuxiliaryPanelHeaderActions includeCloseAction />
</>
);
}
function attachCloseActionToHeaderActions(children: React.ReactNode): {
content: React.ReactNode;
foundActions: boolean;
} {
let foundActions = false;
const content = React.Children.map(children, (child) => {
if (!React.isValidElement<AuxiliaryPanelHeaderActionsProps>(child)) {
return child;
}
if (child.type === AuxiliaryPanelHeaderActions) {
foundActions = true;
return React.cloneElement(child, { includeCloseAction: true });
}
if (child.type === React.Fragment) {
const nested = attachCloseActionToHeaderActions(child.props.children);
if (!nested.foundActions) {
return child;
}
foundActions = true;
return React.cloneElement(child, undefined, nested.content);
}
return child;
});
return { content, foundActions };
}
export function getAuxiliaryPanelBodyClass({
isFloatingOverlay = false,
isSplitLayout = false,
mode,
}: {
isFloatingOverlay?: boolean;
isSplitLayout?: boolean;
mode?: AuxiliaryPanelMode;
}) {
const resolvedMode =
mode ?? getAuxiliaryPanelMode(isSplitLayout, isFloatingOverlay);
return cn(
resolvedMode === "docked" && channelChrome.contentPadding,
resolvedMode === "single-panel" && AUXILIARY_PANEL_HEADER_HEIGHT_CLASS,
);
}
export function AuxiliaryPanelHeaderGroup({
align = "center",
backButtonAriaLabel = "Back",
backButtonTestId,
mode,
children,
onBack,
...props
}: AuxiliaryPanelHeaderGroupProps) {
const panelContext = React.useContext(AuxiliaryPanelContext);
const resolvedMode = mode ?? panelContext?.mode ?? "docked";
const isOverlayLayout = resolvedMode === "panel";
return (
<div
className={cn(
"flex min-w-0 flex-1 gap-1.5",
align === "start" ? "items-start" : "items-center",
)}
{...props}
>
{onBack ? (
<Button
aria-label={backButtonAriaLabel}
// Header text needs a comfortable left inset in split layouts, but a
// leading icon should visually sit closer to the panel edge. Overlay
// headers already use compact row padding, so keep that button flush.
className={cn("shrink-0", isOverlayLayout ? "ml-0" : "-ml-2")}
data-testid={backButtonTestId}
onClick={onBack}
size="icon"
type="button"
variant={isOverlayLayout ? "ghost" : "outline"}
>
<ArrowLeft />
</Button>
) : null}
{children}
</div>
);
}
export function AuxiliaryPanelHeaderActions({
children,
includeCloseAction = false,
}: AuxiliaryPanelHeaderActionsProps) {
if (!children && !includeCloseAction) {
return null;
}
return (
<div className="ml-auto flex shrink-0 items-center gap-0.5">
{children}
{includeCloseAction ? <AuxiliaryPanelHeaderCloseAction /> : null}
</div>
);
}
function AuxiliaryPanelHeaderCloseAction() {
const panelContext = React.useContext(AuxiliaryPanelContext);
if (!panelContext?.onClose) {
return null;
}
return (
<Button
aria-label={AUXILIARY_PANEL_CLOSE_LABEL}
className="shrink-0"
data-testid={AUXILIARY_PANEL_CLOSE_TEST_ID}
onClick={panelContext.onClose}
size="icon"
type="button"
variant="ghost"
>
<X />
</Button>
);
}
export function AuxiliaryPanelHeaderTitleBlock({
subtitle,
subtitleTitle,
title,
}: AuxiliaryPanelHeaderTitleBlockProps) {
if (!subtitle) {
return <AuxiliaryPanelTitle>{title}</AuxiliaryPanelTitle>;
}
return (
<div className="min-w-0 flex-1">
<AuxiliaryPanelTitleContent className="translate-y-0 leading-5">
{title}
</AuxiliaryPanelTitleContent>
<p
className="min-w-0 truncate font-mono text-2xs text-muted-foreground"
title={subtitleTitle}
>
{subtitle}
</p>
</div>
);
}
export function AuxiliaryPanelTitle(props: AuxiliaryPanelTitleProps) {
return <AuxiliaryPanelTitleContent {...props} />;
}
function AuxiliaryPanelTitleContent({
className,
children,
...props
}: AuxiliaryPanelTitleContentProps) {
return (
<h2
className={cn(
"min-w-0 flex-1 translate-y-px truncate text-base font-semibold leading-6 tracking-tight",
className,
)}
{...props}
>
{children}
</h2>
);
}