diff --git a/packages/react-catalog-view-extension/package.json b/packages/react-catalog-view-extension/package.json index 8b75f05f987..8f9c202ed05 100644 --- a/packages/react-catalog-view-extension/package.json +++ b/packages/react-catalog-view-extension/package.json @@ -38,6 +38,7 @@ "@patternfly/patternfly": "4.24.3", "@patternfly/react-core": "^4.32.13", "@patternfly/react-styles": "^4.5.1", + "classnames": "^2.2.5", "patternfly": "^3.59.4" }, "devDependencies": { diff --git a/packages/react-core/src/components/Wizard/Wizard.tsx b/packages/react-core/src/components/Wizard/Wizard.tsx index b93b32c2d7f..248fe81bf7d 100644 --- a/packages/react-core/src/components/Wizard/Wizard.tsx +++ b/packages/react-core/src/components/Wizard/Wizard.tsx @@ -6,7 +6,7 @@ import { Modal, ModalVariant } from '../Modal'; import { WizardFooterInternal } from './WizardFooterInternal'; import { WizardToggle } from './WizardToggle'; import { WizardNav } from './WizardNav'; -import { WizardNavItem } from './WizardNavItem'; +import { WizardNavItem, WizardNavItemProps } from './WizardNavItem'; import { WizardContextProvider } from './WizardContext'; import { PickOptional } from '../../helpers/typeUtils'; import { WizardHeader } from './WizardHeader'; @@ -24,6 +24,8 @@ export interface WizardStep { canJumpTo?: boolean; /** Sub steps */ steps?: WizardStep[]; + /** Props to pass to the WizardNavItem */ + stepNavItemProps?: React.HTMLProps | WizardNavItemProps; /** (Unused if footer is controlled) Can change the Next button text. If nextButtonText is also set for the Wizard, this step specific one overrides it. */ nextButtonText?: React.ReactNode; /** (Unused if footer is controlled) The condition needed to enable the Next button */ @@ -66,6 +68,12 @@ export interface WizardProps extends React.HTMLProps { startAtStep?: number; /** Aria-label for the Nav */ navAriaLabel?: string; + /** Sets aria-labelledby on nav element */ + navAriaLabelledBy?: string; + /** Aria-label for the main element */ + mainAriaLabel?: string; + /** Sets aria-labelledby on the main element */ + mainAriaLabelledBy?: string; /** Can remove the default padding around the main body content by setting this to true */ hasNoBodyPadding?: boolean; /** (Use to control the footer) Passing in a footer component lets you control the buttons yourself */ @@ -99,7 +107,7 @@ export class Wizard extends React.Component { static displayName = 'Wizard'; private static currentId = 0; static defaultProps: PickOptional = { - title: '', + title: null, description: '', className: '', startAtStep: 1, @@ -108,7 +116,10 @@ export class Wizard extends React.Component { cancelButtonText: 'Cancel', hideClose: false, closeButtonAriaLabel: 'Close', - navAriaLabel: 'Steps', + navAriaLabel: null, + navAriaLabelledBy: null, + mainAriaLabel: null, + mainAriaLabelledBy: null, hasNoBodyPadding: false, onBack: null as WizardStepFunctionType, onNext: null as WizardStepFunctionType, @@ -312,6 +323,9 @@ export class Wizard extends React.Component { hideClose, closeButtonAriaLabel = 'Close', navAriaLabel, + navAriaLabelledBy, + mainAriaLabel, + mainAriaLabelledBy, hasNoBodyPadding, footer, appendTo, @@ -328,76 +342,83 @@ export class Wizard extends React.Component { const computedSteps: WizardStep[] = this.initSteps(steps); const firstStep = activeStep === flattenedSteps[0]; const isValid = activeStep && activeStep.enableNext !== undefined ? activeStep.enableNext : true; - - const nav = (isWizardNavOpen: boolean) => ( - - {computedSteps.map((step, index) => { - if (step.isFinishedStep) { - // Don't show finished step in the side nav - return; - } - let enabled; - let navItemStep; - if (step.steps) { - let hasActiveChild = false; - let canJumpToParent = false; - for (const subStep of step.steps) { - if (activeStep.name === subStep.name) { - // one of the children matches - hasActiveChild = true; - } - if (subStep.canJumpTo) { - canJumpToParent = true; + const nav = (isWizardNavOpen: boolean) => { + const wizNavAProps = { + isOpen: isWizardNavOpen, + 'aria-label': navAriaLabel, + 'aria-labelledby': (title || navAriaLabelledBy) && (navAriaLabelledBy || this.titleId) + }; + return ( + + {computedSteps.map((step, index) => { + if (step.isFinishedStep) { + // Don't show finished step in the side nav + return; + } + let enabled; + let navItemStep; + if (step.steps) { + let hasActiveChild = false; + let canJumpToParent = false; + for (const subStep of step.steps) { + if (activeStep.name === subStep.name) { + // one of the children matches + hasActiveChild = true; + } + if (subStep.canJumpTo) { + canJumpToParent = true; + } } + navItemStep = this.getFlattenedStepsIndex(flattenedSteps, step.steps[0].name); + return ( + + + {step.steps.map((childStep: WizardStep, indexChild: number) => { + if (childStep.isFinishedStep) { + // Don't show finished step in the side nav + return; + } + navItemStep = this.getFlattenedStepsIndex(flattenedSteps, childStep.name); + enabled = childStep.canJumpTo; + return ( + + ); + })} + + + ); } - navItemStep = this.getFlattenedStepsIndex(flattenedSteps, step.steps[0].name); + navItemStep = this.getFlattenedStepsIndex(flattenedSteps, step.name); + enabled = step.canJumpTo; return ( - - {step.steps.map((childStep: WizardStep, indexChild: number) => { - if (childStep.isFinishedStep) { - // Don't show finished step in the side nav - return; - } - navItemStep = this.getFlattenedStepsIndex(flattenedSteps, childStep.name); - enabled = childStep.canJumpTo; - return ( - - ); - })} - - + /> ); - } - navItemStep = this.getFlattenedStepsIndex(flattenedSteps, step.name); - enabled = step.canJumpTo; - return ( - - ); - })} - - ); + })} + + ); + }; const context = { goToStepById: this.goToStepById, @@ -427,6 +448,9 @@ export class Wizard extends React.Component { /> )} this.setState({ isNavOpen })} nav={nav} diff --git a/packages/react-core/src/components/Wizard/WizardBody.tsx b/packages/react-core/src/components/Wizard/WizardBody.tsx index a0355d183f1..25f1dbb4bb9 100644 --- a/packages/react-core/src/components/Wizard/WizardBody.tsx +++ b/packages/react-core/src/components/Wizard/WizardBody.tsx @@ -7,14 +7,26 @@ export interface WizardBodyProps { children: any; /** Set to true to remove the default body padding */ hasNoBodyPadding: boolean; + /** An aria-label to use for the main element */ + 'aria-label'?: string; + /** Sets the aria-labelledby attribute for the main element */ + 'aria-labelledby': string; + /** Component used as the primary content container */ + mainComponent?: React.ElementType; } export const WizardBody: React.FunctionComponent = ({ children, - hasNoBodyPadding = false -}: WizardBodyProps) => ( -
-
{children}
-
-); + hasNoBodyPadding = false, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + mainComponent = 'div' +}: WizardBodyProps) => { + const MainComponent = mainComponent; + return ( + +
{children}
+
+ ); +}; WizardBody.displayName = 'WizardBody'; diff --git a/packages/react-core/src/components/Wizard/WizardNav.tsx b/packages/react-core/src/components/Wizard/WizardNav.tsx index 5afa95afc38..9cdfcb86ab2 100644 --- a/packages/react-core/src/components/Wizard/WizardNav.tsx +++ b/packages/react-core/src/components/Wizard/WizardNav.tsx @@ -7,6 +7,8 @@ export interface WizardNavProps { children?: any; /** Aria-label applied to the nav element */ 'aria-label'?: string; + /** Sets the aria-labelledby attribute on the nav element */ + 'aria-labelledby'?: string; /** Whether the nav is expanded */ isOpen?: boolean; /** True to return the inner list without the wrapping nav element */ @@ -16,6 +18,7 @@ export interface WizardNavProps { export const WizardNav: React.FunctionComponent = ({ children, 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, isOpen = false, returnList = false }: WizardNavProps) => { @@ -26,7 +29,11 @@ export const WizardNav: React.FunctionComponent = ({ } return ( -