Move the guided report flow to System while ensuring its menu reliably overlays dashboard content. - Portal the shared ReportActionMenu with a defined dropdown/modal stacking scale. - Make System the sole Command Center report home and retain Copy diagnostics as a local action. - Update regression coverage, documentation, and the published package changeset. Files changed: .changeset/fn-8406-report-home-zindex.md | 7 ++ docs/dashboard-guide.md | 4 +- .../dashboard/app/components/ReportActionMenu.css | 5 +- .../dashboard/app/components/ReportActionMenu.tsx | 72 ++++++++++++- .../components/__tests__/ReportActionMenu.test.tsx | 12 +++ .../components/command-center/CommandCenter.css | 32 ------ .../components/command-center/CommandCenter.tsx | 59 ++++------- .../__tests__/CommandCenter.test.tsx | 8 +- .../__tests__/SystemControlsArea.test.tsx | 94 +---------------- .../command-center/areas/SystemControlsArea.tsx | 112 ++++----------------- packages/dashboard/app/styles.css | 11 ++ 11 files changed, 144 insertions(+), 272 deletions(-) Fusion-Task-Id: FN-8406 Fusion-Task-Lineage: 957a77b0-fb04-4024-9506-225fec562f5d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
85 lines
3.5 KiB
TypeScript
85 lines
3.5 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { createPortal } from "react-dom";
|
|
import { Bug, Lightbulb, LifeBuoy, MessageSquare } from "lucide-react";
|
|
import type { ReportActionType } from "@fusion/core";
|
|
import "./ReportActionMenu.css";
|
|
|
|
const actions: Array<{ type: ReportActionType; label: string; Icon: typeof Bug }> = [
|
|
{ type: "bug", label: "Report bug", Icon: Bug },
|
|
{ type: "feedback", label: "Send feedback", Icon: MessageSquare },
|
|
{ type: "idea", label: "Share idea", Icon: Lightbulb },
|
|
{ type: "help", label: "Get help", Icon: LifeBuoy },
|
|
];
|
|
|
|
/** Four guided entry points share the same report pipeline rather than issue textboxes. */
|
|
export function ReportActionMenu({ onSelect }: { onSelect: (action: ReportActionType) => void }) {
|
|
const [open, setOpen] = useState(false);
|
|
const [position, setPosition] = useState<{ top: number; left: number; right: number }>();
|
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
|
|
const reposition = useCallback(() => {
|
|
const rect = triggerRef.current?.getBoundingClientRect();
|
|
if (!rect) return;
|
|
setPosition({ top: rect.bottom, left: rect.left, right: window.innerWidth - rect.right });
|
|
}, []);
|
|
|
|
/*
|
|
FNXC:ReportPipeline 2026-07-19-14:00:
|
|
FN-8406 requires the shared Report menu to escape Command Center and Settings
|
|
scroll owners. Portal it to document.body and keep it fixed to the trigger so
|
|
the guided actions cannot be clipped beneath surrounding dashboard content.
|
|
*/
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
reposition();
|
|
const handlePositionChange = () => reposition();
|
|
window.addEventListener("resize", handlePositionChange);
|
|
window.addEventListener("scroll", handlePositionChange, true);
|
|
return () => {
|
|
window.removeEventListener("resize", handlePositionChange);
|
|
window.removeEventListener("scroll", handlePositionChange, true);
|
|
};
|
|
}, [open, reposition]);
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const closeOnOutsideClick = (event: MouseEvent) => {
|
|
const target = event.target as Node;
|
|
if (triggerRef.current?.contains(target) || menuRef.current?.contains(target)) return;
|
|
setOpen(false);
|
|
};
|
|
const closeOnEscape = (event: KeyboardEvent) => {
|
|
if (event.key === "Escape") {
|
|
setOpen(false);
|
|
triggerRef.current?.focus();
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", closeOnOutsideClick);
|
|
document.addEventListener("keydown", closeOnEscape);
|
|
return () => {
|
|
document.removeEventListener("mousedown", closeOnOutsideClick);
|
|
document.removeEventListener("keydown", closeOnEscape);
|
|
};
|
|
}, [open]);
|
|
|
|
const menu = open && position && typeof document !== "undefined"
|
|
? createPortal(
|
|
<div
|
|
ref={menuRef}
|
|
className="report-action-menu__list report-action-menu__list--portal"
|
|
role="menu"
|
|
style={{ top: position.top, left: position.left, right: position.right }}
|
|
>
|
|
{actions.map(({ type, label, Icon }) => <button className="report-action-menu__item" type="button" role="menuitem" key={type} onClick={() => { setOpen(false); onSelect(type); }}><Icon aria-hidden="true" />{label}</button>)}
|
|
</div>,
|
|
document.body,
|
|
)
|
|
: null;
|
|
|
|
return <div className="report-action-menu">
|
|
<button ref={triggerRef} className="btn btn-secondary" type="button" aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen((value) => !value)}>Report</button>
|
|
{menu}
|
|
</div>;
|
|
}
|