From 7604c6def49f4a205f3f5dc9bdce62d7125aa533 Mon Sep 17 00:00:00 2001 From: Alan Lail Date: Thu, 24 Sep 2026 12:34:31 -0400 Subject: [PATCH] Repositions the Add buttons located on the bottom of each panel to the top --- .../ui/editor/treePanel/FrameworkTreeList.tsx | 10 +++ .../src/ui/editor/treePanel/TreePanelView.tsx | 65 ++++++++++++------- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/apps/editor/src/ui/editor/treePanel/FrameworkTreeList.tsx b/apps/editor/src/ui/editor/treePanel/FrameworkTreeList.tsx index cc4b2a5..158e385 100644 --- a/apps/editor/src/ui/editor/treePanel/FrameworkTreeList.tsx +++ b/apps/editor/src/ui/editor/treePanel/FrameworkTreeList.tsx @@ -19,6 +19,8 @@ type Props = { noHeader?: boolean /** Optional extra content rendered inside the header (e.g. a framework selector dropdown) */ headerSlot?: React.ReactNode + /** Optional content rendered below the header, above the scrollable tree (e.g. an "Add item" action) */ + belowHeaderSlot?: React.ReactNode onSelect?: (_id: string) => void onAddChild?: (_parentId: string) => void onAddRoot?: () => void @@ -50,6 +52,7 @@ export default function FrameworkTreeList({ onToggleExpand, noHeader, headerSlot, + belowHeaderSlot, onSelect, onAddChild, onAddRoot, @@ -69,6 +72,7 @@ export default function FrameworkTreeList({
{!noHeader &&
} + {belowHeaderSlot ? ( +
+ {belowHeaderSlot} +
+ ) : null} +
{roots.length === 0 ? (
diff --git a/apps/editor/src/ui/editor/treePanel/TreePanelView.tsx b/apps/editor/src/ui/editor/treePanel/TreePanelView.tsx index 38a3486..bdb8942 100644 --- a/apps/editor/src/ui/editor/treePanel/TreePanelView.tsx +++ b/apps/editor/src/ui/editor/treePanel/TreePanelView.tsx @@ -237,6 +237,11 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor const leftPanelRef = useRef(null) const rightPanelRef = useRef(null) + // Right panel's static header has no variable content, so it's kept in sync with the + // left panel's header height (which varies with title/description/publisher) instead + // of hard-coding a height — that way the "Add" buttons below each header stay aligned. + const [leftHeaderHeight, setLeftHeaderHeight] = useState(undefined) + // Stable refs for values used inside recalculateLines (avoids stale closures) const pendingRef = useRef(pendingAssociations) pendingRef.current = pendingAssociations @@ -444,6 +449,19 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor } }, [recalculateLines, expandedTargetId]) + // Track the left panel's header height so the right panel's header can match it + useEffect(() => { + const header = leftPanelRef.current?.querySelector('[data-tree-panel-header]') + if (!header) return + + const sync = () => setLeftHeaderHeight(header.getBoundingClientRect().height) + sync() + + const obs = new ResizeObserver(sync) + obs.observe(header) + return () => obs.disconnect() + }, [frameworkInfo.title, cfDocument?.description, cfDocument?.publisher]) + // Close popover on outside click useEffect(() => { if (!popover) return @@ -876,6 +894,16 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor onDragStart={NOOP_DRAG_START} associationCounts={leftAssociationCounts} onBadgeClick={handleLeftBadgeClick} + belowHeaderSlot={( + + )} />
{/* Gate callout — shown only when a target is expanded but source hasn't been saved */} @@ -887,26 +915,30 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor

)} -
- -
{/* Right panel — accordion list of target frameworks */}
-
+

Associated Frameworks

+
+ +
+
{targets.length === 0 ? (
@@ -1003,17 +1035,6 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor }) )}
- -
- -