fix: use self-portaling DetailPanel from design system v0.1.5
Some checks failed
CI / build (push) Failing after 57s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped

DetailPanel now portals itself to #cameleer-detail-panel-root (a div
AppShell places as a sibling of .main in the top-level flex row).
Pages just render <DetailPanel> inline — no manual createPortal,
no context, no prop drilling.

Remove the old #detail-panel-portal div from LayoutShell and the
createPortal wrappers from Dashboard and AgentHealth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-24 19:00:02 +01:00
parent 5d2eff4f73
commit a5bc7cf6d1
5 changed files with 50 additions and 59 deletions

61
ui/package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "ui", "name": "ui",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@cameleer/design-system": "^0.1.4", "@cameleer/design-system": "file:../../design-system",
"@tanstack/react-query": "^5.90.21", "@tanstack/react-query": "^5.90.21",
"openapi-fetch": "^0.17.0", "openapi-fetch": "^0.17.0",
"react": "^19.2.4", "react": "^19.2.4",
@@ -35,6 +35,34 @@
"vite": "^8.0.0" "vite": "^8.0.0"
} }
}, },
"../../design-system": {
"name": "@cameleer/design-system",
"version": "0.1.5",
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.0.0"
},
"devDependencies": {
"@playwright/test": "^1.58.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.0",
"happy-dom": "^20.8.4",
"typescript": "^5.6.0",
"vite": "^6.0.0",
"vite-plugin-dts": "^4.5.4",
"vitest": "^3.0.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.0.0"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.29.0", "version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
@@ -276,19 +304,8 @@
} }
}, },
"node_modules/@cameleer/design-system": { "node_modules/@cameleer/design-system": {
"version": "0.1.4", "resolved": "../../design-system",
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.4/design-system-0.1.4.tgz", "link": true
"integrity": "sha512-DWyHv/1Nr8/h56T2ny1dsOZGy9YK64hfajTMOsl7gkWF3PwLPiB9Hpapa4O2Y5T/sspegxdlSZSddHF7muyFbw==",
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.0.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.0.0"
}
}, },
"node_modules/@emnapi/core": { "node_modules/@emnapi/core": {
"version": "1.9.1", "version": "1.9.1",
@@ -2955,22 +2972,6 @@
} }
} }
}, },
"node_modules/react-router-dom": {
"version": "7.13.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.2.tgz",
"integrity": "sha512-aR7SUORwTqAW0JDeiWF07e9SBE9qGpByR9I8kJT5h/FrBKxPMS6TiC7rmVO+gC0q52Bx7JnjWe8Z1sR9faN4YA==",
"license": "MIT",
"dependencies": {
"react-router": "7.13.2"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/require-from-string": { "node_modules/require-from-string": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",

View File

@@ -14,7 +14,7 @@
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts" "generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
}, },
"dependencies": { "dependencies": {
"@cameleer/design-system": "^0.1.4", "@cameleer/design-system": "^0.1.5",
"@tanstack/react-query": "^5.90.21", "@tanstack/react-query": "^5.90.21",
"openapi-fetch": "^0.17.0", "openapi-fetch": "^0.17.0",
"react": "^19.2.4", "react": "^19.2.4",

View File

@@ -137,8 +137,6 @@ function LayoutContent() {
<main style={{ flex: 1, overflow: 'auto', padding: '1.5rem' }}> <main style={{ flex: 1, overflow: 'auto', padding: '1.5rem' }}>
<Outlet /> <Outlet />
</main> </main>
{/* Portal target for DetailPanel — pages use createPortal to render here */}
<div id="detail-panel-portal" />
</AppShell> </AppShell>
); );
} }

View File

@@ -1,5 +1,4 @@
import { useState, useMemo } from 'react'; import { useState, useMemo } from 'react';
import { createPortal } from 'react-dom';
import { useParams, Link } from 'react-router'; import { useParams, Link } from 'react-router';
import { import {
StatCard, StatusDot, Badge, MonoText, ProgressBar, StatCard, StatusDot, Badge, MonoText, ProgressBar,
@@ -504,18 +503,15 @@ export default function AgentHealth() {
</div> </div>
)} )}
{/* Detail panel — portaled to AppShell level for proper slide-in */} {/* Detail panel — auto-portals to AppShell level via design system */}
{selectedInstance && document.getElementById('detail-panel-portal') && {selectedInstance && (
createPortal(
<DetailPanel <DetailPanel
open={panelOpen} open={panelOpen}
onClose={() => { setPanelOpen(false); setSelectedInstance(null); }} onClose={() => { setPanelOpen(false); setSelectedInstance(null); }}
title={selectedInstance.name ?? selectedInstance.id} title={selectedInstance.name ?? selectedInstance.id}
tabs={detailTabs} tabs={detailTabs}
/>, />
document.getElementById('detail-panel-portal')!, )}
)
}
</div> </div>
); );
} }

View File

@@ -1,5 +1,4 @@
import { useState, useMemo, useCallback } from 'react' import { useState, useMemo, useCallback } from 'react'
import { createPortal } from 'react-dom'
import { useParams, useNavigate } from 'react-router' import { useParams, useNavigate } from 'react-router'
import { import {
DataTable, DataTable,
@@ -415,9 +414,8 @@ export default function Dashboard() {
{/* Shortcuts bar */} {/* Shortcuts bar */}
<ShortcutsBar shortcuts={SHORTCUTS} /> <ShortcutsBar shortcuts={SHORTCUTS} />
{/* Detail panel — portaled to AppShell level for proper slide-in */} {/* Detail panel — auto-portals to AppShell level via design system */}
{selectedRow && detail && document.getElementById('detail-panel-portal') && {selectedRow && detail && (
createPortal(
<DetailPanel <DetailPanel
open={panelOpen} open={panelOpen}
onClose={() => setPanelOpen(false)} onClose={() => setPanelOpen(false)}
@@ -498,10 +496,8 @@ export default function Dashboard() {
<div style={{ color: 'var(--text-muted)', fontSize: 12 }}>No processor data</div> <div style={{ color: 'var(--text-muted)', fontSize: 12 }}>No processor data</div>
)} )}
</div> </div>
</DetailPanel>, </DetailPanel>
document.getElementById('detail-panel-portal')!, )}
)
}
</> </>
) )
} }