diff --git a/packages/netlify-cms-core/src/constants/defaultPhrases.js b/packages/netlify-cms-core/src/constants/defaultPhrases.js index 79114de0..ebae7694 100644 --- a/packages/netlify-cms-core/src/constants/defaultPhrases.js +++ b/packages/netlify-cms-core/src/constants/defaultPhrases.js @@ -39,6 +39,9 @@ export function getPhrases() { required: '%{fieldLabel} is required.', regexPattern: "%{fieldLabel} didn't match the pattern: %{pattern}.", processing: '%{fieldLabel} is processing.', + range: '%{fieldLabel} must be between %{minValue} and %{maxValue}.', + min: '%{fieldLabel} must be at least %{minValue}.', + max: '%{fieldLabel} must be %{maxValue} or less.', }, }, editor: { diff --git a/packages/netlify-cms-core/src/constants/validationErrorTypes.js b/packages/netlify-cms-core/src/constants/validationErrorTypes.js index 2305209c..b29adddb 100644 --- a/packages/netlify-cms-core/src/constants/validationErrorTypes.js +++ b/packages/netlify-cms-core/src/constants/validationErrorTypes.js @@ -1,5 +1,6 @@ export default { PRESENCE: 'PRESENCE', PATTERN: 'PATTERN', + RANGE: 'RANGE', CUSTOM: 'CUSTOM', }; diff --git a/packages/netlify-cms-widget-number/src/NumberControl.js b/packages/netlify-cms-widget-number/src/NumberControl.js index c926611e..be6e7c61 100644 --- a/packages/netlify-cms-widget-number/src/NumberControl.js +++ b/packages/netlify-cms-widget-number/src/NumberControl.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import ValidationErrorTypes from 'netlify-cms-core/src/constants/validationErrorTypes'; export default class NumberControl extends React.Component { static propTypes = { @@ -15,6 +16,7 @@ export default class NumberControl extends React.Component { step: PropTypes.number, min: PropTypes.number, max: PropTypes.number, + t: PropTypes.func.isRequired, }; static defaultProps = { @@ -33,6 +35,54 @@ export default class NumberControl extends React.Component { } }; + isValid = () => { + const { field, value, t } = this.props; + const hasPattern = !!field.get('pattern', false); + const min = field.get('min', false); + const max = field.get('max', false); + let error; + + // Pattern overrides min/max logic always: + if (hasPattern) { + return true; + } + + switch (true) { + case min !== false && max !== false && (value < min || value > max): + error = { + type: ValidationErrorTypes.RANGE, + message: t('editor.editorControlPane.widget.range', { + fieldLabel: field.get('label', field.get('name')), + minValue: min, + maxValue: max, + }), + }; + break; + case min !== false && value < min: + error = { + type: ValidationErrorTypes.RANGE, + message: t('editor.editorControlPane.widget.min', { + fieldLabel: field.get('label', field.get('name')), + minValue: min, + }), + }; + break; + case max !== false && value > max: + error = { + type: ValidationErrorTypes.RANGE, + message: t('editor.editorControlPane.widget.max', { + fieldLabel: field.get('label', field.get('name')), + maxValue: max, + }), + }; + break; + default: + return true; + } + + return { error }; + }; + render() { const { field, value, classNameWrapper, forID, setActiveStyle, setInactiveStyle } = this.props; const min = field.get('min', ''); diff --git a/website/content/docs/widgets/number.md b/website/content/docs/widgets/number.md index 5b9f31ee..88ce191c 100644 --- a/website/content/docs/widgets/number.md +++ b/website/content/docs/widgets/number.md @@ -13,6 +13,7 @@ The number widget uses an HTML number input, saving the value as a string, integ - `valueType`: accepts `int` or `float`; any other value results in saving as a string - `min`: accepts a number for minimum value accepted; unset by default - `max`: accepts a number for maximum value accepted; unset by default + - `step`: accepts a number for stepping up/down values in the input; 1 by default - **Example:** ```yaml - label: "Puppy Count" @@ -22,4 +23,5 @@ The number widget uses an HTML number input, saving the value as a string, integ valueType: "int" min: 1 max: 101 + step: 2 ```