UNPKG

@patternfly/react-core

Version:

This library provides a set of common React components for use with the PatternFly reference implementation.

862 lines (793 loc) • 23.8 kB
--- id: Wizard section: components cssPrefix: pf-c-wizard propComponents: ['Wizard', 'WizardNav', 'WizardNavItem', 'WizardHeader', 'WizardBody', 'WizardFooter', 'WizardToggle', 'WizardStep'] --- import { Button, Wizard, WizardFooter, WizardContextConsumer, ModalVariant, Alert, EmptyState, EmptyStateIcon, EmptyStateBody, EmptyStateSecondaryActions, Title, Progress } from '@patternfly/react-core'; import ExternalLinkAltIcon from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; import SlackHashIcon from '@patternfly/react-icons/dist/esm/icons/slack-hash-icon'; import FinishedStep from './FinishedStep'; import SampleForm from './SampleForm'; ## Examples ### Basic ```js import React from 'react'; import { Button, Wizard } from '@patternfly/react-core'; class SimpleWizard extends React.Component { constructor(props) { super(props); } render() { const steps = [ { name: 'First step', component: <p>Step 1 content</p> }, { name: 'Second step', component: <p>Step 2 content</p> }, { name: 'Third step', component: <p>Step 3 content</p> }, { name: 'Fourth step', component: <p>Step 4 content</p> }, { name: 'Review', component: <p>Review step content</p>, nextButtonText: 'Finish' } ]; const title = 'Basic wizard'; return <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} steps={steps} height={400} />; } } ``` ### Anchors for nav items ```js import React from 'react'; import { Button, Wizard } from '@patternfly/react-core'; import ExternalLinkAltIcon from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; import SlackHashIcon from '@patternfly/react-icons/dist/esm/icons/slack-hash-icon'; class WizardWithNavAnchors extends React.Component { constructor(props) { super(props); } render() { const steps = [ { name: ( <div> <ExternalLinkAltIcon /> PF3 </div> ), component: <p>Step 1: Read about PF3</p>, stepNavItemProps: { navItemComponent: 'a', href: 'https://www.patternfly.org/v3/', target: '_blank' } }, { name: ( <div> <ExternalLinkAltIcon /> PF4 </div> ), component: <p>Step 2: Read about PF4</p>, stepNavItemProps: { navItemComponent: 'a', href: 'https://www.patternfly.org/v4/', target: '_blank' } }, { name: ( <div> <SlackHashIcon /> Join us on slack </div> ), component: ( <Button variant="link" component="a" target="_blank" href="https://patternfly.slack.com/"> Join the conversation </Button> ), stepNavItemProps: { navItemComponent: 'a', href: 'https://patternfly.slack.com/', target: '_blank' } } ]; const title = 'Anchor link wizard'; return <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} steps={steps} height={400} />; } } ``` ### Incrementally enabled steps ```js import React from 'react'; import { Button, Wizard } from '@patternfly/react-core'; class IncrementallyEnabledStepsWizard extends React.Component { constructor(props) { super(props); this.state = { stepIdReached: 1 }; this.onNext = ({ id }) => { this.setState({ stepIdReached: this.state.stepIdReached < id ? id : this.state.stepIdReached }); }; this.closeWizard = () => { console.log('close wizard'); }; } render() { const { stepIdReached } = this.state; const steps = [ { id: 1, name: 'First step', component: <p>Step 1 content</p> }, { id: 2, name: 'Second step', component: <p>Step 2 content</p>, canJumpTo: stepIdReached >= 2 }, { id: 3, name: 'Third step', component: <p>Step 3 content</p>, canJumpTo: stepIdReached >= 3 }, { id: 4, name: 'Fourth step', component: <p>Step 4 content</p>, canJumpTo: stepIdReached >= 4 }, { id: 5, name: 'Review', component: <p>Review step content</p>, nextButtonText: 'Finish', canJumpTo: stepIdReached >= 5 } ]; const title = 'Incrementally enabled wizard'; return ( <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} onClose={this.closeWizard} steps={steps} onNext={this.onNext} height={400} /> ); } } ``` ### Expandable steps ```js import React from 'react'; import { Button, Wizard } from '@patternfly/react-core'; class SimpleWizard extends React.Component { constructor(props) { super(props); } render() { const steps = [ { name: 'First step', steps: [ { name: 'Substep A', component: <p>Substep A content</p> }, { name: 'Substep B', component: <p>Substep B content</p> } ] }, { name: 'Second step', component: <p>Step 2 content</p> }, { name: 'Third step', steps: [ { name: 'Substep C', component: <p>Substep C content</p> }, { name: 'Substep D', component: <p>Substep D content</p> } ] }, { name: 'Fourth step', component: <p>Step 4 content</p> }, { name: 'Review', component: <p>Review step content</p>, nextButtonText: 'Finish' } ]; const title = 'Basic wizard'; return ( <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} steps={steps} height={400} isNavExpandable /> ); } } ``` ### Finished ```js import React from 'react'; import { Button, Wizard } from '@patternfly/react-core'; import FinishedStep from './examples/FinishedStep'; class FinishedStepWizard extends React.Component { constructor(props) { super(props); this.closeWizard = () => { console.log('close wizard'); }; } render() { const steps = [ { name: 'First step', component: <p>Step 1 content</p> }, { name: 'Second step', component: <p>Step 2 content</p> }, { name: 'Third step', component: <p>Step 3 content</p> }, { name: 'Fourth step', component: <p>Step 4 content</p> }, { name: 'Review', component: <p>Review step content</p>, nextButtonText: 'Finish' }, { name: 'Finish', component: <FinishedStep onClose={this.closeWizard} />, isFinishedStep: true } ]; const title = 'Finished wizard'; return ( <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} onClose={this.closeWizard} steps={steps} height={400} /> ); } } ``` ### Enabled on form validation ```js import React from 'react'; import { Button, Wizard, Form, FormGroup, TextInput } from '@patternfly/react-core'; import SampleForm from './examples/SampleForm'; class ValidationWizard extends React.Component { constructor(props) { super(props); this.state = { isFormValid: false, formValue: 'Thirty', allStepsValid: false, stepIdReached: 1 }; this.closeWizard = () => { console.log('close wizard'); }; this.onFormChange = (isValid, value) => { this.setState( { isFormValid: isValid, formValue: value }, this.areAllStepsValid ); }; this.areAllStepsValid = () => { this.setState({ allStepsValid: this.state.isFormValid }); }; this.onNext = ({ id, name }, { prevId, prevName }) => { console.log(`current id: ${id}, current name: ${name}, previous id: ${prevId}, previous name: ${prevName}`); this.setState({ stepIdReached: this.state.stepIdReached < id ? id : this.state.stepIdReached }); this.areAllStepsValid(); }; this.onBack = ({ id, name }, { prevId, prevName }) => { console.log(`current id: ${id}, current name: ${name}, previous id: ${prevId}, previous name: ${prevName}`); this.areAllStepsValid(); }; this.onGoToStep = ({ id, name }, { prevId, prevName }) => { console.log(`current id: ${id}, current name: ${name}, previous id: ${prevId}, previous name: ${prevName}`); }; this.onSave = () => { console.log('Saved and closed the wizard'); this.setState({ isOpen: false }); }; } render() { const { isFormValid, formValue, allStepsValid, stepIdReached } = this.state; const steps = [ { id: 1, name: 'Information', component: <p>Step 1 content</p> }, { name: 'Configuration', steps: [ { id: 2, name: 'Substep A with validation', component: <SampleForm formValue={formValue} isFormValid={isFormValid} onChange={this.onFormChange} />, enableNext: isFormValid, canJumpTo: stepIdReached >= 2 }, { id: 3, name: 'Substep B', component: <p>Substep B</p>, canJumpTo: stepIdReached >= 3 } ] }, { id: 4, name: 'Additional', component: <p>Step 3 content</p>, enableNext: allStepsValid, canJumpTo: stepIdReached >= 4 }, { id: 5, name: 'Review', component: <p>Step 4 content</p>, nextButtonText: 'Close', canJumpTo: stepIdReached >= 5 } ]; const title = 'Enabled on form validation wizard'; return ( <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} onClose={this.closeWizard} onSave={this.onSave} steps={steps} onNext={this.onNext} onBack={this.onBack} onGoToStep={this.onGoToStep} height={400} /> ); } } ``` ### Validate on button press This example demonstrates how to use the `WizardContextConsumer` to consume the `WizardContext`. `WizardContext` can be used to imperatively move to a specific wizard step. The definition of the `WizardContext` is as follows: ``` interface WizardContext { goToStepById: (stepId: number | string) => void; goToStepByName: (stepName: string) => void; onNext: () => void; onBack: () => void; onClose: () => void; activeStep: WizardStep; } ``` ```js import React from 'react'; import { Button, Wizard, WizardFooter, WizardContextConsumer, Alert } from '@patternfly/react-core'; import SampleForm from './examples/SampleForm'; import FinishedStep from './examples/FinishedStep'; class ValidateButtonPressWizard extends React.Component { constructor(props) { super(props); this.state = { stepsValid: 0 }; this.closeWizard = () => { console.log('close wizard'); }; this.validateLastStep = onNext => { const { stepsValid } = this.state; if (stepsValid !== 1) { this.setState({ stepsValid: 1 }); } else { onNext(); } }; } render() { const { stepsValid } = this.state; const steps = [ { name: 'First step', component: <p>Step 1 content</p> }, { name: 'Second step', component: <p>Step 2 content</p> }, { name: 'Final Step', component: ( <> {stepsValid === 1 && ( <div style={{ padding: '15px 0' }}> <Alert variant="warning" title="Validation failed, please try again" /> </div> )} <SampleForm formValue="Validating on button press" isFormValid={stepsValid !== 1} /> </> ) }, { name: 'Finish', component: <FinishedStep onClose={this.closeWizard} />, isFinishedStep: true } ]; const CustomFooter = ( <WizardFooter> <WizardContextConsumer> {({ activeStep, goToStepByName, goToStepById, onNext, onBack, onClose }) => { if (activeStep.name !== 'Final Step') { return ( <> <Button variant="primary" type="submit" onClick={onNext}> Forward </Button> <Button variant="secondary" onClick={onBack} className={activeStep.name === 'Step 1' ? 'pf-m-disabled' : ''} > Backward </Button> <Button variant="link" onClick={onClose}> Cancel </Button> </> ); } // Final step buttons return ( <> <Button onClick={() => this.validateLastStep(onNext)}>Validate</Button> <Button onClick={() => goToStepByName('Step 1')}>Go to Beginning</Button> </> ); }} </WizardContextConsumer> </WizardFooter> ); const title = 'Validate on button press wizard'; return ( <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} onClose={this.closeWizard} footer={CustomFooter} steps={steps} height={400} /> ); } } ``` ### Progressive steps ```js import React from 'react'; import { Button, Radio, Wizard, WizardFooter, WizardContextConsumer, Alert } from '@patternfly/react-core'; import SampleForm from './examples/SampleForm'; import FinishedStep from './examples/FinishedStep'; class ProgressiveWizard extends React.Component { constructor(props) { super(props); this.state = { showCreateStep: false, showUpdateStep: false, showOptionsStep: false, showReviewStep: false, getStartedStepRadio: 'Create', createStepRadio: 'Quick', updateStepRadio: 'Quick' }; this.closeWizard = () => { console.log('close wizard'); }; this.onGoToStep = ({ id, name }, { prevId, prevName }) => { // Remove steps after the currently clicked step if (name === 'Get started') { this.setState({ showReviewStep: false, showOptionsStep: false, showCreateStep: false, showUpdateStep: false }); } else if (name === 'Create options' || name === 'Update options') { this.setState({ showReviewStep: false, showOptionsStep: false }); } else if (name.indexOf('Substep') > -1) { this.setState({ showReviewStep: false }); } }; this.getNextStep = (activeStep, callback) => { if (activeStep.name === 'Get started') { if (this.state.getStartedStepRadio === 'Create') { this.setState( { showCreateStep: true, showUpdateStep: false, showOptionsStep: false, showReviewStep: false }, () => { callback(); } ); } else { this.setState( { showCreateStep: false, showUpdateStep: true, showOptionsStep: false, showReviewStep: false }, () => { callback(); } ); } } else if (activeStep.name === 'Create options' || activeStep.name === 'Update options') { this.setState( { showOptionsStep: true, showReviewStep: false }, () => { callback(); } ); } else if (activeStep.name === 'Substep 3') { this.setState( { showReviewStep: true }, () => { callback(); } ); } else { callback(); } }; this.getPreviousStep = (activeStep, callback) => { if (activeStep.name === 'Review') { this.setState( { showReviewStep: false }, () => { callback(); } ); } else if (activeStep.name === 'Substep 1') { this.setState( { showOptionsStep: false }, () => { callback(); } ); } else if (activeStep.name === 'Create options') { this.setState( { showCreateStep: false }, () => { callback(); } ); } else if (activeStep.name === 'Update options') { this.setState( { showUpdateStep: false }, () => { callback(); } ); } else { callback(); } }; } render() { const { stepsValid, getStartedStepRadio, createStepRadio, updateStepRadio, showCreateStep, showUpdateStep, showOptionsStep, showReviewStep } = this.state; const getStartedStep = { name: 'Get started', component: ( <div> <Radio value="Create" isChecked={getStartedStepRadio === 'Create'} onChange={(_, event) => this.setState({ getStartedStepRadio: event.currentTarget.value })} label="Create a new thing" name="radio-step-start" id="radio-step-start-1" />{' '} <Radio value="Update" isChecked={getStartedStepRadio === 'Update'} onChange={(_, event) => this.setState({ getStartedStepRadio: event.currentTarget.value })} label="Update an existing thing" name="radio-step-start" id="radio-step-start-2" /> </div> ) }; const createStep = { name: 'Create options', component: ( <div> <Radio value="Quick" isChecked={createStepRadio === 'Quick'} onChange={(_, event) => this.setState({ createStepRadio: event.currentTarget.value })} label="Quick create" name="radio-step-create" id="radio-step-create-1" />{' '} <Radio value="Custom" isChecked={createStepRadio === 'Custom'} onChange={(_, event) => this.setState({ createStepRadio: event.currentTarget.value })} label="Custom create" name="radio-step-create" id="radio-step-create-2" /> </div> ) }; const updateStep = { name: 'Update options', component: ( <div> <Radio value="Quick" isChecked={updateStepRadio === 'Quick'} onChange={(_, event) => this.setState({ updateStepRadio: event.currentTarget.value })} label="Quick update" name="radio-step-update" id="radio-step-update-1" />{' '} <Radio value="Custom" isChecked={updateStepRadio === 'Custom'} onChange={(_, event) => this.setState({ updateStepRadio: event.currentTarget.value })} label="Custom update" name="radio-step-update" id="radio-step-update-2" /> </div> ) }; const optionsStep = { name: showCreateStep ? `${createStepRadio} Options` : `${updateStepRadio} Options`, steps: [ { name: 'Substep 1', component: 'Substep 1' }, { name: 'Substep 2', component: 'Substep 2' }, { name: 'Substep 3', component: 'Substep 3' } ] }; const reviewStep = { name: 'Review', component: ( <div> <div>First choice: {getStartedStepRadio}</div> <div>Second choice: {showCreateStep ? createStepRadio : updateStepRadio}</div> </div> ) }; const steps = [ getStartedStep, ...(showCreateStep ? [createStep] : []), ...(showUpdateStep ? [updateStep] : []), ...(showOptionsStep ? [optionsStep] : []), ...(showReviewStep ? [reviewStep] : []) ]; const CustomFooter = ( <WizardFooter> <WizardContextConsumer> {({ activeStep, goToStepByName, goToStepById, onNext, onBack, onClose }) => { return ( <> <Button variant="primary" type="submit" onClick={() => this.getNextStep(activeStep, onNext)}> {activeStep.name === 'Review' ? 'Finish' : 'Next'} </Button> <Button variant="secondary" onClick={() => this.getPreviousStep(activeStep, onBack)} className={activeStep.name === 'Get Started' ? 'pf-m-disabled' : ''} > Back </Button> <Button variant="link" onClick={onClose}> Cancel </Button> </> ); }} </WizardContextConsumer> </WizardFooter> ); const title = 'Progressive wizard'; return ( <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} onClose={this.closeWizard} footer={CustomFooter} onGoToStep={this.onGoToStep} steps={steps} height={400} /> ); } } ``` ### Remember last step ```js import React from 'react'; import { Button, Wizard } from '@patternfly/react-core'; class RememberLastStepWizard extends React.Component { constructor(props) { super(props); this.state = { step: 1 }; this.closeWizard = () => { console.log('close wizard'); }; this.onMove = (curr, prev) => { this.setState({ step: curr.id }); }; this.onSave = () => { this.setState({ step: 1 }); }; } render() { const { step } = this.state; const steps = [ { id: 1, name: 'First step', component: <p>Step 1 content</p> }, { id: 2, name: 'Second step', component: <p>Step 2 content</p> }, { id: 3, name: 'Third step', component: <p>Step 3 content</p> }, { id: 4, name: 'Fourth step', component: <p>Step 4 content</p> }, { id: 5, name: 'Review', component: <p>Review step content</p>, nextButtonText: 'Finish' } ]; const title = 'Remember last step wizard'; return ( <Wizard navAriaLabel={`${title} steps`} mainAriaLabel={`${title} content`} startAtStep={step} onNext={this.onMove} onBack={this.onMove} onSave={this.onSave} onClose={this.closeWizard} description="Simple Wizard Description" steps={steps} height={400} /> ); } } ``` ### Wizard in modal ```js import React from 'react'; import { Button, Wizard } from '@patternfly/react-core'; class WizardInModal extends React.Component { constructor(props) { super(props); this.state = { isOpen: false }; this.handleModalToggle = () => { this.setState(({ isOpen }) => ({ isOpen: !isOpen })); }; } render() { const { isOpen } = this.state; const steps = [ { name: 'First step', component: <p>Step 1 content</p> }, { name: 'Second step', component: <p>Step 2 content</p> }, { name: 'Third step', component: <p>Step 3 content</p> }, { name: 'Fourth step', component: <p>Step 4 content</p> }, { name: 'Review', component: <p>Review step content</p>, nextButtonText: 'Finish' } ]; const title = 'Wizard in modal'; return ( <React.Fragment> <Button variant="primary" onClick={this.handleModalToggle}> Show Modal </Button> <Wizard title={title} description="Simple Wizard Description" descriptionComponent="div" steps={steps} onClose={this.handleModalToggle} isOpen={isOpen} /> </React.Fragment> ); } } ```