From d76ba701b15386bd2421c243e902043035f91034 Mon Sep 17 00:00:00 2001 From: Zayooo Date: Tue, 1 Sep 2026 09:37:24 +0200 Subject: [PATCH] fix(Combobox): let the menu drop shadow render in prod (MET-3088) Two things were clipping the box shadow (set via `optionsClassName`, `0 4px 48px`) on the deployed menu: - `.options` had `clip-path: inset(0 -48px -48px -48px)` - a leftover from when the menu was an absolutely positioned sibling right below the input and its shadow could bleed up over it. Moved into the canvas-only branch (inline), where the menu is still inline and flush under the input; in prod (portalled + gapped) the clip is gone. - Headless UI's Floating UI `size` middleware stamps `overflow: auto` inline on `.positioner` (the portal root), whose scroll box clips the inner `.options` shadow. Override it back to `overflow: visible`; the inner `.options` keeps its own `max-height` + `overflow-y` so long lists still scroll. Canvas behaviour unchanged. --- src/code-components/Combobox/Combobox.module.css | 13 +++++++------ src/code-components/Combobox/Combobox.tsx | 14 +++++++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/code-components/Combobox/Combobox.module.css b/src/code-components/Combobox/Combobox.module.css index 73a6204c..151456ab 100644 --- a/src/code-components/Combobox/Combobox.module.css +++ b/src/code-components/Combobox/Combobox.module.css @@ -26,13 +26,16 @@ /* * `.positioner` is the element Headless UI portals + positions via Floating UI. - * It also gets an inline `max-width: px` from Floating UI's `size` - * middleware on every reposition, so all author-facing sizing (width via the - * `menuWidth` prop, max-width via `optionsClassName`) lives on the inner - * `.options` wrapper, which Floating UI never touches. + * Floating UI's `size` middleware stamps `overflow: auto` and a `max-width` / + * `max-height` inline on it every reposition, so all author-facing sizing (width + * via `menuWidth`, max-width via `optionsClassName`) lives on the inner + * `.options` wrapper, which Floating UI never touches. We also override that + * `overflow` back to `visible` so it doesn't clip the menu's own drop shadow - + * the inner `.options` keeps its own `max-height` + `overflow-y` for scrolling. */ .positioner { transition: opacity 0.1s ease-in; + overflow: visible !important; } .positioner[data-closed] { @@ -44,6 +47,4 @@ scrollbar-width: thin; user-select: none; margin: 0; - /* Clip the top edge so the box-shadow does not bleed up over the input. */ - clip-path: inset(0 -48px -48px -48px); } diff --git a/src/code-components/Combobox/Combobox.tsx b/src/code-components/Combobox/Combobox.tsx index 2c3b7158..108517fc 100644 --- a/src/code-components/Combobox/Combobox.tsx +++ b/src/code-components/Combobox/Combobox.tsx @@ -147,10 +147,18 @@ export function Combobox({ // `width` / `min-width` are inline so they beat any leftover `width` on // `optionsClassName` (e.g. a `width: 100%` from the design tool). `max-width` // is deliberately left to CSS so it stays overridable via `optionsClassName`. - const optionsStyle = - menuWidth === "fit" + // + // `clipPath` only in the canvas: there the menu sits flush under the input + // (inline, no gap), so clip the top of the box-shadow that would bleed over + // it. In prod the menu is portalled and gapped, so the full shadow should + // show. (Inline rather than a class so a bundler that skips reprocessing + // node_modules CSS can't drop it.) + const optionsStyle = { + ...(menuWidth === "fit" ? { width: "max-content", minWidth: "var(--input-width)" } - : { width: "var(--input-width)" }; + : { width: "var(--input-width)" }), + ...(inCanvas ? { clipPath: "inset(0 -48px -48px -48px)" } : {}), + }; const optionGroups = groupOptions(options ?? []); const visibleOptionGroups = filterOptionGroupsByQuery(optionGroups, query);