fix(dashboard): info popover toggle and click-outside dismiss

- Replace fixed overlay with useRef-based click-outside detection
- Add X close button in top-right corner of popover
- Click ? to toggle (open if closed, close if open)
- Click anywhere outside popover to dismiss
- Remove fixed inset-0 overlay that was blocking page interaction

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
K7ZVX 2026-05-13 16:04:36 +00:00
commit 23151f63ba
5 changed files with 2485 additions and 2463 deletions

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react'
import { useState, useEffect, useCallback, useRef } from 'react'
import NodePicker from '@/components/NodePicker'
import ChannelPicker from '@/components/ChannelPicker'
import {
@ -309,12 +309,28 @@ const US_STATES = [
{ value: 'US-WI', label: 'Wisconsin' }, { value: 'US-WY', label: 'Wyoming' },
]
// InfoButton component
// InfoButton component with click-outside dismiss and X close button
function InfoButton({ info, link, linkText = 'Learn more' }: { info: string; link?: string; linkText?: string }) {
const [open, setOpen] = useState(false)
const popoverRef = useRef<HTMLDivElement>(null)
// Close on click outside
useEffect(() => {
if (!open) return
function handleClickOutside(e: MouseEvent) {
if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) {
setOpen(false)
}
}
const timer = setTimeout(() => document.addEventListener('mousedown', handleClickOutside), 0)
return () => {
clearTimeout(timer)
document.removeEventListener('mousedown', handleClickOutside)
}
}, [open])
return (
<div className="relative inline-block">
<div className="relative inline-block" ref={popoverRef}>
<button
type="button"
onClick={(e) => { e.stopPropagation(); setOpen(!open) }}
@ -324,10 +340,16 @@ function InfoButton({ info, link, linkText = 'Learn more' }: { info: string; lin
?
</button>
{open && (
<>
<div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
<div className="absolute left-0 top-6 z-50 w-72 p-3 bg-[#1a2332] border border-[#2a3a4a] rounded-lg shadow-xl text-xs text-slate-300 leading-relaxed">
{info}
<button
type="button"
onClick={() => setOpen(false)}
className="absolute top-1 right-1 w-5 h-5 rounded hover:bg-slate-700 text-slate-500 hover:text-slate-300 inline-flex items-center justify-center transition-colors"
aria-label="Close"
>
<X size={12} />
</button>
<div className="pr-4">{info}</div>
{link && (
<a
href={link}
@ -340,7 +362,6 @@ function InfoButton({ info, link, linkText = 'Learn more' }: { info: string; lin
</a>
)}
</div>
</>
)}
</div>
)
@ -2139,6 +2160,7 @@ function EnvironmentalSection({ data, onChange }: { data: EnvironmentalConfig; o
</div>
)
}
function DashboardSection({ data, onChange }: { data: DashboardConfig; onChange: (d: DashboardConfig) => void }) {
return (
<div className="space-y-4">

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -8,8 +8,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-DrKrP8CJ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-E1oMzltW.css">
<script type="module" crossorigin src="/assets/index-DARDkZhk.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CYHGOAxN.css">
</head>
<body>
<div id="root"></div>