From fb9fed2b7459d42fb8ad7337685b82e6bacd537b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:27:06 +0200 Subject: [PATCH] Fix dropdown active item outline clipped by adjacent items (#32073) * Initial plan * Fix selected item outline overlapping next item in dropdown menus Replace position:relative + absolute positioning with flexbox layout on .dropdown-item to prevent stacking context overlap of active outline. Co-authored-by: aeaton-overleaf <75253002+aeaton-overleaf@users.noreply.github.com> * Fix invalid HTML - replace `div` with `span` inside dropdown item description container * Do not suppress keyboard focus outlines in toolbar dropdown menus * Add explicit keyboard focus ring for dropdown items * Avoid overlapping link focus rings with inset box-shadow --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aeaton-overleaf <75253002+aeaton-overleaf@users.noreply.github.com> Co-authored-by: Rebeka GitOrigin-RevId: ce4d1b01f04476fd154b6c05a52fc5632bf8b8dc --- .../js/features/contact-form/search.ts | 14 +++++------ .../components/dropdown/dropdown-menu.tsx | 18 +++++++-------- .../stylesheets/abstracts/mixins.scss | 3 ++- .../stylesheets/components/dropdown-menu.scss | 23 ++++++++++--------- .../stylesheets/pages/editor/toolbar.scss | 5 ---- 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/services/web/frontend/js/features/contact-form/search.ts b/services/web/frontend/js/features/contact-form/search.ts index 3e66e4f00f..9ad0d40e15 100644 --- a/services/web/frontend/js/features/contact-form/search.ts +++ b/services/web/frontend/js/features/contact-form/search.ts @@ -64,19 +64,14 @@ export function setupSearch(formEl: Element) { linkEl.setAttribute('role', 'menuitem') liEl.append(linkEl) - const contentWrapperEl = document.createElement('div') + const contentWrapperEl = document.createElement('span') contentWrapperEl.className = 'dropdown-item-description-container' linkEl.append(contentWrapperEl) - const pageNameEl = document.createElement('div') + const pageNameEl = document.createElement('span') pageNameEl.innerHTML = DOMPurify.sanitize(rawPageName) contentWrapperEl.append(pageNameEl) - const iconEl = materialIcon('open_in_new') - iconEl.classList.add('dropdown-item-trailing-icon') - iconEl.setAttribute('aria-hidden', 'true') - contentWrapperEl.append(iconEl) - if (sectionName) { const sectionEl = document.createElement('span') sectionEl.className = 'dropdown-item-description' @@ -84,6 +79,11 @@ export function setupSearch(formEl: Element) { contentWrapperEl.append(sectionEl) } + const iconEl = materialIcon('open_in_new') + iconEl.classList.add('dropdown-item-trailing-icon') + iconEl.setAttribute('aria-hidden', 'true') + linkEl.append(iconEl) + resultsContainerEl.append(liEl) } if (nbHits > 0) { diff --git a/services/web/frontend/js/shared/components/dropdown/dropdown-menu.tsx b/services/web/frontend/js/shared/components/dropdown/dropdown-menu.tsx index 36de1a3ccc..291a2ac07d 100644 --- a/services/web/frontend/js/shared/components/dropdown/dropdown-menu.tsx +++ b/services/web/frontend/js/shared/components/dropdown/dropdown-menu.tsx @@ -83,17 +83,15 @@ function DropdownItem( ref={ref} > {leadingIconComponent} -
- {children} - {trailingIconComponent} - {description && ( + {description ? ( + + {children} {description} - )} -
+ + ) : ( + children + )} + {trailingIconComponent} ) } diff --git a/services/web/frontend/stylesheets/abstracts/mixins.scss b/services/web/frontend/stylesheets/abstracts/mixins.scss index 91b9d2e81f..e8b6acff40 100644 --- a/services/web/frontend/stylesheets/abstracts/mixins.scss +++ b/services/web/frontend/stylesheets/abstracts/mixins.scss @@ -117,7 +117,8 @@ } @mixin box-shadow-button-input { - box-shadow: 0 0 0 2px var(--blue-30); + box-shadow: inset 0 0 0 2px var(--blue-30); + outline: 2px solid transparent; } @mixin animation($animation) { diff --git a/services/web/frontend/stylesheets/components/dropdown-menu.scss b/services/web/frontend/stylesheets/components/dropdown-menu.scss index 507d603ad8..6c53042d21 100644 --- a/services/web/frontend/stylesheets/components/dropdown-menu.scss +++ b/services/web/frontend/stylesheets/components/dropdown-menu.scss @@ -110,11 +110,9 @@ $dropdown-item-min-height: 36px; --bs-dropdown-item-border-radius: var(--border-radius-base); - display: grid; - grid-auto-flow: column; - place-content: center start; + display: flex; + align-items: center; min-height: $dropdown-item-min-height; // a minimum height of 36px to be accessible for touch screens - position: relative; background-color: var(--dropdown-background); &:active { @@ -131,6 +129,10 @@ $dropdown-item-min-height: 36px; background: none; } + &:focus-visible { + @include box-shadow-button-input; + } + &:hover:not(.active), &:focus-visible:not(.active), &.nested-dropdown-toggle-shown { @@ -179,17 +181,16 @@ $dropdown-item-min-height: 36px; } .dropdown-item-description-container { - grid-auto-flow: row; + display: flex; + flex-direction: column; + justify-content: center; + flex: 1; min-height: 44px; - display: grid; - place-content: center start; } .dropdown-item-trailing-icon { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); + margin-left: auto; + padding-left: var(--spacing-04); } .dropdown-item-leading-icon { diff --git a/services/web/frontend/stylesheets/pages/editor/toolbar.scss b/services/web/frontend/stylesheets/pages/editor/toolbar.scss index 48305dab87..aeea5f3b74 100644 --- a/services/web/frontend/stylesheets/pages/editor/toolbar.scss +++ b/services/web/frontend/stylesheets/pages/editor/toolbar.scss @@ -123,11 +123,6 @@ } } - > a:focus, - button:focus { - outline: none; - } - > a:not(.btn), > button, .toolbar-left > a:not(.btn),