import React from 'react'; import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/Wizard/wizard'; import wizardHeightToken from '@patternfly/react-tokens/dist/esm/c_wizard_Height'; import { isWizardParentStep, WizardStepType, isCustomWizardNav, WizardFooterType, WizardNavType, WizardStepChangeScope } from './types'; import { buildSteps, isStepEnabled } from './utils'; import { useWizardContext, WizardContextProvider } from './WizardContext'; import { WizardToggle } from './WizardToggle'; import { WizardNavInternal } from './WizardNavInternal'; /** * Wrapper for all steps and hosts state, including navigation helpers, within context. * The WizardContext provided by default gives any child of wizard access to those resources. */ export interface WizardProps extends React.HTMLProps { /** Step components */ children: React.ReactNode; /** Wizard header */ header?: React.ReactNode; /** Wizard footer */ footer?: WizardFooterType; /** Wizard navigation */ nav?: WizardNavType; /** Aria-label for the Nav */ navAriaLabel?: string; /** The initial index the wizard is to start on (1 or higher). Defaults to 1. */ startIndex?: number; /** Additional classes spread to the wizard */ className?: string; /** Custom width of the wizard */ width?: number | string; /** Custom height of the wizard */ height?: number | string; /** Disables steps that haven't been visited. Defaults to false. */ isVisitRequired?: boolean; /** Progressively shows steps, where all steps following the active step are hidden. Defaults to false. */ isProgressive?: boolean; /** Callback function when navigating between steps */ onStepChange?: ( event: React.MouseEvent, currentStep: WizardStepType, prevStep: WizardStepType, scope: WizardStepChangeScope ) => void | Promise; /** Callback function to save at the end of the wizard, if not specified uses onClose */ onSave?: (event: React.MouseEvent) => void | Promise; /** Callback function to close the wizard */ onClose?: (event: React.MouseEvent) => void; /** Flag indicating whether the wizard content should be focused after the onNext or onBack callbacks * are called. */ shouldFocusContent?: boolean; } export const Wizard = ({ children, footer, height, width, className, header, nav, navAriaLabel, startIndex = 1, isVisitRequired = false, isProgressive = false, onStepChange, onSave, onClose, shouldFocusContent = true, ...wrapperProps }: WizardProps) => { const [activeStepIndex, setActiveStepIndex] = React.useState(startIndex); const initialSteps = buildSteps(children); const firstStepRef = React.useRef(initialSteps[startIndex - 1]); const wrapperRef = React.useRef(null); // When the startIndex maps to a parent step, focus on the first sub-step React.useEffect(() => { if (isWizardParentStep(firstStepRef.current)) { setActiveStepIndex(startIndex + 1); } }, [startIndex]); // When the number of steps changes and pushes activeStepIndex out of bounds, reset back to startIndex React.useEffect(() => { if (activeStepIndex > initialSteps.length) { setActiveStepIndex(startIndex); } }, [initialSteps, activeStepIndex, startIndex]); const focusMainContentElement = () => setTimeout(() => { wrapperRef?.current?.focus && wrapperRef.current.focus(); }, 0); const goToNextStep = (event: React.MouseEvent, steps: WizardStepType[] = initialSteps) => { const newStep = steps.find((step) => step.index > activeStepIndex && isStepEnabled(steps, step)); if (activeStepIndex >= steps.length || !newStep?.index) { return onSave ? onSave(event) : onClose?.(event); } setActiveStepIndex(newStep?.index); onStepChange?.(event, newStep, steps[activeStepIndex - 1], WizardStepChangeScope.Next); shouldFocusContent && focusMainContentElement(); }; const goToPrevStep = (event