agent-ecosystem/src/renderer/components/team/CollapsibleTeamSection.tsx
iliya 321673ff6d feat: update pricing configuration and enhance file content handling
- Removed outdated pricing entries for Claude models from pricing.json to streamline configuration.
- Enhanced the getFileContent method in FileContentResolver to accept snippets for improved content diffing.
- Updated IPC methods to support new parameters for file content retrieval, improving data handling in the review process.
- Introduced accurate line addition and removal tracking in file diffs, enhancing the review experience.
- Improved UI components to reflect changes in file content handling, ensuring better user interaction during reviews.
2026-02-26 14:30:09 +02:00

103 lines
3.5 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react';
import { Badge } from '@renderer/components/ui/badge';
import { ChevronRight } from 'lucide-react';
function scrollAfterExpand(el: HTMLElement): void {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
});
}
interface CollapsibleTeamSectionProps {
title: string;
/** Icon rendered before the title text. */
icon?: React.ReactNode;
badge?: string | number;
/** Secondary badge (e.g. unread count). Shown next to main badge when defined. */
secondaryBadge?: number;
/** Extra element rendered inline after badges (e.g. notification icon). */
headerExtra?: React.ReactNode;
defaultOpen?: boolean;
forceOpen?: boolean;
action?: React.ReactNode;
/** Stable identifier used for programmatic section navigation. */
sectionId?: string;
children: React.ReactNode;
}
export const CollapsibleTeamSection = ({
title,
icon,
badge,
secondaryBadge,
headerExtra,
defaultOpen = true,
forceOpen,
action,
sectionId,
children,
}: CollapsibleTeamSectionProps): React.JSX.Element => {
const [open, setOpen] = useState(defaultOpen);
const isOpen = forceOpen ? true : open;
const sectionRef = useRef<HTMLElement>(null);
const handleNavigate = useCallback((): void => {
setOpen(true);
if (sectionRef.current) scrollAfterExpand(sectionRef.current);
}, []);
useEffect(() => {
const el = sectionRef.current;
if (!el) return;
el.addEventListener('team-section-navigate', handleNavigate);
return () => el.removeEventListener('team-section-navigate', handleNavigate);
}, [handleNavigate]);
return (
<section
ref={sectionRef}
data-section-id={sectionId}
className="min-w-0 overflow-hidden border-b border-[var(--color-border)] pb-3 last:border-b-0"
>
<div className="relative -mx-4 flex min-h-10 w-full items-stretch py-3">
<button
type="button"
className="absolute inset-0 z-0 cursor-pointer rounded-md transition-colors hover:bg-[var(--color-surface-raised)]"
onClick={() => setOpen((prev) => !prev)}
aria-label={isOpen ? 'Collapse section' : 'Expand section'}
/>
<div className="pointer-events-none relative z-10 flex min-w-0 flex-1 basis-0 items-center gap-2 pl-4">
<ChevronRight
size={14}
className={`shrink-0 text-[var(--color-text-muted)] transition-transform duration-150 ${isOpen ? 'rotate-90' : ''}`}
/>
{icon ? <span className="shrink-0 text-[var(--color-text-muted)]">{icon}</span> : null}
<span className="text-sm font-medium text-[var(--color-text)]">{title}</span>
{badge != null && (
<Badge
variant="secondary"
className="px-1.5 py-0.5 text-[10px] font-normal leading-none"
>
{badge}
</Badge>
)}
{secondaryBadge != null && secondaryBadge > 0 && (
<Badge
variant="secondary"
className="bg-blue-500/20 px-1.5 py-0.5 text-[10px] font-normal leading-none text-blue-400"
title={`${secondaryBadge} unread`}
>
{secondaryBadge} new
</Badge>
)}
{headerExtra}
</div>
{action && <div className="relative z-10 flex shrink-0 items-center">{action}</div>}
</div>
{isOpen && <div className="mt-2 min-w-0 overflow-hidden">{children}</div>}
</section>
);
};