import { forwardRef, useCallback, useEffect, useRef, useState, useId, } from 'react' import { StepperProps, StepProps, StepState } from './types' import Block from '../Primitives/Block/Block' import { useResponsiveTokens } from '../../hooks/useResponsiveTokens' import { StepperTokensType } from './stepper.tokens' import { Check, Lock, ChevronDown } from 'lucide-react' import Text from '../Text/Text' import { toPixels } from '../../global-utils/GlobalUtils' import VerticalLine from './VerticalLine' import { FOUNDATION_THEME } from '../../tokens' const StepComponent = forwardRef< HTMLDivElement, StepProps & { onKeyDown?: (event: React.KeyboardEvent, stepIndex: number) => void stepperInstanceId: string } >( ( { step, stepIndex, isCompleted, isCurrent, isLast, onClick, onSubstepClick, clickable, onKeyDown, stepperInstanceId, }, ref ) => { const stepperTokens = useResponsiveTokens('STEPPER') const verticalLineRef = useRef(null) const descriptionRef = useRef(null) const [verticalLineHeight, setVerticalLineHeight] = useState(0) const [descriptionHeight, setDescriptionHeight] = useState(0) const hasSubsteps = !!step?.substeps && step.substeps.length > 0 const isExpandable = step.isExpandable ?? hasSubsteps const [isExpanded, setIsExpanded] = useState( step.isExpanded ?? hasSubsteps ) useEffect(() => { if (verticalLineRef.current || descriptionRef.current) { setVerticalLineHeight( verticalLineRef.current?.clientHeight || 0 ) setDescriptionHeight(descriptionRef.current?.clientHeight || 0) } if (step.isExpanded) { setIsExpanded(true) } }, [verticalLineRef, descriptionRef, step.isExpanded]) const getStepState = (): StepState => { if (step.disabled) return StepState.DISABLED if (step.status) return step.status if (isCompleted) return StepState.COMPLETED if (isCurrent) return StepState.CURRENT return StepState.PENDING } const stepState = getStepState() const isClickable = clickable && !step.disabled && onClick const substepRefs = useRef<(HTMLDivElement | null)[]>([]) const [focusedSubstepIndex, setFocusedSubstepIndex] = useState< number | null >(null) const toggleExpand = useCallback(() => { if (isExpandable) { setIsExpanded((v) => { const newExpanded = !v // Reset substep focus when collapsing if (!newExpanded) { setFocusedSubstepIndex(null) } return newExpanded }) } }, [isExpandable]) const handleClick = useCallback(() => { if (isClickable) { onClick!(stepIndex) } }, [isClickable, onClick, stepIndex]) const handleKeyDown = useCallback( (event: React.KeyboardEvent) => { if (!isClickable) return // If we're in substep navigation mode if ( isExpanded && hasSubsteps && focusedSubstepIndex !== null && step.substeps ) { switch (event.key) { case 'ArrowDown': { event.preventDefault() const nextSubstepIndex = Math.min( focusedSubstepIndex + 1, step.substeps.length - 1 ) if ( !step.substeps[nextSubstepIndex]?.disabled && substepRefs.current[nextSubstepIndex] ) { setFocusedSubstepIndex(nextSubstepIndex) substepRefs.current[nextSubstepIndex]?.focus() } return } case 'ArrowUp': { event.preventDefault() const prevSubstepIndex = Math.max( focusedSubstepIndex - 1, 0 ) if ( !step.substeps[prevSubstepIndex]?.disabled && substepRefs.current[prevSubstepIndex] ) { setFocusedSubstepIndex(prevSubstepIndex) substepRefs.current[prevSubstepIndex]?.focus() } return } case 'ArrowLeft': case 'ArrowRight': // Move back to parent step event.preventDefault() setFocusedSubstepIndex(null) if (ref && typeof ref !== 'function') { ref.current?.focus() } return case 'Enter': case ' ': event.preventDefault() if ( onSubstepClick && step.substeps[focusedSubstepIndex] && !step.substeps[focusedSubstepIndex].disabled ) { onSubstepClick(stepIndex, focusedSubstepIndex) } return } } // Main step navigation switch (event.key) { case 'Enter': case ' ': event.preventDefault() onClick!(stepIndex) break case 'ArrowRight': // Move into substeps if available if (hasSubsteps && !isExpanded) { event.preventDefault() toggleExpand() // Focus first substep after expansion setTimeout(() => { if ( step.substeps && step.substeps.length > 0 && !step.substeps[0]?.disabled && substepRefs.current[0] ) { setFocusedSubstepIndex(0) substepRefs.current[0]?.focus() } }, 100) return } else if (hasSubsteps && isExpanded) { // Move to first substep event.preventDefault() if ( step.substeps && step.substeps.length > 0 && !step.substeps[0]?.disabled && substepRefs.current[0] ) { setFocusedSubstepIndex(0) substepRefs.current[0]?.focus() } return } // Fall through to parent navigation if (onKeyDown) { onKeyDown(event, stepIndex) } break case 'ArrowDown': case 'ArrowUp': case 'Home': case 'End': // Navigation handled by parent component if (onKeyDown) { onKeyDown(event, stepIndex) } break } }, [ isClickable, onClick, stepIndex, onKeyDown, isExpanded, hasSubsteps, focusedSubstepIndex, step.substeps, onSubstepClick, toggleExpand, ref, ] ) const renderStepIcon = () => { if (step.icon) return step.icon switch (stepState) { case StepState.COMPLETED: return (