@zendeskgarden/react-tooltips
Version:
Collection of components and render prop containers relating to Tooltips in the Garden Design System
137 lines (134 loc) • 4.52 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, { useContext, useRef, useEffect, cloneElement } from 'react';
import { createPortal } from 'react-dom';
import PropTypes from 'prop-types';
import { ThemeContext } from 'styled-components';
import { mergeRefs } from 'react-merge-refs';
import { useTooltip } from '@zendeskgarden/container-tooltip';
import { getControlledValue, composeEventHandlers } from '@zendeskgarden/container-utilities';
import '../styled/StyledParagraph.js';
import '../styled/StyledTitle.js';
import { StyledTooltip } from '../styled/StyledTooltip.js';
import { StyledTooltipWrapper } from '../styled/StyledTooltipWrapper.js';
import { PLACEMENT, SIZE, TYPE } from '../types/index.js';
import { useFloating, platform, autoPlacement, flip, autoUpdate } from '@floating-ui/react-dom';
import { DEFAULT_THEME, getFloatingPlacements } from '@zendeskgarden/react-theming';
import { toSize } from './utils.js';
import { Paragraph } from './Paragraph.js';
import { Title } from './Title.js';
const PLACEMENT_DEFAULT = 'top';
const TooltipComponent = _ref => {
let {
id,
delayMS = 500,
isInitialVisible,
content,
refKey = 'ref',
placement: _placement = PLACEMENT_DEFAULT,
fallbackPlacements: _fallbackPlacements,
children,
hasArrow = true,
size,
type = 'dark',
appendToNode,
zIndex,
isVisible: externalIsVisible,
onFocus,
onBlur,
...props
} = _ref;
const theme = useContext(ThemeContext) || DEFAULT_THEME;
const triggerRef = useRef(null);
const floatingRef = useRef(null);
const {
isVisible,
getTooltipProps,
getTriggerProps,
openTooltip,
closeTooltip
} = useTooltip({
id,
delayMilliseconds: delayMS,
isVisible: isInitialVisible,
triggerRef
});
const controlledIsVisible = getControlledValue(externalIsVisible, isVisible);
const [floatingPlacement, fallbackPlacements] = getFloatingPlacements(theme, _placement === 'auto' ? PLACEMENT_DEFAULT : _placement, _fallbackPlacements);
const {
refs,
placement,
update,
floatingStyles: {
transform
}
} = useFloating({
platform: {
...platform,
isRTL: () => theme.rtl
},
elements: {
reference: triggerRef?.current,
floating: floatingRef?.current
},
placement: floatingPlacement,
middleware: _placement === 'auto' ? [autoPlacement()] : [flip({
fallbackPlacements
})]
});
useEffect(() => {
let cleanup;
if (controlledIsVisible && refs.reference.current && refs.floating.current) {
cleanup = autoUpdate(refs.reference.current, refs.floating.current, update, {
elementResize: typeof ResizeObserver === 'function'
});
}
return () => cleanup && cleanup();
}, [controlledIsVisible, refs.reference, refs.floating, update]);
const Child = React.Children.only(children);
const Node = React.createElement(StyledTooltipWrapper, {
ref: floatingRef,
style: {
transform
},
$zIndex: zIndex,
"aria-hidden": !controlledIsVisible
}, React.createElement(StyledTooltip, Object.assign({
$hasArrow: hasArrow,
$placement: placement,
$size: toSize(size, type),
$type: type
}, getTooltipProps({
'aria-hidden': !controlledIsVisible,
onBlur: composeEventHandlers(onBlur, () => closeTooltip(0)),
onFocus: composeEventHandlers(onFocus, openTooltip),
...props
})), content));
return React.createElement(React.Fragment, null, cloneElement(Child, getTriggerProps({
...Child.props,
[refKey]: mergeRefs([triggerRef, Child.ref ? Child.ref : null])
})), appendToNode ? createPortal(Node, appendToNode) : Node);
};
TooltipComponent.displayName = 'Tooltip';
TooltipComponent.propTypes = {
appendToNode: PropTypes.any,
hasArrow: PropTypes.bool,
delayMS: PropTypes.number,
id: PropTypes.string,
content: PropTypes.node.isRequired,
placement: PropTypes.oneOf(PLACEMENT),
fallbackPlacements: PropTypes.arrayOf(PropTypes.oneOf(PLACEMENT.filter(placement => placement !== 'auto'))),
size: PropTypes.oneOf(SIZE),
type: PropTypes.oneOf(TYPE),
zIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
isInitialVisible: PropTypes.bool,
refKey: PropTypes.string
};
const Tooltip = TooltipComponent;
Tooltip.Paragraph = Paragraph;
Tooltip.Title = Title;
export { PLACEMENT_DEFAULT, Tooltip, TooltipComponent };