diff --git a/packages/coreui-react/src/components/conditional-portal/CConditionalPortal.tsx b/packages/coreui-react/src/components/conditional-portal/CConditionalPortal.tsx index aee4bc85..bbbe00cd 100644 --- a/packages/coreui-react/src/components/conditional-portal/CConditionalPortal.tsx +++ b/packages/coreui-react/src/components/conditional-portal/CConditionalPortal.tsx @@ -1,21 +1,45 @@ -import React, { FC, ReactNode } from 'react' +import React, { FC, ReactNode, useEffect, useState } from 'react' import { createPortal } from 'react-dom' import PropTypes from 'prop-types' +const getContainer = (container?: Element | (() => Element | null) | null) => { + if (container) { + return typeof container === 'function' ? container() : container + } + + return document.body +} + export interface CConditionalPortalProps { /** * @ignore */ children: ReactNode + /** + * An HTML element or function that returns a single element, with `document.body` as the default. + * + * @since v4.11.0 + */ + container?: Element | (() => Element | null) | null /** * Render some children into a different part of the DOM */ - portal: boolean + portal: boolean | any } -export const CConditionalPortal: FC = ({ children, portal }) => { - return typeof window !== 'undefined' && portal ? ( - createPortal(children, document.body) +export const CConditionalPortal: FC = ({ + children, + container, + portal, +}) => { + const [_container, setContainer] = useState>(null) + + useEffect(() => { + portal && setContainer(getContainer(container) || document.body) + }, [container, portal]) + + return typeof window !== 'undefined' && portal && _container ? ( + createPortal(children, _container) ) : ( <>{children} ) @@ -23,7 +47,8 @@ export const CConditionalPortal: FC = ({ children, port CConditionalPortal.propTypes = { children: PropTypes.node, - portal: PropTypes.bool.isRequired, + container: PropTypes.any, // HTMLElement + portal: PropTypes.bool, } CConditionalPortal.displayName = 'CConditionalPortal' diff --git a/packages/coreui-react/src/components/dropdown/CDropdown.tsx b/packages/coreui-react/src/components/dropdown/CDropdown.tsx index 59dfbdb9..bc7ab4e3 100644 --- a/packages/coreui-react/src/components/dropdown/CDropdown.tsx +++ b/packages/coreui-react/src/components/dropdown/CDropdown.tsx @@ -51,6 +51,12 @@ export interface CDropdownProps extends HTMLAttributes Element | null) | null /** * Sets a darker color scheme to match a dark navbar. */ @@ -147,6 +153,7 @@ export const CDropdown = forwardRef { export const CDropdownMenu = forwardRef( ({ children, className, component: Component = 'ul', ...rest }, ref) => { - const { alignment, dark, dropdownMenuRef, popper, portal, visible } = + const { alignment, container, dark, dropdownMenuRef, popper, portal, visible } = useContext(CDropdownContext) const forkedRef = useForkedRef(ref, dropdownMenuRef) return ( - + , 'tit * A string of all className you want applied to the component. */ className?: string + /** + * Appends the react popover to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. + * + * @since v4.11.0 + */ + container?: Element | (() => Element | null) | null /** * Content node for your component. */ @@ -74,6 +81,7 @@ export const CPopover = forwardRef( children, animation = true, className, + container, content, delay = 0, fallbackPlacements = ['top', 'right', 'bottom', 'left'], @@ -160,45 +168,43 @@ export const CPopover = forwardRef( onMouseLeave: () => toggleVisible(false), }), })} - {typeof window !== 'undefined' && - createPortal( - - {(state) => ( -
-
-
{title}
-
{content}
-
- )} -
, - document.body, - )} + + + {(state) => ( +
+
+
{title}
+
{content}
+
+ )} +
+
) }, @@ -208,6 +214,7 @@ CPopover.propTypes = { animation: PropTypes.bool, children: PropTypes.node, className: PropTypes.string, + container: PropTypes.any, content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), delay: PropTypes.oneOfType([ PropTypes.number, diff --git a/packages/coreui-react/src/components/tooltip/CTooltip.tsx b/packages/coreui-react/src/components/tooltip/CTooltip.tsx index f056e345..fd29fccc 100644 --- a/packages/coreui-react/src/components/tooltip/CTooltip.tsx +++ b/packages/coreui-react/src/components/tooltip/CTooltip.tsx @@ -1,16 +1,9 @@ -import React, { - forwardRef, - HTMLAttributes, - ReactNode, - useRef, - useEffect, - useState, -} from 'react' -import { createPortal } from 'react-dom' +import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react' import classNames from 'classnames' import PropTypes from 'prop-types' import { Transition } from 'react-transition-group' +import { CConditionalPortal } from '../conditional-portal' import { useForkedRef, usePopper } from '../../hooks' import { fallbackPlacementsPropType, triggerPropType } from '../../props' import type { Placements, Triggers } from '../../types' @@ -27,6 +20,12 @@ export interface CTooltipProps extends Omit, 'con * A string of all className you want applied to the component. */ className?: string + /** + * Appends the react tooltip to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. + * + * @since v4.11.0 + */ + container?: Element | (() => Element | null) | null /** * Content node for your component. */ @@ -77,6 +76,7 @@ export const CTooltip = forwardRef( children, animation = true, className, + container, content, delay = 0, fallbackPlacements = ['top', 'right', 'bottom', 'left'], @@ -162,44 +162,42 @@ export const CTooltip = forwardRef( onMouseLeave: () => toggleVisible(false), }), })} - {typeof window !== 'undefined' && - createPortal( - - {(state) => ( -
-
-
{content}
-
- )} -
, - document.body, - )} + + + {(state) => ( +
+
+
{content}
+
+ )} +
+
) }, @@ -208,6 +206,7 @@ export const CTooltip = forwardRef( CTooltip.propTypes = { animation: PropTypes.bool, children: PropTypes.node, + container: PropTypes.any, content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), delay: PropTypes.oneOfType([ PropTypes.number, diff --git a/packages/docs/content/api/CConditionalPortal.api.mdx b/packages/docs/content/api/CConditionalPortal.api.mdx index cb833359..4a269bf2 100644 --- a/packages/docs/content/api/CConditionalPortal.api.mdx +++ b/packages/docs/content/api/CConditionalPortal.api.mdx @@ -7,4 +7,5 @@ import CConditionalPortal from '@coreui/react/src/components/conditional-portal/ | Property | Description | Type | Default | | --- | --- | --- | --- | -| **portal** | Render some children into a different part of the DOM | `boolean` | - | +| **container** **_v4.11.0+_** | An HTML element or function that returns a single element, with `document.body` as the default. | `Element` \| `(() => Element)` | - | +| **portal** | Render some children into a different part of the DOM | `any` | - | diff --git a/packages/docs/content/api/CDropdown.api.mdx b/packages/docs/content/api/CDropdown.api.mdx index d1ebd44f..7e4c3d32 100644 --- a/packages/docs/content/api/CDropdown.api.mdx +++ b/packages/docs/content/api/CDropdown.api.mdx @@ -11,6 +11,7 @@ import CDropdown from '@coreui/react/src/components/dropdown/CDropdown' | **autoClose** | Configure the auto close behavior of the dropdown:
- `true` - the dropdown will be closed by clicking outside or inside the dropdown menu.
- `false` - the dropdown will be closed by clicking the toggle button and manually calling hide or toggle method. (Also will not be closed by pressing esc key)
- `'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu.
- `'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu. | `boolean` \| `'inside'` \| `'outside'` | true | | **className** | A string of all className you want applied to the base component. | `string` | - | | **component** | Component used for the root node. Either a string to use a HTML element or a component. | `string` \| `ComponentClass` \| `FunctionComponent` | div | +| **container** **_v4.11.0+_** | Appends the react dropdown menu to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. | `Element` \| `(() => Element)` | - | | **dark** | Sets a darker color scheme to match a dark navbar. | `boolean` | - | | **direction** | Sets a specified direction and location of the dropdown menu. | `'center'` \| `'dropup'` \| `'dropup-center'` \| `'dropend'` \| `'dropstart'` | - | | **offset** | Offset of the dropdown menu relative to its target. | `[number, number]` | [0, 2] | diff --git a/packages/docs/content/api/CModal.api.mdx b/packages/docs/content/api/CModal.api.mdx index 7134c041..635c688e 100644 --- a/packages/docs/content/api/CModal.api.mdx +++ b/packages/docs/content/api/CModal.api.mdx @@ -10,6 +10,7 @@ import CModal from '@coreui/react/src/components/modal/CModal' | **alignment** | Align the modal in the center or top of the screen. | `'top'` \| `'center'` | - | | **backdrop** | Apply a backdrop on body while modal is open. | `boolean` \| `'static'` | true | | **className** | A string of all className you want applied to the base component. | `string` | - | +| **focus** **_v4.10.0+_** | Puts the focus on the modal when shown. | `boolean` | true | | **fullscreen** | Set modal to covers the entire user viewport. | `boolean` \| `'sm'` \| `'md'` \| `'lg'` \| `'xl'` \| `'xxl'` | - | | **keyboard** | Closes the modal when escape key is pressed. | `boolean` | true | | **onClose** | Callback fired when the component requests to be closed. | `() => void` | - | diff --git a/packages/docs/content/api/CPopover.api.mdx b/packages/docs/content/api/CPopover.api.mdx index 3cad4f11..40ad6fb7 100644 --- a/packages/docs/content/api/CPopover.api.mdx +++ b/packages/docs/content/api/CPopover.api.mdx @@ -9,6 +9,7 @@ import CPopover from '@coreui/react/src/components/popover/CPopover' | --- | --- | --- | --- | | **animation** **_4.9.0+_** | Apply a CSS fade transition to the popover. | `boolean` | true | | **className** | A string of all className you want applied to the component. | `string` | - | +| **container** **_v4.11.0+_** | Appends the react popover to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. | `Element` \| `(() => Element)` | - | | **content** | Content node for your component. | `ReactNode` | - | | **delay** **_4.9.0+_** | The delay for displaying and hiding the popover (in milliseconds). When a numerical value is provided, the delay applies to both the hide and show actions. The object structure for specifying the delay is as follows: delay: `{ 'show': 500, 'hide': 100 }`. | `number` \| `{ show: number; hide: number; }` | 0 | | **fallbackPlacements** **_4.9.0+_** | Specify the desired order of fallback placements by providing a list of placements as an array. The placements should be prioritized based on preference. | `Placements` \| `Placements[]` | ['top', 'right', 'bottom', 'left'] | diff --git a/packages/docs/content/api/CTooltip.api.mdx b/packages/docs/content/api/CTooltip.api.mdx index 9aa3050d..047a8ff8 100644 --- a/packages/docs/content/api/CTooltip.api.mdx +++ b/packages/docs/content/api/CTooltip.api.mdx @@ -9,6 +9,7 @@ import CTooltip from '@coreui/react/src/components/tooltip/CTooltip' | --- | --- | --- | --- | | **animation** **_4.9.0+_** | Apply a CSS fade transition to the tooltip. | `boolean` | true | | **className** | A string of all className you want applied to the component. | `string` | - | +| **container** **_v4.11.0+_** | Appends the react tooltip to a specific element. You can pass an HTML element or function that returns a single element. By default `document.body`. | `Element` \| `(() => Element)` | - | | **content** | Content node for your component. | `ReactNode` | - | | **delay** **_4.9.0+_** | The delay for displaying and hiding the tooltip (in milliseconds). When a numerical value is provided, the delay applies to both the hide and show actions. The object structure for specifying the delay is as follows: delay: `{ 'show': 500, 'hide': 100 }`. | `number` \| `{ show: number; hide: number; }` | 0 | | **fallbackPlacements** **_4.9.0+_** | Specify the desired order of fallback placements by providing a list of placements as an array. The placements should be prioritized based on preference. | `Placements` \| `Placements[]` | ['top', 'right', 'bottom', 'left'] |