Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import IdTokenVerifier from "idtoken-verifier";
import T from "i18n-react";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
import { SelectBase } from "react-select";
import LibSelectBase from "react-select/lib/Select";
// eslint-disable-next-line
import * as Sentry from "@sentry/react";
import exclusiveSections from "./exclusive-sections.yml";
Expand All @@ -46,7 +48,8 @@ import DefaultRoute from "./routes/default-route";
import { getTimezones } from "./actions/base-actions";
import {
ALLOWED_INVENTORY_IMAGE_FORMATS,
LANGUAGE_CODE_LENGTH
LANGUAGE_CODE_LENGTH,
REACT_SELECT_MENU_Z_INDEX
} from "./utils/constants";
import { SentryFallbackFunction } from "./components/SentryErrorComponent";

Expand Down Expand Up @@ -116,6 +119,18 @@ if (exclusiveSections.hasOwnProperty(process.env.APP_CLIENT_NAME)) {
window.EXCLUSIVE_SECTIONS = exclusiveSections[process.env.APP_CLIENT_NAME];
}

// absolute at 0,0 so react-select's document-based coordinates still line up; one z-index for every menu
const selectMenuPortal = document.createElement("div");
Object.assign(selectMenuPortal.style, {
position: "absolute",
top: 0,
left: 0,
zIndex: REACT_SELECT_MENU_Z_INDEX
});
document.body.appendChild(selectMenuPortal);
SelectBase.defaultProps.menuPortalTarget = selectMenuPortal;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomrndom With the global menuPortalTarget, a react-select near the bottom of a react-bootstrap Modal now opens its menu on top of its own control and scrolls the page behind the modal.

react-select 2.4.4 picks the scroll container from where the menu sits in the DOM (getScrollParent → getMenuPlacement). Inline, the menu sits inside .modal, which Bootstrap 3 makes the scroll container (.modal-open .modal { overflow-y: auto }). When the menu doesn't fit below the control, react-select scrolls the modal and the menu stays attached; that is the behavior on master. Once the menu is portaled under <body>, the scroll container becomes documentElement. react-select then calls animatedScrollTo on the page behind the fixed modal; the overflow: hidden that react-overlays puts on the body doesn't block that. The control stays where it is and the menu moves up by the scroll amount.

I reproduced it with react-select 2.4.4 and the Bootstrap 3.3.7 modal rules, with a select 17px above the viewport bottom:

  • inline (master): the modal scrolled 291px and the menu opened 8px below the control
  • with this patch: the page behind the modal scrolled 299px and the menu ended up 291px above the control's bottom edge, covering it

It happens on every open, without the user scrolling, and the list page stays scrolled after the modal closes. Options remain selectable, so it's cosmetic. One likely place to hit it is the "send emails" modal in summit-speakers-list-page.js: the TagInput / BadgeFeatureInput / TicketTypesInput rows from SpeakerPromoCodeSpecForm sit low in it.

Suggested fix: for selects rendered inside a modal, add menuPosition="fixed", matching what admin-access-form.js and the events grid filters (summit-event-list-page/helpers.js) already do with a portaled menu. With fixed, react-select places the menu in viewport coordinates and flips it above the control instead of scrolling the document. In the same repro it opened above the control with no page scroll.

I'd avoid making menuPosition: "fixed" a global default here. On regular pages an open fixed menu doesn't follow the control when the page is scrolled. It would also bring back the bulk-editor issue, because menuPortalTarget={null} with fixed still renders through MenuPortal. If you'd rather keep this PR as the temporary global fix, documenting it as a known limitation for the legacy modals is fine too.

LibSelectBase.defaultProps.menuPortalTarget = selectMenuPortal;
Comment thread
tomrndom marked this conversation as resolved.
Resolved
Comment on lines +130 to +132

