diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 6104e8070af..08aa94e98a8 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -1,9 +1,18 @@ -import React from 'react'; +import React, { + memo, + useEffect, + useState, + useRef, + RefObject, + useCallback, +} from 'react'; import {createUniqueIDFactory} from '@shopify/javascript-utilities/other'; import {findFirstFocusableNode} from '@shopify/javascript-utilities/focus'; import {PreferredPosition} from '../PositionedOverlay'; import {Portal} from '../Portal'; +import {useUniqueId} from '../../utilities/unique-id'; +import {useFocused} from '../../utilities/use-focused'; import {TooltipOverlay} from './components'; import styles from './Tooltip.scss'; @@ -28,122 +37,103 @@ export interface TooltipProps { activatorWrapper?: string; } -interface State { - active: boolean; - activatorNode: HTMLElement | null; -} - -const getUniqueID = createUniqueIDFactory('TooltipContent'); - -export class Tooltip extends React.PureComponent { - state: State = { - active: Boolean(this.props.active), - activatorNode: null, - }; +export function Tooltip({ + children, + content, + light, + active: originalActive, + preferredPosition = 'below', + activatorWrapper = 'span', +}: TooltipProps) { + const WrapperComponent: any = activatorWrapper; + const [active, {handleFocus, handleBlur}] = useFocused( + Boolean(originalActive), + ); + const [activatorNode, setActivatorNode] = useState(null); + + const id = useUniqueId('TooltipContent'); + const activatorContainer = useRef(null); + const mouseEntered = useRef(false); + + // Check this impl + const setAccessibilityAttributes = useCallback(() => { + if (activatorContainer == null) { + return; + } - private id = getUniqueID(); - private activatorContainer: HTMLElement | null; - private mouseEntered = false; + const firstFocusable = activatorContainer.current + ? findFirstFocusableNode(activatorContainer.current) + : null; + const accessibilityNode = firstFocusable || activatorContainer.current; - componentDidMount() { - this.setAccessibilityAttributes(); - } + if (!accessibilityNode) return; - componentDidUpdate() { - this.setAccessibilityAttributes(); - } - - render() { - const {id} = this; - - const { - children, - content, - light, - preferredPosition = 'below', - activatorWrapper: WrapperComponent = 'span' as any, - } = this.props; - - const {active, activatorNode} = this.state; - - const portal = activatorNode ? ( - - -
- {content} -
-
-
- ) : null; - - return ( - { + setAccessibilityAttributes(); + }, [setAccessibilityAttributes]); + + const portal = activatorNode ? ( + + - {children} - {portal} - - ); - } - - private setActivator = (node: HTMLElement | null) => { +
+ {content} +
+ + + ) : null; + + return ( + + {children} + {portal} + + ); + + function setActivator(node: HTMLElement | null) { + const activatorContainerRef: any = activatorContainer; if (node == null) { - this.activatorContainer = null; - this.setState({activatorNode: null}); + activatorContainerRef.current = null; + setActivatorNode(null); return; } - this.setState({activatorNode: node.firstElementChild as HTMLElement}); - this.activatorContainer = node; - }; - - private handleFocus = () => { - this.setState({active: true}); - }; - - private handleBlur = () => { - this.setState({active: false}); - }; + setActivatorNode(node.firstElementChild as HTMLElement); + activatorContainerRef.current = node; + } - private handleMouseEnter = () => { - this.mouseEntered = true; - this.setState({active: true}); - }; + // maybe add pre focus and pre blur???? + function handleMouseEnter() { + mouseEntered.current = true; + handleFocus(); + } - private handleMouseLeave = () => { - this.mouseEntered = false; - this.setState({active: false}); - }; + function handleMouseLeave() { + mouseEntered.current = false; + handleBlur(); + } // https://github.com/facebook/react/issues/10109 // Mouseenter event not triggered when cursor moves from disabled button - private handleMouseEnterFix = () => { - !this.mouseEntered && this.handleMouseEnter(); - }; - - private setAccessibilityAttributes() { - const {activatorContainer, id} = this; - if (activatorContainer == null) { - return; - } - - const firstFocusable = findFirstFocusableNode(activatorContainer); - const accessibilityNode = firstFocusable || activatorContainer; - - accessibilityNode.tabIndex = 0; - accessibilityNode.setAttribute('aria-describedby', id); + function handleMouseEnterFix() { + !mouseEntered.current && handleMouseEnter(); } }