import { useState, useCallback, useMemo, useEffect } from 'react'; /** * State conditions that affect accordion behavior */ export interface AccordionStateConditions { /** Whether files are present (steps collapse when false) */ hasFiles?: boolean; /** Whether results are available (steps collapse when true) */ hasResults?: boolean; /** Whether the accordion is disabled (steps collapse when true) */ disabled?: boolean; } /** * Configuration for the useAccordionSteps hook */ export interface UseAccordionStepsConfig { /** Special step that represents "no step open" state */ noneValue: T; /** Initial step to open */ initialStep: T; /** Current state conditions that affect accordion behavior */ stateConditions?: AccordionStateConditions; /** Callback to run when interacting with a step when we have results (usually used for resetting params) */ afterResults?: () => void; } /** * Return type for the useAccordionSteps hook */ export interface AccordionStepsAPI { /** Currently active/open step (noneValue if no step is open) */ currentStep: T; /** Get whether a specific step should be collapsed */ getCollapsedState: (step: T) => boolean; /** Toggle a step open/closed (accordion behavior - only one open at a time) */ handleStepToggle: (step: T) => void; /** Set the currently open step */ setOpenStep: (step: T) => void; /** Close all steps */ closeAllSteps: () => void; } /** * Accordion-style step management hook. * * Provides sophisticated accordion behavior where only one step can be open at a time, * with configurable collapse conditions. */ export function useAccordionSteps( config: UseAccordionStepsConfig ): AccordionStepsAPI { const { initialStep, stateConditions, noneValue } = config; const [openStep, setOpenStep] = useState(initialStep); // Determine if all steps should be collapsed based on conditions const shouldCollapseAll = useMemo(() => { if (!stateConditions) { return false; } return ( (stateConditions.hasFiles === false) || (stateConditions.hasResults === true) || (stateConditions.disabled === true) ); }, [stateConditions]); // Get collapsed state for a specific step const getCollapsedState = useCallback((step: T): boolean => { if (shouldCollapseAll) { return true; } else { return openStep !== step; } }, [openStep, shouldCollapseAll]); // Handle step toggle with accordion behavior const handleStepToggle = useCallback((step: T) => { if (stateConditions?.hasResults) { config.afterResults?.(); } // If all steps should be collapsed, don't allow opening if (shouldCollapseAll) { return; } // Accordion behavior: if clicking the open step, close it; otherwise open the clicked step setOpenStep(currentStep => { if (currentStep === step) { // Clicking the open step - close it return noneValue; } else { // Open the clicked step return step; } }); }, [shouldCollapseAll, noneValue, stateConditions?.hasResults, config.afterResults]); // Close all steps const closeAllSteps = useCallback(() => { setOpenStep(noneValue); }, [noneValue]); // Automatically reset to first step if we have results // Note that everything is still collapsed when this happens, it's just preparing for re-running the tool useEffect(() => { if (stateConditions?.hasResults) { setOpenStep(initialStep); } }, [stateConditions?.hasResults, initialStep]); return { currentStep: shouldCollapseAll ? noneValue : openStep, getCollapsedState, handleStepToggle, setOpenStep, closeAllSteps }; }