@lunit/oui
Version:
Lunit Oncology UI components
70 lines (69 loc) • 3.36 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, useCallback, useContext, useEffect, useRef, useState } from 'react';
import StyledTypography from './EllipsisTypography.styled';
import { Tooltip } from '../Tooltip';
import { ResizeObserverContext } from '../ResizeObserver/ResizeObserverContext';
const MAX_RETRY_COUNT = 10;
const RETRY_DELAY_MS = 50;
const DEFAULT_HEIGHT_THRESHOLD = 1;
const EllipsisTypography = forwardRef(({ children, heightThreshold: heightThresholdProp, tooltipPlacement = 'bottom', maxLines = 1, ...otherProps }, forwardedRef) => {
const internalRef = useRef(null);
const retryCountRef = useRef(0);
const timeoutRef = useRef(null);
const direction = otherProps.direction || 'row';
const heightThreshold = heightThresholdProp ?? DEFAULT_HEIGHT_THRESHOLD;
const [showTooltip, setShowTooltip] = useState(false);
const { addResizeHandler, removeResizeHandler } = useContext(ResizeObserverContext);
const heightThresholdRef = useRef(heightThreshold);
heightThresholdRef.current = heightThreshold;
const checkOverflow = useCallback((target) => {
if (!target)
return;
const { scrollHeight, clientHeight, scrollWidth, clientWidth } = target;
// The element may not be laid out yet (all metrics 0); retry until it is.
if (scrollHeight === 0 && clientHeight === 0 && scrollWidth === 0 && clientWidth === 0) {
if (retryCountRef.current < MAX_RETRY_COUNT) {
retryCountRef.current += 1;
timeoutRef.current = setTimeout(() => checkOverflow(target), RETRY_DELAY_MS);
}
return;
}
retryCountRef.current = 0;
const threshold = heightThresholdRef.current;
// Single-line ellipsis truncates horizontally (scrollHeight === clientHeight), so width must
// also be compared. Multi-line clamp still overflows vertically.
const isOverflowing = scrollWidth > clientWidth + threshold || scrollHeight > clientHeight + threshold;
setShowTooltip(isOverflowing);
}, []);
const setRefs = useCallback((node) => {
internalRef.current = node;
if (typeof forwardedRef === 'function') {
forwardedRef(node);
}
else if (forwardedRef) {
forwardedRef.current = node;
}
}, [forwardedRef]);
useEffect(() => {
const target = internalRef.current;
if (!target)
return;
const run = () => checkOverflow(target);
if (addResizeHandler) {
addResizeHandler(target, run);
}
run();
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
if (removeResizeHandler) {
removeResizeHandler(target);
}
};
}, [children, maxLines, checkOverflow, addResizeHandler, removeResizeHandler]);
const TypographyComponent = (_jsx(StyledTypography, { ...otherProps, ref: setRefs, direction: direction, maxLines: maxLines, children: children }));
return showTooltip ? (_jsx(Tooltip, { title: children, placement: tooltipPlacement, size: "small", children: TypographyComponent })) : (TypographyComponent);
});
EllipsisTypography.displayName = 'EllipsisTypography';
export default EllipsisTypography;