import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useStatefulRef } from "@bedrock-layout/use-stateful-ref"; import { useScrollbarWidth, useBreakpoints } from "../../utils"; import { INDICATOR_WIDTH, STEP_X_PADDING, STEP_X_PADDING_SMALL } from "./constants"; import type { ProgressBar } from "."; const STEP_NOT_FOUND_ERROR = new Error(`STEP NOT FOUND`); // eslint-disable-next-line max-lines-per-function export const useProgressBar = ({ steps, value }: React.ComponentProps) => { const firstStepRef = useStatefulRef(null); const [isResizing, setIsResizing] = useState(false); const timerRef = useRef>(); const [dotXpositions, setDotXPositions] = useState>([]); const currentStep = useMemo(() => { const index = steps.findIndex((step) => step.value === value); if (index === -1) { throw STEP_NOT_FOUND_ERROR; } return index; }, [steps, value]); const { s } = useBreakpoints(); const handleResize = useCallback(() => { const step = firstStepRef.current; if (!step) { return; } setIsResizing(true); timerRef.current !== undefined && clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { setIsResizing(false); }, 300); const stepList = step.parentElement!; const rootRect = stepList.getBoundingClientRect(); setDotXPositions( Array.from(stepList.children).map((step, i) => { const stepRect = step.getBoundingClientRect(); const stepCenterPos = stepRect.left - rootRect.left + stepList.scrollLeft + stepRect.width / 2; return ( stepCenterPos - INDICATOR_WIDTH / 2 - (i === 0 ? s ? STEP_X_PADDING_SMALL / 2 : STEP_X_PADDING / 2 : i === stepList.children.length - 1 ? s ? -STEP_X_PADDING_SMALL / 2 : -STEP_X_PADDING / 2 : 0) ); }) ); }, [firstStepRef, s]); useEffect(() => { const element = firstStepRef.current; if (!element) return; const observer = new ResizeObserver(handleResize); observer.observe(element); observer.observe(document.body); return () => { observer.disconnect(); }; }, [firstStepRef, handleResize]); // eslint-disable-next-line max-statements useEffect(() => { const firstStep = firstStepRef.current; if (!firstStep) { return; } const stepIndex = steps.findIndex((step) => step.value === value); const stepList = firstStep.parentElement!; const children = stepList.children; const activeStep = children[stepIndex]; if (!activeStep) { return; } const rect = activeStep.getBoundingClientRect(); const container = activeStep.closest("[data-show-scrollbars]")!; const containerRect = container.getBoundingClientRect(); const left = rect.left - containerRect.left; const isAllVisible = left >= container.scrollLeft && left + rect.width <= container.scrollLeft + containerRect.width; if (!isAllVisible) { container.scrollTo({ left, behavior: "smooth", }); } }, [value, steps, firstStepRef]); const width = useScrollbarWidth(); const shouldOverrideScrollbars = width > 0; return { firstStepRef, currentStep, dotXpositions, shouldOverrideScrollbars, isResizing, }; };