Compare commits

...

2 Commits

Author SHA1 Message Date
hsiegeln
26de5ec58f feat: add click-to-close backdrop behind DetailPanel
All checks were successful
Build & Publish / publish (push) Successful in 58s
Adds a subtle semi-transparent backdrop (rgba(0,0,0,0.15)) behind the
overlay panel. Clicking the backdrop closes the panel. Fades in with
the panel animation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:57:34 +01:00
hsiegeln
d26dc6a8a5 fix: make DetailPanel overlay instead of taking flex space
All checks were successful
Build & Publish / publish (push) Successful in 1m1s
DetailPanel now uses position: fixed to overlay the content area rather
than participating in the AppShell flex row. This prevents the TopBar
from being compressed when the panel opens. Added box-shadow for depth
separation and z-index: 100 for stacking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:55:02 +01:00
3 changed files with 63 additions and 40 deletions

View File

@@ -1,4 +1,21 @@
.backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.15);
z-index: 99;
animation: fadeIn 0.2s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.panel { .panel {
position: fixed;
top: 0;
right: 0;
height: 100vh;
width: 0; width: 0;
overflow: hidden; overflow: hidden;
transition: width 0.25s ease, opacity 0.2s ease; transition: width 0.25s ease, opacity 0.2s ease;
@@ -7,13 +24,15 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--bg-surface); background: var(--bg-surface);
flex-shrink: 0; z-index: 100;
box-shadow: none;
} }
.panel.open { .panel.open {
width: 400px; width: 400px;
opacity: 1; opacity: 1;
border-left-color: var(--border); border-left-color: var(--border);
box-shadow: var(--shadow-lg);
animation: slideInRight 0.25s ease-out both; animation: slideInRight 0.25s ease-out both;
} }

View File

@@ -23,51 +23,54 @@ export function DetailPanel({ open, onClose, title, tabs, children, actions, cla
const activeContent = tabs?.find((t) => t.value === activeTab)?.content const activeContent = tabs?.find((t) => t.value === activeTab)?.content
const panel = ( const content = (
<aside <>
className={`${styles.panel} ${open ? styles.open : ''} ${className ?? ''}`} {open && <div className={styles.backdrop} onClick={onClose} aria-hidden="true" />}
aria-hidden={!open} <aside
> className={`${styles.panel} ${open ? styles.open : ''} ${className ?? ''}`}
<div className={styles.header}> aria-hidden={!open}
<span className={styles.title}>{title}</span> >
<button <div className={styles.header}>
className={styles.closeBtn} <span className={styles.title}>{title}</span>
onClick={onClose} <button
aria-label="Close panel" className={styles.closeBtn}
type="button" onClick={onClose}
> aria-label="Close panel"
&times; type="button"
</button> >
</div> &times;
</button>
{tabs && tabs.length > 0 && (
<div className={styles.tabs}>
{tabs.map((tab) => (
<button
key={tab.value}
className={`${styles.tab} ${tab.value === activeTab ? styles.activeTab : ''}`}
onClick={() => setActiveTab(tab.value)}
type="button"
>
{tab.label}
</button>
))}
</div> </div>
)}
<div className={styles.body}> {tabs && tabs.length > 0 && (
{children ?? activeContent} <div className={styles.tabs}>
</div> {tabs.map((tab) => (
<button
key={tab.value}
className={`${styles.tab} ${tab.value === activeTab ? styles.activeTab : ''}`}
onClick={() => setActiveTab(tab.value)}
type="button"
>
{tab.label}
</button>
))}
</div>
)}
{actions && ( <div className={styles.body}>
<div className={styles.actions}> {children ?? activeContent}
{actions}
</div> </div>
)}
</aside> {actions && (
<div className={styles.actions}>
{actions}
</div>
)}
</aside>
</>
) )
// Portal to AppShell level if target exists, otherwise render in place // Portal to AppShell level if target exists, otherwise render in place
const portalTarget = document.getElementById('cameleer-detail-panel-root') const portalTarget = document.getElementById('cameleer-detail-panel-root')
return portalTarget ? createPortal(panel, portalTarget) : panel return portalTarget ? createPortal(content, portalTarget) : content
} }

View File

@@ -2,6 +2,7 @@
display: flex; display: flex;
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
position: relative;
} }
.main { .main {