diff --git a/src/components/Form/Checkbox.js b/src/components/Form/Checkbox.js
new file mode 100644
index 00000000000..a534135f21b
--- /dev/null
+++ b/src/components/Form/Checkbox.js
@@ -0,0 +1 @@
+export { Checkbox as default } from 'react-bootstrap';
diff --git a/src/components/Form/ControlLabel.js b/src/components/Form/ControlLabel.js
new file mode 100644
index 00000000000..eca5ae64beb
--- /dev/null
+++ b/src/components/Form/ControlLabel.js
@@ -0,0 +1 @@
+export { ControlLabel as default } from 'react-bootstrap';
diff --git a/src/components/Form/Form.js b/src/components/Form/Form.js
new file mode 100644
index 00000000000..b4275639eec
--- /dev/null
+++ b/src/components/Form/Form.js
@@ -0,0 +1 @@
+export { Form as default } from 'react-bootstrap';
diff --git a/src/components/Form/Form.stories.js b/src/components/Form/Form.stories.js
new file mode 100644
index 00000000000..245dee24a9a
--- /dev/null
+++ b/src/components/Form/Form.stories.js
@@ -0,0 +1,254 @@
+/* eslint react/prop-types: 0 */
+
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+import { withKnobs, boolean } from '@storybook/addon-knobs';
+import { defaultTemplate } from '../../../storybook/decorators/storyTemplates';
+import { Icon } from '../Icon';
+import { Col, Row, Grid } from '../Grid';
+import { Button } from '../Button';
+import { Modal } from '../Modal';
+import { Form } from './index';
+
+import {
+ InlineFormFields,
+ InlineFormButtons,
+ getInlineFormKnobs
+} from './Stories/InlineForm';
+import {
+ BasicFormFields,
+ BasicFormButtons,
+ BasicFormSpinner,
+ getBasicFormKnobs
+} from './Stories/BasicForm';
+import {
+ SupportedControlsFormFields,
+ getSupportedControlsFormKnobs
+} from './Stories/SupportedControlsForm';
+import {
+ InputGroupsFormFields,
+ getInputGroupsFormKnobs
+} from './Stories/InputGroupsForm';
+import { InlineFormField } from './Stories/InlineFormField';
+import { HorizontalFormField } from './Stories/HorizontalFormField';
+import { VerticalFormField } from './Stories/VerticalFormField';
+
+const stories = storiesOf('Forms', module);
+
+stories.addDecorator(withKnobs);
+
+const description = (
+
+ Those components are based on React Bootstrap Form components. See{' '}
+
+ React Bootstrap Docs
+ {' '}
+ for complete Form components documentation.
+
+);
+
+stories.addDecorator(
+ defaultTemplate({
+ title: 'Forms',
+ documentationLink:
+ 'http://www.patternfly.org/pattern-library/widgets/#forms',
+ description: description
+ })
+);
+
+stories.addWithInfo('Inline Form', '', () => {
+ const formFieldsKnobs = getInlineFormKnobs();
+ const { bsSize, disabled } = formFieldsKnobs;
+ const buttonsProps = {};
+
+ if (bsSize) buttonsProps.bsSize = bsSize;
+ if (disabled) buttonsProps.disabled = disabled;
+
+ const formFields = InlineFormFields.map(formField =>
+ InlineFormField({ ...formField, ...formFieldsKnobs })
+ ).reduce((result = [], element) => {
+ return [...result, element, ' ']; // create spacing betwwen elements
+ }, []);
+
+ const formButtons = InlineFormButtons.map(({ text, ...props }) => (
+
+ {text}
+
+ ));
+
+ return (
+
+
+
+ );
+});
+
+stories.addWithInfo('Horizontal Form', '', () => {
+ const formFieldsKnobs = getBasicFormKnobs();
+ const { bsSize, disabled } = formFieldsKnobs;
+ const buttonsProps = {};
+
+ if (bsSize) buttonsProps.bsSize = bsSize;
+ if (disabled) buttonsProps.disabled = disabled;
+
+ const showLoading = boolean('Show Loading', false);
+
+ const formFields = BasicFormFields.map(formField =>
+ HorizontalFormField({ ...formField, ...formFieldsKnobs })
+ );
+
+ const formButtons = BasicFormButtons.map(({ text, ...props }) => (
+
+
+ {text}
+ {' '}
+
+ ));
+
+ return (
+
+
+
+ );
+});
+
+stories.addWithInfo('Vertical Form', '', () => {
+ const formFieldsKnobs = getBasicFormKnobs();
+ const { bsSize, disabled } = formFieldsKnobs;
+ const buttonsProps = {};
+
+ if (bsSize) buttonsProps.bsSize = bsSize;
+ if (disabled) buttonsProps.disabled = disabled;
+
+ const showLoading = boolean('Show Loading', false);
+
+ const formFields = BasicFormFields.map(formField =>
+ VerticalFormField({ ...formField, ...formFieldsKnobs })
+ );
+
+ const formButtons = BasicFormButtons.map(({ text, ...props }) => (
+
+
+ {text}
+ {' '}
+
+ ));
+
+ return (
+
+
+
+ );
+});
+
+stories.addWithInfo('Modal Form', '', () => {
+ const formFieldsKnobs = getBasicFormKnobs();
+ const { bsSize, disabled } = formFieldsKnobs;
+ const buttonsProps = {};
+
+ if (bsSize) buttonsProps.bsSize = bsSize;
+ if (disabled) buttonsProps.disabled = disabled;
+
+ const showModal = boolean('Show Modal', true);
+ const showLoading = boolean('Show Loading', false);
+
+ const formFields = BasicFormFields.map(formField =>
+ HorizontalFormField({ ...formField, ...formFieldsKnobs })
+ );
+
+ const formButtons = BasicFormButtons.map(({ text, ...props }) => (
+
+ {text}
+
+ )).reverse();
+
+ const formSpinner = (
+
+ {[...BasicFormSpinner].reverse()}
+
+ );
+
+ return (
+
+
+
+
+
+ Basic Settings
+
+
+
+
+
+ {formButtons}
+ {showLoading && formSpinner}
+
+
+ );
+});
+
+stories.addWithInfo('Supported Controls', '', () => {
+ const formFieldsKnobs = getSupportedControlsFormKnobs();
+
+ const formFields = SupportedControlsFormFields.map(formField =>
+ VerticalFormField({ ...formField, ...formFieldsKnobs })
+ );
+
+ return (
+
+
+
+ );
+});
+
+stories.addWithInfo('Input Groups', '', () => {
+ const formFieldsKnobs = getInputGroupsFormKnobs();
+
+ const formFields = InputGroupsFormFields.map(formField =>
+ VerticalFormField({ ...formField, ...formFieldsKnobs })
+ );
+
+ return (
+
+
+
+ );
+});
diff --git a/src/components/Form/FormControl.js b/src/components/Form/FormControl.js
new file mode 100644
index 00000000000..b0090ba0649
--- /dev/null
+++ b/src/components/Form/FormControl.js
@@ -0,0 +1 @@
+export { FormControl as default } from 'react-bootstrap';
diff --git a/src/components/Form/FormGroup.js b/src/components/Form/FormGroup.js
new file mode 100644
index 00000000000..2b2414bd415
--- /dev/null
+++ b/src/components/Form/FormGroup.js
@@ -0,0 +1 @@
+export { FormGroup as default } from 'react-bootstrap';
diff --git a/src/components/Form/HelpBlock.js b/src/components/Form/HelpBlock.js
new file mode 100644
index 00000000000..7ac25b75970
--- /dev/null
+++ b/src/components/Form/HelpBlock.js
@@ -0,0 +1 @@
+export { HelpBlock as default } from 'react-bootstrap';
diff --git a/src/components/Form/InputGroup.js b/src/components/Form/InputGroup.js
new file mode 100644
index 00000000000..89263986f60
--- /dev/null
+++ b/src/components/Form/InputGroup.js
@@ -0,0 +1 @@
+export { InputGroup as default } from 'react-bootstrap';
diff --git a/src/components/Form/Radio.js b/src/components/Form/Radio.js
new file mode 100644
index 00000000000..0a1d6699ad0
--- /dev/null
+++ b/src/components/Form/Radio.js
@@ -0,0 +1 @@
+export { Radio as default } from 'react-bootstrap';
diff --git a/src/components/Form/Stories/BasicForm.js b/src/components/Form/Stories/BasicForm.js
new file mode 100644
index 00000000000..1fcc20d3f3a
--- /dev/null
+++ b/src/components/Form/Stories/BasicForm.js
@@ -0,0 +1,94 @@
+/* eslint react/prop-types: 0 */
+
+import React from 'react';
+import { action } from '@storybook/addon-actions';
+import { select, boolean } from '@storybook/addon-knobs';
+import { Spinner } from '../../Spinner';
+import { Button } from '../../Button';
+import { FormControl, InputGroup } from '../index';
+
+export const BasicFormFields = [
+ {
+ controlId: 'name',
+ label: 'Name',
+ help: 'Enter your name',
+ formControl: ({ validationState, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'address',
+ label: 'Address',
+ help: 'Enter your address',
+ formControl: ({ validationState, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'city',
+ label: 'City',
+ help: 'Enter your city',
+ formControl: ({ validationState, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'email',
+ label: 'Email',
+ help: 'Enter a valid email address',
+ formControl: ({ validationState, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'url',
+ label: 'My meeting URL',
+ help: 'Enter a valid URL',
+ formControl: ({ validationState, disabled, ...props }) => {
+ const controlProps = { disabled };
+ return (
+
+
+
+
+ Copy URL
+
+
+
+ );
+ }
+ }
+];
+
+export const BasicFormButtons = [
+ {
+ text: 'Save',
+ bsStyle: 'primary',
+ onClick: action('Save')
+ },
+ {
+ text: 'Cancel',
+ bsStyle: 'default',
+ onClick: action('Cancel')
+ }
+];
+
+export const BasicFormSpinner = [
+ ,
+ ' ',
+
+ Do not refresh this page. This request may take a minute...
+
+];
+
+export const getBasicFormKnobs = () => ({
+ validationState: select('Validation State', [
+ null,
+ 'success',
+ 'warning',
+ 'error'
+ ]),
+ bsSize: select('Size', [null, 'small', 'large']),
+ showHelp: boolean('Show Help', true),
+ disabled: boolean('Disabled', false)
+});
diff --git a/src/components/Form/Stories/HorizontalFormField.js b/src/components/Form/Stories/HorizontalFormField.js
new file mode 100644
index 00000000000..1aba6664980
--- /dev/null
+++ b/src/components/Form/Stories/HorizontalFormField.js
@@ -0,0 +1,35 @@
+/* eslint react/prop-types: 0 */
+
+import React from 'react';
+import { Col } from '../../Grid';
+import { FormGroup, ControlLabel, HelpBlock } from '../index';
+
+export const HorizontalFormField = ({
+ controlId,
+ label,
+ help,
+ formControl,
+ validationState,
+ bsSize,
+ showHelp,
+ ...props
+}) => {
+ const controlProps = { ...props };
+
+ if (bsSize) controlProps.bsSize = bsSize;
+ if (validationState) controlProps.validationState = validationState;
+
+ const formGroupProps = { key: controlId, controlId, ...controlProps };
+
+ return (
+
+
+ {label}
+
+
+ {formControl(controlProps)}
+ {showHelp && help && {help} }
+
+
+ );
+};
diff --git a/src/components/Form/Stories/InlineForm.js b/src/components/Form/Stories/InlineForm.js
new file mode 100644
index 00000000000..beb2b2624dd
--- /dev/null
+++ b/src/components/Form/Stories/InlineForm.js
@@ -0,0 +1,35 @@
+/* eslint react/prop-types: 0 */
+
+import React from 'react';
+import { action } from '@storybook/addon-actions';
+import { select, boolean } from '@storybook/addon-knobs';
+import { FormControl } from '../index';
+
+export const InlineFormFields = [
+ {
+ controlId: 'email',
+ label: 'Email',
+ placeholder: 'Email',
+ formControl: props =>
+ },
+ {
+ controlId: 'password',
+ label: 'Password',
+ placeholder: 'Password',
+ formControl: props =>
+ }
+];
+
+export const InlineFormButtons = [
+ {
+ text: 'Log In',
+ bsStyle: 'primary',
+ onClick: action('Login')
+ }
+];
+
+export const getInlineFormKnobs = () => ({
+ bsSize: select('Size', [null, 'small', 'large']),
+ disabled: boolean('Disabled', false),
+ showLabel: boolean('Show Labels', false)
+});
diff --git a/src/components/Form/Stories/InlineFormField.js b/src/components/Form/Stories/InlineFormField.js
new file mode 100644
index 00000000000..cd17f2f73cf
--- /dev/null
+++ b/src/components/Form/Stories/InlineFormField.js
@@ -0,0 +1,28 @@
+/* eslint react/prop-types: 0 */
+
+import React from 'react';
+import { FormGroup, ControlLabel } from '../index';
+
+export const InlineFormField = ({
+ controlId,
+ label,
+ formControl,
+ validationState,
+ bsSize,
+ showLabel,
+ ...props
+}) => {
+ const controlProps = { ...props };
+
+ if (bsSize) controlProps.bsSize = bsSize;
+ if (validationState) controlProps.validationState = validationState;
+
+ const formGroupProps = { key: controlId, controlId, ...controlProps };
+
+ return (
+
+ {showLabel && label && {label} }{' '}
+ {formControl(controlProps)}
+
+ );
+};
diff --git a/src/components/Form/Stories/InputGroupsForm.js b/src/components/Form/Stories/InputGroupsForm.js
new file mode 100644
index 00000000000..bc4de0babb3
--- /dev/null
+++ b/src/components/Form/Stories/InputGroupsForm.js
@@ -0,0 +1,142 @@
+/* eslint react/prop-types: 0 */
+
+import React from 'react';
+import { select, boolean } from '@storybook/addon-knobs';
+import { Button, DropdownButton } from '../../Button';
+import { Icon } from '../../Icon';
+import { MenuItem } from '../../MenuItem';
+import { FormControl, InputGroup } from '../index';
+
+export const InputGroupsFormFields = [
+ {
+ controlId: 'control-1',
+ formControl: ({
+ validationState = 'default',
+ disabled = false,
+ ...props
+ }) => (
+
+ @
+
+
+ )
+ },
+ {
+ controlId: 'control-2',
+ formControl: ({
+ validationState = 'default',
+ disabled = false,
+ ...props
+ }) => (
+
+
+ .00
+
+ )
+ },
+ {
+ controlId: 'control-3',
+ formControl: ({
+ validationState = 'default',
+ disabled = false,
+ ...props
+ }) => (
+
+ $
+
+ .00
+
+ )
+ },
+ {
+ controlId: 'control-4',
+ formControl: ({
+ validationState = 'default',
+ disabled = false,
+ ...props
+ }) => (
+
+
+
+
+
+
+ )
+ },
+ {
+ controlId: 'control-5',
+ formControl: ({
+ validationState = 'default',
+ disabled = false,
+ ...props
+ }) => (
+
+
+ Before
+
+
+
+ )
+ },
+ {
+ controlId: 'control-6',
+ formControl: ({
+ validationState = 'default',
+ disabled = false,
+ ...props
+ }) => (
+
+
+
+ Item
+
+
+ )
+ },
+ {
+ controlId: 'control-7',
+ formControl: ({
+ validationState = 'default',
+ disabled = false,
+ ...props
+ }) => (
+
+
+
+
+
+
+ )
+ },
+ {
+ controlId: 'control-8',
+ formControl: ({
+ validationState = 'default',
+ disabled = false,
+ ...props
+ }) => (
+
+
+
+
+
+
+ )
+ }
+];
+
+export const getInputGroupsFormKnobs = () => ({
+ validationState: select('Validation State', [
+ null,
+ 'success',
+ 'warning',
+ 'error'
+ ]),
+ bsSize: select('Size', [null, 'small', 'large']),
+ disabled: boolean('Disabled', false)
+});
diff --git a/src/components/Form/Stories/SupportedControlsForm.js b/src/components/Form/Stories/SupportedControlsForm.js
new file mode 100644
index 00000000000..9b122824004
--- /dev/null
+++ b/src/components/Form/Stories/SupportedControlsForm.js
@@ -0,0 +1,120 @@
+/* eslint react/prop-types: 0 */
+
+import React from 'react';
+import { select, boolean } from '@storybook/addon-knobs';
+import { FormControl, Checkbox, Radio } from '../index';
+
+export const SupportedControlsFormFields = [
+ {
+ controlId: 'text',
+ label: 'Text',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'email',
+ label: 'Email',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'password',
+ label: 'Password',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'file',
+ label: 'File',
+ help: 'Help text',
+ formControl: ({ validationState, bsSize, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'checkbox',
+ label: 'Checkbox',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+ Checkbox
+ )
+ },
+ {
+ controlId: 'radio',
+ label: 'Radio',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+ Radio
+ )
+ },
+ {
+ controlId: 'checkboxGroup',
+ label: 'Checkbox Group',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ )
+ },
+ {
+ controlId: 'radioGroup',
+ label: 'Radio Group',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ )
+ },
+ {
+ controlId: 'textarea',
+ label: 'Textarea',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+
+ )
+ },
+ {
+ controlId: 'static',
+ label: 'Static',
+ help: 'Help text',
+ formControl: ({ validationState, ...props }) => (
+ email@example.com
+ )
+ }
+];
+
+export const getSupportedControlsFormKnobs = () => ({
+ validationState: select('Validation State', [
+ null,
+ 'success',
+ 'warning',
+ 'error'
+ ]),
+ bsSize: select('Size', [null, 'small', 'large']),
+ showHelp: boolean('Show Help', true),
+ disabled: boolean('Disabled', false)
+});
diff --git a/src/components/Form/Stories/VerticalFormField.js b/src/components/Form/Stories/VerticalFormField.js
new file mode 100644
index 00000000000..22a80b704e9
--- /dev/null
+++ b/src/components/Form/Stories/VerticalFormField.js
@@ -0,0 +1,30 @@
+/* eslint react/prop-types: 0 */
+
+import React from 'react';
+import { FormGroup, ControlLabel, HelpBlock } from '../index';
+
+export const VerticalFormField = ({
+ controlId,
+ label,
+ help,
+ formControl,
+ validationState,
+ bsSize,
+ showHelp,
+ ...props
+}) => {
+ const controlProps = { ...props };
+
+ if (bsSize) controlProps.bsSize = bsSize;
+ if (validationState) controlProps.validationState = validationState;
+
+ const formGroupProps = { key: controlId, controlId, ...controlProps };
+
+ return (
+
+ {label && {label} }
+ {formControl(controlProps)}
+ {showHelp && help && {help} }
+
+ );
+};
diff --git a/src/components/Form/index.js b/src/components/Form/index.js
new file mode 100644
index 00000000000..8a7fb24615b
--- /dev/null
+++ b/src/components/Form/index.js
@@ -0,0 +1,8 @@
+export { default as Checkbox } from './Checkbox';
+export { default as ControlLabel } from './ControlLabel';
+export { default as Form } from './Form';
+export { default as FormControl } from './FormControl';
+export { default as FormGroup } from './FormGroup';
+export { default as HelpBlock } from './HelpBlock';
+export { default as InputGroup } from './InputGroup';
+export { default as Radio } from './Radio';
diff --git a/src/index.js b/src/index.js
index 384fa52de45..89a527b8fb1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,6 +5,7 @@ export * from './components/Button';
export * from './components/Dropdown';
export * from './components/DropdownKebab';
export * from './components/Filter';
+export * from './components/Form';
export * from './components/Grid';
export * from './components/Icon';
export * from './components/Chart';