import { useTheme } from '@mui/material' import type { PropsWithChildren } from 'react' import { useCallback, useLayoutEffect, useRef, useState } from 'react' import { useInView } from 'react-intersection-observer' import type { TextFitterProps } from './types.js' const initialState = { x: 0, y: 0, width: 0, height: 0, } export const TextFitter: React.FC> = ({ children, width = '100%', height, 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]) // biome-ignore lint/correctness/useExhaustiveDependencies: run only when children changes useLayoutEffect(() => { calculateBox() }, [calculateBox, children]) useLayoutEffect(() => { if (document.fonts) { document.fonts.ready.then(() => { calculateBox() }) } }, [calculateBox]) return ( = maxHeight // ? maxHeight // : height // } preserveAspectRatio={preserveAspectRatio} fill={theme.vars.palette.text.primary} ref={ref} > {children} {children} ) }