Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-catalog-view-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
152 changes: 88 additions & 64 deletions packages/react-core/src/components/Wizard/Wizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -24,6 +24,8 @@ export interface WizardStep {
canJumpTo?: boolean;
/** Sub steps */
steps?: WizardStep[];
/** Props to pass to the WizardNavItem */
stepNavItemProps?: React.HTMLProps<HTMLButtonElement | HTMLAnchorElement> | 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 */
Expand Down Expand Up @@ -66,6 +68,12 @@ export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
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 */
Expand Down Expand Up @@ -99,7 +107,7 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
static displayName = 'Wizard';
private static currentId = 0;
static defaultProps: PickOptional<WizardProps> = {
title: '',
title: null,
description: '',
className: '',
startAtStep: 1,
Expand All @@ -108,7 +116,10 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
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,
Expand Down Expand Up @@ -312,6 +323,9 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
hideClose,
closeButtonAriaLabel = 'Close',
navAriaLabel,
navAriaLabelledBy,
mainAriaLabel,
mainAriaLabelledBy,
hasNoBodyPadding,
footer,
appendTo,
Expand All @@ -328,76 +342,83 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
const computedSteps: WizardStep[] = this.initSteps(steps);
const firstStep = activeStep === flattenedSteps[0];
const isValid = activeStep && activeStep.enableNext !== undefined ? activeStep.enableNext : true;

const nav = (isWizardNavOpen: boolean) => (
<WizardNav isOpen={isWizardNavOpen} aria-label={navAriaLabel}>
{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 (
<WizardNav {...wizNavAProps}>
{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 (
<WizardNavItem
key={index}
content={step.name}
isCurrent={hasActiveChild}
isDisabled={!canJumpToParent}
step={navItemStep}
onNavItemClick={this.goToStep}
>
<WizardNav {...wizNavAProps} returnList>
{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 (
<WizardNavItem
key={`child_${indexChild}`}
content={childStep.name}
isCurrent={activeStep.name === childStep.name}
isDisabled={!enabled}
step={navItemStep}
onNavItemClick={this.goToStep}
/>
);
})}
</WizardNav>
</WizardNavItem>
);
}
navItemStep = this.getFlattenedStepsIndex(flattenedSteps, step.steps[0].name);
navItemStep = this.getFlattenedStepsIndex(flattenedSteps, step.name);
enabled = step.canJumpTo;
return (
<WizardNavItem
{...step.stepNavItemProps}
key={index}
content={step.name}
isCurrent={hasActiveChild}
isDisabled={!canJumpToParent}
isCurrent={activeStep.name === step.name}
isDisabled={!enabled}
step={navItemStep}
onNavItemClick={this.goToStep}
>
<WizardNav returnList>
{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 (
<WizardNavItem
key={`child_${indexChild}`}
content={childStep.name}
isCurrent={activeStep.name === childStep.name}
isDisabled={!enabled}
step={navItemStep}
onNavItemClick={this.goToStep}
/>
);
})}
</WizardNav>
</WizardNavItem>
/>
);
}
navItemStep = this.getFlattenedStepsIndex(flattenedSteps, step.name);
enabled = step.canJumpTo;
return (
<WizardNavItem
key={index}
content={step.name}
isCurrent={activeStep.name === step.name}
isDisabled={!enabled}
step={navItemStep}
onNavItemClick={this.goToStep}
/>
);
})}
</WizardNav>
);
})}
</WizardNav>
);
};

