feat(desktop): let a pane prefix the zone tab menu

A Browser tab needs one extra verb without replacing Reload / Close. Give
the pane a tabMenuPrefix slot the strip menu renders when it opens.
This commit is contained in:
Brooklyn Nicholson
2026-08-25 16:34:31 -05:00
committed by brooklyn!
parent 7c5c994397
commit b9513f2e1d
3 changed files with 17 additions and 0 deletions
+4
View File
@@ -10,6 +10,7 @@ import type { ReadableAtom } from 'nanostores'
import type { ReactElement, ReactNode, PointerEvent as ReactPointerEvent } from 'react'
import { registerPaneCloser, removeTreePane, treePanesWithPrefix } from '@/components/pane-shell/tree/store'
import type { MenuKit } from '@/components/ui/actions-menu'
import { registry } from '@/contrib/registry'
import type { WorkspaceMode } from '@/contrib/types'
import type { TileDock } from '@/store/session-states'
@@ -52,6 +53,8 @@ export interface PaneMirror<T> {
* Per tile so a mirror can offer it for some of its tabs and not others. */
newTab?: (key: string) => (() => void) | undefined
render: (key: string) => ReactNode
/** Extra rows at the top of the zone tab menu (see PaneChrome.tabMenuPrefix). */
tabMenuPrefix?: (key: string) => ((kit: MenuKit) => ReactNode) | undefined
/** Wrap the tile's TAB (domain context menu — session verbs). */
tabWrap?: (key: string, tab: ReactElement) => ReactNode
/** Override the tile's TAB drag (session drop language: stack/split/link).
@@ -113,6 +116,7 @@ export function paneMirror<T>(cfg: PaneMirror<T>): () => void {
tabDrag: cfg.tabDrag
? (event: ReactPointerEvent<HTMLElement>, onTap: () => void) => cfg.tabDrag!(key, event, onTap)
: undefined, // returns boolean (handled) — see PaneChrome.tabDrag
tabMenuPrefix: cfg.tabMenuPrefix?.(key),
tabWrap: cfg.tabWrap ? (tab: ReactElement) => cfg.tabWrap!(key, tab) : undefined
},
render: () => cfg.render(key),
@@ -9,6 +9,7 @@
import type * as React from 'react'
import type { MenuKit } from '@/components/ui/actions-menu'
import type { Contribution } from '@/contrib/types'
import type { GroupNode, LayoutNode } from '../model'
@@ -75,6 +76,10 @@ interface PaneChrome extends PaneSizing {
* pin/branch/rename/archive/delete). The wrapper must render `tab` as its
* interactive child; the zone's own strip menu still owns non-tab space. */
tabWrap?: (tab: React.ReactElement) => React.ReactNode
/** Extra rows at the top of the zone tab menu. Called when the menu opens
* against the right-clicked pane — a Browser tab's Open-in-external, without
* replacing Reload / Close / the strip. */
tabMenuPrefix?: (kit: MenuKit) => React.ReactNode
/** Override this pane's TAB drag (a session tab drags like a sidebar row —
* stack / split / composer-link — not the generic pane move). Given the
* tab's tap (activate) so that gesture survives. Returns whether it took the
@@ -97,6 +97,7 @@ function ZoneMenu({
minimized,
nodeId,
stripVisible,
tabMenuPrefix,
targetPane
}: {
children: ReactNode
@@ -111,6 +112,8 @@ function ZoneMenu({
/** Whether the strip is on screen — the Hide/Show row toggles against what
* the user can see, not against the stored mode (a zone on auto has none). */
stripVisible?: boolean
/** Domain verbs for the right-clicked pane, resolved when the menu opens. */
tabMenuPrefix?: (kit: MenuKit) => ReactNode
/** The right-clicked chip (else the active pane) — what the close-others /
* to-the-right / all verbs measure from. Called when the menu RENDERS, not
* on every zone re-render: resolving the siblings reads the layout tree,
@@ -131,8 +134,12 @@ function ZoneMenu({
const paneId = closable?.()
const targetId = targetPane()
const prefix = tabMenuPrefix?.(kit)
return (
<>
{prefix}
{prefix ? <kit.Separator /> : null}
{renderActionItem(kit, {
icon: 'refresh',
label: t.zones.reload,
@@ -394,6 +401,7 @@ export function TreeGroup({
minimized: node.minimized,
nodeId: node.id,
stripVisible,
tabMenuPrefix: (kit: MenuKit) => paneChrome(paneFor(targetPane())).tabMenuPrefix?.(kit),
targetPane
}