Skip to content

Consolidate the floating popover into the shared tool library #320

Description

@BhattaraiSijan

Consolidate the floating popover into the shared tool library

Follow-up accepted in review of #317.

Motivation

Three tools — Timeline, LayerManager, and (as of #317) MapControl — each carry their own private copy of the same floating popover component: the surface that anchors to a trigger button, portals out of clipping containers, positions itself against the viewport, and owns Escape, outside-click, and focus placement/restoration.

The copies were byte-identical until #317 extended only MapControl's with a tooltip variant that nothing uses. That is the drift risk made concrete: a fix or capability added to one copy — a positioning bug, an accessibility improvement — silently doesn't reach the other two. Per-tool copies exist because tools may not import from each other, but a shared home for cross-tool code already exists and is already in use.

How it should work

  • One popover implementation lives in the shared area every tool may draw from; Timeline, LayerManager, and MapControl all use it.
  • The per-tool copies are deleted.
  • The unused tooltip variant is either adopted by a real consumer or dropped, so the shared component carries no speculative surface.
  • While rewiring MapControl: the map bar should only maintain its search surface when search is actually configured, matching how its trigger button already behaves.
  • On screen, nothing changes: popovers open and position as before, close on Escape and outside click, and return focus to their trigger.

Done when

  • Exactly one popover implementation exists in the repo; a repo-wide search finds no per-tool copy.
  • All three tools' popovers (Timeline, LayerManager, MapControl basemap + search) behave as before in a running instance: placement, Escape, outside click, focus restoration.
  • The tooltip variant has a consumer or is gone.
  • The map bar mounts no search surface when search isn't configured.
  • Type check and the full test suite pass.

Out of scope

Draft implementation plan — written as of 1821dc2 (PR #317 head) on 2026-08-13. Rough guide; re-verify against latest code, and land after #317 merges.

Current behavior

  • src/essence/Tools/{Timeline,LayerManager,MapControl}/lib/FloatingPopover/FloatingPopover.tsx — three copies. Timeline and LayerManager are identical; MapControl's adds a role?: 'dialog' | 'tooltip' prop (default 'dialog') that no call site sets.
  • Consumers: Timeline and LayerManager import from their own lib/FloatingPopover; MapControl uses it in lib/geo/MapControlBar/MapControlBar.tsx for the basemap and search panels.
  • Shared-code precedent: src/essence/Tools/_shared/ (e.g. _shared/share/ShareMenu.tsx + share-menu.scss), imported by multiple tools.
  • The search popover in MapControlBar.tsx renders unconditionally while its trigger button is gated on the onSearchSelect prop (basemap popover is gated on hasStyles).

Where the change lands & rough plan

  1. Move one copy (MapControl's, minus the role prop unless a consumer appears) to src/essence/Tools/_shared/FloatingPopover/.
  2. Point the three tools' imports at it; delete the three lib/FloatingPopover/ directories and their index.ts re-exports (including the one in MapControl's lib/index.ts).
  3. Wrap MapControl's search <FloatingPopover> in {onSearchSelect && …}.
  4. Add unit coverage in the shared home — positioning clamps, Escape close, focus restoration are jsdom-testable; tests/unit/ and LayerManager/__tests__/popoverMenu.spec.js show the local patterns.

⚠️ Gotcha: the copies may have drifted further by pickup time — diff all three before choosing the survivor, don't assume Timeline/LayerManager are still identical.

⚠️ Gotcha: focus restoration reads a focusin-tracked ref because document.activeElement has already fallen back to <body> by portal cleanup — preserve that mechanism when touching the component.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions