|
1 | | -import React, { FC, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react' |
| 1 | +import React, { |
| 2 | + forwardRef, |
| 3 | + HTMLAttributes, |
| 4 | + ReactNode, |
| 5 | + useRef, |
| 6 | + useEffect, |
| 7 | + useState, |
| 8 | +} from 'react' |
2 | 9 | import { createPortal } from 'react-dom' |
3 | 10 | import classNames from 'classnames' |
4 | 11 | import PropTypes from 'prop-types' |
5 | 12 | import { Transition } from 'react-transition-group' |
6 | 13 |
|
7 | | -import { usePopper } from '../../hooks' |
| 14 | +import { useForkedRef, usePopper } from '../../hooks' |
8 | 15 | import { fallbackPlacementsPropType, triggerPropType } from '../../props' |
9 | 16 | import type { Placements, Triggers } from '../../types' |
10 | 17 | import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils' |
@@ -64,129 +71,139 @@ export interface CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'con |
64 | 71 | visible?: boolean |
65 | 72 | } |
66 | 73 |
|
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) |
| 74 | +export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>( |
| 75 | + ( |
| 76 | + { |
| 77 | + children, |
| 78 | + animation = true, |
| 79 | + className, |
| 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 { popper, 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( |
154 | | - <Transition |
155 | | - in={_visible} |
156 | | - mountOnEnter |
157 | | - onEnter={onShow} |
158 | | - onExit={onHide} |
159 | | - timeout={{ |
160 | | - enter: 0, |
161 | | - exit: tooltipRef.current ? getTransitionDurationFromElement(tooltipRef.current) + 50 : 200, |
162 | | - }} |
163 | | - unmountOnExit |
164 | | - > |
165 | | - {(state) => ( |
166 | | - <div |
167 | | - className={classNames( |
168 | | - 'tooltip', |
169 | | - 'bs-tooltip-auto', |
170 | | - { |
171 | | - fade: animation, |
172 | | - show: state === 'entered', |
173 | | - }, |
174 | | - className, |
175 | | - )} |
176 | | - ref={tooltipRef} |
177 | | - role="tooltip" |
178 | | - {...rest} |
179 | | - > |
180 | | - <div className="tooltip-arrow"></div> |
181 | | - <div className="tooltip-inner">{content}</div> |
182 | | - </div> |
183 | | - )} |
184 | | - </Transition>, |
185 | | - document.body, |
186 | | - )} |
187 | | - </> |
188 | | - ) |
189 | | -} |
| 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 | + {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 | + )} |
| 203 | + </> |
| 204 | + ) |
| 205 | + }, |
| 206 | +) |
190 | 207 |
|
191 | 208 | CTooltip.propTypes = { |
192 | 209 | animation: PropTypes.bool, |
|
0 commit comments