@zendeskgarden/container-tooltip
Version:
Containers relating to tooltip in the Garden Design System
152 lines (147 loc) • 4.8 kB
JavaScript
/**
* 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 };