From 634eb10b2c5e1850f668ead656c683d9cee2e0ca Mon Sep 17 00:00:00 2001 From: Andy Charlwood Date: Mon, 16 Feb 2026 02:21:45 +0000 Subject: [PATCH] feat: US-002 - Dynamic height matching with work experience column --- src/components/CareerConstellation.tsx | 25 ++++++++++------- src/components/DashboardLayout.tsx | 38 +++++++++++++++++++------- src/index.css | 3 +- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/components/CareerConstellation.tsx b/src/components/CareerConstellation.tsx index a48e2f3..f90a3a0 100644 --- a/src/components/CareerConstellation.tsx +++ b/src/components/CareerConstellation.tsx @@ -7,11 +7,11 @@ interface CareerConstellationProps { onRoleClick: (id: string) => void onSkillClick: (id: string) => void highlightedNodeId?: string | null + containerHeight?: number | null } -const DESKTOP_HEIGHT = 480 -const TABLET_HEIGHT = 380 -const MOBILE_HEIGHT = 310 +const MIN_HEIGHT = 400 +const MOBILE_FALLBACK_HEIGHT = 360 const ROLE_RADIUS = 30 const SKILL_RADIUS = 14 @@ -28,10 +28,12 @@ const domainColorMap: Record = { const roleNodes = constellationNodes.filter(n => n.type === 'role') const srDescription = buildScreenReaderDescription() -function getHeight(width: number): number { - if (width < 768) return MOBILE_HEIGHT - if (width < 1024) return TABLET_HEIGHT - return DESKTOP_HEIGHT +function getHeight(width: number, containerHeight?: number | null): number { + // Mobile/tablet: use fallback since columns stack vertically + if (width < 1024) return MOBILE_FALLBACK_HEIGHT + // Desktop: use measured container height if available, with minimum + if (containerHeight && containerHeight > 0) return Math.max(MIN_HEIGHT, containerHeight) + return MIN_HEIGHT } interface SimNode extends ConstellationNode { @@ -86,13 +88,14 @@ const CareerConstellation: React.FC = ({ onRoleClick, onSkillClick, highlightedNodeId, + containerHeight, }) => { const svgRef = useRef(null) const containerRef = useRef(null) const simulationRef = useRef | null>(null) const highlightGraphRef = useRef<((activeNodeId: string | null) => void) | null>(null) const callbacksRef = useRef({ onRoleClick, onSkillClick }) - const [dimensions, setDimensions] = useState({ width: 800, height: DESKTOP_HEIGHT }) + const [dimensions, setDimensions] = useState({ width: 800, height: MIN_HEIGHT }) const [focusedNodeId, setFocusedNodeId] = useState(null) const [pinnedNodeId, setPinnedNodeId] = useState(null) const [nodeButtonPositions, setNodeButtonPositions] = useState>({}) @@ -117,7 +120,9 @@ const CareerConstellation: React.FC = ({ const updateDimensions = () => { const width = container.clientWidth - const height = getHeight(width) + // Use viewport width for breakpoint check since container may overflow on mobile + const viewportWidth = window.innerWidth + const height = getHeight(viewportWidth, containerHeight) setDimensions({ width, height }) } @@ -127,7 +132,7 @@ const CareerConstellation: React.FC = ({ observer.observe(container) return () => observer.disconnect() - }, []) + }, [containerHeight]) useEffect(() => { const svg = d3.select(svgRef.current) diff --git a/src/components/DashboardLayout.tsx b/src/components/DashboardLayout.tsx index 108dca9..8c20788 100644 --- a/src/components/DashboardLayout.tsx +++ b/src/components/DashboardLayout.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react' +import React, { useState, useEffect, useCallback, useRef } from 'react' import { motion } from 'framer-motion' import { ChevronRight } from 'lucide-react' import { TopBar } from './TopBar' @@ -236,9 +236,25 @@ function LastConsultationSubsection() { export function DashboardLayout() { const [commandPaletteOpen, setCommandPaletteOpen] = useState(false) const [highlightedNodeId, setHighlightedNodeId] = useState(null) + const [chronologyHeight, setChronologyHeight] = useState(null) + const chronologyRef = useRef(null) const activeSection = useActiveSection() const { openPanel } = useDetailPanel() + // Measure the chronology stream height so the constellation graph can match it + useEffect(() => { + const el = chronologyRef.current + if (!el) return + + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + setChronologyHeight(entry.contentRect.height) + } + }) + observer.observe(el) + return () => observer.disconnect() + }, []) + const handleSearchClick = () => { setCommandPaletteOpen(true) } @@ -383,15 +399,7 @@ export function DashboardLayout() { {/* Patient Pathway — parent section with constellation graph + subsections */}
-
- -
- -
+
+
+ +
+ +
diff --git a/src/index.css b/src/index.css index 5218cf7..2301398 100644 --- a/src/index.css +++ b/src/index.css @@ -401,7 +401,7 @@ html { /* Desktop: 2 columns */ @media (min-width: 1024px) { .pathway-columns { - grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); + grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.5fr); align-items: start; gap: 22px; } @@ -409,6 +409,7 @@ html { .pathway-graph-sticky { position: sticky; top: 12px; + min-height: 100%; } }