Skip to content

fix: prevent clipping of dropdowns, autocompletes, and floating menus - #332

Merged
garrity-miepub merged 11 commits into
mainfrom
fix/floating-panel-clipping
Jul 24, 2026
Merged

fix: prevent clipping of dropdowns, autocompletes, and floating menus#332
garrity-miepub merged 11 commits into
mainfrom
fix/floating-panel-clipping

Conversation

@garrity-miepub

@garrity-miepub garrity-miepub commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Add shared useAnchoredPosition hook that portals floating panels to document.body with fixed positioning (vertical flip, viewport clamping, max-height constraint, scroll/resize tracking, transitions suppressed so position updates never animate). Migrate 14 components from inline absolute or hand-rolled portal positioning. Extend useClickOutside to accept multiple refs for portaled menus.

clipping-fix-1.mov
clipping-fix-2.mov
clipping-fix-3.mov

Add shared useAnchoredPosition hook that portals floating panels to
document.body with fixed positioning (vertical flip, viewport clamping,
max-height constraint, scroll/resize tracking, transitions suppressed so
position updates never animate). Migrate 14 components from inline
absolute or hand-rolled portal positioning. Extend useClickOutside to
accept multiple refs for portaled menus.
Copilot AI review requested due to automatic review settings July 23, 2026 22:06
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 23, 2026

Copy link
Copy Markdown

Deploying ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: d9e71a5
Status: ✅  Deploy successful!
Preview URL: https://e83b81d7.ui-6d0.pages.dev
Branch Preview URL: https://fix-floating-panel-clipping.ui-6d0.pages.dev

View logs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a shared positioning strategy for floating UI (dropdowns, autocompletes, menus) to prevent clipping by overflow: hidden ancestors by portaling to document.body and using position: fixed.

Changes:

  • Added a new useAnchoredPosition hook to compute fixed-position styles with vertical flipping, viewport clamping, max-height constraints, and scroll/resize/ResizeObserver tracking.
  • Extended useClickOutside to support multiple refs (anchor + portaled floating element) for outside-click dismissal.
  • Migrated multiple components from inline absolute positioning to portaled, fixed-position floating panels.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/hooks/useClickOutside.ts Extends outside-click handling to accept multiple refs for portaled UIs.
src/hooks/useAnchoredPosition.ts Adds shared fixed-position anchoring logic for portaled floating elements.
src/hooks/index.ts Re-exports useAnchoredPosition and its types from the hooks barrel.
src/components/Select/Select.tsx Moves dropdown to a body portal and positions via useAnchoredPosition.
src/components/ProviderSelector/ProviderSelector.tsx Portals dropdown to body and positions via useAnchoredPosition.
src/components/ProviderSearchFilters/ProviderSearchFilters.tsx Portals multiselect panel to body and positions via useAnchoredPosition.
src/components/PatientHeader/PatientHeader.tsx Portals overflow menu to body and uses multi-ref click-outside support.
src/components/Messaging/MessageComposer.tsx Portals mention suggestions list and positions via useAnchoredPosition.
src/components/LanguageSelector/LanguageSelector.tsx Portals dropdown to body and positions via useAnchoredPosition.
src/components/Dropdown/Dropdown.tsx Refactors Dropdown menu to be portaled + fixed-positioned, using multi-ref click-outside.
src/components/DateRangePicker/DateRangePicker.tsx Portals desktop popup calendar and positions via useAnchoredPosition.
src/components/CountryCodeDropdown/CountryCodeDropdown.tsx Portals panel to body and positions via useAnchoredPosition.
src/components/CountBadge/CountBadge.tsx Portals hover menu and share menu dropdown, positioning via useAnchoredPosition.
src/components/CodeLookup/CodeLookup.tsx Portals results dropdown and positions via useAnchoredPosition.
src/components/BusinessHoursEditor/BusinessHoursEditor.tsx Replaces a hover-only copy menu with the shared Dropdown component.
src/components/BookingDialog/BookingDialog.tsx Portals service dropdown to body and positions via useAnchoredPosition.
src/components/Autocomplete/Autocomplete.tsx Portals listbox to body and positions via useAnchoredPosition.

Comment thread src/hooks/useAnchoredPosition.ts
Comment thread src/hooks/useAnchoredPosition.ts Outdated
…mping

The clamp used offsetWidth measured before minWidth applied, so panels
widened by matchMinWidth could overflow the right viewport edge on
first open.
Copilot AI review requested due to automatic review settings July 23, 2026 22:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

src/components/CountBadge/CountBadge.tsx:386

  • To make viewport clamping work reliably, the scroll region should be able to shrink when the menu container is constrained by maxHeight. Without flex-1, the scroll area can keep its max-height and end up clipped by the container.
      <div
        data-slot="count-badge-menu-scroll"
        className="max-h-[240px] min-h-0 overflow-y-auto"
      >

src/components/CountryCodeDropdown/CountryCodeDropdown.tsx:407

  • The list should be a flex child so it can shrink when the panel is constrained by useAnchoredPosition's computed maxHeight. Otherwise the list can overflow/clipped rather than scrolling within the available space.
            {/* Country list */}
            <div
              ref={listRef}
              data-slot="country-dropdown-list"
              className="max-h-60 min-h-0 overflow-y-auto p-1"
            >

Comment thread src/components/CountBadge/CountBadge.tsx Outdated
Comment thread src/components/CodeLookup/CodeLookup.tsx
Comment thread src/components/CountryCodeDropdown/CountryCodeDropdown.tsx Outdated
Comment thread src/components/ProviderSelector/ProviderSelector.tsx Outdated
When useAnchoredPosition clamps a panel's maxHeight below its content,
the panel needs overflow-hidden and inner lists need min-h-0 so they
shrink and scroll instead of painting past the clamp. Also mark fixed
headers shrink-0 so only the scroll regions give up height.