const context = {
goToStepById: this.goToStepById,
Expand Down Expand Up @@ -427,6 +448,9 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
/>
)}
<WizardToggle
mainAriaLabel={mainAriaLabel}
isInPage={isOpen === undefined}
mainAriaLabelledBy={(title || mainAriaLabelledBy) && (mainAriaLabelledBy || this.titleId)}
isNavOpen={this.state.isNavOpen}
onNavToggle={isNavOpen => this.setState({ isNavOpen })}
nav={nav}
Expand Down
24 changes: 18 additions & 6 deletions packages/react-core/src/components/Wizard/WizardBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<WizardBodyProps> = ({
children,
hasNoBodyPadding = false
}: WizardBodyProps) => (
<main className={css(styles.wizardMain)}>
<div className={css(styles.wizardMainBody, hasNoBodyPadding && styles.modifiers.noPadding)}>{children}</div>
</main>
);
hasNoBodyPadding = false,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
mainComponent = 'div'
}: WizardBodyProps) => {
const MainComponent = mainComponent;
return (
<MainComponent aria-label={ariaLabel} aria-labelledby={ariaLabelledBy} className={css(styles.wizardMain)}>
<div className={css(styles.wizardMainBody, hasNoBodyPadding && styles.modifiers.noPadding)}>{children}</div>
</MainComponent>
);
};
WizardBody.displayName = 'WizardBody';
9 changes: 8 additions & 1 deletion packages/react-core/src/components/Wizard/WizardNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -16,6 +18,7 @@ export interface WizardNavProps {
export const WizardNav: React.FunctionComponent<WizardNavProps> = ({
children,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
isOpen = false,
returnList = false
}: WizardNavProps) => {
Expand All @@ -26,7 +29,11 @@ export const WizardNav: React.FunctionComponent<WizardNavProps> = ({
}

return (
<nav className={css(styles.wizardNav, isOpen && 'pf-m-expanded')} aria-label={ariaLabel}>
<nav
className={css(styles.wizardNav, isOpen && styles.modifiers.expanded)}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
>
<ol className={css(styles.wizardNavList)}>{children}</ol>
</nav>
);
Expand Down
31 changes: 25 additions & 6 deletions packages/react-core/src/components/Wizard/WizardNavItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export interface WizardNavItemProps {
/** Callback for when the nav item is clicked */
onNavItemClick?: (step: number) => any;
/** Component used to render WizardNavItem */
navItemComponent?: React.ReactNode;
navItemComponent?: 'button' | 'a';
/** An optional url to use for when using an anchor component */
href?: string;
}

export const WizardNavItem: React.FunctionComponent<WizardNavItemProps> = ({
Expand All @@ -26,18 +28,35 @@ export const WizardNavItem: React.FunctionComponent<WizardNavItemProps> = ({
isDisabled = false,
step,
onNavItemClick = () => undefined,
navItemComponent = 'a'
navItemComponent = 'button',
href = null,
...rest
}: WizardNavItemProps) => {
const NavItemComponent = navItemComponent as any;
const NavItemComponent = navItemComponent;

if (navItemComponent === 'a' && !href && process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.error('WizardNavItem: When using an anchor, please provide an href');
}

const btnProps = {
disabled: isDisabled
};

const linkProps = {
tabIndex: isDisabled ? -1 : undefined,
href
};

return (
<li className={css(styles.wizardNavItem)}>
<NavItemComponent
aria-current={isCurrent && !children ? 'page' : false}
{...rest}
{...(navItemComponent === 'a' ? { ...linkProps } : { ...btnProps })}
onClick={() => onNavItemClick(step)}
className={css(styles.wizardNavLink, isCurrent && 'pf-m-current', isDisabled && 'pf-m-disabled')}
aria-disabled={isDisabled ? true : false}
tabIndex={isDisabled ? -1 : undefined}
aria-disabled={isDisabled ? true : null}
aria-current={isCurrent && !children ? 'page' : false}
>
{content}
</NavItemComponent>
Expand Down
21 changes: 19 additions & 2 deletions packages/react-core/src/components/Wizard/WizardToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export interface WizardToggleProps {
onNavToggle: (isOpen: boolean) => void;
/** The button's aria-label */
'aria-label'?: string;
/** Sets aria-labelledby on the main element */
mainAriaLabelledBy?: string;
/** The main's aria-label */
mainAriaLabel?: string;
/** If the wizard is in-page */
isInPage?: boolean;
}

export const WizardToggle: React.FunctionComponent<WizardToggleProps> = ({
Expand All @@ -33,7 +39,10 @@ export const WizardToggle: React.FunctionComponent<WizardToggleProps> = ({
activeStep,
children,
hasNoBodyPadding = false,
'aria-label': ariaLabel = 'Wizard Toggle'
'aria-label': ariaLabel = 'Wizard Toggle',
mainAriaLabelledBy = null,
mainAriaLabel = null,
isInPage = true
}: WizardToggleProps) => {
let activeStepIndex;
let activeStepName;
Expand All @@ -54,6 +63,7 @@ export const WizardToggle: React.FunctionComponent<WizardToggleProps> = ({
}
}
}

return (
<React.Fragment>
<button
Expand All @@ -76,7 +86,14 @@ export const WizardToggle: React.FunctionComponent<WizardToggleProps> = ({
<div className={css(styles.wizardOuterWrap)}>
<div className={css(styles.wizardInnerWrap)}>
{nav(isNavOpen)}
<WizardBody hasNoBodyPadding={hasNoBodyPadding}>{activeStep.component}</WizardBody>
<WizardBody
mainComponent={isInPage ? 'div' : 'main'}
aria-label={mainAriaLabel}
aria-labelledby={mainAriaLabelledBy}
hasNoBodyPadding={hasNoBodyPadding}
>
{activeStep.component}
</WizardBody>
</div>
{children}
</div>
Expand Down
Loading