Compare commits

..

No commits in common. "c0566705a1aa88c71a9d1a92d9c739c66b390d29" and "139bf16a8a1c148f3714731928c56042b1bb7f38" have entirely different histories.

2 changed files with 118 additions and 182 deletions

View file

@ -701,6 +701,7 @@ export default function TradeInV2Page() {
{/* CONTENT WRAP */} {/* CONTENT WRAP */}
<div <div
inert={nav !== 0 || drawerOpen ? true : undefined}
style={{ style={{
position: "absolute", position: "absolute",
inset: 14, inset: 14,
@ -709,17 +710,7 @@ export default function TradeInV2Page() {
flexDirection: "column", flexDirection: "column",
}} }}
> >
{/* TopNav stays interactive while a section overlay is open so the <nav aria-label="Разделы" style={{ display: "contents" }}>
user can switch sections directly from the overlay. Only the
LocationDrawer (a full modal over the whole HUD) inerts it. The
dashboard body (<main>/<footer>) is still inerted behind ANY
overlay/drawer preserving the focus + click guard from a11y
audit #2081 for everything except the top tabs. */}
<nav
aria-label="Разделы"
inert={drawerOpen ? true : undefined}
style={{ display: "contents" }}
>
<TopNav <TopNav
active={nav} active={nav}
onNavigate={setNav} onNavigate={setNav}
@ -728,10 +719,7 @@ export default function TradeInV2Page() {
onLogout={logout} onLogout={logout}
/> />
</nav> </nav>
<main <main style={{ display: "contents" }}>
inert={nav !== 0 || drawerOpen ? true : undefined}
style={{ display: "contents" }}
>
<HeroBar <HeroBar
data={{ report, object: objectInfo }} data={{ report, object: objectInfo }}
estimateId={mounted ? currentEstimateId : null} estimateId={mounted ? currentEstimateId : null}
@ -761,10 +749,7 @@ export default function TradeInV2Page() {
</div> </div>
</main> </main>
<footer <footer style={{ display: "contents" }}>
inert={nav !== 0 || drawerOpen ? true : undefined}
style={{ display: "contents" }}
>
<Footer data={report} hasEstimate={hasEstimate} /> <Footer data={report} hasEstimate={hasEstimate} />
</footer> </footer>
</div> </div>

View file

@ -30,11 +30,6 @@ interface SectionOverlayProps {
cache?: CacheData; cache?: CacheData;
} }
// Panel geometry (relative to the artboard). Kept as a constant so the click
// backdrop can align its top edge to the panel — the backdrop starts BELOW the
// TopNav so the top tabs stay clickable while an overlay is open.
const PANEL_TOP = 80;
export default function SectionOverlay({ export default function SectionOverlay({
active, active,
onClose, onClose,
@ -110,182 +105,138 @@ export default function SectionOverlay({
}, []); }, []);
return ( return (
<> <div
{/* Click-outside backdrop. A transparent layer covering the artboard BELOW ref={dialogRef}
the TopNav (top: PANEL_TOP) clicking the margin area around the panel role="dialog"
closes the overlay (in addition to Esc). It is a SIBLING of the dialog aria-modal="true"
(not an ancestor) and sits one z-index below it, so clicks inside the aria-labelledby="v2-overlay-title"
panel / on its buttons never reach this handler (they don't bubble to a tabIndex={-1}
sibling) and therefore never close the overlay. Starting below the nav style={{
keeps the top tabs clickable. aria-hidden + no tabIndex invisible to position: "absolute",
AT and keyboard; Esc remains the keyboard close path. */} left: 28,
right: 28,
top: 80,
bottom: 30,
zIndex: 30,
display: "flex",
flexDirection: "column",
background:
"linear-gradient(180deg,rgba(244,248,252,.985),rgba(235,242,250,.985))",
backdropFilter: "blur(12px)",
border: `1px solid ${tokens.line3}`,
borderRadius: 8,
boxShadow: "0 22px 60px rgba(40,80,130,.18)",
overflow: "hidden",
fontFamily: tokens.font.sans,
}}
>
<style>{`
.tiv2-so-back { color: ${tokens.ink}; transition: all .15s; }
.tiv2-so-back:hover { border-color: ${tokens.accent}; color: ${tokens.accent}; }
`}</style>
{/* HUD corner brackets */}
<div <div
aria-hidden="true"
onClick={onClose}
style={{ style={{
position: "absolute", position: "absolute",
left: 0, left: 12,
right: 0, top: 12,
top: PANEL_TOP, width: 15,
bottom: 0, height: 15,
zIndex: 29, borderLeft: `1.5px solid ${tokens.accent}`,
borderTop: `1.5px solid ${tokens.accent}`,
pointerEvents: "none",
}}
/>
<div
style={{
position: "absolute",
right: 12,
bottom: 12,
width: 15,
height: 15,
borderRight: `1.5px solid ${tokens.accent}`,
borderBottom: `1.5px solid ${tokens.accent}`,
pointerEvents: "none",
}} }}
/> />
{/* overlay header */}
<div <div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-labelledby="v2-overlay-title"
tabIndex={-1}
style={{ style={{
position: "absolute", flex: "0 0 auto",
left: 28,
right: 28,
top: PANEL_TOP,
bottom: 30,
zIndex: 30,
display: "flex", display: "flex",
flexDirection: "column", alignItems: "center",
background: justifyContent: "space-between",
"linear-gradient(180deg,rgba(244,248,252,.985),rgba(235,242,250,.985))", padding: "16px 24px",
backdropFilter: "blur(12px)", borderBottom: "1px solid #cdd9e6",
border: `1px solid ${tokens.line3}`,
borderRadius: 8,
boxShadow: "0 22px 60px rgba(40,80,130,.18)",
overflow: "hidden",
fontFamily: tokens.font.sans,
}} }}
> >
<style>{` <div style={{ display: "flex", alignItems: "baseline", gap: 11 }}>
.tiv2-so-back { <span
transition: all .15s;
background: ${tokens.accentDeep};
box-shadow: 0 3px 10px rgba(46,139,255,.28);
}
.tiv2-so-back:hover {
box-shadow: 0 6px 16px rgba(46,139,255,.45);
transform: translateY(-1px);
}
.tiv2-so-back:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(46,139,255,.3);
}
`}</style>
{/* HUD corner brackets */}
<div
style={{
position: "absolute",
left: 12,
top: 12,
width: 15,
height: 15,
borderLeft: `1.5px solid ${tokens.accent}`,
borderTop: `1.5px solid ${tokens.accent}`,
pointerEvents: "none",
}}
/>
<div
style={{
position: "absolute",
right: 12,
bottom: 12,
width: 15,
height: 15,
borderRight: `1.5px solid ${tokens.accent}`,
borderBottom: `1.5px solid ${tokens.accent}`,
pointerEvents: "none",
}}
/>
{/* overlay header */}
<div
style={{
flex: "0 0 auto",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "16px 24px",
borderBottom: "1px solid #cdd9e6",
}}
>
<div style={{ display: "flex", alignItems: "baseline", gap: 11 }}>
<span
style={{
fontFamily: tokens.font.mono,
fontSize: 15,
color: tokens.accent,
}}
>
{overlayNums[active]}
</span>
<span
id="v2-overlay-title"
style={{
fontSize: 14,
fontWeight: 600,
letterSpacing: 2.5,
color: tokens.ink2,
}}
>
{overlayTitles[active]}
</span>
</div>
<button
type="button"
onClick={onClose}
className="tiv2-so-back"
aria-label="Вернуться к оценке"
style={{ style={{
display: "flex", fontFamily: tokens.font.mono,
alignItems: "center", fontSize: 15,
gap: 8, color: tokens.accent,
cursor: "pointer",
border: `1px solid ${tokens.accentDeep}`,
borderRadius: 6,
padding: "8px 15px",
// background + shadow live in .tiv2-so-back so :hover can override
// them (an inline background would beat the hover rule on
// specificity). Solid accentDeep keeps white text ≥4.5:1 (AA).
fontFamily: "inherit",
}} }}
> >
<span {overlayNums[active]}
style={{ fontSize: 15, color: tokens.onAccent, lineHeight: 1 }} </span>
> <span
id="v2-overlay-title"
</span> style={{
<span fontSize: 14,
style={{ fontWeight: 600,
fontSize: 11, letterSpacing: 2.5,
fontWeight: 600, color: tokens.ink2,
letterSpacing: 1.5, }}
color: tokens.onAccent, >
}} {overlayTitles[active]}
> </span>
К ОЦЕНКЕ
</span>
</button>
</div> </div>
<button
{/* overlay body */} type="button"
<div onClick={onClose}
className="tiv2-so-back"
style={{ style={{
flex: 1, display: "flex",
minHeight: 0, alignItems: "center",
overflowY: "auto", gap: 8,
padding: "22px 24px 26px", cursor: "pointer",
border: `1px solid ${tokens.line}`,
borderRadius: 6,
padding: "6px 12px",
background: tokens.surface.w60,
// reset UA button defaults while keeping the original box look;
// base colour lives in .tiv2-so-back so :hover can win
fontFamily: "inherit",
}} }}
> >
{active === 1 && <HistoryView data={history} />} <span style={{ fontSize: 13 }}></span>
{active === 2 && <SourcesView data={sources} />} <span
{active === 3 && ( style={{ fontSize: 10, letterSpacing: 1.5, color: tokens.body2 }}
<AnalyticsView data={analytics} onNavigate={onNavigate} /> >
)} К ОЦЕНКЕ
{active === 4 && <CacheView data={cache} />} </span>
</div> </button>
</div> </div>
</>
{/* overlay body */}
<div
style={{
flex: 1,
minHeight: 0,
overflowY: "auto",
padding: "22px 24px 26px",
}}
>
{active === 1 && <HistoryView data={history} />}
{active === 2 && <SourcesView data={sources} />}
{active === 3 && (
<AnalyticsView data={analytics} onNavigate={onNavigate} />
)}
{active === 4 && <CacheView data={cache} />}
</div>
</div>
); );
} }