shared dropdown: don't reopen when the trigger is clicked while open
mara, #3775: clicking a badge with its own dropdown open reopens it instead of closing it. Root cause: the outside-click listener only excludes the dropdown's own ref, not the sibling trigger that opened it — a click on the trigger closes via that listener (pointerdown fires first), then the trigger's own onClick toggle fires straight after and reopens it, since its closure reads the pre-close state. Dropdown now takes an optional anchorRef (the trigger's wrapper, which callers already have for CSS positioning) and excludes it from the outside-click check too — the same shape MetaNav's own hand-rolled popover already uses correctly. Wired into StatusChips's Picker + StatusMenu and swarm-ui's ComponentsPage demo (the only three Dropdown consumers). Verified two ways: reverted the fix, rebuilt, confirmed the bug reproduces via a raw-CDP interaction test (two real clicks dispatched through headless chromium, not just a static screenshot); restored the fix, rebuilt, confirmed it passes.
This commit is contained in:
parent
e8813b86eb
commit
b1254f89cd
3 changed files with 33 additions and 8 deletions
|
|
@ -10,7 +10,7 @@
|
|||
// selection state live in the caller (`Root.tsx`, via the
|
||||
// `useAgentState` hook), so this component can still be demoed and
|
||||
// reviewed against plain sample data independent of live `/api/state`.
|
||||
import { useState } from 'preact/hooks';
|
||||
import { useRef, useState } from 'preact/hooks';
|
||||
import { Badge, type BadgeTone } from '@hive/shared/badge.js';
|
||||
import { Dropdown, type DropdownOption } from '@hive/shared/dropdown.js';
|
||||
import './StatusChips.css';
|
||||
|
|
@ -68,13 +68,14 @@ function Picker({
|
|||
title?: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
const dropdownOptions: DropdownOption[] = options.map((name) => ({
|
||||
value: name,
|
||||
label: name,
|
||||
description: descriptions[name] || undefined,
|
||||
}));
|
||||
return (
|
||||
<div class="status-chip-anchor">
|
||||
<div class="status-chip-anchor" ref={anchorRef}>
|
||||
<Badge label={label} value={value} onClick={() => setOpen((o) => !o)} expanded={open} title={title} />
|
||||
<Dropdown
|
||||
open={open}
|
||||
|
|
@ -86,6 +87,7 @@ function Picker({
|
|||
setOpen(false);
|
||||
}}
|
||||
onClose={() => setOpen(false)}
|
||||
anchorRef={anchorRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -112,6 +114,7 @@ function StatusMenu({
|
|||
>) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [confirmCancel, setConfirmCancel] = useState(false);
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function close() {
|
||||
setOpen(false);
|
||||
|
|
@ -128,7 +131,7 @@ function StatusMenu({
|
|||
}
|
||||
|
||||
return (
|
||||
<div class="status-chip-anchor">
|
||||
<div class="status-chip-anchor" ref={anchorRef}>
|
||||
<Badge
|
||||
value={statusLabel}
|
||||
tone={statusTone}
|
||||
|
|
@ -155,6 +158,7 @@ function StatusMenu({
|
|||
close();
|
||||
}}
|
||||
onClose={close}
|
||||
anchorRef={anchorRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue