feat: move toggle to toolbar, sort apps by name, overlay expand

- Move expand/collapse toggle from stat strip to dedicated toolbar
  below KPIs
- Sort app groups alphabetically by name
- Expanded card overlays from clicked card position instead of
  pushing other cards down
- Viewport constraint: overlay flips right-alignment and limits
  height when near edges

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-16 13:48:39 +02:00
parent 9f7951aa2b
commit 1fa897fbb5
2 changed files with 125 additions and 82 deletions

View File

@@ -13,7 +13,7 @@
/* Stat strip */ /* Stat strip */
.statStrip { .statStrip {
display: grid; display: grid;
grid-template-columns: repeat(5, 1fr) auto; grid-template-columns: repeat(5, 1fr);
gap: 10px; gap: 10px;
margin-bottom: 16px; margin-bottom: 16px;
} }
@@ -99,6 +99,7 @@
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 10px; gap: 10px;
margin-bottom: 20px; margin-bottom: 20px;
position: relative;
} }
/* Group meta row */ /* Group meta row */
@@ -317,25 +318,49 @@
color: var(--card-accent); color: var(--card-accent);
} }
/* Expanded card inside compact grid */ /* Wrapper for each compact grid cell — anchor for overlay */
.compactGridCell {
position: relative;
}
/* Expanded card overlay — floats from the clicked card */
.compactGridExpanded { .compactGridExpanded {
grid-column: span 2; position: absolute;
z-index: 10;
top: 0;
left: 0;
min-width: 500px;
background: var(--bg-body);
border-radius: var(--radius-md);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
padding: 4px;
} }
/* Expand/collapse animation wrapper */ /* Expand/collapse animation wrapper */
.expandWrapper { .expandWrapper {
overflow: hidden; overflow: hidden;
transition: max-height 200ms ease, opacity 150ms ease; transition: opacity 200ms ease, transform 200ms ease;
} }
.expandWrapperCollapsed { .expandWrapperCollapsed {
max-height: 0;
opacity: 0; opacity: 0;
transform: scaleY(0.95);
transform-origin: top;
} }
.expandWrapperExpanded { .expandWrapperExpanded {
max-height: 1000px;
opacity: 1; opacity: 1;
transform: scaleY(1);
transform-origin: top;
}
/* View toolbar — between stat strip and cards grid */
.viewToolbar {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
margin-bottom: 12px;
} }
/* View mode toggle */ /* View mode toggle */

View File

@@ -289,7 +289,7 @@ export default function AgentHealth() {
const agentList = agents ?? []; const agentList = agents ?? [];
const groups = useMemo(() => groupByApp(agentList), [agentList]); const groups = useMemo(() => groupByApp(agentList).sort((a, b) => a.appId.localeCompare(b.appId)), [agentList]);
// Aggregate stats // Aggregate stats
const totalInstances = agentList.length; const totalInstances = agentList.length;
@@ -515,6 +515,10 @@ export default function AgentHealth() {
accent={deadCount > 0 ? 'error' : 'success'} accent={deadCount > 0 ? 'error' : 'success'}
detail={deadCount > 0 ? 'requires attention' : 'all healthy'} detail={deadCount > 0 ? 'requires attention' : 'all healthy'}
/> />
</div>
{/* View toolbar */}
<div className={styles.viewToolbar}>
<div className={styles.viewToggle}> <div className={styles.viewToggle}>
<button <button
className={`${styles.viewToggleBtn} ${viewMode === 'compact' ? styles.viewToggleBtnActive : ''}`} className={`${styles.viewToggleBtn} ${viewMode === 'compact' ? styles.viewToggleBtnActive : ''}`}
@@ -702,10 +706,29 @@ export default function AgentHealth() {
</div> </div>
) : ( ) : (
<div className={styles.compactGrid}> <div className={styles.compactGrid}>
{groups.map((group) => {groups.map((group) => (
expandedApps.has(group.appId) ? ( <div key={group.appId} className={styles.compactGridCell}>
<CompactAppCard
group={group}
onExpand={() => animateToggle(group.appId)}
/>
{expandedApps.has(group.appId) && (
<div <div
key={group.appId} ref={(el) => {
if (!el) return;
// Constrain overlay within viewport
const rect = el.getBoundingClientRect();
const vw = document.documentElement.clientWidth;
if (rect.right > vw - 16) {
el.style.left = 'auto';
el.style.right = '0';
}
if (rect.bottom > document.documentElement.clientHeight) {
const overflow = rect.bottom - document.documentElement.clientHeight + 16;
el.style.maxHeight = `${rect.height - overflow}px`;
el.style.overflowY = 'auto';
}
}}
className={`${styles.compactGridExpanded} ${styles.expandWrapper} ${ className={`${styles.compactGridExpanded} ${styles.expandWrapper} ${
animatingApps.get(group.appId) === 'expanding' animatingApps.get(group.appId) === 'expanding'
? styles.expandWrapperCollapsed ? styles.expandWrapperCollapsed
@@ -773,15 +796,10 @@ export default function AgentHealth() {
/> />
</GroupCard> </GroupCard>
</div> </div>
) : (
<CompactAppCard
key={group.appId}
group={group}
onExpand={() => animateToggle(group.appId)}
/>
),
)} )}
</div> </div>
))}
</div>
)} )}
{/* Log + Timeline side by side */} {/* Log + Timeline side by side */}