Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/editor/src/ui/editor/treePanel/FrameworkTreeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -50,6 +52,7 @@ export default function FrameworkTreeList({
onToggleExpand,
noHeader,
headerSlot,
belowHeaderSlot,
onSelect,
onAddChild,
onAddRoot,
Expand All @@ -69,6 +72,7 @@ export default function FrameworkTreeList({
<div className="flex h-full flex-col overflow-hidden">
{!noHeader && <button
type="button"
data-tree-panel-header
onClick={() => frameworkNodeId && onSelect?.(frameworkNodeId)}
className={[
'flex shrink-0 w-full items-start justify-between gap-2 border-b px-4 py-3 text-left transition-colors',
Expand Down Expand Up @@ -100,6 +104,12 @@ export default function FrameworkTreeList({
</div>
</button>}

{belowHeaderSlot ? (
<div className="shrink-0 border-b border-black/10 p-3">
{belowHeaderSlot}
</div>
) : null}

<div className="flex-1 overflow-y-auto p-3" data-scroll-container role="tree" aria-label="Framework items">
{roots.length === 0 ? (
<div className="flex flex-col items-center justify-center gap-3 py-12 text-center text-sm text-slate-400">
Expand Down
65 changes: 43 additions & 22 deletions apps/editor/src/ui/editor/treePanel/TreePanelView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor
const leftPanelRef = useRef<HTMLDivElement>(null)
const rightPanelRef = useRef<HTMLDivElement>(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<number | undefined>(undefined)

// Stable refs for values used inside recalculateLines (avoids stale closures)
const pendingRef = useRef(pendingAssociations)
pendingRef.current = pendingAssociations
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -876,6 +894,16 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor
onDragStart={NOOP_DRAG_START}
associationCounts={leftAssociationCounts}
onBadgeClick={handleLeftBadgeClick}
belowHeaderSlot={(
<button
type="button"
onClick={() => { if (frameworkNodeId) addChild(frameworkNodeId) }}
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-slate-300 px-3 py-2 text-xs text-slate-500 transition-colors hover:border-teal-400 hover:text-teal-600 focus:outline-none"
>
<Plus className="h-3.5 w-3.5" />
Add framework item
</button>
)}
/>
</div>
{/* Gate callout — shown only when a target is expanded but source hasn't been saved */}
Expand All @@ -887,26 +915,30 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor
</p>
</div>
)}
<div className="shrink-0 border-t border-black/10 p-3">
<button
type="button"
onClick={() => { if (frameworkNodeId) addChild(frameworkNodeId) }}
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-slate-300 px-3 py-2 text-xs text-slate-500 transition-colors hover:border-teal-400 hover:text-teal-600 focus:outline-none"
>
<Plus className="h-3.5 w-3.5" />
Add framework item
</button>
</div>
</div>
</div>

{/* Right panel — accordion list of target frameworks */}
<div ref={rightPanelRef} className={panelClass}>
<div className="flex h-full flex-col overflow-hidden">
<div className="shrink-0 border-b border-black/10 bg-blue-50 px-4 py-3">
<div
className="shrink-0 border-b border-black/10 bg-blue-50 px-4 py-3"
style={leftHeaderHeight ? { height: leftHeaderHeight } : undefined}
>
<p className="text-sm font-semibold text-slate-800">Associated Frameworks</p>
</div>

<div className="shrink-0 border-b border-black/10 p-3">
<button
type="button"
onClick={() => setIsAddModalOpen(true)}
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-slate-300 px-3 py-2 text-xs text-slate-500 transition-colors hover:border-teal-400 hover:text-teal-600 focus:outline-none"
>
<Plus className="h-3.5 w-3.5" />
Add associated framework
</button>
</div>

<div className="flex-1 overflow-y-auto">
{targets.length === 0 ? (
<div className="flex flex-col items-center justify-center gap-2 px-6 py-16 text-center text-sm text-slate-400">
Expand Down Expand Up @@ -1003,17 +1035,6 @@ export default function TreePanelView({ availableFrameworks = [], serverFramewor
})
)}
</div>

<div className="shrink-0 border-t border-black/10 p-3">
<button
type="button"
onClick={() => setIsAddModalOpen(true)}
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-slate-300 px-3 py-2 text-xs text-slate-500 transition-colors hover:border-teal-400 hover:text-teal-600 focus:outline-none"
>
<Plus className="h-3.5 w-3.5" />
Add associated framework
</button>
</div>
</div>
</div>
</div>
Expand Down
Loading