Skip to content

Commit a93c502

Browse files
committed
refactor(CPopover, CTooltip): improve fade animation
1 parent 5c76876 commit a93c502

File tree

2 files changed

+130
-92
lines changed

2 files changed

+130
-92
lines changed

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

+65-46
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
1+
import React, {
2+
forwardRef,
3+
HTMLAttributes,
4+
ReactNode,
5+
useEffect,
6+
useId,
7+
useRef,
8+
useState,
9+
} from 'react'
210
import classNames from 'classnames'
311
import PropTypes from 'prop-types'
412

@@ -97,12 +105,13 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
97105
const popoverRef = useRef<HTMLDivElement>(null)
98106
const togglerRef = useRef(null)
99107
const forkedRef = useForkedRef(ref, popoverRef)
100-
const uID = useRef(`popover${Math.floor(Math.random() * 1_000_000)}`)
101108

102-
const { initPopper, destroyPopper } = usePopper()
109+
const id = `popover${useId()}`
103110
const [mounted, setMounted] = useState(false)
104111
const [_visible, setVisible] = useState(visible)
105112

113+
const { initPopper, destroyPopper } = usePopper()
114+
106115
const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
107116

108117
const popperConfig = {
@@ -130,77 +139,87 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
130139
}
131140

132141
useEffect(() => {
133-
setVisible(visible)
142+
if (visible) {
143+
handleShow()
144+
return
145+
}
146+
147+
handleHide()
134148
}, [visible])
135149

