fix: replace Portal with inline block to avoid Radix pointer-event capture

This commit is contained in:
null 2026-06-04 01:50:40 -05:00
parent 68667fea59
commit d04f03b6b1
1 changed files with 26 additions and 38 deletions

View File

@ -8,7 +8,6 @@ import {
import { api } from '@/api'; import { api } from '@/api';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import * as Portal from '@radix-ui/react-portal';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
// Debounce helper // Debounce helper
@ -256,44 +255,33 @@ export default function BillMerchantRules({ billId, onRulesChanged }) {
</Button> </Button>
</div> </div>
{/* Suggestions rendered in a Portal so the list escapes the BillModal's {/* Suggestions inline block, no absolute positioning.
overflow-y-auto container and is fully clickable regardless of scroll */} Avoids overflow-y-auto clipping AND Radix Dialog pointer-event capture. */}
{showSuggestions && filteredSuggestions.length > 0 && inputRef.current && ( {showSuggestions && filteredSuggestions.length > 0 && (
<Portal.Root> <div className="overflow-hidden rounded-lg border border-border/80 bg-card shadow-sm">
<div <p className="border-b border-border/50 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
style={{ Recent unmatched transactions
position: 'fixed', </p>
top: inputRef.current.getBoundingClientRect().bottom + 4, <div className="max-h-40 overflow-y-auto">
left: inputRef.current.getBoundingClientRect().left, {filteredSuggestions.map(s => {
width: inputRef.current.getBoundingClientRect().width, const amountVal = Math.abs(Number(s.amount || 0)) / 100;
zIndex: 9999, return (
}} <button
className="overflow-hidden rounded-lg border border-border/80 bg-card shadow-md" key={s.id}
> type="button"
<p className="border-b border-border/50 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> className="flex w-full items-center gap-3 px-3 py-2 text-left text-xs hover:bg-muted/50 focus:bg-muted/50 focus:outline-none"
Recent unmatched transactions onMouseDown={e => { e.preventDefault(); pickSuggestion(s); }}
</p> >
<div className="max-h-48 overflow-y-auto"> <Building2 className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
{filteredSuggestions.map(s => { <span className="min-w-0 flex-1 truncate font-medium">{s.label}</span>
const amountVal = Math.abs(Number(s.amount || 0)) / 100; <span className="shrink-0 font-mono text-muted-foreground tabular-nums">
return ( ${amountVal.toFixed(2)}
<button </span>
key={s.id} </button>
type="button" );
className="flex w-full items-center gap-3 px-3 py-2 text-left text-xs hover:bg-muted/50 focus:bg-muted/50 focus:outline-none" })}
onMouseDown={e => { e.preventDefault(); pickSuggestion(s); }}
>
<Building2 className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="min-w-0 flex-1 truncate font-medium">{s.label}</span>
<span className="shrink-0 font-mono text-muted-foreground tabular-nums">
${amountVal.toFixed(2)}
</span>
</button>
);
})}
</div>
</div> </div>
</Portal.Root> </div>
)} )}
{/* Conflict warning */} {/* Conflict warning */}