Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 16 additions & 21 deletions apps/web/src/components/RightPanelTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
TerminalSquare,
Volume2,
VolumeOff,
X,
} from "lucide-react";
import {
type KeyboardEvent as ReactKeyboardEvent,
Expand All @@ -33,6 +32,7 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip";
import { Kbd } from "~/components/ui/kbd";
import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "~/components/ui/menu";
import { ScrollArea } from "~/components/ui/scroll-area";
import { PanelTabCloseButton } from "~/components/ui/panel-tab-close-button";
import { faviconUrlForOrigin } from "~/lib/favicon";
import { useTheme } from "~/hooks/useTheme";
import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";
Expand Down Expand Up @@ -823,29 +823,24 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
: "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
)}
>
<button
type="button"
className="cursor-pointer group/close relative flex size-4 shrink-0 items-center justify-center rounded-sm hover:bg-muted"
aria-label={`Close ${title}`}
<PanelTabCloseButton
label={`Close ${title}`}
onClick={() => props.onCloseSurface(surface)}
>
<span className="relative flex size-3 items-center justify-center group-hover/tab:hidden group-focus-visible/close:hidden">
<SurfaceIcon
surface={surface}
sessions={props.previewSessions}
desktopByTabId={props.desktopByTabId}
theme={resolvedTheme}
pullRequestStatuses={props.pullRequestStatuses}
<SurfaceIcon
surface={surface}
sessions={props.previewSessions}
desktopByTabId={props.desktopByTabId}
theme={resolvedTheme}
pullRequestStatuses={props.pullRequestStatuses}
/>
{pending ? (
<span
className="absolute -right-0.5 -bottom-0.5 size-1.5 rounded-full bg-current"
aria-hidden
/>
{pending ? (
<span
className="absolute -right-0.5 -bottom-0.5 size-1.5 rounded-full bg-current"
aria-hidden
/>
) : null}
</span>
<X className="hidden size-3 group-hover/tab:block group-focus-visible/close:block" />
</button>
) : null}
</PanelTabCloseButton>
{audio === "none" || !audioRuntimeTabId ? null : (
<Tooltip>
<TooltipTrigger
Expand Down
89 changes: 40 additions & 49 deletions apps/web/src/components/ThreadTerminalDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {
import { type TerminalSessionState } from "@t3tools/client-runtime/state/terminal";
import {
Plus,
Square,
SquareSplitHorizontal,
SquareSplitVertical,
TerminalSquare,
Trash2,
XIcon,
} from "lucide-react";
import {
type ContextMenuItem,
Expand All @@ -33,6 +33,7 @@ import {
} from "react";
import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover";
import { Button } from "~/components/ui/button";
import { PanelTabCloseButton } from "~/components/ui/panel-tab-close-button";
import { readTextFromClipboard, writeTextToClipboard } from "~/hooks/useCopyToClipboard";
import { cn } from "~/lib/utils";
import { type TerminalContextSelection } from "~/lib/terminalContext";
Expand Down Expand Up @@ -1622,85 +1623,75 @@ export default function ThreadTerminalDrawer({
</div>

<div className="min-h-0 flex-1 overflow-y-auto px-1 py-1">
{resolvedTerminalGroups.map((terminalGroup, groupIndex) => {
{resolvedTerminalGroups.map((terminalGroup) => {
const isGroupActive =
terminalGroup.terminalIds.includes(resolvedActiveTerminalId);
const groupActiveTerminalId = isGroupActive
? resolvedActiveTerminalId
: (terminalGroup.terminalIds[0] ?? resolvedActiveTerminalId);
const terminalCount = terminalGroup.terminalIds.length;
const isSplitGroup = terminalCount > 1;
const groupLabel = !isSplitGroup
? "Single"
: terminalGroup.splitDirection === "vertical"
? "Stacked"
: "Side by side";
const GroupIcon = !isSplitGroup
? Square
: terminalGroup.splitDirection === "vertical"
? SquareSplitVertical
: SquareSplitHorizontal;

return (
<div key={terminalGroup.id} className="pb-0.5">
{showGroupHeaders && (
<button
type="button"
className={`flex w-full items-center rounded px-1 py-0.5 text-[10px] uppercase tracking-[0.08em] ${
className={`flex h-[22px] w-full items-center gap-1 rounded px-1.5 text-[11px] ${
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Outdated
isGroupActive
? "bg-accent/70 text-foreground"
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
? "bg-accent/50 text-foreground"
: "text-muted-foreground hover:bg-accent/40 hover:text-foreground"
}`}
onClick={() => onActiveTerminalChange(groupActiveTerminalId)}
>
Group {groupIndex + 1}
<GroupIcon className="size-3 shrink-0" />
<span className="min-w-0 flex-1 truncate text-left">{groupLabel}</span>
<span className="text-muted-foreground/70 text-[10px] tabular-nums">
{terminalCount}
</span>
</button>
)}

<div
className={showGroupHeaders ? "ml-1 border-l border-border/60 pl-1.5" : ""}
>
<div className="flex flex-col gap-0.5">
{terminalGroup.terminalIds.map((terminalId) => {
const isActive = terminalId === resolvedActiveTerminalId;
const closeTerminalLabel = `Close ${
terminalLabelById.get(terminalId) ?? "terminal"
}${isActive && closeShortcutLabel ? ` (${closeShortcutLabel})` : ""}`;
const terminalLabel = terminalLabelById.get(terminalId) ?? "Terminal";
const closeTerminalLabel = `Close ${terminalLabel}${
isActive && closeShortcutLabel ? ` (${closeShortcutLabel})` : ""
}`;
return (
<div
key={terminalId}
className={`group flex items-center gap-1 rounded px-1 py-0.5 text-[11px] ${
className={cn(
"group/tab flex h-6 w-full items-center gap-0.5 rounded-md pr-2 pl-1.5 text-xs",
isActive
? "bg-accent text-foreground"
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
}`}
>
{showGroupHeaders && (
<span className="text-[10px] text-muted-foreground/80">└</span>
: "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
)}
>
<PanelTabCloseButton
label={closeTerminalLabel}
onClick={() => confirmCloseTerminal(terminalId)}
>
<TerminalSquare className="size-3 shrink-0" />
</PanelTabCloseButton>
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
<button
type="button"
className="flex min-w-0 flex-1 items-center gap-1 text-left"
className="flex min-w-0 flex-1 cursor-pointer items-center gap-1 text-left"
onClick={() => onActiveTerminalChange(terminalId)}
>
<TerminalSquare className="size-3 shrink-0" />
<span className="truncate">
{terminalLabelById.get(terminalId) ?? "Terminal"}
</span>
<span className="truncate">{terminalLabel}</span>
</button>
{normalizedTerminalIds.length > 1 && (
<Popover>
<PopoverTrigger
openOnHover
render={
<button
type="button"
className="inline-flex size-3.5 items-center justify-center rounded text-xs font-medium leading-none text-muted-foreground opacity-0 transition hover:bg-accent hover:text-foreground group-hover:opacity-100"
onClick={() => confirmCloseTerminal(terminalId)}
aria-label={closeTerminalLabel}
/>
}
>
<XIcon className="size-2.5" />
</PopoverTrigger>
<PopoverPopup
tooltipStyle
side="bottom"
sideOffset={6}
align="center"
className="pointer-events-none select-none"
>
{closeTerminalLabel}
</PopoverPopup>
</Popover>
)}
</div>
);
})}
Expand Down
25 changes: 25 additions & 0 deletions apps/web/src/components/ui/panel-tab-close-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { X } from "lucide-react";
import type { ReactNode } from "react";

interface PanelTabCloseButtonProps {
children: ReactNode;
label: string;
onClick: () => void;
}

/** Inside a `group/tab` row, swaps the tab identity for its close action on hover or focus. */
export function PanelTabCloseButton({ children, label, onClick }: PanelTabCloseButtonProps) {
return (
<button
type="button"
className="cursor-pointer group/close relative flex size-4 shrink-0 items-center justify-center rounded-sm hover:bg-muted"
aria-label={label}
onClick={onClick}
>
<span className="relative flex size-3 items-center justify-center group-hover/tab:hidden group-focus-visible/close:hidden">
{children}
</span>
<X className="hidden size-3 group-hover/tab:block group-focus-visible/close:block" />
</button>
);
}
Loading