+);
+
+React.render(staticTextExample, mountNode);
diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js
index e268bc27f5..863e71b78d 100644
--- a/docs/src/ComponentsPage.js
+++ b/docs/src/ComponentsPage.js
@@ -577,8 +577,10 @@ const ComponentsPage = React.createClass({
The helper method getInputDOMNode() returns the internal input element. If you don't want the form-group class applied apply the prop named standalone.
Types
-
Supports select, textarea, static as well as standard HTML input types. getValue() returns an array for multiple select.
+
Supports select, textarea, as well as standard HTML input types. getValue() returns an array for multiple select.
+
Static text can be added to your form controls through the use of the FormControls.Static component.
+
Button Input Types
Form buttons are encapsulated by ButtonInput. Pass in type="reset" or type="submit" to suit your needs. Styling is the same as Button.
diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js
index 6561e87287..097fd47fb5 100644
--- a/docs/src/ReactPlayground.js
+++ b/docs/src/ReactPlayground.js
@@ -13,6 +13,7 @@ import * as modCarousel from '../../src/Carousel';
import * as modCarouselItem from '../../src/CarouselItem';
import * as modCol from '../../src/Col';
import * as modDropdownButton from '../../src/DropdownButton';
+import * as modFormControls from '../../src/FormControls';
import * as modGlyphicon from '../../src/Glyphicon';
import * as modGrid from '../../src/Grid';
import * as modInput from '../../src/Input';
@@ -64,6 +65,7 @@ const Carousel = modCarousel.default;
const CarouselItem = modCarouselItem.default;
const Col = modCol.default;
const DropdownButton = modDropdownButton.default;
+const FormControls = modFormControls.default;
const Glyphicon = modGlyphicon.default;
const Grid = modGrid.default;
const Input = modInput.default;
diff --git a/docs/src/Samples.js b/docs/src/Samples.js
index 3fc3a163a5..b15e400a85 100644
--- a/docs/src/Samples.js
+++ b/docs/src/Samples.js
@@ -89,6 +89,7 @@ export default {
TableResponsive: require('fs').readFileSync(__dirname + '/../examples/TableResponsive.js', 'utf8'),
Input: require('fs').readFileSync(__dirname + '/../examples/Input.js', 'utf8'),
InputTypes: require('fs').readFileSync(__dirname + '/../examples/InputTypes.js', 'utf8'),
+ StaticText: require('fs').readFileSync(__dirname + '/../examples/StaticText.js', 'utf8'),
ButtonInput: require('fs').readFileSync(__dirname + '/../examples/ButtonInput.js', 'utf8'),
InputAddons: require('fs').readFileSync(__dirname + '/../examples/InputAddons.js', 'utf8'),
InputSizes: require('fs').readFileSync(__dirname + '/../examples/InputSizes.js', 'utf8'),
diff --git a/src/ButtonInput.js b/src/ButtonInput.js
index 4df4c6a9f1..8667df0178 100644
--- a/src/ButtonInput.js
+++ b/src/ButtonInput.js
@@ -2,13 +2,7 @@ import React from 'react';
import Button from './Button';
import FormGroup from './FormGroup';
import InputBase from './InputBase';
-
-function valueValidation({children, value}, propName, componentName) {
- if (children && value) {
- return new Error('Both value and children cannot be passed to ButtonInput');
- }
- return React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).call(null, {children, value}, propName, componentName);
-}
+import childrenValueValidation from './utils/childrenValueInputValidation';
class ButtonInput extends InputBase {
renderFormGroup(children) {
@@ -23,18 +17,20 @@ class ButtonInput extends InputBase {
}
}
+ButtonInput.types = ['button', 'reset', 'submit'];
+
ButtonInput.defaultProps = {
type: 'button'
};
ButtonInput.propTypes = {
- type: React.PropTypes.oneOf(['button', 'reset', 'submit']),
+ type: React.PropTypes.oneOf(ButtonInput.types),
bsStyle(props) {
//defer to Button propTypes of bsStyle
return null;
},
- children: valueValidation,
- value: valueValidation
+ children: childrenValueValidation,
+ value: childrenValueValidation
};
export default ButtonInput;
diff --git a/src/FormControls/Static.js b/src/FormControls/Static.js
new file mode 100644
index 0000000000..3f0f4a40a3
--- /dev/null
+++ b/src/FormControls/Static.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import classNames from 'classnames';
+import InputBase from '../InputBase';
+import childrenValueValidation from '../utils/childrenValueInputValidation';
+
+class Static extends InputBase {
+ getValue() {
+ const {children, value} = this.props;
+ return children ? children : value;
+ }
+
+ renderInput() {
+ return (
+
+ {this.getValue()}
+
+ );
+ }
+}
+
+Static.propTypes = {
+ value: childrenValueValidation,
+ children: childrenValueValidation
+};
+
+export default Static;
diff --git a/src/FormControls/index.js b/src/FormControls/index.js
new file mode 100644
index 0000000000..5a7c16286e
--- /dev/null
+++ b/src/FormControls/index.js
@@ -0,0 +1,5 @@
+import Static from './Static';
+
+export default {
+ Static
+};
diff --git a/src/Input.js b/src/Input.js
index 61456c70c3..fa516a4ee4 100644
--- a/src/Input.js
+++ b/src/Input.js
@@ -1,15 +1,17 @@
import React from 'react';
import InputBase from './InputBase';
import ButtonInput from './ButtonInput';
+import FormControls from './FormControls';
import deprecationWarning from './utils/deprecationWarning';
-const buttonTypes = ['button', 'reset', 'submit'];
-
class Input extends InputBase {
render() {
- if (buttonTypes.indexOf(this.props.type) > -1) {
+ if (ButtonInput.types.indexOf(this.props.type) > -1) {
deprecationWarning(`Input type=${this.props.type}`, 'ButtonInput');
return ;
+ } else if (this.props.type === 'static') {
+ deprecationWarning('Input type=static', 'StaticText');
+ return ;
}
return super.render();
diff --git a/src/index.js b/src/index.js
index 27a3d931f3..0f05aed32f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -17,6 +17,7 @@ import DropdownButton from './DropdownButton';
import DropdownMenu from './DropdownMenu';
import DropdownStateMixin from './DropdownStateMixin';
import FadeMixin from './FadeMixin';
+import FormControls from './FormControls';
import Glyphicon from './Glyphicon';
import Grid from './Grid';
import Input from './Input';
@@ -71,6 +72,7 @@ export default {
DropdownMenu,
DropdownStateMixin,
FadeMixin,
+ FormControls,
Glyphicon,
Grid,
Input,
diff --git a/src/utils/CustomPropTypes.js b/src/utils/CustomPropTypes.js
index c56a0a4e8a..5a6ae4e0fc 100644
--- a/src/utils/CustomPropTypes.js
+++ b/src/utils/CustomPropTypes.js
@@ -22,7 +22,17 @@ let CustomPropTypes = {
* @param componentName
* @returns {Error|undefined}
*/
- keyOf: createKeyOfChecker
+ keyOf: createKeyOfChecker,
+ /**
+ * Checks if only one of the listed properties is in use. An error is given
+ * if multiple have a value
+ *
+ * @param props
+ * @param propName
+ * @param componentName
+ * @returns {Error|undefined}
+ */
+ singlePropFrom: createSinglePropFromChecker
};
/**
@@ -80,4 +90,22 @@ function createKeyOfChecker(obj) {
return createChainableTypeChecker(validate);
}
+function createSinglePropFromChecker(arrOfProps) {
+ function validate(props, propName, componentName) {
+ const usedPropCount = arrOfProps
+ .map(listedProp => props[listedProp])
+ .reduce((acc, curr) => acc + (curr !== undefined ? 1 : 0), 0);
+
+ if (usedPropCount > 1) {
+ const [first, ...others] = arrOfProps;
+ const message = `${others.join(', ')} and ${first}`;
+ return new Error(
+ `Invalid prop '${propName}', only one of the following ` +
+ `may be provided: ${message}`
+ );
+ }
+ }
+ return validate;
+}
+
export default CustomPropTypes;
diff --git a/src/utils/childrenValueInputValidation.js b/src/utils/childrenValueInputValidation.js
new file mode 100644
index 0000000000..075a4c42b0
--- /dev/null
+++ b/src/utils/childrenValueInputValidation.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import { singlePropFrom } from './CustomPropTypes';
+
+const propList = ['children', 'value'];
+const typeList = [React.PropTypes.number, React.PropTypes.string];
+
+export default function valueValidation(props, propName, componentName) {
+ let error = singlePropFrom(propList)(props, propName, componentName);
+ if (!error) {
+ const oneOfType = React.PropTypes.oneOfType(typeList);
+ error = oneOfType(props, propName, componentName);
+ }
+ return error;
+}
diff --git a/test/ButtonInputSpec.js b/test/ButtonInputSpec.js
index 5118181938..7f5e66ac66 100644
--- a/test/ButtonInputSpec.js
+++ b/test/ButtonInputSpec.js
@@ -55,11 +55,11 @@ describe('ButtonInput', () =>{
});
it('throws a warning if given both children and a value property', function () {
- ReactTestUtils.renderIntoDocument(
- button
- );
+ const testData = { value: 5, children: 'button' };
+ const result = ButtonInput.propTypes.value(testData, 'value', 'ButtonInput');
- shouldWarn('Both value and children');
+ result.should.be.instanceOf(Error);
+ result.message.should.have.string('value and children');
});
it('does not throw an error for strings and numbers', function () {
diff --git a/test/CustomPropTypesSpec.js b/test/CustomPropTypesSpec.js
index 9a0b94deb2..d28137f585 100644
--- a/test/CustomPropTypesSpec.js
+++ b/test/CustomPropTypesSpec.js
@@ -47,4 +47,24 @@ describe('CustomPropTypes', function () {
assert.isUndefined(validate('bar'));
});
});
+
+ describe('singlePropFrom', function () {
+ function validate(testProps) {
+ const propList = ['children', 'value'];
+
+ return CustomPropTypes.singlePropFrom(propList)(testProps, 'value', 'Component');
+ }
+
+ it('Should return undefined if only one listed prop in used', function () {
+ const testProps = {value: 5};
+
+ assert.isUndefined(validate(testProps));
+ });
+
+ it('Should return error if multiple of the listed properties have values', function () {
+ const testProps = {value: 5, children: 5};
+
+ validate(testProps).should.be.instanceOf(Error);
+ });
+ });
});
diff --git a/test/FormControlsSpec.js b/test/FormControlsSpec.js
new file mode 100644
index 0000000000..cc56ecd618
--- /dev/null
+++ b/test/FormControlsSpec.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import FormControls from '../src/FormControls';
+
+describe('Form Controls', function () {
+ describe('Static', function () {
+ it('renders a p element wrapped around the given value', function () {
+ const instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ const result = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'p');
+ result.props.children.should.equal('v');
+ });
+
+ it('getValue() pulls from either value or children', function () {
+ let instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ instance.getValue().should.equal('v');
+
+ instance = ReactTestUtils.renderIntoDocument(
+ 5
+ );
+
+ instance.getValue().should.equal('5');
+ });
+
+ it('throws an error if both value and children are provided', function () {
+ const testData = { value: 'blah', children: 'meh' };
+ const result = FormControls.Static.propTypes.children(testData, 'children', 'Static');
+
+ result.should.be.instanceOf(Error);
+ });
+ });
+});
diff --git a/test/InputSpec.js b/test/InputSpec.js
index 065ff1824c..3a73cba5cf 100644
--- a/test/InputSpec.js
+++ b/test/InputSpec.js
@@ -65,13 +65,12 @@ describe('Input', function () {
shouldWarn('deprecated');
});
- it('renders a p element when type=static', function () {
- let instance = ReactTestUtils.renderIntoDocument(
+ it('throws a warning when type=static', function () {
+ ReactTestUtils.renderIntoDocument(
);
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'p'));
- assert.equal(instance.getValue(), 'v');
+ shouldWarn('deprecated');
});
it('renders an input element of given type when type is anything else', function () {