if (window.SENTRY_DSN && window.SENTRY_DSN !== "") {
console.log("app init sentry ...");
// Initialize Sentry
Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/ticket-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import React from "react";
import T from "i18n-react/dist/i18n-react";
import Swal from "sweetalert2";
import Input from "openstack-uicore-foundation/lib/components/inputs/text-input"
import Input from "openstack-uicore-foundation/lib/components/inputs/text-input";
import TicketTypesInput from "openstack-uicore-foundation/lib/components/inputs/ticket-types-input";
import OwnerInput from "../inputs/owner-input";
import { isEmpty, scrollToError, shallowEqual } from "../../utils/methods";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import React from "react";
import T from "i18n-react/dist/i18n-react";
import Swal from "sweetalert2";
import DateTimePicker from "openstack-uicore-foundation/lib/components/inputs/datetimepicker"
import SummitVenuesSelect from "openstack-uicore-foundation/lib/components/inputs/summit-venues-select"
import Input from "openstack-uicore-foundation/lib/components/inputs/text-input"
import DateTimePicker from "openstack-uicore-foundation/lib/components/inputs/datetimepicker";
import SummitVenuesSelect from "openstack-uicore-foundation/lib/components/inputs/summit-venues-select";
import Input from "openstack-uicore-foundation/lib/components/inputs/text-input";
import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown";
import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods";
import moment from "moment-timezone";
Expand Down Expand Up @@ -429,6 +429,8 @@ class SummitEventBulkEditorForm extends React.Component {
<div className="bulk-edit-col-empty">&nbsp;</div>
<div className="bulk-edit-col">
<Select
// keep the menu inline so it scrolls with .bulk-edit-table instead of the global portal (see app.js)
menuPortalTarget={null}
placeholder={T.translate(
"schedule.placeholders.select_presentation_selection_plan"
)}
Expand All @@ -441,6 +443,7 @@ class SummitEventBulkEditorForm extends React.Component {
</div>
<div className="bulk-edit-col">
<SummitVenuesSelect
menuPortalTarget={null}
currentValue={currentBulkLocation}
onVenueChanged={this.onBulkLocationChange}
venues={venuesOptions}
Expand Down Expand Up @@ -501,6 +504,7 @@ class SummitEventBulkEditorForm extends React.Component {
</div>
<div className="bulk-edit-col">
<Dropdown
menuPortalTarget={null}
id="type_id"
placeholder={T.translate(
"bulk_actions_page.placeholders.event_type"
Expand All @@ -512,6 +516,7 @@ class SummitEventBulkEditorForm extends React.Component {
</div>
<div className="bulk-edit-col">
<Dropdown
menuPortalTarget={null}
id="track_id"
placeholder={T.translate(
"bulk_actions_page.placeholders.track"
Expand Down Expand Up @@ -547,6 +552,7 @@ class SummitEventBulkEditorForm extends React.Component {
</div>
<div className="bulk-edit-col">
<Dropdown
menuPortalTarget={null}
id="streaming_type"
value={currentBulkStreamingType}
onChange={this.onBulkStreamingType}
Expand All @@ -558,6 +564,7 @@ class SummitEventBulkEditorForm extends React.Component {
</div>
<div className="bulk-edit-col">
<Dropdown
menuPortalTarget={null}
id="stream_is_secure"
value={currentBulkStreamIsSecure}
onChange={this.onBulkStreamIsSecure}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
* */
import React from "react";
import { FormGroup, FormControl } from "react-bootstrap";
import DateTimePicker from "openstack-uicore-foundation/lib/components/inputs/datetimepicker"
import SummitVenuesSelect from "openstack-uicore-foundation/lib/components/inputs/summit-venues-select"
import DateTimePicker from "openstack-uicore-foundation/lib/components/inputs/datetimepicker";
import SummitVenuesSelect from "openstack-uicore-foundation/lib/components/inputs/summit-venues-select";
import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown";
import { SummitEvent } from "openstack-uicore-foundation/lib/models";
import moment from "moment-timezone";
Expand Down Expand Up @@ -275,6 +275,8 @@ class SummitEventBulkEditorItem extends React.Component {
<div className="bulk-edit-col">
<FormGroup validationState={this.getValidationEventSelectionPlan()}>
<Select
// keep the menu inline so it scrolls with .bulk-edit-table instead of the global portal (see app.js)
menuPortalTarget={null}
placeholder={T.translate(
"schedule.placeholders.select_presentation_selection_plan"
)}
Expand All @@ -290,6 +292,7 @@ class SummitEventBulkEditorItem extends React.Component {
<div className="bulk-edit-col">
<FormGroup validationState={this.getValidationStateVenue()}>
<SummitVenuesSelect
menuPortalTarget={null}
onVenueChanged={this.onLocationChanged}
currentValue={currentLocation}
venues={venuesOptions}
Expand Down Expand Up @@ -355,6 +358,7 @@ class SummitEventBulkEditorItem extends React.Component {
<div className="bulk-edit-col">
<FormGroup>
<Dropdown
menuPortalTarget={null}
id="type_id"
placeholder={T.translate(
"bulk_actions_page.placeholders.event_type"
Expand All @@ -369,6 +373,7 @@ class SummitEventBulkEditorItem extends React.Component {
<div className="bulk-edit-col">
<FormGroup>
<Dropdown
menuPortalTarget={null}
id="track_id"
placeholder={T.translate("bulk_actions_page.placeholders.track")}
value={event.track_id}
Expand Down Expand Up @@ -415,6 +420,7 @@ class SummitEventBulkEditorItem extends React.Component {
<div className="bulk-edit-col">
<FormGroup>
<Dropdown
menuPortalTarget={null}
id="streaming_type"
value={event.streaming_type}
onChange={this.onStreamingTypeLocalChanged}
Expand All @@ -429,6 +435,7 @@ class SummitEventBulkEditorItem extends React.Component {
<div className="bulk-edit-col">
<FormGroup>
<Dropdown
menuPortalTarget={null}
id="streaming_type"
value={event.stream_is_secure}
onChange={this.onStreamIsSecureLocalChanged}
Expand Down
2 changes: 2 additions & 0 deletions src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ export const DEFAULT_Z_INDEX = 1;

export const HIGH_Z_INDEX = 9999;

export const REACT_SELECT_MENU_Z_INDEX = 1400;

export const DELTA_SECS = 300;

export const MAX_CENTS = 99;
Expand Down
Loading