136150
useEffect(() => {
137-
if (_visible) {
138-
setMounted(true)
139-
140-
if (popoverRef.current) {
141-
popoverRef.current.classList.remove('fade', 'show')
142-
destroyPopper()
143-
}
144-
151+
if (mounted && togglerRef.current && popoverRef.current) {
152+
initPopper(togglerRef.current, popoverRef.current, popperConfig)
145153
setTimeout(() => {
146-
if (togglerRef.current && popoverRef.current) {
147-
if (animation) {
148-
popoverRef.current.classList.add('fade')
149-
}
150-
151-
initPopper(togglerRef.current, popoverRef.current, popperConfig)
152-
popoverRef.current.style.removeProperty('display')
153-
popoverRef.current.classList.add('show')
154-
onShow && onShow()
155-
}
154+
setVisible(true)
156155
}, _delay.show)
156+
157+
return
157158
}
158159

159-
return () => {
160-
if (popoverRef.current) {
161-
popoverRef.current.classList.remove('show')
162-
onHide && onHide()
163-
executeAfterTransition(() => {
164-
if (popoverRef.current) {
165-
popoverRef.current.style.display = 'none'
166-
}
167-
168-
destroyPopper()
169-
setMounted(false)
170-
}, popoverRef.current)
171-
}
160+
if (!mounted && togglerRef.current && popoverRef.current) {
161+
destroyPopper()
162+
}
163+
}, [mounted])
164+
165+
useEffect(() => {
166+
if (!_visible && togglerRef.current && popoverRef.current) {
167+
executeAfterTransition(() => {
168+
setMounted(false)
169+
}, popoverRef.current)
172170
}
173171
}, [_visible])
174172

173+
const handleShow = () => {
174+
setMounted(true)
175+
if (onShow) {
176+
onShow()
177+
}
178+
}
179+
180+
const handleHide = () => {
181+
setTimeout(() => {
182+
setVisible(false)
183+
if (onHide) {
184+
onHide()
185+
}
186+
}, _delay.hide)
187+
}
188+
175189
return (
176190
<>
177191
{React.cloneElement(children as React.ReactElement<any>, {
178192
...(_visible && {
179-
'aria-describedby': uID.current,
193+
'aria-describedby': id,
180194
}),
181195
ref: togglerRef,
182196
...((trigger === 'click' || trigger.includes('click')) && {
183-
onClick: () => setVisible(!_visible),
197+
onClick: () => (_visible ? handleHide() : handleShow()),
184198
}),
185199
...((trigger === 'focus' || trigger.includes('focus')) && {
186-
onFocus: () => setVisible(true),
187-
onBlur: () => setVisible(false),
200+
onFocus: () => handleShow(),
201+
onBlur: () => handleHide(),
188202
}),
189203
...((trigger === 'hover' || trigger.includes('hover')) && {
190-
onMouseEnter: () => setVisible(true),
191-
onMouseLeave: () => setVisible(false),
204+
onMouseEnter: () => handleShow(),
205+
onMouseLeave: () => handleHide(),
192206
}),
193207
})}
194208
<CConditionalPortal container={container} portal={true}>
195209
{mounted && (
196210
<div
197-
className={classNames('popover', 'bs-popover-auto', className)}
198-
id={uID.current}
211+
className={classNames(
212+
'popover',
213+
'bs-popover-auto',
214+
{
215+
fade: animation,
216+
show: _visible,
217+
},
218+
className,
219+
)}
220+
id={id}
199221
ref={forkedRef}
200222
role="tooltip"
201-
style={{
202-
display: 'none',
203-
}}
204223
{...rest}
205224
>
206225
<div className="popover-arrow"></div>

packages/coreui-react/src/components/tooltip/CTooltip.tsx

+65-46
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
1+
import React, {
2+
forwardRef,
3+
HTMLAttributes,
4+
ReactNode,
5+
useEffect,
6+
useId,
7+
useRef,
8+
useState,
9+
} from 'react'
210
import classNames from 'classnames'
311
import PropTypes from 'prop-types'
412

@@ -92,12 +100,13 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
92100
const tooltipRef = useRef<HTMLDivElement>(null)
93101
const togglerRef = useRef(null)
94102
const forkedRef = useForkedRef(ref, tooltipRef)
95-
const uID = useRef(`tooltip${Math.floor(Math.random() * 1_000_000)}`)
96103

97-
const { initPopper, destroyPopper, updatePopper } = usePopper()
104+
const id = `tooltip${useId()}`
98105
const [mounted, setMounted] = useState(false)
99106
const [_visible, setVisible] = useState(visible)
100107

108+
const { initPopper, destroyPopper, updatePopper } = usePopper()
109+
101110
const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
102111

103112
const popperConfig = {
@@ -125,48 +134,53 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
125134
}
126135

127136
useEffect(() => {
128-
setVisible(visible)
137+
if (visible) {
138+
handleShow()
139+
return
140+
}
141+
142+
handleHide()
129143
}, [visible])
130144

131145
useEffect(() => {
132-
if (_visible) {
133-
setMounted(true)
134-
135-
if (tooltipRef.current) {
136-
tooltipRef.current.classList.remove('fade', 'show')
137-
destroyPopper()
138-
}
139-
146+
if (mounted && togglerRef.current && tooltipRef.current) {
147+
initPopper(togglerRef.current, tooltipRef.current, popperConfig)
140148
setTimeout(() => {
141-
if (togglerRef.current && tooltipRef.current) {
142-
if (animation) {
143-
tooltipRef.current.classList.add('fade')
144-
}
145-
146-
initPopper(togglerRef.current, tooltipRef.current, popperConfig)
147-
tooltipRef.current.style.removeProperty('display')
148-
tooltipRef.current.classList.add('show')
149-
onShow && onShow()
150-
}
149+
setVisible(true)
151150
}, _delay.show)
151+
152+
return
152153
}
153154

154-
return () => {
155-
if (tooltipRef.current) {
156-
tooltipRef.current.classList.remove('show')
157-
onHide && onHide()
158-
executeAfterTransition(() => {
159-
if (tooltipRef.current) {
160-
tooltipRef.current.style.display = 'none'
161-
}
162-
163-
destroyPopper()
164-
setMounted(false)
165-
}, tooltipRef.current)
166-
}
155+
if (!mounted && togglerRef.current && tooltipRef.current) {
156+
destroyPopper()
157+
}
158+
}, [mounted])
159+
160+
useEffect(() => {
161+
if (!_visible && togglerRef.current && tooltipRef.current) {
162+
executeAfterTransition(() => {
163+
setMounted(false)
164+
}, tooltipRef.current)
167165
}
168166
}, [_visible])
169167

168+
const handleShow = () => {
169+
setMounted(true)
170+
if (onShow) {
171+
onShow()
172+
}
173+
}
174+
175+
const handleHide = () => {
176+
setTimeout(() => {
177+
setVisible(false)
178+
if (onHide) {
179+
onHide()
180+
}
181+
}, _delay.hide)
182+
}
183+
170184
useEffect(() => {
171185
updatePopper()
172186
}, [content])
@@ -175,31 +189,36 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
175189
<>
176190
{React.cloneElement(children as React.ReactElement<any>, {
177191
...(_visible && {
178-
'aria-describedby': uID.current,
192+
'aria-describedby': id,
179193
}),
180194
ref: togglerRef,
181195
...((trigger === 'click' || trigger.includes('click')) && {
182-
onClick: () => setVisible(!_visible),
196+
onClick: () => (_visible ? handleHide() : handleShow()),
183197
}),
184198
...((trigger === 'focus' || trigger.includes('focus')) && {
185-
onFocus: () => setVisible(true),
186-
onBlur: () => setVisible(false),
199+
onFocus: () => handleShow(),
200+
onBlur: () => handleHide(),
187201
}),
188202
...((trigger === 'hover' || trigger.includes('hover')) && {
189-
onMouseEnter: () => setVisible(true),
190-
onMouseLeave: () => setVisible(false),
203+
onMouseEnter: () => handleShow(),
204+
onMouseLeave: () => handleHide(),
191205
}),
192206
})}
193207
<CConditionalPortal container={container} portal={true}>
194208
{mounted && (
195209
<div
196-
className={classNames('tooltip', 'bs-tooltip-auto', className)}
197-
id={uID.current}
210+
className={classNames(
211+
'tooltip',
212+
'bs-tooltip-auto',
213+
{
214+
fade: animation,
215+
show: _visible,
216+
},
217+
className,
218+
)}
219+
id={id}
198220
ref={forkedRef}
199221
role="tooltip"
200-
style={{
201-
display: 'none',
202-
}}
203222
{...rest}
204223
>
205224
<div className="tooltip-arrow"></div>

0 commit comments

Comments
 (0)