fix(ui): back recent header only when stuck

This commit is contained in:
Bohdan Triapitsyn
2026-07-29 18:56:22 +03:00
parent 98c716cf92
commit 4b976141e0
3 changed files with 49 additions and 4 deletions
@@ -1705,9 +1705,10 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
openSidebarMenuKey={openSidebarMenuKey}
expansionState={recentExpandedParents}
variant="section"
isDesktopShellRuntime={isDesktopShellRuntime}
/>
) : null,
[activitySections, editingId, hasSessionSearchQuery, isVSCode, openSidebarMenuKey, recentExpandedParents, renderSessionNode, showRecentSection],
[activitySections, editingId, hasSessionSearchQuery, isDesktopShellRuntime, isVSCode, openSidebarMenuKey, recentExpandedParents, renderSessionNode, showRecentSection],
);
const isInlineEditing = Boolean(renamingFolderId || editingId || editingProjectDialogId);
@@ -10,6 +10,7 @@ import {
resolveMenuOpenSessionId,
} from './sessionNodeItemUtils';
import type { SessionNodeRenderExtras } from './sessionNodeItemUtils';
import { useStickyHeader } from './hooks/useStickyProjectHeaders';
type ActivityItem = {
node: SessionNode;
@@ -45,6 +46,7 @@ type Props = {
variant?: 'section' | 'flat';
initialVisibleCount?: number;
batchSize?: number;
isDesktopShellRuntime: boolean;
};
type RenderExtras = SessionNodeRenderExtras;
@@ -60,12 +62,17 @@ export function SidebarActivitySections(props: Props): React.ReactNode {
variant = 'section',
initialVisibleCount = MAX_VISIBLE_RECENT_SESSIONS,
batchSize = MAX_VISIBLE_RECENT_SESSIONS,
isDesktopShellRuntime,
} = props;
const { t } = useI18n();
const stickyZoneHeaders = useSessionDisplayStore((state) => state.stickyZoneHeaders);
const [collapsed, setCollapsed] = React.useState<Set<string>>(new Set());
const [visibleCountBySection, setVisibleCountBySection] = React.useState<Map<string, number>>(new Map());
const flatVariant = variant === 'flat';
const { isStuck: isRecentHeaderStuck, sentinelRef: recentHeaderSentinelRef } = useStickyHeader({
enabled: stickyZoneHeaders && !flatVariant,
isDesktopShellRuntime,
});
const resetSectionLimit = React.useCallback((key: string) => {
setVisibleCountBySection((prev) => {
@@ -177,11 +184,17 @@ export function SidebarActivitySections(props: Props): React.ReactNode {
return (
<div key={section.key} className="relative space-y-1">
{/* Zone header styled like a project header band; sticky with a
solid sidebar backing so rows never show through. */}
{/* Zone header styled like a project header band; its solid
backing applies only after the header is actually stuck. */}
<div
ref={recentHeaderSentinelRef}
className="absolute top-0 h-px w-full pointer-events-none"
aria-hidden="true"
/>
<div className={cn(
'-ml-2.5 -mr-2',
stickyZoneHeaders && 'oc-zone-header-backing sticky top-0 z-20 bg-sidebar',
stickyZoneHeaders && 'sticky top-0 z-20 bg-sidebar',
stickyZoneHeaders && isRecentHeaderStuck && 'oc-zone-header-backing',
)}>
<button
type="button"
@@ -7,6 +7,37 @@ type Args = {
projectHeaderSentinelRefs: React.MutableRefObject<Map<string, HTMLDivElement | null>>;
};
type StickyHeaderArgs = {
enabled: boolean;
isDesktopShellRuntime: boolean;
};
export const useStickyHeader = (args: StickyHeaderArgs) => {
const { enabled, isDesktopShellRuntime } = args;
const [sentinel, setSentinel] = React.useState<HTMLDivElement | null>(null);
const [isStuck, setIsStuck] = React.useState(false);
const sentinelRef = React.useCallback((node: HTMLDivElement | null) => {
setSentinel(node);
}, []);
React.useEffect(() => {
if (!enabled || !isDesktopShellRuntime || !sentinel) {
setIsStuck(false);
return;
}
const observer = new IntersectionObserver(([entry]) => {
setIsStuck(entry.intersectionRatio < 1);
}, { threshold: 1 });
observer.observe(sentinel);
return () => observer.disconnect();
}, [enabled, isDesktopShellRuntime, sentinel]);
return { isStuck, sentinelRef };
};
export const useStickyProjectHeaders = (args: Args): Set<string> => {
const { enabled = true, isDesktopShellRuntime, projectSections, projectHeaderSentinelRefs } = args;
const [stuckProjectHeaders, setStuckProjectHeaders] = React.useState<Set<string>>(new Set());