fix(map): восстановить легенду и убрать лишние рамки фокуса

This commit is contained in:
Denozordec committed 2026-10-15 23:36:19 +07:00
1 parent 1c11a133f3
commit eb24a08981
5 files changed
+155 -10

No files matched your search

+14 -10
View File
@@ -11,6 +11,7 @@ import {Dialog,DialogContent,DialogHeader,DialogTitle,DialogDescription} from "@
import {usePrefersReducedMotion} from "@/hooks/use-prefers-reduced-motion"
import {toast} from "sonner"
import { PageHeader } from "@/components/page-header"
import { NetworkMapLegend } from "@/components/network-map-legend"
import {
servers as mockServers,
greTunnels as mockGreTunnels,
@@ -556,7 +557,7 @@ function GreEdgeMetricBadge({
"Клик — панель справа."
return (
<g
role="button" aria-label={"Метрики "+(outerSummary??"связи")} tabIndex={0} className="focus-visible:outline-2 focus-visible:outline-ring"
role="button" aria-label={"Метрики "+(outerSummary??"связи")} tabIndex={0} className="network-map-focusable"
onKeyDown={e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();e.stopPropagation();onOpen(e)}}}
transform={`translate(${mx},${my})`}
style={{ cursor: "pointer" }}
@@ -564,6 +565,7 @@ function GreEdgeMetricBadge({
onClick={(e) => { e.stopPropagation(); onOpen(e) }}
>
<title>{hint}</title>
<rect className="network-map-focus-ring" x={-bw / 2 - 4} y={-bh / 2 - 4} width={bw + 8} height={bh + 8} rx="9" fill="none" stroke="var(--ring)" strokeWidth="2" />
<rect
x={-bw / 2}
y={-bh / 2}
@@ -607,7 +609,7 @@ function NetflowRateBadge({
const bh = showDir ? 32 : 20
return (
<g
role={onOpen?"button":undefined} aria-label={onOpen?"Поток NetFlow между узлами":undefined} tabIndex={onOpen?0:undefined} className="focus-visible:outline-2 focus-visible:outline-ring"
role={onOpen?"button":undefined} aria-label={onOpen?"Поток NetFlow между узлами":undefined} tabIndex={onOpen?0:undefined} className="network-map-focusable"
onKeyDown={e=>{if(onOpen&&(e.key==="Enter"||e.key===" ")){e.preventDefault();e.stopPropagation();onOpen(e)}}}
transform={`translate(${mx},${my})`}
style={{ cursor: onOpen ? "pointer" : "default" }}
@@ -617,6 +619,7 @@ function NetflowRateBadge({
<title>
Поток NetFlow между узлами (как в «Трафик»: 5 мин, без overlay/mesh). Скорость канала — отдельно.
</title>
<rect className="network-map-focus-ring" x={-bw / 2 - 4} y={-bh / 2 - 4} width={bw + 8} height={bh + 8} rx="9" fill="none" stroke="var(--ring)" strokeWidth="2" />
<rect
x={-bw / 2}
y={-bh / 2}
@@ -687,7 +690,7 @@ function ServerNode({ labelScale = 1, n, isSel, isVis, isDragged, hideCatalogLat
const ts = TYPE_STYLE[n.type]
return (
<g className="focus-visible:outline-2 focus-visible:outline-ring" role="button" aria-label={"Устройство "+n.name} aria-pressed={isSel} tabIndex={isVis?0:-1}
<g className="network-map-focusable" role="button" aria-label={"Устройство "+n.name} aria-pressed={isSel} tabIndex={isVis?0:-1}
onKeyDown={e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();e.stopPropagation();onClick()}}}
transform={`translate(${n.x},${n.y})`}
style={{ cursor: isDragged ? "grabbing" : "grab", transition: isDragged ? "none" : "opacity 0.25s" }}
@@ -699,6 +702,7 @@ function ServerNode({ labelScale = 1, n, isSel, isVis, isDragged, hideCatalogLat
onMouseEnter={() => onHoverChange(true)}
onMouseLeave={() => onHoverChange(false)}
>
<circle className="network-map-focus-ring" r={ts.r + 6} fill="none" stroke="var(--ring)" strokeWidth="2" />
{n.status === "online" && isVis && (
<circle r={ts.r + 14} fill={ss.glow} opacity="0.7">
</circle>
@@ -707,7 +711,7 @@ function ServerNode({ labelScale = 1, n, isSel, isVis, isDragged, hideCatalogLat
<circle r={ts.r + 12} fill={ss.glow} />
)}
{isSel && (
<circle r={ts.r + 10} fill="none" stroke="var(--foreground)" strokeWidth="1.5" strokeDasharray="4 3" />
<circle r={ts.r + 10} fill="none" stroke="var(--foreground)" strokeWidth="1.5" strokeDasharray="4 3" opacity="0.45" />
)}
<circle r={ts.r} fill={ss.fill} stroke={ss.stroke} strokeWidth={isSel ? 2.5 : 1.8} />
<g transform={`translate(${ts.r - 10},-${ts.r - 10})`}>
@@ -807,7 +811,7 @@ function ServiceNode({
const bh = MAP_SERVICE_NODE_H
const flagIso = destMode === "countries" && iso && iso !== "Прочее" ? iso : ""
return (
<g role="button" aria-label={(destMode==="countries"?"Страна ":"Сервис ")+label} aria-pressed={isSel} tabIndex={isVis?0:-1} className="focus-visible:outline-2 focus-visible:outline-ring"
<g role="button" aria-label={(destMode==="countries"?"Страна ":"Сервис ")+label} aria-pressed={isSel} tabIndex={isVis?0:-1} className="network-map-focusable"
onKeyDown={e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();e.stopPropagation();onClick()}}}
transform={`translate(${x},${y})`}
style={{ cursor: isDragged ? "grabbing" : "grab", transition: isDragged ? "none" : "opacity 0.25s" }}
@@ -816,6 +820,7 @@ function ServiceNode({
onClick={(e) => { e.stopPropagation(); onClick() }}
>
<title>{`${label} · ${serviceSharePct(share)} ${shareLabel ?? "payload окна"}`}</title>
<rect className="network-map-focus-ring" x={-bw / 2 - 3} y={-bh / 2 - 3} width={bw + 6} height={bh + 6} rx="9" fill="none" stroke="var(--ring)" strokeWidth="2" />
{isSel && (
<rect
x={-bw / 2 - 6}
@@ -925,12 +930,13 @@ function WanSatNode({ x, y, wan, color, active, isSel, isDragged, onSelect, onPo
}) {
const r = 18
return (
<g role="button" aria-label={"Аплинк "+wan.name} aria-pressed={isSel} tabIndex={0} className="focus-visible:outline-2 focus-visible:outline-ring"
<g role="button" aria-label={"Аплинк "+wan.name} aria-pressed={isSel} tabIndex={0} className="network-map-focusable"
onKeyDown={e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();e.stopPropagation();onSelect()}}}
transform={`translate(${x},${y})`}
style={{ cursor: isDragged ? "grabbing" : "grab" }}
onPointerDown={e => { e.stopPropagation(); onPointerDown(e) }}
onClick={e => { e.stopPropagation(); onSelect() }}>
<circle className="network-map-focus-ring" r={r + 4} fill="none" stroke="var(--ring)" strokeWidth="2" />
{active && (
<circle r={r + 6} fill={color} opacity="0.12">
</circle>
@@ -1718,6 +1724,7 @@ function NetworkMapContent() {
if (!svg) return
// Clone with full viewBox (reset pan/zoom)
const clone = svg.cloneNode(true) as SVGSVGElement
clone.querySelectorAll(".network-map-focus-ring").forEach(ring => ring.remove())
clone.setAttribute("viewBox", `0 0 ${W} ${H}`)
clone.setAttribute("width", String(W))
clone.setAttribute("height", String(H))
@@ -2252,10 +2259,7 @@ function NetworkMapContent() {
{/* ── Canvas ── */}
<div ref={canvasRef} className={cn("flex-1 min-h-0 min-w-0 relative overflow-hidden bg-background select-none", (selected || selectedGreEdge || selectedService) && "hidden lg:block")}>
<details className="absolute top-3 left-3 z-20 rounded-md border bg-popover p-2 text-xs shadow-sm">
<summary className="cursor-pointer focus-visible:outline-2 focus-visible:outline-ring">Обозначения карты</summary>
<div className="mt-2 space-y-1"><p>HR · домашний роутер</p><p>JH · промежуточный узел</p><p>EN · выходной узел</p><p>Круг · устройство / WAN</p><p>Прямоугольник · сервис / страна</p><p>Пунктир WAN → JH · модель связности</p><p>Цвет · статус; неизвестно ≠ оффлайн</p></div>
</details>
<NetworkMapLegend />
<p id="network-map-help" className="sr-only">Стрелки перемещают карту, плюс и минус меняют масштаб, F вписывает объекты. Tab выбирает объект, Enter открывает детали.</p>
<svg
ref={svgRef} role="group" aria-label="Интерактивная карта сети" tabIndex={0} aria-describedby="network-map-help"
+12
View File
@@ -2,6 +2,18 @@
@import "tw-animate-css";
@import "shadcn/tailwind.css";
/* SVG focus follows the object shape, never the text/foreignObject bounding box. */
.network-map-focusable {
outline: none;
}
.network-map-focus-ring {
opacity: 0;
pointer-events: none;
}
.network-map-focusable:focus-visible > .network-map-focus-ring {
opacity: 1;
}
@custom-variant dark (&:is(.dark *));
@theme inline {