From 7066117cb8a60c3d974c56747f85b13a2e847647 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sun, 28 Jun 2026 21:29:19 +0300 Subject: [PATCH] =?UTF-8?q?fix(tradein/v2):=20a11y=20P3a=20=E2=80=94=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D1=8B=20+=20=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=B4=D0=B8=D0=B0=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=20+=20aria-live=20(#2062)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL-доступность (часть 1): клавиатура/скринридер. Без визуальных изменений (chrome-reset кнопок вынесен в CSS-классы, :hover и font-size сохранены). - Клик-only
→ нативные ))}
diff --git a/tradein-mvp/frontend/src/components/trade-in/v2/ResultPanel.tsx b/tradein-mvp/frontend/src/components/trade-in/v2/ResultPanel.tsx index 227d691c..179d5686 100644 --- a/tradein-mvp/frontend/src/components/trade-in/v2/ResultPanel.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/v2/ResultPanel.tsx @@ -273,9 +273,11 @@ export default function ResultPanel({ /> ))} -
onNavigate(card.nav)} + aria-label={`Подробнее — ${card.title.join(" ")}`} style={{ display: "flex", alignItems: "center", @@ -285,18 +287,24 @@ export default function ResultPanel({ letterSpacing: 0.3, color: accent, whiteSpace: "nowrap", + background: "none", + border: "none", + padding: 0, + margin: 0, + fontFamily: "inherit", }} > Подробнее -
+ ) : ( <> -
onNavigate(card.nav)} + aria-label={`Подробнее — ${card.title.join(" ")}`} style={{ - marginTop: 16, display: "flex", alignItems: "center", justifyContent: "flex-end", @@ -305,10 +313,17 @@ export default function ResultPanel({ fontSize: 9, letterSpacing: 0.3, color: accent, + width: "100%", + background: "none", + border: "none", + padding: 0, + margin: 0, + marginTop: 16, + fontFamily: "inherit", }} > Подробнее -
+
-
onNavigate(3)} + aria-label="Подробнее — цена и срок продажи" style={{ display: "flex", alignItems: "center", @@ -646,11 +663,16 @@ export default function ResultPanel({ fontSize: 9, letterSpacing: 0.3, color: accent, + background: "none", + border: "none", + padding: 0, + margin: 0, marginTop: 2, + fontFamily: "inherit", }} > Подробнее -
+ {/* right range — deals */} @@ -756,9 +778,11 @@ export default function ResultPanel({ > ИСТОЧНИКИ ДАННЫХ - onNavigate(2)} + aria-label="Подробнее — источники данных" style={{ display: "flex", alignItems: "center", @@ -767,10 +791,15 @@ export default function ResultPanel({ fontSize: 9.5, letterSpacing: 0.3, color: accent, + background: "none", + border: "none", + padding: 0, + margin: 0, + fontFamily: "inherit", }} > Подробнее - +
(null); + const lastFocused = useRef(null); + const onCloseRef = useRef(onClose); + onCloseRef.current = onClose; + + useEffect(() => { + const dialog = dialogRef.current; + // Remember the trigger, then move focus into the dialog. + lastFocused.current = document.activeElement; + dialog?.focus(); + + function getFocusable(): HTMLElement[] { + if (!dialog) return []; + const nodes = dialog.querySelectorAll( + 'button, [href], input, select, [tabindex]:not([tabindex="-1"])', + ); + return Array.from(nodes).filter((el) => !el.hasAttribute("disabled")); + } + + function onKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") { + e.preventDefault(); + onCloseRef.current(); + return; + } + if (e.key !== "Tab" || !dialog) return; + + const focusable = getFocusable(); + if (focusable.length === 0) { + // Nothing tabbable inside — keep focus pinned on the dialog itself. + e.preventDefault(); + dialog.focus(); + return; + } + + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + const activeEl = document.activeElement; + const inside = dialog.contains(activeEl); + + if (e.shiftKey) { + if (activeEl === first || activeEl === dialog || !inside) { + e.preventDefault(); + last.focus(); + } + } else if (activeEl === last || !inside) { + e.preventDefault(); + first.focus(); + } + } + + document.addEventListener("keydown", onKeyDown); + return () => { + document.removeEventListener("keydown", onKeyDown); + // Return focus to whatever opened the overlay. + (lastFocused.current as HTMLElement | null)?.focus?.(); + }; + // Mount-only: refs (dialogRef/onCloseRef) are stable, so no extra deps. + }, []); + return (
@@ -111,6 +183,7 @@ export default function SectionOverlay({ {overlayNums[active]}
-
@@ -141,7 +218,7 @@ export default function SectionOverlay({ > К ОЦЕНКЕ -
+
{/* overlay body */} diff --git a/tradein-mvp/frontend/src/components/trade-in/v2/TopNav.tsx b/tradein-mvp/frontend/src/components/trade-in/v2/TopNav.tsx index 8b967fd1..89a9d2ff 100644 --- a/tradein-mvp/frontend/src/components/trade-in/v2/TopNav.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/v2/TopNav.tsx @@ -80,8 +80,10 @@ export default function TopNav({ .tnav-tab { color: ${tokens.muted}; font-weight: 400; cursor: pointer; transition: color .2s; } .tnav-tab:hover { color: ${tokens.ink}; } .tnav-tab--active { color: ${tokens.ink}; font-weight: 600; } + .tnav-userbtn { background: none; border: none; } .tnav-userbtn:hover { background: rgba(46,139,255,.07); } .tnav-menuitem:hover { background: rgba(46,139,255,.09); } + .tnav-logout { background: none; border: none; } .tnav-logout:hover { background: rgba(205,104,104,.1); } `} @@ -184,11 +186,27 @@ export default function TopNav({ }} > {navLabels.map((label, i) => ( -
onNavigate(i)} + aria-current={active === i ? "page" : undefined} className={`tnav-tab${active === i ? " tnav-tab--active" : ""}`} - style={{ position: "relative", whiteSpace: "nowrap" }} + style={{ + position: "relative", + whiteSpace: "nowrap", + background: "none", + border: "none", + padding: 0, + margin: 0, + // NB: family/size/letterSpacing inherit for chrome reset, but do + // NOT set `font`/`color` shorthand — the .tnav-tab(--active)/:hover + // CSS must keep driving tab colour + weight. + fontFamily: "inherit", + fontSize: "inherit", + letterSpacing: "inherit", + cursor: "pointer", + }} > {label}
)} -
+ ))}
{/* User menu */} -
-
{ + if (e.key === "Escape") setUserOpen(false); + }} + > +
+ {userOpen && (
-
{ setUserOpen(false); onLogout?.(); }} className="tnav-logout" - style={{ ...menuItemStyle, color: tokens.danger }} + style={{ + ...menuItemStyle, + color: tokens.danger, + width: "100%", + fontFamily: "inherit", + textAlign: "left", + }} > Выйти -
+
)}