@coreui/react 4.10.0 → 4.11.0

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.0",
3
+ "version": "4.11.0",
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',
@@ -1,10 +1,11 @@
1
- import React, { FC, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
- import { createPortal } from 'react-dom'
1
+ import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
+ // import { createPortal } from 'react-dom'
3
3
  import classNames from 'classnames'
4
4
  import PropTypes from 'prop-types'
5
5
  import { Transition } from 'react-transition-group'
6
6
 
7
- import { usePopper } from '../../hooks'
7
+ import { CConditionalPortal } from '../conditional-portal'
8
+ import { useForkedRef, usePopper } from '../../hooks'
8
9
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
10
  import type { Placements, Triggers } from '../../types'
10
11
  import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
@@ -20,6 +21,12 @@ export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'tit
20
21
  * A string of all className you want applied to the component.
21
22
  */
22
23
  className?: string
24
+ /**
25
+ * 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`.
26
+ *
27
+ * @since v4.11.0
28
+ */
29
+ container?: Element | (() => Element | null) | null
23
30
  /**
24
31
  * Content node for your component.
25
32
  */
@@ -68,94 +75,100 @@ export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'tit
68
75
  visible?: boolean
69
76
  }
70
77
 
71
- export const CPopover: FC<CPopoverProps> = ({
72
- children,
73
- animation = true,
74
- className,
75
- content,
76
- delay = 0,
77
- fallbackPlacements = ['top', 'right', 'bottom', 'left'],
78
- offset = [0, 8],
79
- onHide,
80
- onShow,
81
- placement = 'top',
82
- title,
83
- trigger = 'click',
84
- visible,
85
- ...rest
86
- }) => {
87
- const popoverRef = useRef(null)
88
- const togglerRef = useRef(null)
89
- const { initPopper, destroyPopper } = usePopper()
90
- const [_visible, setVisible] = useState(visible)
78
+ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
79
+ (
80
+ {
81
+ children,
82
+ animation = true,
83
+ className,
84
+ container,
85
+ content,
86
+ delay = 0,
87
+ fallbackPlacements = ['top', 'right', 'bottom', 'left'],
88
+ offset = [0, 8],
89
+ onHide,
90
+ onShow,
91
+ placement = 'top',
92
+ title,
93
+ trigger = 'click',
94
+ visible,
95
+ ...rest
96
+ },
97
+ ref,
98
+ ) => {
99
+ const popoverRef = useRef(null)
100
+ const togglerRef = useRef(null)
101
+ const forkedRef = useForkedRef(ref, popoverRef)
91
102
 
92
- const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
103
+ const { initPopper, destroyPopper } = usePopper()
104
+ const [_visible, setVisible] = useState(visible)
93
105
 
94
- const popperConfig = {
95
- modifiers: [
96
- {
97
- name: 'arrow',
98
- options: {
99
- element: '.popover-arrow',
106
+ const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
107
+
108
+ const popperConfig = {
109
+ modifiers: [
110
+ {
111
+ name: 'arrow',
112
+ options: {
113
+ element: '.popover-arrow',
114
+ },
100
115
  },
101
- },
102
- {
103
- name: 'flip',
104
- options: {
105
- fallbackPlacements: fallbackPlacements,
116
+ {
117
+ name: 'flip',
118
+ options: {
119
+ fallbackPlacements: fallbackPlacements,
120
+ },
106
121
  },
107
- },
108
- {
109
- name: 'offset',
110
- options: {
111
- offset: offset,
122
+ {
123
+ name: 'offset',
124
+ options: {
125
+ offset: offset,
126
+ },
112
127
  },
113
- },
114
- ],
115
- placement: getRTLPlacement(placement, togglerRef.current),
116
- }
128
+ ],
129
+ placement: getRTLPlacement(placement, togglerRef.current),
130
+ }
117
131
 
118
- useEffect(() => {
119
- setVisible(visible)
120
- }, [visible])
132
+ useEffect(() => {
133
+ setVisible(visible)
134
+ }, [visible])
121
135
 
122
- useEffect(() => {
123
- if (_visible && togglerRef.current && popoverRef.current) {
124
- initPopper(togglerRef.current, popoverRef.current, popperConfig)
125
- }
136
+ useEffect(() => {
137
+ if (_visible && togglerRef.current && popoverRef.current) {
138
+ initPopper(togglerRef.current, popoverRef.current, popperConfig)
139
+ }
126
140
 
127
- return () => {
128
- destroyPopper()
129
- }
130
- }, [_visible])
141
+ return () => {
142
+ destroyPopper()
143
+ }
144
+ }, [_visible])
131
145
 
132
- const toggleVisible = (visible: boolean) => {
133
- if (visible) {
134
- setTimeout(() => setVisible(true), _delay.show)
135
- return
136
- }
146
+ const toggleVisible = (visible: boolean) => {
147
+ if (visible) {
148
+ setTimeout(() => setVisible(true), _delay.show)
149
+ return
150
+ }
137
151
 
138
- setTimeout(() => setVisible(false), _delay.hide)
139
- }
152
+ setTimeout(() => setVisible(false), _delay.hide)
153
+ }
140
154
 
141
- return (
142
- <>
143
- {React.cloneElement(children as React.ReactElement<any>, {
144
- ref: togglerRef,
145
- ...((trigger === 'click' || trigger.includes('click')) && {
146
- onClick: () => toggleVisible(!_visible),
147
- }),
148
- ...((trigger === 'focus' || trigger.includes('focus')) && {
149
- onFocus: () => toggleVisible(true),
150
- onBlur: () => toggleVisible(false),
151
- }),
152
- ...((trigger === 'hover' || trigger.includes('hover')) && {
153
- onMouseEnter: () => toggleVisible(true),
154
- onMouseLeave: () => toggleVisible(false),
155
- }),
156
- })}
157
- {typeof window !== 'undefined' &&
158
- createPortal(
155
+ return (
156
+ <>
157
+ {React.cloneElement(children as React.ReactElement<any>, {
158
+ ref: togglerRef,
159
+ ...((trigger === 'click' || trigger.includes('click')) && {
160
+ onClick: () => toggleVisible(!_visible),
161
+ }),
162
+ ...((trigger === 'focus' || trigger.includes('focus')) && {
163
+ onFocus: () => toggleVisible(true),
164
+ onBlur: () => toggleVisible(false),
165
+ }),
166
+ ...((trigger === 'hover' || trigger.includes('hover')) && {
167
+ onMouseEnter: () => toggleVisible(true),
168
+ onMouseLeave: () => toggleVisible(false),
169
+ }),
170
+ })}
171
+ <CConditionalPortal container={container} portal={true}>
159
172
  <Transition
160
173
  in={_visible}
161
174
  mountOnEnter
@@ -164,7 +177,9 @@ export const CPopover: FC<CPopoverProps> = ({
164
177
  onExit={onHide}
165
178
  timeout={{
166
179
  enter: 0,
167
- exit: popoverRef.current ? getTransitionDurationFromElement(popoverRef.current) + 50 : 200,
180
+ exit: popoverRef.current
181
+ ? getTransitionDurationFromElement(popoverRef.current) + 50
182
+ : 200,
168
183
  }}
169
184
  unmountOnExit
170
185
  >
@@ -179,7 +194,7 @@ export const CPopover: FC<CPopoverProps> = ({
179
194
  },
180
195
  className,
181
196
  )}
182
- ref={popoverRef}
197
+ ref={forkedRef}
183
198
  role="tooltip"
184
199
  {...rest}
185
200
  >
@@ -188,17 +203,18 @@ export const CPopover: FC<CPopoverProps> = ({
188
203
  <div className="popover-body">{content}</div>
189
204
  </div>
190
205
  )}
191
- </Transition>,
192
- document.body,
193
- )}
194
- </>
195
- )
196
- }
206
+ </Transition>
207
+ </CConditionalPortal>
208
+ </>
209
+ )
210
+ },
211
+ )
197
212
 
198
213
  CPopover.propTypes = {
199
214
  animation: PropTypes.bool,
200
215
  children: PropTypes.node,
201
216
  className: PropTypes.string,
217
+ container: PropTypes.any,
202
218
  content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
203
219
  delay: PropTypes.oneOfType([
204
220
  PropTypes.number,
@@ -1,10 +1,10 @@
1
- import React, { FC, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
- import { createPortal } from 'react-dom'
1
+ import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
3
2
  import classNames from 'classnames'
4
3
  import PropTypes from 'prop-types'
5
4
  import { Transition } from 'react-transition-group'
6
5
 
7
- import { usePopper } from '../../hooks'
6
+ import { CConditionalPortal } from '../conditional-portal'
7
+ import { useForkedRef, usePopper } from '../../hooks'
8
8
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
9
  import type { Placements, Triggers } from '../../types'
10
10
  import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
@@ -20,6 +20,12 @@ export interface CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'con
20
20
  * A string of all className you want applied to the component.
21
21
  */
22
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
23
29
  /**
24
30
  * Content node for your component.
25
31
  */
@@ -64,101 +70,110 @@ export interface CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'con
64
70
  visible?: boolean
65
71
  }
66
72
 
67
- export const CTooltip: FC<CTooltipProps> = ({
68
- children,
69
- animation = true,
70
- className,
71
- content,
72
- delay = 0,
73
- fallbackPlacements = ['top', 'right', 'bottom', 'left'],
74
- offset = [0, 6],
75
- onHide,
76
- onShow,
77
- placement = 'top',
78
- trigger = ['hover', 'focus'],
79
- visible,
80
- ...rest
81
- }) => {
82
- const tooltipRef = useRef(null)
83
- const togglerRef = useRef(null)
84
- const { initPopper, destroyPopper } = usePopper()
85
- const [_visible, setVisible] = useState(visible)
73
+ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
74
+ (
75
+ {
76
+ children,
77
+ animation = true,
78
+ className,
79
+ container,
80
+ content,
81
+ delay = 0,
82
+ fallbackPlacements = ['top', 'right', 'bottom', 'left'],
83
+ offset = [0, 6],
84
+ onHide,
85
+ onShow,
86
+ placement = 'top',
87
+ trigger = ['hover', 'focus'],
88
+ visible,
89
+ ...rest
90
+ },
91
+ ref,
92
+ ) => {
93
+ const tooltipRef = useRef(null)
94
+ const togglerRef = useRef(null)
95
+ const forkedRef = useForkedRef(ref, tooltipRef)
86
96
 
87
- const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
97
+ const { initPopper, destroyPopper } = usePopper()
98
+ const [_visible, setVisible] = useState(visible)
88
99
 
89
- const popperConfig = {
90
- modifiers: [
91
- {
92
- name: 'arrow',
93
- options: {
94
- element: '.tooltip-arrow',
100
+ const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
101
+
102
+ const popperConfig = {
103
+ modifiers: [
104
+ {
105
+ name: 'arrow',
106
+ options: {
107
+ element: '.tooltip-arrow',
108
+ },
95
109
  },
96
- },
97
- {
98
- name: 'flip',
99
- options: {
100
- fallbackPlacements: fallbackPlacements,
110
+ {
111
+ name: 'flip',
112
+ options: {
113
+ fallbackPlacements: fallbackPlacements,
114
+ },
101
115
  },
102
- },
103
- {
104
- name: 'offset',
105
- options: {
106
- offset: offset,
116
+ {
117
+ name: 'offset',
118
+ options: {
119
+ offset: offset,
120
+ },
107
121
  },
108
- },
109
- ],
110
- placement: getRTLPlacement(placement, togglerRef.current),
111
- }
122
+ ],
123
+ placement: getRTLPlacement(placement, togglerRef.current),
124
+ }
112
125
 
113
- useEffect(() => {
114
- setVisible(visible)
115
- }, [visible])
126
+ useEffect(() => {
127
+ setVisible(visible)
128
+ }, [visible])
116
129
 
117
- useEffect(() => {
118
- if (_visible && togglerRef.current && tooltipRef.current) {
119
- initPopper(togglerRef.current, tooltipRef.current, popperConfig)
120
- }
130
+ useEffect(() => {
131
+ if (_visible && togglerRef.current && tooltipRef.current) {
132
+ initPopper(togglerRef.current, tooltipRef.current, popperConfig)
133
+ }
121
134
 
122
- return () => {
123
- destroyPopper()
124
- }
125
- }, [_visible])
135
+ return () => {
136
+ destroyPopper()
137
+ }
138
+ }, [_visible])
126
139
 
127
- const toggleVisible = (visible: boolean) => {
128
- if (visible) {
129
- setTimeout(() => setVisible(true), _delay.show)
130
- return
131
- }
140
+ const toggleVisible = (visible: boolean) => {
141
+ if (visible) {
142
+ setTimeout(() => setVisible(true), _delay.show)
143
+ return
144
+ }
132
145
 
133
- setTimeout(() => setVisible(false), _delay.hide)
134
- }
146
+ setTimeout(() => setVisible(false), _delay.hide)
147
+ }
135
148
 
136
- return (
137
- <>
138
- {React.cloneElement(children as React.ReactElement<any>, {
139
- ref: togglerRef,
140
- ...((trigger === 'click' || trigger.includes('click')) && {
141
- onClick: () => toggleVisible(!_visible),
142
- }),
143
- ...((trigger === 'focus' || trigger.includes('focus')) && {
144
- onFocus: () => toggleVisible(true),
145
- onBlur: () => toggleVisible(false),
146
- }),
147
- ...((trigger === 'hover' || trigger.includes('hover')) && {
148
- onMouseEnter: () => toggleVisible(true),
149
- onMouseLeave: () => toggleVisible(false),
150
- }),
151
- })}
152
- {typeof window !== 'undefined' &&
153
- createPortal(
149
+ return (
150
+ <>
151
+ {React.cloneElement(children as React.ReactElement<any>, {
152
+ ref: togglerRef,
153
+ ...((trigger === 'click' || trigger.includes('click')) && {
154
+ onClick: () => toggleVisible(!_visible),
155
+ }),
156
+ ...((trigger === 'focus' || trigger.includes('focus')) && {
157
+ onFocus: () => toggleVisible(true),
158
+ onBlur: () => toggleVisible(false),
159
+ }),
160
+ ...((trigger === 'hover' || trigger.includes('hover')) && {
161
+ onMouseEnter: () => toggleVisible(true),
162
+ onMouseLeave: () => toggleVisible(false),
163
+ }),
164
+ })}
165
+ <CConditionalPortal container={container} portal={true}>
154
166
  <Transition
155
167
  in={_visible}
156
168
  mountOnEnter
169
+ nodeRef={tooltipRef}
157
170
  onEnter={onShow}
158
171
  onExit={onHide}
159
172
  timeout={{
160
173
  enter: 0,
161
- exit: tooltipRef.current ? getTransitionDurationFromElement(tooltipRef.current) + 50 : 200,
174
+ exit: tooltipRef.current
175
+ ? getTransitionDurationFromElement(tooltipRef.current) + 50
176
+ : 200,
162
177
  }}
163
178
  unmountOnExit
164
179
  >
@@ -173,7 +188,7 @@ export const CTooltip: FC<CTooltipProps> = ({
173
188
  },
174
189
  className,
175
190
  )}
176
- ref={tooltipRef}
191
+ ref={forkedRef}
177
192
  role="tooltip"
178
193
  {...rest}
179
194
  >
@@ -181,16 +196,17 @@ export const CTooltip: FC<CTooltipProps> = ({
181
196
  <div className="tooltip-inner">{content}</div>
182
197
  </div>
183
198
  )}
184
- </Transition>,
185
- document.body,
186
- )}
187
- </>
188
- )
189
- }
199
+ </Transition>
200
+ </CConditionalPortal>
201
+ </>
202
+ )
203
+ },
204
+ )
190
205
 
191
206
  CTooltip.propTypes = {
192
207
  animation: PropTypes.bool,
193
208
  children: PropTypes.node,
209
+ container: PropTypes.any,
194
210
  content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
195
211
  delay: PropTypes.oneOfType([
196
212
  PropTypes.number,