Skip to content

Migrate Breadcrumbs internals to PopoverNext via compat shim#8090

Closed
ggdouglas wants to merge 3 commits into
developfrom
gd/breadcrumbs-popover-next-shim
Closed

Migrate Breadcrumbs internals to PopoverNext via compat shim#8090
ggdouglas wants to merge 3 commits into
developfrom
gd/breadcrumbs-popover-next-shim

Conversation

@ggdouglas

@ggdouglas ggdouglas commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Context

PopoverNext was introduced in palantir/blueprint#7573 as the Floating-UI–based replacement for the legacy Popper.js-backed Popover. That PR explicitly defers migrating the components that wrap Popover internally and re-expose it via a popoverProps pass-through. Migration utilities and codemods were called out as forthcoming follow-ups.

PopoverProps and PopoverNextProps are similar but not type-compatible. A handful of props were renamed, a few were dropped, and two defaults shifted. So a direct in-place swap of <Popover> for <PopoverNext> doesn't work: any popoverProps bag a consumer passes is typed against the legacy shape, and a few of those legacy fields just don't exist on the new component.

This PR migrates Breadcrumbs and lands a reusable shim along the way. Breadcrumbs is the smallest/easiest case, which makes it a good blueprint: the same shim and the same wiring pattern should carry the rest of the deferred component migrations with very little per-component work. The shim is also useful to external consumers migrating their own code.

The aim: swap Breadcrumbs' internal Popover for PopoverNext with no changes to its public API and no observable behavior change for existing consumers.

What changed

1. New popoverPropsToNextProps() migration utility

packages/core/src/components/popover-next/popoverNextMigrationUtils.ts

export function popoverPropsToNextProps<T extends DefaultPopoverTargetHTMLProps>(
    props: Partial<PopoverProps<T>>,
): Partial<PopoverNextProps<T>>

It lives next to the two migration helpers already in this file (popoverPositionToNextPlacement, popperModifiersToNextMiddleware) and reuses them internally. Exported from packages/core/src/components/index.ts so external consumers can use it for their own migration.

2. Breadcrumbs swapped to PopoverNext

packages/core/src/components/breadcrumbs/breadcrumbs.tsx

The internal overflow-menu <Popover> is now a <PopoverNext>. Consumer-supplied popoverProps go through popoverPropsToNextProps() before being spread. The shim runs even when no popoverProps are passed (popoverProps ?? {}), so the legacy default for shouldReturnFocusOnClose: false always applies and consumers don't see a shift.

The public BreadcrumbsProps interface is unchanged. popoverProps continues to accept the legacy PopoverProps shape.

Prop mapping reference

The legacy PopoverProps exposes ~47 props once you include everything inherited from PopoverSharedProps, OverlayableProps, OverlayLifecycleProps, and Props. The shim handles them in four buckets.

Category Count Props Handling
1:1 pass-through 39 children, content, isOpen, defaultIsOpen, disabled, usePortal, interactionKind, popupKind, hasBackdrop, backdropProps, captureDismiss, canEscapeKeyClose, enforceFocus, lazy, transitionDuration, placement, popoverClassName, portalClassName, portalContainer, inheritDarkTheme, matchTargetWidth, hoverOpenDelay, hoverCloseDelay, openOnTargetFocus, autoFocus, fill, targetTagName, targetProps, renderTarget, rootBoundary, positioningStrategy, onClose, onClosed, onClosing, onOpened, onOpening, onInteraction, className Spread as-is
Transformed 4 position to placement Via popoverPositionToNextPlacement(). If both position and placement are present, placement wins (matches legacy mutex semantics).
modifiers to middleware Via popperModifiersToNextMiddleware()
minimal: true to animation: "minimal" plus arrow: false Legacy minimal both uses the subtler animation and disables the arrow
boundary: "clippingParents" to "clippingAncestors" Floating UI's semantic equivalent. Element and Element[] pass through unchanged.
Pinned defaults 1 shouldReturnFocusOnClose ?? false Legacy default is false; PopoverNext defaults to true. Pinned so the swap is invisible.
Dropped (dev console.warn) 3 modifiersCustom No Floating UI equivalent; consumers must migrate manually to middleware.
popoverRef PopoverNext's forwardRef exposes { reposition }, not the popover DOM element.
portalStopPropagationEvents Already deprecated; non-functional under React 17+.

