├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── dist └── index.js ├── index.html ├── package.json ├── selectrix.d.ts ├── src ├── PlayGround.js ├── actions │ └── index.js ├── components │ ├── App │ │ ├── App.js │ │ ├── index.js │ │ └── partials │ │ │ ├── Header │ │ │ ├── Header.js │ │ │ ├── index.js │ │ │ └── partials │ │ │ │ └── Searchable │ │ │ │ ├── Searchable.js │ │ │ │ └── index.js │ │ │ ├── MultiHeader │ │ │ ├── MultiHeader.js │ │ │ └── index.js │ │ │ ├── NoResults │ │ │ ├── NoResults.js │ │ │ └── index.js │ │ │ ├── SearchPrompt │ │ │ ├── SearchPrompt.js │ │ │ └── index.js │ │ │ └── Tags │ │ │ ├── Tags.js │ │ │ └── index.js │ ├── Selectrix.js │ └── index.js ├── development.js ├── helpers │ └── index.js ├── index.js ├── reducers │ └── index.js ├── scss │ ├── _colors.scss │ ├── _loader.scss │ └── react-selectrix.scss └── store │ ├── configureStore.js │ ├── devStore.js │ └── productionStore.js ├── webpack-stats.json ├── webpack.config.js ├── webpack.config.production.js ├── wipe-dependencies.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", {"modules": false}], 4 | "stage-2", 5 | "react" 6 | ], 7 | "env": { 8 | "development": { 9 | "plugins": [ "react-hot-loader/babel" ] 10 | }, 11 | "production": {} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | parser: "babel-eslint", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "env": { 7 | "es6": true, 8 | "node": true, 9 | "browser": true 10 | }, 11 | "rules": { 12 | "space-in-parens": [ 1, "always" ], 13 | "no-console": 0, 14 | "no-unused-vars": 1, 15 | "object-curly-spacing": [ 1, "always" ], 16 | "array-bracket-spacing": [ 1, "always" ], 17 | "comma-spacing": [2, { "before": false, "after": true }], 18 | "computed-property-spacing": [ 1, "always" ] 19 | }, 20 | "extends": [ "eslint:recommended", "plugin:react/recommended" ], 21 | "globals": { 22 | "document": true, 23 | "window": true, 24 | "module": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stratos-vetsos/react-selectrix/e5d677670556e8f9f1df1ad128038fcbee92ac4f/.npmignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stratos Vetsos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Selectrix 2 | A beautiful, materialized, easy to use and flexible **React Select** replacement 3 | ### Demo 4 | [Check out the demo and use examples here](https://stratos-vetsos.github.io/react-selectrix/)! 5 | 6 | ### Installing 7 | ``` 8 | npm i --save-dev react-selectrix 9 | ``` 10 | 11 | ### Documentation 12 | https://github.com/stratos-vetsos/react-selectrix/ 13 | 14 | ### Import to your project 15 | ``` 16 | import Selectrix from "react-selectrix"; 17 | ``` 18 | 19 | ### Basic Example 20 | 21 | ```javascript 22 | console.log( value ) } 44 | /> 45 | ``` 46 | 47 | ## Props 48 | 49 | Name | Type | Default Value | Description 50 | --- | --- | --- | --- 51 | options | array | [] | An array of the available options ( Objects with "key", "label" pairs and optionally "disabled" property ). 52 | multiple | boolean | false | Whether the Select supports multiple values. 53 | searchable | boolean | true | Whether the Select is **searchable**. 54 | noResultsMessage | string | No results match | The message of the **no results match**. 55 | materialize | boolean | true | Whether the style of the Select should be **Materialized** or default. 56 | defaultValue | boolean / array / string | false | If you have preselected values use this property. Use an array of option keys for multiple selections, or key as a string for single selection. 57 | checkBoxes | boolean | false | Set this to true if you want to render **Checkboxes** instead of list items. 58 | height | number | 190 | The height of the dropdown. 59 | placeHolderInside | boolean | false | If the placeholder should be an option. 60 | placeholder | string | Please Select | The placeholder of the Select. 61 | isOpen | boolean | false | If the Select should be rendered open. 62 | arrow | boolean | true | Whether to show an arrow on Select's header. 63 | disabled | boolean | false | Whether the Select should be disabled. 64 | customScrollbar | boolean | false | A custom scrollbar ( only for Chrome ) 65 | stayOpen | boolean | false | If the Select should stay open or not. 66 | commaSeperated | boolean | false | If you want the selected values to be a comma seperated string, turn this to "true". ( Available only with multiple prop set to "true". ) 67 | singleLine | boolean | false | Where the selected values ( Select's Header ) should be contained to one line. 68 | lifo | boolean | false | **Last In First Out Mode**. The user's last selection, goes first. ( Available only with multiple prop set to "true". ) 69 | searchIndex | boolean | true | Enable search by both Index and Value fields 70 | selectAllButton | boolean | false | Whether a "select all button" should be visible on Select's header. 71 | isDropDown | boolean | true | Set this to true if you want to use the Select as a **Dropdown**. When you select an option, the Select collapses and the header continue to have the placeholder as a value. 72 | tags | boolean | false | Whether to support custom tags. 73 | customKeys | object / boolean | false | Pass an object to change the default option keys ( key, label ). Example Syntax: ``{ key: "url", label: "title" }`` , to change the key to "url" and the label to "title". 74 | ajax | boolean / object | false | Whether to enable ajax. The library supports asynchronous calls through fetch API. Available default properties of ajax object: ``{ url: '', headers: {}, debounce: 200, fetchOnSearch: false, q: "", nestedKey: false, searchPrompt: true, minLength: 1 }.`` You can find details for all the ajax object properties, in the next section and in our demo page. 75 | onRenderOption | function / boolean | false | Use this function to render custom option items 76 | onRenderSelection | function / boolean | false | Use this function to render custom selected items 77 | onChange | function | undefined | Use this callback to catch Select's change trigger. 78 | onOpen | function | undefined | Use this callback to catch Select's open trigger. 79 | onClose | function | undefined | Use this callback to catch Select's close trigger. 80 | 81 | ## Ajax prop - breakdown 82 | 83 | Name | Type | Default Value | Description 84 | --- | --- | --- | --- 85 | url | string | '' | The url which the Select going to fetch the available options. 86 | headers | object | {} | Pass any headers you want to fetch api. 87 | debounce | number | 200 | The debounce of the ajax calls in milliseconds. 88 | fetchOnSearch | boolean | false | Whether you don't want to have the options prepopulated, when the Select opens, but you want to query them based on user's search value. 89 | q | string | '' | This property goes alongside with fetchOnSearch property, setted to "true". Depending the REST API providing you with options, you have to change this value accordingly. e.g. "&search={q}". Wherever you use the pseudo variable {q}, the user's search query will injected in the final request. 90 | nestedKey | string / boolean | false | If your REST API returns the actual data in a deeper level, inside a nested key, let's say "articles", set nestedKey to "articles". 91 | searchPrompt | boolean | true | This property goes alongside with fetchOnSearch property and indicates the user how many more characters should type, before the ajax search will happen. 92 | minLength | number | 1 | This property goes alongside with fetchOnSearch property and searchPrompt setted to "true". It is the min length of characters the user should type, before the ajax call search takes place. 93 | 94 | ## Callbacks - breakdown 95 | 96 | Name | Arguments | Description 97 | --- | --- | --- 98 | onChange | value | The selected object if the Select is single and an array of objects if the Select is multiple. 99 | onRenderOption | option, index | The option which is going to be rendered and it's corresponding index. 100 | onRenderSelection | selected, settings, deselect | The selected object, the Select's settings and a callback function to use for deselection. 101 | onOpen | N/A | 102 | onClose | N/A | 103 | 104 | ### Ajax Example 105 | Many thanks to [newsapi.org](https://newsapi.org/) for their great api. 106 | [Check this example in action, in our demo page.](https://stratos-vetsos.github.io/react-selectrix/) 107 | 108 | ```javascript 109 | 119 | ``` 120 | 121 | ### Ajax Example with fetchOnSearch 122 | [Check this example in action, in our demo page.](https://stratos-vetsos.github.io/react-selectrix/) 123 | 124 | ```javascript 125 | 142 | ``` 143 | 144 | ### Tags Example 145 | [Check this example in action, in our demo page.](https://stratos-vetsos.github.io/react-selectrix/) 146 | 147 | ```javascript 148 | console.log( value ) } 163 | /> 164 | ``` 165 | 166 | ### Custom Render Example 167 | [Check this example in action, in our demo page.](https://stratos-vetsos.github.io/react-selectrix/) 168 | 169 | ```javascript 170 | console.log( value ) } 194 | /> 195 | 196 | const onRenderOption = ( option, index ) => ( 197 | { option.label } 198 | ) 199 | 200 | const onRenderSelection = ( selected, settings, deselect ) => ( 201 | 202 | { selected.label } 203 | 204 | 205 | ) 206 | ``` 207 | 208 | # License 209 | MIT Licensed. Stratos Vetsos. 210 | 211 | # Contributions 212 | Contributions are more than welcome. Run npm install && npm start on master and you are good to go! 213 | The CONTRIBUTING.md is going to be published soon. 214 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | !function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t(require("prop-types"),require("react"));else if("function"==typeof define&&define.amd)define(["prop-types","React"],t);else{var r="object"==typeof exports?t(require("prop-types"),require("react")):t(e.PropTypes,e.React);for(var n in r)("object"==typeof exports?exports:e)[n]=r[n]}}(window,(function(e,t){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var s=t[n]={i:n,l:!1,exports:{}};return e[n].call(s.exports,s,s.exports,r),s.l=!0,s.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)r.d(n,s,function(t){return e[t]}.bind(null,s));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="/dist/",r(r.s=15)}([function(t,r){t.exports=e},function(e,r){e.exports=t},function(e,t,r){"use strict";e.exports=function(e,t,r,n,s,o,i,a){if(!e){var c;if(void 0===t)c=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[r,n,s,o,i,a],u=0;(c=new Error(t.replace(/%s/g,(function(){return l[u++]})))).name="Invariant Violation"}throw c.framesToPop=1,c}}},function(e,t,r){"use strict";e.exports=r(7)},function(e,t,r){"use strict";(function(e,n){var s,o=r(6);s="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==e?e:n;var i=Object(o.a)(s);t.a=i}).call(this,r(8),r(9)(e))},function(e,t,r){"use strict";var n=r(3),s={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},a={};function c(e){return n.isMemo(e)?i:a[e.$$typeof]||s}a[n.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},a[n.Memo]=i;var l=Object.defineProperty,u=Object.getOwnPropertyNames,p=Object.getOwnPropertySymbols,d=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,h=Object.prototype;e.exports=function e(t,r,n){if("string"!=typeof r){if(h){var s=f(r);s&&s!==h&&e(t,s,n)}var i=u(r);p&&(i=i.concat(p(r)));for(var a=c(t),g=c(r),_=0;_ * {\n display: inline-block;\n vertical-align: middle; }\n .react-selectrix .rs-header {\n border: 1px solid #bdc8d5;\n color: #6a7f9d;\n border-radius: 2px;\n background-color: #ffffff;\n position: relative;\n font-size: 14px;\n margin: 0;\n user-select: none; }\n .react-selectrix .rs-header ::-webkit-input-placeholder {\n color: #6a7f9d; }\n .react-selectrix .rs-header ::-moz-placeholder {\n color: #6a7f9d; }\n .react-selectrix .rs-header :-ms-input-placeholder {\n color: #6a7f9d; }\n .react-selectrix .rs-header :-moz-placeholder {\n color: #6a7f9d; }\n .react-selectrix.rs-base-arrow .rs-reset-wrapper {\n right: 25px; }\n .react-selectrix.rs-base-customscrollbar ::-webkit-scrollbar {\n width: 12px;\n height: 12px; }\n .react-selectrix.rs-base-customscrollbar ::-webkit-scrollbar-thumb {\n background: #6f747b; }\n .react-selectrix.rs-base-customscrollbar ::-webkit-scrollbar-track {\n background: #dfdfdf; }\n .react-selectrix.rs-base-disabled {\n opacity: 0.6;\n pointer-events: none; }\n .react-selectrix.rs-base-searchable .rs-header, .react-selectrix.rs-base-tags .rs-header {\n padding: 0; }\n .react-selectrix.rs-base-searchable .rs-toggle, .react-selectrix.rs-base-tags .rs-toggle {\n cursor: text; }\n .react-selectrix.rs-base-singleline .rs-toggle {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: block; }\n .react-selectrix.rs-base-multiple:not(.rs-base-empty):not(.rs-base-commaseperated) .rs-toggle {\n padding: 5px 50px 5px 5px; }\n .react-selectrix.rs-base-multiple.rs-base-commaseperated:not(.rs-base-empty) .rs-toggle {\n padding: 7px 50px 7px 15px;\n line-height: 1.8; }\n .react-selectrix.rs-base-multiple.rs-base-commaseperated.rs-base-searchable .rs-toggle {\n display: flex; }\n .react-selectrix.rs-base-multiple.rs-base-commaseperated.rs-base-searchable .rs-commaseperated-wrapper {\n display: block;\n padding-right: 10px;\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .react-selectrix.rs-base-multiple .rs-selection {\n display: inline-block;\n background-color: #00B2EE;\n padding: 3px 10px 3px 25px;\n color: #ffffff;\n margin: 2px;\n font-size: 12px;\n border-radius: 2px;\n border: 1px solid #21a4cf;\n position: relative;\n cursor: pointer;\n max-width: 100%;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden; }\n .react-selectrix.rs-base-multiple:not(.rs-base-empty) .rs-selection, .react-selectrix.rs-base-multiple:not(.rs-base-empty) .rs-searchable {\n vertical-align: middle; }\n .react-selectrix.rs-base-multiple .rs-remove {\n font-size: 15px;\n color: #fff3f3;\n position: absolute;\n left: 0;\n width: 20px;\n text-align: center;\n border-right: 1px solid #d9d6d6;\n top: 0;\n height: 100%;\n transition: 0.2s background-color ease-in-out;\n font-family: "Arial" !important; }\n .react-selectrix.rs-base-multiple .rs-remove:hover {\n background-color: #23c8ff; }\n .react-selectrix.rs-base-multiple .rs-searchable {\n border: none;\n box-shadow: none;\n outline: none;\n -webkit-appearance: none;\n max-width: 100%;\n color: #6a7f9d;\n padding: 0;\n font-size: 14px; }\n .react-selectrix .rs-reset {\n font-size: 20px;\n color: #6a7f9d;\n position: relative;\n top: 1px;\n font-family: "Arial" !important; }\n .react-selectrix .rs-reset:hover {\n color: #b90e0e; }\n .react-selectrix .rs-arrow-wrapper {\n position: absolute;\n width: 10px;\n height: 100%;\n top: 0;\n right: 10px; }\n .react-selectrix .rs-reset-wrapper {\n position: absolute;\n width: 10px;\n height: 100%;\n top: 0;\n right: 10px;\n cursor: pointer; }\n .react-selectrix .rs-reset-wrapper:before {\n margin-right: -0.15em; }\n .react-selectrix .rs-arrow-indicator {\n width: 0;\n height: 0;\n border-style: solid;\n border-width: 5px 5px 0 5px;\n border-color: #6a7f9d transparent transparent;\n transition: 0.1s transform ease-in-out; }\n .react-selectrix .rs-arrow-indicator.up {\n transform: rotate(180deg); }\n .react-selectrix .rs-body {\n border-radius: 2px;\n background-color: #ffffff;\n box-shadow: 0 3px 7px 0 rgba(139, 155, 175, 0.5);\n position: absolute;\n width: 100%;\n left: 0;\n z-index: 1;\n overflow: auto; }\n .react-selectrix .rs-body > ul {\n list-style-type: none;\n padding: 0;\n margin: 0;\n overflow: hidden; }\n .react-selectrix .rs-option {\n cursor: pointer;\n padding: 11px 20px;\n color: #6a7f9d;\n font-size: 14px;\n user-select: none;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n line-height: normal; }\n .react-selectrix .rs-option.disabled {\n opacity: 0.6;\n pointer-events: none; }\n .react-selectrix .rs-option:not(.disabled).focused {\n background-color: #faf7f7; }\n .react-selectrix .rs-option:not(.disabled).selected {\n background-color: #eeeeee; }\n .react-selectrix .rs-no-results {\n padding: 11px 20px;\n color: #6a7f9d;\n font-size: 12px; }\n .react-selectrix .rs-toggle {\n border: none;\n outline: none;\n padding: 10px 50px 10px 15px;\n width: 100%;\n cursor: pointer;\n color: #6a7f9d;\n font-size: 14px;\n line-height: normal; }\n .react-selectrix .rs-toggle.rs-focused {\n outline: 0;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(81, 152, 209, 0.6); }\n .react-selectrix .rs-toggle.rs-searchable:focus {\n cursor: text; }\n .react-selectrix.rs-base-checkboxes .rs-option:not(.disabled).selected {\n background-color: inherit; }\n .react-selectrix.rs-base-checkboxes .rs-option:not(.disabled).focused {\n background-color: #faf7f7; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"] {\n position: absolute;\n opacity: 0;\n margin: 0; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"]:not(:checked) + label:before {\n width: 0;\n height: 0;\n border: 3px solid transparent;\n left: 6px;\n top: 10px;\n -webkit-transform: rotateZ(37deg);\n transform: rotateZ(37deg);\n -webkit-transform-origin: 100% 100%;\n transform-origin: 100% 100%; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"]:not(:checked) + label:after {\n height: 13px;\n width: 13px;\n background-color: transparent;\n border: 1px solid #D5D9DF;\n border-radius: 2px;\n top: 0;\n z-index: 0; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"] + label {\n position: relative;\n padding-left: 25px;\n cursor: pointer;\n display: inline-block;\n user-select: none;\n font-size: 14px;\n line-height: 14px;\n font-weight: 400;\n margin-bottom: 0; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"] + label:after, .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"] + label:before {\n content: \'\';\n left: 0;\n position: absolute;\n -webkit-transition: border 0.25s, background-color 0.25s, width 0.20s 0.1s, height 0.20s 0.1s, top 0.20s 0.1s, left 0.20s 0.1s;\n transition: border 0.25s, background-color 0.25s, width 0.20s 0.1s, height 0.20s 0.1s, top 0.20s 0.1s, left 0.20s 0.1s;\n z-index: 1; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"] + label:after {\n height: 13px;\n width: 13px;\n background-color: transparent;\n border: 2px solid #5a5a5a;\n top: 0;\n z-index: 0; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"]:checked + label:before {\n top: 1px;\n left: 0px;\n width: 6px;\n height: 10px;\n border-top: 2px solid transparent;\n border-left: 2px solid transparent;\n border-right: 2px solid #fff;\n border-bottom: 2px solid #fff;\n -webkit-transform: rotateZ(37deg);\n transform: rotateZ(37deg);\n -webkit-transform-origin: 100% 100%;\n transform-origin: 100% 100%; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"]:checked + label:after {\n top: 0;\n height: 13px;\n width: 13px;\n border: 1px solid #1DA4CF;\n background-color: #1DA4CF;\n z-index: 0;\n border-radius: 2px; }\n .react-selectrix.rs-base-checkboxes .rs-option input[type="checkbox"]:disabled + label {\n opacity: 0.4;\n cursor: not-allowed; }\n .react-selectrix.rs-base-open.rs-base-materialize .rs-header:after {\n content: "";\n height: 2px;\n background-color: #4379db;\n display: block;\n width: 100%;\n left: 0;\n visibility: visible; }\n .react-selectrix.rs-base-open.rs-base-materialize .rs-arrow-indicator {\n border-color: #4379db transparent transparent; }\n .react-selectrix.rs-base-open.rs-base-materialize .rs-body {\n opacity: 1;\n -webkit-transform: scale(1) translateY(0);\n -ms-transform: scale(1) translateY(0);\n transform: scale(1) translateY(0);\n visibility: visible;\n height: auto; }\n .react-selectrix .rs-arrow-indicator {\n transition: 0.2s transform ease-in-out; }\n .react-selectrix.rs-base-materialize.rs-base-multiple:not(.rs-base-empty) .rs-toggle {\n padding-left: 0; }\n .react-selectrix.rs-base-materialize .rs-header {\n border: none;\n border-bottom: 1px solid #eee;\n border-radius: 0; }\n .react-selectrix.rs-base-materialize .rs-header:after {\n content: "";\n height: 1px;\n background-color: transparent;\n position: absolute;\n bottom: -1px;\n display: block;\n width: 15px;\n left: 45%;\n transition-duration: .2s;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n visibility: hidden; }\n .react-selectrix.rs-base-materialize .rs-body {\n display: block;\n opacity: 0;\n box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.15), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n transform-origin: 50% 0;\n transform: scale(0.8) translateY(-15px);\n transition: all 0.2s cubic-bezier(0.5, 0, 0, 1.25), opacity 0.15s ease-in-out;\n visibility: hidden;\n height: 0; }\n .react-selectrix.rs-base-materialize .rs-toggle {\n padding-left: 0; }\n .react-selectrix.rs-base-materialize .rs-toggle.rs-focused {\n box-shadow: none; }\n .react-selectrix .rs-loader {\n font-size: 10px;\n margin: 15px auto;\n overflow: hidden;\n text-indent: -9999em;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #00B2EE;\n background: -moz-linear-gradient(left, #00B2EE 10%, rgba(255, 255, 255, 0) 42%);\n background: -webkit-linear-gradient(left, #00B2EE 10%, rgba(255, 255, 255, 0) 42%);\n background: -o-linear-gradient(left, #00B2EE 10%, rgba(255, 255, 255, 0) 42%);\n background: -ms-linear-gradient(left, #00B2EE 10%, rgba(255, 255, 255, 0) 42%);\n background: linear-gradient(to right, #00B2EE 10%, rgba(255, 255, 255, 0) 42%);\n position: relative;\n -webkit-animation: load3 1.4s infinite linear;\n animation: load3 1.4s infinite linear;\n -webkit-transform: translateZ(0);\n -ms-transform: translateZ(0);\n transform: translateZ(0); }\n .react-selectrix.rs-base-materialize .rs-loader {\n background: #4379db;\n background: -moz-linear-gradient(left, #4379db 10%, rgba(255, 255, 255, 0) 42%);\n background: -webkit-linear-gradient(left, #4379db 10%, rgba(255, 255, 255, 0) 42%);\n background: -o-linear-gradient(left, #4379db 10%, rgba(255, 255, 255, 0) 42%);\n background: -ms-linear-gradient(left, #4379db 10%, rgba(255, 255, 255, 0) 42%);\n background: linear-gradient(to right, #4379db 10%, rgba(255, 255, 255, 0) 42%); }\n .react-selectrix.rs-base-materialize .rs-loader:before {\n background: #4379db; }\n .react-selectrix .rs-loader:before {\n width: 50%;\n height: 50%;\n background: #00B2EE;\n border-radius: 100% 0 0 0;\n position: absolute;\n top: 0;\n left: 0;\n content: \'\'; }\n .react-selectrix .rs-loader:after {\n background: white;\n width: 75%;\n height: 75%;\n border-radius: 50%;\n content: \'\';\n margin: auto;\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0; }\n\n@-webkit-keyframes load3 {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg); }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg); } }\n\n@keyframes load3 {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg); }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg); } }\n',""])},function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var r=function(e,t){var r=e[1]||"",n=e[3];if(!n)return r;if(t&&"function"==typeof btoa){var s=function(e){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e))))+" */"}(n),o=n.sources.map((function(e){return"/*# sourceURL="+n.sourceRoot+e+" */"}));return[r].concat(o).concat([s]).join("\n")}return[r].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+r+"}":r})).join("")},t.i=function(e,r){"string"==typeof e&&(e=[[null,e,""]]);for(var n={},s=0;s=0&&c.splice(t,1)}function h(e){var t=document.createElement("style");return e.attrs.type="text/css",g(t,e.attrs),d(e,t),t}function g(e,t){Object.keys(t).forEach((function(r){e.setAttribute(r,t[r])}))}function _(e,t){var r,n,s,o;if(t.transform&&e.css){if(!(o=t.transform(e.css)))return function(){};e.css=o}if(t.singleton){var c=a++;r=i||(i=h(t)),n=m.bind(null,r,c,!1),s=m.bind(null,r,c,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(r=function(e){var t=document.createElement("link");return e.attrs.type="text/css",e.attrs.rel="stylesheet",g(t,e.attrs),d(e,t),t}(t),n=function(e,t,r){var n=r.css,s=r.sourceMap,o=void 0===t.convertToAbsoluteUrls&&s;(t.convertToAbsoluteUrls||o)&&(n=l(n)),s&&(n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(s))))+" */");var i=new Blob([n],{type:"text/css"}),a=e.href;e.href=URL.createObjectURL(i),a&&URL.revokeObjectURL(a)}.bind(null,r,t),s=function(){f(r),r.href&&URL.revokeObjectURL(r.href)}):(r=h(t),n=function(e,t){var r=t.css,n=t.media;if(n&&e.setAttribute("media",n),e.styleSheet)e.styleSheet.cssText=r;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(r))}}.bind(null,r),s=function(){f(r)});return n(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;n(e=t)}else s()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=s()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var r=p(e,t);return u(r,t),function(e){for(var s=[],o=0;o=0||(s[r]=e[r]);return s}var h=r(5),g=r.n(h),_=r(2),b=r.n(_),m=r(3),y=null,x={notify:function(){}},O=function(){function e(e,t,r){this.store=e,this.parentSub=t,this.onStateChange=r,this.unsubscribe=null,this.listeners=x}var t=e.prototype;return t.addNestedSub=function(e){return this.trySubscribe(),this.listeners.subscribe(e)},t.notifyNestedSubs=function(){this.listeners.notify()},t.isSubscribed=function(){return Boolean(this.unsubscribe)},t.trySubscribe=function(){this.unsubscribe||(this.unsubscribe=this.parentSub?this.parentSub.addNestedSub(this.onStateChange):this.store.subscribe(this.onStateChange),this.listeners=function(){var e=[],t=[];return{clear:function(){t=y,e=y},notify:function(){for(var r=e=t,n=0;n, or explicitly pass "'+C+'" as a prop to "'+s+'".'),n.initSelector(),n.initSubscription(),n}o(a,r);var c=a.prototype;return c.getChildContext=function(){var e,t=this.propsMode?null:this.subscription;return(e={})[L]=t||this.context[L],e},c.componentDidMount=function(){R&&(this.subscription.trySubscribe(),this.selector.run(this.props),this.selector.shouldComponentUpdate&&this.forceUpdate())},c.componentWillReceiveProps=function(e){this.selector.run(e)},c.shouldComponentUpdate=function(){return this.selector.shouldComponentUpdate},c.componentWillUnmount=function(){this.subscription&&this.subscription.tryUnsubscribe(),this.subscription=null,this.notifyNestedSubs=T,this.store=null,this.selector.run=T,this.selector.shouldComponentUpdate=!1},c.getWrappedInstance=function(){return b()(D,"To access the wrapped instance, you need to specify { withRef: true } in the options argument of the "+_+"() call."),this.wrappedInstance},c.setWrappedInstance=function(e){this.wrappedInstance=e},c.initSelector=function(){var t=e(this.store.dispatch,i);this.selector=function(e,t){var r={run:function(n){try{var s=e(t.getState(),n);(s!==r.props||r.error)&&(r.shouldComponentUpdate=!0,r.props=s,r.error=null)}catch(e){r.shouldComponentUpdate=!0,r.error=e}}};return r}(t,this.store),this.selector.run(this.props)},c.initSubscription=function(){if(R){var e=(this.propsMode?this.props:this.context)[L];this.subscription=new O(this.store,e,this.onStateChange.bind(this)),this.notifyNestedSubs=this.subscription.notifyNestedSubs.bind(this.subscription)}},c.onStateChange=function(){this.selector.run(this.props),this.selector.shouldComponentUpdate?(this.componentDidUpdate=this.notifyNestedSubsOnComponentDidUpdate,this.setState(S)):this.notifyNestedSubs()},c.notifyNestedSubsOnComponentDidUpdate=function(){this.componentDidUpdate=void 0,this.notifyNestedSubs()},c.isSubscribed=function(){return Boolean(this.subscription)&&this.subscription.isSubscribed()},c.addExtraProps=function(e){if(!(D||x||this.propsMode&&this.subscription))return e;var t=d({},e);return D&&(t.ref=this.setWrappedInstance),x&&(t[x]=this.renderCount++),this.propsMode&&this.subscription&&(t[L]=this.subscription),t},c.render=function(){var e=this.selector;if(e.shouldComponentUpdate=!1,e.error)throw e.error;return Object(n.createElement)(t,this.addExtraProps(e.props))},a}(n.Component);return E&&(a.prototype.UNSAFE_componentWillReceiveProps=a.prototype.componentWillReceiveProps,delete a.prototype.componentWillReceiveProps),a.WrappedComponent=t,a.displayName=s,a.childContextTypes=H,a.contextTypes=I,a.propTypes=I,g()(a,t)}}var R=Object.prototype.hasOwnProperty;function A(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function C(e,t){if(A(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var r=Object.keys(e),n=Object.keys(t);if(r.length!==n.length)return!1;for(var s=0;s=0;n--){var s=t[n](e);if(s)return s}return function(t,n){throw new Error("Invalid value of type "+typeof e+" for "+r+" argument when connecting component "+n.wrappedComponentName+".")}}function Y(e,t){return e===t}var J=function(e){var t={},r=t.connectHOC,n=void 0===r?j:r,s=t.mapStateToPropsFactories,o=void 0===s?B:s,i=t.mapDispatchToPropsFactories,a=void 0===i?F:i,c=t.mergePropsFactories,l=void 0===c?K:c,u=t.selectorFactory,p=void 0===u?W:u;return function(e,t,r,s){void 0===s&&(s={});var i=s,c=i.pure,u=void 0===c||c,h=i.areStatesEqual,g=void 0===h?Y:h,_=i.areOwnPropsEqual,b=void 0===_?C:_,m=i.areStatePropsEqual,y=void 0===m?C:m,x=i.areMergedPropsEqual,O=void 0===x?C:x,E=f(i,["pure","areStatesEqual","areOwnPropsEqual","areStatePropsEqual","areMergedPropsEqual"]),v=V(e,o,"mapStateToProps"),S=V(t,a,"mapDispatchToProps"),T=V(r,l,"mergeProps");return n(p,d({methodName:"connect",getDisplayName:function(e){return"Connect("+e+")"},shouldHandleStateChanges:Boolean(e),initMapStateToProps:v,initMapDispatchToProps:S,initMergeProps:T,pure:u,areStatesEqual:g,areOwnPropsEqual:b,areStatePropsEqual:y,areMergedPropsEqual:O},E))}}(),Q=function(e,t){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return function(e,t){var r=[],n=!0,s=!1,o=void 0;try{for(var i,a=e[Symbol.iterator]();!(n=(i=a.next()).done)&&(r.push(i.value),!t||r.length!==t);n=!0);}catch(e){s=!0,o=e}finally{try{!n&&a.return&&a.return()}finally{if(s)throw o}}return r}(e,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")},Z="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},X=function(e){return e instanceof Array},ee=function(e){return"string"==typeof e},te=function(e){return null!==e&&"object"===(void 0===e?"undefined":Z(e))&&!Array.isArray(e)},re=function(e){return!isNaN(parseFloat(e))&&isFinite(e)},ne=function(e){return e.constructor===String?""===e:e.constructor!==Object||Array.isArray(e)?e.constructor===Array&&0===e.length:0===Object.keys(e).length},se=function(e,t,r,n){var s=["disabled","multiple","placeHolderInside","arrow","customScrollbar","searchable","commaSeperated","singleLine","checkBoxes","materialize","tags"],o="",i=!0,a=!1,c=void 0;try{for(var l,u=Object.entries(e)[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var p=l.value,d=Q(p,2),f=d[0];!0===d[1]&&s.includes(f)&&(o+="rs-base-"+f.toLowerCase()+" ")}}catch(e){a=!0,c=e}finally{try{!i&&u.return&&u.return()}finally{if(a)throw c}}return t&&(o+="rs-base-open "),0===r.length&&(o+="rs-base-empty "),n.enabled&&(o+="rs-base-tags "),""!==e.className&&(o+=e.className),""!==(o=o.trim())?" "+o:""},oe=function(e,t){var r={selected:[],selectedIndex:[]},n=!0,s=!1,o=void 0;try{for(var i,a=t.entries()[Symbol.iterator]();!(n=(i=a.next()).done);n=!0){var c=i.value,l=Q(c,2),u=l[0],p=l[1];X(e)?e.includes(p.key)&&(r.selected.push(p.key),r.selectedIndex.push(u)):e===p.key&&(r.selected.push(p.key),r.selectedIndex.push(u))}}catch(e){s=!0,o=e}finally{try{!n&&a.return&&a.return()}finally{if(s)throw o}}return r},ie=function(e,t){if(!t)return!1;var r=t.offsetTop,n=e.scrollTop,s=e.clientHeight,o=t.clientHeight;return(r-n<=0||r-n>=s)&&r-s+o},ae=function(e,t,r){var n=void 0;return function(){var s=this,o=arguments,i=r&&!n;clearTimeout(n),n=setTimeout((function(){n=null,r||e.apply(s,o)}),t),i&&e.apply(s,o)}};function ce(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function le(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t1&&void 0!==arguments[1]&&arguments[1];return 0===e.length?"":!1===t?e[0]:""}),"getSelectValue","D:/Projects/react-selectrix/src/helpers/index.js"),__REACT_HOT_LOADER__.register((function(e,t){var r=!0,n=!1,s=void 0;try{for(var o,i=t.entries()[Symbol.iterator]();!(r=(o=i.next()).done);r=!0){var a=o.value,c=Q(a,2),l=c[0];if(e===c[1].key)return l}}catch(e){n=!0,s=e}finally{try{!r&&i.return&&i.return()}finally{if(n)throw s}}return!1}),"getSelectedIndex","D:/Projects/react-selectrix/src/helpers/index.js"),__REACT_HOT_LOADER__.register(oe,"normalizeSelected","D:/Projects/react-selectrix/src/helpers/index.js"),__REACT_HOT_LOADER__.register(ie,"isInViewport","D:/Projects/react-selectrix/src/helpers/index.js"),__REACT_HOT_LOADER__.register((function(e,t){return-1!==t.findIndex((function(t){return t.key===e}))}),"itemInOptions","D:/Projects/react-selectrix/src/helpers/index.js"),__REACT_HOT_LOADER__.register(ae,"debounce","D:/Projects/react-selectrix/src/helpers/index.js"));var ue=function(e){return function(t,r){var n=r(),s={key:"tag-"+e,label:e};t({type:"CREATE_TAG",tag:s,options:[].concat(le(n.options),[s]),resultSet:[].concat(le(n.search.resultSet),[s])}),n=r();var o=[].concat(le(n.settings.searchable?n.search.resultSet:n.options));(n.settings.commaSeperated||n.settings.checkBoxes)&&(o=o.filter((function(e){return!n.selected.includes(e.key)}))),t(Ee(o.length-1)),t({type:"CLEAR_SEARCH"})}},pe=function(e){return function(t,r){t({type:"REMOVE_ITEM",index:e});var n=r();n.isOpen||t(Se()),n.onChange([].concat(le(n.selectedIndex)).map((function(e){var t;return n.customKeys?Object.assign({},(ce(t={},n.customKeys.key,n.options[e].key),ce(t,n.customKeys.label,n.options[e].label),t)):n.options[e]})))}},de=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return function(r,n){var s=n(),o={},i=[].concat(le(e.options));t&&s.tags.tagSet.length>0&&(i=[].concat(le(e.options),le(s.tags.tagSet)));var a={active:!1,url:"",debounce:200,fetchOnSearch:!1,q:"",fetching:!1,needsUpdate:!0,nestedKey:!1,searchPrompt:!0,minLength:1,headers:{}};if(e.customKeys){var c=["key","label"];Object.keys(e.customKeys).forEach((function(t){c.includes(t)&&(o[t]=e.customKeys[t])}))}(o=!ne(o)&&Object.assign({key:"key",label:"label"},o))&&(i=i.map((function(e){return e.hasOwnProperty(o.key)&&e.hasOwnProperty(o.label)?{key:e[o.key],label:e[o.label]}:null})).filter((function(e){return e})));var l=e.defaultValue?oe(e.defaultValue,[].concat(le(i))):{selected:s.selected,selectedIndex:s.selectedIndex},u=l.selected,p=l.selectedIndex;e.ajax&&e.ajax.hasOwnProperty("url")&&""!==e.ajax.url&&(i=u=p=[],a.active=!0,a.url=e.ajax.url,e.ajax.hasOwnProperty("debounce")&&re(e.ajax.debounce)&&(a.debounce=e.ajax.debounce),e.ajax.fetchOnSearch&&!ne(e.ajax.q)&&ee(e.ajax.q)&&(a.fetchOnSearch=!0,a.q=e.ajax.q),e.ajax.hasOwnProperty("nestedKey")&&ee(e.ajax.nestedKey)&&(a.nestedKey=e.ajax.nestedKey),e.ajax.hasOwnProperty("searchPrompt")&&(a.searchPrompt=a.fetchOnSearch&&!1===e.ajax.searchPrompt),e.ajax.hasOwnProperty("minLength")&&re(e.ajax.minLength)&&a.fetchOnSearch&&(a.minLength=e.ajax.minLength),e.ajax.hasOwnProperty("headers")&&te(e.ajax.headers)&&(a.headers=e.ajax.headers)),r({type:t?"UPDATE_INSTANCE":"SETUP_INSTANCE",props:e,selected:u,selectedIndex:p,options:i,customKeys:o,ajax:a})}},fe=function(e){return{type:"SET_QUERY_STRING",queryString:e}},he=function(e){return function(t,r){r().isOpen||t(Se()),t({type:"SET_TAG",tag:e}),t(_e())}},ge=function(e){return function(t,r){if(""!==e){var n=r();if(n.isOpen||t(Se()),n.ajax.active&&n.ajax.fetchOnSearch&&e.length>=n.ajax.minLength)return t({type:"CLEAR_OPTIONS"}),t(be()).then(t(Te())).catch((function(e){return console.error(e)}));t({type:"SEARCH_OPTIONS",queryString:e})}else t({type:"CLEAR_SEARCH"});var s=r();return s.tags.active?t(_e()):s.settings.multiple?t(we(0)):void(s.search.active?t(we(0)):t({type:"CHECK_FOR_SCROLL"}))}},_e=function(){return{type:"FOCUS_TAG"}},be=function(){return function(e,t){return new Promise((function(r,n){var s=t();if(s.isOpen){e({type:"FETCHING_OPTIONS"});var o=s.ajax.url;s.ajax.fetchOnSearch&&(o+=s.ajax.q.replace("{q}",s.search.queryString)),fetch(o,{headers:s.ajax.headers}).then((function(e){if(!e.ok)throw"Your ajax url "+s.ajax.url+" failed with a status "+e.status;var t=e.headers.get("content-type");if(t&&t.includes("application/json"))return e.json();throw"Your ajax url "+s.ajax.url+" response was not a json"})).then((function(t){if(s.ajax.nestedKey){if(!t.hasOwnProperty(s.ajax.nestedKey))throw"Invalid nested key on "+s.ajax.url+" response";t=t[s.ajax.nestedKey]}if(!X(t))throw"Invalid data type on "+s.ajax.url+" response. Expected array.";e(me(t)),r(t)})).catch((function(e){n(e)}))}}))}},me=function(e){return function(t,r){var n=r();if(n.isOpen){var s=n.customKeys&&n.customKeys.hasOwnProperty("key")?n.customKeys.key:"key",o=n.customKeys&&n.customKeys.hasOwnProperty("label")?n.customKeys.label:"label";t({type:"SETUP_AJAX_OPTIONS",options:e.map((function(e){if(!e.hasOwnProperty(s)||!e.hasOwnProperty(o))return null;var t={key:e[s],label:e[o]};return e.hasOwnProperty("disabled")&&e.disabled&&(t.disabled=!0),t})).filter((function(e){return e}))})}}},ye=function(){return function(e,t){t().isOpen?e(je()):e(Se())}},xe=function(){return function(e,t){var r=t(),n=r.selected,s=r.selectedIndex,o=r.ajax,i=r.options;if(r.settings.multiple){if(o.fetchOnSearch)return r.onChange(n);r.onChange([].concat(le(s)).map((function(e){return i[e]})))}else r.onChange(i[s[0]])}},Oe=function(){return function(e,t){e({type:"SELECT_ALL"});var r=t();e(xe()),r.settings.stayOpen||e(je())}},Ee=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return function(r,n){if(-1===e)return r(ve());var s=n(),o=s.search.active?s.search.resultSet:s.options,i=s.ajax.fetchOnSearch?s.selected.map((function(e){return e.key})):s.selected;!s.settings.multiple||s.settings.commaSeperated||s.settings.checkBoxes||(o=[].concat(le(o)).filter((function(e){return!i.includes(e.key)})));var a=s.search.active||s.settings.multiple&&!s.settings.commaSeperated&&!s.settings.checkBoxes?s.options.findIndex((function(t){return t.key===o[e].key})):e;if((s.settings.commaSeperated||s.settings.checkBoxes)&&s.selectedIndex.includes(a))return r(pe(a));o[e]&&(r({type:"SELECT_ITEM",item:o[e],index:s.search.active||s.settings.multiple&&i.length?a:e,isKeyboard:t}),s.settings.stayOpen||r(je())),!(s=n()).isOpen||!s.settings.multiple||s.settings.checkBoxes||s.settings.commaSeperated||s.settings.isDropDown||(t?e===o.length-1?r(we(e-1)):r(we(e)):e===o.length-1&&r(we(e-1))),r(xe())}},ve=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return function(t,r){var n=r();t({type:"CLEAR_SELECT",stayOpen:e}),n.onChange(""),n.settings.stayOpen||t(je())}},Se=function(){return function(e,t){e({type:"OPEN_SELECT"});var r=t();r.onOpen(),r.ajax.active&&!r.ajax.fetchOnSearch&&r.ajax.needsUpdate?e(be()).then((function(){return e(Te())})).catch((function(e){return console.error(e)})):e(Te())}},Te=function(){return function(e,t){var r=t();if(r.settings.isDropDown)return e(we(0));if(r.isOpen&&(0===r.selected.length||r.settings.multiple||r.settings.checkBoxes)&&null===r.focusedItem)if(r.settings.checkBoxes&&!r.settings.multiple)e(we(r.selectedIndex[0]));else if(r.settings.multiple&&r.settings.commaSeperated&&r.selected.length>0){var n=r.settings.lifo?r.selectedIndex[0]:r.selectedIndex[r.selectedIndex.length-1];e(we(n))}else e(Ae("down"))}},je=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return function(t,r){t({type:"CLOSE_SELECT",blur:e}),r().onClose()}},Re=function(e){return function(t,r){var n=r(),s=e.key;switch(s){case"Tab":n.isOpen?t(je(!0)):n.focused&&t({type:"BLUR_SELECT"});break;case"Enter":if(e.preventDefault(),n.isOpen)if(null!==n.focusedItem)t(Ee(n.focusedItemIndex,!0));else{if(n.tags.active)return t(ue(n.search.queryString));t(je())}else t(Se());break;case"Esc":case"Escape":t(je());break;case"Up":case"Left":case"ArrowUp":case"ArrowLeft":if(n.search.active&&n.search.queryString.length>0&&["Left","ArrowLeft"].includes(s))return;e.preventDefault(),t(Ae("up"));break;case"Down":case"Right":case"ArrowDown":case"ArrowRight":if(n.search.active&&n.search.queryString.length>0&&["Right","ArrowRight"].includes(s))return;e.preventDefault(),t(Ae("down"))}}},Ae=function(e){return function(t,r){var n=r(),s=!n.settings.multiple&&n.settings.placeHolderInside,o=n.search.active?n.search.resultSet:n.options;!n.settings.multiple||n.settings.commaSeperated||n.settings.checkBoxes||(o=[].concat(le(o)).filter((function(e){return!n.selected.includes(e.key)})));var i,a=!1;null!==n.focusedItem?a=n.focusedItemIndex:n.selected.length>0&&!n.settings.multiple&&!n.settings.isDropDown&&(n.search.active?-1===(a=o.findIndex((function(e){return e.key===n.options[n.selectedIndex].key})))&&(a=!1):a=n.selectedIndex[0]),!1!==(i=!1!==a?"up"===e?n.tags.active&&0===a?"tag":a>0||s?a-1:0:a+10?e.options[e.selectedIndex]:null,isOpen:e.isOpen,queryString:e.search.queryString,focused:e.focused,ajax:e.ajax,tags:e.tags}},He=function(e){return{searchOptions:function(t){e(ge(t))},focusSelect:function(){e((function(e,t){t().focused||e({type:"FOCUS_SELECT"})}))},setTag:function(t){e(he(t))},blurSelect:function(){e({type:"BLUR_SELECT"})},setQueryString:function(t){e(fe(t))}}},Ne=J(Ie,He)(ke),qe=Ne,Ue=("undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(Ie,"mapStateToProps","D:/Projects/react-selectrix/src/components/App/partials/Header/partials/Searchable/index.js"),__REACT_HOT_LOADER__.register(He,"mapDispatchToProps","D:/Projects/react-selectrix/src/components/App/partials/Header/partials/Searchable/index.js"),__REACT_HOT_LOADER__.register(Ne,"default","D:/Projects/react-selectrix/src/components/App/partials/Header/partials/Searchable/index.js")),function(e){var t=e.settings,r=e.isOpen,n=e.selected,o=e.focused,i=e.onRenderSelection,a=e.tags,c=t.searchable||a.enabled?s.a.createElement(qe,null):s.a.createElement("div",{tabIndex:"0",className:"rs-toggle"+(o?" rs-focused":"")},null===n||t.isDropDown?t.placeholder:n.label);if(!t.searchable&&!1!==i){var l=i(n,t);l&&(c=l)}return s.a.createElement("div",{className:"rs-header",onClick:e.toggleSelect},!t.placeHolderInside&&!t.isDropDown&&null!==n&&s.a.createElement("span",{className:"rs-reset-wrapper vertical-align"},s.a.createElement("span",{className:"rs-reset",onClick:function(t){return e.clearSelect(t)}},"×")),t.arrow&&s.a.createElement("span",{className:"rs-arrow-wrapper vertical-align"},s.a.createElement("span",{className:"rs-arrow-indicator "+(r?"up":"down")})),c)});Ue.propTypes={settings:a.a.object.isRequired,isOpen:a.a.bool.isRequired,selected:a.a.object,toggleSelect:a.a.func.isRequired,clearSelect:a.a.func.isRequired,focused:a.a.bool.isRequired,onRenderSelection:a.a.oneOfType([a.a.func,a.a.bool]),tags:a.a.object.isRequired};var Me=Ue,Fe=Me,Be=("undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(Ue,"Header","D:/Projects/react-selectrix/src/components/App/partials/Header/Header.js"),__REACT_HOT_LOADER__.register(Me,"default","D:/Projects/react-selectrix/src/components/App/partials/Header/Header.js")),function(e){return{settings:e.settings,selected:e.selected.length>0?e.options[e.selectedIndex]:null,isOpen:e.isOpen,focused:e.focused,onRenderSelection:e.onRenderSelection,tags:e.tags}}),ze=function(e){return{toggleSelect:function(){e(ye())},clearSelect:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];t&&(t.stopPropagation(),t.nativeEvent.stopImmediatePropagation()),e(ve())}}},Ke=J(Be,ze)(Fe),$e=Ke,Ge=("undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(Be,"mapStateToProps","D:/Projects/react-selectrix/src/components/App/partials/Header/index.js"),__REACT_HOT_LOADER__.register(ze,"mapDispatchToProps","D:/Projects/react-selectrix/src/components/App/partials/Header/index.js"),__REACT_HOT_LOADER__.register(Ke,"default","D:/Projects/react-selectrix/src/components/App/partials/Header/index.js")),function(){function e(e,t){for(var r=0;r0&&s.a.createElement("span",{className:"rs-reset-wrapper vertical-align"},s.a.createElement("span",{className:"rs-reset",onClick:function(t){return e.clearSelect(t)}},"×")),t.arrow&&s.a.createElement("span",{className:"rs-arrow-wrapper vertical-align"},s.a.createElement("span",{className:"rs-arrow-indicator "+(r?"up":"down")})),s.a.createElement("div",{tabIndex:"0",className:o},this.getJSX()))}}]),t}(),Ve=We,Ye=Ve;We.propTypes={settings:a.a.object.isRequired,isOpen:a.a.bool.isRequired,selected:a.a.array,openSelect:a.a.func.isRequired,clearSelect:a.a.func.isRequired,toggleSelect:a.a.func.isRequired,selectedIndex:a.a.array.isRequired,options:a.a.array.isRequired,removeItem:a.a.func.isRequired,focused:a.a.bool.isRequired,ajax:a.a.object.isRequired,onRenderSelection:a.a.oneOfType([a.a.func,a.a.bool]),tags:a.a.object.isRequired},"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(We,"MultiHeader","D:/Projects/react-selectrix/src/components/App/partials/MultiHeader/MultiHeader.js"),__REACT_HOT_LOADER__.register(Ve,"default","D:/Projects/react-selectrix/src/components/App/partials/MultiHeader/MultiHeader.js"));var Je=function(e){return{settings:e.settings,selected:e.selected,selectedIndex:e.selectedIndex,options:e.options,isOpen:e.isOpen,focused:e.focused,ajax:e.ajax,onRenderSelection:e.onRenderSelection,tags:e.tags}},Qe=function(e){return{clearSelect:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];t&&(t.stopPropagation(),t.nativeEvent.stopImmediatePropagation()),e(ve())},removeItem:function(t){e(pe(t))},openSelect:function(){e(Se())},toggleSelect:function(){e(ye())}}},Ze=J(Je,Qe)(Ye),Xe=Ze,et=("undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(Je,"mapStateToProps","D:/Projects/react-selectrix/src/components/App/partials/MultiHeader/index.js"),__REACT_HOT_LOADER__.register(Qe,"mapDispatchToProps","D:/Projects/react-selectrix/src/components/App/partials/MultiHeader/index.js"),__REACT_HOT_LOADER__.register(Ze,"default","D:/Projects/react-selectrix/src/components/App/partials/MultiHeader/index.js")),function(e){return e.active?s.a.createElement("div",{className:"rs-search-prompt"},"Please enter ",e.requiredLength," or more characters"):null}),tt=et,rt=tt;et.propTypes={active:a.a.bool.isRequired,requiredLength:a.a.number.isRequired},"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(et,"SearchPrompt","D:/Projects/react-selectrix/src/components/App/partials/SearchPrompt/SearchPrompt.js"),__REACT_HOT_LOADER__.register(tt,"default","D:/Projects/react-selectrix/src/components/App/partials/SearchPrompt/SearchPrompt.js"));var nt=function(e){return{active:e.ajax.active&&e.ajax.fetchOnSearch&&e.ajax.searchPrompt&&e.search.queryString.length0&&(!e.ajax.active||e.ajax.minLength<=e.search.queryString.length&&!e.ajax.fetching)&&!e.tags.active,queryString:e.search.queryString}}),bt=J(_t,void 0)(gt),mt=bt,yt=("undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(_t,"mapStateToProps","D:/Projects/react-selectrix/src/components/App/partials/NoResults/index.js"),__REACT_HOT_LOADER__.register(bt,"default","D:/Projects/react-selectrix/src/components/App/partials/NoResults/index.js")),function(){function e(e,t){for(var r=0;r0&&this.props.maybeScroll(this.rsBodyRef,this["option-"+this.props.selectedIndex[0].toString()])}},{key:"componentWillUnmount",value:function(){document.removeEventListener("click",this.handleBodyClick),document.removeEventListener("touchstart",this.handleBodyClick),document.removeEventListener("keydown",this.handleKeyDown),document.removeEventListener("mousemove",this.handleMouseMove)}},{key:"handleBodyClick",value:function(e){this.ref.contains(e.target)||(this.props.isOpen?this.props.closeSelect(this.props.focused):this.props.focused&&this.props.blurSelect())}},{key:"handleKeyDown",value:function(e){this.props.focused&&this.props.handleKeyDown(e)}},{key:"checkIfHovered",value:function(){if(this.props.settings.stayOpen&&this.props.settings.multiple&&this.props.checkForHover&&!this.props.settings.commaSeperated&&!this.props.settings.checkBoxes&&!this.props.settings.isDropDown&&!this.props.tags.focused)for(var e=0;e=s?this.props.focusItem(e-1,!0):this.props.focusItem(e,!0);break}}}},{key:"maybeScroll",value:function(){if(this.props.isOpen&&this.props.checkForScroll)return this.props.tags.focused?this.props.maybeScroll(this.rsBodyRef,this.tagsRef):void(null!==this.props.focusedItem?this.props.maybeScroll(this.rsBodyRef,this["option-"+this.props.focusedItemIndex.toString()]):!this.props.settings.multiple&&this.props.selected.length>0&&this.props.maybeScroll(this.rsBodyRef,this["option-"+this.props.selectedIndex[0].toString()]))}},{key:"componentDidUpdate",value:function(){this.checkIfHovered(),this.maybeScroll()}},{key:"handleMouseMove",value:function(){this.props.mouseEventLocked&&this.props.unlockMouseFocus()}},{key:"buildOptionClassName",value:function(e){var t="rs-option";return e.hasOwnProperty("disabled")&&!0===e.disabled&&(t+=" disabled"),!this.props.settings.isDropDown&&this.props.selected.includes(e.key)&&(t+=" selected"),null!==this.props.focusedItem&&this.props.focusedItem===e.key&&(t+=" focused"),t.trim()}},{key:"render",value:function(){var e=this,t=this.props,r=t.options,n=t.settings,o=t.isOpen,i=t.selected,a=t.originalCount,c=t.ajax,l=t.onRenderOption,u=t.tags,p=t.queryString,d=se(n,o,i,u);return s.a.createElement("div",{className:"react-selectrix"+d,ref:function(t){return e.ref=t},onFocus:this.props.focusSelect},s.a.createElement("input",{type:"hidden",value:JSON.stringify(i)}),s.a.createElement("div",{className:"rs-wrapper"},n.multiple?s.a.createElement(Xe,null):s.a.createElement($e,null),(o||n.materialize)&&s.a.createElement("div",{className:"rs-body"+(o?"":" hidden"),ref:function(t){return e.rsBodyRef=t},style:{maxHeight:this.props.height}},s.a.createElement(dt,{extractRef:function(t){return e.tagsRef=t}}),n.selectAllButton&&s.a.createElement("div",{className:"rs-toggle-wrapper"},s.a.createElement("button",{type:"button",className:"rs-toggle-button",onClick:function(){return a>i.length||c.fetchOnSearch?e.props.selectAll():e.props.clearSelect(!1,!0)}},a>i.length||c.fetchOnSearch?"Select All":"Deselect All")),s.a.createElement(ot,null),s.a.createElement("ul",null,c.active&&c.fetching&&s.a.createElement("div",{className:"rs-loader"},"Loading..."),s.a.createElement(mt,{options:r}),n.placeHolderInside&&!n.multiple&&(!c.active||!c.fetching&&c.minLength<=p)&&s.a.createElement("li",{onClick:this.props.clearSelect,className:this.buildOptionClassName({key:"default"}),onMouseEnter:function(){return e.props.mouseEventLocked?"":e.props.focusItem(-1,!0)}},n.placeholder),r.map((function(t,r){var o=n.checkBoxes?s.a.createElement("span",{className:"rs-checkbox-wrapper"},s.a.createElement("input",{type:"checkbox",checked:e.props.selected.includes(t.key),readOnly:!0}),s.a.createElement("label",null,t.label)):t.label;if(!1!==l){var i=l(t,r);i&&(o=i)}return s.a.createElement("li",{ref:function(t){return e["option-"+r]=t},onClick:function(t){t.stopPropagation(),t.nativeEvent.stopImmediatePropagation(),e.props.selectItem(r)},key:"li-"+r,className:e.buildOptionClassName(t,r),onMouseOver:function(){e.props.mouseEventLocked?n.stayOpen&&e.props.unlockMouseFocus():e.props.focusItem(r,!0)}},o)}))))))}}]),t}(),Ot=xt,Et=Ot;function vt(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t1&&void 0!==arguments[1]&&arguments[1];e(we(t,r))},openSelect:function(){e(Se())},clearSelect:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],r=arguments.length>1&&void 0!==arguments[1]&&arguments[1];t&&(t.stopPropagation(),t.nativeEvent.stopImmediatePropagation()),e(ve(r))},closeSelect:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];e(je(t))},focusSelect:function(){e((function(e,t){t().focused||e({type:"FOCUS_SELECT"})}))},blurSelect:function(){e({type:"BLUR_SELECT"})},handleKeyDown:function(t){e(Re(t))},maybeScroll:function(t,r){e(Ce(t,r))},unlockMouseFocus:function(){e({type:"UNLOCK_MOUSE_FOCUS"})},selectAll:function(){e(Oe())}}},jt=J(St,Tt)(Et),Rt=jt,At=("undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(St,"mapStateToProps","D:/Projects/react-selectrix/src/components/App/index.js"),__REACT_HOT_LOADER__.register(Tt,"mapDispatchToProps","D:/Projects/react-selectrix/src/components/App/index.js"),__REACT_HOT_LOADER__.register(jt,"default","D:/Projects/react-selectrix/src/components/App/index.js")),function(){function e(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:Ft,t=arguments[1];switch(t.type){case"FOCUS_TAG":return Object.assign({},e,{tags:Object.assign({},e.tags,{focused:!0}),focusedItem:null,focusedItemIndex:null,checkForScroll:!0});case"CREATE_TAG":return Object.assign({},e,{options:t.options,search:Object.assign({},e.search,{resultSet:t.resultSet}),tags:Object.assign({},e.tags,{tagSet:[].concat(Mt(e.tags.tagSet),[t.tag])})});case"SELECT_ALL":var r=e.ajax.fetchOnSearch?e.selected.map((function(e){return e.key})):e.selected,n=e.search.active?[].concat(Mt(e.search.resultSet)):[].concat(Mt(e.options));n=n.filter((function(e){return!r.includes(e.key)}));var s=[].concat(Mt(n)).map((function(e){return e.key})),o=e.ajax.fetchOnSearch?n:s,i=[].concat(Mt(e.options)).map((function(e,t){return s.includes(e.key)?t:null})).filter((function(e){return null!==e}));return e.settings.lifo&&(o=o.reverse(),i=i.reverse()),Object.assign({},e,{selected:e.settings.lifo?[].concat(Mt(o),Mt(e.selected)):[].concat(Mt(e.selected),Mt(o)),selectedIndex:e.settings.lifo?[].concat(Mt(i),Mt(e.selectedIndex)):[].concat(Mt(e.selectedIndex),Mt(i))});case"REMOVE_ITEM":return Object.assign({},e,{selected:e.ajax.active&&e.ajax.fetchOnSearch?[].concat(Mt(e.selected)).filter((function(e){return e.key!==t.index})):[].concat(Mt(e.selected)).filter((function(r){return r!==e.options[t.index].key})),selectedIndex:[].concat(Mt(e.selectedIndex)).filter((function(e){return e!==t.index}))});case"SET_QUERY_STRING":return Object.assign({},e,{search:Object.assign({},e.search,{active:!0,queryString:t.queryString,resultSet:[]}),ajax:Object.assign({},e.ajax,{fetching:t.queryString.length>=e.ajax.minLength}),options:[]});case"SETUP_INSTANCE":case"UPDATE_INSTANCE":return Object.assign({},e,{settings:Object.assign({},e.settings,{className:t.props.className,placeHolderInside:!t.props.multiple&&t.props.placeHolderInside,placeholder:t.props.placeholder,arrow:t.props.arrow,multiple:t.props.multiple,disabled:t.props.disabled,searchIndex:t.props.searchIndex,customScrollbar:t.props.customScrollbar,searchable:t.props.searchable,noResultsMessage:t.props.noResultsMessage,stayOpen:t.props.hasOwnProperty("stayOpen")?t.props.stayOpen&&!t.props.isDropDown:!!t.props.multiple,commaSeperated:t.props.multiple&&t.props.commaSeperated,singleLine:t.props.singleLine,lifo:t.props.multiple&&t.props.lifo,selectAllButton:t.props.multiple&&t.props.selectAllButton,checkBoxes:t.props.checkBoxes,materialize:t.props.materialize,isDropDown:t.props.isDropDown&&!t.props.multiple}),options:"UPDATE_INSTANCE"===t.type&&e.ajax.active&&e.settings.multiple===t.props.multiple?e.options:t.options,height:t.props.height,isOpen:t.props.isOpen?t.props.isOpen:"UPDATE_INSTANCE"===t.type&&e.isOpen,selected:"UPDATE_INSTANCE"===t.type&&e.ajax.active&&e.settings.multiple===t.props.multiple?e.selected:t.selected,selectedIndex:"UPDATE_INSTANCE"===t.type&&e.ajax.active&&e.settings.multiple===t.props.multiple?e.selectedIndex:t.selectedIndex,customKeys:t.customKeys,ajax:t.ajax,initialized:!0,onChange:t.props.onChange,onOpen:t.props.onOpen,onClose:t.props.onClose,checkForScroll:"UPDATE_INSTANCE"===t.type?e.isOpen:t.props.isOpen,onRenderOption:t.props.onRenderOption,onRenderSelection:t.props.onRenderSelection,tags:Object.assign({},e.tags,{enabled:t.props.tags}),id:t.props.id});case"SET_TAG":return Object.assign({},e,{tags:Object.assign({},e.tags,{enabled:e.tags.enabled,active:e.tags.enabled&&t.tag.length>0&&t.tag.trim()&&void 0===e.options.find((function(e){return e.label===t.tag}))}),search:Object.assign({},e.search,{queryString:t.tag})});case"SEARCH_OPTIONS":var a=t.queryString.toLowerCase();return Object.assign({},e,{search:Object.assign({},e.search,{active:!0,queryString:t.queryString,resultSet:e.ajax.active&&e.ajax.fetchOnSearch?t.queryString.length0&&t.queryString.trim()&&void 0===e.options.find((function(e){return e.label===t.queryString}))})});case"CHECK_FOR_SCROLL":return Object.assign({},e,{checkForScroll:!0});case"CLEAR_SEARCH":return Object.assign({},e,{search:Ft.search,focusedItem:null,focusedItemIndex:null,options:e.ajax.active&&e.ajax.fetchOnSearch?[]:e.options,tags:Object.assign({},e.tags,{enabled:e.tags.enabled,active:!1})});case"CLOSE_SELECT":return Object.assign({},e,{isOpen:!1,focusedItem:null,focusedItemIndex:null,search:Ft.search,options:e.ajax.fetchOnSearch&&e.settings.multiple?[]:e.options,ajax:Object.assign({},e.ajax,{fetching:!1}),tags:Object.assign({},e.tags,{enabled:e.tags.enabled,active:!1}),focused:!t.blur&&e.focused});case"OPEN_SELECT":return Object.assign({},e,{isOpen:!0,checkForScroll:!0});case"CLEAR_SELECT":return Object.assign({},e,{selected:[],selectedIndex:[],focusedItem:null,focusedItemIndex:null,search:t.stayOpen?e.search:Ft.search,options:e.ajax.fetchOnSearch?[]:e.options,tags:Object.assign({},e.tags,{enabled:e.tags.enabled,active:!1})});case"FOCUS_SELECT":return Object.assign({},e,{focused:!0});case"BLUR_SELECT":return Object.assign({},e,{focused:!1});case"SELECT_ITEM":return Object.assign({},e,{selected:e.settings.multiple?e.settings.lifo?[e.ajax.active&&e.ajax.fetchOnSearch?t.item:t.item.key].concat(Mt(e.selected)):[].concat(Mt(e.selected),[e.ajax.active&&e.ajax.fetchOnSearch?t.item:t.item.key]):[t.item.key],selectedIndex:e.settings.multiple?e.settings.lifo?[t.index].concat(Mt(e.selectedIndex)):[].concat(Mt(e.selectedIndex),[t.index]):[t.index],focusedItem:e.settings.stayOpen&&e.selected.length 2 | 3 | 4 | 5 | 6 | React Selectrix 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-selectrix", 3 | "version": "1.0.17", 4 | "description": "A beautiful, clean coded select replacement for React.js", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server", 9 | "build": "webpack --config webpack.config.production.js", 10 | "update:packages": "node wipe-dependencies.js && rm -rf node_modules && npm update --save-dev && npm update --save" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/stratos-vetsos/react-selectrix.git" 15 | }, 16 | "author": "Stratos Vetsos", 17 | "license": "MIT", 18 | "dependencies": { 19 | "prop-types": "^15.6.1" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.26.0", 23 | "babel-eslint": "^8.0.3", 24 | "babel-loader": "^7.1.2", 25 | "babel-preset-env": "^1.6.1", 26 | "babel-preset-react": "^6.24.1", 27 | "babel-preset-stage-2": "^6.24.1", 28 | "browser-sync": "^2.18.13", 29 | "css-loader": "^0.28.7", 30 | "eslint": "^4.13.0", 31 | "eslint-loader": "^2.0.0", 32 | "eslint-plugin-react": "^7.5.1", 33 | "extract-text-webpack-plugin": "^3.0.2", 34 | "gulp": "^3.9.1", 35 | "gulp-autoprefixer": "^4.0.0", 36 | "gulp-sass": "^3.1.0", 37 | "gulp-sourcemaps": "^2.6.1", 38 | "gulp-util": "^3.0.8", 39 | "node-sass": "^4.7.2", 40 | "react": "^16.3.2", 41 | "react-dom": "^16.3.2", 42 | "react-hot-loader": "^3.1.3", 43 | "react-redux": "^5.0.7", 44 | "redux": "^4.0.0", 45 | "redux-logger": "^3.0.6", 46 | "redux-thunk": "^2.2.0", 47 | "rimraf": "^2.6.2", 48 | "sass-loader": "^6.0.6", 49 | "style-loader": "^0.19.0", 50 | "uglifyjs-webpack-plugin": "^1.2.5", 51 | "webpack": "^4.8.1", 52 | "webpack-bundle-analyzer": "^3.6.0", 53 | "webpack-cli": "^3.3.11", 54 | "webpack-bundle-analyzer": "^3.6.0", 55 | "webpack-cli": "^2.1.3", 56 | "webpack-dev-server": "^3.1.4" 57 | }, 58 | "peerDependencies": { 59 | "react": "*", 60 | "react-dom": "*" 61 | }, 62 | "keywords": [ 63 | "select", 64 | "multiselect", 65 | "react-select", 66 | "input", 67 | "combobox", 68 | "dropdown", 69 | "form", 70 | "tags", 71 | "material-design", 72 | "autocomplete" 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /selectrix.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-selectrix" { 2 | class Selectrix extends React.Component {} 3 | 4 | interface SelectrixProps { 5 | ajax?: boolean | object; 6 | arrow?: boolean; 7 | checkBoxes?: boolean; 8 | className?: string; 9 | commaSeperated?: boolean; 10 | customKeys?: boolean | SelectrixOption | SelectrixOption[]; 11 | customScrollbar?: boolean; 12 | defaultValue?: boolean | string | SelectrixOption[]; 13 | disabled?: boolean; 14 | height?: number; 15 | id?: string; 16 | initialized?: boolean; 17 | isDropDown?: boolean; 18 | isOpen?: boolean; 19 | lifo?: boolean; 20 | materialize?: boolean; 21 | multiple?: boolean; 22 | onChange?: Function; 23 | onClose?: Function; 24 | onOpen?: Function; 25 | onRenderOption?: boolean | Function; 26 | onRenderSelection?: boolean | Function; 27 | options?: SelectrixOption[]; 28 | placeHolderInside?: boolean; 29 | placeholder?: string; 30 | searchable?: boolean; 31 | selectAllButton?: boolean; 32 | singleLine?: boolean; 33 | stayOpen?: boolean; 34 | tags?: boolean; 35 | updateInstance?: Function; 36 | } 37 | 38 | interface SelectrixOption { 39 | key: string; 40 | label: string; 41 | disabled?: boolean; 42 | } 43 | 44 | export default Selectrix; 45 | } 46 | -------------------------------------------------------------------------------- /src/PlayGround.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Selectrix from 'components/'; 3 | 4 | export default class PlayGround extends React.Component { 5 | 6 | constructor( props ) { 7 | super( props ); 8 | this.state = { 9 | value: '', 10 | multiple: true, 11 | searchable: true 12 | }; 13 | 14 | this.setValue = this.setValue.bind( this ); 15 | this.onRenderOption = this.onRenderOption.bind( this ); 16 | this.onRenderSelection = this.onRenderSelection.bind( this ); 17 | } 18 | 19 | setValue( value ) { 20 | this.setState( { value } ); 21 | } 22 | 23 | onRenderOption( option ) { 24 | return( 25 |
  • { option.label }
  • 26 | ) 27 | } 28 | 29 | onRenderSelection( selected, settings, deselect ) { 30 | return( 31 |

    remove{ selected.label }

    32 | ) 33 | } 34 | 35 | render() { 36 | 37 | let options = [ 38 | { 39 | value: 'a10', 40 | label: 'Option A 10' 41 | }, 42 | { 43 | value: 'b10', 44 | label: 'Option B' 45 | }, 46 | { 47 | value: 'c', 48 | label: 'Option C' 49 | }, 50 | { 51 | value: 'd', 52 | label: 'Option D' 53 | }, 54 | { 55 | value: 'e', 56 | label: 'Option E' 57 | }, 58 | { 59 | value: 'f', 60 | label: 'Option F' 61 | } 62 | ]; 63 | 64 | return( 65 |
    66 |
    Current value is : 67 | { this.state.value !== '' && 68 | 69 | { this.state.multiple 70 | ? ` ${ this.state.value.map( v => v.label ).join( ', ' ) }` 71 | : this.state.value.label 72 | } 73 | 74 | } 75 |
    76 | 109 |
    110 | ) 111 | 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | import { normalizeSelected, isInViewport, isEmpty, isNumeric, isString, isArray, isObject } from 'helpers'; 2 | 3 | export const SETUP_INSTANCE = 'SETUP_INSTANCE'; 4 | export const UPDATE_INSTANCE = 'UPDATE_INSTANCE'; 5 | export const CLOSE_SELECT = 'CLOSE_SELECT'; 6 | export const OPEN_SELECT = 'OPEN_SELECT'; 7 | export const SELECT_ITEM = 'SELECT_ITEM'; 8 | export const SELECT_MULTI_ITEM = 'SELECT_MULTI_ITEM'; 9 | export const FOCUS_ITEM = 'FOCUS_ITEM'; 10 | export const CLEAR_SELECT = 'CLEAR_SELECT'; 11 | export const FOCUS_SELECT = 'FOCUS_SELECT'; 12 | export const BLUR_SELECT = 'BLUR_SELECT'; 13 | export const SCROLL_SELECT = 'SCROLL_SELECT'; 14 | export const SEARCH_OPTIONS = 'SEARCH_OPTIONS'; 15 | export const UNLOCK_MOUSE_FOCUS = 'UNLOCK_MOUSE_FOCUS'; 16 | export const REMOVE_ITEM = 'REMOVE_ITEM'; 17 | export const CLEAR_SEARCH = 'CLEAR_SEARCH'; 18 | export const CHECK_FOR_SCROLL = 'CHECK_FOR_SCROLL'; 19 | export const SELECT_ALL = 'SELECT_ALL'; 20 | export const FETCHING_OPTIONS = 'FETCHING_OPTIONS'; 21 | export const SETUP_AJAX_OPTIONS = 'SETUP_AJAX_OPTIONS'; 22 | export const CLEAR_OPTIONS = 'CLEAR_OPTIONS'; 23 | export const SET_QUERY_STRING = 'SET_QUERY_STRING'; 24 | export const CREATE_TAG = 'CREATE_TAG'; 25 | export const FOCUS_TAG = 'FOCUS_TAG'; 26 | export const SET_TAG = 'SET_TAG'; 27 | 28 | export const createTag = ( tag ) => { 29 | return ( dispatch, getState ) => { 30 | 31 | let state = getState(); 32 | const tagObj = { key: `tag-${ tag }`, label: tag }; 33 | const options = [ ... state.options, tagObj ]; 34 | const resultSet = [ ...state.search.resultSet, tagObj ]; 35 | 36 | dispatch( { 37 | type: CREATE_TAG, 38 | tag: tagObj, 39 | options, 40 | resultSet 41 | } ); 42 | 43 | state = getState(); 44 | let dataSet = [ ... state.settings.searchable ? state.search.resultSet : state.options ]; 45 | if( state.settings.commaSeperated || state.settings.checkBoxes ) { 46 | dataSet = dataSet.filter( o => ! state.selected.includes( o .key ) ); 47 | } 48 | 49 | dispatch( selectItem( dataSet.length - 1 ) ); 50 | dispatch( { 51 | type: CLEAR_SEARCH 52 | } ); 53 | 54 | } 55 | } 56 | 57 | export const removeItem = ( index ) => { 58 | 59 | return ( dispatch, getState ) => { 60 | 61 | dispatch( { 62 | type: REMOVE_ITEM, 63 | index 64 | } ); 65 | 66 | const state = getState(); 67 | if( ! state.isOpen ) { 68 | dispatch( openSelect() ); 69 | } 70 | 71 | state.onChange( [ ... state.selectedIndex ].map( i => { 72 | return state.customKeys ? Object.assign( {}, { 73 | [ state.customKeys.key ]: state.options[ i ].key, 74 | [ state.customKeys.label ]: state.options[ i ].label 75 | } ) : state.options[ i ]; 76 | } ) ); 77 | 78 | } 79 | 80 | } 81 | 82 | export const checkForScroll = () => { 83 | return { 84 | type: CHECK_FOR_SCROLL 85 | } 86 | } 87 | 88 | export const setupInstance = ( props, update = false ) => { 89 | 90 | return ( dispatch, getState ) => { 91 | 92 | const state = getState(); 93 | 94 | let customKeys = {}, 95 | options = [ ... props.options ]; 96 | 97 | if( update && state.tags.tagSet.length > 0 ) { 98 | options = [ ... props.options, ... state.tags.tagSet ]; 99 | } 100 | 101 | const ajax = { 102 | active: false, 103 | url: '', 104 | debounce: 200, 105 | fetchOnSearch: false, 106 | q: '', 107 | fetching: false, 108 | needsUpdate: true, 109 | nestedKey: false, 110 | searchPrompt: true, 111 | minLength: 1, 112 | headers: {} 113 | }; 114 | 115 | if( props.customKeys ) { 116 | const target = [ 'key', 'label' ]; 117 | Object.keys( props.customKeys ).forEach( key => { 118 | if( target.includes( key ) ) { 119 | customKeys[ key ] = props.customKeys[ key ]; 120 | } 121 | } ); 122 | } 123 | 124 | customKeys = isEmpty( customKeys ) ? false : Object.assign( { key: 'key', label: 'label' }, customKeys ); 125 | 126 | if( customKeys ) { 127 | options = options.map( o => { 128 | if( o.hasOwnProperty( customKeys.key ) && o.hasOwnProperty( customKeys.label ) ) { 129 | return { 130 | key: o[ customKeys.key ], 131 | label: o[ customKeys.label ] 132 | }; 133 | } 134 | return null; 135 | } ).filter( x => x ); 136 | } 137 | 138 | let { selected, selectedIndex } = props.defaultValue 139 | ? normalizeSelected( props.defaultValue, [ ... options ] ) 140 | : { selected: state.selected, selectedIndex: state.selectedIndex }; 141 | 142 | if( props.ajax && props.ajax.hasOwnProperty( 'url' ) && props.ajax.url !== '' ) { 143 | 144 | options = selected = selectedIndex = []; 145 | ajax.active = true; 146 | ajax.url = props.ajax.url; 147 | 148 | if( props.ajax.hasOwnProperty( 'debounce' ) && isNumeric( props.ajax.debounce ) ) { 149 | ajax.debounce = props.ajax.debounce; 150 | } 151 | 152 | if( props.ajax.fetchOnSearch && ! isEmpty( props.ajax.q ) && isString( props.ajax.q ) ) { 153 | ajax.fetchOnSearch = true; 154 | ajax.q = props.ajax.q; 155 | } 156 | 157 | if( props.ajax.hasOwnProperty( 'nestedKey' ) && isString( props.ajax.nestedKey ) ) { 158 | ajax.nestedKey = props.ajax.nestedKey; 159 | } 160 | 161 | if( props.ajax.hasOwnProperty( 'searchPrompt' ) ) { 162 | ajax.searchPrompt = ajax.fetchOnSearch && props.ajax.searchPrompt === false; 163 | } 164 | 165 | if( props.ajax.hasOwnProperty( 'minLength' ) && isNumeric( props.ajax.minLength ) && ajax.fetchOnSearch ) { 166 | ajax.minLength = props.ajax.minLength; 167 | } 168 | 169 | if( props.ajax.hasOwnProperty( 'headers' ) && isObject( props.ajax.headers ) ) { 170 | ajax.headers = props.ajax.headers; 171 | } 172 | 173 | } 174 | 175 | dispatch( { 176 | type: update ? UPDATE_INSTANCE : SETUP_INSTANCE, 177 | props, 178 | selected, 179 | selectedIndex, 180 | options, 181 | customKeys, 182 | ajax 183 | } ); 184 | } 185 | 186 | 187 | } 188 | 189 | export const clearOptions = () => { 190 | return { 191 | type: CLEAR_OPTIONS 192 | } 193 | } 194 | 195 | export const setQueryString = ( queryString ) => { 196 | return { 197 | type: SET_QUERY_STRING, 198 | queryString 199 | } 200 | } 201 | 202 | 203 | export const setTag = ( queryString ) => { 204 | return ( dispatch, getState ) => { 205 | if( ! getState().isOpen ) { 206 | dispatch( openSelect() ); 207 | } 208 | dispatch( { 209 | type: SET_TAG, 210 | tag: queryString 211 | } ); 212 | dispatch( focusTag() ) 213 | } 214 | } 215 | 216 | 217 | export const searchOptions = ( queryString ) => { 218 | 219 | return ( dispatch, getState ) => { 220 | 221 | if( queryString !== '' ) { 222 | 223 | const state = getState(); 224 | 225 | if( ! state.isOpen ) { 226 | dispatch( openSelect() ); 227 | } 228 | 229 | if( state.ajax.active && state.ajax.fetchOnSearch && queryString.length >= state.ajax.minLength ) { 230 | dispatch( clearOptions() ); 231 | return dispatch( fetchOptions() ) 232 | .then( dispatch( findFocusedItem() ) ) 233 | .catch( err => console.error( err ) ) 234 | } 235 | 236 | dispatch( { 237 | type: SEARCH_OPTIONS, 238 | queryString 239 | } ) 240 | 241 | } 242 | else { 243 | dispatch( { 244 | type: CLEAR_SEARCH 245 | } ) 246 | } 247 | 248 | const state = getState(); 249 | 250 | if( state.tags.active ) { 251 | return dispatch( focusTag() ); 252 | } 253 | 254 | if( state.settings.multiple ) { 255 | return dispatch( focusItem( 0 ) ); 256 | } 257 | 258 | state.search.active ? dispatch( focusItem( 0 ) ) : dispatch( checkForScroll() ); 259 | 260 | } 261 | } 262 | 263 | export const focusTag = () => { 264 | return { 265 | type: FOCUS_TAG 266 | } 267 | } 268 | 269 | export const fetchOptions = () => { 270 | 271 | return( dispatch, getState ) => { 272 | return new Promise( ( resolve, reject ) => { 273 | 274 | let state = getState(); 275 | if( ! state.isOpen ) { 276 | return; 277 | } 278 | 279 | dispatch( { type: FETCHING_OPTIONS } ); 280 | 281 | let url = state.ajax.url; 282 | 283 | if( state.ajax.fetchOnSearch ) { 284 | url += state.ajax.q.replace( '{q}', state.search.queryString ); 285 | } 286 | 287 | fetch( url, { headers: state.ajax.headers } ) 288 | .then( res => { 289 | 290 | if( ! res.ok ) { 291 | throw `Your ajax url ${ state.ajax.url } failed with a status ${ res.status }`; 292 | } 293 | 294 | const contentType = res.headers.get( 'content-type' ); 295 | if( contentType && contentType.includes( 'application/json' ) ) { 296 | return res.json(); 297 | } 298 | else { 299 | throw `Your ajax url ${ state.ajax.url } response was not a json`; 300 | } 301 | 302 | } ) 303 | .then( data => { 304 | if( state.ajax.nestedKey ) { 305 | if( ! data.hasOwnProperty( state.ajax.nestedKey ) ) { 306 | throw `Invalid nested key on ${ state.ajax.url } response`; 307 | } 308 | else { 309 | data = data[ state.ajax.nestedKey ]; 310 | } 311 | } 312 | if( ! isArray( data ) ) { 313 | throw `Invalid data type on ${ state.ajax.url } response. Expected array.`; 314 | } 315 | dispatch( setupAjaxOptions( data ) ); 316 | resolve( data ); 317 | } ) 318 | .catch( err => { 319 | reject( err ); 320 | } ) 321 | 322 | } ) 323 | 324 | } 325 | 326 | } 327 | 328 | export const setupAjaxOptions = ( data ) => { 329 | 330 | return( dispatch, getState ) => { 331 | 332 | let state = getState(); 333 | if( ! state.isOpen ) { 334 | return; 335 | } 336 | 337 | const key = state.customKeys && state.customKeys.hasOwnProperty( 'key' ) ? state.customKeys.key : 'key'; 338 | const labelKey = state.customKeys && state.customKeys.hasOwnProperty( 'label' ) ? state.customKeys.label : 'label'; 339 | 340 | const options = data.map( d => { 341 | 342 | if( ! d.hasOwnProperty( key ) || ! d.hasOwnProperty( labelKey ) ) { 343 | return null; 344 | } 345 | 346 | let item = { 347 | key: d[ key ], 348 | label: d[ labelKey ], 349 | }; 350 | 351 | if( d.hasOwnProperty( 'disabled' ) && d.disabled ) { 352 | item[ 'disabled' ] = true; 353 | } 354 | 355 | return item; 356 | 357 | } ).filter( x => x ); 358 | 359 | 360 | dispatch( { 361 | type: SETUP_AJAX_OPTIONS, 362 | options 363 | } ) 364 | 365 | } 366 | 367 | } 368 | 369 | export const toggleSelect = () => { 370 | 371 | return ( dispatch, getState ) => { 372 | 373 | let state = getState(); 374 | state.isOpen ? dispatch( closeSelect() ) : dispatch( openSelect() ); 375 | 376 | } 377 | } 378 | 379 | export const returnValue = () => { 380 | return ( dispatch, getState ) => { 381 | const state = getState(); 382 | const { selected, selectedIndex, ajax, options } = state; 383 | const multiple = state.settings.multiple; 384 | 385 | if( multiple ) { 386 | if( ajax.fetchOnSearch ) { 387 | return state.onChange( selected ); 388 | } 389 | state.onChange( [ ... selectedIndex ].map( i => options[ i ] ) ); 390 | } 391 | else { 392 | state.onChange( options[ selectedIndex[ 0 ] ] ); 393 | } 394 | } 395 | } 396 | 397 | export const selectAll = () => { 398 | 399 | return ( dispatch, getState ) => { 400 | dispatch( { 401 | type: SELECT_ALL 402 | } ) 403 | 404 | const state = getState(); 405 | dispatch( returnValue() ); 406 | 407 | if( ! state.settings.stayOpen ) { 408 | dispatch( closeSelect() ); 409 | } 410 | 411 | } 412 | } 413 | 414 | export const selectItem = ( index, isKeyboard = false ) => { 415 | 416 | return ( dispatch, getState ) => { 417 | 418 | if( index === -1 ) { 419 | return dispatch( clearSelect() ); 420 | } 421 | 422 | let state = getState(); 423 | let options = state.search.active ? state.search.resultSet : state.options; 424 | const selected = state.ajax.fetchOnSearch ? state.selected.map( s => s.key ) : state.selected; 425 | 426 | if( state.settings.multiple && ! state.settings.commaSeperated && ! state.settings.checkBoxes ) { 427 | options = [ ... options ].filter( o => ! selected.includes( o.key ) ); 428 | } 429 | 430 | const targetIndex = state.search.active || ( state.settings.multiple && ! state.settings.commaSeperated && ! state.settings.checkBoxes ) 431 | ? state.options.findIndex( o => o.key === options[ index ].key ) 432 | : index; 433 | 434 | if( ( state.settings.commaSeperated || state.settings.checkBoxes ) && state.selectedIndex.includes( targetIndex ) ) { 435 | return dispatch( removeItem( targetIndex ) ); 436 | } 437 | else { 438 | if( options[ index ] ) { 439 | dispatch( { 440 | type: SELECT_ITEM, 441 | item: options[ index ], 442 | index: state.search.active || ( state.settings.multiple && selected.length ) ? targetIndex : index, 443 | isKeyboard 444 | } ) 445 | if( ! state.settings.stayOpen ) { 446 | dispatch( closeSelect() ); 447 | } 448 | } 449 | } 450 | 451 | state = getState(); 452 | 453 | if( state.isOpen && state.settings.multiple && ! state.settings.checkBoxes && ! state.settings.commaSeperated && ! state.settings.isDropDown ) { 454 | 455 | if( isKeyboard ) { 456 | index === options.length - 1 ? dispatch( focusItem( index - 1 ) ) : dispatch( focusItem( index ) ) 457 | } 458 | else { 459 | index === options.length - 1 ? dispatch( focusItem( index - 1 ) ) : ''; 460 | } 461 | 462 | } 463 | 464 | dispatch( returnValue () ); 465 | 466 | } 467 | } 468 | 469 | export const clearSelect = ( stayOpen = false ) => { 470 | 471 | return ( dispatch, getState ) => { 472 | 473 | const state = getState(); 474 | dispatch( { type: CLEAR_SELECT, stayOpen } ) 475 | state.onChange( '' ); 476 | if( ! state.settings.stayOpen ) { 477 | dispatch( closeSelect() ); 478 | } 479 | 480 | } 481 | } 482 | 483 | export const openSelect = () => { 484 | 485 | return ( dispatch, getState ) => { 486 | 487 | dispatch( { 488 | type: OPEN_SELECT 489 | } ); 490 | 491 | const state = getState(); 492 | state.onOpen(); 493 | 494 | if( state.ajax.active && ! state.ajax.fetchOnSearch && state.ajax.needsUpdate ) { 495 | dispatch( fetchOptions() ) 496 | .then( () => dispatch( findFocusedItem() ) ) 497 | .catch( err => console.error( err ) ) 498 | return; 499 | } 500 | 501 | dispatch( findFocusedItem() ); 502 | 503 | } 504 | 505 | } 506 | 507 | export const findFocusedItem = () => { 508 | 509 | return ( dispatch, getState ) => { 510 | 511 | const state = getState(); 512 | 513 | if( state.settings.isDropDown ) { 514 | return dispatch( focusItem( 0 ) ); 515 | } 516 | 517 | if( state.isOpen && ( state.selected.length === 0 || state.settings.multiple || state.settings.checkBoxes ) && state.focusedItem === null ) { 518 | if( state.settings.checkBoxes && ! state.settings.multiple ) { 519 | dispatch( focusItem( state.selectedIndex[ 0 ] ) ); 520 | } 521 | else if( state.settings.multiple && state.settings.commaSeperated && state.selected.length > 0 ) { 522 | const index = state.settings.lifo ? state.selectedIndex[ 0 ] : state.selectedIndex[ state.selectedIndex.length - 1 ]; 523 | dispatch( focusItem( index ) ); 524 | } 525 | else { 526 | dispatch( moveFocus( 'down' ) ); 527 | } 528 | 529 | } 530 | 531 | } 532 | 533 | } 534 | 535 | export const closeSelect = ( blur = false ) => { 536 | 537 | return ( dispatch, getState ) => { 538 | 539 | dispatch( { 540 | type: CLOSE_SELECT, 541 | blur 542 | } ); 543 | 544 | getState().onClose(); 545 | 546 | } 547 | 548 | } 549 | 550 | export const focusSelect = () => { 551 | 552 | return ( dispatch, getState ) => { 553 | if( ! getState().focused ) { 554 | dispatch( { 555 | type: FOCUS_SELECT 556 | } ) 557 | } 558 | } 559 | 560 | } 561 | 562 | export const blurSelect = () => { 563 | 564 | return { 565 | type: BLUR_SELECT 566 | } 567 | 568 | } 569 | 570 | export const handleKeyDown = ( e ) => { 571 | 572 | return ( dispatch, getState ) => { 573 | 574 | const state = getState(); 575 | const key = e.key; 576 | 577 | switch( key ) { 578 | 579 | case 'Tab': { 580 | 581 | if( state.isOpen ) { 582 | dispatch( closeSelect( true ) ); 583 | } else if( state.focused ) { 584 | dispatch( blurSelect() ); 585 | } 586 | 587 | break; 588 | } 589 | 590 | case 'Enter': { 591 | e.preventDefault(); 592 | if( state.isOpen ) { 593 | if( state.focusedItem !== null ) { 594 | dispatch( selectItem( state.focusedItemIndex, true ) ); 595 | } 596 | else { 597 | if( state.tags.active ) { 598 | return dispatch( createTag( state.search.queryString ) ); 599 | } 600 | dispatch( closeSelect() ); 601 | } 602 | } 603 | else { 604 | dispatch( openSelect() ); 605 | } 606 | 607 | break; 608 | } 609 | case 'Esc': 610 | case 'Escape': { 611 | dispatch( closeSelect() ); 612 | break; 613 | } 614 | case 'Up': 615 | case 'Left': 616 | case 'ArrowUp': 617 | case 'ArrowLeft': { 618 | if( state.search.active && state.search.queryString.length > 0 && [ 'Left', 'ArrowLeft' ].includes( key ) ) { 619 | return; 620 | } 621 | e.preventDefault(); 622 | dispatch( moveFocus( 'up' ) ); 623 | break; 624 | } 625 | 626 | case 'Down': 627 | case 'Right': 628 | case 'ArrowDown': 629 | case 'ArrowRight': { 630 | if( state.search.active && state.search.queryString.length > 0 && [ 'Right', 'ArrowRight' ].includes( key ) ) { 631 | return; 632 | } 633 | e.preventDefault(); 634 | dispatch( moveFocus( 'down' ) ); 635 | break; 636 | } 637 | 638 | } 639 | } 640 | } 641 | 642 | export const moveFocus = ( direction ) => { 643 | 644 | return ( dispatch, getState ) => { 645 | 646 | const state = getState(); 647 | const placeHolderInside = ! state.settings.multiple && state.settings.placeHolderInside; 648 | let options = state.search.active ? state.search.resultSet : state.options; 649 | 650 | if( state.settings.multiple && ! state.settings.commaSeperated && ! state.settings.checkBoxes ) { 651 | options = [ ... options ].filter( o => ! state.selected.includes( o.key ) ); 652 | } 653 | 654 | let index = false, 655 | targetIndex = false; 656 | 657 | if( state.focusedItem !== null ) { 658 | index = state.focusedItemIndex; 659 | } 660 | else { 661 | if( state.selected.length > 0 && ! state.settings.multiple && ! state.settings.isDropDown ) { 662 | if( state.search.active ) { 663 | index = options.findIndex( o => o.key === state.options[ state.selectedIndex ].key ); 664 | if( index === -1 ) { 665 | index = false; 666 | } 667 | } 668 | else { 669 | index = state.selectedIndex[ 0 ]; 670 | } 671 | } 672 | } 673 | 674 | if( index !== false ) { 675 | if( direction === 'up' ) { 676 | if( state.tags.active && index === 0 ) { 677 | targetIndex = 'tag'; 678 | } 679 | else { 680 | targetIndex = index > 0 || placeHolderInside ? index - 1 : 0; 681 | } 682 | } 683 | else { 684 | targetIndex = index + 1 < options.length ? index + 1 : options.length - 1; 685 | } 686 | } 687 | else { 688 | if( state.tags.active && direction === 'up' ) { 689 | targetIndex = 'tag'; 690 | } 691 | else { 692 | targetIndex = direction === 'up' ? options.length - 1 : placeHolderInside ? -1 : 0; 693 | } 694 | } 695 | 696 | if( targetIndex !== false ) { 697 | if( state.isOpen === false ) { 698 | dispatch( openSelect() ); 699 | } 700 | targetIndex === 'tag' ? dispatch( focusTag() ) : dispatch( focusItem( targetIndex, false ) ); 701 | } 702 | 703 | } 704 | } 705 | 706 | export const maybeScroll = ( selectEl, itemEl ) => { 707 | 708 | return ( dispatch ) => { 709 | 710 | const scroll = isInViewport( selectEl, itemEl ); 711 | let scrollNum = 0; 712 | 713 | if( scroll ) { 714 | scrollNum = scroll; 715 | selectEl.scrollTop = scroll; 716 | } 717 | 718 | dispatch( { 719 | type: SCROLL_SELECT, 720 | active: scroll !== false, 721 | scroll: scrollNum 722 | } ) 723 | } 724 | } 725 | 726 | export const focusItem = ( index, mouseEvent ) => { 727 | 728 | return ( dispatch, getState ) => { 729 | 730 | const state = getState(); 731 | let options = state.search.active ? state.search.resultSet : state.options; 732 | const selected = state.ajax.fetchOnSearch ? state.selected.map( s => s.key ) : state.selected; 733 | 734 | if( state.settings.multiple && ! state.settings.commaSeperated && ! state.settings.checkBoxes ) { 735 | options = [ ... options ].filter( o => ! selected.includes( o.key ) ); 736 | } 737 | 738 | if( options[ index ] || ( index === -1 && state.settings.placeHolderInside ) ) { 739 | dispatch( { 740 | type: FOCUS_ITEM, 741 | item: index !== -1 ? options[ index ] : { key: 'default' }, 742 | index, 743 | mouseEvent 744 | } ) 745 | } 746 | 747 | } 748 | } 749 | 750 | export const unlockMouseFocus = () => { 751 | return { 752 | type: UNLOCK_MOUSE_FOCUS 753 | } 754 | } 755 | -------------------------------------------------------------------------------- /src/components/App/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { buildClassName } from 'helpers'; 3 | import PropTypes from 'prop-types'; 4 | import Header from './partials/Header/'; 5 | import MultiHeader from './partials/MultiHeader/'; 6 | import SearchPrompt from './partials/SearchPrompt/'; 7 | import Tags from './partials/Tags/'; 8 | import NoResults from './partials/NoResults/'; 9 | 10 | export default class App extends React.Component { 11 | 12 | constructor( props ) { 13 | 14 | super( props ); 15 | 16 | const methods = [ 17 | 'buildOptionClassName', 18 | 'handleBodyClick', 19 | 'handleKeyDown', 20 | 'handleMouseMove', 21 | 'checkIfHovered', 22 | 'maybeScroll' 23 | ]; 24 | 25 | methods.forEach( method => this[ method ] = this[ method ].bind( this ) ); 26 | 27 | } 28 | 29 | componentDidMount() { 30 | 31 | document.addEventListener( 'click', this.handleBodyClick ); 32 | document.addEventListener( 'touchstart', this.handleBodyClick ); 33 | document.addEventListener( 'keydown', this.handleKeyDown ); 34 | document.addEventListener( 'mousemove', this.handleMouseMove ); 35 | 36 | if( ( ! this.props.settings.multiple && ! this.props.settings.commaSeperated && ! this.props.settings.isDropDown ) && this.props.isOpen && this.props.checkForScroll && this.props.selected.length > 0 ) { 37 | this.props.maybeScroll( this.rsBodyRef, this[ `option-${ this.props.selectedIndex[ 0 ].toString() }` ] ); 38 | } 39 | 40 | } 41 | 42 | componentWillUnmount() { 43 | document.removeEventListener( 'click', this.handleBodyClick ); 44 | document.removeEventListener( 'touchstart', this.handleBodyClick ); 45 | document.removeEventListener( 'keydown', this.handleKeyDown ); 46 | document.removeEventListener( 'mousemove', this.handleMouseMove ); 47 | } 48 | 49 | handleBodyClick( e ) { 50 | 51 | if( ! this.ref.contains( e.target ) ) { 52 | if( this.props.isOpen ) { 53 | this.props.closeSelect( this.props.focused ); 54 | } else if ( this.props.focused ) { 55 | this.props.blurSelect(); 56 | } 57 | } 58 | 59 | } 60 | 61 | handleKeyDown( e ) { 62 | 63 | if( ! this.props.focused ) { 64 | return; 65 | } 66 | 67 | this.props.handleKeyDown( e ); 68 | 69 | } 70 | 71 | checkIfHovered() { 72 | 73 | if( ! this.props.settings.stayOpen || ! this.props.settings.multiple || ! this.props.checkForHover || this.props.settings.commaSeperated || this.props.settings.checkBoxes || this.props.settings.isDropDown || this.props.tags.focused ) { 74 | return; 75 | } 76 | 77 | for( let i = 0; i < this.props.options.length; i++ ) { 78 | 79 | const option = this[ `option-${ i }` ]; 80 | if( option && option.parentNode.querySelector( ':hover' ) === option ) { 81 | 82 | const scrollTop = this.rsBodyRef.scrollTop; 83 | const innerHeight = this.rsBodyRef.clientHeight; 84 | const scrollHeight = this.rsBodyRef.scrollHeight; 85 | 86 | if( this.props.options[ i ] && scrollTop !== 0 && scrollTop + innerHeight >= scrollHeight ) { 87 | this.props.focusItem( i - 1, true ); 88 | } 89 | else { 90 | this.props.focusItem( i, true ); 91 | } 92 | 93 | break; 94 | } 95 | } 96 | } 97 | 98 | maybeScroll() { 99 | 100 | if( ! this.props.isOpen || ! this.props.checkForScroll ) { 101 | return; 102 | } 103 | 104 | if( this.props.tags.focused ) { 105 | return this.props.maybeScroll( this.rsBodyRef, this.tagsRef ); 106 | } 107 | 108 | if( this.props.focusedItem !== null ) { 109 | this.props.maybeScroll( this.rsBodyRef, this[ `option-${ this.props.focusedItemIndex.toString() }` ] ); 110 | } 111 | else if( ! this.props.settings.multiple && this.props.selected.length > 0 ) { 112 | this.props.maybeScroll( this.rsBodyRef, this[ `option-${ this.props.selectedIndex[ 0 ].toString() }` ] ); 113 | } 114 | 115 | } 116 | 117 | componentDidUpdate() { 118 | this.checkIfHovered(); 119 | this.maybeScroll(); 120 | 121 | } 122 | 123 | handleMouseMove() { 124 | if( this.props.mouseEventLocked ) { 125 | this.props.unlockMouseFocus(); 126 | } 127 | } 128 | 129 | buildOptionClassName( option ) { 130 | 131 | let className = 'rs-option'; 132 | if( option.hasOwnProperty( 'disabled' ) && option.disabled === true ) { 133 | className += ' disabled'; 134 | } 135 | 136 | if( ! this.props.settings.isDropDown && this.props.selected.includes( option.key ) ) { 137 | className += ' selected'; 138 | } 139 | 140 | if( this.props.focusedItem !== null && this.props.focusedItem === option.key ) { 141 | className += ' focused'; 142 | } 143 | 144 | return className.trim(); 145 | } 146 | 147 | render() { 148 | 149 | const { options, settings, isOpen, selected, originalCount, ajax, onRenderOption, tags, queryString } = this.props; 150 | const className = buildClassName( settings, isOpen, selected, tags ); 151 | 152 | return( 153 | 154 |
    this.ref = ref } 157 | onFocus={ this.props.focusSelect } 158 | > 159 | 160 |
    161 | { settings.multiple ? :
    } 162 | { ( isOpen || settings.materialize ) && 163 |
    this.rsBodyRef = ref } 166 | style={{ maxHeight: this.props.height }} 167 | > 168 | this.tagsRef = ref } /> 169 | { settings.selectAllButton && 170 |
    171 | 177 |
    178 | } 179 | 180 |
      181 | { ajax.active && ajax.fetching && 182 |
      183 | Loading... 184 |
      185 | } 186 | 187 | { settings.placeHolderInside && ! settings.multiple && ( ! ajax.active || ! ajax.fetching && ajax.minLength <= queryString ) && 188 |
    • ! this.props.mouseEventLocked ? this.props.focusItem( -1, true ) : '' } 192 | > 193 | { settings.placeholder } 194 |
    • 195 | } 196 | { options.map( ( o, index ) => { 197 | 198 | let jsx = ( 199 | settings.checkBoxes ? 200 | 201 | 202 | 203 | : 204 | o.label 205 | ); 206 | 207 | if( onRenderOption !== false ) { 208 | const html = onRenderOption( o, index ); 209 | if( html ) jsx = html; 210 | } 211 | 212 | return( 213 |
    • this[ `option-${ index }` ] = ref } 215 | onClick={ e => { 216 | e.stopPropagation(); 217 | e.nativeEvent.stopImmediatePropagation(); 218 | this.props.selectItem( index ); 219 | } } 220 | key={ `li-${index}` } 221 | className={ this.buildOptionClassName( o, index ) } 222 | onMouseOver={ () => { 223 | ! this.props.mouseEventLocked 224 | ? this.props.focusItem( index, true ) 225 | : settings.stayOpen ? this.props.unlockMouseFocus() : '' 226 | } } 227 | > 228 | { jsx } 229 |
    • 230 | ) 231 | } ) } 232 |
    233 |
    234 | } 235 |
    236 |
    237 | ) 238 | } 239 | } 240 | 241 | 242 | App.propTypes = { 243 | settings: PropTypes.object.isRequired, 244 | toggleSelect: PropTypes.func.isRequired, 245 | options: PropTypes.array.isRequired, 246 | height: PropTypes.number.isRequired, 247 | isOpen: PropTypes.bool.isRequired, 248 | selected: PropTypes.array.isRequired, 249 | selectedIndex: PropTypes.array.isRequired, 250 | selectItem: PropTypes.func.isRequired, 251 | focusItem: PropTypes.func.isRequired, 252 | focusedItem: PropTypes.string, 253 | focusedItemIndex: PropTypes.number, 254 | clearSelect: PropTypes.func.isRequired, 255 | closeSelect: PropTypes.func.isRequired, 256 | focused: PropTypes.bool.isRequired, 257 | focusSelect: PropTypes.func.isRequired, 258 | blurSelect: PropTypes.func.isRequired, 259 | handleKeyDown: PropTypes.func.isRequired, 260 | maybeScroll: PropTypes.func.isRequired, 261 | checkForScroll: PropTypes.bool.isRequired, 262 | mouseEventLocked: PropTypes.bool.isRequired, 263 | unlockMouseFocus: PropTypes.func.isRequired, 264 | checkForHover: PropTypes.bool.isRequired, 265 | selectAll: PropTypes.func.isRequired, 266 | originalCount: PropTypes.number.isRequired, 267 | ajax: PropTypes.object.isRequired, 268 | onRenderOption: PropTypes.oneOfType( [ 269 | PropTypes.func, 270 | PropTypes.bool 271 | ] ), 272 | tags: PropTypes.object.isRequired, 273 | queryString: PropTypes.string.isRequired 274 | } 275 | -------------------------------------------------------------------------------- /src/components/App/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { toggleSelect, selectItem, focusItem, clearSelect, openSelect, closeSelect, focusSelect, blurSelect, handleKeyDown, maybeScroll, unlockMouseFocus, selectAll } from 'actions'; 3 | import App from './App'; 4 | 5 | const mapStateToProps = state => { 6 | 7 | const queryString = state.search.queryString.toLowerCase(); 8 | 9 | let options = ! state.search.active || queryString === '' ? [ ... state.options ] : [ ... state.search.resultSet ]; 10 | const selected = state.ajax.fetchOnSearch ? state.selected.map( s => s.key ) : state.selected; 11 | const originalCount = state.options.length; 12 | 13 | if( state.settings.multiple && ! state.settings.commaSeperated && ! state.settings.checkBoxes ) { 14 | options = options.filter( o => ! selected.includes( o.key ) ); 15 | } 16 | 17 | return { 18 | settings: state.settings, 19 | selected, 20 | selectedIndex: state.selectedIndex, 21 | isOpen: state.isOpen, 22 | options, 23 | focused: state.focused, 24 | focusedItem: state.focusedItem, 25 | focusedItemIndex: state.focusedItemIndex, 26 | checkForScroll: state.checkForScroll, 27 | mouseEventLocked: state.mouseEventLocked, 28 | checkForHover: state.checkForHover, 29 | originalCount, 30 | height: state.height, 31 | ajax: state.ajax, 32 | onRenderOption: state.onRenderOption, 33 | tags: state.tags, 34 | queryString 35 | } 36 | 37 | } 38 | 39 | const mapDispatchToProps = ( dispatch ) => { 40 | 41 | return { 42 | 43 | toggleSelect: () => { 44 | dispatch( toggleSelect() ); 45 | }, 46 | 47 | selectItem: ( index ) => { 48 | dispatch( selectItem( index ) ); 49 | }, 50 | 51 | focusItem: ( index, mouseEvent = false ) => { 52 | dispatch( focusItem( index, mouseEvent ) ); 53 | }, 54 | 55 | openSelect: () => { 56 | dispatch( openSelect() ); 57 | }, 58 | 59 | clearSelect: ( event = false, stayOpen = false ) => { 60 | if( event ) { 61 | event.stopPropagation(); 62 | event.nativeEvent.stopImmediatePropagation(); 63 | } 64 | dispatch( clearSelect( stayOpen ) ); 65 | }, 66 | 67 | closeSelect: ( blur = false ) => { 68 | dispatch( closeSelect( blur ) ); 69 | }, 70 | 71 | focusSelect: () => { 72 | dispatch( focusSelect() ); 73 | }, 74 | 75 | blurSelect: () => { 76 | dispatch( blurSelect() ); 77 | }, 78 | 79 | handleKeyDown: ( e ) => { 80 | dispatch( handleKeyDown( e ) ); 81 | }, 82 | 83 | maybeScroll: ( selectEl, itemEl ) => { 84 | dispatch( maybeScroll( selectEl, itemEl ) ); 85 | }, 86 | 87 | unlockMouseFocus: () => { 88 | dispatch( unlockMouseFocus() ); 89 | }, 90 | 91 | selectAll: () => { 92 | dispatch( selectAll() ); 93 | } 94 | 95 | } 96 | } 97 | 98 | export default connect( mapStateToProps, mapDispatchToProps )( App ); 99 | -------------------------------------------------------------------------------- /src/components/App/partials/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Searchable from './partials/Searchable/'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const Header = ( props ) => { 6 | 7 | const { settings, isOpen, selected, focused, onRenderSelection, tags } = props; 8 | 9 | let jsx = ( 10 | settings.searchable || tags.enabled 11 | ? 12 | :
    13 | { ( selected === null || settings.isDropDown ) ? settings.placeholder : selected.label } 14 |
    15 | ); 16 | 17 | if( ! settings.searchable && onRenderSelection !== false ) { 18 | const html = onRenderSelection( selected, settings ); 19 | if( html ) jsx = html; 20 | } 21 | 22 | return ( 23 |
    24 | { ! settings.placeHolderInside && ! settings.isDropDown && selected !== null && 25 | 26 | props.clearSelect( e ) }>× 27 | 28 | } 29 | { settings.arrow && 30 | 31 | 32 | 33 | } 34 | { jsx } 35 | 36 |
    37 | ) 38 | } 39 | 40 | Header.propTypes = { 41 | settings: PropTypes.object.isRequired, 42 | isOpen: PropTypes.bool.isRequired, 43 | selected: PropTypes.object, 44 | toggleSelect: PropTypes.func.isRequired, 45 | clearSelect: PropTypes.func.isRequired, 46 | focused: PropTypes.bool.isRequired, 47 | onRenderSelection: PropTypes.oneOfType( [ 48 | PropTypes.func, 49 | PropTypes.bool 50 | ] ), 51 | tags: PropTypes.object.isRequired 52 | } 53 | 54 | export default Header; 55 | -------------------------------------------------------------------------------- /src/components/App/partials/Header/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { toggleSelect, clearSelect } from 'actions'; 3 | import Header from './Header'; 4 | 5 | const mapStateToProps = ( state ) => { 6 | 7 | return { 8 | settings: state.settings, 9 | selected: state.selected.length > 0 ? state.options[ state.selectedIndex ] : null, 10 | isOpen: state.isOpen, 11 | focused: state.focused, 12 | onRenderSelection: state.onRenderSelection, 13 | tags: state.tags 14 | } 15 | 16 | } 17 | 18 | const mapDispatchToProps = ( dispatch ) => { 19 | 20 | return { 21 | 22 | toggleSelect: () => { 23 | dispatch( toggleSelect() ); 24 | }, 25 | 26 | clearSelect: ( event = false ) => { 27 | if( event ) { 28 | event.stopPropagation(); 29 | event.nativeEvent.stopImmediatePropagation(); 30 | } 31 | dispatch( clearSelect() ); 32 | } 33 | 34 | } 35 | } 36 | 37 | export default connect( mapStateToProps, mapDispatchToProps )( Header ); 38 | -------------------------------------------------------------------------------- /src/components/App/partials/Header/partials/Searchable/Searchable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { debounce } from 'helpers'; 4 | 5 | export default class Searchable extends React.Component { 6 | 7 | constructor( props ) { 8 | super( props ); 9 | 10 | this.state = { 11 | size: props.settings.multiple ? '1' : '20' 12 | } 13 | 14 | this.calculateSize = this.calculateSize.bind( this ); 15 | this.onSearch = this.onSearch.bind( this ); 16 | 17 | } 18 | 19 | onSearch = debounce( ( value ) => { 20 | this.props.searchOptions( value ); 21 | }, this.props.ajax.debounce ) 22 | 23 | calculateSize( props ) { 24 | 25 | if( ! props.settings.multiple ) { 26 | return; 27 | } 28 | 29 | let size = 1; 30 | 31 | if( props.queryString === '' ) { 32 | if( props.selected.length === 0 && props.settings.placeholder ) { 33 | size = props.settings.placeholder.length; 34 | } 35 | } 36 | else { 37 | size = props.queryString.length; 38 | } 39 | 40 | this.setState( { size: size === 0 ? '1' : size.toString() } ); 41 | 42 | } 43 | 44 | componentWillMount() { 45 | this.calculateSize( this.props ); 46 | } 47 | 48 | componentWillReceiveProps( nextProps ) { 49 | 50 | if( nextProps.settings.multiple && ( this.props.queryString !== nextProps.queryString || this.props.selected.length !== nextProps.selected.length ) || this.props.settings.placeholder.length !== nextProps.settings.placeholder.length ) { 51 | this.calculateSize( nextProps ); 52 | } 53 | 54 | if( nextProps.focused ) { 55 | this.input.focus(); 56 | } 57 | 58 | } 59 | 60 | render() { 61 | 62 | let className = 'rs-searchable'; 63 | const multiple = this.props.settings.multiple; 64 | const { focused, queryString } = this.props; 65 | let placeholder = ''; 66 | 67 | if( multiple ) { 68 | if( this.props.selected.length === 0 ) { 69 | placeholder = this.props.settings.placeholder; 70 | } 71 | } 72 | else { 73 | className += ' rs-toggle'; 74 | placeholder = this.props.selected && ! this.props.settings.isDropDown ? this.props.selected.label : this.props.settings.placeholder; 75 | } 76 | 77 | if( focused ) { 78 | className += ' rs-focused'; 79 | } 80 | 81 | if( queryString ) { 82 | className += ' rs-active'; 83 | } 84 | 85 | return ( 86 | this.input = ref } 88 | onFocus={ this.props.focusSelect } 89 | type="text" 90 | id="searchable" 91 | className={ className } 92 | placeholder={ placeholder } 93 | value={ this.props.queryString } 94 | onChange={ e => { 95 | const value = e.target.value; 96 | if( this.props.ajax.fetchOnSearch ) { 97 | this.props.setQueryString( value ); 98 | return this.onSearch( value ); 99 | } 100 | this.props.settings.searchable ? this.props.searchOptions( value ) : this.props.setTag( value ); 101 | } } 102 | size={ this.state.size } 103 | /> 104 | ) 105 | 106 | } 107 | 108 | } 109 | 110 | Searchable.propTypes = { 111 | queryString: PropTypes.string, 112 | searchOptions: PropTypes.func.isRequired, 113 | focusSelect: PropTypes.func.isRequired, 114 | blurSelect: PropTypes.func.isRequired, 115 | selected: PropTypes.oneOfType( [ 116 | PropTypes.object, 117 | PropTypes.array, 118 | ] ), 119 | settings: PropTypes.object.isRequired, 120 | isOpen: PropTypes.bool.isRequired, 121 | focused: PropTypes.bool.isRequired, 122 | ajax: PropTypes.object.isRequired, 123 | setQueryString: PropTypes.func.isRequired, 124 | setTag: PropTypes.func.isRequired, 125 | tags: PropTypes.object.isRequired 126 | } 127 | -------------------------------------------------------------------------------- /src/components/App/partials/Header/partials/Searchable/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Searchable from './Searchable'; 3 | import { searchOptions, focusSelect, blurSelect, setQueryString, setTag } from 'actions'; 4 | 5 | const mapStateToProps = ( state ) => { 6 | 7 | return { 8 | settings: state.settings, 9 | selected: state.settings.multiple ? state.selected : state.selected.length > 0 ? state.options[ state.selectedIndex ] : null, 10 | isOpen: state.isOpen, 11 | queryString: state.search.queryString, 12 | focused: state.focused, 13 | ajax: state.ajax, 14 | tags: state.tags 15 | } 16 | 17 | } 18 | 19 | const mapDispatchToProps = ( dispatch ) => { 20 | 21 | return { 22 | 23 | searchOptions: ( queryString ) => { 24 | dispatch( searchOptions( queryString ) ) 25 | }, 26 | 27 | focusSelect: () => { 28 | dispatch( focusSelect() ); 29 | }, 30 | 31 | setTag: ( queryString ) => { 32 | dispatch( setTag( queryString ) ); 33 | }, 34 | 35 | blurSelect: () => { 36 | dispatch( blurSelect() ); 37 | }, 38 | 39 | setQueryString: ( queryString ) => { 40 | dispatch( setQueryString( queryString ) ); 41 | } 42 | 43 | } 44 | } 45 | 46 | export default connect( mapStateToProps, mapDispatchToProps )( Searchable ); 47 | -------------------------------------------------------------------------------- /src/components/App/partials/MultiHeader/MultiHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Searchable from './../Header/partials/Searchable/'; 3 | import PropTypes from 'prop-types'; 4 | 5 | export default class MultiHeader extends React.Component { 6 | 7 | constructor( props ) { 8 | super( props ); 9 | } 10 | 11 | getJSX() { 12 | 13 | const { selected, settings, selectedIndex, options, ajax, onRenderSelection, tags } = this.props; 14 | const jsx = []; 15 | let iterable = []; 16 | 17 | if( selected.length === 0 ) { 18 | jsx.push( settings.searchable || tags.enabled ? '' : settings.placeholder ); 19 | } 20 | else { 21 | 22 | iterable = ajax.active && ajax.fetchOnSearch ? selected : selectedIndex; 23 | 24 | if( settings.commaSeperated ) { 25 | jsx.push( 26 | 27 | { selectedIndex.map( s => options[ s ].label ).join( ', ' ) } 28 | 29 | ) 30 | } 31 | else { 32 | iterable.map( s => { 33 | 34 | const key = ajax.active && ajax.fetchOnSearch ? s.key : s; 35 | const label = ajax.active && ajax.fetchOnSearch ? s.label : options[ s ].label; 36 | const onClick = ( e ) => { 37 | e.stopPropagation(); 38 | this.props.removeItem( key ) 39 | }; 40 | 41 | if( onRenderSelection !== false ) { 42 | const html = onRenderSelection( options[ s ], settings, onClick ); 43 | if( html ) jsx.push( html ); 44 | } 45 | else { 46 | jsx.push( 47 |
    48 | 49 | × 50 | 51 | { label } 52 |
    53 | ) 54 | } 55 | } ) 56 | } 57 | 58 | } 59 | 60 | if( settings.searchable || tags.enabled ) { 61 | jsx.push( ); 62 | } 63 | 64 | return jsx; 65 | 66 | } 67 | 68 | render() { 69 | 70 | const props = this.props; 71 | const { settings, isOpen, selected, focused } = props; 72 | 73 | let toggleClassName = 'rs-toggle'; 74 | 75 | if( focused ) { 76 | toggleClassName += ' rs-focused'; 77 | } 78 | 79 | return ( 80 |
    { 83 | if( settings.searchable ) { 84 | isOpen ? e.preventDefault() : props.openSelect(); 85 | } 86 | else { 87 | props.toggleSelect(); 88 | } 89 | }} 90 | > 91 | { selected.length > 0 && 92 | 93 | props.clearSelect( e ) }>× 94 | 95 | } 96 | { settings.arrow && 97 | 98 | 99 | 100 | } 101 |
    102 | { this.getJSX() } 103 |
    104 | 105 |
    106 | ) 107 | } 108 | } 109 | 110 | MultiHeader.propTypes = { 111 | settings: PropTypes.object.isRequired, 112 | isOpen: PropTypes.bool.isRequired, 113 | selected: PropTypes.array, 114 | openSelect: PropTypes.func.isRequired, 115 | clearSelect: PropTypes.func.isRequired, 116 | toggleSelect: PropTypes.func.isRequired, 117 | selectedIndex: PropTypes.array.isRequired, 118 | options: PropTypes.array.isRequired, 119 | removeItem: PropTypes.func.isRequired, 120 | focused: PropTypes.bool.isRequired, 121 | ajax: PropTypes.object.isRequired, 122 | onRenderSelection: PropTypes.oneOfType( [ 123 | PropTypes.func, 124 | PropTypes.bool 125 | ] ), 126 | tags: PropTypes.object.isRequired 127 | } 128 | -------------------------------------------------------------------------------- /src/components/App/partials/MultiHeader/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { clearSelect, removeItem, openSelect, toggleSelect } from 'actions'; 3 | import MultiHeader from './MultiHeader'; 4 | 5 | const mapStateToProps = ( state ) => { 6 | 7 | return { 8 | settings: state.settings, 9 | selected: state.selected, 10 | selectedIndex: state.selectedIndex, 11 | options: state.options, 12 | isOpen: state.isOpen, 13 | focused: state.focused, 14 | ajax: state.ajax, 15 | onRenderSelection: state.onRenderSelection, 16 | tags: state.tags 17 | } 18 | 19 | } 20 | 21 | const mapDispatchToProps = ( dispatch ) => { 22 | 23 | return { 24 | 25 | clearSelect: ( event = false ) => { 26 | if( event ) { 27 | event.stopPropagation(); 28 | event.nativeEvent.stopImmediatePropagation(); 29 | } 30 | dispatch( clearSelect() ); 31 | }, 32 | 33 | removeItem: ( index ) => { 34 | dispatch( removeItem( index ) ); 35 | }, 36 | 37 | openSelect: () => { 38 | dispatch( openSelect() ); 39 | }, 40 | 41 | toggleSelect: () => { 42 | dispatch( toggleSelect() ); 43 | } 44 | 45 | } 46 | } 47 | 48 | export default connect( mapStateToProps, mapDispatchToProps )( MultiHeader ); 49 | -------------------------------------------------------------------------------- /src/components/App/partials/NoResults/NoResults.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const NoResults = ( props ) => { 5 | 6 | if( ! props.active ) { 7 | return null; 8 | } 9 | 10 | return( 11 |
  • { props.settings.noResultsMessage } "{ props.queryString }"
  • 12 | ) 13 | 14 | } 15 | 16 | NoResults.propTypes = { 17 | settings: PropTypes.object.isRequired, 18 | active: PropTypes.bool.isRequired, 19 | queryString: PropTypes.string 20 | } 21 | 22 | export default NoResults; 23 | -------------------------------------------------------------------------------- /src/components/App/partials/NoResults/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import NoResults from './NoResults'; 3 | 4 | const mapStateToProps = ( state, ownProps ) => { 5 | 6 | return { 7 | settings: state.settings, 8 | active: ownProps.options.length === 0 9 | && state.search.queryString.length > 0 10 | && ( ! state.ajax.active || ( state.ajax.minLength <= state.search.queryString.length && ! state.ajax.fetching ) ) 11 | && ! state.tags.active, 12 | queryString: state.search.queryString 13 | } 14 | 15 | } 16 | 17 | export default connect( mapStateToProps, undefined )( NoResults ); 18 | -------------------------------------------------------------------------------- /src/components/App/partials/SearchPrompt/SearchPrompt.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const SearchPrompt = ( props ) => { 5 | 6 | if( ! props.active ) return null; 7 | 8 | return ( 9 |
    10 | Please enter { props.requiredLength } or more characters 11 |
    12 | ) 13 | } 14 | 15 | export default SearchPrompt; 16 | 17 | SearchPrompt.propTypes = { 18 | active: PropTypes.bool.isRequired, 19 | requiredLength: PropTypes.number.isRequired 20 | } 21 | -------------------------------------------------------------------------------- /src/components/App/partials/SearchPrompt/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import SearchPrompt from './SearchPrompt'; 3 | 4 | const mapStateToProps = ( state ) => { 5 | return { 6 | active: state.ajax.active 7 | && state.ajax.fetchOnSearch 8 | && state.ajax.searchPrompt 9 | && state.search.queryString.length < state.ajax.minLength, 10 | requiredLength: state.ajax.minLength - state.search.queryString.length 11 | } 12 | 13 | } 14 | 15 | export default connect( mapStateToProps, undefined )( SearchPrompt ); 16 | -------------------------------------------------------------------------------- /src/components/App/partials/Tags/Tags.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Tags = ( props ) => { 5 | 6 | if( ! props.active ) { 7 | return null; 8 | } 9 | 10 | return( 11 |
    props.createTag( props.tag ) } 14 | ref={ ref => props.extractRef( ref ) } 15 | > 16 | Create tag "{props.tag}" 17 |
    18 | ) 19 | 20 | } 21 | 22 | Tags.propTypes = { 23 | active: PropTypes.bool.isRequired, 24 | tag: PropTypes.string, 25 | createTag: PropTypes.func.isRequired, 26 | focused: PropTypes.bool.isRequired, 27 | extractRef: PropTypes.func.isRequired 28 | } 29 | 30 | export default Tags; 31 | -------------------------------------------------------------------------------- /src/components/App/partials/Tags/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Tags from './Tags'; 3 | import { createTag } from 'actions'; 4 | 5 | const mapStateToProps = ( state ) => { 6 | 7 | return { 8 | active: state.tags.enabled && state.tags.active, 9 | tag: state.search.queryString, 10 | focused: state.tags.focused 11 | } 12 | 13 | } 14 | 15 | const mapDispatchToProps = ( dispatch ) => { 16 | 17 | return { 18 | 19 | createTag: ( tag ) => { 20 | dispatch( createTag( tag ) ); 21 | } 22 | 23 | } 24 | } 25 | 26 | export default connect( mapStateToProps, mapDispatchToProps )( Tags ); 27 | -------------------------------------------------------------------------------- /src/components/Selectrix.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'scss/react-selectrix.scss'; 3 | import PropTypes from 'prop-types'; 4 | import App from 'components/App/'; 5 | 6 | export default class Selectrix extends React.Component { 7 | 8 | constructor( props ) { 9 | 10 | super( props ); 11 | 12 | const methods = [ 'handleMouseMove' ]; 13 | 14 | methods.forEach( method => this[ method ] = this[ method ].bind( this ) ); 15 | 16 | this.ref = null; 17 | this.rsBodyRef = null; 18 | 19 | } 20 | 21 | handleMouseMove() { 22 | if( ! this.state.mouseEventLocked ) { 23 | return; 24 | } 25 | this.setState( { 26 | mouseEventLocked: false 27 | } ) 28 | } 29 | 30 | componentWillMount() { 31 | this.props.setupInstance( this.props ); 32 | } 33 | 34 | componentWillReceiveProps( nextProps ) { 35 | this.props.updateInstance( nextProps ); 36 | } 37 | 38 | render() { 39 | 40 | return( 41 | 42 | ); 43 | 44 | } 45 | } 46 | 47 | Selectrix.defaultProps = { 48 | id: '', 49 | options: [], 50 | height: 190, 51 | className: '', 52 | isOpen: false, 53 | placeHolderInside: false, 54 | placeholder: 'Please Select', 55 | arrow: true, 56 | defaultValue: false, 57 | multiple: false, 58 | disabled: false, 59 | searchIndex: true, 60 | onChange: () => {}, 61 | onOpen: () => {}, 62 | onClose: () => {}, 63 | customScrollbar: false, 64 | searchable: true, 65 | noResultsMessage: 'No results match', 66 | stayOpen: false, 67 | initialized: false, 68 | commaSeperated: false, 69 | singleLine: false, 70 | lifo: false, 71 | selectAllButton: false, 72 | checkBoxes: false, 73 | materialize: false, 74 | isDropDown: false, 75 | customKeys: false, 76 | ajax: false, 77 | onRenderOption: false, 78 | onRenderSelection: false, 79 | tags: false, 80 | updateInstance: () => {} 81 | } 82 | 83 | Selectrix.propTypes = { 84 | options: PropTypes.array.isRequired, 85 | id: PropTypes.string.isRequired, 86 | height: PropTypes.oneOfType( [ 87 | PropTypes.number, 88 | PropTypes.string, 89 | ] ), 90 | className: PropTypes.string, 91 | isOpen: PropTypes.bool, 92 | placeHolderInside: PropTypes.bool, 93 | placeholder: PropTypes.string, 94 | arrow: PropTypes.bool, 95 | defaultValue: PropTypes.oneOfType( [ 96 | PropTypes.string, 97 | PropTypes.array, 98 | PropTypes.bool 99 | ] ), 100 | multiple: PropTypes.bool, 101 | disabled: PropTypes.bool, 102 | searchIndex: PropTypes.bool, 103 | onChange: PropTypes.func, 104 | customScrollbar: PropTypes.bool, 105 | searchable: PropTypes.bool, 106 | noResultsMessage: PropTypes.string, 107 | stayOpen: PropTypes.bool, 108 | commaSeperated: PropTypes.bool, 109 | singleLine: PropTypes.bool, 110 | lifo: PropTypes.bool, 111 | selectAllButton: PropTypes.bool, 112 | checkBoxes: PropTypes.bool, 113 | materialize: PropTypes.bool, 114 | isDropDown: PropTypes.bool, 115 | customKeys: PropTypes.oneOfType( [ 116 | PropTypes.object, 117 | PropTypes.bool 118 | ] ), 119 | ajax: PropTypes.oneOfType( [ 120 | PropTypes.object, 121 | PropTypes.bool 122 | ] ), 123 | onRenderOption: PropTypes.oneOfType( [ 124 | PropTypes.func, 125 | PropTypes.bool 126 | ] ), 127 | onRenderSelection: PropTypes.oneOfType( [ 128 | PropTypes.func, 129 | PropTypes.bool 130 | ] ), 131 | tags: PropTypes.bool, 132 | setupInstance: PropTypes.func.isRequired, 133 | updateInstance: PropTypes.func.isRequired 134 | } 135 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { setupInstance } from 'actions'; 3 | import Selectrix from './Selectrix'; 4 | let index = 0; 5 | 6 | const mapStateToProps = ( state, ownProps ) => ownProps; 7 | 8 | const mapDispatchToProps = ( dispatch ) => { 9 | 10 | return { 11 | 12 | setupInstance: ( props ) => { 13 | 14 | dispatch( setupInstance( Object.assign( {}, props, { 15 | id: `selectrix_instance_${ index }` 16 | } ) ) ); 17 | 18 | index++; 19 | 20 | }, 21 | 22 | updateInstance: ( props ) => { 23 | dispatch( setupInstance( props, true ) ); 24 | } 25 | 26 | } 27 | } 28 | 29 | export default connect( mapStateToProps, mapDispatchToProps )( Selectrix ); 30 | -------------------------------------------------------------------------------- /src/development.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | 5 | import ReduxStore from 'store/devStore'; 6 | import configureStore from 'store/configureStore'; 7 | 8 | const elRoot = document.getElementById( 'root' ); 9 | const store = configureStore(); 10 | 11 | const render = () => { 12 | ReactDOM.render( 13 | 14 | 17 | , 18 | elRoot 19 | ) 20 | } 21 | 22 | render(); 23 | 24 | if( module.hot ) { 25 | module.hot.accept(); 26 | } 27 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export const isArray = variable => variable instanceof Array; 2 | 3 | export const isString = v => typeof v === 'string'; 4 | 5 | export const isObject = variable => variable !== null && typeof variable === 'object' && ! Array.isArray( variable ) 6 | 7 | export const isNumeric = n => ! isNaN( parseFloat( n ) ) && isFinite( n ); 8 | 9 | export const isEmpty = variable => { 10 | 11 | if( variable.constructor === String ) { 12 | return variable === '' ? true : false; 13 | } 14 | else if( variable.constructor === Object && ! Array.isArray( variable ) ) { 15 | return Object.keys( variable ).length === 0 ? true : false; 16 | } 17 | else if( variable.constructor === Array ) { 18 | return variable.length === 0 ? true : false; 19 | } 20 | 21 | return false; 22 | 23 | } 24 | 25 | export const buildClassName = ( props, isOpen, selected, tags ) => { 26 | 27 | const targetProps = [ 28 | 'disabled', 29 | 'multiple', 30 | 'placeHolderInside', 31 | 'arrow', 32 | 'customScrollbar', 33 | 'searchable', 34 | 'commaSeperated', 35 | 'singleLine', 36 | 'checkBoxes', 37 | 'materialize', 38 | 'tags' 39 | ]; 40 | 41 | let className = ''; 42 | 43 | for( let [ key, value ] of Object.entries( props ) ) { 44 | if( value === true && targetProps.includes( key ) ) { 45 | className += `rs-base-${ key.toLowerCase() } `; 46 | } 47 | } 48 | 49 | if( isOpen ) { 50 | className += 'rs-base-open '; 51 | } 52 | 53 | if( selected.length === 0 ) { 54 | className += 'rs-base-empty '; 55 | } 56 | 57 | if( tags.enabled ) { 58 | className += 'rs-base-tags '; 59 | } 60 | 61 | if( props.className !== '' ) { 62 | className += props.className; 63 | } 64 | 65 | className = className.trim(); 66 | 67 | return className !== '' ? ` ${ className }` : ''; 68 | 69 | } 70 | 71 | export const getFocusedItemByKey = ( key, options ) => { 72 | return options.filter( o => o.key === key )[ 0 ]; 73 | } 74 | 75 | export const getSelectValue = ( selected, isMultiple = false ) => { 76 | if( selected.length === 0 ) { 77 | return ''; 78 | } 79 | if( isMultiple === false ) { 80 | return selected[ 0 ]; 81 | } 82 | return ''; 83 | } 84 | 85 | export const getSelectedIndex = ( selected, options ) => { 86 | for( let [ index, option ] of options.entries() ) { 87 | if( selected === option.key ) { 88 | return index; 89 | } 90 | } 91 | return false; 92 | } 93 | 94 | export const normalizeSelected = ( selected, options ) => { 95 | 96 | const results = { 97 | selected: [], 98 | selectedIndex: [] 99 | }; 100 | 101 | for( let [ index, o ] of options.entries() ) { 102 | 103 | if( isArray( selected ) ) { 104 | 105 | if( selected.includes( o.key ) ) { 106 | results.selected.push( o.key ); 107 | results.selectedIndex.push( index ); 108 | } 109 | 110 | } 111 | else { 112 | if( selected === o.key ) { 113 | results.selected.push( o.key ); 114 | results.selectedIndex.push( index ); 115 | } 116 | } 117 | } 118 | 119 | return results; 120 | 121 | } 122 | 123 | export const isInViewport = ( selectEl, itemEl ) => { 124 | 125 | if ( ! itemEl ) return false; 126 | const top = itemEl.offsetTop; 127 | const offset = selectEl.scrollTop; 128 | const height = selectEl.clientHeight; 129 | const elHeight = itemEl.clientHeight; 130 | 131 | if( top - offset <= 0 || top - offset >= height ) { 132 | return ( top - height ) + elHeight; 133 | } 134 | 135 | return false; 136 | 137 | } 138 | 139 | export const itemInOptions = ( itemKey, options ) => ( 140 | options.findIndex( o => o.key === itemKey ) !== -1 141 | ) 142 | 143 | export const debounce = ( func, wait, immediate ) => { 144 | let timeout; 145 | return function() { 146 | let context = this, args = arguments; 147 | let later = function() { 148 | timeout = null; 149 | if ( !immediate ) func.apply( context, args ); 150 | }; 151 | let callNow = immediate && !timeout; 152 | clearTimeout( timeout ); 153 | timeout = setTimeout( later, wait ); 154 | if ( callNow ) func.apply( context, args ); 155 | }; 156 | }; 157 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ReduxStore from 'store/productionStore'; 2 | export default ReduxStore; 3 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { SETUP_INSTANCE, UPDATE_INSTANCE, CLOSE_SELECT, OPEN_SELECT, SELECT_ITEM, FOCUS_ITEM, CLEAR_SELECT, FOCUS_SELECT, BLUR_SELECT, SCROLL_SELECT, SEARCH_OPTIONS, UNLOCK_MOUSE_FOCUS, REMOVE_ITEM, CLEAR_SEARCH, CHECK_FOR_SCROLL, SELECT_ALL, FETCHING_OPTIONS, SETUP_AJAX_OPTIONS, CLEAR_OPTIONS, SET_QUERY_STRING, CREATE_TAG, FOCUS_TAG, SET_TAG } from 'actions'; 2 | 3 | const initialState = { 4 | settings: { 5 | className: '', 6 | placeHolderInside: true, 7 | placeholder: 'Please Select', 8 | arrow: true, 9 | multiple: false, 10 | disabled: false, 11 | searchIndex: true, 12 | customScrollbar: false, 13 | searchable: true, 14 | noResultsMessage: 'No results match', 15 | commaSeperated: false, 16 | singleLine: false, 17 | lifo: false, 18 | selectAllButton: false, 19 | checkBoxes: false, 20 | materialize: false, 21 | isDropDown: false 22 | }, 23 | options: [], 24 | height: '150', 25 | isOpen: false, 26 | selected: [], 27 | selectedIndex: [], 28 | customKeys: false, 29 | initialized: false, 30 | focused: false, 31 | focusedItem: null, 32 | focusedItemIndex: null, 33 | mouseEventLocked: false, 34 | checkForScroll: false, 35 | checkForHover: false, 36 | tags: { 37 | enabled: false, 38 | active: false, 39 | focused: false, 40 | tagSet: [] 41 | }, 42 | scrolled: { 43 | active: false, 44 | scroll: 0 45 | }, 46 | search: { 47 | active: false, 48 | queryString: '', 49 | resultSet: [] 50 | }, 51 | ajax: false, 52 | onChange: () => {}, 53 | onOpen: () => {}, 54 | onClose: () => {} 55 | }; 56 | 57 | const reducer = ( state = initialState, action ) => { 58 | 59 | switch( action.type ) { 60 | 61 | case FOCUS_TAG: { 62 | return Object.assign( {}, state, { 63 | tags: Object.assign( {}, state.tags, { 64 | focused: true 65 | } ), 66 | focusedItem: null, 67 | focusedItemIndex: null, 68 | checkForScroll: true 69 | } ) 70 | } 71 | 72 | case CREATE_TAG: { 73 | return Object.assign( {}, state, { 74 | options: action.options, 75 | search: Object.assign( {}, state.search, { 76 | resultSet: action.resultSet 77 | } ), 78 | tags: Object.assign( {}, state.tags, { 79 | tagSet: [ ... state.tags.tagSet, action.tag ] 80 | } ) 81 | } ) 82 | } 83 | 84 | case SELECT_ALL: { 85 | 86 | const selected = state.ajax.fetchOnSearch ? state.selected.map( s => s.key ) : state.selected; 87 | let options = state.search.active ? [ ... state.search.resultSet ] : [ ... state.options ] 88 | options = options.filter( o => ! selected.includes( o.key ) ); 89 | 90 | const keys = [ ... options ].map( o => o.key ); 91 | let selectedKeys = state.ajax.fetchOnSearch ? options : keys; 92 | let indexes = [ ... state.options ].map( ( option, index ) => keys.includes( option.key ) ? index : null ).filter( x => x !== null ); 93 | 94 | if( state.settings.lifo ) { 95 | selectedKeys = selectedKeys.reverse(); 96 | indexes = indexes.reverse(); 97 | } 98 | 99 | return Object.assign( {}, state, { 100 | selected: state.settings.lifo ? [ ... selectedKeys, ... state.selected ] : [ ... state.selected, ... selectedKeys ], 101 | selectedIndex: state.settings.lifo ? [ ... indexes, ... state.selectedIndex ] : [ ... state.selectedIndex, ... indexes ] 102 | } ) 103 | 104 | } 105 | 106 | case REMOVE_ITEM: { 107 | 108 | return Object.assign( {}, state, { 109 | selected: state.ajax.active && state.ajax.fetchOnSearch 110 | ? [ ... state.selected ].filter( k => k.key !== action.index ) 111 | : [ ... state.selected ].filter( k => k !== state.options[ action.index ].key ), 112 | selectedIndex: [ ... state.selectedIndex ].filter( i => i !== action.index ) 113 | } ) 114 | } 115 | 116 | case SET_QUERY_STRING: { 117 | 118 | return Object.assign( {}, state, { 119 | search: Object.assign( {}, state.search, { 120 | active: true, 121 | queryString: action.queryString, 122 | resultSet: [] 123 | } ), 124 | ajax: Object.assign( {}, state.ajax, { 125 | fetching: action.queryString.length >= state.ajax.minLength 126 | } ), 127 | options: [] 128 | } ) 129 | 130 | } 131 | 132 | case SETUP_INSTANCE: 133 | case UPDATE_INSTANCE: { 134 | 135 | return Object.assign( {}, state, { 136 | settings: Object.assign( {}, state.settings, { 137 | className: action.props.className, 138 | placeHolderInside: ! action.props.multiple && action.props.placeHolderInside, 139 | placeholder: action.props.placeholder, 140 | arrow: action.props.arrow, 141 | multiple: action.props.multiple, 142 | disabled: action.props.disabled, 143 | searchIndex: action.props.searchIndex, 144 | customScrollbar: action.props.customScrollbar, 145 | searchable: action.props.searchable, 146 | noResultsMessage: action.props.noResultsMessage, 147 | stayOpen: action.props.hasOwnProperty( 'stayOpen' ) 148 | ? action.props.stayOpen && ! action.props.isDropDown 149 | : action.props.multiple ? true : false, 150 | commaSeperated: action.props.multiple && action.props.commaSeperated, 151 | singleLine: action.props.singleLine, 152 | lifo: action.props.multiple && action.props.lifo, 153 | selectAllButton: action.props.multiple && action.props.selectAllButton, 154 | checkBoxes: action.props.checkBoxes, 155 | materialize: action.props.materialize, 156 | isDropDown: action.props.isDropDown && ! action.props.multiple 157 | } ), 158 | options: action.type === UPDATE_INSTANCE && state.ajax.active && state.settings.multiple === action.props.multiple ? state.options : action.options, 159 | height: action.props.height, 160 | isOpen: action.props.isOpen ? action.props.isOpen : action.type === UPDATE_INSTANCE ? state.isOpen : false, 161 | selected: action.type === UPDATE_INSTANCE && state.ajax.active && state.settings.multiple === action.props.multiple ? state.selected : action.selected, 162 | selectedIndex: action.type === UPDATE_INSTANCE && state.ajax.active && state.settings.multiple === action.props.multiple ? state.selectedIndex : action.selectedIndex, 163 | customKeys: action.customKeys, 164 | ajax: action.ajax, 165 | initialized: true, 166 | onChange: action.props.onChange, 167 | onOpen: action.props.onOpen, 168 | onClose: action.props.onClose, 169 | checkForScroll: action.type === UPDATE_INSTANCE ? state.isOpen : action.props.isOpen, 170 | onRenderOption: action.props.onRenderOption, 171 | onRenderSelection: action.props.onRenderSelection, 172 | tags: Object.assign( {}, state.tags, { 173 | enabled: action.props.tags 174 | } ), 175 | id: action.props.id 176 | } ); 177 | } 178 | 179 | case SET_TAG: { 180 | 181 | return Object.assign( {}, state, { 182 | tags: Object.assign( {}, state.tags, { 183 | enabled: state.tags.enabled, 184 | active: state.tags.enabled 185 | && action.tag.length > 0 186 | && action.tag.trim() 187 | && state.options.find( o => o.label === action.tag ) === undefined 188 | } ), 189 | search: Object.assign( {}, state.search, { 190 | queryString: action.tag 191 | } ) 192 | } ); 193 | 194 | } 195 | 196 | case SEARCH_OPTIONS: { 197 | 198 | const queryString = action.queryString.toLowerCase(); 199 | return Object.assign( {}, state, { 200 | search: Object.assign( {}, state.search, { 201 | active: true, 202 | queryString: action.queryString, 203 | resultSet: state.ajax.active && state.ajax.fetchOnSearch ? action.queryString.length < state.ajax.minLength ? [] : state.options : state.options.filter( o => 204 | o.label.toLowerCase().includes( queryString ) || ( state.settings.searchIndex && o.key.toString().toLowerCase().includes( queryString ) ) 205 | ) 206 | } ), 207 | focusedItem: null, 208 | focusedItemIndex: null, 209 | tags: Object.assign( {}, state.tags, { 210 | enabled: state.tags.enabled, 211 | active: state.tags.enabled 212 | && action.queryString.length > 0 213 | && action.queryString.trim() 214 | && state.options.find( o => o.label === action.queryString ) === undefined 215 | } ) 216 | } ) 217 | } 218 | 219 | case CHECK_FOR_SCROLL: { 220 | return Object.assign( {}, state, { 221 | checkForScroll: true 222 | } ) 223 | } 224 | 225 | case CLEAR_SEARCH: { 226 | return Object.assign( {}, state, { 227 | search: initialState.search, 228 | focusedItem: null, 229 | focusedItemIndex: null, 230 | options: state.ajax.active && state.ajax.fetchOnSearch ? [] : state.options, 231 | tags: Object.assign( {}, state.tags, { 232 | enabled: state.tags.enabled, 233 | active: false 234 | } ) 235 | } ) 236 | } 237 | 238 | case CLOSE_SELECT: { 239 | return Object.assign( {}, state, { 240 | isOpen: false, 241 | focusedItem: null, 242 | focusedItemIndex: null, 243 | search: initialState.search, 244 | options: state.ajax.fetchOnSearch && state.settings.multiple ? [] : state.options, 245 | ajax: Object.assign( {}, state.ajax, { 246 | fetching: false 247 | } ), 248 | tags: Object.assign( {}, state.tags, { 249 | enabled: state.tags.enabled, 250 | active: false 251 | } ), 252 | focused: action.blur ? false : state.focused 253 | } ) 254 | } 255 | 256 | case OPEN_SELECT: { 257 | return Object.assign( {}, state, { 258 | isOpen: true, 259 | checkForScroll: true 260 | } ) 261 | } 262 | 263 | case CLEAR_SELECT: { 264 | return Object.assign( {}, state, { 265 | selected: [], 266 | selectedIndex: [], 267 | focusedItem: null, 268 | focusedItemIndex: null, 269 | search: action.stayOpen ? state.search : initialState.search, 270 | options: state.ajax.fetchOnSearch ? [] : state.options, 271 | tags: Object.assign( {}, state.tags, { 272 | enabled: state.tags.enabled, 273 | active: false 274 | } ) 275 | } ) 276 | } 277 | 278 | case FOCUS_SELECT: { 279 | return Object.assign( {}, state, { 280 | focused: true 281 | } ) 282 | } 283 | 284 | case BLUR_SELECT: { 285 | return Object.assign( {}, state, { 286 | focused: false 287 | } ) 288 | } 289 | 290 | case SELECT_ITEM: { 291 | 292 | return Object.assign( {}, state, { 293 | selected: state.settings.multiple 294 | ? state.settings.lifo 295 | ? [ ... [ state.ajax.active && state.ajax.fetchOnSearch ? action.item : action.item.key ], ... state.selected ] 296 | : [ ... state.selected, ... [ state.ajax.active && state.ajax.fetchOnSearch ? action.item : action.item.key ] ] 297 | : [ action.item.key ], 298 | selectedIndex: state.settings.multiple 299 | ? state.settings.lifo 300 | ? [ ... [ action.index ], ... state.selectedIndex ] 301 | : [ ... state.selectedIndex, ... [ action.index ] ] 302 | : [ action.index ], 303 | focusedItem: state.settings.stayOpen && state.selected.length < state.options.length - 1 ? state.focusedItem : null, 304 | focusedItemIndex: state.settings.stayOpen && state.selected.length < state.options.length - 1 ? state.focusedItemIndex : null, 305 | mouseEventLocked: state.settings.stayOpen, 306 | checkForHover: state.settings.stayOpen && ! action.isKeyboard, 307 | search: state.settings.stayOpen && state.settings.searchable 308 | ? state.search 309 | : initialState.search, 310 | tags: Object.assign( {}, state.tags, { 311 | enabled: state.tags.enabled, 312 | active: false 313 | } ) 314 | } ) 315 | } 316 | 317 | case FOCUS_ITEM: { 318 | 319 | return Object.assign( {}, state, { 320 | focusedItem: action.item.key, 321 | focusedItemIndex: action.index, 322 | mouseEventLocked: ! action.mouseEvent, 323 | checkForScroll: ! action.mouseEvent, 324 | checkForHover: false, 325 | tags: Object.assign( {}, state.tags, { 326 | focused: false 327 | } ) 328 | } ) 329 | } 330 | 331 | case UNLOCK_MOUSE_FOCUS: { 332 | return Object.assign( {}, state, { 333 | mouseEventLocked: false, 334 | checkForScroll: false, 335 | checkForHover: false 336 | } ) 337 | } 338 | 339 | case SCROLL_SELECT: { 340 | return Object.assign( {}, state, { 341 | checkForScroll: false, 342 | scrolled: Object.assign( {}, state.scrolled, { 343 | active: action.active, 344 | scroll: action.scroll 345 | } ) 346 | } ) 347 | } 348 | 349 | case FETCHING_OPTIONS: { 350 | return Object.assign( {}, state, { 351 | ajax: Object.assign( {}, state.ajax, { 352 | fetching: true 353 | } ) 354 | } ) 355 | } 356 | 357 | case SETUP_AJAX_OPTIONS: { 358 | return Object.assign( {}, state, { 359 | options: action.options, 360 | ajax: Object.assign( {}, state.ajax, { 361 | fetching: false, 362 | needsUpdate: false 363 | } ), 364 | search: state.ajax.fetchOnSearch ? Object.assign( {}, state.search, {}, { 365 | active: true, 366 | resultSet: action.options 367 | } ) : state.search 368 | } ) 369 | } 370 | 371 | case CLEAR_OPTIONS: { 372 | return Object.assign( {}, state, { 373 | options: [], 374 | ajax: Object.assign( {}, state.ajax, { 375 | fetching: true 376 | } ), 377 | search: Object.assign( {}, state.search, { 378 | resultSet: [] 379 | } ) 380 | } ) 381 | } 382 | 383 | default: { 384 | return state; 385 | } 386 | 387 | } 388 | 389 | } 390 | 391 | export default reducer; 392 | -------------------------------------------------------------------------------- /src/scss/_colors.scss: -------------------------------------------------------------------------------- 1 | $border-color: #bdc8d5; 2 | $background-color: #ffffff; 3 | $font-color: #6a7f9d; 4 | $scrollbar-thumb-color: #6f747b; 5 | $scrollbar-track-color: #dfdfdf; 6 | $reset-color: #6a7f9d; 7 | $reset-hover: #b90e0e; 8 | $focused-color: #faf7f7; 9 | $selected-color: #eeeeee; 10 | $multi-selection-bgcolor: #00B2EE; 11 | $multi-selection-border-color: #21a4cf; 12 | $multi-remove-color: #fff3f3; 13 | $multi-remove-border: #d9d6d6; 14 | $toggle-all-color: #92a2b9; 15 | // material design 16 | $material-blue: #4379db; 17 | -------------------------------------------------------------------------------- /src/scss/_loader.scss: -------------------------------------------------------------------------------- 1 | $dimension: 20px; 2 | 3 | .rs-loader { 4 | font-size: 10px; 5 | margin: ( ( $dimension / 2 ) + 5 ) auto; 6 | overflow: hidden; 7 | text-indent: -9999em; 8 | width: $dimension; 9 | height: $dimension; 10 | border-radius: 50%; 11 | background: $multi-selection-bgcolor; 12 | background: -moz-linear-gradient(left, $multi-selection-bgcolor 10%, rgba(255, 255, 255, 0) 42%); 13 | background: -webkit-linear-gradient(left, $multi-selection-bgcolor 10%, rgba(255, 255, 255, 0) 42%); 14 | background: -o-linear-gradient(left, $multi-selection-bgcolor 10%, rgba(255, 255, 255, 0) 42%); 15 | background: -ms-linear-gradient(left, $multi-selection-bgcolor 10%, rgba(255, 255, 255, 0) 42%); 16 | background: linear-gradient(to right, $multi-selection-bgcolor 10%, rgba(255, 255, 255, 0) 42%); 17 | position: relative; 18 | -webkit-animation: load3 1.4s infinite linear; 19 | animation: load3 1.4s infinite linear; 20 | -webkit-transform: translateZ(0); 21 | -ms-transform: translateZ(0); 22 | transform: translateZ(0); 23 | } 24 | 25 | &.rs-base-materialize { 26 | .rs-loader { 27 | background: $material-blue; 28 | background: -moz-linear-gradient(left, $material-blue 10%, rgba(255, 255, 255, 0) 42%); 29 | background: -webkit-linear-gradient(left, $material-blue 10%, rgba(255, 255, 255, 0) 42%); 30 | background: -o-linear-gradient(left, $material-blue 10%, rgba(255, 255, 255, 0) 42%); 31 | background: -ms-linear-gradient(left, $material-blue 10%, rgba(255, 255, 255, 0) 42%); 32 | background: linear-gradient(to right, $material-blue 10%, rgba(255, 255, 255, 0) 42%); 33 | &:before { 34 | background: $material-blue; 35 | } 36 | } 37 | } 38 | 39 | .rs-loader:before { 40 | width: 50%; 41 | height: 50%; 42 | background: $multi-selection-bgcolor; 43 | border-radius: 100% 0 0 0; 44 | position: absolute; 45 | top: 0; 46 | left: 0; 47 | content: ''; 48 | } 49 | 50 | .rs-loader:after { 51 | background: white; 52 | width: 75%; 53 | height: 75%; 54 | border-radius: 50%; 55 | content: ''; 56 | margin: auto; 57 | position: absolute; 58 | top: 0; 59 | left: 0; 60 | bottom: 0; 61 | right: 0; 62 | } 63 | @-webkit-keyframes load3 { 64 | 0% { 65 | -webkit-transform: rotate(0deg); 66 | transform: rotate(0deg); 67 | } 68 | 69 | 100% { 70 | -webkit-transform: rotate(360deg); 71 | transform: rotate(360deg); 72 | } 73 | } 74 | @keyframes load3 { 75 | 0% { 76 | -webkit-transform: rotate(0deg); 77 | transform: rotate(0deg); 78 | } 79 | 80 | 100% { 81 | -webkit-transform: rotate(360deg); 82 | transform: rotate(360deg); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/scss/react-selectrix.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | 3 | .react-selectrix { 4 | position: relative; 5 | 6 | *, ::after, ::before { 7 | box-sizing: border-box; 8 | } 9 | 10 | .hidden { 11 | display: none; 12 | } 13 | 14 | .clearfix { 15 | &:after, 16 | &:before { 17 | content: ""; 18 | display: table; 19 | } 20 | 21 | &:after { 22 | clear: both; 23 | } 24 | } 25 | 26 | .rs-toggle-wrapper, .rs-search-prompt { 27 | padding: 11px 20px; 28 | font-size: 11px; 29 | color: $toggle-all-color; 30 | } 31 | 32 | .rs-tag-wrapper { 33 | padding: 11px 20px; 34 | font-size: 12px; 35 | cursor: pointer; 36 | color: $font-color; 37 | box-shadow: 0 0px 4px rgba( 0, 0, 0, 0.1 ); 38 | &:hover, &:focus, &:active { 39 | background-color: $focused-color; 40 | } 41 | &.focused { 42 | background-color: $focused-color; 43 | } 44 | } 45 | 46 | input#searchable { 47 | &.rs-active { 48 | min-width: 30px; 49 | } 50 | } 51 | 52 | button.rs-toggle-button { 53 | background-color: $background-color; 54 | border: none; 55 | font-size: 11px; 56 | cursor: pointer; 57 | outline: none; 58 | color: $toggle-all-color; 59 | padding: 0; 60 | &:hover { 61 | text-decoration: underline; 62 | } 63 | } 64 | 65 | .vertical-align { 66 | &:before { 67 | content: ''; 68 | display: inline-block; 69 | height: 100%; 70 | vertical-align: middle; 71 | margin-right: 0; 72 | } 73 | 74 | > * { 75 | display: inline-block; 76 | vertical-align: middle; 77 | } 78 | } 79 | 80 | .rs-header { 81 | border: 1px solid $border-color; 82 | color: #6a7f9d; 83 | border-radius: 2px; 84 | background-color: $background-color; 85 | position: relative; 86 | font-size: 14px; 87 | margin: 0; 88 | user-select: none; 89 | 90 | ::-webkit-input-placeholder { 91 | color: $font-color; 92 | } 93 | 94 | ::-moz-placeholder { 95 | color: $font-color; 96 | } 97 | 98 | :-ms-input-placeholder { 99 | color: $font-color; 100 | } 101 | 102 | :-moz-placeholder { 103 | color: $font-color; 104 | } 105 | } 106 | 107 | &.rs-base-arrow { 108 | .rs-reset-wrapper { 109 | right: 25px; 110 | } 111 | } 112 | 113 | &.rs-base-customscrollbar { 114 | ::-webkit-scrollbar { 115 | width: 12px; 116 | height: 12px; 117 | } 118 | 119 | ::-webkit-scrollbar-thumb { 120 | background: $scrollbar-thumb-color; 121 | } 122 | 123 | ::-webkit-scrollbar-track { 124 | background: $scrollbar-track-color; 125 | } 126 | } 127 | 128 | &.rs-base-disabled { 129 | opacity: 0.6; 130 | pointer-events: none; 131 | } 132 | 133 | &.rs-base-searchable, &.rs-base-tags { 134 | .rs-header { 135 | padding: 0; 136 | } 137 | .rs-toggle { 138 | cursor: text; 139 | } 140 | } 141 | 142 | &.rs-base-singleline { 143 | .rs-toggle { 144 | white-space: nowrap; 145 | overflow: hidden; 146 | text-overflow: ellipsis; 147 | display: block; 148 | } 149 | } 150 | 151 | &.rs-base-multiple { 152 | &:not(.rs-base-empty):not(.rs-base-commaseperated) { 153 | .rs-toggle { 154 | padding: 5px 50px 5px 5px; 155 | } 156 | } 157 | 158 | &.rs-base-commaseperated { 159 | &:not(.rs-base-empty) { 160 | .rs-toggle { 161 | padding: 7px 50px 7px 15px; 162 | line-height: 1.8; 163 | } 164 | } 165 | 166 | &.rs-base-searchable { 167 | .rs-toggle { 168 | display: flex; 169 | } 170 | .rs-commaseperated-wrapper { 171 | display: block; 172 | padding-right: 10px; 173 | max-width: 100%; 174 | overflow: hidden; 175 | text-overflow: ellipsis; 176 | } 177 | } 178 | } 179 | 180 | .rs-selection { 181 | display: inline-block; 182 | background-color: $multi-selection-bgcolor; 183 | padding: 3px 10px 3px 25px; 184 | color: $background-color; 185 | margin: 2px; 186 | font-size: 12px; 187 | border-radius: 2px; 188 | border: 1px solid $multi-selection-border-color; 189 | position: relative; 190 | cursor: pointer; 191 | max-width: 100%; 192 | white-space: nowrap; 193 | text-overflow: ellipsis; 194 | overflow: hidden; 195 | } 196 | 197 | &:not(.rs-base-empty) { 198 | .rs-selection, .rs-searchable { 199 | vertical-align: middle; 200 | } 201 | } 202 | 203 | .rs-remove { 204 | font-size: 15px; 205 | color: $multi-remove-color; 206 | position: absolute; 207 | left: 0; 208 | width: 20px; 209 | text-align: center; 210 | border-right: 1px solid $multi-remove-border; 211 | top: 0; 212 | height: 100%; 213 | transition: 0.2s background-color ease-in-out; 214 | font-family: "Arial" !important; 215 | 216 | &:hover { 217 | background-color: #23c8ff; 218 | } 219 | } 220 | 221 | .rs-searchable { 222 | border: none; 223 | box-shadow: none; 224 | outline: none; 225 | -webkit-appearance: none; 226 | max-width: 100%; 227 | color: $font-color; 228 | padding: 0; 229 | font-size: 14px; 230 | } 231 | } 232 | 233 | .rs-reset { 234 | font-size: 20px; 235 | color: $reset-color; 236 | position: relative; 237 | top: 1px; 238 | font-family: "Arial" !important; 239 | 240 | &:hover { 241 | color: $reset-hover; 242 | } 243 | } 244 | 245 | .rs-arrow-wrapper { 246 | position: absolute; 247 | width: 10px; 248 | height: 100%; 249 | top: 0; 250 | right: 10px; 251 | } 252 | 253 | .rs-reset-wrapper { 254 | position: absolute; 255 | width: 10px; 256 | height: 100%; 257 | top: 0; 258 | right: 10px; 259 | cursor: pointer; 260 | 261 | &:before { 262 | margin-right: -0.15em; 263 | } 264 | } 265 | 266 | .rs-arrow-indicator { 267 | width: 0; 268 | height: 0; 269 | border-style: solid; 270 | border-width: 5px 5px 0 5px; 271 | border-color: $font-color transparent transparent; 272 | transition: 0.1s transform ease-in-out; 273 | 274 | &.up { 275 | transform: rotate(180deg); 276 | } 277 | } 278 | 279 | .rs-body { 280 | 281 | border-radius: 2px; 282 | background-color: $background-color; 283 | box-shadow: 0 3px 7px 0 rgba(139, 155, 175, 0.5); 284 | position: absolute; 285 | width: 100%; 286 | left: 0; 287 | z-index: 1; 288 | overflow: auto; 289 | 290 | > ul { 291 | list-style-type: none; 292 | padding: 0; 293 | margin: 0; 294 | overflow: hidden; 295 | } 296 | } 297 | 298 | .rs-option { 299 | 300 | cursor: pointer; 301 | padding: 11px 20px; 302 | color: $font-color; 303 | font-size: 14px; 304 | user-select: none; 305 | overflow: hidden; 306 | text-overflow: ellipsis; 307 | white-space: nowrap; 308 | line-height: normal; 309 | 310 | &.disabled { 311 | opacity: 0.6; 312 | pointer-events: none; 313 | } 314 | 315 | &:not(.disabled) { 316 | &.focused { 317 | background-color: $focused-color; 318 | } 319 | 320 | &.selected { 321 | background-color: $selected-color; 322 | } 323 | } 324 | } 325 | 326 | .rs-no-results { 327 | padding: 11px 20px; 328 | color: $font-color; 329 | font-size: 12px; 330 | } 331 | 332 | .rs-toggle { 333 | border: none; 334 | outline: none; 335 | padding: 10px 50px 10px 15px; 336 | width: 100%; 337 | cursor: pointer; 338 | color: $font-color; 339 | font-size: 14px; 340 | line-height: normal; 341 | 342 | &.rs-focused { 343 | outline: 0; 344 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(81, 152, 209, 0.6); 345 | } 346 | 347 | &.rs-searchable { 348 | &:focus { 349 | cursor: text; 350 | } 351 | } 352 | } 353 | 354 | &.rs-base-checkboxes { 355 | 356 | .rs-option { 357 | 358 | &:not(.disabled) { 359 | 360 | &.selected { 361 | background-color: inherit; 362 | } 363 | 364 | &.focused { 365 | background-color: $focused-color; 366 | } 367 | } 368 | 369 | input[type="checkbox"] { 370 | position: absolute; 371 | opacity: 0; 372 | margin: 0; 373 | 374 | &:not(:checked) { 375 | + label { 376 | &:before { 377 | width: 0; 378 | height: 0; 379 | border: 3px solid transparent; 380 | left: 6px; 381 | top: 10px; 382 | -webkit-transform: rotateZ(37deg); 383 | transform: rotateZ(37deg); 384 | -webkit-transform-origin: 100% 100%; 385 | transform-origin: 100% 100%; 386 | } 387 | 388 | &:after { 389 | height: 13px; 390 | width: 13px; 391 | background-color: transparent; 392 | border: 1px solid #D5D9DF; 393 | border-radius: 2px; 394 | top: 0; 395 | z-index: 0; 396 | } 397 | } 398 | } 399 | 400 | + label { 401 | 402 | position: relative; 403 | padding-left: 25px; 404 | cursor: pointer; 405 | display: inline-block; 406 | user-select: none; 407 | font-size: 14px; 408 | line-height: 14px; 409 | font-weight: 400; 410 | margin-bottom: 0; 411 | 412 | &:after, 413 | &:before { 414 | content: ''; 415 | left: 0; 416 | position: absolute; 417 | -webkit-transition: border 0.25s, background-color 0.25s, width 0.20s 0.1s, height 0.20s 0.1s, top 0.20s 0.1s, left 0.20s 0.1s; 418 | transition: border 0.25s, background-color 0.25s, width 0.20s 0.1s, height 0.20s 0.1s, top 0.20s 0.1s, left 0.20s 0.1s; 419 | z-index: 1; 420 | } 421 | 422 | &:after { 423 | height: 13px; 424 | width: 13px; 425 | background-color: transparent; 426 | border: 2px solid #5a5a5a; 427 | top: 0; 428 | z-index: 0; 429 | } 430 | } 431 | 432 | &:checked { 433 | + label { 434 | &:before { 435 | top: 1px; 436 | left: 0px; 437 | width: 6px; 438 | height: 10px; 439 | border-top: 2px solid transparent; 440 | border-left: 2px solid transparent; 441 | border-right: 2px solid #fff; 442 | border-bottom: 2px solid #fff; 443 | -webkit-transform: rotateZ(37deg); 444 | transform: rotateZ(37deg); 445 | -webkit-transform-origin: 100% 100%; 446 | transform-origin: 100% 100%; 447 | } 448 | 449 | &:after { 450 | top: 0; 451 | height: 13px; 452 | width: 13px; 453 | border: 1px solid #1DA4CF; 454 | background-color: #1DA4CF; 455 | z-index: 0; 456 | border-radius: 2px; 457 | } 458 | } 459 | } 460 | 461 | &:disabled { 462 | +label { 463 | opacity: 0.4; 464 | cursor: not-allowed; 465 | } 466 | } 467 | } 468 | } 469 | } 470 | 471 | &.rs-base-open { 472 | 473 | &.rs-base-materialize { 474 | 475 | .rs-header { 476 | &:after { 477 | content: ""; 478 | height: 2px; 479 | background-color: $material-blue; 480 | display: block; 481 | width: 100%; 482 | left: 0; 483 | visibility: visible; 484 | } 485 | } 486 | 487 | .rs-arrow-indicator { 488 | border-color: $material-blue transparent transparent; 489 | } 490 | 491 | .rs-body { 492 | opacity: 1; 493 | -webkit-transform: scale(1) translateY(0); 494 | -ms-transform: scale(1) translateY(0); 495 | transform: scale(1) translateY(0); 496 | visibility: visible; 497 | height: auto; 498 | } 499 | 500 | } 501 | } 502 | 503 | .rs-arrow-indicator { 504 | transition: 0.2s transform ease-in-out; 505 | } 506 | 507 | &.rs-base-materialize { 508 | 509 | &.rs-base-multiple:not(.rs-base-empty) { 510 | .rs-toggle { 511 | padding-left: 0; 512 | } 513 | } 514 | 515 | .rs-header { 516 | border: none; 517 | border-bottom: 1px solid #eee; 518 | border-radius: 0; 519 | &:after { 520 | content: ""; 521 | height: 1px; 522 | background-color: transparent; 523 | position: absolute; 524 | bottom: -1px; 525 | display: block; 526 | width: 15px; 527 | left: 45%; 528 | transition-duration: .2s; 529 | transition-timing-function: cubic-bezier( .4, 0, .2, 1 ); 530 | visibility: hidden; 531 | } 532 | } 533 | 534 | .rs-body { 535 | display: block; 536 | opacity: 0; 537 | box-shadow: 0 2px 2px 0 rgba( 0, 0, 0, .15 ), 0 3px 1px -2px rgba( 0, 0, 0, .2 ), 0 1px 5px 0 rgba( 0, 0, 0, .12 ); 538 | transform-origin: 50% 0; 539 | transform: scale( 0.80 ) translateY( -15px ); 540 | transition: all 0.2s cubic-bezier( 0.5, 0, 0, 1.25 ), opacity 0.15s ease-in-out; 541 | visibility: hidden; 542 | height: 0; 543 | } 544 | 545 | .rs-toggle { 546 | padding-left: 0; 547 | 548 | &.rs-focused { 549 | box-shadow: none; 550 | } 551 | } 552 | } 553 | 554 | @import 'loader'; 555 | 556 | } 557 | -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import reducer from 'reducers'; 4 | import { createLogger } from 'redux-logger'; 5 | const loggerMiddleware = createLogger(); 6 | 7 | export const configureStore = () => { 8 | const env = process.env.NODE_ENV; 9 | return env === 'development' 10 | ? createStore( 11 | reducer, 12 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), 13 | applyMiddleware( 14 | thunkMiddleware, 15 | loggerMiddleware 16 | ) 17 | ) 18 | : createStore( reducer, applyMiddleware( thunkMiddleware ) ) 19 | } 20 | 21 | export default configureStore; 22 | -------------------------------------------------------------------------------- /src/store/devStore.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import PlayGround from 'PlayGround'; 4 | 5 | const ReduxStore = ownProps => { 6 | const store = ownProps.store; 7 | return( 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default ReduxStore; 15 | -------------------------------------------------------------------------------- /src/store/productionStore.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import App from 'components/'; 4 | import { createStore, applyMiddleware } from 'redux'; 5 | import thunkMiddleware from 'redux-thunk'; 6 | import reducer from 'reducers'; 7 | 8 | const ReduxStore = ownProps => { 9 | const store = createStore( reducer, applyMiddleware( thunkMiddleware ) ); 10 | return( 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | export default ReduxStore; 18 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const webpack = require( 'webpack' ); 3 | 4 | module.exports = { 5 | 6 | context: path.resolve( __dirname ), 7 | 8 | entry: { 9 | index: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-dev-server/client?http://localhost:3010', 12 | 'webpack/hot/only-dev-server', 13 | './src/development' 14 | ] 15 | }, 16 | 17 | mode: 'development', 18 | 19 | resolve: { 20 | extensions: [ 21 | '.jsx', '.js' 22 | ], 23 | modules: [ 24 | path.resolve( `./src` ), 25 | 'node_modules' 26 | ], 27 | alias: { 28 | 'components': path.resolve( __dirname, 'src/components' ), 29 | 'scss': path.resolve( __dirname, 'src/scss' ), 30 | 'helpers': path.resolve( __dirname, 'src/helpers' ), 31 | 'actions': path.resolve( __dirname, 'src/actions' ), 32 | 'reducers': path.resolve( __dirname, 'src/reducers' ) 33 | } 34 | }, 35 | output: { 36 | filename: '[name].js', 37 | // the output bundle 38 | 39 | path: path.resolve( __dirname, `./dist` ), 40 | 41 | publicPath: '/dist/' 42 | 43 | }, 44 | 45 | devtool: 'cheap-module-eval-source-map', 46 | module: { 47 | rules: [ 48 | { 49 | test: /\.jsx?$/, 50 | loader: 'eslint-loader', 51 | enforce: "pre", 52 | exclude: /node_modules/, 53 | options: { 54 | emitWarning: true 55 | } 56 | }, 57 | { 58 | test: /\.jsx?$/, 59 | use: [ 'babel-loader' ], 60 | exclude: /node_modules/ 61 | }, 62 | { 63 | test: /\.css$/, 64 | use: [ 'style-loader', 'css-loader' ] 65 | }, 66 | { 67 | test: /\.scss$/, 68 | use: [ { 69 | loader: "style-loader" // creates style nodes from JS strings 70 | }, { 71 | loader: "css-loader" // translates CSS into CommonJS 72 | }, { 73 | loader: "sass-loader" // compiles Sass to CSS 74 | } ] 75 | } 76 | ] 77 | }, 78 | 79 | plugins: [ 80 | 81 | //new webpack.ProvidePlugin({ 'Promise': 'es6-promise', 'fetch': 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch' }), 82 | 83 | new webpack.HotModuleReplacementPlugin( ), 84 | 85 | new webpack.DefinePlugin( { 86 | 'process.env.NODE_ENV': JSON.stringify( 'development' ) 87 | } ), 88 | 89 | // enable HMR globally 90 | 91 | new webpack.NamedModulesPlugin( ), 92 | // prints more readable module names in the browser console on HMR updates 93 | 94 | new webpack.NoEmitOnErrorsPlugin( ), 95 | // do not emit compiled assets that include errors 96 | 97 | ], 98 | 99 | devServer: { 100 | host: 'localhost', 101 | port: 3010, 102 | historyApiFallback: false, 103 | hot: true, 104 | publicPath: '/dist/' 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const webpack = require( 'webpack' ); 3 | // const BundleAnalyzerPlugin = require( 'webpack-bundle-analyzer' ).BundleAnalyzerPlugin; 4 | const UglifyJSPlugin = require( 'uglifyjs-webpack-plugin' ); 5 | 6 | module.exports = { 7 | 8 | context: path.resolve( __dirname ), 9 | 10 | entry: { 11 | 'index': './src/index' 12 | }, 13 | 14 | mode: 'production', 15 | 16 | resolve: { 17 | alias: { 18 | 'components': path.resolve( __dirname, 'src/components' ), 19 | 'scss': path.resolve( __dirname, 'src/scss' ), 20 | 'helpers': path.resolve( __dirname, 'src/helpers' ), 21 | 'actions': path.resolve( __dirname, 'src/actions' ), 22 | 'reducers': path.resolve( __dirname, 'src/reducers' ), 23 | 'react': path.resolve( __dirname, './node_modules/react' ), 24 | 'react-dom': path.resolve( __dirname, './node_modules/react-dom' ) 25 | }, 26 | extensions: [ 27 | '.jsx', '.js' 28 | ], 29 | modules: [ path.resolve( `./src` ), 'node_modules' ] 30 | }, 31 | output: { 32 | filename: '[name].js', 33 | // the output bundle 34 | 35 | path: path.resolve( __dirname, `./dist` ), 36 | 37 | publicPath: '/dist/', 38 | libraryTarget: 'umd', 39 | umdNamedDefine: true 40 | 41 | }, 42 | 43 | externals: { 44 | react: { 45 | commonjs: 'react', 46 | commonjs2: 'react', 47 | amd: 'React', 48 | root: 'React' 49 | }, 50 | "react-dom": { 51 | commonjs: 'react-dom', 52 | commonjs2: 'react-dom', 53 | amd: 'ReactDOM', 54 | root: 'ReactDOM' 55 | }, 56 | "prop-types": { 57 | root: 'PropTypes', 58 | commonjs2: 'prop-types', 59 | commonjs: 'prop-types', 60 | amd: 'prop-types' 61 | } 62 | }, 63 | 64 | module: { 65 | rules: [ 66 | { 67 | test: /\.jsx?$/, 68 | loader: 'eslint-loader', 69 | enforce: "pre", 70 | exclude: /node_modules/, 71 | options: { 72 | emitWarning: true 73 | } 74 | }, { 75 | test: /\.jsx?$/, 76 | use: [ 'babel-loader' ], 77 | exclude: /node_modules/ 78 | }, { 79 | test: /\.css$/, 80 | use: [ 'style-loader', 'css-loader' ] 81 | }, { 82 | test: /\.scss$/, 83 | use: [ 84 | { 85 | loader: "style-loader" // creates style nodes from JS strings 86 | }, { 87 | loader: "css-loader" // translates CSS into CommonJS 88 | }, { 89 | loader: "sass-loader" // compiles Sass to CSS 90 | } 91 | ] 92 | } 93 | ] 94 | }, 95 | 96 | plugins: [ 97 | 98 | new webpack.DefinePlugin( { 99 | 'process.env.NODE_ENV': JSON.stringify( 'production' ) 100 | } ), 101 | 102 | new UglifyJSPlugin(), 103 | 104 | // new BundleAnalyzerPlugin(), 105 | 106 | new webpack.optimize.AggressiveMergingPlugin() 107 | 108 | ] 109 | }; 110 | -------------------------------------------------------------------------------- /wipe-dependencies.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | wipeDependencies = function() { 3 | var file = fs.readFileSync('package.json'), 4 | content = JSON.parse(file); 5 | for (var devDep in content.devDependencies) { 6 | content.devDependencies[devDep] = '*'; 7 | } 8 | for (var dep in content.dependencies) { 9 | content.dependencies[dep] = '*'; 10 | } 11 | fs.writeFileSync('package.json', JSON.stringify(content)); 12 | }; 13 | if (require.main === module) { 14 | wipeDependencies(); 15 | } else { 16 | module.exports = wipeDependencies; 17 | } --------------------------------------------------------------------------------