Files
multica/packages/views/plugins/plugin-panel-section.tsx
Multica Eveandmultica-agent e3f265ae5a MUL-8280: refactor(issues): trim issue sidebar header actions (#9580)
* MUL-8280: refactor(issues): trim issue sidebar header actions

- Drop settings-only '…' menus from Properties, Quick Actions, Wakeups
  and Plugins; keep the Pull requests menu, which carries a per-issue
  action (keep status on merge).
- Remove the Quick Actions '+' (workspace-level creation belongs in
  Settings) and hide the section again when there are no actions.
- Restore the inline '+ Add property' row with its popover instead of
  the header icon.

Co-authored-by: multica-agent <[email protected]>

* MUL-8280: fix(issues): bound the add property list to the viewport

The Popover that replaced the header DropdownMenu has no height bound,
so a long property catalog pushed its tail options off-screen. Cap it
to --available-height and scroll inside, and add an e2e that wheels to
the last option and picks it.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-27 12:29:04 +08:00

65 lines
2.6 KiB
TypeScript

"use client";
import { useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { pluginInstallationsOptions } from "@multica/core/plugins";
import { useCurrentWorkspace } from "@multica/core/paths";
import { useFeatureEnabled } from "@multica/core/config";
import { PLUGINS_V1_FLAG } from "@multica/core/feature-flags";
import { useT } from "../i18n";
import { isDesktopShell } from "../platform/local-directory";
import { IssueSidebarSectionHeader } from "../issues/components/issue-sidebar-section";
import { PluginSurfaceFrame } from "./plugin-surface-frame";
/**
* Renders every enabled `issue_panel` surface on an issue.
*
* Disabled installations are filtered here AND refused by the Action API — the
* client filter is presentation, the server check is the control. A surface
* left open in a stale tab stops working even though this component never
* re-rendered.
*/
export function PluginPanelSection({ issueId }: { issueId: string }) {
const { t } = useT("issues");
const [open, setOpen] = useState(true);
const platform = isDesktopShell() ? "desktop" : "web";
const workspace = useCurrentWorkspace();
const wsId = workspace?.id ?? "";
const pluginsEnabled = useFeatureEnabled(PLUGINS_V1_FLAG, false);
const { data } = useQuery({ ...pluginInstallationsOptions(wsId), enabled: pluginsEnabled && wsId.length > 0 });
const panels = useMemo(() => {
const installations = data?.plugins ?? [];
return installations
.filter((installation) => installation.enabled === true)
.flatMap((installation) =>
installation.surfaces
.filter((surface) => surface.type === "issue_panel")
// An empty list means "anywhere"; a declared list is a filter, not a
// hint, or a desktop-only panel renders on web anyway.
.filter((surface) => (surface.platforms ?? []).length === 0 || (surface.platforms ?? []).includes(platform))
.map((surface) => ({ installation, surface })),
);
}, [data, platform]);
if (!pluginsEnabled || panels.length === 0) return null;
return (
<section>
<IssueSidebarSectionHeader title={t(($) => $.plugins.panels)} open={open} onOpenChange={setOpen} count={panels.length} />
<div hidden={!open} className="space-y-2 px-2">
{panels.map(({ installation, surface }) => (
<PluginSurfaceFrame
key={`${installation.id}:${surface.key}`}
wsId={wsId}
installation={installation}
surface={surface}
issueId={issueId}
/>
))}
</div>
</section>
);
}