From d5fb544b868e9e261ec54e75a03ca7d7841aa374 Mon Sep 17 00:00:00 2001 From: jfusco Date: Sat, 30 Jul 2016 17:59:02 -0400 Subject: [PATCH] Adding finishing touches on unit tests for V1 - adding maxTags options - changing name of allowDupes to uniqueTags - changing name of events to be prefixed with 'on' - adding test task to the build process - refacotring SCSS into multip[le files based on core and components - now destructuring properties in methods in tags component - changing name of delimiters to addKeys. --- .eslintrc | 8 +- README.md | 61 +++++++------- __tests__/Tag-test.js | 4 +- __tests__/Tags-test.js | 79 ++++++++++++++---- dist-components/Tag.js | 4 +- dist-components/Tags.js | 140 ++++++++++++++++++++------------ dist/react-tags.css | 93 +++++++++++---------- dist/react-tags.min.css | 2 +- dist/react-tags.min.js | 2 +- gulp/tasks/clean.js | 9 ++ gulpfile.babel.js | 7 +- package.json | 7 +- src/js/Tag.js | 4 +- src/js/Tags.js | 121 ++++++++++++++++----------- src/scss/_base.scss | 51 ++++++++++++ src/scss/_colorpalette.scss | 2 +- src/scss/_variables.scss | 2 +- src/scss/components/_input.scss | 35 ++++++++ src/scss/components/_tag.scss | 45 ++++++++++ src/scss/components/_tags.scss | 12 +++ src/scss/react-tags.scss | 136 ++----------------------------- 21 files changed, 488 insertions(+), 336 deletions(-) create mode 100644 gulp/tasks/clean.js create mode 100644 src/scss/_base.scss create mode 100644 src/scss/components/_input.scss create mode 100644 src/scss/components/_tag.scss create mode 100644 src/scss/components/_tags.scss diff --git a/.eslintrc b/.eslintrc index b200980..bd7520c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -50,7 +50,7 @@ "brace-style": [2, "1tbs", { "allowSingleLine": true }], - "object-curly-spacing": [2, "never", { + "object-curly-spacing": [2, "always", { "objectsInObjects": true, "arraysInObjects": true }], @@ -78,10 +78,6 @@ }] }, "globals": { - "console": true, - "alert": true, - "document": true, - "window": true, - "WeakMap": true + "console": true } } diff --git a/README.md b/README.md index 4dc7c7c..3a8ae3d 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ class Application extends Component{ + onChange={this.onTagsChange} /> ); } @@ -57,14 +57,14 @@ render(, document.getElementById('application')); #### Options * **[`initialTags`](#initialTags)** * **[`placeholder`](#placeholder)** -* **[`delimiters`](#delimiters)** -* **[`change`](#change)** -* **[`added`](#added)** -* **[`removed`](#removed)** +* **[`addKeys`](#addKeys)** +* **[`onChange`](#onChange)** +* **[`onAdded`](#onAdded)** +* **[`onRemoved`](#onRemoved)** +* **[`maxTags`](#maxTags)** * **[`readOnly`](#readOnly)** -* **[`removeTagWithDeleteKey`](#removeTagWithDeleteKey)** * **[`removeTagIcon`](#removeTagIcon)** -* **[`allowDupes`](#allowDupes)** +* **[`uniqueTags`](#allowDupes)** * **[`id`](#id)** @@ -83,46 +83,53 @@ A `string` used as placeholder text in the tags input field ``` - -##### delimiters ~ optional ~ default `[13, 9, 32]` + +##### addKeys ~ optional ~ default `[13, 9, 32]` An `array` of keyCodes used to tell the tags component which delimiter to use to add a tag [Here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode) is more info and a list of keyCodes ```js - + ``` - -##### change ~ optional + +##### onChange ~ optional A `function` fired anytime there is a change - returns the new `array` of tags ```js onTagsChange(tags){ console.log(`new tags: ${tags}`); } - + ``` - -##### added ~ optional + +##### onAdded ~ optional A `function` fired when a new tag is added - returns a `string` of the new tag ```js onTagAdded(tag){ console.log(`new tag: ${tags}`); } - + ``` - -##### removed ~ optional + +##### onRemoved ~ optional A `function` fired when a new tag is deleted - returns a `string` of the tag that was deleted ```js onTagRemoved(tag){ console.log(`deleted tag: ${tag}`); } - + +``` + + +##### maxTags ~ optional ~ default `-1` (infinite) +An `integer` representing the maximum number of tags that are allowed to be added +```js + ``` @@ -132,13 +139,6 @@ A `boolean` that sets the tag component to read only mode. No adding or removing ``` - -##### removeTagWithDeleteKey ~ optional ~ default `true` -A `boolean` that allows tags to be removed with the delete key when the input field is empty -```js - -``` - ##### removeTagIcon ~ optional ~ default `"x"` The `element` to be used for the delete icon @@ -152,11 +152,12 @@ const removeIcon = () => { ``` - -##### allowDupes ~ optional ~ default `false` -A `boolean` that allows tags to be added more than once + +##### uniqueTags ~ optional ~ default `false` +A `boolean` that allows the same tag to be added more than once ```js - +//-- Only allow unique tags to be added + ``` diff --git a/__tests__/Tag-test.js b/__tests__/Tag-test.js index 629bdd7..421dee5 100644 --- a/__tests__/Tag-test.js +++ b/__tests__/Tag-test.js @@ -137,7 +137,7 @@ describe('Tag - "removeTagIcon"', () => { }); }); -describe('Tag - "removeTag"', () => { +describe('Tag - "onRemoveTag"', () => { it('should be called when clicking the remove icon', () => { const onRemoveClick = jest.genMockFunction(); @@ -145,7 +145,7 @@ describe('Tag - "removeTag"', () => {
+ onRemoveTag={onRemoveClick} />
); diff --git a/__tests__/Tags-test.js b/__tests__/Tags-test.js index b6f1017..217465e 100644 --- a/__tests__/Tags-test.js +++ b/__tests__/Tags-test.js @@ -40,11 +40,18 @@ describe('Tags', () => { }); it('should render custom placeholder if provided', () => { - expect(tags.props.children[1].type).toBe('input'); - expect(tags.props.children[1].props.placeholder).toBe('Custom placeholder text'); + const input = tags.props.children[1]; + + expect(input.type).toBe('input'); + expect(input.props.placeholder).toBe('Custom placeholder text'); }); -}); + it('should set aria-label', () => { + const input = tags.props.children[1]; + + expect(input.props['aria-label']).toBe('Custom placeholder text'); + }); +}); describe('Tags - "initialTags"', () => { it('should render tags (shallow render)', () => { @@ -109,7 +116,7 @@ describe('Tags - "readOnly"', () => { }); -describe('Tags - "delimiters"', () => { +describe('Tags - "addKeys"', () => { let tags, input, tagContainer; @@ -118,7 +125,7 @@ describe('Tags - "delimiters"', () => { tags = renderIntoDocument( + addKeys={[13, 9, 32, 188]} /> ); const renderedDOM = findDOMNode(tags); @@ -177,14 +184,16 @@ describe('Tags - events', () => { const onTagAdded = jest.genMockFunction(); const onTagRemoved = jest.genMockFunction(); const onTagsChanged = jest.genMockFunction(); + const onTagsInputChange = jest.genMockFunction(); beforeEach(() => { tags = renderIntoDocument( + onAdded={onTagAdded} + onRemoved={onTagRemoved} + onChange={onTagsChanged} + onInputChange={onTagsInputChange} /> ); const renderedDOM = findDOMNode(tags); @@ -206,11 +215,11 @@ describe('Tags - events', () => { Simulate.keyDown(input, {key: 'Enter', keyCode: 13, which: 13}); }); - it('should call the "added" event and return the new tag', () => { + it('should call the "onAdded" event and return the new tag', () => { expect(onTagAdded).toBeCalledWith(TEST_TAGS[0]); }); - it('should call the "changed" event and return the new tags list as an array', () => { + it('should call the "onChange" event and return the new tags list as an array', () => { const newArray = TEST_TAGS.concat('foo'); expect(onTagsChanged).toBeCalledWith(newArray); @@ -225,14 +234,26 @@ describe('Tags - events', () => { Simulate.keyDown(input, {key: 'Delete', keyCode: 8, which: 8}); }); - it('should call the "removed" event and return the tag that was removed', () => { + it('should call the "onRemoved" event and return the tag that was removed', () => { expect(onTagRemoved).toBeCalledWith(TEST_TAGS[1]); }); - it('should call the "changed" event and return the new tags list as an array', () => { + it('should call the "onChange" event and return the new tags list as an array', () => { expect(onTagsChanged).toBeCalledWith([TEST_TAGS[0]]); }); }); + + describe('when typing in the input field', () => { + it('should call the "inputChange" and return the word as you\'re typing', () => { + Simulate.change(input, { + target: { + value: 'a' + } + }); + + expect(onTagsInputChange).toBeCalledWith('a'); + }); + }); }); describe('Tags - removing', () => { @@ -293,11 +314,12 @@ describe('Tags - removing', () => { }); }); -describe('Tags - "allowDupes"', () => { +describe('Tags - "uniqueTags"', () => { it('should allow duplicate tags to be created', () => { const tags = renderIntoDocument( + initialTags={TEST_TAGS} + uniqueTags={false} /> ); const renderedDOM = findDOMNode(tags); @@ -318,7 +340,7 @@ describe('Tags - "allowDupes"', () => { const tags = renderIntoDocument( + uniqueTags={true} /> ); const renderedDOM = findDOMNode(tags); @@ -333,3 +355,30 @@ describe('Tags - "allowDupes"', () => { expect(tagContainer.children.length).toBe(2); }); }); + +describe('Tags - "maxTags"', () => { + describe('when maxTags is set to 3', () => { + it('should allow no more than 3 tags to be added', () => { + const tags = renderIntoDocument( + + ); + + const renderedDOM = findDOMNode(tags); + const input = renderedDOM.getElementsByTagName('input')[0]; + const tagContainer = renderedDOM.querySelector('.tags-container'); + + input.value = TEST_TAGS[0]; + + Simulate.change(input); + Simulate.keyDown(input, {key: 'Enter', keyCode: 13, which: 13}); + + input.value = TEST_TAGS[0]; + + Simulate.keyDown(input, {key: 'Enter', keyCode: 13, which: 13}); + + expect(tagContainer.children.length).toBe(3); + }); + }); +}); diff --git a/dist-components/Tag.js b/dist-components/Tag.js index b89e3da..6f8e56c 100644 --- a/dist-components/Tag.js +++ b/dist-components/Tag.js @@ -14,7 +14,7 @@ var Tag = function Tag(props) { var onRemoveClick = function onRemoveClick(e) { e.preventDefault(); - props.removeTag(); + props.onRemoveTag(e); }; var removeIcon = !props.readOnly ? _react2.default.createElement( @@ -36,7 +36,7 @@ exports.default = Tag; Tag.propTypes = { name: _react2.default.PropTypes.string.isRequired, - removeTag: _react2.default.PropTypes.func, + onRemoveTag: _react2.default.PropTypes.func, selectedTag: _react2.default.PropTypes.bool, readOnly: _react2.default.PropTypes.bool, removeTagIcon: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.element]) diff --git a/dist-components/Tags.js b/dist-components/Tags.js index f20224e..8e4afe0 100644 --- a/dist-components/Tags.js +++ b/dist-components/Tags.js @@ -35,58 +35,42 @@ var Tags = function (_Component) { var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Tags).call(this, props)); _this.state = { - tags: _this.props.initialTags + tags: _this.props.initialTags, + value: '' }; return _this; } _createClass(Tags, [{ - key: 'onInputKey', - value: function onInputKey(e) { - switch (e.keyCode) { - case Tags.KEYS.backspace: - if (this.state.tags.length === 0) return; - - if (this.input.value === '') { - this.removeTag(this.state.tags.length - 1); - } - - break; - - default: - if (this.input.value === '') return; - - if (this.props.delimiters.indexOf(e.keyCode) !== -1) { - if (Tags.KEYS.enter !== e.keyCode) { - e.preventDefault(); - } - - this.addTag(); - } - - break; - } - } - }, { key: 'addTag', value: function addTag() { var _this2 = this; + if (this.props.maxTags >= 0) { + if (this.state.tags.length >= this.props.maxTags) return; + } + + var _props = this.props; + var uniqueTags = _props.uniqueTags; + var onChange = _props.onChange; + var onAdded = _props.onAdded; + + var value = this.input.value.trim(); - if (!this.props.allowDupes) { + if (uniqueTags) { if (this.state.tags.indexOf(value) >= 0) return; } this.setState({ tags: (0, _reactAddonsUpdate2.default)(this.state.tags, { $push: [value] }) }, function () { - if (typeof _this2.props.change !== 'undefined') { - _this2.props.change(_this2.state.tags); + if (typeof onChange !== 'undefined') { + onChange(_this2.state.tags); } - if (typeof _this2.props.added !== 'undefined') { - _this2.props.added(value); + if (typeof onAdded !== 'undefined') { + onAdded(value); } _this2.input.value = ''; @@ -97,48 +81,101 @@ var Tags = function (_Component) { value: function removeTag(index) { var _this3 = this; + var _props2 = this.props; + var onChange = _props2.onChange; + var onRemoved = _props2.onRemoved; + var value = this.state.tags[index]; this.setState({ tags: (0, _reactAddonsUpdate2.default)(this.state.tags, { $splice: [[index, 1]] }) }, function () { - if (typeof _this3.props.change !== 'undefined') { - _this3.props.change(_this3.state.tags); + if (typeof onChange !== 'undefined') { + onChange(_this3.state.tags); } - if (typeof _this3.props.removed !== 'undefined') { - _this3.props.removed(value); + if (typeof onRemoved !== 'undefined') { + onRemoved(value); } }); } + }, { + key: 'onInputKey', + value: function onInputKey(e) { + switch (e.keyCode) { + case Tags.KEYS.backspace: + if (this.state.tags.length === 0) return; + + if (this.input.value === '') { + this.removeTag(this.state.tags.length - 1); + } + + break; + + default: + if (this.input.value === '') return; + + if (this.props.addKeys.indexOf(e.keyCode) !== -1) { + if (Tags.KEYS.enter !== e.keyCode) { + e.preventDefault(); + } + + this.addTag(); + } + + break; + } + } + }, { + key: 'onInputChange', + value: function onInputChange(e) { + var value = e.target.value.trim(); + + if (typeof this.props.onInputChange !== 'undefined') { + this.props.onInputChange(value); + } + + this.setState({ + value: value + }); + } }, { key: 'render', value: function render() { var _this4 = this; + var _props3 = this.props; + var readOnly = _props3.readOnly; + var removeTagIcon = _props3.removeTagIcon; + var placeholder = _props3.placeholder; + var id = _props3.id; + + var tagItems = this.state.tags.map(function (tag, v) { return _react2.default.createElement(_Tag2.default, { key: v, name: tag, - readOnly: _this4.props.readOnly, - removeTagIcon: _this4.props.removeTagIcon, - removeTag: _this4.removeTag.bind(_this4, v) }); + readOnly: readOnly, + removeTagIcon: removeTagIcon, + onRemoveTag: _this4.removeTag.bind(_this4, v) }); }); var tagInput = !this.props.readOnly ? _react2.default.createElement('input', { type: 'text', role: 'textbox', - placeholder: this.props.placeholder, + 'aria-label': placeholder, + placeholder: placeholder, + onChange: this.onInputChange.bind(this), onKeyDown: this.onInputKey.bind(this), ref: function ref(el) { return _this4.input = el; } }) : null; - var classNames = this.props.readOnly ? 'tags-container readonly' : 'tags-container'; + var classNames = readOnly ? 'tags-container readonly' : 'tags-container'; return _react2.default.createElement( 'div', - { className: 'react-tags', id: this.props.id }, + { className: 'react-tags', id: id }, _react2.default.createElement( 'ul', { className: classNames }, @@ -162,21 +199,24 @@ Tags.KEYS = { }; Tags.propTypes = { initialTags: _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.string), - change: _react2.default.PropTypes.func, - added: _react2.default.PropTypes.func, - removed: _react2.default.PropTypes.func, + onChange: _react2.default.PropTypes.func, + onAdded: _react2.default.PropTypes.func, + onRemoved: _react2.default.PropTypes.func, + onInputChange: _react2.default.PropTypes.func, + maxTags: _react2.default.PropTypes.number, placeholder: _react2.default.PropTypes.string, - delimiters: _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.number), + addKeys: _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.number), id: _react2.default.PropTypes.string, readOnly: _react2.default.PropTypes.bool, - allowDupes: _react2.default.PropTypes.bool, + uniqueTags: _react2.default.PropTypes.bool, removeTagIcon: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.element]) }; Tags.defaultProps = { initialTags: [], + maxTags: -1, placeholder: 'Add a tag', - delimiters: [Tags.KEYS.enter, Tags.KEYS.tab, Tags.KEYS.spacebar], - allowDupes: true, + addKeys: [Tags.KEYS.enter, Tags.KEYS.tab, Tags.KEYS.spacebar], + uniqueTags: false, readOnly: false }; exports.default = Tags; \ No newline at end of file diff --git a/dist/react-tags.css b/dist/react-tags.css index 077a7da..b880bbf 100644 --- a/dist/react-tags.css +++ b/dist/react-tags.css @@ -8,6 +8,30 @@ display: inline-block; padding: 0 9px; } +@-webkit-keyframes slide-left { + 0% { + -webkit-transform: translate3d(8, 0, 0); } + 100% { + -webkit-transform: translate3d(0, 0, 0); } } + +@-moz-keyframes slide-left { + 0% { + -moz-transform: translate3d(8px, 0, 0); } + 100% { + -moz-transform: translate3d(0, 0, 0); } } + +@-o-keyframes slide-left { + 0% { + -o-transform: translate3d(8px, 0, 0); } + 100% { + -o-transform: translate3d(0, 0, 0); } } + +@keyframes slide-left { + 0% { + transform: translate3d(8px, 0, 0); } + 100% { + transform: translate3d(0, 0, 0); } } + .react-tags input { transition: border-color 0.3s ease; background: #FFF; @@ -30,52 +54,31 @@ margin: 0; padding: 0; list-style: none; } - .react-tags .tags-container > li { - -webkit-animation: slide-left 0.8s cubic-bezier(0.19, 1, 0.22, 1); - -moz-animation: slide-left 0.8s cubic-bezier(0.19, 1, 0.22, 1); - -o-animation: slide-left 0.8s cubic-bezier(0.19, 1, 0.22, 1); - animation: slide-left 0.8s cubic-bezier(0.19, 1, 0.22, 1); - background: #D9D8DD; - margin: 5px 5px 0 0; - transition: background 0.3s ease; - cursor: default; } - .react-tags .tags-container > li:hover { - background: #c9c8cf; } - .react-tags .tags-container a { - font-size: 12px; - color: #000; - transition: color 0.3s ease; - display: inline-block; - margin-left: 7px; - text-decoration: none; } - .react-tags .tags-container a:hover { - color: #666666; } - .react-tags .tags-container i { - font-style: normal; } - .react-tags .tags-container.readonly { - pointer-events: none; } -@-webkit-keyframes slide-left { - 0% { - -webkit-transform: translate3d(8, 0, 0); } - 100% { - -webkit-transform: translate3d(0, 0, 0); } } +.react-tags .tags-container > li { + -webkit-animation: slide-left 0.8s cubic-bezier(0.19, 1, 0.22, 1); + -moz-animation: slide-left 0.8s cubic-bezier(0.19, 1, 0.22, 1); + -o-animation: slide-left 0.8s cubic-bezier(0.19, 1, 0.22, 1); + animation: slide-left 0.8s cubic-bezier(0.19, 1, 0.22, 1); + background: #D9D8DD; + margin: 5px 5px 0 0; + transition: background 0.3s ease; + cursor: default; } + .react-tags .tags-container > li:hover, .react-tags .tags-container > li.active { + background: #c9c8cf; } -@-moz-keyframes slide-left { - 0% { - -moz-transform: translate3d(8px, 0, 0); } - 100% { - -moz-transform: translate3d(0, 0, 0); } } +.react-tags .tags-container a { + font-size: 12px; + color: #000; + transition: color 0.3s ease; + display: inline-block; + margin-left: 7px; + text-decoration: none; } + .react-tags .tags-container a:hover { + color: #666666; } -@-o-keyframes slide-left { - 0% { - -o-transform: translate3d(8px, 0, 0); } - 100% { - opacity: 1; - -o-transform: translate3d(0, 0, 0); } } +.react-tags .tags-container i { + font-style: normal; } -@keyframes slide-left { - 0% { - transform: translate3d(8px, 0, 0); } - 100% { - transform: translate3d(0, 0, 0); } } +.react-tags .tags-container.readonly { + pointer-events: none; } diff --git a/dist/react-tags.min.css b/dist/react-tags.min.css index 074a50b..9b73b41 100644 --- a/dist/react-tags.min.css +++ b/dist/react-tags.min.css @@ -1 +1 @@ -.react-tags .tags-container>li,.react-tags input{font-size:12px;height:26px;line-height:26px;color:#626166;border-radius:12px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;display:inline-block;padding:0 9px}.react-tags input{transition:border-color .3s ease;background:#FFF;border:1px solid #D9D8DD;height:24px;line-height:24px;margin-top:5px}.react-tags input::-webkit-input-placeholder{color:rgba(98,97,102,.5)}.react-tags input::-moz-placeholder{color:rgba(98,97,102,.5)}.react-tags input:-ms-input-placeholder{color:rgba(98,97,102,.5)}.react-tags input:focus{outline:0;border-color:#b1afb9}.react-tags .tags-container{display:inline;margin:0;padding:0;list-style:none}.react-tags .tags-container>li{-webkit-animation:slide-left .8s cubic-bezier(.19,1,.22,1);-moz-animation:slide-left .8s cubic-bezier(.19,1,.22,1);-o-animation:slide-left .8s cubic-bezier(.19,1,.22,1);animation:slide-left .8s cubic-bezier(.19,1,.22,1);background:#D9D8DD;margin:5px 5px 0 0;transition:background .3s ease;cursor:default}.react-tags .tags-container>li:hover{background:#c9c8cf}.react-tags .tags-container a{font-size:12px;color:#000;transition:color .3s ease;display:inline-block;margin-left:7px;text-decoration:none}.react-tags .tags-container a:hover{color:#666}.react-tags .tags-container i{font-style:normal}.react-tags .tags-container.readonly{pointer-events:none}@-webkit-keyframes slide-left{0%{-webkit-transform:translate3d(8,0,0)}100%{-webkit-transform:translate3d(0,0,0)}}@-moz-keyframes slide-left{0%{-moz-transform:translate3d(8px,0,0)}100%{-moz-transform:translate3d(0,0,0)}}@-o-keyframes slide-left{0%{-o-transform:translate3d(8px,0,0)}100%{opacity:1;-o-transform:translate3d(0,0,0)}}@keyframes slide-left{0%{transform:translate3d(8px,0,0)}100%{transform:translate3d(0,0,0)}} \ No newline at end of file +.react-tags .tags-container>li,.react-tags input{font-size:12px;height:26px;line-height:26px;color:#626166;border-radius:12px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;display:inline-block;padding:0 9px}@-webkit-keyframes slide-left{0%{-webkit-transform:translate3d(8,0,0)}100%{-webkit-transform:translate3d(0,0,0)}}@-moz-keyframes slide-left{0%{-moz-transform:translate3d(8px,0,0)}100%{-moz-transform:translate3d(0,0,0)}}@-o-keyframes slide-left{0%{-o-transform:translate3d(8px,0,0)}100%{-o-transform:translate3d(0,0,0)}}@keyframes slide-left{0%{transform:translate3d(8px,0,0)}100%{transform:translate3d(0,0,0)}}.react-tags input{transition:border-color .3s ease;background:#FFF;border:1px solid #D9D8DD;height:24px;line-height:24px;margin-top:5px}.react-tags input::-webkit-input-placeholder{color:rgba(98,97,102,.5)}.react-tags input::-moz-placeholder{color:rgba(98,97,102,.5)}.react-tags input:-ms-input-placeholder{color:rgba(98,97,102,.5)}.react-tags input:focus{outline:0;border-color:#b1afb9}.react-tags .tags-container{display:inline;margin:0;padding:0;list-style:none}.react-tags .tags-container>li{-webkit-animation:slide-left .8s cubic-bezier(.19,1,.22,1);-moz-animation:slide-left .8s cubic-bezier(.19,1,.22,1);-o-animation:slide-left .8s cubic-bezier(.19,1,.22,1);animation:slide-left .8s cubic-bezier(.19,1,.22,1);background:#D9D8DD;margin:5px 5px 0 0;transition:background .3s ease;cursor:default}.react-tags .tags-container>li.active,.react-tags .tags-container>li:hover{background:#c9c8cf}.react-tags .tags-container a{font-size:12px;color:#000;transition:color .3s ease;display:inline-block;margin-left:7px;text-decoration:none}.react-tags .tags-container a:hover{color:#666}.react-tags .tags-container i{font-style:normal}.react-tags .tags-container.readonly{pointer-events:none} \ No newline at end of file diff --git a/dist/react-tags.min.js b/dist/react-tags.min.js index f8bbeb5..772b2c9 100644 --- a/dist/react-tags.min.js +++ b/dist/react-tags.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("React"),require("update")):"function"==typeof define&&define.amd?define(["React","update"],t):"object"==typeof exports?exports.Tags=t(require("React"),require("update")):e.Tags=t(e.React,e.update)}(this,function(e,t){return function(e){function t(a){if(r[a])return r[a].exports;var n=r[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){"use strict";function a(e){return e&&e.__esModule?e:{"default":e}}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var p=function(){function e(e,t){for(var r=0;r=0||this.setState({tags:(0,f["default"])(this.state.tags,{$push:[t]})},function(){"undefined"!=typeof e.props.change&&e.props.change(e.state.tags),"undefined"!=typeof e.props.added&&e.props.added(t),e.input.value=""})}},{key:"removeTag",value:function(e){var t=this,r=this.state.tags[e];this.setState({tags:(0,f["default"])(this.state.tags,{$splice:[[e,1]]})},function(){"undefined"!=typeof t.props.change&&t.props.change(t.state.tags),"undefined"!=typeof t.props.removed&&t.props.removed(r)})}},{key:"render",value:function(){var e=this,t=this.state.tags.map(function(t,r){return i["default"].createElement(c["default"],{key:r,name:t,readOnly:e.props.readOnly,removeTagIcon:e.props.removeTagIcon,removeTag:e.removeTag.bind(e,r)})}),r=this.props.readOnly?null:i["default"].createElement("input",{type:"text",role:"textbox",placeholder:this.props.placeholder,onKeyDown:this.onInputKey.bind(this),ref:function(t){return e.input=t}}),a=this.props.readOnly?"tags-container readonly":"tags-container";return i["default"].createElement("div",{className:"react-tags",id:this.props.id},i["default"].createElement("ul",{className:a},t),r)}}]),t}(u.Component);y.KEYS={enter:13,tab:9,spacebar:32,backspace:8,left:37,right:39},y.propTypes={initialTags:i["default"].PropTypes.arrayOf(i["default"].PropTypes.string),change:i["default"].PropTypes.func,added:i["default"].PropTypes.func,removed:i["default"].PropTypes.func,placeholder:i["default"].PropTypes.string,delimiters:i["default"].PropTypes.arrayOf(i["default"].PropTypes.number),id:i["default"].PropTypes.string,readOnly:i["default"].PropTypes.bool,allowDupes:i["default"].PropTypes.bool,removeTagIcon:i["default"].PropTypes.oneOfType([i["default"].PropTypes.string,i["default"].PropTypes.element])},y.defaultProps={initialTags:[],placeholder:"Add a tag",delimiters:[y.KEYS.enter,y.KEYS.tab,y.KEYS.spacebar],allowDupes:!0,readOnly:!1},t["default"]=y},function(t,r){t.exports=e},function(e,r){e.exports=t},function(e,t,r){"use strict";function a(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var n=r(1),o=a(n),s=function(e){var t=function(t){t.preventDefault(),e.removeTag()},r=e.readOnly?null:o["default"].createElement("a",{onClick:t,href:"#"},e.removeTagIcon||String.fromCharCode(215));return o["default"].createElement("li",null,e.name,r)};t["default"]=s,s.propTypes={name:o["default"].PropTypes.string.isRequired,removeTag:o["default"].PropTypes.func,selectedTag:o["default"].PropTypes.bool,readOnly:o["default"].PropTypes.bool,removeTagIcon:o["default"].PropTypes.oneOfType([o["default"].PropTypes.string,o["default"].PropTypes.element])}}])}); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("React"),require("update")):"function"==typeof define&&define.amd?define(["React","update"],t):"object"==typeof exports?exports.Tags=t(require("React"),require("update")):e.Tags=t(e.React,e.update)}(this,function(e,t){return function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;n=0&&this.state.tags.length>=this.props.maxTags)){var t=this.props,n=t.uniqueTags,a=t.onChange,r=t.onAdded,o=this.input.value.trim();n&&this.state.tags.indexOf(o)>=0||this.setState({tags:(0,f["default"])(this.state.tags,{$push:[o]})},function(){"undefined"!=typeof a&&a(e.state.tags),"undefined"!=typeof r&&r(o),e.input.value=""})}}},{key:"removeTag",value:function(e){var t=this,n=this.props,a=n.onChange,r=n.onRemoved,o=this.state.tags[e];this.setState({tags:(0,f["default"])(this.state.tags,{$splice:[[e,1]]})},function(){"undefined"!=typeof a&&a(t.state.tags),"undefined"!=typeof r&&r(o)})}},{key:"onInputKey",value:function(e){switch(e.keyCode){case t.KEYS.backspace:if(0===this.state.tags.length)return;""===this.input.value&&this.removeTag(this.state.tags.length-1);break;default:if(""===this.input.value)return;this.props.addKeys.indexOf(e.keyCode)!==-1&&(t.KEYS.enter!==e.keyCode&&e.preventDefault(),this.addTag())}}},{key:"onInputChange",value:function(e){var t=e.target.value.trim();"undefined"!=typeof this.props.onInputChange&&this.props.onInputChange(t),this.setState({value:t})}},{key:"render",value:function(){var e=this,t=this.props,n=t.readOnly,a=t.removeTagIcon,r=t.placeholder,o=t.id,s=this.state.tags.map(function(t,r){return p["default"].createElement(c["default"],{key:r,name:t,readOnly:n,removeTagIcon:a,onRemoveTag:e.removeTag.bind(e,r)})}),u=this.props.readOnly?null:p["default"].createElement("input",{type:"text",role:"textbox","aria-label":r,placeholder:r,onChange:this.onInputChange.bind(this),onKeyDown:this.onInputKey.bind(this),ref:function(t){return e.input=t}}),i=n?"tags-container readonly":"tags-container";return p["default"].createElement("div",{className:"react-tags",id:o},p["default"].createElement("ul",{className:i},s),u)}}]),t}(i.Component);y.KEYS={enter:13,tab:9,spacebar:32,backspace:8,left:37,right:39},y.propTypes={initialTags:p["default"].PropTypes.arrayOf(p["default"].PropTypes.string),onChange:p["default"].PropTypes.func,onAdded:p["default"].PropTypes.func,onRemoved:p["default"].PropTypes.func,onInputChange:p["default"].PropTypes.func,maxTags:p["default"].PropTypes.number,placeholder:p["default"].PropTypes.string,addKeys:p["default"].PropTypes.arrayOf(p["default"].PropTypes.number),id:p["default"].PropTypes.string,readOnly:p["default"].PropTypes.bool,uniqueTags:p["default"].PropTypes.bool,removeTagIcon:p["default"].PropTypes.oneOfType([p["default"].PropTypes.string,p["default"].PropTypes.element])},y.defaultProps={initialTags:[],maxTags:-1,placeholder:"Add a tag",addKeys:[y.KEYS.enter,y.KEYS.tab,y.KEYS.spacebar],uniqueTags:!1,readOnly:!1},t["default"]=y},function(t,n){t.exports=e},function(e,n){e.exports=t},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),o=a(r),s=function(e){var t=function(t){t.preventDefault(),e.onRemoveTag(t)},n=e.readOnly?null:o["default"].createElement("a",{onClick:t,href:"#"},e.removeTagIcon||String.fromCharCode(215));return o["default"].createElement("li",null,e.name,n)};t["default"]=s,s.propTypes={name:o["default"].PropTypes.string.isRequired,onRemoveTag:o["default"].PropTypes.func,selectedTag:o["default"].PropTypes.bool,readOnly:o["default"].PropTypes.bool,removeTagIcon:o["default"].PropTypes.oneOfType([o["default"].PropTypes.string,o["default"].PropTypes.element])}}])}); \ No newline at end of file diff --git a/gulp/tasks/clean.js b/gulp/tasks/clean.js new file mode 100644 index 0000000..eb12ac0 --- /dev/null +++ b/gulp/tasks/clean.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = (gulp, $) => { + gulp.task('clean', () => { + $.del(['dist/**', 'dist-components/**', 'coverage/**']).then(paths => { + $.util.log($.util.colors.green.bold(`Deleted files and folders: ${paths.join('\n')}`)); + }); + }); +}; diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 9517dea..9a5ff1a 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -3,7 +3,7 @@ import gulp from 'gulp'; import gulpLoadPlugins from 'gulp-load-plugins'; -const $ = gulpLoadPlugins({pattern: ['gulp-*', 'run-sequence', 'minimist'], scope: ['devDependencies']}); +const $ = gulpLoadPlugins({pattern: ['gulp-*', 'run-sequence', 'del', 'minimist'], scope: ['devDependencies']}); const argv = $.minimist(process.argv.slice(2)); require('./gulp/bridge.js')(gulp, [ @@ -14,11 +14,12 @@ require('./gulp/bridge.js')(gulp, [ 'sass', 'clean-css', 'test', - 'watch' + 'watch', + 'clean' ], $); gulp.task('default', cb => { - $.runSequence('sass', 'cleanCSS', 'eslint', 'test', 'webpack', 'babel', () => { + $.runSequence('clean', 'sass', 'cleanCSS', 'eslint', 'test', 'webpack', 'babel', () => { $.util.log($.util.colors.green.bold('FINISHED BUILD')); if(argv.w){ diff --git a/package.json b/package.json index 7f54038..2e974b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-tagging-input", - "version": "1.2.6", + "version": "1.3.6", "description": "Simple tagging component", "main": "dist-components/Tags.js", "license": "MIT", @@ -33,7 +33,7 @@ "babel-plugin-transform-class-properties": "^6.10.2", "babel-register": "^6.9.0", "babel-eslint": "^6.1.2", - "babel-jest": "^13.2.2", + "babel-jest": "^14.0.0", "gulp-sass": "^2.3.2", "gulp-shell": "^0.5.2", "gulp": "^3.9.1", @@ -46,12 +46,13 @@ "gulp-util": "~3.0.7", "gulp-watch": "~4.3.6", "gulp-filelog": "~0.4.1", + "del": "^2.2.1", "http-server": "^0.9.0", "react": "^15.2.1", "react-addons-update": "^15.2.1", "react-addons-test-utils": "^15.2.1", "react-dom": "^15.2.1", - "jest-cli": "13.2.3", + "jest-cli": "^14.0.0", "webpack": "^1.13.1", "run-sequence": "^1.2.1", "minimist": "~1.2.0" diff --git a/src/js/Tag.js b/src/js/Tag.js index 56ddfc3..2528503 100644 --- a/src/js/Tag.js +++ b/src/js/Tag.js @@ -6,7 +6,7 @@ const Tag = props => { const onRemoveClick = e => { e.preventDefault(); - props.removeTag(); + props.onRemoveTag(e); }; const removeIcon = !props.readOnly ? ( @@ -27,7 +27,7 @@ export default Tag; Tag.propTypes = { name: React.PropTypes.string.isRequired, - removeTag: React.PropTypes.func, + onRemoveTag: React.PropTypes.func, selectedTag: React.PropTypes.bool, readOnly: React.PropTypes.bool, removeTagIcon: React.PropTypes.oneOfType([ diff --git a/src/js/Tags.js b/src/js/Tags.js index 993cd58..b68c994 100644 --- a/src/js/Tags.js +++ b/src/js/Tags.js @@ -1,6 +1,6 @@ 'use strict'; -import React, {Component} from 'react'; +import React, { Component } from 'react'; import update from 'react-addons-update'; import Tag from './Tag'; @@ -16,14 +16,16 @@ class Tags extends Component{ static propTypes = { initialTags: React.PropTypes.arrayOf(React.PropTypes.string), - change: React.PropTypes.func, - added: React.PropTypes.func, - removed: React.PropTypes.func, + onChange: React.PropTypes.func, + onAdded: React.PropTypes.func, + onRemoved: React.PropTypes.func, + onInputChange: React.PropTypes.func, + maxTags: React.PropTypes.number, placeholder: React.PropTypes.string, - delimiters: React.PropTypes.arrayOf(React.PropTypes.number), + addKeys: React.PropTypes.arrayOf(React.PropTypes.number), id: React.PropTypes.string, readOnly: React.PropTypes.bool, - allowDupes: React.PropTypes.bool, + uniqueTags: React.PropTypes.bool, removeTagIcon: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.element @@ -32,20 +34,67 @@ class Tags extends Component{ static defaultProps = { initialTags: [], + maxTags: -1, placeholder: 'Add a tag', - delimiters: [Tags.KEYS.enter, Tags.KEYS.tab, Tags.KEYS.spacebar], - allowDupes: true, + addKeys: [Tags.KEYS.enter, Tags.KEYS.tab, Tags.KEYS.spacebar], + uniqueTags: false, readOnly: false }; state = { - tags: this.props.initialTags + tags: this.props.initialTags, + value: '' }; constructor(props){ super(props); } + addTag(){ + if (this.props.maxTags >= 0){ + if (this.state.tags.length >= this.props.maxTags) return; + } + + const { uniqueTags, onChange, onAdded } = this.props; + + const value = this.input.value.trim(); + + if (uniqueTags){ + if (this.state.tags.indexOf(value) >= 0) return; + } + + this.setState({ + tags: update(this.state.tags, { $push: [value] }) + }, () => { + if (typeof onChange !== 'undefined'){ + onChange(this.state.tags); + } + + if (typeof onAdded !== 'undefined'){ + onAdded(value); + } + + this.input.value = ''; + }); + } + + removeTag(index){ + const { onChange, onRemoved } = this.props; + const value = this.state.tags[index]; + + this.setState({ + tags: update(this.state.tags, { $splice: [[index, 1]] }) + }, () => { + if (typeof onChange !== 'undefined'){ + onChange(this.state.tags); + } + + if (typeof onRemoved !== 'undefined'){ + onRemoved(value); + } + }); + } + onInputKey(e){ switch (e.keyCode){ case Tags.KEYS.backspace: @@ -60,7 +109,7 @@ class Tags extends Component{ default: if (this.input.value === '') return; - if (this.props.delimiters.indexOf(e.keyCode) !== -1){ + if (this.props.addKeys.indexOf(e.keyCode) !== -1){ if (Tags.KEYS.enter !== e.keyCode){ e.preventDefault(); } @@ -72,67 +121,45 @@ class Tags extends Component{ } } - addTag(){ - const value = this.input.value.trim(); + onInputChange(e){ + const value = e.target.value.trim(); - if (!this.props.allowDupes){ - if (this.state.tags.indexOf(value) >= 0) return; + if (typeof this.props.onInputChange !== 'undefined'){ + this.props.onInputChange(value); } this.setState({ - tags: update(this.state.tags, {$push: [value] }) - }, () => { - if (typeof this.props.change !== 'undefined'){ - this.props.change(this.state.tags); - } - - if (typeof this.props.added !== 'undefined'){ - this.props.added(value); - } - - this.input.value = ''; - }); - } - - removeTag(index){ - const value = this.state.tags[index]; - - this.setState({ - tags: update(this.state.tags, {$splice: [[index, 1]] }) - }, () => { - if (typeof this.props.change !== 'undefined'){ - this.props.change(this.state.tags); - } - - if (typeof this.props.removed !== 'undefined'){ - this.props.removed(value); - } + value }); } render(){ + const { readOnly, removeTagIcon, placeholder, id } = this.props; + const tagItems = this.state.tags.map((tag, v) => { return ; + readOnly={readOnly} + removeTagIcon={removeTagIcon} + onRemoveTag={this.removeTag.bind(this, v)} />; }); const tagInput = !this.props.readOnly ? ( this.input = el} /> ) : null; - const classNames = this.props.readOnly ? 'tags-container readonly' : 'tags-container'; + const classNames = readOnly ? 'tags-container readonly' : 'tags-container'; return ( -
+
    {tagItems}
diff --git a/src/scss/_base.scss b/src/scss/_base.scss new file mode 100644 index 0000000..fec0794 --- /dev/null +++ b/src/scss/_base.scss @@ -0,0 +1,51 @@ +//** +// Tags base +//** + +%tags-ui-base { + font-size: $tag-base-font-size; + height: $tag-base-height; + line-height: $tag-base-height; + color: $tag-base-font-color; + border-radius: $tag-base-border-radius; + font-family: $tag-base-font-family; + display: inline-block; + padding: 0 9px; +} + +@-webkit-keyframes slide-left { + 0% { + -webkit-transform: translate3d(8, 0, 0); + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + } +} +@-moz-keyframes slide-left { + 0% { + -moz-transform: translate3d(8px, 0, 0); + } + + 100% { + -moz-transform: translate3d(0, 0, 0); + } +} +@-o-keyframes slide-left { + 0% { + -o-transform: translate3d(8px, 0, 0); + } + + 100% { + -o-transform: translate3d(0, 0, 0); + } +} +@keyframes slide-left { + 0% { + transform: translate3d(8px, 0, 0); + } + + 100% { + transform: translate3d(0, 0, 0); + } +} diff --git a/src/scss/_colorpalette.scss b/src/scss/_colorpalette.scss index 5c1385c..2a0d128 100644 --- a/src/scss/_colorpalette.scss +++ b/src/scss/_colorpalette.scss @@ -1,5 +1,5 @@ //** -// React tags colors +// Tags colors //** $white: #FFF; diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss index 248a84e..8a1186f 100644 --- a/src/scss/_variables.scss +++ b/src/scss/_variables.scss @@ -1,5 +1,5 @@ //** -// React tags variables +// Tags variables //-- Variables may be overridden //** diff --git a/src/scss/components/_input.scss b/src/scss/components/_input.scss new file mode 100644 index 0000000..b7597e2 --- /dev/null +++ b/src/scss/components/_input.scss @@ -0,0 +1,35 @@ +//** +// Tags input +//** + +.react-tags { + input { + @extend %tags-ui-base; + + $height: $tag-base-height - 2; + + transition: border-color 0.3s ease; + background: $tag-input-bg-color; + border: $tag-input-border; + height: $height; + line-height: $height; + margin-top: $tag-base-margin; + + &::-webkit-input-placeholder { + color: $tag-input-placeholder-color; + } + + &::-moz-placeholder { + color: $tag-input-placeholder-color; + } + + &:-ms-input-placeholder { + color: $tag-input-placeholder-color; + } + + &:focus { + outline: none; + border-color: $tag-input-border-focus-color; + } + } +} diff --git a/src/scss/components/_tag.scss b/src/scss/components/_tag.scss new file mode 100644 index 0000000..c71d717 --- /dev/null +++ b/src/scss/components/_tag.scss @@ -0,0 +1,45 @@ +//** +// Tags tags +//** + +.react-tags { + .tags-container { + > li { + @extend %tags-ui-base; + + -webkit-animation: $tag-intro-animation; + -moz-animation: $tag-intro-animation; + -o-animation: $tag-intro-animation; + animation: $tag-intro-animation; + background: $tag-background-color; + margin: $tag-base-margin $tag-base-margin 0 0; + transition: background 0.3s ease; + cursor: default; + + &:hover, &.active { + background: $tag-background-hover-color; + } + } + + a { + font-size: $tag-remove-font-size; + color: $tag-remove-color; + transition: color 0.3s ease; + display: inline-block; + margin-left: 7px; + text-decoration: none; + + &:hover { + color: $tag-remove-hover-color; + } + } + + i { + font-style: normal; + } + + &.readonly { + pointer-events: none; + } + } +} diff --git a/src/scss/components/_tags.scss b/src/scss/components/_tags.scss new file mode 100644 index 0000000..447daf1 --- /dev/null +++ b/src/scss/components/_tags.scss @@ -0,0 +1,12 @@ +//** +// Tags list +//** + +.react-tags { + .tags-container { + display: inline; + margin: 0; + padding: 0; + list-style: none; + } +} diff --git a/src/scss/react-tags.scss b/src/scss/react-tags.scss index f10abc0..9bf5edf 100644 --- a/src/scss/react-tags.scss +++ b/src/scss/react-tags.scss @@ -1,133 +1,15 @@ //** -// React tags component +// Tags component +//-- All imports //** @import +//-- Core "colorpalette", -"variables"; +"variables", +"base", -%tags-ui-base { - font-size: $tag-base-font-size; - height: $tag-base-height; - line-height: $tag-base-height; - color: $tag-base-font-color; - border-radius: $tag-base-border-radius; - font-family: $tag-base-font-family; - display: inline-block; - padding: 0 9px; -} - -.react-tags { - input { - @extend %tags-ui-base; - - $height: $tag-base-height - 2; - - transition: border-color 0.3s ease; - background: $tag-input-bg-color; - border: $tag-input-border; - height: $height; - line-height: $height; - margin-top: $tag-base-margin; - - &::-webkit-input-placeholder { - color: $tag-input-placeholder-color; - } - - &::-moz-placeholder { - color: $tag-input-placeholder-color; - } - - &:-ms-input-placeholder { - color: $tag-input-placeholder-color; - } - - &:focus { - outline: none; - border-color: $tag-input-border-focus-color; - } - } - - .tags-container { - display: inline; - margin: 0; - padding: 0; - list-style: none; - - > li { - @extend %tags-ui-base; - - -webkit-animation: $tag-intro-animation; - -moz-animation: $tag-intro-animation; - -o-animation: $tag-intro-animation; - animation: $tag-intro-animation; - background: $tag-background-color; - margin: $tag-base-margin $tag-base-margin 0 0; - transition: background 0.3s ease; - cursor: default; - - &:hover { - background: $tag-background-hover-color; - } - } - - a { - font-size: $tag-remove-font-size; - color: $tag-remove-color; - transition: color 0.3s ease; - display: inline-block; - margin-left: 7px; - text-decoration: none; - - &:hover { - color: $tag-remove-hover-color; - } - } - - i { - font-style: normal; - } - - &.readonly { - pointer-events: none; - } - } -} - -@-webkit-keyframes slide-left { - 0% { - -webkit-transform: translate3d(8, 0, 0); - } - - 100% { - -webkit-transform: translate3d(0, 0, 0); - } -} -@-moz-keyframes slide-left { - 0% { - -moz-transform: translate3d(8px, 0, 0); - } - - 100% { - -moz-transform: translate3d(0, 0, 0); - } -} -@-o-keyframes slide-left { - 0% { - -o-transform: translate3d(8px, 0, 0); - } - - 100% { - opacity: 1; - -o-transform: translate3d(0, 0, 0); - } -} -@keyframes slide-left { - 0% { - transform: translate3d(8px, 0, 0); - } - - 100% { - transform: translate3d(0, 0, 0); - } -} +//-- Components +"components/input", +"components/tags", +"components/tag";