From 48368bc4a7b0e2abd4a2c093ba5db96818603f2c Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 27 Jul 2026 20:56:48 -0500 Subject: [PATCH] feat(desktop): right-click parity for every panel list row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Teach PanelListRow a menuItems prop that renders BOTH the hover kebab and a matching right-click menu from one PanelMenuItem[] (via the shared actions-menu primitive), so a panel row's two menus can't drift. Migrate the cron, webhooks, and profiles panels onto it — right-clicking a row now opens the same edit/delete/enable actions as its kebab. --- apps/desktop/src/app/cron/index.tsx | 24 +++---- apps/desktop/src/app/overlays/panel.tsx | 87 ++++++++++++++++--------- apps/desktop/src/app/profiles/index.tsx | 37 +++++------ apps/desktop/src/app/webhooks/index.tsx | 21 +++--- 4 files changed, 92 insertions(+), 77 deletions(-) diff --git a/apps/desktop/src/app/cron/index.tsx b/apps/desktop/src/app/cron/index.tsx index 75e0020a793..a8c74e2a9c6 100644 --- a/apps/desktop/src/app/cron/index.tsx +++ b/apps/desktop/src/app/cron/index.tsx @@ -63,10 +63,10 @@ import { PanelHeader, PanelList, PanelListRow, + type PanelMenuItem, PanelMeta, PanelPill, type PanelPillTone, - PanelRowMenu, PanelSectionLabel } from '../overlays/panel' import type { SetStatusbarItemGroup } from '../shell/statusbar-controls' @@ -501,14 +501,11 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt active={selectedJob?.id === job.id} job={job} key={job.id} - menu={ - setEditor({ mode: 'edit', job }) }, - { icon: 'trash', label: t.common.delete, onSelect: () => setPendingDelete(job), tone: 'danger' } - ]} - /> - } + menuItems={[ + { icon: 'edit', label: c.edit, onSelect: () => setEditor({ mode: 'edit', job }) }, + { icon: 'trash', label: t.common.delete, onSelect: () => setPendingDelete(job), tone: 'danger' } + ]} + menuLabel={c.manage} onSelect={() => setSelectedJobId(job.id)} /> ))} @@ -571,12 +568,14 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt function CronJobListRow({ active, job, - menu, + menuItems, + menuLabel, onSelect }: { active: boolean job: CronJob - menu?: React.ReactNode + menuItems?: PanelMenuItem[] + menuLabel?: string onSelect: () => void }) { const state = jobState(job) @@ -585,7 +584,8 @@ function CronJobListRow({ ). Reveals on hover/focus. + // Per-row actions. Pass `menuItems` to get BOTH the hover kebab and a matching + // right-click menu from one array (preferred). `menu` takes a raw node for the + // rare custom trigger; it gets no right-click parity. menu?: ReactNode + menuItems?: PanelMenuItem[] + // aria/tooltip label for the kebab + right-click menu built from `menuItems`. + menuLabel?: string // Short always-visible trailing meta (a tag/time, like the trace label's duration). meta?: ReactNode onSelect: () => void @@ -160,19 +165,22 @@ interface PanelListRowProps { // A row is a container (not a - - - - {items.map(item => ( - - {item.icon ? : null} - {item.label} - - ))} - - + + + ) } diff --git a/apps/desktop/src/app/profiles/index.tsx b/apps/desktop/src/app/profiles/index.tsx index b04d9c6c32b..6b30a0789f6 100644 --- a/apps/desktop/src/app/profiles/index.tsx +++ b/apps/desktop/src/app/profiles/index.tsx @@ -43,9 +43,9 @@ import { PanelHeader, PanelList, PanelListRow, + type PanelMenuItem, PanelMeta, PanelPill, - PanelRowMenu, PanelSectionLabel } from '../overlays/panel' @@ -197,22 +197,18 @@ export function ProfilesView({ onClose }: ProfilesViewProps) { setPendingRename(profile) }, - { - icon: 'trash', - label: t.common.delete, - onSelect: () => setPendingDelete(profile), - tone: 'danger' - } - ] - } - /> + menuItems={ + profile.is_default + ? [] + : [ + { icon: 'edit', label: p.renameMenu, onSelect: () => setPendingRename(profile) }, + { + icon: 'trash', + label: t.common.delete, + onSelect: () => setPendingDelete(profile), + tone: 'danger' + } + ] } onSelect={() => setSelectedName(profile.name)} profile={profile} @@ -281,12 +277,12 @@ export function ProfilesView({ onClose }: ProfilesViewProps) { function ProfileRow({ active, - menu, + menuItems, onSelect, profile }: { active: boolean - menu?: React.ReactNode + menuItems: PanelMenuItem[] onSelect: () => void profile: ProfileInfo }) { @@ -302,7 +298,8 @@ function ProfileRow({ name={profile.name} /> } - menu={menu} + menuItems={menuItems} + menuLabel={profile.name} onSelect={onSelect} rowKey={profile.name} title={profile.name} diff --git a/apps/desktop/src/app/webhooks/index.tsx b/apps/desktop/src/app/webhooks/index.tsx index c2f86cb29ed..6e9c0fe1263 100644 --- a/apps/desktop/src/app/webhooks/index.tsx +++ b/apps/desktop/src/app/webhooks/index.tsx @@ -49,7 +49,6 @@ import { PanelListRow, PanelMeta, PanelPill, - PanelRowMenu, PanelSectionLabel } from '../overlays/panel' import { ListRow } from '../settings/primitives' @@ -387,18 +386,14 @@ export function WebhooksView({ onClose }: WebhooksViewProps) { active={selectedSub?.name === sub.name} dotClassName={sub.enabled ? 'bg-emerald-500' : 'bg-muted-foreground/50'} key={sub.name} - menu={ - void handleToggle(sub.name, !sub.enabled) - }, - { icon: 'trash', label: w.delete, onSelect: () => setPendingDelete(sub.name), tone: 'danger' } - ]} - /> - } + menuItems={[ + { + icon: sub.enabled ? 'circle-slash' : 'check', + label: sub.enabled ? w.disableRow : w.enableRow, + onSelect: () => void handleToggle(sub.name, !sub.enabled) + }, + { icon: 'trash', label: w.delete, onSelect: () => setPendingDelete(sub.name), tone: 'danger' } + ]} onSelect={() => setSelectedName(sub.name)} title={sub.name} />