|
1 |
| -import React, { FC, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react' |
| 1 | +import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react' |
2 | 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 { 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'
|
@@ -68,132 +68,141 @@ export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'tit
|
68 | 68 | visible?: boolean
|
69 | 69 | }
|
70 | 70 |
|
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) |
91 | 94 |
|
92 |
| - const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay |
| 95 | + const { initPopper, destroyPopper } = usePopper() |
| 96 | + const [_visible, setVisible] = useState(visible) |
93 | 97 |
|
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 | + }, |
100 | 107 | },
|
101 |
| - }, |
102 |
| - { |
103 |
| - name: 'flip', |
104 |
| - options: { |
105 |
| - fallbackPlacements: fallbackPlacements, |
| 108 | + { |
| 109 | + name: 'flip', |
| 110 | + options: { |
| 111 | + fallbackPlacements: fallbackPlacements, |
| 112 | + }, |
106 | 113 | },
|
107 |
| - }, |
108 |
| - { |
109 |
| - name: 'offset', |
110 |
| - options: { |
111 |
| - offset: offset, |
| 114 | + { |
| 115 | + name: 'offset', |
| 116 | + options: { |
| 117 | + offset: offset, |
| 118 | + }, |
112 | 119 | },
|
113 |
| - }, |
114 |
| - ], |
115 |
| - placement: getRTLPlacement(placement, togglerRef.current), |
116 |
| - } |
| 120 | + ], |
| 121 | + placement: getRTLPlacement(placement, togglerRef.current), |
| 122 | + } |
117 | 123 |
|
118 |
| - useEffect(() => { |
119 |
| - setVisible(visible) |
120 |
| - }, [visible]) |
| 124 | + useEffect(() => { |
| 125 | + setVisible(visible) |
| 126 | + }, [visible]) |
121 | 127 |
|
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 | + } |
126 | 132 |
|
127 |
| - return () => { |
128 |
| - destroyPopper() |
129 |
| - } |
130 |
| - }, [_visible]) |
| 133 | + return () => { |
| 134 | + destroyPopper() |
| 135 | + } |
| 136 | + }, [_visible]) |
131 | 137 |
|
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 | + } |
137 | 143 |
|
138 |
| - setTimeout(() => setVisible(false), _delay.hide) |
139 |
| - } |
| 144 | + setTimeout(() => setVisible(false), _delay.hide) |
| 145 | + } |
140 | 146 |
|
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 | +) |
197 | 206 |
|
198 | 207 | CPopover.propTypes = {
|
199 | 208 | animation: PropTypes.bool,
|
|
0 commit comments