Note that boundary itself isn't explicitly pinned. PopoverNext's default of "clippingAncestors" is the Floating UI equivalent of legacy's "clippingParents". The strings differ; the semantics don't. Pinning would have been a no-op.

Behavior preservation

For Breadcrumbs specifically, the only popoverProps fields the existing test suite exercises are isOpen and usePortal, both 1:1. There are no doc-app examples that pass popoverProps. The plausible consumer-visible behavior shifts come down to three spots, and each is mitigated:

  1. shouldReturnFocusOnClose default. Legacy is false, PopoverNext is true. The shim runs unconditionally and pins it to false.
  2. Boundary semantics. Legacy "clippingParents" and new "clippingAncestors" are the equivalent concept in Floating UI, so no remap is needed when the consumer doesn't supply a boundary.
  3. position: "auto". Converts to placement: undefined, which makes PopoverNext fall back to its autoPlacement middleware. Same end-user behavior as legacy's flip-based auto.

Verification

Manual UI checks worth running before merge (not covered automatically):

  • Render Breadcrumbs with overflow and click the collapsed indicator. Default placement: bottom-start.
  • collapseFrom={Boundary.END} should produce bottom-end.
  • popoverProps={{ usePortal: false }}: menu mounts inline. This is the path the two skipped tests exercise.
  • popoverProps={{ minimal: true }}: no arrow, minimal animation.
  • popoverProps={{ position: "right" }}: shim converts to placement: "right".
  • popoverProps={{ modifiersCustom: [] }} in dev mode: console warning fires.

Caveats and follow-ups

None of these block Breadcrumbs, but they're worth tracking as a heads-up for the next component migrations that pick up this shim.

The popoverRef gap is the one I'd flag first. Legacy consumers that reach into the popover DOM via popoverRef lose that capability; PopoverNext's forwarded ref exposes only { reposition }. If a future component migration runs into a consumer that actually uses popoverRef, that needs a separate fix, probably another ref-forwarding layer in PopoverNext that surfaces the floating element.

The modifiersCustom gap is the same shape: arbitrary Popper.js modifier objects can't be machine-translated into Floating UI middleware. Anything reaching for it has to migrate manually to middleware.

portalStopPropagationEvents doesn't work under React 17+ anyway, so dropping it is a no-op in practice. The dev warning exists for discoverability.

For the wider migration, the pattern Breadcrumbs follows here is the template: swap <Popover> for <PopoverNext> and spread popoverPropsToNextProps(popoverProps ?? {}). Each future component has its own internal Popover call to swap, but the public-facing popoverProps API can stay frozen.

@changelog-app

changelog-app Bot commented Apr 28, 2026

Copy link
Copy Markdown

Generate changelog in packages/core/changelog/@unreleased

Type (Select exactly one)

  • Feature (Adding new functionality)
  • Improvement (Improving existing functionality)
  • Fix (Fixing an issue with existing functionality)
  • Break (Creating a new major version by breaking public APIs)
  • Deprecation (Removing functionality in a non-breaking way)
  • Migration (Automatically moving data/functionality to a new system)

Description

Migrate Breadcrumbs internals to PopoverNext via compat shim

Check the box to generate changelog(s)

  • Generate changelog entry

@blueprint-previews

Copy link
Copy Markdown

feat(core): migrate Breadcrumbs to PopoverNext via compat shim

Build artifact links for this commit: documentation | landing | table | demo | storybook

This is an automated comment from the deploy-preview CircleCI job.

@ggdouglas ggdouglas force-pushed the gd/breadcrumbs-popover-next-shim branch from 6bb8a23 to 2556609 Compare May 5, 2026 16:33
@blueprint-previews

Copy link
Copy Markdown

Update compat shim for stricter typing

Build artifact links for this commit: documentation | landing | table | demo | storybook

This is an automated comment from the deploy-preview CircleCI job.

@ggdouglas ggdouglas closed this May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants