@kubit-ui-web/react-components
Version:
Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience
149 lines • 6.36 kB
JavaScript
import React from 'react';
import { STYLES_NAME } from '../../../constants/stylesName/stylesName';
import { useClickOutside } from '../../../hooks/useClickOutside/useClickOutside';
import { useEscPressedV2 } from '../../../hooks/useEscPressedV2/useEscPressedV2';
import { useMediaDevice } from '../../../hooks/useMediaDevice/useMediaDevice';
import { useStyles } from '../../../hooks/useStyles/useStyles';
import { DeviceBreakpointsType } from '../../../types/breakpoints/breakpoints';
import { focusElementOrFirstDescendant } from '../../../utils/focusHandlers/focusHandlers';
import { computePosition } from '../positioning/computePosition';
// floating
import { arrow } from '../positioning/middlewares/arrow';
import { flip } from '../positioning/middlewares/flip';
import { shift } from '../positioning/middlewares/shift';
import { TooltipAlignType } from '../types/tooltipAlign';
/**
* @name useTooltip
* @description
* Hook to handle the tooltip functionality
* @param {UseTooltipType} props
* @returns {UseTooltipReturnType}
*/
export const useTooltip = ({ align = TooltipAlignType.TOP, ...props }) => {
const mediaDevice = useMediaDevice();
const lastFocus = React.useRef(null);
// Avoid tooltip is opened automatically after closing the tooltip
const allowFocusOpenTooltip = React.useRef(true);
const openRef = React.useRef(false);
const [open, setOpen] = React.useState(false);
// Need the styles to calc the arrow tooltip position
const styles = useStyles(STYLES_NAME.TOOLTIP, props.variant, props.ctv);
// Avoid to show tooltip when scrolling
React.useEffect(() => {
const handleResize = () => {
updateTooltipPosition();
};
window.addEventListener('scroll', onScroll, true);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('scroll', onScroll, true);
window.removeEventListener('resize', handleResize);
};
}, []);
const onScroll = (e) => {
if (!props.tooltipRef.current?.contains(e.target)) {
updateTooltipPosition();
}
};
const showTooltip = () => {
if (openRef.current) {
return;
}
openRef.current = true;
// Save last focusable element
lastFocus.current = document.activeElement;
if (mediaDevice === DeviceBreakpointsType.DESKTOP) {
if (!props.tooltipRef.current) {
return;
}
props.tooltipRef.current.style.display = 'flex';
updateTooltipPosition();
focusElementOrFirstDescendant(props.tooltipRef.current);
}
setOpen(true);
props.onOpenClose?.(true);
};
const hideTooltip = React.useCallback(() => {
if (!openRef.current) {
return;
}
// Apply focus in last focusable element
openRef.current = false;
allowFocusOpenTooltip.current = false;
// Only focus in the last element if the tooltip is a modal
if (props.tooltipAsModal) {
lastFocus.current?.focus();
}
allowFocusOpenTooltip.current = true;
if (mediaDevice === DeviceBreakpointsType.DESKTOP) {
if (!props.tooltipRef.current) {
return;
}
props.tooltipRef.current.style.display = 'none';
}
setOpen(false);
props.onOpenClose?.(false);
}, [mediaDevice, props.onOpenClose, props.tooltipAsModal]);
const handleEscPress = React.useCallback((event) => {
if (!openRef.current) {
return;
}
// Only stop propagation if the tooltip is opened and its going to be closed
event.stopPropagation();
hideTooltip();
}, [hideTooltip]);
// Because desktop do not use popover component, we need to manually call manually to close on scape and click outside functionality
useEscPressedV2({
ref: props.labelRef,
onEscPress: handleEscPress,
disableStopPropagation: true,
});
// Prevent to be closed when clicking the label, it will be handled by the tooltip label click in order to open/close the tooltip
useClickOutside(props.tooltipRef, hideTooltip, [props.labelRef.current]);
// This function calc the position position where the tooltip should be displayed
const updateTooltipPosition = () => {
if (!props.labelRef.current || !props.tooltipRef.current) {
return;
}
const arrowElement = props.tooltipRef.current.lastElementChild;
const { x, y, placement, middlewareData } = computePosition(props.labelRef.current, props.tooltipRef.current, {
placement: align,
middleware: [
// Offset is handle internally with a padding
// Avoiding onMouseLeave label close the tooltip when you want to put the mouse inside
// offset(props.styles.DESKTOP?.tooltip_offset),
flip(),
shift({ crossAxis: true }),
arrow({ element: arrowElement }),
],
});
Object.assign(props.tooltipRef.current.style, {
left: `${x}px`,
top: `${y}px`,
});
// Arrow pos
const { x: arrowX, y: arrowY } = middlewareData.arrow;
const staticSide = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right',
}[placement.split('-')[0]];
const arrowStyle = {
left: arrowX !== null ? `${arrowX}px` : '',
top: arrowY !== null ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide]: `calc(${styles.tooltipExternalContainer?.[DeviceBreakpointsType.DESKTOP]?.padding ?? 0} - ${styles.arrowContainer?.arrow_position ?? '0px'} / 2)`,
};
if (arrowElement && arrowElement.style) {
arrowElement.style.removeProperty('left');
arrowElement.style.removeProperty('top');
arrowElement.style.removeProperty('right');
arrowElement.style.removeProperty('bottom');
Object.assign(arrowElement?.style, arrowStyle);
}
};
return { showTooltip, hideTooltip, allowFocusOpenTooltip, open };
};
//# sourceMappingURL=useTooltip.js.map