UNPKG

@zendeskgarden/container-tooltip

Version:

Containers relating to tooltip in the Garden Design System

152 lines (147 loc) 4.8 kB
/** * Copyright Zendesk, Inc. * * Use of this source code is governed under the Apache License, Version 2.0 * found at http://www.apache.org/licenses/LICENSE-2.0. */ import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react'; import { useId, composeEventHandlers, KEYS } from '@zendeskgarden/container-utilities'; import PropTypes from 'prop-types'; const useTooltip = _ref => { let { delayMilliseconds = 500, id, isVisible, triggerRef } = _ref; const _id = useId(id); const [visibility, setVisibility] = useState(isVisible); const [isTriggerPopupExpanded, setIsTriggerPopupExpanded] = useState(false); const isMounted = useRef(false); const openTooltipTimeoutId = useRef(); const closeTooltipTimeoutId = useRef(); const openTooltip = useCallback(function (delayMs) { if (delayMs === void 0) { delayMs = delayMilliseconds; } clearTimeout(closeTooltipTimeoutId.current); const timerId = setTimeout(() => { if (isMounted.current) { setVisibility(true); } }, delayMs); openTooltipTimeoutId.current = Number(timerId); }, [delayMilliseconds]); const closeTooltip = useCallback(function (delayMs) { if (delayMs === void 0) { delayMs = delayMilliseconds; } clearTimeout(openTooltipTimeoutId.current); const timerId = setTimeout(() => { if (isMounted.current) { setVisibility(false); } }, delayMs); closeTooltipTimeoutId.current = Number(timerId); }, [delayMilliseconds]); useEffect(() => { isMounted.current = true; return () => { isMounted.current = false; }; }, []); useEffect(() => { return () => { clearTimeout(openTooltipTimeoutId.current); clearTimeout(closeTooltipTimeoutId.current); }; }, [closeTooltipTimeoutId, openTooltipTimeoutId]); useEffect(() => { const triggerElement = triggerRef?.current?.getAttribute('aria-haspopup') === 'true' ? triggerRef.current : null; const updateTriggerPopupExpandedState = () => { if (triggerElement) { setIsTriggerPopupExpanded(triggerElement.getAttribute('aria-expanded') === 'true'); } }; const mutationObserver = new MutationObserver(updateTriggerPopupExpandedState); if (triggerElement) { mutationObserver.observe(triggerElement, { attributes: true, attributeFilter: ['aria-expanded'] }); } updateTriggerPopupExpandedState(); return () => mutationObserver.disconnect(); }, [triggerRef]); const getTriggerProps = useCallback(function (_temp) { let { tabIndex = 0, onMouseEnter, onMouseLeave, onFocus, onBlur, onKeyDown, ...other } = _temp === void 0 ? {} : _temp; return { tabIndex, onMouseEnter: composeEventHandlers(onMouseEnter, () => openTooltip()), onMouseLeave: composeEventHandlers(onMouseLeave, () => closeTooltip()), onFocus: composeEventHandlers(onFocus, () => openTooltip()), onBlur: composeEventHandlers(onBlur, () => closeTooltip(0)), onKeyDown: composeEventHandlers(onKeyDown, event => { if (event.key === KEYS.ESCAPE && visibility) { closeTooltip(0); } }), 'aria-describedby': _id, 'data-garden-container-id': 'containers.tooltip', 'data-garden-container-version': '2.0.0', ref: triggerRef, ...other }; }, [_id, closeTooltip, openTooltip, triggerRef, visibility]); const getTooltipProps = useCallback(function (_temp2) { let { role = 'tooltip', onMouseEnter, onMouseLeave, ...other } = _temp2 === void 0 ? {} : _temp2; return { role, onMouseEnter: composeEventHandlers(onMouseEnter, () => openTooltip()), onMouseLeave: composeEventHandlers(onMouseLeave, () => closeTooltip()), 'aria-hidden': !visibility || isTriggerPopupExpanded, id: _id, ...other }; }, [_id, closeTooltip, openTooltip, visibility, isTriggerPopupExpanded]); return useMemo(() => ({ isVisible: visibility && !isTriggerPopupExpanded, getTooltipProps, getTriggerProps, openTooltip, closeTooltip }), [closeTooltip, getTooltipProps, getTriggerProps, openTooltip, visibility, isTriggerPopupExpanded]); }; const TooltipContainer = props => { const { children, render = children, ...options } = props; return React.createElement(React.Fragment, null, render(useTooltip(options))); }; TooltipContainer.defaultProps = { delayMilliseconds: 500 }; TooltipContainer.propTypes = { children: PropTypes.func, render: PropTypes.func, delayMilliseconds: PropTypes.number, id: PropTypes.string, isVisible: PropTypes.bool, triggerRef: PropTypes.any.isRequired }; export { TooltipContainer, useTooltip };