diff --git a/UNRELEASED.md b/UNRELEASED.md index 8d14e8526f7..7168ac76d21 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -12,6 +12,8 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f ### Bug fixes +- Moved rendering of the portal component’s node within the node created by the theme provider component to enable theming through CSS Custom Properties ([#2224](https://github.com/Shopify/polaris-react/pull/2224)) + ### Documentation ### Development workflow diff --git a/src/components/Portal/Portal.tsx b/src/components/Portal/Portal.tsx index 9ec79f9948c..b072d2d89c3 100644 --- a/src/components/Portal/Portal.tsx +++ b/src/components/Portal/Portal.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {createPortal} from 'react-dom'; import {createUniqueIDFactory} from '@shopify/javascript-utilities/other'; +import {themeProvider} from '../shared'; export interface PortalProps { children?: React.ReactNode; @@ -20,6 +21,7 @@ export class Portal extends React.PureComponent { state: State = {isMounted: false}; private portalNode: HTMLElement; + private portalContainerNode: HTMLElement | null; private portalId = this.props.idPrefix !== '' @@ -29,7 +31,13 @@ export class Portal extends React.PureComponent { componentDidMount() { this.portalNode = document.createElement('div'); this.portalNode.setAttribute('data-portal-id', this.portalId); - document.body.appendChild(this.portalNode); + this.portalContainerNode = document.querySelector( + `${themeProvider.selector}`, + ); + if (this.portalContainerNode != null) { + this.portalContainerNode.appendChild(this.portalNode); + } + this.setState({isMounted: true}); } @@ -41,7 +49,9 @@ export class Portal extends React.PureComponent { } componentWillUnmount() { - document.body.removeChild(this.portalNode); + if (this.portalContainerNode != null) { + this.portalContainerNode.removeChild(this.portalNode); + } } render() { diff --git a/src/components/Portal/tests/Portal.test.tsx b/src/components/Portal/tests/Portal.test.tsx index 5ae84800401..d303789e0d7 100644 --- a/src/components/Portal/tests/Portal.test.tsx +++ b/src/components/Portal/tests/Portal.test.tsx @@ -46,15 +46,20 @@ describe('', () => { }); describe('DOM node', () => { + const appendChildSpy = jest.fn(); + const removeChildSpy = jest.fn(); + Object.defineProperty(document, 'querySelector', { + value: () => { + return {appendChild: appendChildSpy, removeChild: removeChildSpy}; + }, + }); it('gets added to the DOM on mount', () => { - const appendChildSpy = jest.spyOn(document.body, 'appendChild'); mountWithAppProvider(); expect(appendChildSpy).toHaveBeenCalledWith(expect.any(HTMLDivElement)); appendChildSpy.mockRestore(); }); it('gets removed from the DOM when the component unmounts', () => { - const removeChildSpy = jest.spyOn(document.body, 'removeChild'); const portal = mountWithAppProvider(); portal.unmount(); expect(removeChildSpy).toHaveBeenCalledWith(expect.any(HTMLDivElement)); diff --git a/src/components/ThemeProvider/ThemeProvider.tsx b/src/components/ThemeProvider/ThemeProvider.tsx index 7bf607f1bdc..b8edc5c8a4d 100644 --- a/src/components/ThemeProvider/ThemeProvider.tsx +++ b/src/components/ThemeProvider/ThemeProvider.tsx @@ -3,6 +3,7 @@ import isEqual from 'lodash/isEqual'; import {ThemeContext} from '../../utilities/theme'; import {Theme} from '../../utilities/theme/types'; import {setColors} from '../../utilities/theme/utils'; +import {themeProvider} from '../shared'; interface State { theme: Theme; @@ -55,7 +56,9 @@ export class ThemeProvider extends React.Component { return ( -
{children}
+
+ {children} +
); } diff --git a/src/components/shared.ts b/src/components/shared.ts index 5888a46b90c..b8c0ea8be67 100644 --- a/src/components/shared.ts +++ b/src/components/shared.ts @@ -28,6 +28,11 @@ export const headerCell = { selector: '[data-polaris-header-cell]', }; +export const themeProvider = { + props: {'data-polaris-theme-provider': true}, + selector: '[data-polaris-theme-provider]', +}; + export const DATA_ATTRIBUTE = { overlay, layer,