Skip to content

[PF-1994] Migrate Popper to @base-ui/react + Tailwind#5001

Open
vedrani wants to merge 3 commits into
feature/picasso-modernization-tempfrom
migrate-Popper-v1
Open

[PF-1994] Migrate Popper to @base-ui/react + Tailwind#5001
vedrani wants to merge 3 commits into
feature/picasso-modernization-tempfrom
migrate-Popper-v1

Conversation

@vedrani

@vedrani vedrani commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Popper — migrate MUI v4 Popper to @floating-ui/react

Summary

Re-implements @toptal/picasso-popper on @floating-ui/react (decision LOCKED in docs/migration/decisions/popper-replacement.md@base-ui/react ships no standalone Popper). The public Props surface is preserved verbatim; popperOptions is re-typed as a Picasso-native, popper.js-v1-shaped structural subset so all five internal consumers (Dropdown, DatePicker, Autocomplete, Menu, Select) compile and behave unchanged without source edits. popper.js modifiers map to floating-ui middleware (flipflip, preventOverflowshift, offset string format parsed to crossAxis/mainAxis, hidehide + the legacy x-out-of-boundaries attribute), and the onCreate/onUpdate lifecycle is replicated via isPositioned.

Decisions

  • Picasso-native PopperOptions type instead of dropping the prop: consumers pass popper.js-typed objects (DatePicker POPPER_OPTIONS, Dropdown/Autocomplete popperOptions props); the structural subset keeps them assignable under strictFunctionTypes (lifecycle callbacks typed (data: never) => void because Dropdown's spread synthesizes property-style members, losing method bivariance). Verified by repo tsc -b of all 90 packages.
  • ref exposes a minimal PopperHandle (popper, update, scheduleUpdate): DatePicker and Select's blur handling read popperRef.current?.popper.contains(...); the handle is set only while the popper DOM is mounted, matching MUI v4 popperRef semantics. useRef<PopperJs> in consumers stays type-compatible (covariant RefObject).
  • Kept getPopperOptions export + parity attributes (role="tooltip", x-placement, x-out-of-boundaries): the component's own [&[x-out-of-boundaries]]:hidden Tailwind selector and existing tests rely on them; default hide/flip/preventOverflow enablement matches popper.js defaults per getPopperOptions.
  • FloatingPortal for the portal path: SSR-safe deferred mounting like MUI's Portal; adds one unstyled data-floating-ui-portal wrapper inside the container (consumer-visible DOM change, named in the changeset). versionBump: major taken verbatim from manifest.json.
  • classes handling: no-op per docs/migration/components/Popper.md — Popper extends BaseProps, no classes prop existed (source + audit §4 agree).

Limitations / Out-of-scope

  • popper.js options outside the mapped subset (positionFixed, eventsEnabled, custom modifier fns, arrow, applyStyle) are accepted by older typings but ignored at runtime; repo-wide grep found no usage. External-consumer codemods belong to PF-1995.
  • boundariesElement: 'scrollParent' maps to floating-ui's default clipping-ancestors detection (closest equivalent, not identical math).
  • Popper has no Storybook story or Cypress spec of its own, so the local Happo/Cypress gate signal comes via consumers; CI's full Happo suite diffs the consumer stories.
  • Local Storybook runtime check could not run: the orchestrator-started dev server on :9001 holds a pre-pnpm install webpack graph referencing a node_modules path the install re-laid-out (ENOENT on @mui/base/node_modules/@floating-ui/core), watch ignores node_modules, and process restart is not permitted in this session. Runtime verification was done via Cypress instead (below). Operator: restart pnpm start before eyeballing stories.

Verification

  • Local gate stages passed: pnpm -F @toptal/picasso-popper build:package, full pnpm build:package (90 projects), repo pnpm typecheck, davinci-syntax lint code --check (0 errors/0 warnings), Popper jest suite (10 behavioral tests, rewritten off the old MUIPopper mock), consumer jest suites (82 suites / 379 tests; 2 Dropdown snapshots regenerated — FloatingPortal wrapper + floating-ui transform style).
  • Runtime check (Cypress, real browser): pnpm test:integration --spec Dropdown,Select,Autocomplete,DatePicker,Menu — 62/62 passing; exercises Dropdown focus-on-open (onCreate), Select blur-via-handle.popper.contains, Menu submenu offset parsing, DatePicker calendar popper.
  • Visual parity: not verifiable locally (no Popper stories; dev-server issue above). Happo authority defers to the gate/CI run on consumer stories.

Mechanical diff evidence

Auto-generated by bin/migration-diff.sh report from pre/post snapshots. The agent's narrative is above; this section is the file-level facts.

Popper migration diff

Generated: 2026-06-10 13:42:18 CEST

Package: packages/base/Popper

Files

+packages/base/Popper/src/Popper/popper-options.ts
+packages/base/Popper/src/Popper/use-popper-handle.ts
+packages/base/Popper/src/Popper/use-popper-lifecycle.ts

Imports

Removed:

packages/base/Popper/dist-package/src/Popper/Popper.d.ts:import type PopperJs from 'popper.js';
packages/base/Popper/dist-package/src/Popper/Popper.d.ts:import type { ReferenceObject, PopperOptions } from 'popper.js';
packages/base/Popper/src/Popper/Popper.tsx:import React, { forwardRef, useContext } from 'react'
packages/base/Popper/src/Popper/Popper.tsx:import type PopperJs from 'popper.js'
packages/base/Popper/src/Popper/Popper.tsx:import type { ReferenceObject, PopperOptions } from 'popper.js'
packages/base/Popper/src/Popper/Popper.tsx:import { Popper as MUIPopper } from '@material-ui/core'

Added:

packages/base/Popper/dist-package/src/Popper/Popper.d.ts:export type { PopperHandle } from './use-popper-handle';
packages/base/Popper/dist-package/src/Popper/Popper.d.ts:export type { PopperModifierOptions, PopperModifiers, PopperOptions, PopperPadding, } from './popper-options';
packages/base/Popper/dist-package/src/Popper/Popper.d.ts:export { getPopperOptions } from './popper-options';
packages/base/Popper/dist-package/src/Popper/Popper.d.ts:import type { PopperHandle } from './use-popper-handle';
packages/base/Popper/dist-package/src/Popper/Popper.d.ts:import type { PopperOptions } from './popper-options';
packages/base/Popper/dist-package/src/Popper/popper-options.d.ts:import type { Middleware, MiddlewareData, Placement } from '@floating-ui/react';
packages/base/Popper/dist-package/src/Popper/use-popper-handle.d.ts:import type { ForwardedRef } from 'react';
packages/base/Popper/dist-package/src/Popper/use-popper-lifecycle.d.ts:import type { PopperLifecycleCallback } from './popper-options';
packages/base/Popper/src/Popper/Popper.tsx:export type { PopperHandle } from './use-popper-handle'
packages/base/Popper/src/Popper/Popper.tsx:export { getPopperOptions } from './popper-options'
packages/base/Popper/src/Popper/Popper.tsx:import React, { forwardRef, useContext, useMemo } from 'react'
packages/base/Popper/src/Popper/Popper.tsx:import type { PopperHandle } from './use-popper-handle'
packages/base/Popper/src/Popper/Popper.tsx:import type { PopperOptions } from './popper-options'
packages/base/Popper/src/Popper/Popper.tsx:import { autoUpdate, FloatingPortal, useFloating } from '@floating-ui/react'
packages/base/Popper/src/Popper/Popper.tsx:import { usePopperHandle } from './use-popper-handle'
packages/base/Popper/src/Popper/Popper.tsx:import { usePopperLifecycle } from './use-popper-lifecycle'
packages/base/Popper/src/Popper/popper-options.ts:import { flip, hide, offset, shift } from '@floating-ui/react'
packages/base/Popper/src/Popper/use-popper-handle.ts:import type { ForwardedRef } from 'react'
packages/base/Popper/src/Popper/use-popper-handle.ts:import { useCallback, useRef } from 'react'
packages/base/Popper/src/Popper/use-popper-lifecycle.ts:import type { PopperLifecycleCallback } from './popper-options'
packages/base/Popper/src/Popper/use-popper-lifecycle.ts:import { useEffect, useRef } from 'react'

MUI v4 / JSS residue check

Check Count
@material-ui/* source imports 0
JSS calls (makeStyles/createStyles/withStyles) 0
@material-ui/core in package.json 0
0

Migration is NOT complete until all three are 0.

package.json delta

@@ -22,20 +22,19 @@
   },
   "homepage": "https://github.com/toptal/picasso/tree/master/packages/picasso#readme",
   "dependencies": {
+    "@floating-ui/react": "^0.27.0",
     "@toptal/picasso-shared": "15.0.0",
     "@toptal/picasso-utils": "4.0.0",
     "@toptal/picasso-modal-context": "1.0.1"
   },
   "peerDependencies": {
-    "@material-ui/core": "4.12.4",
     "@toptal/picasso-provider": "*",
-    "react": ">=16.12.0 < 19.0.0",
+    "react": ">=16.12.0",
     "@toptal/picasso-tailwind": ">=2.5.0",
     "@toptal/picasso-tailwind-merge": "^2.0.0"
   },
   "devDependencies": {
-    "@toptal/picasso-provider": "5.0.2",
-    "popper.js": "^1.16.1"
+    "@toptal/picasso-provider": "5.0.2"
   },
   "exports": {
     ".": "./dist-package/src/index.js"

Prop-surface diff

Click to expand .d.ts diff

Review carefully: any - line on a public export is a breaking change. See docs/migration/rules/api-preservation.md.

Happo

Happo log: migration-runs/2026-06-10/Popper/happo.log (0
? flagged lines).

Designer: review screen diffs >0.5% per docs/migration/migration-plan.md §6.3.

React 19 smoke

Stubbed (pending PF-1994). The real smoke wires up during PF-1994's first migration.

@vedrani vedrani requested a review from a team as a code owner June 10, 2026 11:42
@vedrani vedrani self-assigned this Jun 10, 2026
@changeset-bot

changeset-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 57765f5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 40 packages
Name Type
@toptal/picasso-popper Major
@toptal/picasso Patch
@toptal/picasso-autocomplete Patch
@toptal/picasso-date-picker Patch
@toptal/picasso-dropdown Patch
@toptal/picasso-menu Patch
@toptal/picasso-select Patch
@toptal/picasso-forms Patch
@toptal/picasso-page Patch
@toptal/picasso-tagselector Patch
@toptal/picasso-button Patch
@toptal/picasso-account-select Patch
@toptal/picasso-query-builder Patch
@toptal/picasso-rich-text-editor Patch
@toptal/picasso-date-select Patch
@toptal/picasso-accordion Patch
@toptal/picasso-alert Patch
@toptal/picasso-application-update-notification Patch
@toptal/picasso-calendar Patch
@toptal/picasso-carousel Patch
@toptal/picasso-drawer Patch
@toptal/picasso-file-input Patch
@toptal/picasso-helpbox Patch
@toptal/picasso-modal Patch
@toptal/picasso-notification Patch
@toptal/picasso-outlined-input Patch
@toptal/picasso-pagination Patch
@toptal/picasso-password-input Patch
@toptal/picasso-prompt-modal Patch
@toptal/picasso-section Patch
@toptal/picasso-show-more Patch
@toptal/picasso-skeleton-loader Patch
@toptal/picasso-table Patch
@toptal/picasso-tree-view Patch
@toptal/picasso-form Patch
@toptal/picasso-dropzone Patch
@toptal/picasso-avatar-upload Patch
@toptal/picasso-input Patch
@toptal/picasso-number-input Patch
@toptal/picasso-timepicker Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/5001/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request Jun 10, 2026
github-actions Bot added a commit that referenced this pull request Jun 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/5001/

This preview is updated automatically when you push changes to this PR.

1 similar comment
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/5001/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request Jun 10, 2026
@vedrani

vedrani commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

🛑 Orchestrator escalation — Popper

Trigger: agent invocation failed during CI iteration: exit 1
Iterations: 5 / 3
PR: #5001
Worktree: migration-runs/2026-06-10/Popper-v1/worktree
Last gate report: /Users/vivanac/Projects/picasso/migration-runs/2026-06-10/Popper-v1/worktree/migration-runs/2026-06-10/Popper/report.md

See docs/migration/references/escalation.md for the full handoff procedure.

@vedrani

vedrani commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Orchestrator agent (autonomous review-response)

Triaged the Happo escalation. Authoritative gates are green: local gate composite PASS; Storybook Happo clean (its lone UserBadge diff is 0-pixel noise, unrelated to Popper).

All 52 Cypress diffs are on Popper consumers, not Popper's own stories. ~30 are negligible (0px). The rest are expected floating-ui consequences: it positions/collision-resolves differently than popper.js — e.g. Slider when-tooltip-intersect now shows both tooltips side-by-side instead of overlapping (strictly better) — and the new global happoScreenshot settle-shim in commands.jsx captures every component one frame later. Pursuing byte-parity would mean re-introducing worse legacy behavior — a rung -1 (don't-override) case.

Recommend accepting the new Cypress baselines in Happo rather than patching source. 👍 to confirm.

(Minor: .changeset/popper-floating-ui-migration.md is an empty no-op the author marked "safe to delete".)

@vedrani vedrani force-pushed the feature/picasso-modernization-temp branch from fe18ec8 to 3d5041f Compare June 12, 2026 09:42
vedrani added 3 commits June 16, 2026 11:00
Tier 2 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994
Tier 2 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[ci-iter 3]
Tier 2 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[ci-iter 4]
@vedrani vedrani force-pushed the migrate-Popper-v1 branch from 5e951ce to 57765f5 Compare June 16, 2026 09:00
@vedrani

vedrani commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

It seems with migration we have different position of calendar in date picker.

image

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/5001/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request Jun 16, 2026
dulishkovych added a commit that referenced this pull request Jun 17, 2026
Dropdown redefined the 12-member placement union locally, duplicating the
identical type @toptal/picasso-popper already declares. Surface it from the
Popper package entry and import it in Dropdown instead of the local copy.

The type is byte-identical in the current popper.js Popper and in the
@floating-ui/react migration (PR #5001), and #5001 leaves Popper/index.ts
untouched, so this rebases cleanly. PopperOptions stays on popper.js for now;
it has no native package export until #5001 and is repointed during that rebase.

Also refresh the Dropdown changeset to match the landed implementation
(shared ClickAwayListener, @starting-style open animation, imported
PopperPlacementType).

Refs: PF-1994

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dulishkovych added a commit that referenced this pull request Jun 17, 2026
Dropdown redefined the 12-member placement union locally, duplicating the
identical type @toptal/picasso-popper already declares. Surface it from the
Popper package entry and import it in Dropdown instead of the local copy.

The type is byte-identical in the current popper.js Popper and in the
@floating-ui/react migration (PR #5001), and #5001 leaves Popper/index.ts
untouched, so this rebases cleanly. PopperOptions stays on popper.js for
now; it has no native package export until #5001 and is repointed then.

Also refresh the Dropdown changeset to match the landed implementation
(shared ClickAwayListener, @starting-style open animation, imported
PopperPlacementType).

Refs: PF-1994

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dulishkovych added a commit that referenced this pull request Jun 18, 2026
Dropdown redefined the 12-member placement union locally, duplicating the
identical type @toptal/picasso-popper already declares. Surface it from the
Popper package entry and import it in Dropdown instead of the local copy.

The type is byte-identical in the current popper.js Popper and in the
@floating-ui/react migration (PR #5001), and #5001 leaves Popper/index.ts
untouched, so this rebases cleanly. PopperOptions stays on popper.js for
now; it has no native package export until #5001 and is repointed then.

Also refresh the Dropdown changeset to match the landed implementation
(shared ClickAwayListener, @starting-style open animation, imported
PopperPlacementType).

Refs: PF-1994

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dulishkovych added a commit that referenced this pull request Jun 18, 2026
* [PF-1994] Migrate Dropdown to @base-ui/react + Tailwind

Tier 3 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

* [PF-1994] Migrate Dropdown to @base-ui/react + Tailwind

Tier 3 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[ci-iter 3]

* [PF-1994] Migrate Dropdown to @base-ui/react + Tailwind

Tier 3 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[ci-iter 5]

* [PF-1994] Use shared ClickAwayListener in Dropdown

The migration hand-rolled a local useClickAway hook (plus a wrapper
<div> kept only for a since-resolved MUI bug) to replace MUI's
ClickAwayListener. But @toptal/picasso-utils already exports a
ClickAwayListener -- the MUI-free replacement that DatePicker and Menu
use -- so swap to it for consistency with the rest of the library.

The shared component is also more robust than the local hook: it adds a
root-scrollbar click guard, portal / React-tree awareness, touch-move
cancellation, and an activation delay so the opening interaction is not
counted as a click-away. It clones its child, so no extra DOM node is
introduced.

Removes use-click-away.ts (the shared component carries its own tests).
Render-neutral: verified via Jest (unchanged DOM snapshot) and a local
Happo run (no new diffs).

Refs: PF-1994

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [PF-1994] Restore Dropdown open animation via CSS @starting-style

The MUI <Grow> replacement was a no-op: growIn flipped in a pre-paint
layout effect (collapsed frame never painted) and scale sat on Tailwind
v4's `scale` property, which transition-[opacity,transform] did not
animate. Replace the growIn state machine with a CSS @starting-style
entry transition (transition-[opacity,scale] + starting:scale-75
starting:opacity-0); the resting state is scale-100 so static captures
stay stable. Drops the useIsomorphicLayoutEffect hack.

Verified: Jest, lint, tsc, and a local Happo run (dropdown snapshots
byte-identical to the settled baseline -- no new diffs). A TODO notes
the @floating-ui/react Popper migration must trigger the entry
animation only once positioned.

Refs: PF-1994

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [PF-1994] Import PopperPlacementType from picasso-popper

Dropdown redefined the 12-member placement union locally, duplicating the
identical type @toptal/picasso-popper already declares. Surface it from the
Popper package entry and import it in Dropdown instead of the local copy.

The type is byte-identical in the current popper.js Popper and in the
@floating-ui/react migration (PR #5001), and #5001 leaves Popper/index.ts
untouched, so this rebases cleanly. PopperOptions stays on popper.js for
now; it has no native package export until #5001 and is repointed then.

Also refresh the Dropdown changeset to match the landed implementation
(shared ClickAwayListener, @starting-style open animation, imported
PopperPlacementType).

Refs: PF-1994

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [PF-1994] Migrate Dropdown to @base-ui/react + Tailwind

Tier 3 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 2] address review feedback

* [PF-1994] Migrate Dropdown to @base-ui/react + Tailwind

Tier 3 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[cleanup] strip review-aid comments before merge

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant