@coreui/react 4.10.1 → 4.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreui/react",
3
- "version": "4.10.1",
3
+ "version": "4.11.1",
4
4
  "description": "UI Components Library for React.js",
5
5
  "keywords": [
6
6
  "react",
@@ -1,21 +1,45 @@
1
- import React, { FC, ReactNode } from 'react'
1
+ import React, { FC, ReactNode, useEffect, useState } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
3
  import PropTypes from 'prop-types'
4
4
 
5
+ const getContainer = (container?: Element | (() => Element | null) | null) => {
6
+ if (container) {
7
+ return typeof container === 'function' ? container() : container
8
+ }
9
+
10
+ return document.body
11
+ }
12
+
5
13
  export interface CConditionalPortalProps {
6
14
  /**
7
15
  * @ignore
8
16
  */
9
17
  children: ReactNode
18
+ /**
19
+ * An HTML element or function that returns a single element, with `document.body` as the default.
20
+ *
21
+ * @since v4.11.0
22
+ */
23
+ container?: Element | (() => Element | null) | null
10
24
  /**
11
25
  * Render some children into a different part of the DOM
12
26
  */
13
- portal: boolean
27
+ portal: boolean | any
14
28
  }
15
29
 
16
- export const CConditionalPortal: FC<CConditionalPortalProps> = ({ children, portal }) => {
17
- return typeof window !== 'undefined' && portal ? (
18
- createPortal(children, document.body)
30
+ export const CConditionalPortal: FC<CConditionalPortalProps> = ({
31
+ children,
32
+ container,
33
+ portal,
34
+ }) => {
35
+ const [_container, setContainer] = useState<ReturnType<typeof getContainer>>(null)
36
+
37
+ useEffect(() => {
38
+ portal && setContainer(getContainer(container) || document.body)
39
+ }, [container, portal])
40
+
41
+ return typeof window !== 'undefined' && portal && _container ? (
42
+ createPortal(children, _container)
19
43
  ) : (
20
44
  <>{children}</>
21
45
  )
@@ -23,7 +47,8 @@ export const CConditionalPortal: FC<CConditionalPortalProps> = ({ children, port
23
47
 
24
48
  CConditionalPortal.propTypes = {
25
49
  children: PropTypes.node,
26
- portal: PropTypes.bool.isRequired,
50
+ container: PropTypes.any, // HTMLElement
51
+ portal: PropTypes.bool,
27
52
  }
28
53
 
29
54
  CConditionalPortal.displayName = 'CConditionalPortal'
@@ -51,6 +51,12 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
51
51
  * Component used for the root node. Either a string to use a HTML element or a component.
52
52
  */
53
53
  component?: string | ElementType
54
+ /**
55
+ * 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`.
56
+ *
57
+ * @since v4.11.0
58
+ */
59
+ container?: Element | (() => Element | null) | null
54
60
  /**
55
61
  * Sets a darker color scheme to match a dark navbar.
56
62
  */
@@ -147,6 +153,7 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
147
153
  alignment,
148
154
  autoClose = true,
149
155
  className,
156
+ container,
150
157
  dark,
151
158
  direction,
152
159
  offset = [0, 2],
@@ -179,6 +186,7 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
179
186
 
180
187
  const contextValues = {
181
188
  alignment,
189
+ container,
182
190
  dark,
183
191
  dropdownToggleRef,
184
192
  dropdownMenuRef,
@@ -35,13 +35,13 @@ const alignmentClassNames = (alignment: Alignments) => {
35
35
 
36
36
  export const CDropdownMenu = forwardRef<HTMLDivElement | HTMLUListElement, CDropdownMenuProps>(
37
37
  ({ children, className, component: Component = 'ul', ...rest }, ref) => {
38
- const { alignment, dark, dropdownMenuRef, popper, portal, visible } =
38
+ const { alignment, container, dark, dropdownMenuRef, popper, portal, visible } =
39
39
  useContext(CDropdownContext)
40
40
 
41
41
  const forkedRef = useForkedRef(ref, dropdownMenuRef)
42
42
 
43
43
  return (
44
- <CConditionalPortal portal={portal ?? false}>
44
+ <CConditionalPortal container={container} portal={portal ?? false}>
45
45
  <Component
46
46
  className={classNames(
47
47
  'dropdown-menu',
@@ -200,10 +200,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
200
200
  }, [_visible])
201
201
 
202
202
  const handleClickOutside = (event: Event) => {
203
- if (
204
- modalContentRef.current &&
205
- !modalContentRef.current.contains(event.target as HTMLElement)
206
- ) {
203
+ if (modalRef.current && modalRef.current == event.target) {
207
204
  handleDismiss()
208
205
  }
209
206
  }
@@ -1,9 +1,9 @@
1
1
  import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
- import { createPortal } from 'react-dom'
3
2
  import classNames from 'classnames'
4
3
  import PropTypes from 'prop-types'
5
4
  import { Transition } from 'react-transition-group'
6
5
 
6
+ import { CConditionalPortal } from '../conditional-portal'
7
7
  import { useForkedRef, usePopper } from '../../hooks'
8
8
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
9
  import type { Placements, Triggers } from '../../types'
@@ -20,6 +20,12 @@ export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'tit
20
20
  * A string of all className you want applied to the component.
21
21
  */
22
22
  className?: string
23
+ /**
24
+ * 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`.
25
+ *
26
+ * @since v4.11.0
27
+ */
28
+ container?: Element | (() => Element | null) | null
23
29
  /**
24
30
  * Content node for your component.
25
31
  */
@@ -74,6 +80,7 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
74
80
  children,
75
81
  animation = true,
76
82
  className,
83
+ container,
77
84
  content,
78
85
  delay = 0,
79
86
  fallbackPlacements = ['top', 'right', 'bottom', 'left'],
@@ -88,9 +95,10 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
88
95
  },
89
96
  ref,
90
97
  ) => {
91
- const popoverRef = useRef(null)
98
+ const popoverRef = useRef<HTMLDivElement>(null)
92
99
  const togglerRef = useRef(null)
93
100
  const forkedRef = useForkedRef(ref, popoverRef)
101
+ const uID = useRef(`popover${Math.floor(Math.random() * 1_000_000)}`)
94
102
 
95
103
  const { initPopper, destroyPopper } = usePopper()
96
104
  const [_visible, setVisible] = useState(visible)
@@ -125,16 +133,6 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
125
133
  setVisible(visible)
126
134
  }, [visible])
127
135
 
128
- useEffect(() => {
129
- if (_visible && togglerRef.current && popoverRef.current) {
130
- initPopper(togglerRef.current, popoverRef.current, popperConfig)
131
- }
132
-
133
- return () => {
134
- destroyPopper()
135
- }
136
- }, [_visible])
137
-
138
136
  const toggleVisible = (visible: boolean) => {
139
137
  if (visible) {
140
138
  setTimeout(() => setVisible(true), _delay.show)
@@ -147,6 +145,9 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
147
145
  return (
148
146
  <>
149
147
  {React.cloneElement(children as React.ReactElement<any>, {
148
+ ...(_visible && {
149
+ 'aria-describedby': uID.current,
150
+ }),
150
151
  ref: togglerRef,
151
152
  ...((trigger === 'click' || trigger.includes('click')) && {
152
153
  onClick: () => toggleVisible(!_visible),
@@ -160,45 +161,61 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
160
161
  onMouseLeave: () => toggleVisible(false),
161
162
  }),
162
163
  })}
163
- {typeof window !== 'undefined' &&
164
- createPortal(
165
- <Transition
166
- in={_visible}
167
- mountOnEnter
168
- nodeRef={popoverRef}
169
- onEnter={onShow}
170
- onExit={onHide}
171
- timeout={{
172
- enter: 0,
173
- exit: popoverRef.current
174
- ? getTransitionDurationFromElement(popoverRef.current) + 50
175
- : 200,
176
- }}
177
- unmountOnExit
178
- >
179
- {(state) => (
180
- <div
181
- className={classNames(
182
- 'popover',
183
- 'bs-popover-auto',
184
- {
185
- fade: animation,
186
- show: state === 'entered',
187
- },
188
- className,
189
- )}
190
- ref={forkedRef}
191
- role="tooltip"
192
- {...rest}
193
- >
194
- <div className="popover-arrow"></div>
195
- <div className="popover-header">{title}</div>
196
- <div className="popover-body">{content}</div>
197
- </div>
198
- )}
199
- </Transition>,
200
- document.body,
201
- )}
164
+ <CConditionalPortal container={container} portal={true}>
165
+ <Transition
166
+ in={_visible}
167
+ mountOnEnter
168
+ nodeRef={popoverRef}
169
+ onEnter={() => {
170
+ if (togglerRef.current && popoverRef.current) {
171
+ initPopper(togglerRef.current, popoverRef.current, popperConfig)
172
+ }
173
+
174
+ onShow
175
+ }}
176
+ onEntering={() => {
177
+ if (togglerRef.current && popoverRef.current) {
178
+ popoverRef.current.style.display = 'initial'
179
+ }
180
+ }}
181
+ onExit={onHide}
182
+ onExited={() => {
183
+ destroyPopper()
184
+ }}
185
+ timeout={{
186
+ enter: 0,
187
+ exit: popoverRef.current
188
+ ? getTransitionDurationFromElement(popoverRef.current) + 50
189
+ : 200,
190
+ }}
191
+ unmountOnExit
192
+ >
193
+ {(state) => (
194
+ <div
195
+ className={classNames(
196
+ 'popover',
197
+ 'bs-popover-auto',
198
+ {
199
+ fade: animation,
200
+ show: state === 'entered',
201
+ },
202
+ className,
203
+ )}
204
+ id={uID.current}
205
+ ref={forkedRef}
206
+ role="tooltip"
207
+ style={{
208
+ display: 'none',
209
+ }}
210
+ {...rest}
211
+ >
212
+ <div className="popover-arrow"></div>
213
+ <div className="popover-header">{title}</div>
214
+ <div className="popover-body">{content}</div>
215
+ </div>
216
+ )}
217
+ </Transition>
218
+ </CConditionalPortal>
202
219
  </>
203
220
  )
204
221
  },
@@ -208,6 +225,7 @@ CPopover.propTypes = {
208
225
  animation: PropTypes.bool,
209
226
  children: PropTypes.node,
210
227
  className: PropTypes.string,
228
+ container: PropTypes.any,
211
229
  content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
212
230
  delay: PropTypes.oneOfType([
213
231
  PropTypes.number,
@@ -1,16 +1,9 @@
1
- import React, {
2
- forwardRef,
3
- HTMLAttributes,
4
- ReactNode,
5
- useRef,
6
- useEffect,
7
- useState,
8
- } from 'react'
9
- import { createPortal } from 'react-dom'
1
+ import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
10
2
  import classNames from 'classnames'
11
3
  import PropTypes from 'prop-types'
12
4
  import { Transition } from 'react-transition-group'
13
5
 
6
+ import { CConditionalPortal } from '../conditional-portal'
14
7
  import { useForkedRef, usePopper } from '../../hooks'
15
8
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
16
9
  import type { Placements, Triggers } from '../../types'
@@ -27,6 +20,12 @@ export interface CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'con
27
20
  * A string of all className you want applied to the component.
28
21
  */
29
22
  className?: string
23
+ /**
24
+ * 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`.
25
+ *
26
+ * @since v4.11.0
27
+ */
28
+ container?: Element | (() => Element | null) | null
30
29
  /**
31
30
  * Content node for your component.
32
31
  */
@@ -77,6 +76,7 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
77
76
  children,
78
77
  animation = true,
79
78
  className,
79
+ container,
80
80
  content,
81
81
  delay = 0,
82
82
  fallbackPlacements = ['top', 'right', 'bottom', 'left'],
@@ -90,9 +90,10 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
90
90
  },
91
91
  ref,
92
92
  ) => {
93
- const tooltipRef = useRef(null)
93
+ const tooltipRef = useRef<HTMLDivElement>(null)
94
94
  const togglerRef = useRef(null)
95
95
  const forkedRef = useForkedRef(ref, tooltipRef)
96
+ const uID = useRef(`tooltip${Math.floor(Math.random() * 1_000_000)}`)
96
97
 
97
98
  const { initPopper, destroyPopper } = usePopper()
98
99
  const [_visible, setVisible] = useState(visible)
@@ -127,16 +128,6 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
127
128
  setVisible(visible)
128
129
  }, [visible])
129
130
 
130
- useEffect(() => {
131
- if (_visible && togglerRef.current && tooltipRef.current) {
132
- initPopper(togglerRef.current, tooltipRef.current, popperConfig)
133
- }
134
-
135
- return () => {
136
- destroyPopper()
137
- }
138
- }, [_visible])
139
-
140
131
  const toggleVisible = (visible: boolean) => {
141
132
  if (visible) {
142
133
  setTimeout(() => setVisible(true), _delay.show)
@@ -149,6 +140,9 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
149
140
  return (
150
141
  <>
151
142
  {React.cloneElement(children as React.ReactElement<any>, {
143
+ ...(_visible && {
144
+ 'aria-describedby': uID.current,
145
+ }),
152
146
  ref: togglerRef,
153
147
  ...((trigger === 'click' || trigger.includes('click')) && {
154
148
  onClick: () => toggleVisible(!_visible),
@@ -162,44 +156,60 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
162
156
  onMouseLeave: () => toggleVisible(false),
163
157
  }),
164
158
  })}
165
- {typeof window !== 'undefined' &&
166
- createPortal(
167
- <Transition
168
- in={_visible}
169
- mountOnEnter
170
- nodeRef={tooltipRef}
171
- onEnter={onShow}
172
- onExit={onHide}
173
- timeout={{
174
- enter: 0,
175
- exit: tooltipRef.current
176
- ? getTransitionDurationFromElement(tooltipRef.current) + 50
177
- : 200,
178
- }}
179
- unmountOnExit
180
- >
181
- {(state) => (
182
- <div
183
- className={classNames(
184
- 'tooltip',
185
- 'bs-tooltip-auto',
186
- {
187
- fade: animation,
188
- show: state === 'entered',
189
- },
190
- className,
191
- )}
192
- ref={forkedRef}
193
- role="tooltip"
194
- {...rest}
195
- >
196
- <div className="tooltip-arrow"></div>
197
- <div className="tooltip-inner">{content}</div>
198
- </div>
199
- )}
200
- </Transition>,
201
- document.body,
202
- )}
159
+ <CConditionalPortal container={container} portal={true}>
160
+ <Transition
161
+ in={_visible}
162
+ mountOnEnter
163
+ nodeRef={tooltipRef}
164
+ onEnter={() => {
165
+ if (togglerRef.current && tooltipRef.current) {
166
+ initPopper(togglerRef.current, tooltipRef.current, popperConfig)
167
+ }
168
+
169
+ onShow
170
+ }}
171
+ onEntering={() => {
172
+ if (togglerRef.current && tooltipRef.current) {
173
+ tooltipRef.current.style.display = 'initial'
174
+ }
175
+ }}
176
+ onExit={onHide}
177
+ onExited={() => {
178
+ destroyPopper()
179
+ }}
180
+ timeout={{
181
+ enter: 0,
182
+ exit: tooltipRef.current
183
+ ? getTransitionDurationFromElement(tooltipRef.current) + 50
184
+ : 200,
185
+ }}
186
+ unmountOnExit
187
+ >
188
+ {(state) => (
189
+ <div
190
+ className={classNames(
191
+ 'tooltip',
192
+ 'bs-tooltip-auto',
193
+ {
194
+ fade: animation,
195
+ show: state === 'entered',
196
+ },
197
+ className,
198
+ )}
199
+ id={uID.current}
200
+ ref={forkedRef}
201
+ role="tooltip"
202
+ style={{
203
+ display: 'none',
204
+ }}
205
+ {...rest}
206
+ >
207
+ <div className="tooltip-arrow"></div>
208
+ <div className="tooltip-inner">{content}</div>
209
+ </div>
210
+ )}
211
+ </Transition>
212
+ </CConditionalPortal>
203
213
  </>
204
214
  )
205
215
  },
@@ -208,6 +218,7 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
208
218
  CTooltip.propTypes = {
209
219
  animation: PropTypes.bool,
210
220
  children: PropTypes.node,
221
+ container: PropTypes.any,
211
222
  content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
212
223
  delay: PropTypes.oneOfType([
213
224
  PropTypes.number,