import { useTheme } from '@mui/material/styles'; import type { PropsWithChildren } from 'react'; import { useCallback, useLayoutEffect, useRef, useState } from 'react'; import { useInView } from 'react-intersection-observer'; import type { TextFitterProps } from './types'; const initialState = { x: 0, y: 0, width: 0, height: 0, }; export const TextFitter: React.FC> = ({ children, width = '100%', height, maxHeight, preserveAspectRatio = 'xMinYMid meet', textStyle, svgStyle, cropTop, cropBottom, onFit, }) => { const theme = useTheme(); const textRef = useRef(null); const [viewBox, setViewBox] = useState>(initialState); const [ref] = useInView({ onChange(inView) { if (inView) { calculateBox(); } }, }); const calculateBox = useCallback(() => { if (!textRef.current) { return; } const box = textRef.current.getBBox(); if (cropTop) { box.y += box.height * cropTop; box.height -= box.height * cropTop; } if (cropBottom) { box.height -= box.height * cropBottom; } setViewBox(box); onFit?.(); }, [cropBottom, cropTop, onFit]); useLayoutEffect(() => { calculateBox(); }, [calculateBox, children]); useLayoutEffect(() => { if (document.fonts) { document.fonts.ready.then(() => { calculateBox(); }); } }, [calculateBox]); return ( = maxHeight // ? maxHeight // : height // } preserveAspectRatio={preserveAspectRatio} fill={theme.palette.text.primary} ref={ref} > {children} ); };