feat: add expand/collapse animation for compact card toggle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo, useCallback } from 'react';
|
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router';
|
import { useParams, useNavigate } from 'react-router';
|
||||||
import { ExternalLink, RefreshCw, Pencil, LayoutGrid, List, ChevronRight, ChevronDown } from 'lucide-react';
|
import { ExternalLink, RefreshCw, Pencil, LayoutGrid, List, ChevronRight, ChevronDown } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
@@ -179,6 +179,57 @@ export default function AgentHealth() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [animatingApps, setAnimatingApps] = useState<Map<string, 'expanding' | 'collapsing'>>(new Map());
|
||||||
|
const animationTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
||||||
|
|
||||||
|
const animateToggle = useCallback((appIdToToggle: string) => {
|
||||||
|
// Clear any existing timer for this app
|
||||||
|
const existing = animationTimers.current.get(appIdToToggle);
|
||||||
|
if (existing) clearTimeout(existing);
|
||||||
|
|
||||||
|
const isCurrentlyExpanded = expandedApps.has(appIdToToggle);
|
||||||
|
|
||||||
|
if (isCurrentlyExpanded) {
|
||||||
|
// Collapsing: start animation, then remove from expandedApps after transition
|
||||||
|
setAnimatingApps((prev) => new Map(prev).set(appIdToToggle, 'collapsing'));
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setExpandedApps((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(appIdToToggle);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
setAnimatingApps((prev) => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
next.delete(appIdToToggle);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
animationTimers.current.delete(appIdToToggle);
|
||||||
|
}, 200);
|
||||||
|
animationTimers.current.set(appIdToToggle, timer);
|
||||||
|
} else {
|
||||||
|
// Expanding: add to expandedApps immediately, animate in
|
||||||
|
setExpandedApps((prev) => new Set(prev).add(appIdToToggle));
|
||||||
|
setAnimatingApps((prev) => new Map(prev).set(appIdToToggle, 'expanding'));
|
||||||
|
// Use requestAnimationFrame to ensure the collapsed state renders first
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setAnimatingApps((prev) => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
next.delete(appIdToToggle);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [expandedApps]);
|
||||||
|
|
||||||
|
// Cleanup timers on unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
animationTimers.current.forEach((timer) => clearTimeout(timer));
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [configEditing, setConfigEditing] = useState(false);
|
const [configEditing, setConfigEditing] = useState(false);
|
||||||
const [configDraft, setConfigDraft] = useState<Record<string, string | boolean>>({});
|
const [configDraft, setConfigDraft] = useState<Record<string, string | boolean>>({});
|
||||||
|
|
||||||
@@ -653,7 +704,16 @@ export default function AgentHealth() {
|
|||||||
<div className={styles.compactGrid}>
|
<div className={styles.compactGrid}>
|
||||||
{groups.map((group) =>
|
{groups.map((group) =>
|
||||||
expandedApps.has(group.appId) ? (
|
expandedApps.has(group.appId) ? (
|
||||||
<div key={group.appId} className={styles.compactGridExpanded}>
|
<div
|
||||||
|
key={group.appId}
|
||||||
|
className={`${styles.compactGridExpanded} ${styles.expandWrapper} ${
|
||||||
|
animatingApps.get(group.appId) === 'expanding'
|
||||||
|
? styles.expandWrapperCollapsed
|
||||||
|
: animatingApps.get(group.appId) === 'collapsing'
|
||||||
|
? styles.expandWrapperCollapsed
|
||||||
|
: styles.expandWrapperExpanded
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<GroupCard
|
<GroupCard
|
||||||
title={group.appId}
|
title={group.appId}
|
||||||
accent={appHealth(group)}
|
accent={appHealth(group)}
|
||||||
@@ -666,7 +726,7 @@ export default function AgentHealth() {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className={styles.collapseBtn}
|
className={styles.collapseBtn}
|
||||||
onClick={() => toggleAppExpanded(group.appId)}
|
onClick={() => animateToggle(group.appId)}
|
||||||
title="Collapse"
|
title="Collapse"
|
||||||
>
|
>
|
||||||
<ChevronDown size={14} />
|
<ChevronDown size={14} />
|
||||||
@@ -717,7 +777,7 @@ export default function AgentHealth() {
|
|||||||
<CompactAppCard
|
<CompactAppCard
|
||||||
key={group.appId}
|
key={group.appId}
|
||||||
group={group}
|
group={group}
|
||||||
onExpand={() => toggleAppExpanded(group.appId)}
|
onExpand={() => animateToggle(group.appId)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user