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);