import { useEffect, useRef } from 'react'; interface Props { technical: string[]; soft: string[]; } export default function PatchBay({ technical, soft }: Props) { const containerRef = useRef(null); const svgRef = useRef(null); useEffect(() => { const container = containerRef.current; const svg = svgRef.current; if (!container || !svg) return; function drawCables() { if (!svg || !container) return; const rect = container.getBoundingClientRect(); const width = rect.width; const height = rect.height; svg.setAttribute('viewBox', `0 0 ${width} ${height}`); svg.setAttribute('width', String(width)); svg.setAttribute('height', String(height)); // Find left + right jack positions const leftJacks = container.querySelectorAll('[data-jack="left"]'); const rightJacks = container.querySelectorAll('[data-jack="right"]'); const cr = container.getBoundingClientRect(); const leftPoints = Array.from(leftJacks).map((el) => { const r = el.getBoundingClientRect(); return { x: r.right - cr.left, y: r.top + r.height / 2 - cr.top }; }); const rightPoints = Array.from(rightJacks).map((el) => { const r = el.getBoundingClientRect(); return { x: r.left - cr.left, y: r.top + r.height / 2 - cr.top }; }); // Build cable paths pairing left to right const colors = ['var(--color-cyan)', 'var(--color-magenta)', 'var(--color-acid)']; svg.innerHTML = ''; leftPoints.forEach((p1, i) => { const pairIdx = i % rightPoints.length; const p2 = rightPoints[pairIdx]; if (!p2) return; const dx = p2.x - p1.x; const sag = 20 + (i % 3) * 8; const midY = (p1.y + p2.y) / 2 + sag; const c1x = p1.x + dx * 0.4; const c2x = p1.x + dx * 0.6; const d = `M ${p1.x} ${p1.y} C ${c1x} ${midY}, ${c2x} ${midY}, ${p2.x} ${p2.y}`; const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', d); path.setAttribute('fill', 'none'); path.setAttribute('stroke', colors[i % colors.length]); path.setAttribute('stroke-width', '1.6'); path.setAttribute('stroke-opacity', '0.55'); path.style.filter = `drop-shadow(0 0 4px ${colors[i % colors.length]})`; svg.appendChild(path); // Plug tips [p1, p2].forEach((p) => { const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.setAttribute('cx', String(p.x)); circle.setAttribute('cy', String(p.y)); circle.setAttribute('r', '3'); circle.setAttribute('fill', colors[i % colors.length]); circle.style.filter = `drop-shadow(0 0 6px ${colors[i % colors.length]})`; svg.appendChild(circle); }); }); } drawCables(); const ro = new ResizeObserver(drawCables); ro.observe(container); window.addEventListener('resize', drawCables); return () => { ro.disconnect(); window.removeEventListener('resize', drawCables); }; }, [technical.length, soft.length]); return (
{/* Left panel : technical */}

Techniques

    {technical.map((s) => (
  • {s}
  • ))}
{/* Middle : SVG cables */}
{/* Right panel : soft */}

Transverses

    {soft.map((s) => (
  • {s}
  • ))}
); }