Skip to content

Commit e7fc7fe

Browse files
committed
refactor(CPopover): move from FC to forwardRef
1 parent 317dc92 commit e7fc7fe

File tree

1 file changed

+126
-117
lines changed

1 file changed

+126
-117
lines changed

packages/coreui-react/src/components/popover/CPopover.tsx

+126-117
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import React, { FC, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
1+
import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
22
import { createPortal } from 'react-dom'
33
import classNames from 'classnames'
44
import PropTypes from 'prop-types'
55
import { Transition } from 'react-transition-group'
66

7-
import { usePopper } from '../../hooks'
7+
import { useForkedRef, usePopper } from '../../hooks'
88
import { fallbackPlacementsPropType, triggerPropType } from '../../props'
99
import type { Placements, Triggers } from '../../types'
1010
import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
@@ -68,132 +68,141 @@ export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'tit
6868
visible?: boolean
6969
}
7070

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)
71+
export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
72+
(
73+
{
74+
children,
75+
animation = true,
76+
className,
77+
content,
78+
delay = 0,
79+
fallbackPlacements = ['top', 'right', 'bottom', 'left'],
80+
offset = [0, 8],
81+
onHide,
82+
onShow,
83+
placement = 'top',
84+
title,
85+
trigger = 'click',
86+
visible,
87+
...rest
88+
},
89+
ref,
90+
) => {
91+
const popoverRef = useRef(null)
92+
const togglerRef = useRef(null)
93+
const forkedRef = useForkedRef(ref, popoverRef)
9194

92-
const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
95+
const { initPopper, destroyPopper } = usePopper()
96+
const [_visible, setVisible] = useState(visible)
9397

94-
const popperConfig = {
95-
modifiers: [
96-
{
97-
name: 'arrow',
98-
options: {
99-
element: '.popover-arrow',
98+
const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
99+
100+
const popperConfig = {
101+
modifiers: [
102+
{
103+
name: 'arrow',
104+
options: {
105+
element: '.popover-arrow',
106+
},
100107
},
101-
},
102-
{
103-
name: 'flip',
104-
options: {
105-
fallbackPlacements: fallbackPlacements,
108+
{
109+
name: 'flip',
110+
options: {
111+
fallbackPlacements: fallbackPlacements,
112+
},
106113
},
107-
},
108-
{
109-
name: 'offset',
110-
options: {
111-
offset: offset,
114+
{
115+
name: 'offset',
116+
options: {
117+
offset: offset,
118+
},
112119
},
113-
},
114-
],
115-
placement: getRTLPlacement(placement, togglerRef.current),
116-
}
120+
],
121+
placement: getRTLPlacement(placement, togglerRef.current),
122+
}
117123

118-
useEffect(() => {
119-
setVisible(visible)
120-
}, [visible])
124+
useEffect(() => {
125+
setVisible(visible)
126+
}, [visible])
121127

122-
useEffect(() => {
123-
if (_visible && togglerRef.current && popoverRef.current) {
124-
initPopper(togglerRef.current, popoverRef.current, popperConfig)
125-
}
128+
useEffect(() => {
129+
if (_visible && togglerRef.current && popoverRef.current) {
130+
initPopper(togglerRef.current, popoverRef.current, popperConfig)
131+
}
126132

127-
return () => {
128-
destroyPopper()
129-
}
130-
}, [_visible])
133+
return () => {
134+
destroyPopper()
135+
}
136+
}, [_visible])
131137

132-
const toggleVisible = (visible: boolean) => {
133-
if (visible) {
134-
setTimeout(() => setVisible(true), _delay.show)
135-
return
136-
}
138+
const toggleVisible = (visible: boolean) => {
139+
if (visible) {
140+
setTimeout(() => setVisible(true), _delay.show)
141+
return
142+
}
137143

138-
setTimeout(() => setVisible(false), _delay.hide)
139-
}
144+
setTimeout(() => setVisible(false), _delay.hide)
145+
}
140146

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(
159-
<Transition
160-
in={_visible}
161-
mountOnEnter
162-
nodeRef={popoverRef}
163-
onEnter={onShow}
164-
onExit={onHide}
165-
timeout={{
166-
enter: 0,
167-
exit: popoverRef.current ? getTransitionDurationFromElement(popoverRef.current) + 50 : 200,
168-
}}
169-
unmountOnExit
170-
>
171-
{(state) => (
172-
<div
173-
className={classNames(
174-
'popover',
175-
'bs-popover-auto',
176-
{
177-
fade: animation,
178-
show: state === 'entered',
179-
},
180-
className,
181-
)}
182-
ref={popoverRef}
183-
role="tooltip"
184-
{...rest}
185-
>
186-
<div className="popover-arrow"></div>
187-
<div className="popover-header">{title}</div>
188-
<div className="popover-body">{content}</div>
189-
</div>
190-
)}
191-
</Transition>,
192-
document.body,
193-
)}
194-
</>
195-
)
196-
}
147+
return (
148+
<>
149+
{React.cloneElement(children as React.ReactElement<any>, {
150+
ref: togglerRef,
151+
...((trigger === 'click' || trigger.includes('click')) && {
152+
onClick: () => toggleVisible(!_visible),
153+
}),
154+
...((trigger === 'focus' || trigger.includes('focus')) && {
155+
onFocus: () => toggleVisible(true),
156+
onBlur: () => toggleVisible(false),
157+
}),
158+
...((trigger === 'hover' || trigger.includes('hover')) && {
159+
onMouseEnter: () => toggleVisible(true),
160+
onMouseLeave: () => toggleVisible(false),
161+
}),
162+
})}
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+
)}
202+
</>
203+
)
204+
},
205+
)
197206

198207
CPopover.propTypes = {
199208
animation: PropTypes.bool,

0 commit comments

Comments
 (0)