import { type CSSProperties, useEffect, useLayoutEffect, useState } from 'react'; import { Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput'; type InputStyleObject = { value: string; focus: boolean; inputElement: HTMLInputElement | null; } & Pick; export const useInputStyle = ({ value, focus, inputElement, loading }: InputStyleObject) => { const initialRender = !useTimeout(300); const inputWidth = useFirstDefinedValue(inputElement?.clientWidth, [inputElement, value]); const getStyle = (): CSSProperties => { const fontSize = getFontSize(value, focus, inputWidth); return { fontSize, height: fontSize, // aligns the top of the digit with the currency button marginTop: fontSize * -0.19, // if the input loads with a pre-filled value, we don't want to animate the font-size immediately transition: initialRender ? 'none' : undefined, color: loading ? 'var(--color-interactive-secondary)' : undefined, }; }; const [style, setStyle] = useState(getStyle()); useLayoutEffect(() => { setStyle(getStyle()); }, [value, focus, loading, inputWidth]); return style; }; function getFontSize(inputValue: string, isFocused: boolean, inputWidth: number | undefined) { const defaultFontSize = 52; const focusFontSize = 90; const minimumFontSize = 34; let fontSize = isFocused ? focusFontSize : defaultFontSize; if (typeof inputWidth === 'undefined') { return fontSize; } const textLength = inputValue.length; const maxCharactersWithoutShrinking = Math.floor(inputWidth / 40); if (textLength > maxCharactersWithoutShrinking) { const adjustedSize = Math.round((inputWidth / textLength) * 1.9); fontSize = Math.min(fontSize, adjustedSize); } return Math.max(fontSize, minimumFontSize); } const useFirstDefinedValue = (newValue: number | undefined, dependencies: unknown[]) => { const [value, setValue] = useState(newValue); useLayoutEffect(() => { if (typeof newValue !== 'undefined' && typeof value === 'undefined') { setValue(newValue); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [...dependencies, value]); return value; }; const useTimeout = (delay: number) => { const [ready, setReady] = useState(false); useEffect(() => { const timeout = setTimeout(() => { setReady(true); }, delay); return () => { clearTimeout(timeout); }; }, [delay]); return ready; };