From 3d1e8fa98db44861821092c9fad0b376baaac94d Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Thu, 1 Jan 2026 14:24:57 +0200 Subject: [PATCH] feat(sidebar): add dynamic background for sticky headers --- .../src/components/session/SessionSidebar.tsx | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index 6126a9bb..dcadde9f 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -130,6 +130,8 @@ export const SessionSidebar: React.FC = ({ const [isGitRepo, setIsGitRepo] = React.useState(null); const [expandedSessionGroups, setExpandedSessionGroups] = React.useState>(new Set()); const [hoveredGroupId, setHoveredGroupId] = React.useState(null); + const [stuckHeaders, setStuckHeaders] = React.useState>(new Set()); + const headerSentinelRefs = React.useRef>(new Map()); const currentDirectory = useDirectoryStore((state) => state.currentDirectory); const homeDirectory = useDirectoryStore((state) => state.homeDirectory); @@ -664,6 +666,38 @@ export const SessionSidebar: React.FC = ({ }); }, []); + // Track when sticky headers become "stuck" using sentinel elements + React.useEffect(() => { + if (!isDesktopRuntime) return; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + const groupId = (entry.target as HTMLElement).dataset.groupId; + if (!groupId) return; + + setStuckHeaders((prev) => { + const next = new Set(prev); + // When sentinel is NOT intersecting, header is stuck + if (!entry.isIntersecting) { + next.add(groupId); + } else { + next.delete(groupId); + } + return next; + }); + }); + }, + { threshold: 0 } + ); + + headerSentinelRefs.current.forEach((el) => { + if (el) observer.observe(el); + }); + + return () => observer.disconnect(); + }, [isDesktopRuntime, groupedSessions]); + const renderSessionNode = React.useCallback( (node: SessionNode, depth = 0, groupDirectory?: string | null): React.ReactNode => { const session = node.session; @@ -1055,13 +1089,23 @@ export const SessionSidebar: React.FC = ({ ) : ( groupedSessions.map((group) => (
- {} + {/* Sentinel element to detect when header becomes stuck */} + {isDesktopRuntime && ( +
{ headerSentinelRefs.current.set(group.id, el); }} + data-group-id={group.id} + className="absolute top-0 h-px w-full pointer-events-none" + aria-hidden="true" + /> + )}