Files
kandev/apps/web/components/task/tab-context-menu.tsx
T
José Almeida 3e676e3bb2 feat(web): double-click tab to toggle maximize (#823)
* feat(web): double-click tab to toggle maximize

Adds a dblclick gesture on dockview tabs that toggles maximize/restore on
the containing group, mirroring the existing maximize button. Preview tabs
keep their promote-to-pin shortcut: first dblclick pins, then dblclick
maximizes. Sidebar group is excluded.

* fix(web): add cursor-pointer to tab wrappers for consistency

* fix(web): add cursor-pointer to plan-tab wrapper

* fix(web): make tab dblclick maximize resilient to layout rebuild

Refactor the dblclick-to-maximize handler so it reads the panel's group ID
and store state at call time instead of from a render-time closure, and
guards against firing while the dockview store is mid-layout-restore.

This addresses oscillation when double-clicking a session tab on a task
that has multiple sessions: the maximize call now stops event propagation,
respects isRestoringLayout, and always uses the live api.group.id, so it
remains correct even after dockview rebuilds groups via fromJSON.
2026-05-05 13:55:46 +01:00

62 lines
2.0 KiB
TypeScript

"use client";
import { useCallback } from "react";
import { DockviewDefaultTab, type IDockviewPanelHeaderProps } from "dockview-react";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "@kandev/ui/context-menu";
import { useTabMaximizeOnDoubleClick } from "./use-tab-maximize";
/** An item in the tab right-click context menu.
* Items are ephemeral — not serialized to the saved layout. */
export type TabContextMenuItem = {
label: string;
onSelect: () => void;
disabled?: boolean;
};
/** Params shape that panels can use to inject context menu items. */
export type TabContextMenuParams = {
contextMenuItems?: TabContextMenuItem[];
};
/** Default tab component — wraps DockviewDefaultTab with a right-click menu.
* Always provides "Close Others". Panels may inject additional items via
* props.params.contextMenuItems. */
export function ContextMenuTab(props: IDockviewPanelHeaderProps) {
const { api, containerApi } = props;
const onDoubleClick = useTabMaximizeOnDoubleClick(api);
const handleCloseOthers = useCallback(() => {
const toClose = api.group.panels.filter(
(p) => p.id !== api.id && p.id !== "chat" && !p.id.startsWith("session:"),
);
for (const panel of toClose) containerApi.removePanel(panel);
}, [api, containerApi]);
const extraItems: TabContextMenuItem[] =
(props.params as TabContextMenuParams | undefined)?.contextMenuItems ?? [];
return (
<ContextMenu>
<ContextMenuTrigger
className="flex h-full items-center cursor-pointer select-none"
onDoubleClick={onDoubleClick}
>
<DockviewDefaultTab {...props} />
</ContextMenuTrigger>
<ContextMenuContent>
{extraItems.map((item) => (
<ContextMenuItem key={item.label} onSelect={item.onSelect} disabled={item.disabled}>
{item.label}
</ContextMenuItem>
))}
<ContextMenuItem onSelect={handleCloseOthers}>Close Others</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
}