Addresses Copilot review on PR #332.
Copilot AI review requested due to automatic review settings July 23, 2026 22:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comment thread src/hooks/useClickOutside.ts
Widen the multi-ref parameter to ReadonlyArray so callers can pass
readonly tuples (e.g. built with 'as const') without casting.
Copilot AI review requested due to automatic review settings July 23, 2026 22:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Comment thread src/hooks/useAnchoredPosition.ts
Comment thread src/hooks/useClickOutside.ts Outdated
Batch bursty scroll/resize/ResizeObserver events into one layout read
and state update per frame, cancelling any pending frame on cleanup.

Also tighten useClickOutside: guard event.target instanceof Node instead
of casting, and avoid traversing the ref list twice.
Copilot AI review requested due to automatic review settings July 23, 2026 23:00
@garrity-miepub
garrity-miepub marked this pull request as ready for review July 23, 2026 23:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Comment thread src/hooks/useAnchoredPosition.ts Outdated
Comment thread src/hooks/useAnchoredPosition.ts
12 renderHook tests covering hidden style, bottom/top placement,
vertical flipping, horizontal viewport clamping, matchWidth /
matchMinWidth, maxHeight capping, viewport height clamping,
rAF-coalesced scroll updates, and close reset.

Also clarify that maxHeight is an additional cap on top of the
always-applied viewport space clamp.
Copilot AI review requested due to automatic review settings July 23, 2026 23:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

src/components/CountBadge/CountBadge.tsx:708

  • e.target can be null (or not a Node). The current as Node cast can cause .contains(...) to throw. Add a target instanceof Node guard before checking both the trigger container and the portaled menu.
        if (
          containerRef.current &&
          !containerRef.current.contains(e.target as Node) &&
          !floatingRef.current?.contains(e.target as Node)
        ) {

Comment thread src/components/LanguageSelector/LanguageSelector.tsx Outdated
Comment thread src/components/ProviderSelector/ProviderSelector.tsx Outdated
Comment thread src/components/ProviderSearchFilters/ProviderSearchFilters.tsx Outdated
Comment thread src/components/BookingDialog/BookingDialog.tsx Outdated
Comment thread src/components/CountBadge/CountBadge.tsx Outdated
Seven duplicated mousedown handlers cast event.target to Node without a
guard. The shared useClickOutside hook already guards with instanceof
Node and accepts ref arrays for portaled panels, so use it instead:
LanguageSelector, ProviderSelector, ProviderSearchFilters,
BookingDialog ServiceSelect, CountBadge (menu, share, container), and
Select.
Copilot AI review requested due to automatic review settings July 23, 2026 23:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comment thread src/components/Select/Select.tsx
Comment thread src/components/ProviderSelector/ProviderSelector.tsx
Comment thread src/components/ProviderSearchFilters/ProviderSearchFilters.tsx
Comment thread src/components/BookingDialog/BookingDialog.tsx
Comment thread src/components/Autocomplete/Autocomplete.tsx
Use the latest-ref pattern so the effect only depends on ref/enabled.
Inline close callbacks (the norm at call sites) no longer cause the
document mousedown/touchstart listeners to detach/attach on every
re-render while a panel is open.
Copilot AI review requested due to automatic review settings July 23, 2026 23:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread src/components/PatientHeader/PatientHeader.tsx
Comment thread src/components/DateRangePicker/DateRangePicker.tsx Outdated
Comment thread src/components/CountryCodeDropdown/CountryCodeDropdown.tsx Outdated
PatientHeader, DateRangePicker, and CountryCodeDropdown called
useClickOutside without the enabled argument, keeping document
mousedown/touchstart listeners attached while closed. Pass the open
state (and drop DateRangePicker's now-redundant isCalendarOpen guard).
Copilot AI review requested due to automatic review settings July 23, 2026 23:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Comment thread src/components/Dropdown/Dropdown.tsx
Comment thread src/hooks/useClickOutside.ts
Apply the clamped-panel pattern to Dropdown's portaled menu (flex-col +
overflow-hidden panel, shrink-0 search header, min-h-0 scrolling menu)
so the search input stays visible while the list scrolls under the
computed maxHeight.

Mark useClickOutside's document touchstart listener passive — the
handler never calls preventDefault, and passive avoids scroll-blocking
warnings.
Copilot AI review requested due to automatic review settings July 23, 2026 23:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@garrity-miepub
garrity-miepub merged commit 9b994b3 into main Jul 24, 2026
11 checks passed
garrity-miepub added a commit that referenced this pull request Jul 24, 2026
When useAnchoredPosition clamps a panel's maxHeight below its content,
the panel needs overflow-hidden and inner lists need min-h-0 so they
shrink and scroll instead of painting past the clamp. Also mark fixed
headers shrink-0 so only the scroll regions give up height.

Addresses Copilot review on PR #332.
@garrity-miepub
garrity-miepub deleted the fix/floating-panel-clipping branch July 24, 2026 00:50
garrity-miepub added a commit that referenced this pull request Jul 24, 2026
Minor bump for the floating-panel clipping fix (#332): dropdown/select/
autocomplete panels now portal to document.body with viewport-aware
positioning (useAnchoredPosition), plus shared useClickOutside hook.
Behavioral changes (portal DOM location, z-index 9999, touchstart close)
are documented in the GitHub release notes.

Also consolidate local scratch-file ignores to *.local.md.
garrity-miepub added a commit that referenced this pull request Aug 3, 2026
…to viewport

The calendar was portaled but hand-rolled its fixed positioning (always
below the trigger, no flip/clamp), so it went off screen near the
viewport bottom. Migrate to the shared useAnchoredPosition hook from #332.
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.

2 participants