diff --git a/README.md b/README.md index ebfedd7..226665a 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ ReactDOM.render(, document.getElementById('app')) - [`tagComponent`](#tagcomponent-optional) - [`suggestionComponent`](#suggestioncomponent-optional) - [`inputAttributes`](#inputAttributes-optional) +- [minTags](#minTags-optional) +- [maxTags](#maxTags-optional) #### id (optional) @@ -315,6 +317,20 @@ function SuggestionComponent({ item, query }) { An object containing additional attributes that will be applied to the text input. _Please note_ that this prop cannot overwrite existing attributes, it can only add new ones. Defaults to `{}`. +#### minTags (optional) + +An integer representing the minimum number of tags that should be selected +by the user. If the amount of selected tags is lower, the input will be +considered to be in an invalid state. + + +#### maxTags (optional) + +An integer representing the maximum number of tags that should be selected +by the user. If the amount of selected tags is higher, the input will be +considered to be in an invalid state. + + ### API By adding a `ref` to any instances of this component you can access its API methods. @@ -332,6 +348,12 @@ Removes a tag from the list of selected tags. This will trigger the delete callb Clears the input and current query. +#### `isValid()` + +Return the validation state of the input. Note that this function will return +`true` even in the case you haven't specified `minTags` and `maxTags`. + + ### Styling It is possible to customize the appearance of the component, the included styles found in `/example/styles.css` are only an example. diff --git a/example/index.html b/example/index.html index 449af7b..c1a9211 100644 --- a/example/index.html +++ b/example/index.html @@ -82,6 +82,9 @@

Country Selector


Custom tags

+
+

Validation

+
diff --git a/example/main.js b/example/main.js index edfdf3b..f6c11f2 100644 --- a/example/main.js +++ b/example/main.js @@ -105,3 +105,59 @@ class CustomTags extends React.Component { } ReactDOM.render(, document.getElementById('demo-2')) + +/** + * Demo 3 - Validation + */ +class Validation extends React.Component { + constructor (props) { + super(props) + + this.state = { + tags: [], + suggestions: [] + } + + this.reactTags = React.createRef() + } + + onDelete (i) { + const tags = this.state.tags.slice(0) + tags.splice(i, 1) + this.setState({ tags }) + } + + onAddition (tag) { + const tags = [].concat(this.state.tags, tag) + this.setState({ tags }) + } + + onValidate (tag) { + return /^[a-z]{3,12}$/i.test(tag.name); + } + + render () { + return ( + <> +

Enter new tags meeting the requirements below:

+ +

Tags must be 3–12 characters in length and only contain the letters A-Z

+

Output:

+
{JSON.stringify(this.state.tags, null, 2)}
+ + ) + } +} + +ReactDOM.render(, document.getElementById('demo-3')) diff --git a/example/styles.css b/example/styles.css index 46646df..5321fb3 100644 --- a/example/styles.css +++ b/example/styles.css @@ -36,6 +36,14 @@ cursor: text; } +.react-tags.react-tags__valid { + border: 1px solid green; +} + +.react-tags.react-tags__invalid { + border: 1px solid red; +} + .react-tags.is-focused { border-color: #B1B1B1; } diff --git a/lib/ReactTags.js b/lib/ReactTags.js index 362317c..196e047 100644 --- a/lib/ReactTags.js +++ b/lib/ReactTags.js @@ -17,6 +17,8 @@ const KEYS = { const CLASS_NAMES = { root: 'react-tags', + rootValid: 'react-tags__valid', + rootInvalid: 'react-tags__invalid', rootFocused: 'is-focused', selected: 'react-tags__selected', selectedTag: 'react-tags__selected-tag', @@ -108,7 +110,8 @@ class ReactTags extends React.Component { this.state = { query: '', focused: false, - index: -1 + index: -1, + validationState: this._getValidationState() } this.inputEventHandlers = { @@ -243,6 +246,35 @@ class ReactTags extends React.Component { }) } + isValid () { + return this.state.validationState + } + + _getValidationState () { + const tl = this.props.tags.length + + let validationState = true + if (this.props.minTags && tl < this.props.minTags) { + validationState = false + } + + if (this.props.maxTags && tl > this.props.maxTags) { + validationState = false + } + + return validationState + } + + componentDidUpdate (prevProps) { + const validationState = this._getValidationState() + + if (this.state.validationState !== validationState) { + this.setState({ + validationState + }) + } + } + render () { const TagComponent = this.props.tagComponent || Tag @@ -251,6 +283,7 @@ class ReactTags extends React.Component { const rootClasses = [classes.root] this.state.focused && rootClasses.push(classes.rootFocused) + rootClasses.push(this.state.validationState ? classes.rootValid : classes.rootInvalid) return (
@@ -329,7 +362,9 @@ ReactTags.defaultProps = { addOnBlur: false, tagComponent: null, suggestionComponent: null, - inputAttributes: {} + inputAttributes: {}, + minTags: null, + maxTags: null } ReactTags.propTypes = { @@ -365,7 +400,9 @@ ReactTags.propTypes = { PropTypes.func, PropTypes.element ]), - inputAttributes: PropTypes.object + inputAttributes: PropTypes.object, + minTags: PropTypes.number, + maxTags: PropTypes.number } export default ReactTags