diff --git a/src/components/Modal/InnerComponents/CustomModalDialog.js b/src/components/Modal/InnerComponents/CustomModalDialog.js
new file mode 100644
index 00000000000..f797c1de344
--- /dev/null
+++ b/src/components/Modal/InnerComponents/CustomModalDialog.js
@@ -0,0 +1,85 @@
+/**
+ * CustomModalDialog creates custom ReactBootstrap ModalDialog
+ * https://github.com/react-bootstrap/react-bootstrap/blob/master/src/ModalDialog.js
+ *
+ * This extends ModalDialog and adds contentClassName prop for setting
+ * `modal-content` div's class
+ */
+import classNames from 'classnames';
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { utils } from 'react-bootstrap';
+
+const bsClass = utils.bootstrapUtils.bsClass;
+const bsSizes = utils.bootstrapUtils.bsSizes;
+const getClassSet = utils.bootstrapUtils.getClassSet;
+const prefix = utils.bootstrapUtils.prefix;
+const splitBsProps = utils.bootstrapUtils.splitBsProps;
+
+// React Bootstrap utils/StyleConfig Size is currently not exported
+const Size = {
+ LARGE: 'large',
+ SMALL: 'small',
+};
+
+class CustomModalDialog extends React.Component {
+ render() {
+ const {
+ dialogClassName,
+ contentClassName,
+ className,
+ style,
+ children,
+ ...props
+ } = this.props;
+ const [bsProps, elementProps] = splitBsProps(props);
+
+ const bsClassName = prefix(bsProps);
+
+ const modalStyle = { display: 'block', ...style };
+
+ const dialogClasses = {
+ ...getClassSet(bsProps),
+ [bsClassName]: false,
+ [prefix(bsProps, 'dialog')]: true,
+ };
+
+ return (
+
+ );
+ }
+}
+
+CustomModalDialog.propTypes = {
+ /** A css class to apply to the Modal dialog DOM node. */
+ dialogClassName: PropTypes.string,
+ /** custom modal-content class added to the content DOM node */
+ contentClassName: PropTypes.string,
+ /** base modal class name */
+ className: PropTypes.string,
+ /** additional modal styles */
+ style: PropTypes.object,
+ /** Children nodes */
+ children: PropTypes.node,
+};
+
+export default bsClass(
+ 'modal',
+ bsSizes([Size.LARGE, Size.SMALL], CustomModalDialog),
+);
diff --git a/src/components/Modal/Modal.js b/src/components/Modal/Modal.js
new file mode 100644
index 00000000000..6a560019f7c
--- /dev/null
+++ b/src/components/Modal/Modal.js
@@ -0,0 +1,17 @@
+import CustomModalDialog from './InnerComponents/CustomModalDialog';
+import { Modal as BsModal } from 'react-bootstrap';
+
+/**
+ * Modal Component for Patternfly React
+ */
+class Modal extends BsModal {
+ render() {
+ return super.render();
+ }
+}
+
+Modal.defaultProps = Object.assign(BsModal.defaultProps, {
+ dialogComponentClass: CustomModalDialog,
+});
+
+export default Modal;
diff --git a/src/components/Modal/Modal.stories.js b/src/components/Modal/Modal.stories.js
new file mode 100644
index 00000000000..79f7c75fb4e
--- /dev/null
+++ b/src/components/Modal/Modal.stories.js
@@ -0,0 +1,63 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { withInfo } from '@storybook/addon-info';
+import { defaultTemplate } from '../../../storybook/decorators/storyTemplates';
+
+import {
+ MockModalManager,
+ basicExampleSource,
+} from './__mocks__/mockModalManager';
+
+import {
+ MockAboutModalManager,
+ aboutExampleSource,
+} from './__mocks__/mockAboutModalManager';
+
+const stories = storiesOf('Modal Overlay', module);
+
+const description = (
+
+ This component is based on React Bootstrap Modal component. See{' '}
+
+ React Bootstrap Docs
+ {' '}
+ for complete Modal component documentation.
+
+);
+
+stories.addDecorator(
+ defaultTemplate({
+ title: 'Modal Overlay',
+ documentationLink:
+ 'http://www.patternfly.org/pattern-library/forms-and-controls/modal-overlay/',
+ description: description,
+ }),
+);
+
+stories.add(
+ 'Basic example',
+ withInfo({
+ source: false,
+ propTablesExclude: [MockModalManager],
+ text: (
+
+
Story Source
+
{basicExampleSource}
+
+ ),
+ })(() => ),
+);
+
+stories.add(
+ 'About Modal',
+ withInfo({
+ source: false,
+ propTablesExclude: [MockAboutModalManager],
+ text: (
+
+
Story Source
+
{aboutExampleSource}
+
+ ),
+ })(() => ),
+);
diff --git a/src/components/Modal/__mocks__/mockAboutModalManager.js b/src/components/Modal/__mocks__/mockAboutModalManager.js
new file mode 100644
index 00000000000..859b9c14c3f
--- /dev/null
+++ b/src/components/Modal/__mocks__/mockAboutModalManager.js
@@ -0,0 +1,131 @@
+import React from 'react';
+import { Button } from '../../Button';
+import { Icon } from '../../Icon';
+import { Modal } from '../index';
+import logo from 'patternfly/dist/img/logo-alt.svg';
+
+export class MockAboutModalManager extends React.Component {
+ constructor() {
+ super();
+ this.state = { showModal: false };
+ this.open = this.open.bind(this);
+ this.close = this.close.bind(this);
+ }
+ open() {
+ this.setState({ showModal: true });
+ }
+ close() {
+ this.setState({ showModal: false });
+ }
+ render() {
+ return (
+
+
+
+
+
+
+
+
+ Product Title
+
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+
+
+
+ Trademark and Copyright Information
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export const aboutExampleSource = `
+
+
+
+
+
+
+
+ Product Title
+
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+ -
+ Label Version
+
+
+
+
+ Trademark and Copyright Information
+
+
+
+
+
+
+`;
diff --git a/src/components/Modal/__mocks__/mockModalManager.js b/src/components/Modal/__mocks__/mockModalManager.js
new file mode 100644
index 00000000000..86775a91de4
--- /dev/null
+++ b/src/components/Modal/__mocks__/mockModalManager.js
@@ -0,0 +1,142 @@
+import React from 'react';
+import { Button } from '../../Button';
+import { Icon } from '../../Icon';
+import { Modal } from '../index';
+
+export class MockModalManager extends React.Component {
+ constructor() {
+ super();
+ this.state = { showModal: false };
+ this.open = this.open.bind(this);
+ this.close = this.close.bind(this);
+ }
+ open() {
+ this.setState({ showModal: true });
+ }
+ close() {
+ this.setState({ showModal: false });
+ }
+ render() {
+ return (
+
+
+
+
+
+
+ Modal Overlay Title
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export const basicExampleSource = `
+
+
+
+
+
+ Modal Overlay Title
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/components/Modal/index.js b/src/components/Modal/index.js
new file mode 100644
index 00000000000..c6b35681c7e
--- /dev/null
+++ b/src/components/Modal/index.js
@@ -0,0 +1 @@
+export { default as Modal } from './Modal';
diff --git a/src/components/Wizard/Wizard.js b/src/components/Wizard/Wizard.js
new file mode 100644
index 00000000000..2b7268f4c3f
--- /dev/null
+++ b/src/components/Wizard/Wizard.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import WizardHeader from './WizardHeader';
+
+/**
+ * Wizard - main Wizard component.
+ */
+const Wizard = ({ children, className, embedded, ...rest }) => {
+ const renderChildren = () => {
+ return React.Children.map(children, child => {
+ if (child && child.type === WizardHeader) {
+ return React.cloneElement(child, {
+ embedded: embedded,
+ });
+ } else {
+ return child;
+ }
+ });
+ };
+
+ return (
+
+ {renderChildren()}
+
+ );
+};
+Wizard.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** Embedded wizard */
+ embedded: PropTypes.bool,
+};
+export default Wizard;
diff --git a/src/components/Wizard/Wizard.stories.js b/src/components/Wizard/Wizard.stories.js
new file mode 100644
index 00000000000..8d1bfba6474
--- /dev/null
+++ b/src/components/Wizard/Wizard.stories.js
@@ -0,0 +1,100 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { withInfo } from '@storybook/addon-info';
+import { Row, Col } from 'react-bootstrap';
+import { withKnobs } from '@storybook/addon-knobs';
+import { defaultTemplate } from '../../../storybook/decorators/storyTemplates';
+
+import { mockWizardItems } from './__mocks__/mockWizardItems';
+
+import {
+ MockLoadingWizardManager,
+ mockLoadingWizardSource,
+} from './__mocks__/mockLoadingWizardManager';
+
+import {
+ MockModalWizardManager,
+ mockModalWizardSource,
+} from './__mocks__/mockModalWizardManager';
+
+// import {
+// MockEmbeddedWizardManager,
+// mockEmbeddedWizardSource,
+// } from './__mocks__/mockEmbeddedWizardManager';
+
+const stories = storiesOf('Wizard', module);
+stories.addDecorator(withKnobs);
+stories.addDecorator(
+ defaultTemplate({
+ title: 'Wizard',
+ documentationLink:
+ 'http://www.patternfly.org/pattern-library/communication/wizard/#/overview',
+ }),
+);
+
+stories.add(
+ 'Loading wizard',
+ withInfo({
+ source: false,
+ propTablesExclude: [Row, Col, MockLoadingWizardManager],
+ text: (
+
+
Story Source
+
{mockLoadingWizardSource}
+
+ ),
+ })(() => (
+
+
+
+
+
+ )),
+);
+
+stories.add(
+ 'Modal wizard',
+ withInfo({
+ source: false,
+ propTablesExclude: [Row, Col, MockModalWizardManager],
+ text: (
+
+
Story Source
+
{mockModalWizardSource}
+
+ ),
+ })(() => (
+
+
+
+
+
+ )),
+);
+
+/**
+ * Embedded Wizard will be finalized in a subsequent PR.
+ *
+ * Open PF Core issues:
+ * https://github.com/patternfly/patternfly/issues/869
+ * https://github.com/patternfly/patternfly/issues/841
+ */
+// stories.add(
+// 'Embedded in page',
+// withInfo({
+// source: false,
+// propTablesExclude: [Row, Col, MockEmbeddedWizardManager],
+// text: (
+//
+//
Story Source
+//
{mockEmbeddedWizardSource}
+//
+// ),
+// })(() => (
+//
+//
+//
+//
+//
+// )),
+// );
diff --git a/src/components/Wizard/Wizard.test.js b/src/components/Wizard/Wizard.test.js
new file mode 100644
index 00000000000..8179914ac60
--- /dev/null
+++ b/src/components/Wizard/Wizard.test.js
@@ -0,0 +1,99 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { Row, Col } from 'react-bootstrap';
+import { Button } from '../Button';
+import { Wizard } from './index';
+
+import {
+ mockWizardItems,
+ mockLoadingContents,
+} from './__mocks__/mockWizardItems';
+
+import {
+ renderWizardSteps,
+ renderSidebarItems,
+ renderWizardContents,
+} from './__mocks__/mockWizardRenderers';
+
+test('Wizard loading renders properly', () => {
+ const component = renderer.create(
+
+
+
+
+
+
+ {mockLoadingContents()}
+
+
+
+
+
+
+
+
+
+
,
+ );
+
+ const tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+});
+
+test('Wizard embedded renders properly', () => {
+ const onStepClick = jest.fn();
+ const onSidebarItemClick = jest.fn();
+ const activeStepIndex = 0;
+ const activeSubStepIndex = 0;
+ const onNextButtonClick = jest.fn();
+ const onBackButtonClick = jest.fn();
+
+ const component = renderer.create(
+
+
+
+
+
+
+
+ {renderWizardContents(
+ mockWizardItems,
+ activeStepIndex,
+ activeSubStepIndex,
+ )}
+
+
+
+
+
+
+
+
+
+ ,
+ );
+
+ const tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/Wizard/WizardBody.js b/src/components/Wizard/WizardBody.js
new file mode 100644
index 00000000000..abd4858d141
--- /dev/null
+++ b/src/components/Wizard/WizardBody.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardBody component for Patternfly React
+ */
+const WizardBody = ({ children, className, ...rest }) => {
+ const classes = cx('wizard-pf-body', className);
+ return (
+
+ {children}
+
+ );
+};
+WizardBody.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+};
+export default WizardBody;
diff --git a/src/components/Wizard/WizardContents.js b/src/components/Wizard/WizardContents.js
new file mode 100644
index 00000000000..a4155ade54a
--- /dev/null
+++ b/src/components/Wizard/WizardContents.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardContents component for Patternfly React
+ */
+const WizardContents = ({
+ children,
+ className,
+ stepIndex,
+ subStepIndex,
+ activeStepIndex,
+ activeSubStepIndex,
+ ...rest
+}) => {
+ const classes = cx(
+ 'wizard-pf-contents',
+ {
+ // hide contents if the step is not active
+ // OR if we have sub steps and this sub step is not active
+ hidden:
+ activeStepIndex !== stepIndex ||
+ (activeSubStepIndex !== null && activeSubStepIndex !== subStepIndex),
+ },
+ className,
+ );
+ return (
+
+ {children}
+
+ );
+};
+WizardContents.propTypes = {
+ /** WizardStep nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** The wizard step index for these contents */
+ stepIndex: PropTypes.number,
+ /** The wizard sub step index for these contents */
+ subStepIndex: PropTypes.number,
+ /** The active wizard step index */
+ activeStepIndex: PropTypes.number,
+ /** The active wizard sub step index */
+ activeSubStepIndex: PropTypes.number,
+};
+export default WizardContents;
diff --git a/src/components/Wizard/WizardFooter.js b/src/components/Wizard/WizardFooter.js
new file mode 100644
index 00000000000..d7d3cbf240d
--- /dev/null
+++ b/src/components/Wizard/WizardFooter.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardFooter component for Patternfly React
+ */
+const WizardFooter = ({ children, className, ...rest }) => {
+ const classes = cx('wizard-pf-footer', className);
+ return (
+
+ {children}
+
+ );
+};
+WizardFooter.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+};
+export default WizardFooter;
diff --git a/src/components/Wizard/WizardHeader.js b/src/components/Wizard/WizardHeader.js
new file mode 100644
index 00000000000..4ff4632a815
--- /dev/null
+++ b/src/components/Wizard/WizardHeader.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardHeader component for Patternfly React
+ */
+const WizardHeader = ({ children, className, embedded, title, ...rest }) => {
+ const classes = cx({ 'wizard-pf-header': !embedded }, className);
+
+ if (embedded) {
+ return (
+
+ {title}
+
+ );
+ } else {
+ return (
+
+
{title}
+
+ );
+ }
+};
+WizardHeader.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** Embedded wizard */
+ embedded: PropTypes.bool,
+ /** The wizard title */
+ title: PropTypes.string,
+};
+export default WizardHeader;
diff --git a/src/components/Wizard/WizardMain.js b/src/components/Wizard/WizardMain.js
new file mode 100644
index 00000000000..a7872506517
--- /dev/null
+++ b/src/components/Wizard/WizardMain.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardMain component for Patternfly React
+ */
+const WizardMain = ({ children, className, ...rest }) => {
+ const classes = cx('wizard-pf-main', className);
+ return (
+
+ {children}
+
+ );
+};
+WizardMain.propTypes = {
+ /** WizardStep nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+};
+export default WizardMain;
diff --git a/src/components/Wizard/WizardReviewContent.js b/src/components/Wizard/WizardReviewContent.js
new file mode 100644
index 00000000000..864a21d2f7f
--- /dev/null
+++ b/src/components/Wizard/WizardReviewContent.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardReviewContent component for Patternfly React
+ */
+const WizardReviewContent = ({ children, className, collapsed, ...rest }) => {
+ const classes = cx(
+ 'wizard-pf-review-content',
+ { collapse: collapsed },
+ className,
+ );
+ return (
+
+ {children}
+
+ );
+};
+WizardReviewContent.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** Step collapsed */
+ collapsed: PropTypes.bool,
+};
+export default WizardReviewContent;
diff --git a/src/components/Wizard/WizardReviewItem.js b/src/components/Wizard/WizardReviewItem.js
new file mode 100644
index 00000000000..2cb910e897a
--- /dev/null
+++ b/src/components/Wizard/WizardReviewItem.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardReviewItem component for Patternfly React
+ */
+const WizardReviewItem = ({ children, className, ...rest }) => {
+ const classes = cx('wizard-pf-review-item', className);
+ return (
+
+ {children}
+
+ );
+};
+WizardReviewItem.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+};
+export default WizardReviewItem;
diff --git a/src/components/Wizard/WizardReviewStep.js b/src/components/Wizard/WizardReviewStep.js
new file mode 100644
index 00000000000..8e70282e6d9
--- /dev/null
+++ b/src/components/Wizard/WizardReviewStep.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { ListGroupItem } from '../ListGroup';
+
+/**
+ * WizardReviewStep component for Patternfly React
+ */
+const WizardReviewStep = ({ children, onClick, title, collapsed, ...rest }) => {
+ return (
+
+
+ {title}
+
+ {children}
+
+ );
+};
+WizardReviewStep.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Click handler */
+ onClick: PropTypes.func,
+ /** Step title */
+ title: PropTypes.string,
+ /** Step collapsed */
+ collapsed: PropTypes.bool,
+};
+export default WizardReviewStep;
diff --git a/src/components/Wizard/WizardReviewSteps.js b/src/components/Wizard/WizardReviewSteps.js
new file mode 100644
index 00000000000..2e0c540988a
--- /dev/null
+++ b/src/components/Wizard/WizardReviewSteps.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+import { ListGroup } from '../ListGroup';
+
+/**
+ * WizardReviewSteps component for Patternfly React
+ */
+const WizardReviewSteps = ({ children, className, ...rest }) => {
+ const classes = cx('wizard-pf-review-steps', className);
+ return (
+
+ {children}
+
+ );
+};
+WizardReviewSteps.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+};
+export default WizardReviewSteps;
diff --git a/src/components/Wizard/WizardReviewSubStep.js b/src/components/Wizard/WizardReviewSubStep.js
new file mode 100644
index 00000000000..2f012bd63b4
--- /dev/null
+++ b/src/components/Wizard/WizardReviewSubStep.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { ListGroupItem } from '../ListGroup';
+
+/**
+ * WizardReviewSubStep component for Patternfly React
+ */
+const WizardReviewSubStep = ({
+ children,
+ onClick,
+ label,
+ title,
+ collapsed,
+ ...rest
+}) => {
+ return (
+
+
+ {label}
+ {title}
+
+ {children}
+
+ );
+};
+WizardReviewSubStep.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Click handler */
+ onClick: PropTypes.func,
+ /** Review step label */
+ label: PropTypes.string,
+ /** Review step title */
+ title: PropTypes.string,
+ /** Step collapsed */
+ collapsed: PropTypes.bool,
+};
+export default WizardReviewSubStep;
diff --git a/src/components/Wizard/WizardReviewSubSteps.js b/src/components/Wizard/WizardReviewSubSteps.js
new file mode 100644
index 00000000000..6f6e3804ff3
--- /dev/null
+++ b/src/components/Wizard/WizardReviewSubSteps.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+import { ListGroup } from '../ListGroup';
+
+/**
+ * WizardReviewSubSteps component for Patternfly React
+ */
+const WizardReviewSubSteps = ({ children, className, collapsed, ...rest }) => {
+ const classes = cx(
+ 'wizard-pf-review-substeps',
+ { collapse: collapsed },
+ className,
+ );
+ return (
+
+ {children}
+
+ );
+};
+WizardReviewSubSteps.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** Step collapsed */
+ collapsed: PropTypes.bool,
+};
+export default WizardReviewSubSteps;
diff --git a/src/components/Wizard/WizardRow.js b/src/components/Wizard/WizardRow.js
new file mode 100644
index 00000000000..b3629c8cdbc
--- /dev/null
+++ b/src/components/Wizard/WizardRow.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardRow component for Patternfly React
+ */
+const WizardRow = ({ children, className, ...rest }) => {
+ const classes = cx('wizard-pf-row', className);
+ return (
+
+ );
+};
+WizardRow.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+};
+export default WizardRow;
diff --git a/src/components/Wizard/WizardSidebar.js b/src/components/Wizard/WizardSidebar.js
new file mode 100644
index 00000000000..aec1baae2ef
--- /dev/null
+++ b/src/components/Wizard/WizardSidebar.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardSidebar component for Patternfly React
+ */
+const WizardSidebar = ({ items, className, ...rest }) => {
+ const classes = cx('wizard-pf-sidebar', className);
+ return (
+
+ {items}
+
+ );
+};
+WizardSidebar.propTypes = {
+ /** Wizard sidebar items */
+ items: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+};
+export default WizardSidebar;
diff --git a/src/components/Wizard/WizardSidebarGroup.js b/src/components/Wizard/WizardSidebarGroup.js
new file mode 100644
index 00000000000..59c444636d0
--- /dev/null
+++ b/src/components/Wizard/WizardSidebarGroup.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+import { ListGroup } from '../ListGroup';
+
+/**
+ * WizardSidebarGroup component for Patternfly React
+ */
+const WizardSidebarGroup = ({
+ children,
+ className,
+ step,
+ activeStep,
+ ...rest
+}) => {
+ const classes = cx({ hidden: step !== activeStep }, className);
+ return (
+
+ {children}
+
+ );
+};
+WizardSidebarGroup.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** The wizard step number for this step */
+ step: PropTypes.string,
+ /** The active step */
+ activeStep: PropTypes.string,
+};
+export default WizardSidebarGroup;
diff --git a/src/components/Wizard/WizardSidebarGroupItem.js b/src/components/Wizard/WizardSidebarGroupItem.js
new file mode 100644
index 00000000000..0cdb8c1ad02
--- /dev/null
+++ b/src/components/Wizard/WizardSidebarGroupItem.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+import { ListGroupItem } from '../ListGroup';
+
+/**
+ * WizardSidebarGroupItem component for Patternfly React
+ */
+const WizardSidebarGroupItem = ({
+ stepIndex,
+ subStepIndex,
+ className,
+ subStep,
+ label,
+ title,
+ activeSubStep,
+ onClick,
+ ...rest
+}) => {
+ const classes = cx({ active: subStep === activeSubStep }, className);
+ return (
+
+ {
+ e.preventDefault();
+ onClick(stepIndex, subStepIndex);
+ }}
+ >
+ {label}
+ {title}
+
+
+ );
+};
+WizardSidebarGroupItem.propTypes = {
+ /** The wizard parent step index */
+ stepIndex: PropTypes.number,
+ /** The wizard sub step index */
+ subStepIndex: PropTypes.number,
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** This wizard sub step name */
+ subStep: PropTypes.string,
+ /** This wizard sub step label */
+ label: PropTypes.string,
+ /** This wizard sub step title */
+ title: PropTypes.string,
+ /** The currently active wizard substep */
+ activeSubStep: PropTypes.string,
+ /** Sidebar group item click handler */
+ onClick: PropTypes.func,
+};
+export default WizardSidebarGroupItem;
diff --git a/src/components/Wizard/WizardStep.js b/src/components/Wizard/WizardStep.js
new file mode 100644
index 00000000000..92db57d1d80
--- /dev/null
+++ b/src/components/Wizard/WizardStep.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardStep component for Patternfly React
+ */
+const WizardStep = ({
+ children,
+ className,
+ stepIndex,
+ step,
+ label,
+ title,
+ activeStep,
+ onClick,
+ ...rest
+}) => {
+ const classes = cx(
+ 'wizard-pf-step',
+ { active: step === activeStep },
+ className,
+ );
+ return (
+
+ {
+ e.preventDefault();
+ onClick(stepIndex);
+ }}
+ >
+ {label}
+ {title}
+ {children}
+
+
+ );
+};
+WizardStep.propTypes = {
+ /** Children nodes */
+ children: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** The wizard step index */
+ stepIndex: PropTypes.number,
+ /** The wizard step for this step */
+ step: PropTypes.string,
+ /** The wizard step number label */
+ label: PropTypes.string,
+ /** The wizard step title */
+ title: PropTypes.string,
+ /** The active step */
+ activeStep: PropTypes.string,
+ /** Step click handler */
+ onClick: PropTypes.func,
+};
+export default WizardStep;
diff --git a/src/components/Wizard/WizardSteps.js b/src/components/Wizard/WizardSteps.js
new file mode 100644
index 00000000000..33263eb7f4e
--- /dev/null
+++ b/src/components/Wizard/WizardSteps.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardSteps component for Patternfly React
+ */
+const WizardSteps = ({ steps, className, ...rest }) => {
+ const classes = cx('wizard-pf-steps', className);
+ return (
+
+ );
+};
+WizardSteps.propTypes = {
+ /** WizardStep nodes */
+ steps: PropTypes.node,
+ /** Additional css classes */
+ className: PropTypes.string,
+};
+export default WizardSteps;
diff --git a/src/components/Wizard/WizardSubStep.js b/src/components/Wizard/WizardSubStep.js
new file mode 100644
index 00000000000..1822908d229
--- /dev/null
+++ b/src/components/Wizard/WizardSubStep.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+
+/**
+ * WizardSubStep component for Patternfly React
+ */
+const WizardSubStep = ({
+ className,
+ subStep,
+ title,
+ activeSubStep,
+ ...rest
+}) => {
+ const classes = cx(
+ 'wizard-pf-step-title-substep',
+ { active: subStep === activeSubStep },
+ className,
+ );
+ return (
+
+ {title}
+
+ );
+};
+WizardSubStep.propTypes = {
+ /** Additional css classes */
+ className: PropTypes.string,
+ /** The wizard sub step for this step */
+ subStep: PropTypes.string,
+ /** The wizard sub step title */
+ title: PropTypes.string,
+ /** The active step */
+ activeSubStep: PropTypes.string,
+};
+export default WizardSubStep;
diff --git a/src/components/Wizard/__mocks__/mockEmbeddedWizardManager.js b/src/components/Wizard/__mocks__/mockEmbeddedWizardManager.js
new file mode 100644
index 00000000000..33cfa711a08
--- /dev/null
+++ b/src/components/Wizard/__mocks__/mockEmbeddedWizardManager.js
@@ -0,0 +1,143 @@
+import React from 'react';
+import MockWizardBase from './mockWizardBase';
+import { Button } from '../../Button';
+import { Icon } from '../../Icon';
+import { Wizard } from '../index';
+
+import { mockWizardItems } from './mockWizardItems';
+
+import {
+ renderWizardSteps,
+ renderSidebarItems,
+ renderWizardContents,
+} from './mockWizardRenderers';
+
+export class MockEmbeddedWizardManager extends MockWizardBase {
+ render() {
+ const { activeStepIndex, activeSubStepIndex } = this.state;
+
+ return (
+
+
+
+
+
+
+
+ {renderWizardContents(
+ mockWizardItems,
+ activeStepIndex,
+ activeSubStepIndex,
+ )}
+
+
+
+
+
+
+ {(activeStepIndex === 0 || activeStepIndex === 1) && (
+
+ )}
+ {activeStepIndex === 2 &&
+ activeSubStepIndex === 0 && (
+
+ )}
+ {activeStepIndex === 2 &&
+ activeSubStepIndex === 1 && (
+
+ )}
+
+
+ );
+ }
+}
+
+export const mockEmbeddedWizardSource = `
+
+
+
+
+
+
+
+ {renderWizardContents(
+ mockWizardItems,
+ activeStepIndex,
+ activeSubStepIndex,
+ )}
+
+
+
+
+
+
+ {(activeStepIndex === 0 || activeStepIndex === 1) && (
+
+ )}
+ {activeStepIndex === 2 &&
+ activeSubStepIndex === 0 && (
+
+ )}
+ {activeStepIndex === 2 &&
+ activeSubStepIndex === 1 && (
+
+ )}
+
+
+`;
diff --git a/src/components/Wizard/__mocks__/mockLoadingWizardManager.js b/src/components/Wizard/__mocks__/mockLoadingWizardManager.js
new file mode 100644
index 00000000000..aaa9b68c200
--- /dev/null
+++ b/src/components/Wizard/__mocks__/mockLoadingWizardManager.js
@@ -0,0 +1,121 @@
+import React from 'react';
+import { bindMethods } from '../../../common/helpers';
+import { Button } from '../../Button';
+import { Icon } from '../../Icon';
+import { Modal } from '../../Modal';
+import { Wizard } from '../index';
+
+import { mockLoadingContents } from './mockWizardItems';
+
+export class MockLoadingWizardManager extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { showModal: false };
+ bindMethods(this, ['open', 'close']);
+ }
+ open() {
+ this.setState({ showModal: true });
+ }
+ close() {
+ this.setState({ showModal: false });
+ }
+ render() {
+ const { showModal } = this.state;
+
+ return (
+
+
+
+
+
+
+
+ Wizard Title
+
+
+
+ {mockLoadingContents()}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export const mockLoadingWizardSource = `
+
+
+
+
+
+
+
+ Wizard Title
+
+
+
+ {mockLoadingContents()}
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/components/Wizard/__mocks__/mockModalWizardManager.js b/src/components/Wizard/__mocks__/mockModalWizardManager.js
new file mode 100644
index 00000000000..05ccd085921
--- /dev/null
+++ b/src/components/Wizard/__mocks__/mockModalWizardManager.js
@@ -0,0 +1,207 @@
+import React from 'react';
+import MockWizardBase from './mockWizardBase';
+import { bindMethods } from '../../../common/helpers';
+import { Button } from '../../Button';
+import { Icon } from '../../Icon';
+import { Modal } from '../../Modal';
+import { Wizard } from '../index';
+
+import { mockWizardItems } from './mockWizardItems';
+
+import {
+ renderWizardSteps,
+ renderSidebarItems,
+ renderWizardContents,
+} from './mockWizardRenderers';
+
+export class MockModalWizardManager extends MockWizardBase {
+ constructor(props) {
+ super(props);
+ bindMethods(this, ['open', 'close']);
+ }
+ open() {
+ this.setState({ showModal: true });
+ }
+ close() {
+ this.setState({ showModal: false });
+ }
+ render() {
+ const { showModal, activeStepIndex, activeSubStepIndex } = this.state;
+
+ return (
+
+
+
+
+
+
+
+ Wizard Title
+
+
+
+
+
+
+ {renderWizardContents(
+ mockWizardItems,
+ activeStepIndex,
+ activeSubStepIndex,
+ )}
+
+
+
+
+
+
+ {(activeStepIndex === 0 || activeStepIndex === 1) && (
+
+ )}
+ {activeStepIndex === 2 &&
+ activeSubStepIndex === 0 && (
+
+ )}
+ {activeStepIndex === 2 &&
+ activeSubStepIndex === 1 && (
+
+ )}
+
+
+
+
+ );
+ }
+}
+
+export const mockModalWizardSource = `
+
+
+
+
+
+
+
+ Wizard Title
+
+
+
+
+
+
+ {renderWizardContents(
+ mockWizardItems,
+ activeStepIndex,
+ activeSubStepIndex,
+ )}
+
+
+
+
+
+
+ {(activeStepIndex === 0 || activeStepIndex === 1) && (
+
+ )}
+ {activeStepIndex === 2 &&
+ activeSubStepIndex === 0 && (
+
+ )}
+ {activeStepIndex === 2 &&
+ activeSubStepIndex === 1 && (
+
+ )}
+
+
+
+
+`;
diff --git a/src/components/Wizard/__mocks__/mockWizardBase.js b/src/components/Wizard/__mocks__/mockWizardBase.js
new file mode 100644
index 00000000000..437ff9d8ba8
--- /dev/null
+++ b/src/components/Wizard/__mocks__/mockWizardBase.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { bindMethods } from '../../../common/helpers';
+
+class MockWizardBase extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ activeStepIndex: props.initialStepIndex || 0,
+ activeSubStepIndex: props.initialSubStepIndex || 0,
+ };
+ bindMethods(this, [
+ 'onSidebarItemClick',
+ 'onStepClick',
+ 'onNextButtonClick',
+ 'onBackButtonClick',
+ ]);
+ }
+ onSidebarItemClick(stepIndex, subStepIndex) {
+ this.setState({
+ activeStepIndex: stepIndex,
+ activeSubStepIndex: subStepIndex,
+ });
+ }
+ onStepClick(stepIndex) {
+ if (stepIndex === this.state.activeStepIndex) {
+ return;
+ }
+ this.setState({
+ activeStepIndex: stepIndex,
+ activeSubStepIndex: 0,
+ });
+ }
+ onNextButtonClick() {
+ const { steps } = this.props;
+ const { activeStepIndex, activeSubStepIndex } = this.state;
+ const activeStep = steps[activeStepIndex];
+
+ if (activeSubStepIndex < activeStep.subSteps.length - 1) {
+ this.setState(prevState => ({
+ activeSubStepIndex: prevState.activeSubStepIndex + 1,
+ }));
+ } else if (activeStepIndex < steps.length - 1) {
+ this.setState(prevState => ({
+ activeStepIndex: prevState.activeStepIndex + 1,
+ activeSubStepIndex: 0,
+ }));
+ }
+ }
+ onBackButtonClick() {
+ const { steps } = this.props;
+ const { activeStepIndex, activeSubStepIndex } = this.state;
+
+ if (activeSubStepIndex > 0) {
+ this.setState(prevState => ({
+ activeSubStepIndex: prevState.activeSubStepIndex - 1,
+ }));
+ } else if (activeStepIndex > 0) {
+ this.setState(prevState => ({
+ activeStepIndex: prevState.activeStepIndex - 1,
+ activeSubStepIndex:
+ steps[prevState.activeStepIndex - 1].subSteps.length - 1,
+ }));
+ }
+ }
+ render() {
+ return false;
+ }
+}
+MockWizardBase.propTypes = {
+ /** Initial step index */
+ initialStepIndex: PropTypes.number,
+ /** Initial sub step index */
+ initialSubStepIndex: PropTypes.number,
+ /** Wizard steps */
+ steps: PropTypes.array,
+};
+export default MockWizardBase;
diff --git a/src/components/Wizard/__mocks__/mockWizardDeployContents.js b/src/components/Wizard/__mocks__/mockWizardDeployContents.js
new file mode 100644
index 00000000000..2537e8e0b07
--- /dev/null
+++ b/src/components/Wizard/__mocks__/mockWizardDeployContents.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class MockWizardDeployContents extends React.Component {
+ constructor() {
+ super();
+ this.state = { deploying: true };
+ }
+ componentWillReceiveProps(nextProps) {
+ const { active } = this.props;
+ if (!nextProps.active) {
+ this.setState({ deploying: true });
+ } else if (!active && nextProps.active) {
+ setTimeout(() => {
+ this.setState({ deploying: false });
+ }, 3000);
+ }
+ }
+ render() {
+ if (this.state.deploying) {
+ return (
+
+
+
Deployment in progress
+
+ Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi
+ vivamus, lorem sociosqu eget nunc amet.{' '}
+
+
+ );
+ } else {
+ return (
+
+
+
+
+
+ Deployment was successful
+
+
+ Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi
+ vivamus, lorem sociosqu eget nunc amet.{' '}
+
+
+
+ );
+ }
+ }
+}
+MockWizardDeployContents.propTypes = {
+ active: PropTypes.bool,
+};
+export default MockWizardDeployContents;
diff --git a/src/components/Wizard/__mocks__/mockWizardItems.js b/src/components/Wizard/__mocks__/mockWizardItems.js
new file mode 100644
index 00000000000..3463612ce82
--- /dev/null
+++ b/src/components/Wizard/__mocks__/mockWizardItems.js
@@ -0,0 +1,103 @@
+import React from 'react';
+
+export const mockLoadingContents = () => {
+ return (
+
+
+
Loading Wizard
+
+ Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus,
+ lorem sociosqu eget nunc amet.{' '}
+
+
+ );
+};
+
+export const mockWizardItems = [
+ {
+ step: '1',
+ label: '1',
+ title: 'First Step',
+ subSteps: [
+ {
+ subStep: '1.1',
+ label: '1A.',
+ title: 'Details',
+ contents: {
+ label1: 'Name',
+ label2: 'Description',
+ },
+ },
+ {
+ subStep: '1.2',
+ label: '1B.',
+ title: 'Settings',
+ contents: {
+ label1: 'Lorem ipsum',
+ label2: 'Dolor',
+ },
+ },
+ ],
+ },
+ {
+ step: '2',
+ label: '2',
+ title: 'Second Step',
+ subSteps: [
+ {
+ subStep: '2.1',
+ label: '2A.',
+ title: 'Details',
+ contents: {
+ label1: 'Aliquam',
+ label2: 'Fermentum',
+ },
+ },
+ {
+ subStep: '2.2',
+ label: '2B.',
+ title: 'Settings',
+ contents: {
+ label1: 'Consectetur',
+ label2: 'Adipiscing',
+ },
+ },
+ ],
+ },
+ {
+ step: '3',
+ label: '3',
+ title: 'Review',
+ subSteps: [
+ {
+ subStep: '3.1',
+ label: '3A.',
+ title: 'Summary',
+ },
+ {
+ subStep: '3.2',
+ label: '3B.',
+ title: 'Progress',
+ },
+ ],
+ },
+];
+
+export const mockWizardFormContents = (label1, label2) => {
+ return (
+
+ );
+};
diff --git a/src/components/Wizard/__mocks__/mockWizardRenderers.js b/src/components/Wizard/__mocks__/mockWizardRenderers.js
new file mode 100644
index 00000000000..d2938c8d475
--- /dev/null
+++ b/src/components/Wizard/__mocks__/mockWizardRenderers.js
@@ -0,0 +1,131 @@
+import React from 'react';
+import MockWizardDeployContents from './mockWizardDeployContents';
+import { Wizard } from '../index';
+import { mockWizardFormContents } from './mockWizardItems';
+import MockWizardReviewStepsManager from './mockWizardReviewStepsManager';
+
+export const renderWizardSteps = (
+ wizardSteps,
+ activeStepIndex,
+ activeSubStepIndex,
+ onStepClick,
+) => {
+ const activeStep = wizardSteps[activeStepIndex];
+ const activeSubStep = activeStep.subSteps[activeSubStepIndex];
+
+ return wizardSteps.map((step, stepIndex) => {
+ return (
+
+ {step.subSteps.map((sub, subStepIndex) => {
+ return (
+
+ );
+ })}
+
+ );
+ });
+};
+
+export const renderSidebarItems = (
+ wizardSteps,
+ activeStepIndex,
+ activeSubStepIndex,
+ onSidebarItemClick,
+) => {
+ const activeStep = wizardSteps[activeStepIndex];
+ const activeSubStep = activeStep.subSteps[activeSubStepIndex];
+
+ return wizardSteps.map((step, stepIndex) => {
+ return (
+
+ {step.subSteps.map((sub, subStepIndex) => {
+ return (
+
+ );
+ })}
+
+ );
+ });
+};
+
+export const renderWizardContents = (
+ wizardSteps,
+ activeStepIndex,
+ activeSubStepIndex,
+) => {
+ return wizardSteps.map((step, stepIndex) => {
+ return step.subSteps.map((sub, subStepIndex) => {
+ if (stepIndex === 0 || stepIndex === 1) {
+ // render steps 1 and 2 mock contents
+ return (
+
+ {mockWizardFormContents(sub.contents.label1, sub.contents.label2)}
+
+ );
+ } else if (stepIndex === 2 && subStepIndex === 0) {
+ // render mock summary
+ return (
+
+
+
+ );
+ } else if (stepIndex === 2 && subStepIndex === 1) {
+ // render mock progress
+ return (
+
+
+
+ );
+ }
+ });
+ });
+};
diff --git a/src/components/Wizard/__mocks__/mockWizardReviewStepsManager.js b/src/components/Wizard/__mocks__/mockWizardReviewStepsManager.js
new file mode 100644
index 00000000000..ab7a835f00c
--- /dev/null
+++ b/src/components/Wizard/__mocks__/mockWizardReviewStepsManager.js
@@ -0,0 +1,91 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { bindMethods } from '../../../common/helpers';
+import { Wizard } from '../index';
+
+class MockWizardReviewStepsManager extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ steps: [...props.steps],
+ };
+ bindMethods(this, ['stepClicked', 'subStepClicked']);
+ }
+ stepClicked(e, stepIndex) {
+ e.preventDefault();
+ const updated = [...this.state.steps];
+ updated[stepIndex].collapsed = !updated[stepIndex].collapsed;
+ this.setState({
+ steps: updated,
+ });
+ }
+ subStepClicked(e, stepIndex, subStepIndex) {
+ e.preventDefault();
+ const updated = [...this.state.steps];
+ updated[stepIndex].subSteps[subStepIndex].collapsed = !updated[stepIndex]
+ .subSteps[subStepIndex].collapsed;
+ this.setState({
+ steps: updated,
+ });
+ }
+ render() {
+ const { steps } = this.state;
+ return (
+
+ {steps.map((step, stepIndex) => {
+ if (stepIndex === 2) {
+ return;
+ }
+ return (
+ this.stepClicked(e, stepIndex)}
+ key={stepIndex}
+ >
+
+ {step.subSteps.map((sub, subStepIndex) => {
+ return (
+
+ this.subStepClicked(e, stepIndex, subStepIndex)
+ }
+ key={subStepIndex}
+ >
+
+
+
+ {sub.contents.label1}:
+
+
+ Brian Johnson
+
+
+
+
+ {sub.contents.label2}:
+
+
+ This is the description.
+
+
+
+
+ );
+ })}
+
+
+ );
+ })}
+
+ );
+ }
+}
+MockWizardReviewStepsManager.propTypes = {
+ /** Wizard steps */
+ steps: PropTypes.array,
+};
+export default MockWizardReviewStepsManager;
diff --git a/src/components/Wizard/__snapshots__/Wizard.test.js.snap b/src/components/Wizard/__snapshots__/Wizard.test.js.snap
new file mode 100644
index 00000000000..e0c46061f68
--- /dev/null
+++ b/src/components/Wizard/__snapshots__/Wizard.test.js.snap
@@ -0,0 +1,824 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Wizard embedded renders properly 1`] = `
+
+
+ Wizard Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Deployment in progress
+
+
+ Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Wizard loading renders properly 1`] = `
+
+
+
+
+
+ Wizard Title
+
+
+
+
+
+
+
+
+ Loading Wizard
+
+
+ Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/components/Wizard/index.js b/src/components/Wizard/index.js
new file mode 100644
index 00000000000..e95f97a11e8
--- /dev/null
+++ b/src/components/Wizard/index.js
@@ -0,0 +1,60 @@
+import Wizard from './Wizard';
+import WizardBody from './WizardBody';
+import WizardContents from './WizardContents';
+import WizardFooter from './WizardFooter';
+import WizardHeader from './WizardHeader';
+import WizardMain from './WizardMain';
+import WizardReviewContent from './WizardReviewContent';
+import WizardReviewItem from './WizardReviewItem';
+import WizardReviewStep from './WizardReviewStep';
+import WizardReviewSteps from './WizardReviewSteps';
+import WizardReviewSubStep from './WizardReviewSubStep';
+import WizardReviewSubSteps from './WizardReviewSubSteps';
+import WizardRow from './WizardRow';
+import WizardSidebar from './WizardSidebar';
+import WizardSidebarGroup from './WizardSidebarGroup';
+import WizardSidebarGroupItem from './WizardSidebarGroupItem';
+import WizardStep from './WizardStep';
+import WizardSteps from './WizardSteps';
+import WizardSubStep from './WizardSubStep';
+
+Wizard.Body = WizardBody;
+Wizard.Contents = WizardContents;
+Wizard.Footer = WizardFooter;
+Wizard.Header = WizardHeader;
+Wizard.Main = WizardMain;
+Wizard.ReviewContent = WizardReviewContent;
+Wizard.ReviewItem = WizardReviewItem;
+Wizard.ReviewStep = WizardReviewStep;
+Wizard.ReviewSteps = WizardReviewSteps;
+Wizard.ReviewSubStep = WizardReviewSubStep;
+Wizard.ReviewSubSteps = WizardReviewSubSteps;
+Wizard.Row = WizardRow;
+Wizard.Sidebar = WizardSidebar;
+Wizard.SidebarGroup = WizardSidebarGroup;
+Wizard.SidebarGroupItem = WizardSidebarGroupItem;
+Wizard.Step = WizardStep;
+Wizard.Steps = WizardSteps;
+Wizard.SubStep = WizardSubStep;
+
+export {
+ Wizard,
+ WizardBody,
+ WizardContents,
+ WizardFooter,
+ WizardHeader,
+ WizardMain,
+ WizardReviewContent,
+ WizardReviewItem,
+ WizardReviewStep,
+ WizardReviewSteps,
+ WizardReviewSubStep,
+ WizardReviewSubSteps,
+ WizardRow,
+ WizardSidebar,
+ WizardSidebarGroup,
+ WizardSidebarGroupItem,
+ WizardStep,
+ WizardSteps,
+ WizardSubStep,
+};
diff --git a/src/index.js b/src/index.js
index e41a0fd8f69..763b7737502 100644
--- a/src/index.js
+++ b/src/index.js
@@ -9,7 +9,9 @@ export * from './components/Icon';
export * from './components/ListGroup';
export * from './components/ListView';
export * from './components/MenuItem';
+export * from './components/Modal';
export * from './components/OverlayTrigger';
export * from './components/Popover';
export * from './components/ToastNotification';
export * from './components/Tooltip';
+export * from './components/Wizard';