UNPKG

@lunit/oui

Version:

Lunit Oncology UI components

70 lines (69 loc) 3.36 kB
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;