├── .npmignore ├── .gitignore ├── .editorconfig ├── CHANGELOG.md ├── .jshintrc ├── bower.json ├── package.json ├── react-tagsinput.css ├── example └── index.html ├── README.md └── react-tagsinput.js /.npmignore: -------------------------------------------------------------------------------- 1 | example -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | components 3 | node_modules 4 | bower_components 5 | *.log 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.2.0 (2015-01-07) 2 | 3 | * Add [UMD](https://github.com/umdjs/umd) support 4 | * Get rid of warnings `"Something is calling a React component directly. Use a factory or JSX instead"` 5 | * Get rid of deprecated `transferPropsTo` 6 | * Update CSS styles 7 | * Add usage example 8 | 9 | ### 0.1.1 (2014-10-16) 10 | 11 | * `validate` prop which is a function that returns true if a tag is valid. 12 | * Add tag when input is blurred. 13 | * `addKeys` prop which defines key code that add a tag, default is Tab (9) and Enter (13) -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "define", 4 | "module", 5 | "require" 6 | ], 7 | 8 | "asi" : false, 9 | "bitwise": true, 10 | "boss": false, 11 | "browser": true, 12 | "curly" : true, 13 | "debug": false, 14 | "devel": false, 15 | "eqeqeq": true, 16 | "evil": false, 17 | "expr": true, 18 | "forin": false, 19 | "immed": true, 20 | "jquery" : false, 21 | "latedef" : false, 22 | "laxbreak": true, 23 | "laxcomma": true, 24 | "multistr": true, 25 | "newcap": true, 26 | "noarg": true, 27 | "noempty": false, 28 | "onevar": false, 29 | "plusplus": false, 30 | "quotmark": "double", 31 | "regexp": false, 32 | "strict": false, 33 | "sub": false, 34 | "trailing" : true, 35 | "undef": true, 36 | "unused": "vars" 37 | } 38 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tagsinput", 3 | "main": [ 4 | "react-tagsinput.js", 5 | "react-tagsinput.css" 6 | ], 7 | "version": "0.2.2", 8 | "homepage": "https://github.com/olahol/react-tagsinput", 9 | "description": "Simple react.js component for inputing tags", 10 | "keywords": [ 11 | "react", 12 | "tags", 13 | "input", 14 | "component", 15 | "javascript", 16 | "react-component" 17 | ], 18 | "authors": [ 19 | "Ola Holmström", 20 | "Dmitri Voronianski " 21 | ], 22 | "license": "MIT", 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules", 26 | "bower_components", 27 | "test", 28 | "tests", 29 | "example" 30 | ], 31 | "dependencies": { 32 | "react": "~0.12.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tagsinput", 3 | "version": "0.2.2", 4 | "description": "Simple react.js component for inputing tags", 5 | "main": "react-tagsinput.js", 6 | "dependencies": { 7 | "react": "^0.12.2" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/olahol/react-tagsinput" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "tags", 20 | "input", 21 | "component", 22 | "javascript", 23 | "react-component" 24 | ], 25 | "author": "Ola Holmström", 26 | "contributors": [ 27 | "Dmitri Voronianski " 28 | ], 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/olahol/react-tagsinput/issues" 32 | }, 33 | "homepage": "https://github.com/olahol/react-tagsinput" 34 | } 35 | -------------------------------------------------------------------------------- /react-tagsinput.css: -------------------------------------------------------------------------------- 1 | .react-tagsinput { 2 | border: 1px solid #ccc; 3 | background: #fff; 4 | padding: 10px; 5 | overflow-y: auto; 6 | border-radius: 3px; 7 | } 8 | 9 | .react-tagsinput-tag { 10 | display: block; 11 | border: 1px solid #a5d24a; 12 | background: #cde69c; 13 | color: #638421; 14 | font-size: 12px; 15 | font-family: 'Helvetica Neue', 'Arial', sans-serif; 16 | float: left; 17 | padding: 5px; 18 | margin-right: 5px; 19 | margin-bottom: 5px; 20 | text-decoration: none; 21 | border-radius: 2px; 22 | } 23 | 24 | .react-tagsinput-invalid { 25 | background: #FBD8DB !important; 26 | color: #90111A !important; 27 | } 28 | 29 | .react-tagsinput-remove { 30 | font-weight: bold; 31 | color: #638421; 32 | text-decoration: none; 33 | font-size: 11px; 34 | cursor: pointer; 35 | } 36 | 37 | .react-tagsinput-remove:before { 38 | content: "x"; 39 | } 40 | 41 | .react-tagsinput-input { 42 | background: transparent; 43 | color: #777; 44 | border: 0; 45 | font-size: 13px; 46 | font-family: 'Helvetica Neue', 'Arial', sans-serif; 47 | padding: 5px; 48 | margin: 0; 49 | width: 80px; 50 | outline: none; 51 | } 52 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | react-tagsinput example (without JSX) 5 | 6 | 37 | 38 | 39 |
40 | 41 | 42 | 43 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-tagsinput 2 | 3 | > Simple [React](http://facebook.github.io/react/index.html) component for inputing tags. 4 | 5 | ### [Demo](https://olahol.github.io/react-tagsinput) 6 | 7 | 8 | 9 | 10 | ## Install 11 | 12 | ```bash 13 | npm install react-tagsinput --save 14 | ``` 15 | 16 | or 17 | 18 | ```bash 19 | bower install react-tagsinput --save 20 | ``` 21 | 22 | ## Example 23 | 24 | ```javascript 25 | var TagsInput = require('./react-tagsinput'); 26 | 27 | var App = React.createClass({ 28 | saveTags: function () { 29 | console.log('tags: ', this.refs.tags.getTags().join(', ')); 30 | }, 31 | 32 | render: function () { 33 | return ( 34 |
35 | 36 | 37 |
38 | ); 39 | } 40 | }); 41 | ``` 42 | 43 | ## API 44 | 45 | ### Props 46 | 47 | ##### tags 48 | 49 | Tags to preloaded, default is `[]`. 50 | 51 | ##### placeholder 52 | 53 | Placeholder text for the add a tag input, default is "Add a tag". 54 | 55 | ##### validate 56 | 57 | A function which returns true if a tag is valid, default function returns 58 | true for every string but the empty string. 59 | 60 | ##### addKeys 61 | 62 | An array of key codes that add a tag, default is `[9, 13]` (Tab and Enter). 63 | 64 | ##### onChange 65 | 66 | Callback when the tag input changes, the argument is an array of the current tags. 67 | 68 | ##### onTagAdd 69 | 70 | Callback when a tag is added, argument is the added tag. 71 | 72 | ##### onTagRemove 73 | 74 | Callback when a tag is removed, argument is the removed tag. 75 | 76 | ### Methods 77 | 78 | ##### getTags() 79 | 80 | Returns an array of the current tags. 81 | 82 | ## Styles 83 | 84 | Look at `react-tagsinput.css` for an idea on how to style this component. 85 | 86 | --- 87 | 88 | MIT Licensed 89 | 90 | 91 | -------------------------------------------------------------------------------- /react-tagsinput.js: -------------------------------------------------------------------------------- 1 | ;(function (root, factory) { 2 | if (typeof module !== "undefined" && module.exports) { 3 | module.exports = factory(require("react")); 4 | } else if (typeof define === "function" && define.amd) { 5 | define(["react"], factory); 6 | } else { 7 | root.ReactTagsInput = factory(root.React); 8 | } 9 | })(this, function (React) { 10 | "use strict"; 11 | 12 | var Input = React.createClass({ 13 | render: function () { 14 | var inputClass = this.props.invalid ? 15 | "react-tagsinput-input react-tagsinput-invalid" : 16 | "react-tagsinput-input"; 17 | 18 | return React.createElement("input", 19 | // https://gist.github.com/sebmarkbage/a6e220b7097eb3c79ab7 20 | // avoid dependency on ES6's `Object.assign()` 21 | React.__spread({}, this.props, { 22 | type: "text" 23 | , className: inputClass 24 | , placeholder: this.props.placeholder 25 | }) 26 | ); 27 | } 28 | }); 29 | 30 | var Tag = React.createClass({ 31 | render: function () { 32 | return ( 33 | React.createElement("span", { 34 | className: "react-tagsinput-tag" 35 | }, this.props.tag + " ", React.createElement("a", { 36 | onClick: this.props.remove 37 | , className: "react-tagsinput-remove" 38 | })) 39 | ); 40 | } 41 | }); 42 | 43 | var TagsInput = React.createClass({ 44 | getDefaultProps: function () { 45 | return { 46 | tags: [] 47 | , placeholder: "Add a tag" 48 | , validate: function (tag) { return tag !== ""; } 49 | , addKeys: [13, 9] 50 | , removeKeys: [8] 51 | , onTagAdd: function () { } 52 | , onTagRemove: function () { } 53 | , onChange: function () { } 54 | }; 55 | } 56 | 57 | , getInitialState: function () { 58 | return { 59 | tags: [] 60 | , tag: "" 61 | , invalid: false 62 | }; 63 | } 64 | 65 | , componentWillMount: function () { 66 | this.setState({ 67 | tags: this.props.tags.slice(0) 68 | }); 69 | } 70 | 71 | , getTags: function () { 72 | return this.state.tags; 73 | } 74 | 75 | , addTag: function () { 76 | var tag = this.state.tag.trim(); 77 | 78 | if (this.state.tags.indexOf(tag) !== -1 || !this.props.validate(tag)) { 79 | return this.setState({ 80 | invalid: true 81 | }); 82 | } 83 | 84 | this.setState({ 85 | tags: this.state.tags.concat([tag]) 86 | , tag: "" 87 | , invalid: false 88 | }, function () { 89 | this.props.onTagAdd(tag); 90 | this.props.onChange(this.state.tags); 91 | this.inputFocus(); 92 | }); 93 | } 94 | 95 | , removeTag: function (i) { 96 | var tags = this.state.tags.slice(0); 97 | var tag = tags.splice(i, 1); 98 | this.setState({ 99 | tags: tags 100 | , invalid: false 101 | }, function () { 102 | this.props.onTagRemove(tag[0]); 103 | this.props.onChange(this.state.tags); 104 | }); 105 | } 106 | 107 | , onKeyDown: function (e) { 108 | var add = this.props.addKeys.indexOf(e.keyCode) !== -1 109 | , remove = this.props.removeKeys.indexOf(e.keyCode) !== -1; 110 | 111 | if (add) { 112 | e.preventDefault(); 113 | this.addTag(); 114 | } 115 | 116 | if (remove && this.state.tags.length > 0 && this.state.tag === "") { 117 | this.removeTag(this.state.tags.length - 1); 118 | } 119 | } 120 | 121 | , onChange: function (e) { 122 | this.setState({ 123 | tag: e.target.value 124 | , invalid: false 125 | }); 126 | } 127 | 128 | , onBlur: function (e) { 129 | if (this.state.tag !== "" && !this.state.invalid) { 130 | this.addTag(); 131 | } 132 | } 133 | 134 | , inputFocus: function () { 135 | this.refs.input.getDOMNode().focus(); 136 | } 137 | 138 | , render: function() { 139 | var tagNodes = this.state.tags.map(function (tag, i) { 140 | return React.createElement(Tag, { 141 | key: i 142 | , tag: tag 143 | , remove: this.removeTag.bind(null, i) 144 | }); 145 | }.bind(this)); 146 | 147 | return ( 148 | React.createElement("div", { 149 | className: "react-tagsinput" 150 | }, tagNodes, React.createElement(Input, { 151 | ref: "input" 152 | , placeholder: this.props.placeholder 153 | , value: this.state.tag 154 | , invalid: this.state.invalid 155 | , onKeyDown: this.onKeyDown 156 | , onChange: this.onChange 157 | , onBlur: this.onBlur 158 | })) 159 | ); 160 | } 161 | }); 162 | 163 | return TagsInput; 164 | }); 165 | --------------------------------------------------------------------------------