Skip to content

Commit

Permalink
Merge pull request react-bootstrap#674 from aabenoja/static-split
Browse files Browse the repository at this point in the history
Static FormControl Component
  • Loading branch information
mtscout6 committed May 26, 2015
2 parents d278201 + 93c95b6 commit 09e5af9
Show file tree
Hide file tree
Showing 17 changed files with 167 additions and 24 deletions.
1 change: 1 addition & 0 deletions docs/examples/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"CarouselItem",
"Col",
"DropdownButton",
"FormControls",
"Glyphicon",
"Grid",
"Input",
Expand Down
1 change: 0 additions & 1 deletion docs/examples/InputTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const inputTypeInstance = (
<option value='other'>...</option>
</Input>
<Input type='textarea' label='Text Area' placeholder='textarea' />
<Input type='static' value='Static Text' />
<ButtonInput value='Button Input' />
<ButtonInput type='reset' value='Reset Button' />
<ButtonInput type='submit' value='Submit Button' />
Expand Down
9 changes: 9 additions & 0 deletions docs/examples/StaticText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const staticTextExample = (
<form className="form-horizontal">
<FormControls.Static className="col-xs-10 col-xs-offset-2" value="I'm in a form" />
<FormControls.Static label="First Name" labelClassName="col-xs-2" wrapperClassName="col-xs-10" value="Billy" />
<FormControls.Static label="Last Name" labelClassName="col-xs-2" wrapperClassName="col-xs-10">Bob</FormControls.Static>
</form>
);

React.render(staticTextExample, mountNode);
4 changes: 3 additions & 1 deletion docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,10 @@ const ComponentsPage = React.createClass({
The helper method <code>getInputDOMNode()</code> returns the internal input element. If you don't want the <code>form-group</code> class applied apply the prop named <code>standalone</code>.</p>
<ReactPlayground codeText={Samples.Input} />
<h2 id='input-types'>Types</h2>
<p>Supports <code>select</code>, <code>textarea</code>, <code>static</code> as well as standard HTML input types. <code>getValue()</code> returns an array for multiple select.</p>
<p>Supports <code>select</code>, <code>textarea</code>, as well as standard HTML input types. <code>getValue()</code> returns an array for multiple select.</p>
<ReactPlayground codeText={Samples.InputTypes} />
<p>Static text can be added to your form controls through the use of the <code>FormControls.Static</code> component.</p>
<ReactPlayground codeText={Samples.StaticText} />
<h2 id='button-input-types'>Button Input Types</h2>
<p>Form buttons are encapsulated by <code>ButtonInput</code>. Pass in <code>type="reset"</code> or <code>type="submit"</code> to suit your needs. Styling is the same as <code>Button</code>.</p>
<ReactPlayground codeText={Samples.ButtonInput} />
Expand Down
2 changes: 2 additions & 0 deletions docs/src/ReactPlayground.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions docs/src/Samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
16 changes: 6 additions & 10 deletions src/ButtonInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
26 changes: 26 additions & 0 deletions src/FormControls/Static.js
Original file line number Diff line number Diff line change
@@ -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 (
<p {...this.props} className={classNames(this.props.className, 'form-control-static')} ref="input" key="input">
{this.getValue()}
</p>
);
}
}

Static.propTypes = {
value: childrenValueValidation,
children: childrenValueValidation
};

export default Static;
5 changes: 5 additions & 0 deletions src/FormControls/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Static from './Static';

export default {
Static
};
8 changes: 5 additions & 3 deletions src/Input.js
Original file line number Diff line number Diff line change
@@ -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 <ButtonInput {...this.props} />;
} else if (this.props.type === 'static') {
deprecationWarning('Input type=static', 'StaticText');
return <FormControls.Static {...this.props} />;
}

return super.render();
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -71,6 +72,7 @@ export default {
DropdownMenu,
DropdownStateMixin,
FadeMixin,
FormControls,
Glyphicon,
Grid,
Input,
Expand Down
30 changes: 29 additions & 1 deletion src/utils/CustomPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

/**
Expand Down Expand Up @@ -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;
14 changes: 14 additions & 0 deletions src/utils/childrenValueInputValidation.js
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 4 additions & 4 deletions test/ButtonInputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ describe('ButtonInput', () =>{
});

it('throws a warning if given both children and a value property', function () {
ReactTestUtils.renderIntoDocument(
<ButtonInput value="button">button</ButtonInput>
);
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 () {
Expand Down
20 changes: 20 additions & 0 deletions test/CustomPropTypesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
37 changes: 37 additions & 0 deletions test/FormControlsSpec.js
Original file line number Diff line number Diff line change
@@ -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(
<FormControls.Static value='v' />
);

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(
<FormControls.Static value='v' />
);

instance.getValue().should.equal('v');

instance = ReactTestUtils.renderIntoDocument(
<FormControls.Static>5</FormControls.Static>
);

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);
});
});
});
7 changes: 3 additions & 4 deletions test/InputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<Input type="static" value="v" />
);

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 () {
Expand Down

0 comments on commit 09e5af9

Please sign in to comment.