From 2c486d0ac51e8e77051b86ac2625930c56f4202f Mon Sep 17 00:00:00 2001 From: "krable55@gmail.com" Date: Tue, 29 Mar 2022 09:15:08 -0700 Subject: [PATCH 1/2] Export portal component --- packages/react-components/source/index.js | 2 + .../source/react/library/copy/Copy.js | 4 +- .../source/react/library/portal/Portal.md | 100 ++++++++++++++++++ .../source/react/library/portal/index.js | 3 + .../source/react/library/portal/portal.js | 46 +++++++- .../library/tooltips/TooltipHoverArea.js | 4 +- .../library/tooltips/TooltipHoverArea.md | 2 +- 7 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 packages/react-components/source/react/library/portal/Portal.md create mode 100644 packages/react-components/source/react/library/portal/index.js diff --git a/packages/react-components/source/index.js b/packages/react-components/source/index.js index c1b8c38aa..a261d8ea9 100644 --- a/packages/react-components/source/index.js +++ b/packages/react-components/source/index.js @@ -26,6 +26,7 @@ import Loading from './react/library/loading'; import Logo from './react/library/logo'; import Modal from './react/library/modal'; import Popover from './react/library/popover'; +import Portal from './react/library/portal'; import Stepper from './react/library/stepper'; import RadioButton from './react/library/radiobutton'; import Select from './react/library/select'; @@ -67,6 +68,7 @@ export { Modal, Overlay, Popover, + Portal, Stepper, RadioButton, Select, diff --git a/packages/react-components/source/react/library/copy/Copy.js b/packages/react-components/source/react/library/copy/Copy.js index f8457cc63..e50c64cf0 100644 --- a/packages/react-components/source/react/library/copy/Copy.js +++ b/packages/react-components/source/react/library/copy/Copy.js @@ -56,10 +56,10 @@ const Copy = ({ const child = React.Children.only(children); // An explicitly set `value` prop on the child node supercedes child text // value = child.props?.children ? child.props.children : value; - if (typeof child.props?.children === 'string') + if (child.props && typeof child.props.children === 'string') copyValue = child.props.children; - if (child.props?.value) copyValue = child.props.value; + if (child.props && child.props.value) copyValue = child.props.value; } catch (e) { // If `children` is not a single React element, a string node is a valid value if (typeof React.Children.toArray(children)[0] === 'string') diff --git a/packages/react-components/source/react/library/portal/Portal.md b/packages/react-components/source/react/library/portal/Portal.md new file mode 100644 index 000000000..39dff1c06 --- /dev/null +++ b/packages/react-components/source/react/library/portal/Portal.md @@ -0,0 +1,100 @@ +## Overview + +Portals provide a quick and easy way to render elements at any given point in the DOM hierarchy. This can be useful when positioning tooltips, modals, nav menus, or other elements that need to be positioned higher in the DOM but controlled or triggered from a deeply nested component. + +
+### Portal + +```jsx +const { useState } = require('react'); +import Portal from '../portal'; +import Button from '../button'; +import Content from '../content'; +import Heading from '../heading'; +import Link from '../link'; +import Text from '../text'; + +const [portalLocation, setPortalLocation] = useState(); +const [portalActive, setPortalActive] = useState(false); +const renderIn = target => { + setPortalLocation(target); + setPortalActive(!!target); +}; + +const location = { + 'ex-sibling': 'the sibling div', + 'ex-overview': 'the Overview section', + 'ex-parent': 'the parent', +}; +<> +
+
+ Sibling Element +
+
+ +

+ {portalLocation + ? `I'm rendering in ${location[portalLocation]}!` + : `I'm not rendering in a portal `} +

+
+
+
+ + + + +; +``` + +## Variations + +By default, if the target id was not found within the DOM, a div will be created and appended to the root node of the application. The target id, style, and className are then applied to the newly created div. If the target div already exists, the portal's children are appended to it. + +```jsx +const { useState } = require('react'); +import Button from '../button'; +import Portal from '../portal'; + +const [showMenu, setShowMenu] = useState(false); +const [showMore, setShowMore] = useState(false); + +const menuStyle = { + backgroundColor: 'lightSlateGrey', + borderRadius: '4px', + color: 'mintCream', + height: 'fit-content', + width: '90%', + position: 'absolute', + top: '25px', + left: '5%', + zIndex: '100', + textAlign: 'center', +}; +<> + + +

I'm some menu content

+
+ + + {showMenu && showMore &&

I'm more content

}
+; +``` + +## Related + +- [TooltipHoverArea](#/React%20Components/TooltipHoverArea) diff --git a/packages/react-components/source/react/library/portal/index.js b/packages/react-components/source/react/library/portal/index.js new file mode 100644 index 000000000..c2380915d --- /dev/null +++ b/packages/react-components/source/react/library/portal/index.js @@ -0,0 +1,3 @@ +import Portal from './portal'; + +export default Portal; diff --git a/packages/react-components/source/react/library/portal/portal.js b/packages/react-components/source/react/library/portal/portal.js index ba8df9865..2b7ed0bda 100644 --- a/packages/react-components/source/react/library/portal/portal.js +++ b/packages/react-components/source/react/library/portal/portal.js @@ -1,26 +1,62 @@ +import React, { useEffect } from 'react'; import { createPortal } from 'react-dom'; import PropTypes from 'prop-types'; const propTypes = { - target: PropTypes.oneOf(['tooltip']), + /** Target id of div where portal will append content. Creates the div at application root if id can't be found in the DOM. All target div Ids must have the prefix: `rc-portal-` */ + target: PropTypes.string, + /** Boolean value used to conditionally render content in target div. If false, it will render the content at its current location in the dom */ + active: PropTypes.bool, + /** Optional additional className to apply to portal div */ + className: PropTypes.string, + /** Optional inline styles to apply to portal div */ + style: PropTypes.shape({}), + /** Content to render in portal */ + children: PropTypes.node, +}; +const defaultProps = { + target: 'default', + active: true, + className: '', + style: {}, + children: null, }; -const Portal = ({ children, target = 'tooltip' }) => { +const Portal = ({ children, target, active, style, className }) => { // portal target with fallbacks const root = document.getElementsByClassName('app')[0] || document.getElementById('root') || document.body; - const portalId = `portal-${target}`; + const portalId = `rc-portal-${target}`; let portal = document.getElementById(portalId); - if (!portal && root) { + if (!portal && root && target) { portal = document.createElement('div'); portal.id = portalId; + + // TODO: add option to prepend root.appendChild(portal); } - return createPortal(children, portal); + + // Apply classes and styles to portal div + if (className) portal.className = className; + if (style) Object.assign(portal.style, style); + + // Remove portal on unmount + useEffect(() => { + return () => { + const p = document.getElementById(portalId); + if (p) p.remove(); + }; + }, []); + + // Remove portal if not active + if (!active && portal) portal.remove(); + + return <>{active && target ? createPortal(children, portal) : children}; }; Portal.propTypes = propTypes; +Portal.defaultProps = defaultProps; export default Portal; diff --git a/packages/react-components/source/react/library/tooltips/TooltipHoverArea.js b/packages/react-components/source/react/library/tooltips/TooltipHoverArea.js index 115ab387c..6aa7d9fbe 100644 --- a/packages/react-components/source/react/library/tooltips/TooltipHoverArea.js +++ b/packages/react-components/source/react/library/tooltips/TooltipHoverArea.js @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { usePopper } from 'react-popper'; -import Portal from '../portal/portal'; +import Portal from '../portal'; export const propTypes = { /** Position of tooltip relative to the activating element */ @@ -128,7 +128,7 @@ const TooltipHoverArea = ({ return ( <> {!!children && !!tooltip && ( - + {/* eslint-disable-next-line */}
; From 979c7454fee4d04febee8f1acaca8742f0007ac4 Mon Sep 17 00:00:00 2001 From: "krable55@gmail.com" Date: Tue, 29 Mar 2022 10:46:06 -0700 Subject: [PATCH 2/2] Bump version to 5.33.5 --- packages/react-components/package-lock.json | 2 +- packages/react-components/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/package-lock.json b/packages/react-components/package-lock.json index 768fd003e..6f4396e12 100644 --- a/packages/react-components/package-lock.json +++ b/packages/react-components/package-lock.json @@ -1,6 +1,6 @@ { "name": "@puppet/react-components", - "version": "5.33.4", + "version": "5.33.5", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 623d2c181..77800f136 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@puppet/react-components", - "version": "5.33.4", + "version": "5.33.5", "author": "Puppet, Inc.", "license": "Apache-2.0", "main": "build/library.js",