|
1 | | -import classNames from 'classnames' |
2 | | -import { Dispatch, FC, MutableRefObject, ReactNode, SetStateAction, useCallback, useEffect, useRef, useState } from 'react' |
3 | | - |
4 | | -import { |
5 | | - useClickOutside, |
6 | | - UseHoverElementValue, |
7 | | - useOnHoverElement, |
8 | | - useWindowSize, |
9 | | - WindowSize |
10 | | -} from '../hooks' |
11 | | -import { Portal } from '../portal' |
12 | | -import { TooltipArrowIcon } from '../svgs' |
| 1 | +import { FC, ReactNode, RefObject, useRef } from 'react' |
| 2 | +import ReactTooltip from 'react-tooltip' |
| 3 | +import { v4 as uuidv4 } from 'uuid' |
13 | 4 |
|
14 | 5 | import styles from './Tooltip.module.scss' |
15 | 6 |
|
| 7 | +interface TooltipEvent { |
| 8 | + [key: string]: string |
| 9 | +} |
| 10 | + |
16 | 11 | interface TooltipProps { |
17 | | - className?: string |
18 | 12 | content?: string |
19 | | - positionX?: 'start' | 'middle' | 'end' |
20 | | - positionY?: 'start' | 'middle' | 'end' |
| 13 | + place?: 'top' | 'right' | 'bottom' | 'left' |
21 | 14 | trigger: ReactNode |
22 | 15 | triggerOn?: 'click' | 'hover' |
23 | 16 | } |
24 | 17 |
|
25 | | -interface ClickHandlersValue { |
26 | | - onClick: (ev: any) => void |
27 | | -} |
28 | | - |
29 | | -function useClickHandlers( |
30 | | - trigger: MutableRefObject<any>, |
31 | | - toggle: (ev: any) => void, |
32 | | - enabled: boolean = true |
33 | | -): ClickHandlersValue | {} { |
34 | | - useClickOutside(trigger.current, () => toggle(false), enabled) |
35 | | - |
36 | | - return enabled ? { |
37 | | - onClick: toggle, |
38 | | - } : {} |
39 | | -} |
40 | | - |
41 | 18 | const Tooltip: FC<TooltipProps> = ({ |
42 | | - className, |
43 | 19 | content, |
44 | 20 | trigger, |
45 | | - triggerOn = 'click', |
46 | | - positionX = 'middle', |
47 | | - positionY = 'end', |
| 21 | + triggerOn = 'hover', |
| 22 | + place = 'bottom', |
48 | 23 | }: TooltipProps) => { |
49 | | - |
50 | | - const portalRef: MutableRefObject<any> = useRef(undefined) |
51 | | - const triggerRef: MutableRefObject<any> = useRef(undefined) |
52 | | - const tooltipRef: MutableRefObject<any> = useRef(undefined) |
53 | | - const [tooltipOpen, setTooltipOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false) |
54 | | - const { width: windowWidth, height: windowHeight }: WindowSize = useWindowSize() |
55 | | - |
56 | | - const toggleOpen: (toggleValue?: boolean) => void = useCallback((toggleValue?: boolean) => { |
57 | | - setTooltipOpen(currentTooltipOpen => typeof toggleValue === 'boolean' ? toggleValue : !currentTooltipOpen) |
58 | | - }, []) |
59 | | - |
60 | | - const evHandlers: ClickHandlersValue & UseHoverElementValue | {} = { |
61 | | - ...useClickHandlers(triggerRef, toggleOpen, triggerOn === 'click'), |
62 | | - ...useOnHoverElement(triggerRef.current, toggleOpen, triggerOn === 'hover'), |
63 | | - } |
64 | | - |
65 | | - useEffect(() => { |
66 | | - |
67 | | - if (!tooltipOpen || !portalRef?.current || !tooltipRef?.current) { |
68 | | - return |
69 | | - } |
70 | | - |
71 | | - const triggerEl: HTMLElement = triggerRef.current |
72 | | - const box: DOMRect = triggerEl.getBoundingClientRect() |
73 | | - const left: number = Math.max(box.left, windowWidth - (box.left + tooltipRef.current.getBoundingClientRect().width)) |
74 | | - |
75 | | - Object.assign(portalRef.current.style, { |
76 | | - height: `${box.width}px`, |
77 | | - left: `${left}px`, |
78 | | - top: `${box.top + window.scrollY}px`, |
79 | | - width: `${box.width + window.scrollX}px`, |
80 | | - }) |
81 | | - }, [ |
82 | | - tooltipOpen, |
83 | | - windowWidth, |
84 | | - windowHeight, |
85 | | - ]) |
| 24 | + const tooltipId: RefObject<string> = useRef<string>(uuidv4()) |
86 | 25 |
|
87 | 26 | // if we didn't get a tooltip, just return an empty fragment |
88 | 27 | if (!content) { |
89 | 28 | return <></> |
90 | 29 | } |
91 | 30 |
|
| 31 | + let event: TooltipEvent = {} |
| 32 | + let tooltipProps: TooltipEvent = {} |
| 33 | + // The following attributes are required by react-tooltip when we want to show the tooltip on click rather than hover |
| 34 | + if (triggerOn === 'click') { |
| 35 | + tooltipProps = { |
| 36 | + globalEventOff: 'click', |
| 37 | + } |
| 38 | + event = { |
| 39 | + 'data-event': 'click focus', |
| 40 | + } |
| 41 | + } |
| 42 | + |
92 | 43 | return ( |
93 | | - <div className={styles.tooltip}> |
| 44 | + <div> |
94 | 45 | <div |
95 | | - className={classNames('tooltip-icon', styles['tooltip-icon'])} |
96 | | - ref={triggerRef} |
97 | | - {...evHandlers} |
| 46 | + className='tooltip-icon' |
| 47 | + data-tip |
| 48 | + data-for={tooltipId.current} |
| 49 | + {...event} |
98 | 50 | > |
99 | 51 | {trigger} |
100 | 52 | </div> |
101 | | - {tooltipOpen && ( |
102 | | - <Portal portalRef={portalRef} className={styles['tooltip-portal']}> |
103 | | - <div className={classNames(styles['tooltip-open'], `posy-${positionY}`, `posx-${positionX}`, className)} ref={tooltipRef}> |
104 | | - <div className={styles['tooltip-arrow']}> |
105 | | - <TooltipArrowIcon /> |
106 | | - </div> |
107 | | - <div className={styles['tooltip-content']}> |
108 | | - {content} |
109 | | - </div> |
110 | | - </div> |
111 | | - </Portal> |
112 | | - )} |
| 53 | + <ReactTooltip |
| 54 | + className={styles['tooltip']} |
| 55 | + id={tooltipId.current} |
| 56 | + aria-haspopup='true' |
| 57 | + place={place} |
| 58 | + effect='solid' |
| 59 | + event='' |
| 60 | + {...tooltipProps} |
| 61 | + > |
| 62 | + {content} |
| 63 | + </ReactTooltip> |
113 | 64 | </div> |
114 | 65 | ) |
115 | 66 | } |
|
0 commit comments