Skip to content

Commit 0cc854e

Browse files
authored
Merge pull request #78 from UgnisSoftware/UGN-281
Fix UGN-281 Make dropdown above scrollbar
2 parents de06f51 + df704be commit 0cc854e

File tree

2 files changed

+63
-11
lines changed

2 files changed

+63
-11
lines changed

src/components/Selects/MenuPortal.tsx

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
import React, { useEffect } from "react"
1+
import React, { useEffect, useLayoutEffect, useState } from "react"
2+
import ReactDOM from "react-dom"
23
import { Box, useTheme } from "@chakra-ui/react"
34
import { usePopper } from "@chakra-ui/popper"
5+
import { rootId } from "../Providers"
6+
7+
function createWrapperAndAppendToBody(wrapperId: string) {
8+
const wrapperElement = document.createElement("div")
9+
wrapperElement.setAttribute("id", wrapperId)
10+
document.body.appendChild(wrapperElement)
11+
return wrapperElement
12+
}
13+
14+
export const SELECT_DROPDOWN_ID = "react-select-dropdown-wrapper"
415

516
interface PortalProps {
617
controlElement: HTMLDivElement | null
@@ -13,12 +24,32 @@ const MenuPortal = (props: PortalProps) => {
1324
strategy: "fixed",
1425
matchWidth: true,
1526
})
27+
const [wrapperElement, setWrapperElement] = useState<HTMLElement | null>(null)
28+
29+
useLayoutEffect(() => {
30+
let element = document.getElementById(SELECT_DROPDOWN_ID)
31+
let systemCreated = false
32+
if (!element) {
33+
systemCreated = true
34+
element = createWrapperAndAppendToBody(SELECT_DROPDOWN_ID)
35+
}
36+
setWrapperElement(element)
37+
38+
return () => {
39+
if (systemCreated && element?.parentNode) {
40+
element.parentNode.removeChild(element)
41+
}
42+
}
43+
}, [])
1644

1745
useEffect(() => {
1846
referenceRef(props.controlElement)
1947
}, [props.controlElement])
2048

21-
return (
49+
// wrapperElement state will be null on very first render.
50+
if (wrapperElement === null) return null
51+
52+
return ReactDOM.createPortal(
2253
<Box
2354
ref={popperRef}
2455
zIndex={theme.zIndices.tooltip}
@@ -28,9 +59,11 @@ const MenuPortal = (props: PortalProps) => {
2859
pointerEvents: "none",
2960
},
3061
}}
62+
id={rootId}
3163
>
3264
{props.children}
33-
</Box>
65+
</Box>,
66+
wrapperElement,
3467
)
3568
}
3669

src/steps/MatchColumnsStep/tests/MatchColumnsStep.test.tsx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import userEvent from "@testing-library/user-event"
99
import type { Fields } from "../../../types"
1010
import selectEvent from "react-select-event"
1111
import { translations } from "../../../translationsRSIProps"
12+
import { SELECT_DROPDOWN_ID } from "../../../components/Selects/MenuPortal"
1213

