import React from 'react'; import { cn } from '@/lib/utils'; import type { SessionNode } from './types'; import { useI18n } from '@/lib/i18n'; import { Icon } from "@/components/icon/Icon"; import { collectSubtreeContainingId, computeNodeStructureKey, resolveMenuOpenSessionId, } from './sessionNodeItemUtils'; import type { SessionNodeRenderExtras } from './sessionNodeItemUtils'; type ActivityItem = { node: SessionNode; projectId: string | null; groupDirectory: string | null; secondaryMeta: { projectLabel?: string | null; branchLabel?: string | null; } | null; }; type ActivitySection = { key: 'active-now'; title: string; items: ActivityItem[]; }; type Props = { sections: ActivitySection[]; renderSessionNode: ( node: SessionNode, depth?: number, groupDirectory?: string | null, projectId?: string | null, archivedBucket?: boolean, secondaryMeta?: { projectLabel?: string | null; branchLabel?: string | null } | null, renderContext?: 'project' | 'recent', renderExtras?: SessionNodeRenderExtras, ) => React.ReactNode; currentSessionId: string | null; editingId: string | null; openSidebarMenuKey: string | null; variant?: 'section' | 'flat'; initialVisibleCount?: number; batchSize?: number; }; type RenderExtras = SessionNodeRenderExtras; const MAX_VISIBLE_RECENT_SESSIONS = 7; export function SidebarActivitySections({ sections, renderSessionNode, currentSessionId, editingId, openSidebarMenuKey, variant = 'section', initialVisibleCount = MAX_VISIBLE_RECENT_SESSIONS, batchSize = MAX_VISIBLE_RECENT_SESSIONS, }: Props): React.ReactNode { const { t } = useI18n(); const [collapsed, setCollapsed] = React.useState>(new Set()); const [visibleCountBySection, setVisibleCountBySection] = React.useState>(new Map()); const flatVariant = variant === 'flat'; const resetSectionLimit = React.useCallback((key: string) => { setVisibleCountBySection((prev) => { if (!prev.has(key)) { return prev; } const next = new Map(prev); next.delete(key); return next; }); }, []); const toggleSection = React.useCallback((key: string) => { // Collapsing/expanding resets any "show more" batches, matching the // worktree/project group behavior. resetSectionLimit(key); setCollapsed((prev) => { const next = new Set(prev); if (next.has(key)) { next.delete(key); } else { next.add(key); } return next; }); }, [resetSectionLimit]); const showMoreSessions = React.useCallback((key: string, currentVisibleCount: number, totalCount: number) => { setVisibleCountBySection((prev) => { const nextVisibleCount = Math.min(totalCount, currentVisibleCount + batchSize); const next = new Map(prev); next.set(key, nextVisibleCount); return next; }); }, [batchSize]); const buildRenderExtras = React.useCallback((nodes: SessionNode[]) => { const subtreeContainsActive = new Set(); collectSubtreeContainingId(nodes, currentSessionId, subtreeContainsActive); const subtreeContainsEditing = new Set(); collectSubtreeContainingId(nodes, editingId, subtreeContainsEditing); const menuOpenSessionId = resolveMenuOpenSessionId(nodes, openSidebarMenuKey, 'recent', false); const nodeStructureKeyByNode = new WeakMap(); const visit = (node: SessionNode): void => { nodeStructureKeyByNode.set(node, computeNodeStructureKey(node)); node.children.forEach(visit); }; nodes.forEach(visit); const childRenderExtrasFor = (child: SessionNode): RenderExtras => ({ subtreeContainsActive, subtreeContainsEditing, menuOpenSessionId, nodeStructureKey: nodeStructureKeyByNode.get(child) ?? '', childRenderExtrasFor, }); return (node: SessionNode): RenderExtras => ({ subtreeContainsActive, subtreeContainsEditing, menuOpenSessionId, nodeStructureKey: nodeStructureKeyByNode.get(node) ?? '', childRenderExtrasFor, }); }, [currentSessionId, editingId, openSidebarMenuKey]); const visibleSections = sections.filter((section) => section.items.length > 0); if (visibleSections.length === 0) { return null; } return (
{visibleSections.map((section) => { const isCollapsed = collapsed.has(section.key); const visibleLimit = Math.max( initialVisibleCount, visibleCountBySection.get(section.key) ?? initialVisibleCount, ); const visibleItems = section.items.slice(0, visibleLimit); const remainingCount = section.items.length - visibleItems.length; const canShowFewer = !flatVariant && section.items.length > initialVisibleCount && remainingCount === 0; const getRenderExtras = buildRenderExtras(visibleItems.map((item) => item.node)); const renderItem = (item: ActivityItem) => renderSessionNode( item.node, 0, item.groupDirectory, item.projectId, false, item.secondaryMeta, 'recent', getRenderExtras(item.node), ); if (flatVariant) { return (
{visibleItems.map(renderItem)} {remainingCount > 0 ? ( ) : null}
); } return (
{!isCollapsed ? (
{visibleItems.map(renderItem)} {remainingCount > 0 ? ( ) : null} {canShowFewer ? ( ) : null}
) : null}
); })}
); }