diff --git a/packages/core/dist/components/ChoiceList/ChoiceList.js b/packages/core/dist/components/ChoiceList/ChoiceList.js index 96aaea6c99..9d486c3192 100644 --- a/packages/core/dist/components/ChoiceList/ChoiceList.js +++ b/packages/core/dist/components/ChoiceList/ChoiceList.js @@ -144,6 +144,7 @@ var ChoiceList = exports.ChoiceList = function (_React$PureComponent) { }, { key: 'select', value: function select(selectProps, options) { + var classes = this.props.size && 'ds-c-field--' + this.props.size; return _react2.default.createElement( _Select2.default, _extends({ @@ -152,7 +153,8 @@ var ChoiceList = exports.ChoiceList = function (_React$PureComponent) { inversed: this.props.inversed, name: this.props.name, onBlur: this.props.onBlur, - onChange: this.props.onChange + onChange: this.props.onChange, + className: classes }, selectProps), options ); @@ -268,6 +270,10 @@ ChoiceList.propTypes = { name: _propTypes2.default.string.isRequired, onBlur: _propTypes2.default.func, onChange: _propTypes2.default.func, + /** + * If the component renders a select, set the max-width of the input either to `'small'` or `'medium'`. + */ + size: _propTypes2.default.oneOf(['small', 'medium']), /** * You can manually set the `type` if you prefer things to be less magical. * Otherwise, the type will be inferred by the other `props`, based diff --git a/packages/core/dist/components/ChoiceList/Select.js b/packages/core/dist/components/ChoiceList/Select.js index ce03348c6a..86360ae18d 100644 --- a/packages/core/dist/components/ChoiceList/Select.js +++ b/packages/core/dist/components/ChoiceList/Select.js @@ -39,10 +39,11 @@ var Select = function Select(props) { className = props.className, id = props.id, inversed = props.inversed, - selectProps = _objectWithoutProperties(props, ['children', 'className', 'id', 'inversed']); + size = props.size, + selectProps = _objectWithoutProperties(props, ['children', 'className', 'id', 'inversed', 'size']); /* eslint-enable prefer-const */ - var classes = (0, _classnames2.default)('ds-c-field', { 'ds-c-field--inverse': inversed }, className); + var classes = (0, _classnames2.default)('ds-c-field', { 'ds-c-field--inverse': inversed }, className, size && 'ds-c-field--' + size); if (!id) { id = (0, _lodash2.default)('select_' + selectProps.name + '_'); @@ -95,6 +96,10 @@ Select.propTypes = { name: _propTypes2.default.string.isRequired, onBlur: _propTypes2.default.func, onChange: _propTypes2.default.func, + /** + * Set the max-width of the input either to `'small'` or `'medium'`. + */ + size: _propTypes2.default.oneOf(['small', 'medium']), /** * Sets the field's `value`. Use this in combination with `onChange` * for a controlled component; otherwise, set `defaultValue`. diff --git a/packages/core/dist/components/TextField/TextField.js b/packages/core/dist/components/TextField/TextField.js index ad4c21dbfb..d2e82b1873 100644 --- a/packages/core/dist/components/TextField/TextField.js +++ b/packages/core/dist/components/TextField/TextField.js @@ -56,9 +56,60 @@ var TextField = exports.TextField = function (_React$PureComponent) { } _createClass(TextField, [{ + key: 'ariaLabel', + value: function ariaLabel() { + if (this.props.ariaLabel) { + return this.props.ariaLabel; + } else if (this.props.mask === 'currency') { + return this.props.label + '. Enter amount in dollars.'; + } + } + + /** + * @param {React.Component} field + * @returns {React.Component} The input field, optionally including mask + * markup if a mask is present + */ + + }, { + key: 'renderFieldAndMask', + value: function renderFieldAndMask(field) { + var maskName = this.props.mask; + + return maskName ? _react2.default.createElement( + 'div', + { className: 'ds-c-field-mask ds-c-field-mask--' + maskName }, + this.renderMask(), + field + ) : field; + } + + /** + * UI overlayed on top of a field to support certain masks + */ + + }, { + key: 'renderMask', + value: function renderMask() { + if (this.props.mask) { + var content = { + currency: '$' + }; + + return _react2.default.createElement( + 'div', + { + className: 'ds-c-field__before ds-c-field__before--' + this.props.mask + }, + content[this.props.mask] + ); + } + } + }, { key: 'render', value: function render() { var _props = this.props, + ariaLabel = _props.ariaLabel, className = _props.className, labelClassName = _props.labelClassName, fieldClassName = _props.fieldClassName, @@ -68,21 +119,33 @@ var TextField = exports.TextField = function (_React$PureComponent) { requirementLabel = _props.requirementLabel, inversed = _props.inversed, rows = _props.rows, + mask = _props.mask, multiline = _props.multiline, label = _props.label, fieldRef = _props.fieldRef, + size = _props.size, type = _props.type, - fieldProps = _objectWithoutProperties(_props, ['className', 'labelClassName', 'fieldClassName', 'errorMessage', 'hint', 'id', 'requirementLabel', 'inversed', 'rows', 'multiline', 'label', 'fieldRef', 'type']); + fieldProps = _objectWithoutProperties(_props, ['ariaLabel', 'className', 'labelClassName', 'fieldClassName', 'errorMessage', 'hint', 'id', 'requirementLabel', 'inversed', 'rows', 'mask', 'multiline', 'label', 'fieldRef', 'size', 'type']); var FieldComponent = multiline ? 'textarea' : 'input'; var _rows = multiline && rows ? rows : undefined; var classes = (0, _classnames2.default)('ds-u-clearfix', // fixes issue where the label's margin is collapsed className); - var fieldClasses = (0, _classnames2.default)('ds-c-field', { + + var fieldClasses = (0, _classnames2.default)('ds-c-field', mask && 'ds-c-field--' + mask, { 'ds-c-field--error': typeof errorMessage === 'string', 'ds-c-field--inverse': inversed - }, fieldClassName); + }, fieldClassName, size && 'ds-c-field--' + size); + + var field = _react2.default.createElement(FieldComponent, _extends({ + 'aria-label': this.ariaLabel(), + className: fieldClasses, + id: this.id, + ref: fieldRef, + rows: _rows, + type: multiline ? undefined : type + }, fieldProps)); return _react2.default.createElement( 'div', @@ -99,13 +162,7 @@ var TextField = exports.TextField = function (_React$PureComponent) { }, label ), - _react2.default.createElement(FieldComponent, _extends({ - className: fieldClasses, - id: this.id, - ref: fieldRef, - rows: _rows, - type: multiline ? undefined : type - }, fieldProps)) + this.renderFieldAndMask(field, mask) ); } }]); @@ -118,6 +175,11 @@ TextField.defaultProps = { }; TextField.propTypes = { + /** + * Apply an `aria-label` to the text field to provide additional + * context to assistive devices. + */ + ariaLabel: _propTypes2.default.string, /** * Additional classes to be added to the root `div` element */ @@ -161,6 +223,12 @@ TextField.propTypes = { * Additional classes to be added to the label */ labelClassName: _propTypes2.default.string, + /** + * Apply formatting to the field that's unique to the value + * you expect to be entered. Depending on the mask, the + * field's appearance and functionality may be affected. + */ + mask: _propTypes2.default.oneOf(['currency']), /** * `max` HTML input attribute */ @@ -170,7 +238,7 @@ TextField.propTypes = { */ min: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string]), /** - * Whether or not the textfield is a multiline textfield + * Whether or not the text field is a multiline text field */ multiline: _propTypes2.default.bool, name: _propTypes2.default.string.isRequired, @@ -181,6 +249,10 @@ TextField.propTypes = { * applicable if this is a multiline field. */ rows: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string]), + /** + * Set the max-width of the input either to `'small'` or `'medium'`. + */ + size: _propTypes2.default.oneOf(['small', 'medium']), /** * Any valid `input` [type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). */ diff --git a/packages/core/src/components/ChoiceList/ChoiceList.example.jsx b/packages/core/src/components/ChoiceList/ChoiceList.example.jsx index 0a4e985ec8..c1755c09b0 100644 --- a/packages/core/src/components/ChoiceList/ChoiceList.example.jsx +++ b/packages/core/src/components/ChoiceList/ChoiceList.example.jsx @@ -30,6 +30,27 @@ ReactDOM.render( label="Select example" name="select_choices_field" /> + + , document.getElementById('js-example') ); @@ -70,5 +91,6 @@ function choices() { function options() { const options = generateChoices(8); options[1].defaultChecked = true; + console.log(options); return options; } diff --git a/packages/core/src/components/ChoiceList/ChoiceList.jsx b/packages/core/src/components/ChoiceList/ChoiceList.jsx index f39329115f..83aff1088b 100644 --- a/packages/core/src/components/ChoiceList/ChoiceList.jsx +++ b/packages/core/src/components/ChoiceList/ChoiceList.jsx @@ -81,6 +81,7 @@ export class ChoiceList extends React.PureComponent { * @param {array} options - components */ select(selectProps, options) { + const classes = this.props.size && `ds-c-field--${this.props.size}`; return ( {options} @@ -202,6 +204,10 @@ ChoiceList.propTypes = { name: PropTypes.string.isRequired, onBlur: PropTypes.func, onChange: PropTypes.func, + /** + * If the component renders a select, set the max-width of the input either to `'small'` or `'medium'`. + */ + size: PropTypes.oneOf(['small', 'medium']), /** * You can manually set the `type` if you prefer things to be less magical. * Otherwise, the type will be inferred by the other `props`, based diff --git a/packages/core/src/components/ChoiceList/ChoiceList.test.jsx b/packages/core/src/components/ChoiceList/ChoiceList.test.jsx index e1d935e3b5..7e02f59c7a 100644 --- a/packages/core/src/components/ChoiceList/ChoiceList.test.jsx +++ b/packages/core/src/components/ChoiceList/ChoiceList.test.jsx @@ -244,6 +244,15 @@ describe('ChoiceList', () => { expect(selectId).toBe(labelId); }); + it('adds size classes to root element', () => { + props.size = 'small'; + const data = shallowRender(props); + + expect(data.wrapper.find('Select').hasClass('ds-c-field--small')).toBe( + true + ); + }); + it('is disabled', () => { props.disabled = true; const data = shallowRender(props); diff --git a/packages/core/src/components/ChoiceList/Select.example.jsx b/packages/core/src/components/ChoiceList/Select.example.jsx index 2ed7020df8..d278897aaa 100644 --- a/packages/core/src/components/ChoiceList/Select.example.jsx +++ b/packages/core/src/components/ChoiceList/Select.example.jsx @@ -3,14 +3,31 @@ import ReactDOM from 'react-dom'; import Select from './Select'; ReactDOM.render( - - Option 1 - Option 2 - Option 3 - Option 4 - Option 5 - Option 6 - Option 7 - , + + + Option 1 + Option 2 + Option 3 + Option 4 + Option 5 + Option 6 + Option 7 + + Small size modifier + + Jr. + Sr. + + Medium size modifier + + Option 1 + Option 2 + Option 3 + Option 4 + Option 5 + Option 6 + Option 7 + + , document.getElementById('js-example') ); diff --git a/packages/core/src/components/ChoiceList/Select.jsx b/packages/core/src/components/ChoiceList/Select.jsx index 4131da802c..0ef3efb9e5 100644 --- a/packages/core/src/components/ChoiceList/Select.jsx +++ b/packages/core/src/components/ChoiceList/Select.jsx @@ -17,6 +17,7 @@ export const Select = function(props) { className, id, inversed, + size, ...selectProps } = props; /* eslint-enable prefer-const */ @@ -24,7 +25,8 @@ export const Select = function(props) { const classes = classNames( 'ds-c-field', { 'ds-c-field--inverse': inversed }, - className + className, + size && `ds-c-field--${size}` ); if (!id) { @@ -68,9 +70,7 @@ Select.propTypes = { if (props[propName]) { /* eslint-disable quotes */ return new Error( - `'${propName}' supplied to '${ - componentName - }'. [A11Y]: Users often don’t` + + `'${propName}' supplied to '${componentName}'. [A11Y]: Users often don’t` + ` understand how to select multiple items from dropdowns. Use checkboxes instead.` ); /* eslint-enable */ @@ -82,6 +82,10 @@ Select.propTypes = { name: PropTypes.string.isRequired, onBlur: PropTypes.func, onChange: PropTypes.func, + /** + * Set the max-width of the input either to `'small'` or `'medium'`. + */ + size: PropTypes.oneOf(['small', 'medium']), /** * Sets the field's `value`. Use this in combination with `onChange` * for a controlled component; otherwise, set `defaultValue`. diff --git a/packages/core/src/components/ChoiceList/Select.test.jsx b/packages/core/src/components/ChoiceList/Select.test.jsx index c2791cf239..008f8e248c 100644 --- a/packages/core/src/components/ChoiceList/Select.test.jsx +++ b/packages/core/src/components/ChoiceList/Select.test.jsx @@ -90,6 +90,14 @@ describe('Select', () => { expect(data.wrapper.hasClass('ds-c-field')).toBe(true); }); + it('adds size classes to root element', () => { + const mediumData = shallowRender({ size: 'medium' }); + const smallData = shallowRender({ size: 'small' }); + + expect(mediumData.wrapper.hasClass('ds-c-field--medium')).toBe(true); + expect(smallData.wrapper.hasClass('ds-c-field--small')).toBe(true); + }); + it('is disabled', () => { const data = shallowRender({ disabled: true }); diff --git a/packages/core/src/components/TextField/TextField.example.jsx b/packages/core/src/components/TextField/TextField.example.jsx index 40235c19c1..b189a06d1f 100644 --- a/packages/core/src/components/TextField/TextField.example.jsx +++ b/packages/core/src/components/TextField/TextField.example.jsx @@ -11,6 +11,12 @@ ReactDOM.render( name="single_example" requirementLabel="Optional" /> + + First name +Small size modifier + +Medium size modifier + Last name @@ -61,6 +65,14 @@ Style guide: components.text-field } } +.ds-c-field--small { + max-width: $input-small-width; +} + +.ds-c-field--medium { + max-width: $input-medium-width; +} + .ds-c-field, .ds-c-field-mask { font-family: $font-sans; diff --git a/packages/core/src/components/TextField/TextField.test.jsx b/packages/core/src/components/TextField/TextField.test.jsx index 1806bef8a0..0e033bcd8a 100644 --- a/packages/core/src/components/TextField/TextField.test.jsx +++ b/packages/core/src/components/TextField/TextField.test.jsx @@ -124,6 +124,16 @@ describe('TextField', function() { expect(data.wrapper.find('FormLabel').hasClass('bar')).toBe(true); }); + it('adds size classes to input', () => { + const mediumData = render({ size: 'medium' }); + const mediumField = mediumData.wrapper.find('.ds-c-field').first(); + const smallData = render({ size: 'small' }); + const smallField = smallData.wrapper.find('.ds-c-field').first(); + + expect(mediumField.hasClass('ds-c-field--medium')).toBe(true); + expect(smallField.hasClass('ds-c-field--small')).toBe(true); + }); + it('adds min/max input attributes', () => { const data = render({ max: 10, diff --git a/packages/core/src/components/TextField/__snapshots__/TextField.test.jsx.snap b/packages/core/src/components/TextField/__snapshots__/TextField.test.jsx.snap index 0208af2cd1..3fe41017e1 100644 --- a/packages/core/src/components/TextField/__snapshots__/TextField.test.jsx.snap +++ b/packages/core/src/components/TextField/__snapshots__/TextField.test.jsx.snap @@ -6,7 +6,7 @@ exports[`TextField masked renders currency mask 1`] = ` > Foo @@ -21,7 +21,7 @@ exports[`TextField masked renders currency mask 1`] = ` diff --git a/packages/support/src/settings/_variables.forms.scss b/packages/support/src/settings/_variables.forms.scss index 7a11bbf6ef..fb56ea9a31 100644 --- a/packages/support/src/settings/_variables.forms.scss +++ b/packages/support/src/settings/_variables.forms.scss @@ -18,4 +18,6 @@ $button-primary-bg--active: $color-primary-darkest !default; // These variables apply to form fields (input, select, textarea, button) $input-line-height: 1.3 !default; $input-border-width: 2px !default; +$input-small-width: 6em !default; +$input-medium-width: 12em !default; $input-padding: $spacer-1 !default;