import React from 'react'; import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/Wizard/wizard'; 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; } export const Wizard = ({ children, footer, height, width, className, header, nav, navAriaLabel, startIndex = 1, isVisitRequired = false, isProgressive = false, onStepChange, onSave, onClose, ...wrapperProps }: WizardProps) => { const [activeStepIndex, setActiveStepIndex] = React.useState(startIndex); const initialSteps = buildSteps(children); const firstStepRef = React.useRef(initialSteps[startIndex - 1]); // When the startIndex maps to a parent step, focus on the first sub-step React.useEffect(() => { if (isWizardParentStep(firstStepRef.current)) { setActiveStepIndex(startIndex + 1); } }, [startIndex]); 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); }; const goToPrevStep = (event: React.MouseEvent, steps: WizardStepType[] = initialSteps) => { const newStep = [...steps] .reverse() .find((step: WizardStepType) => step.index < activeStepIndex && isStepEnabled(steps, step)); setActiveStepIndex(newStep?.index); onStepChange?.(event, newStep, steps[activeStepIndex - 1], WizardStepChangeScope.Back); }; const goToStepByIndex = ( event: React.MouseEvent, steps: WizardStepType[] = initialSteps, index: number ) => { const lastStepIndex = steps.length + 1; // Handle index when out of bounds or hidden if (index < 1) { index = 1; } else if (index > lastStepIndex) { index = lastStepIndex; } const currStep = steps[index - 1]; const prevStep = steps[activeStepIndex - 1]; setActiveStepIndex(index); onStepChange?.(event, currStep, prevStep, WizardStepChangeScope.Nav); }; const goToStepById = (steps: WizardStepType[] = initialSteps, id: number | string) => { const step = steps.find((step) => step.id === id); const stepIndex = step?.index; const lastStepIndex = steps.length + 1; if (stepIndex > 0 && stepIndex < lastStepIndex && !step.isDisabled && !step.isHidden) { setActiveStepIndex(stepIndex); } }; const goToStepByName = (steps: WizardStepType[] = initialSteps, name: string) => { const step = steps.find((step) => step.name === name); const stepIndex = step?.index; const lastStepIndex = steps.length + 1; if (stepIndex > 0 && stepIndex < lastStepIndex && !step.isDisabled && !step.isHidden) { setActiveStepIndex(stepIndex); } }; return (
{header}
); }; const WizardInternal = ({ nav, navAriaLabel, isVisitRequired, isProgressive }: Pick) => { const { activeStep, steps, footer, goToStepByIndex } = useWizardContext(); const [isNavExpanded, setIsNavExpanded] = React.useState(false); const wizardNav = React.useMemo(() => { if (isCustomWizardNav(nav)) { return typeof nav === 'function' ? nav(isNavExpanded, steps, activeStep, goToStepByIndex) : nav; } return ( ); }, [activeStep, isVisitRequired, isProgressive, goToStepByIndex, isNavExpanded, nav, navAriaLabel, steps]); return ( setIsNavExpanded((prevIsExpanded) => !prevIsExpanded)} /> ); }; Wizard.displayName = 'Wizard';