UNPKG

@react-ui-org/react-ui

Version:

React UI is a themeable UI library for React apps.

124 lines (115 loc) 3.69 kB
import PropTypes from 'prop-types'; import React from 'react'; import { createPortal } from 'react-dom'; import { transferProps } from '../../helpers/transferProps'; import { classNames } from '../../helpers/classNames'; import { withGlobalProps } from '../../providers/globalProps'; import cleanPlacementStyle from './_helpers/cleanPlacementStyle'; import getRootSideClassName from './_helpers/getRootSideClassName'; import getRootAlignmentClassName from './_helpers/getRootAlignmentClassName'; import styles from './Popover.module.scss'; export const Popover = React.forwardRef((props, ref) => { const { placement, children, placementStyle, popoverTargetId, portalId, ...restProps } = props; const PopoverEl = ( <> {/** * This hack is needed because the default behavior of the Popover API is to place the popover into a * top-layer. It is currently not possible to position an element in the top-layer relative to a normal element. * This will create a hidden browser popover, then with CSS it will open and close the RUI popover. */} {!!popoverTargetId && ( <div className={styles.helper} id={popoverTargetId} popover="auto" /> )} <div {...transferProps(restProps)} className={classNames( styles.root, ref && styles.isRootControlled, popoverTargetId && styles.controlledPopover, getRootSideClassName(placement, styles), getRootAlignmentClassName(placement, styles), )} ref={ref} style={placementStyle ? cleanPlacementStyle(placementStyle) : undefined} > {children} <span className={styles.arrow} /> </div> </> ); if (portalId === undefined) { return PopoverEl; } return createPortal(PopoverEl, document.getElementById(portalId)); }); Popover.defaultProps = { placement: 'bottom', placementStyle: undefined, popoverTargetId: undefined, portalId: undefined, }; Popover.propTypes = { /** * Popover content. */ children: PropTypes.node.isRequired, /** * Popover placement affects position of the arrow. * Compatible with [Floating UI API](https://floating-ui.com/docs/computePosition#placement). */ placement: PropTypes.oneOf([ 'top', 'top-start', 'top-end', 'right', 'right-start', 'right-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', ]), /** * Used for positioning the popover with a library like Floating UI. It is filtered, * then passed to the popover as the `style` prop. */ placementStyle: PropTypes.shape({ bottom: PropTypes.string, inset: PropTypes.string, 'inset-block-end': PropTypes.string, 'inset-block-start': PropTypes.string, 'inset-inline-end': PropTypes.string, 'inset-inline-start': PropTypes.string, left: PropTypes.string, position: PropTypes.string, right: PropTypes.string, top: PropTypes.string, 'transform-origin': PropTypes.string, translate: PropTypes.string, }), /** * If set, the popover will become controlled, meaning it will be hidden by default and will need a trigger to open. * This sets the ID of the internal helper element for the popover. * Assign the same ID to `popovertarget` of a trigger to make it open and close. */ popoverTargetId: PropTypes.string, /** * If set, popover is rendered in the React Portal with that ID. */ portalId: PropTypes.string, }; export const PopoverWithGlobalProps = withGlobalProps(Popover, 'Popover'); export default PopoverWithGlobalProps;