Fix command palette results being unclickable
The backdrop div was nested inside .modal instead of rendered as its sibling. Tabler/Bootstrap's backdrop (z-index 1050) is only supposed to sit below the modal (z-index 1055) because normally they're both direct children of the same stacking context; nesting it inside .modal instead made it establish z-index inside .modal's own stacking context, where it painted on top of .modal-dialog and silently absorbed every click meant for a result row (confirmed via elementFromPoint in a real browser: clicking a result hit .modal-backdrop, not the result div). Moved the backdrop to be a sibling rendered before .modal, matching the structure elementFromPoint now confirms resolves to the actual result element. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c1376cf1c
commit
9c07718d2d
1 file changed
+52
-45
@@ -192,57 +192,64 @@ export default function CommandPalette() {
|
||||
let lastCategory = "";
|
||||
|
||||
return (
|
||||
<div className="modal modal-blur show" style={{ display: "block" }} onClick={() => setOpen(false)}>
|
||||
<div className="modal-dialog modal-dialog-centered" style={{ maxWidth: 600 }} onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-content">
|
||||
<div className="modal-body p-0">
|
||||
<div className="p-2 border-bottom">
|
||||
<div className="input-icon">
|
||||
<span className="input-icon-addon">
|
||||
<IconSearch size={18} />
|
||||
</span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="form-control form-control-lg border-0"
|
||||
placeholder="Search servers, DNS, secrets, IPs, integrations…"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<>
|
||||
{/* Rendered as a sibling, not a child, of .modal — Tabler/Bootstrap's z-index values
|
||||
(backdrop 1050 vs modal 1055) only sort correctly when compared in the same
|
||||
stacking context. Nested inside .modal, the backdrop was created its own stacking
|
||||
context and painted on top of .modal-dialog, silently swallowing every click meant
|
||||
for a result row. */}
|
||||
<div className="modal-backdrop show"></div>
|
||||
<div className="modal modal-blur show" style={{ display: "block" }} onClick={() => setOpen(false)}>
|
||||
<div className="modal-dialog modal-dialog-centered" style={{ maxWidth: 600 }} onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-content">
|
||||
<div className="modal-body p-0">
|
||||
<div className="p-2 border-bottom">
|
||||
<div className="input-icon">
|
||||
<span className="input-icon-addon">
|
||||
<IconSearch size={18} />
|
||||
</span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="form-control form-control-lg border-0"
|
||||
placeholder="Search servers, DNS, secrets, IPs, integrations…"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ maxHeight: "60vh", overflowY: "auto" }}>
|
||||
{loading && <div className="p-3 text-secondary">Searching…</div>}
|
||||
{!loading && query.trim().length >= 2 && items.length === 0 && <div className="p-3 text-secondary">No matches.</div>}
|
||||
{!loading && query.trim().length < 2 && <div className="p-3 text-secondary">Type at least 2 characters…</div>}
|
||||
{items.map((item, index) => {
|
||||
const showHeader = item.category !== lastCategory;
|
||||
lastCategory = item.category;
|
||||
return (
|
||||
<div key={item.key}>
|
||||
{showHeader && (
|
||||
<div className="px-3 pt-2 pb-1 text-uppercase text-secondary" style={{ fontSize: "0.7rem" }}>
|
||||
{item.category}
|
||||
<div style={{ maxHeight: "60vh", overflowY: "auto" }}>
|
||||
{loading && <div className="p-3 text-secondary">Searching…</div>}
|
||||
{!loading && query.trim().length >= 2 && items.length === 0 && <div className="p-3 text-secondary">No matches.</div>}
|
||||
{!loading && query.trim().length < 2 && <div className="p-3 text-secondary">Type at least 2 characters…</div>}
|
||||
{items.map((item, index) => {
|
||||
const showHeader = item.category !== lastCategory;
|
||||
lastCategory = item.category;
|
||||
return (
|
||||
<div key={item.key}>
|
||||
{showHeader && (
|
||||
<div className="px-3 pt-2 pb-1 text-uppercase text-secondary" style={{ fontSize: "0.7rem" }}>
|
||||
{item.category}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`px-3 py-2 d-flex justify-content-between align-items-center gap-3 ${index === activeIndex ? "bg-secondary-lt" : ""}`}
|
||||
style={{ cursor: "pointer" }}
|
||||
onMouseEnter={() => setActiveIndex(index)}
|
||||
onClick={() => go(item)}
|
||||
>
|
||||
<div className="text-truncate">{item.title}</div>
|
||||
{item.subtitle && <div className="text-secondary small text-truncate">{item.subtitle}</div>}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`px-3 py-2 d-flex justify-content-between align-items-center gap-3 ${index === activeIndex ? "bg-secondary-lt" : ""}`}
|
||||
style={{ cursor: "pointer" }}
|
||||
onMouseEnter={() => setActiveIndex(index)}
|
||||
onClick={() => go(item)}
|
||||
>
|
||||
<div className="text-truncate">{item.title}</div>
|
||||
{item.subtitle && <div className="text-secondary small text-truncate">{item.subtitle}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-backdrop show"></div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user