Skip to content

Commit

Permalink
feat(Select): Expose the Popover components portalContainer prop
Browse files Browse the repository at this point in the history
  • Loading branch information
mcwinter07 committed Nov 20, 2023
1 parent 73c89f8 commit 241f4fb
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-tips-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kaizen/components": patch
---

expose the portalContainer prop the Select's popover
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { HTMLAttributes } from "react"
import React, { HTMLAttributes, ReactNode } from "react"

Check failure on line 1 in packages/components/src/MultiSelect/subcomponents/Popover/Popover.tsx

View workflow job for this annotation

GitHub Actions / eslint

'ReactNode' is defined but never used. Allowed unused vars must match /^_/u
import { createPortal } from "react-dom"
import {
autoUpdate,
Expand Down
70 changes: 68 additions & 2 deletions packages/components/src/__future__/Select/Select.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react"
import { render, waitFor } from "@testing-library/react"
import React, { useEffect, useRef, useState } from "react"
import { render, waitFor, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { Select, SelectProps } from "./Select"
import { singleMockItems } from "./_docs/mockData"
Expand Down Expand Up @@ -337,4 +337,70 @@ describe("<Select />", () => {
})
})
})

describe("Popover portal", () => {
it("has accessible trigger controls", async () => {
render(<SelectWrapper id="id--select" isOpen />)

const expectedPopoverId = "id--select--popover"

const trigger = screen.getByRole("combobox", {
name: "Select Mock Label",
})

await waitFor(() => {
expect(trigger).toHaveAttribute("aria-controls", expectedPopoverId)
})
})

it("will portal to the document body by default", async () => {
render(<SelectWrapper selectedKey="batch-brew" id="id--select" isOpen />)

const popover = screen.getByRole("dialog")
// expected div that FocusOn adds to the popover
const popoverFocusWrapper = popover.parentNode

await waitFor(() => {
const expectedBodyTag = popoverFocusWrapper?.parentNode
expect(expectedBodyTag?.nodeName).toEqual("BODY")
})
})

it("will render as a descendant of a given element", async () => {
const SelectWithPortal = (): JSX.Element => {
const portalRef = useRef<HTMLDivElement>(null)
const [portalContainer, setPortalContainer] = useState<HTMLDivElement>()

useEffect(() => {
if (portalRef.current !== null) {
setPortalContainer(portalRef.current)
}
}, [])

return (
<>
<div ref={portalRef} data-testid="id--new-portal-region"></div>
<SelectWrapper
selectedKey="batch-brew"
id="id--select"
isOpen
portalContainer={portalContainer}
/>
</>
)
}

render(<SelectWithPortal />)

await waitFor(() => {
const newPortalRegion = screen.getByTestId("id--new-portal-region")

const popoverAsDescendant = newPortalRegion.querySelector(
"#id--select--popover"
)

expect(popoverAsDescendant).toHaveAttribute("role", "dialog")
})
})
})
})
6 changes: 6 additions & 0 deletions packages/components/src/__future__/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type SelectProps<Option extends SelectOption = SelectOption> = {
* @deprecated: Either define `disabled` in your `Option` (in `items`), or use `disabledKeys`
*/
disabledValues?: Key[]
portalContainer?: Element | DocumentFragment
} & OverrideClassName<Omit<AriaSelectProps<Option>, OmittedAriaSelectProps>>

/**
Expand All @@ -89,13 +90,15 @@ export const Select = <Option extends SelectOption = SelectOption>({
description,
placeholder,
isDisabled,
portalContainer,
...restProps
}: SelectProps<Option>): JSX.Element => {
const { refs } = useFloating<HTMLButtonElement>()
const triggerRef = refs.reference

const id = propsId ?? useId()
const descriptionId = `${id}--description`
const popoverId = `${id}--popover`

const disabledKeys = getDisabledKeysFromItems(items)

Expand Down Expand Up @@ -136,6 +139,7 @@ export const Select = <Option extends SelectOption = SelectOption>({
isDisabled: triggerProps.isDisabled,
isReversed,
ref: refs.setReference,
"aria-controls": popoverId,
}

return (
Expand All @@ -160,6 +164,8 @@ export const Select = <Option extends SelectOption = SelectOption>({
)}
{state.isOpen && (
<Popover
id={popoverId}
portalContainer={portalContainer}
refs={refs}
focusOnProps={{
onEscapeKey: state.close,
Expand Down

0 comments on commit 241f4fb

Please sign in to comment.