@zendeskgarden/container-tooltip
Version:
Containers relating to tooltip in the Garden Design System
152 lines (146 loc) • 4.89 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.
*/
;
var React = require('react');
var containerUtilities = require('@zendeskgarden/container-utilities');
var PropTypes = require('prop-types');
const useTooltip = ({
delayMilliseconds = 500,
id,
isLabel,
isVisible,
triggerRef
}) => {
const _id = containerUtilities.useId(id);
const [visibility, setVisibility] = React.useState(isVisible);
const isMounted = React.useRef(false);
const openTooltipTimeoutId = React.useRef();
const closeTooltipTimeoutId = React.useRef();
const isTriggerPopupExpanded = React.useRef(false);
const openTooltip = React.useCallback((delayMs = delayMilliseconds) => {
if (isTriggerPopupExpanded.current) return;
clearTimeout(closeTooltipTimeoutId.current);
const timerId = setTimeout(() => {
if (isMounted.current && !isTriggerPopupExpanded.current
) {
setVisibility(true);
}
}, delayMs);
openTooltipTimeoutId.current = Number(timerId);
}, [delayMilliseconds]);
const closeTooltip = React.useCallback((delayMs = delayMilliseconds) => {
clearTimeout(openTooltipTimeoutId.current);
const timerId = setTimeout(() => {
if (isMounted.current) {
setVisibility(false);
}
}, delayMs);
closeTooltipTimeoutId.current = Number(timerId);
}, [delayMilliseconds]);
React.useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
React.useEffect(() => {
return () => {
clearTimeout(openTooltipTimeoutId.current);
clearTimeout(closeTooltipTimeoutId.current);
};
}, [closeTooltipTimeoutId, openTooltipTimeoutId]);
React.useEffect(() => {
const triggerElement = triggerRef?.current?.getAttribute('aria-haspopup') === 'true' ? triggerRef.current : null;
const handleTriggerPopupChange = () => {
const isExpanded = triggerElement?.getAttribute('aria-expanded') === 'true';
if (triggerElement && isExpanded) {
setVisibility(false);
}
isTriggerPopupExpanded.current = isExpanded;
};
const mutationObserver = new MutationObserver(handleTriggerPopupChange);
if (triggerElement) {
mutationObserver.observe(triggerElement, {
attributes: true,
attributeFilter: ['aria-expanded']
});
}
handleTriggerPopupChange();
return () => mutationObserver.disconnect();
}, [triggerRef]);
const getTriggerProps = React.useCallback(({
tabIndex = 0,
onMouseEnter,
onMouseLeave,
onFocus,
onBlur,
onKeyDown,
...other
} = {}) => ({
tabIndex,
onMouseEnter: containerUtilities.composeEventHandlers(onMouseEnter, () => openTooltip()),
onMouseLeave: containerUtilities.composeEventHandlers(onMouseLeave, () => closeTooltip()),
onFocus: containerUtilities.composeEventHandlers(onFocus, event => {
if (event.currentTarget.matches(':focus-visible')) {
openTooltip();
}
}),
onBlur: containerUtilities.composeEventHandlers(onBlur, () => closeTooltip(0)),
onKeyDown: containerUtilities.composeEventHandlers(onKeyDown, event => {
if (event.key === containerUtilities.KEYS.ESCAPE && visibility) {
closeTooltip(0);
}
}),
[isLabel ? 'aria-labelledby' : 'aria-describedby']: _id,
'data-garden-container-id': 'containers.tooltip',
'data-garden-container-version': '2.2.2',
ref: triggerRef,
...other
}), [_id, isLabel, closeTooltip, openTooltip, triggerRef, visibility]);
const getTooltipProps = React.useCallback(({
role = 'tooltip',
onMouseEnter,
onMouseLeave,
...other
} = {}) => ({
role,
onMouseEnter: containerUtilities.composeEventHandlers(onMouseEnter, () => openTooltip()),
onMouseLeave: containerUtilities.composeEventHandlers(onMouseLeave, () => closeTooltip()),
'aria-hidden': !visibility,
id: _id,
...other
}), [_id, closeTooltip, openTooltip, visibility]);
return React.useMemo(() => ({
isVisible: visibility,
getTooltipProps,
getTriggerProps,
openTooltip,
closeTooltip
}), [closeTooltip, getTooltipProps, getTriggerProps, openTooltip, visibility]);
};
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,
isLabel: PropTypes.bool,
isVisible: PropTypes.bool,
triggerRef: PropTypes.any.isRequired
};
exports.TooltipContainer = TooltipContainer;
exports.useTooltip = useTooltip;