fix(ui): prevent skill autocomplete menu clipping (#1488)

This commit is contained in:
Leonardo Reis
2026-09-10 20:00:34 -03:00
committed by GitHub
parent ba1d5207d3
commit 680b6dd0ed
2 changed files with 30 additions and 4 deletions
+9 -4
View File
@@ -529,6 +529,7 @@ export const CommentPopover: React.FC<CommentPopoverProps> = ({
hasUnsavedContent ||
(allowEmptySubmit && initialText.trim().length > 0);
const canAskAI = !!onAskAI && !askAIDisabled && text.trim().length > 0;
const showsSkillMenu = skillAc.menu !== null;
// Shared by both footers. Disabled once anything is typed or attached so a
// click can never discard a draft; with content present, Save is the path.
@@ -570,7 +571,9 @@ export const CommentPopover: React.FC<CommentPopoverProps> = ({
aria-modal="true"
aria-label={isGlobal ? 'Global comment' : 'Comment'}
tabIndex={-1}
className="relative w-full max-w-xl max-h-full min-h-0 bg-popover border border-border rounded-xl shadow-2xl flex flex-col overflow-hidden"
className={`relative w-full max-w-xl max-h-full min-h-0 bg-popover border border-border rounded-xl shadow-2xl flex flex-col ${
showsSkillMenu ? 'overflow-visible' : 'overflow-hidden'
}`}
style={{
animation: 'comment-dialog-in 0.15s ease-out',
}}
@@ -617,7 +620,9 @@ export const CommentPopover: React.FC<CommentPopoverProps> = ({
{chipsRow}
{/* Textarea */}
<div className="relative px-4 py-3 min-h-0 flex-1 overflow-y-auto">
<div className={`relative px-4 py-3 min-h-0 flex-1 ${
showsSkillMenu ? 'overflow-visible' : 'overflow-y-auto'
}`}>
{skillAc.menu && (
<SkillReferenceMenu
id={skillListboxId}
@@ -713,14 +718,14 @@ export const CommentPopover: React.FC<CommentPopoverProps> = ({
left: dragPosition.left,
width: position.width,
maxHeight: visibleBounds.height,
overflowY: 'auto',
overflowY: showsSkillMenu ? 'visible' : 'auto',
}
: {
top: position.top,
left: position.left,
width: position.width,
maxHeight: position.maxHeight,
overflowY: 'auto',
overflowY: showsSkillMenu ? 'visible' : 'auto',
...(position.flipAbove ? { transform: 'translateY(-100%)' } : {}),
animation: position.flipAbove
? 'comment-popover-in-above 0.15s ease-out'
@@ -139,6 +139,18 @@ function list(): HTMLElement {
return el;
}
function clippingAncestor(el: HTMLElement): HTMLElement | null {
let ancestor = el.parentElement;
while (ancestor && ancestor !== document.body) {
const style = window.getComputedStyle(ancestor);
const clipsOverflow = [style.overflow, style.overflowX, style.overflowY]
.some((value) => /^(auto|clip|hidden|scroll)$/.test(value));
if (clipsOverflow) return ancestor;
ancestor = ancestor.parentElement;
}
return null;
}
function makeRect(top: number, bottom: number): DOMRect {
return {
x: 100,
@@ -208,6 +220,15 @@ function assertMenuInsideViewport(): { direction: string; maxListHeight: number
}
describe('SkillReferenceMenu adaptive placement', () => {
test.skipIf(!hasDom)(
'the menu escapes the comment card overflow instead of being clipped at its edge',
async () => {
await mountPopover();
await type(textarea(), '$');
expect(clippingAncestor(menu()) === null).toBe(true);
},
);
test.skipIf(!hasDom)(
'THE bug: composer near the top of the viewport opens the menu BELOW, on screen',
async () => {