1314
const fields: Fields<any> = [
1415
{
@@ -346,11 +347,14 @@ describe("Match Columns general tests", () => {
346347
<Providers theme={defaultTheme} rsiValues={{ ...mockRsiValues, fields }}>
347348
<ModalWrapper isOpen={true} onClose={() => {}}>
348349
<MatchColumnsStep headerValues={header} data={data} onContinue={onContinue} />
350+
<div id={SELECT_DROPDOWN_ID} />
349351
</ModalWrapper>
350352
</Providers>,
351353
)
352354

353-
await selectEvent.select(screen.getByLabelText(header[0]), fields[0].label)
355+
await selectEvent.select(screen.getByLabelText(header[0]), fields[0].label, {
356+
container: document.getElementById(SELECT_DROPDOWN_ID)!,
357+
})
354358

355359
const nextButton = screen.getByRole("button", {
356360
name: "Next",
@@ -377,6 +381,7 @@ describe("Match Columns general tests", () => {
377381
<Providers theme={defaultTheme} rsiValues={{ ...mockRsiValues, fields }}>
378382
<ModalWrapper isOpen={true} onClose={() => {}}>
379383
<MatchColumnsStep headerValues={header} data={data} onContinue={onContinue} />
384+
<div id={SELECT_DROPDOWN_ID} />
380385
</ModalWrapper>
381386
</Providers>,
382387
)
@@ -385,7 +390,9 @@ describe("Match Columns general tests", () => {
385390
// kinda dumb way to check if it has checkmark or not
386391
expect(checkmark).toBeEmptyDOMElement()
387392

388-
await selectEvent.select(screen.getByLabelText(header[0]), fields[0].label)
393+
await selectEvent.select(screen.getByLabelText(header[0]), fields[0].label, {
394+
container: document.getElementById(SELECT_DROPDOWN_ID)!,
395+
})
389396

390397
expect(checkmark).not.toBeEmptyDOMElement()
391398
})
@@ -430,21 +437,28 @@ describe("Match Columns general tests", () => {
430437
<Providers theme={defaultTheme} rsiValues={{ ...mockRsiValues, fields: enumFields }}>
431438
<ModalWrapper isOpen={true} onClose={() => {}}>
432439
<MatchColumnsStep headerValues={header} data={data} onContinue={onContinue} />
440+
<div id={SELECT_DROPDOWN_ID} />
433441
</ModalWrapper>
434442
</Providers>,
435443
)
436444

437445
expect(screen.queryByTestId("accordion-button")).not.toBeInTheDocument()
438446

439-
await selectEvent.select(screen.getByLabelText(header[0]), enumFields[0].label)
447+
await selectEvent.select(screen.getByLabelText(header[0]), enumFields[0].label, {
448+
container: document.getElementById(SELECT_DROPDOWN_ID)!,
449+
})
440450

441451
expect(screen.queryByTestId("accordion-button")).toBeInTheDocument()
442452

443453
userEvent.click(screen.getByTestId("accordion-button"))
444454

445-
await selectEvent.select(screen.getByLabelText(data[0][0]), options[0].label)
455+
await selectEvent.select(screen.getByLabelText(data[0][0]), options[0].label, {
456+
container: document.getElementById(SELECT_DROPDOWN_ID)!,
457+
})
446458

447-
await selectEvent.select(screen.getByLabelText(data[1][0]), options[1].label)
459+
await selectEvent.select(screen.getByLabelText(data[1][0]), options[1].label, {
460+
container: document.getElementById(SELECT_DROPDOWN_ID)!,
461+
})
448462

449463
const nextButton = screen.getByRole("button", {
450464
name: "Next",
@@ -538,7 +552,7 @@ describe("Match Columns general tests", () => {
538552
})
539553
})
540554

541-
test("Required unselected fields show warning alert on submit", async () => {
555+
test("Selecting the same field twice shows toast", async () => {
542556
const header = ["Something random", "Phone", "Email"]
543557
const data = [
544558
["John", "123", "[email protected]"],
@@ -551,12 +565,17 @@ describe("Match Columns general tests", () => {
551565
<Providers theme={defaultTheme} rsiValues={{ ...mockRsiValues, fields }}>
552566
<ModalWrapper isOpen={true} onClose={() => {}}>
553567
<MatchColumnsStep headerValues={header} data={data} onContinue={onContinue} />
568+
<div id={SELECT_DROPDOWN_ID} />
554569
</ModalWrapper>
555570
</Providers>,
556571
)
557572

558-
await selectEvent.select(screen.getByLabelText(header[0]), fields[0].label)
559-
await selectEvent.select(screen.getByLabelText(header[1]), fields[0].label)
573+
await selectEvent.select(screen.getByLabelText(header[0]), fields[0].label, {
574+
container: document.getElementById(SELECT_DROPDOWN_ID)!,
575+
})
576+
await selectEvent.select(screen.getByLabelText(header[1]), fields[0].label, {
577+
container: document.getElementById(SELECT_DROPDOWN_ID)!,
578+
})
560579

561580
expect(screen.queryByText(translations.matchColumnsStep.duplicateColumnWarningDescription)).toBeInTheDocument()
562581
})

0 commit comments

Comments
 (0)