├── .eslintrc
├── .gitignore
├── .jscs.json
├── .jshintrc
├── LICENSE
├── README.md
├── dist
├── humanize-string.js
├── icon.js
├── index.js
├── multiple.js
├── option-mixin.js
├── option-wrapper.js
├── option.js
├── options.js
├── search-mixin.js
├── single.js
└── text-highlight.js
├── example
├── base.jsx
├── build
│ └── .gitkeep
├── code-snippets
│ ├── custom-render.jsx
│ ├── multiple.jsx
│ └── single.jsx
├── components
│ ├── code-snippet.jsx
│ ├── custom-render.jsx
│ ├── features.jsx
│ ├── footer.jsx
│ ├── github-ribbon.jsx
│ ├── header.jsx
│ └── install.jsx
├── css
│ ├── _variables.scss
│ ├── choice
│ │ ├── _icon.scss
│ │ ├── _input.scss
│ │ ├── _multiple.scss
│ │ ├── _option.scss
│ │ ├── _options.scss
│ │ ├── _single.scss
│ │ ├── _text-highlight.scss
│ │ ├── _value.scss
│ │ ├── _wrapper.scss
│ │ ├── index.css
│ │ └── index.scss
│ ├── components
│ │ ├── _code-snippet.scss
│ │ ├── _features.scss
│ │ ├── _footer.scss
│ │ ├── _header.scss
│ │ ├── _ofs-credit.scss
│ │ ├── _pokemon.scss
│ │ └── _tutorial.scss
│ └── index.scss
├── data
│ ├── countries.js
│ └── pokemon.js
├── img
│ └── logo.png
├── index.jsx
└── js
│ └── index.js
├── gulpfile.js
├── package.json
├── src
├── humanize-string.js
├── icon.jsx
├── index.js
├── multiple.jsx
├── option-mixin.js
├── option-wrapper.jsx
├── option.jsx
├── options.jsx
├── search-mixin.js
├── single.jsx
└── text-highlight.jsx
└── webpack.config.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ecmaFeatures": {
3 | "arrowFunctions": true,
4 | "binaryLiterals": false,
5 | "blockBindings": true,
6 | "classes": true,
7 | "defaultParams": true,
8 | "destructuring": true,
9 | "forOf": false,
10 | "generators": true,
11 | "modules": true,
12 | "objectLiteralComputedProperties": true,
13 | "objectLiteralDuplicateProperties": false,
14 | "objectLiteralShorthandMethods": true,
15 | "objectLiteralShorthandProperties": true,
16 | "octalLiterals": true,
17 | "regexUFlag": true,
18 | "regexYFlag": true,
19 | "superInFunctions": false,
20 | "templateStrings": true,
21 | "unicodeCodePointEscapes": false,
22 | "globalReturn": false,
23 | "jsx": true
24 | },
25 |
26 | "parser": "babel-eslint",
27 |
28 | "plugins": [
29 | "react"
30 | ],
31 |
32 | "env": {
33 | "browser": true,
34 | "node": true,
35 | "es6": true
36 | },
37 |
38 | "rules": {
39 | "no-alert": 2,
40 | "no-array-constructor": 2,
41 | "no-bitwise": 0,
42 | "no-caller": 2,
43 | "no-catch-shadow": 0,
44 | "no-cond-assign": 2,
45 | "no-console": 1,
46 | "no-constant-condition": 2,
47 | "no-control-regex": 2,
48 | "no-debugger": 2,
49 | "no-delete-var": 2,
50 | "no-div-regex": 0,
51 | "no-dupe-keys": 2,
52 | "no-dupe-args": 2,
53 | "no-else-return": 1,
54 | "no-empty": 2,
55 | "no-empty-class": 2,
56 | "no-empty-label": 2,
57 | "no-eq-null": 0,
58 | "no-eval": 2,
59 | "no-ex-assign": 2,
60 | "no-extend-native": 2,
61 | "no-extra-bind": 2,
62 | "no-extra-boolean-cast": 2,
63 | "no-extra-parens": 0,
64 | "no-extra-semi": 2,
65 | "no-extra-strict": 2,
66 | "no-fallthrough": 2,
67 | "no-floating-decimal": 0,
68 | "no-func-assign": 2,
69 | "no-implied-eval": 2,
70 | "no-inline-comments": 0,
71 | "no-inner-declarations": [2, "functions"],
72 | "no-invalid-regexp": 2,
73 | "no-irregular-whitespace": 2,
74 | "no-iterator": 2,
75 | "no-label-var": 2,
76 | "no-labels": 2,
77 | "no-lone-blocks": 2,
78 | "no-lonely-if": 0,
79 | "no-loop-func": 2,
80 | "no-mixed-requires": [1, true],
81 | "no-mixed-spaces-and-tabs": [2, false],
82 | "no-multi-spaces": [2, { exceptions: { "VariableDeclarator": true } }],
83 | "no-multi-str": 2,
84 | "no-multiple-empty-lines": [0, {"max": 2}],
85 | "no-native-reassign": 2,
86 | "no-negated-in-lhs": 2,
87 | "no-nested-ternary": 1,
88 | "no-new": 2,
89 | "no-new-func": 2,
90 | "no-new-object": 2,
91 | "no-new-require": 0,
92 | "no-new-wrappers": 2,
93 | "no-obj-calls": 2,
94 | "no-octal": 2,
95 | "no-octal-escape": 2,
96 | "no-path-concat": 0,
97 | "no-plusplus": 0,
98 | "no-process-env": 0,
99 | "no-process-exit": 2,
100 | "no-proto": 2,
101 | "no-redeclare": 2,
102 | "no-regex-spaces": 2,
103 | "no-reserved-keys": 0,
104 | "no-restricted-modules": 0,
105 | "no-return-assign": 2,
106 | "no-script-url": 2,
107 | "no-self-compare": 0,
108 | "no-sequences": 2,
109 | "no-shadow": 2,
110 | "no-shadow-restricted-names": 2,
111 | "no-space-before-semi": 0,
112 | "no-spaced-func": 2,
113 | "no-sparse-arrays": 2,
114 | "no-sync": 0,
115 | "no-ternary": 0,
116 | "no-trailing-spaces": 2,
117 | "no-throw-literal": 0,
118 | "no-undef": 2,
119 | "no-undef-init": 2,
120 | "no-undefined": 0,
121 | "no-underscore-dangle": 0,
122 | "no-unreachable": 2,
123 | "no-unused-expressions": 2,
124 | "no-unused-vars": [1, {"vars": "all", "args": "after-used"}],
125 | "no-use-before-define": 2,
126 | "no-void": 0,
127 | "no-var": 0,
128 | "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
129 | "no-with": 2,
130 | "no-wrap-func": 2,
131 |
132 | "block-scoped-var": 0,
133 | "brace-style": [0, "1tbs"],
134 | "camelcase": 2,
135 | "comma-spacing": 2,
136 | "comma-style": 0,
137 | "comma-dangle": 0,
138 | "complexity": [0, 11],
139 | "consistent-return": 2,
140 | "consistent-this": [0, "that"],
141 | "curly": [2, "all"],
142 | "default-case": 0,
143 | "dot-notation": [2, { "allowKeywords": true, "allowPattern": "^[a-zA-Z/d]+(_[a-zA-Z/d]+)+$" }],
144 | "eol-last": 2,
145 | "eqeqeq": 2,
146 | "func-names": 0,
147 | "func-style": [0, "declaration"],
148 | "generator-star": 0,
149 | "global-strict": [0, "never"],
150 | "guard-for-in": 0,
151 | "handle-callback-err": 0,
152 | "indent": [2, 2],
153 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
154 | "max-depth": [0, 4],
155 | "max-len": [0, 80, 4],
156 | "max-nested-callbacks": [0, 2],
157 | "max-params": [0, 3],
158 | "max-statements": [0, 10],
159 | "new-cap": 0,
160 | "new-parens": 2,
161 | "one-var": 0,
162 | "operator-assignment": [0, "always"],
163 | "padded-blocks": 0,
164 | "quote-props": 0,
165 | "quotes": [0, "double"],
166 | "radix": 0,
167 | "semi": 2,
168 | "semi-spacing": [2, {"before": false, "after": true}],
169 | "sort-vars": 0,
170 | "space-after-function-name": [2, "never"],
171 | "space-after-keywords": [2, "always"],
172 | "space-before-blocks": [0, "always"],
173 | "space-before-function-parentheses": [0, "always"],
174 | "space-in-brackets": [0, "never"],
175 | "space-in-parens": [0, "never"],
176 | "space-infix-ops": 2,
177 | "space-return-throw-case": 2,
178 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
179 | "spaced-line-comment": [0, "always"],
180 | "strict": 2,
181 | "use-isnan": 2,
182 | "valid-jsdoc": 0,
183 | "valid-typeof": 2,
184 | "vars-on-top": 0,
185 | "wrap-iife": 0,
186 | "wrap-regex": 0,
187 | "yoda": [2, "never"],
188 |
189 | // eslint-plugin-react rules
190 | "react/jsx-boolean-value": [1, "always"],
191 | "react/jsx-uses-react": 1,
192 | "react/jsx-uses-vars": 1,
193 | "react/jsx-no-undef": 2,
194 | "react/no-did-mount-set-state": 1,
195 | "react/no-did-update-set-state": 1,
196 | "react/no-multi-comp": 1,
197 | "react/no-unknown-property": 1,
198 | "react/prop-types": 1,
199 | "react/react-in-jsx-scope": 2,
200 | "react/self-closing-comp": 1,
201 | "react/wrap-multilines": 2
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .sass-cache/
2 | node_modules/
3 | .module-cache/
4 | example/index.html
5 | example/build/
6 | example/css/*.css
7 |
--------------------------------------------------------------------------------
/.jscs.json:
--------------------------------------------------------------------------------
1 | {
2 | "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
3 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
4 | "requireSpaceBeforeBinaryOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
5 | "requireSpaceAfterBinaryOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=", ","],
6 | "requireSpaceBeforePostfixUnaryOperators": ["+", "-", "~", "!"],
7 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
8 | "disallowSpaceAfterBinaryOperators": ["!"],
9 | "requireSpacesInConditionalExpression": true,
10 | "disallowSpaceBeforeBinaryOperators": [","],
11 | "disallowImplicitTypeConversion": ["string"],
12 | "disallowKeywords": ["with"],
13 | "disallowMultipleLineBreaks": true,
14 | "disallowKeywordsOnNewLine": ["else"],
15 | "disallowMixedSpacesAndTabs": true,
16 | "disallowTrailingWhitespace": true,
17 | "requireLineFeedAtFileEnd": true,
18 | "requireSpacesInFunctionExpression": {
19 | "beforeOpeningCurlyBrace": true
20 | },
21 | "disallowSpacesInFunctionExpression": {
22 | "beforeOpeningRoundBrace": true
23 | },
24 | "validateIndentation": 2
25 | }
26 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "newcap": false,
3 | "globalstrict": true,
4 | "node": true,
5 | "browser": true
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Likealike, Ltd. DBA onefinestay
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | React Choice
2 | ======================
3 |
4 | A React based customisable select box.
5 |
6 | [Demo](http://onefinestay.github.io/react-choice/)
7 |
8 | ## Features
9 |
10 | * Search text highlighting
11 | * Single or multiple selection
12 | * Custom results rendering
13 |
14 | ## Contribute
15 |
16 | Please feel to contribute to this project by making pull requests. You can see a
17 | list of tasks that can be worked on in the [issues list](https://github.com/onefinestay/react-choice/issues).
18 |
19 | ### Building example page
20 |
21 | Once you have the repository cloned run the following commands to get started:
22 |
23 | ```shell
24 | npm install
25 | gulp develop
26 | ```
27 |
28 | This will start a local server at `http://localhost:9989` where you can see the
29 | example page. It will also watch for any files changes and rebuild.
30 |
--------------------------------------------------------------------------------
/dist/humanize-string.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (string, upperCase) {
4 | if (typeof string == 'string') {
5 | var firstCharacter = string.charAt(0);
6 |
7 | if (typeof upperCase === 'undefined' || upperCase === true) {
8 | firstCharacter = firstCharacter.toUpperCase();
9 | }
10 |
11 | var display = firstCharacter + string.slice(1);
12 | return display.replace(/_|-/g, ' ');
13 | } else {
14 | return string;
15 | }
16 | };
--------------------------------------------------------------------------------
/dist/icon.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 | var cx = React.addons.classSet;
5 |
6 | var Icon = React.createClass({
7 | displayName: 'Icon',
8 |
9 | propTypes: {
10 | focused: React.PropTypes.bool.isRequired
11 | },
12 |
13 | render: function render() {
14 | var arrowClasses = cx({
15 | 'react-choice-icon__arrow': true,
16 | 'react-choice-icon__arrow--up': this.props.focused,
17 | 'react-choice-icon__arrow--down': !this.props.focused
18 | });
19 |
20 | return React.createElement('div', { className: arrowClasses });
21 | }
22 | });
23 |
24 | module.exports = Icon;
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true
5 | });
6 | exports['default'] = {
7 | 'Select': require('./single'),
8 | 'SelectMultiple': require('./multiple'),
9 | 'Option': require('./option'),
10 | 'OptionMixin': require('./option-mixin'),
11 | 'TextHighlight': require('./text-highlight')
12 | };
13 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/dist/multiple.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 | var _ = require('lodash');
5 | var cx = React.addons.classSet;
6 | var cloneWithProps = React.addons.cloneWithProps;
7 |
8 | var Options = require('./options');
9 | var OptionWrapper = require('./option-wrapper');
10 |
11 | var SearchMixin = require('./search-mixin');
12 |
13 | var ValueWrapper = React.createClass({
14 | displayName: 'ValueWrapper',
15 |
16 | propTypes: {
17 | onClick: React.PropTypes.func.isRequired,
18 | onDeleteClick: React.PropTypes.func.isRequired
19 | },
20 |
21 | onDeleteClick: function onDeleteClick(event) {
22 | event.stopPropagation();
23 | this.props.onDeleteClick(event);
24 | },
25 |
26 | render: function render() {
27 | var classes = cx({
28 | 'react-choice-value': true,
29 | 'react-choice-value--is-selected': this.props.selected
30 | });
31 |
32 | return React.createElement(
33 | 'div',
34 | { className: classes, onClick: this.props.onClick },
35 | React.createElement(
36 | 'div',
37 | { className: 'react-choice-value__children' },
38 | this.props.children
39 | ),
40 | React.createElement(
41 | 'a',
42 | { className: 'react-choice-value__delete', onClick: this.onDeleteClick },
43 | 'x'
44 | )
45 | );
46 | }
47 | });
48 |
49 | var MultipleChoice = React.createClass({
50 | displayName: 'MultipleChoice',
51 |
52 | mixins: [SearchMixin],
53 |
54 | propTypes: {
55 | name: React.PropTypes.string, // name of input
56 | placeholder: React.PropTypes.string, // input placeholder
57 | values: React.PropTypes.array, // initial values
58 |
59 | children: React.PropTypes.array.isRequired,
60 |
61 | valueField: React.PropTypes.string, // value field name
62 | labelField: React.PropTypes.string, // label field name
63 |
64 | searchField: React.PropTypes.array, // array of search fields
65 |
66 | onSelect: React.PropTypes.func, // function called when option is selected
67 | onDelete: React.PropTypes.func, // function called when option is deleted
68 | allowDuplicates: React.PropTypes.bool // if true, the same values can be added multiple times
69 | },
70 |
71 | getDefaultProps: function getDefaultProps() {
72 | return {
73 | values: [],
74 | valueField: 'value',
75 | labelField: 'children',
76 | searchField: ['children'],
77 | allowDuplicates: false
78 | };
79 | },
80 |
81 | getInitialState: function getInitialState() {
82 | var props = this.props.searchField;
83 | props.push(this.props.valueField);
84 | props.push(this.props.searchField);
85 | props = _.uniq(props);
86 |
87 | var options = _.map(this.props.children, function (child) {
88 | // TODO Validation ?
89 | return _.pick(child.props, props);
90 | }, this);
91 |
92 | return {
93 | focus: false,
94 | searchResults: this._sort(options),
95 | values: this.props.values,
96 | initialOptions: options,
97 | highlighted: null,
98 | selected: null,
99 | selectedIndex: -1,
100 | searchTokens: []
101 | };
102 | },
103 |
104 | _handleContainerInput: function _handleContainerInput(event) {
105 | var keys = {
106 | 37: this._moveLeft,
107 | 39: this._moveRight,
108 | 8: this._removeSelectedContainer
109 | };
110 |
111 | if (typeof keys[event.keyCode] === 'function') {
112 | keys[event.keyCode](event);
113 | }
114 | },
115 |
116 | _handleContainerBlur: function _handleContainerBlur() {
117 | if (this.state.selectedIndex) {
118 | this.setState({
119 | selectedIndex: -1
120 | });
121 | }
122 | },
123 |
124 | _selectOption: function _selectOption(option) {
125 | if (option) {
126 | var values = this.state.values.slice(0); // copy
127 | var options = this._getAvailableOptions(values);
128 |
129 | // determine which item to highlight
130 | var valueField = this.props.valueField;
131 | var optionIndex = _.findIndex(options, function (o) {
132 | return option[valueField] === o[valueField];
133 | });
134 |
135 | values.push(option);
136 |
137 | options = this._getAvailableOptions(values);
138 | var state = this._resetSearch(options);
139 | state.values = values;
140 |
141 | var nextOption = options[optionIndex];
142 | if (_.isUndefined(nextOption)) {
143 | // at the end of the list so select previous one
144 | nextOption = options[optionIndex - 1];
145 | if (_.isUndefined(nextOption)) {
146 | // bail out
147 | nextOption = _.first(options);
148 | }
149 | }
150 |
151 | state.highlighted = nextOption;
152 |
153 | this.setState(state);
154 |
155 | if (typeof this.props.onSelect === 'function') {
156 | this.props.onSelect(option, values);
157 | }
158 | }
159 | },
160 |
161 | _getAvailableOptions: function _getAvailableOptions(values) {
162 | var options = this.state.initialOptions;
163 | var valueField = this.props.valueField;
164 |
165 | if (this.props.allowDuplicates === false && values) {
166 | options = _.filter(options, function (option) {
167 | var found = _.find(values, function (value) {
168 | return value[valueField] === option[valueField];
169 | });
170 |
171 | return typeof found === 'undefined';
172 | });
173 | }
174 |
175 | return this._sort(options);
176 | },
177 |
178 | _moveLeft: function _moveLeft(event) {
179 | var input = this.refs.input.getDOMNode();
180 |
181 | if (!this.state.values.length) {
182 | return false;
183 | }
184 |
185 | if (event.target === input && event.target.selectionStart === 0) {
186 | event.preventDefault();
187 |
188 | // select stage
189 | this.setState({
190 | selectedIndex: this.state.values.length - 1
191 | });
192 |
193 | // focus on container
194 | this.refs.container.getDOMNode().focus();
195 | } else if (this.state.selectedIndex !== -1) {
196 | var nextIndex = this.state.selectedIndex - 1;
197 | if (nextIndex > -1) {
198 | this.setState({
199 | selectedIndex: nextIndex
200 | });
201 | }
202 | }
203 | },
204 |
205 | _moveRight: function _moveRight() {
206 | var input = this.refs.input.getDOMNode();
207 |
208 | if (!this.state.values.length) {
209 | return false;
210 | }
211 |
212 | if (this.state.selectedIndex !== -1) {
213 | var nextIndex = this.state.selectedIndex + 1;
214 | if (nextIndex < this.state.values.length) {
215 | this.setState({
216 | selectedIndex: nextIndex
217 | });
218 | } else {
219 | // focus input box
220 | input.focus();
221 | this.setState({
222 | selectedIndex: -1
223 | });
224 | }
225 | }
226 | },
227 |
228 | _removeValue: function _removeValue(index) {
229 | var values = this.state.values.slice(0); // copy
230 | var removedOption = values.splice(index, 1);
231 |
232 | var options = this._getAvailableOptions(values);
233 |
234 | var state = this._resetSearch(options);
235 | state.values = values;
236 |
237 | this.setState(state);
238 |
239 | if (typeof this.props.onDelete === 'function') {
240 | this.props.onDelete(removedOption, values);
241 | }
242 | },
243 |
244 | // removes last element
245 | _remove: function _remove(event) {
246 | if (!this.state.value) {
247 | event.preventDefault();
248 |
249 | // remove last stage
250 | if (this.state.values.length) {
251 | this._removeValue(this.state.values.length - 1);
252 | }
253 | }
254 | },
255 |
256 | // called from within, removes selected element
257 | _removeSelectedContainer: function _removeSelectedContainer(event) {
258 | if (this.state.selectedIndex !== -1) {
259 | event.preventDefault();
260 |
261 | // move selection to the element before the removed one (gmail behavior)
262 | this.setState({
263 | selectedIndex: this.state.selectedIndex - 1
264 | });
265 |
266 | this._removeValue(this.state.selectedIndex);
267 | }
268 | },
269 |
270 | _removeDeletedContainer: function _removeDeletedContainer(index) {
271 | this._removeValue(index);
272 | },
273 |
274 | _selectValue: function _selectValue(index, event) {
275 | if (event) {
276 | event.preventDefault();
277 | event.stopPropagation();
278 | }
279 |
280 | this.setState({
281 | selectedIndex: index
282 | });
283 |
284 | this.refs.container.getDOMNode().focus();
285 | },
286 |
287 | _handleBlur: function _handleBlur(event) {
288 | if (this._optionsMouseDown === true) {
289 | this._optionsMouseDown = false;
290 | this.refs.input.getDOMNode().focus();
291 | event.preventDefault();
292 | event.stopPropagation();
293 | } else {
294 | event.preventDefault();
295 | this.setState({
296 | focus: false
297 | });
298 | }
299 | },
300 |
301 | _handleOptionsMouseDown: function _handleOptionsMouseDown() {
302 | this._optionsMouseDown = true;
303 | },
304 |
305 | componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
306 | if (_.isEqual(nextProps.values, this.props.values)) {
307 | var options = this._getAvailableOptions(nextProps.values);
308 |
309 | var state = this._resetSearch(options);
310 | state.values = nextProps.values;
311 | state.selected = null;
312 |
313 | this.setState(state);
314 | }
315 | },
316 |
317 | componentDidUpdate: function componentDidUpdate() {
318 | this._updateScrollPosition();
319 | },
320 |
321 | render: function render() {
322 | var values = _.map(this.state.values, function (v, i) {
323 | var key = v[this.props.valueField];
324 |
325 | var selected = i === this.state.selectedIndex;
326 |
327 | var label = v[this.props.labelField];
328 |
329 | return React.createElement(
330 | ValueWrapper,
331 | { key: i,
332 | onClick: this._selectValue.bind(null, i),
333 | onDeleteClick: this._removeDeletedContainer.bind(null, i),
334 | selected: selected },
335 | React.createElement(
336 | 'div',
337 | null,
338 | label
339 | )
340 | );
341 | }, this);
342 |
343 | var options = _.map(this.state.searchResults, function (option) {
344 | var valueField = this.props.valueField;
345 | var v = option[valueField];
346 |
347 | var child = _.find(this.props.children, function (c) {
348 | return c.props[valueField] === v;
349 | });
350 |
351 | var highlighted = this.state.highlighted && v === this.state.highlighted[valueField];
352 |
353 | child = cloneWithProps(child, { tokens: this.state.searchTokens });
354 |
355 | return React.createElement(
356 | OptionWrapper,
357 | { key: v,
358 | selected: highlighted,
359 | ref: highlighted ? 'highlighted' : null,
360 | option: option,
361 | onHover: this._handleOptionHover,
362 | onClick: this._handleOptionClick },
363 | child
364 | );
365 | }, this);
366 |
367 | var value = this.state.value;
368 |
369 | var wrapperClasses = cx({
370 | 'react-choice-wrapper': true,
371 | 'react-choice-multiple': true,
372 | 'react-choice-multiple--in-focus': this.state.focus,
373 | 'react-choice-multiple--not-in-focus': !this.state.focus
374 | });
375 |
376 | return React.createElement(
377 | 'div',
378 | { className: 'react-choice' },
379 | React.createElement(
380 | 'div',
381 | { className: wrapperClasses, onClick: this._handleClick,
382 | tabIndex: '-1', ref: 'container', onKeyDown: this._handleContainerInput,
383 | onBlur: this._handleContainerBlur },
384 | values,
385 | React.createElement('input', { type: 'text',
386 | placeholder: this.props.placeholder,
387 | value: value,
388 | className: 'react-choice-input react-choice-multiple__input',
389 |
390 | onKeyDown: this._handleInput,
391 | onChange: this._handleChange,
392 | onFocus: this._handleFocus,
393 | onBlur: this._handleBlur,
394 |
395 | autoComplete: 'off',
396 | role: 'combobox',
397 | 'aria-autocomplete': 'list',
398 | 'aria-expanded': this.state.focus,
399 | ref: 'input' })
400 | ),
401 | this.state.focus ? React.createElement(
402 | Options,
403 | { onMouseDown: this._handleOptionsMouseDown, ref: 'options' },
404 | options
405 | ) : null
406 | );
407 | }
408 | });
409 |
410 | module.exports = MultipleChoice;
--------------------------------------------------------------------------------
/dist/option-mixin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require("react/addons");
4 |
5 | var OptionMixin = {
6 | propTypes: {
7 | tokens: React.PropTypes.array.isRequired,
8 | children: React.PropTypes.string.isRequired
9 | }
10 | };
11 |
12 | module.exports = OptionMixin;
--------------------------------------------------------------------------------
/dist/option-wrapper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 | var cx = React.addons.classSet;
5 |
6 | function isArray(test) {
7 | return Object.prototype.toString.call(test) === '[object Array]';
8 | }
9 |
10 | var OptionWrapper = React.createClass({
11 | displayName: 'OptionWrapper',
12 |
13 | render: function render() {
14 | var classes = cx({
15 | 'react-choice-option': true,
16 | 'react-choice-option--selected': !!this.props.selected
17 | });
18 |
19 | return React.createElement(
20 | 'li',
21 | { className: classes,
22 | onMouseEnter: this.props.onHover.bind(null, this.props.option),
23 | onMouseDown: this.props.onClick.bind(null, this.props.option),
24 | onTouchStart: this.props.onClick.bind(null, this.props.option) },
25 | this.props.children
26 | );
27 | }
28 | });
29 |
30 | module.exports = OptionWrapper;
--------------------------------------------------------------------------------
/dist/option.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 | var cx = React.addons.classSet;
5 |
6 | var OptionMixin = require('./option-mixin');
7 | var TextHighlight = require('./text-highlight');
8 |
9 | //
10 | // Select option
11 | //
12 | var SelectOption = React.createClass({
13 | displayName: 'SelectOption',
14 |
15 | mixins: [OptionMixin],
16 |
17 | render: function render() {
18 | return React.createElement(
19 | 'div',
20 | null,
21 | React.createElement(
22 | TextHighlight,
23 | { tokens: this.props.tokens },
24 | this.props.children
25 | )
26 | );
27 | }
28 | });
29 |
30 | module.exports = SelectOption;
--------------------------------------------------------------------------------
/dist/options.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require("react/addons");
4 |
5 | var Options = React.createClass({
6 | displayName: "Options",
7 |
8 | propTypes: {
9 | children: React.PropTypes.array.isRequired,
10 | onMouseDown: React.PropTypes.func.isRequired
11 | },
12 |
13 | _handleMouseDown: function _handleMouseDown(event) {
14 | this.props.onMouseDown(event);
15 | },
16 |
17 | render: function render() {
18 | return React.createElement(
19 | "div",
20 | { className: "react-choice-options",
21 | onMouseDown: this._handleMouseDown,
22 | onMouseUp: this._handleMouseUp },
23 | React.createElement(
24 | "ul",
25 | { className: "react-choice-options__list" },
26 | this.props.children
27 | )
28 | );
29 | }
30 | });
31 |
32 | module.exports = Options;
--------------------------------------------------------------------------------
/dist/search-mixin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var Sifter = require('sifter');
5 |
6 | var SearchMixin = {
7 | //
8 | // Public methods
9 | //
10 | focus: function focus(openOptions) {
11 | this.refs.input.getDOMNode().focus();
12 | },
13 |
14 | _sort: function _sort(list) {
15 | if (typeof this.props.sorter === 'function') {
16 | return this.props.sorter(list);
17 | }
18 | return _.sortBy(list, this.props.labelField);
19 | },
20 |
21 | _handleClick: function _handleClick(event) {
22 | this.refs.input.getDOMNode().focus();
23 | },
24 |
25 | _handleInput: function _handleInput(event) {
26 | var keys = {
27 | 13: this._enter,
28 | 37: this._moveLeft,
29 | 38: this._moveUp,
30 | 39: this._moveRight,
31 | 40: this._moveDown,
32 | 8: this._remove
33 | };
34 |
35 | if (typeof keys[event.keyCode] == 'function') {
36 | keys[event.keyCode](event);
37 | }
38 | },
39 |
40 | _handleChange: function _handleChange(event) {
41 | event.preventDefault();
42 |
43 | var query = event.target.value;
44 |
45 | var options = this._getAvailableOptions();
46 |
47 | var searcher = new Sifter(options);
48 |
49 | var result = searcher.search(query, {
50 | fields: this.props.searchField
51 | });
52 |
53 | var searchResults = _.map(result.items, function (res) {
54 | return options[res.id];
55 | });
56 |
57 | var highlighted = _.first(searchResults);
58 |
59 | this.setState({
60 | value: query,
61 | searchResults: searchResults,
62 | searchTokens: result.tokens,
63 | highlighted: highlighted,
64 | selected: null });
65 | },
66 |
67 | _handleFocus: function _handleFocus(event) {
68 | event.preventDefault();
69 |
70 | var highlighted;
71 | if (this.state.selected) {
72 | highlighted = _.find(this.state.searchResults, function (option) {
73 | return option[this.props.valueField] == this.state.selected[this.props.valueField];
74 | }, this);
75 | } else {
76 | highlighted = _.first(this.state.searchResults);
77 | }
78 |
79 | this.setState({
80 | focus: true,
81 | highlighted: highlighted
82 | });
83 | },
84 |
85 | _handleOptionHover: function _handleOptionHover(option, event) {
86 | event.preventDefault();
87 | this.setState({
88 | highlighted: option
89 | });
90 | },
91 |
92 | _handleOptionClick: function _handleOptionClick(option, event) {
93 | event.preventDefault();
94 | event.stopPropagation();
95 | this._selectOption(option);
96 | },
97 |
98 | _moveUp: function _moveUp(event) {
99 | var options = this.state.searchResults;
100 | if (options.length > 0) {
101 | event.preventDefault();
102 | var index = _.indexOf(options, this.state.highlighted);
103 | if (!_.isUndefined(options[index - 1])) {
104 | this.setState({
105 | highlighted: options[index - 1]
106 | });
107 | }
108 | }
109 | },
110 |
111 | _moveDown: function _moveDown(event) {
112 | var options = this.state.searchResults;
113 | if (options.length > 0) {
114 | event.preventDefault();
115 | var index = _.indexOf(options, this.state.highlighted);
116 | if (!_.isUndefined(options[index + 1])) {
117 | this.setState({
118 | highlighted: options[index + 1]
119 | });
120 | }
121 | }
122 | },
123 |
124 | _enter: function _enter(event) {
125 | event.preventDefault();
126 | this._selectOption(this.state.highlighted);
127 | },
128 |
129 | _updateScrollPosition: function _updateScrollPosition() {
130 | var highlighted = this.refs.highlighted;
131 | if (highlighted) {
132 | // find if highlighted option is not visible
133 | var el = highlighted.getDOMNode();
134 | var parent = this.refs.options.getDOMNode();
135 | var offsetTop = el.offsetTop + el.clientHeight - parent.scrollTop;
136 |
137 | // scroll down
138 | if (offsetTop > parent.clientHeight) {
139 | var diff = el.offsetTop + el.clientHeight - parent.clientHeight;
140 | parent.scrollTop = diff;
141 | } else if (offsetTop - el.clientHeight < 0) {
142 | // scroll up
143 | parent.scrollTop = el.offsetTop;
144 | }
145 | }
146 | },
147 |
148 | _resetSearch: function _resetSearch(options) {
149 | return {
150 | value: '',
151 | searchResults: options,
152 | searchTokens: [],
153 | highlighted: _.first(options)
154 | };
155 | } };
156 |
157 | module.exports = SearchMixin;
--------------------------------------------------------------------------------
/dist/single.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 | var _ = require('lodash');
5 | var cx = React.addons.classSet;
6 | var cloneWithProps = React.addons.cloneWithProps;
7 |
8 | var Icon = require('./icon');
9 | var Options = require('./options');
10 | var OptionWrapper = require('./option-wrapper');
11 |
12 | var SearchMixin = require('./search-mixin');
13 |
14 | //
15 | // Auto complete select box
16 | //
17 | var SingleChoice = React.createClass({
18 | displayName: 'SingleChoice',
19 |
20 | mixins: [SearchMixin],
21 |
22 | propTypes: {
23 | name: React.PropTypes.string, // name of input
24 | placeholder: React.PropTypes.string, // input placeholder
25 | value: React.PropTypes.string, // initial value for input field
26 | children: React.PropTypes.array.isRequired,
27 |
28 | valueField: React.PropTypes.string, // value field name
29 | labelField: React.PropTypes.string, // label field name
30 |
31 | searchField: React.PropTypes.array, // array of search fields
32 |
33 | icon: React.PropTypes.func, // icon render
34 |
35 | onSelect: React.PropTypes.func // function called when option is selected
36 | },
37 |
38 | getDefaultProps: function getDefaultProps() {
39 | return {
40 | valueField: 'value',
41 | labelField: 'children',
42 | searchField: ['children']
43 | };
44 | },
45 |
46 | _getAvailableOptions: function _getAvailableOptions() {
47 | var options = this.state.initialOptions;
48 |
49 | return this._sort(options);
50 | },
51 |
52 | getInitialState: function getInitialState() {
53 | var selected = null;
54 |
55 | var props = this.props.searchField;
56 | props.push(this.props.valueField);
57 | props.push(this.props.searchField);
58 | props = _.uniq(props);
59 |
60 | var options = _.map(this.props.children, function (child) {
61 | // TODO Validation ?
62 | return _.pick(child.props, props);
63 | }, this);
64 |
65 | if (this.props.value) {
66 | // find selected value
67 | selected = _.find(options, function (option) {
68 | return option[this.props.valueField] === this.props.value;
69 | }, this);
70 | }
71 |
72 | return {
73 | value: selected ? selected[this.props.labelField] : this.props.value,
74 | focus: false,
75 | searchResults: this._sort(options),
76 | initialOptions: options,
77 | highlighted: null,
78 | selected: selected,
79 | searchTokens: []
80 | };
81 | },
82 |
83 | //
84 | // Public methods
85 | //
86 | getValue: function getValue() {
87 | return this.state.selected ? this.state.selected[this.props.valueField] : null;
88 | },
89 |
90 | //
91 | // Events
92 | //
93 | _handleArrowClick: function _handleArrowClick(event) {
94 | if (this.state.focus) {
95 | this._handleBlur(event);
96 | this.refs.input.getDOMNode().blur();
97 | } else {
98 | this._handleFocus(event);
99 | this.refs.input.getDOMNode().focus();
100 | }
101 | },
102 |
103 | _remove: function _remove(event) {
104 | if (this.state.selected) {
105 | event.preventDefault();
106 |
107 | var state = this._resetSearch(this.state.initialOptions);
108 | state.selected = null;
109 |
110 | this.setState(state);
111 | }
112 | },
113 |
114 | _selectOption: function _selectOption(option) {
115 | this._optionsMouseDown = false;
116 | this.refs.input.getDOMNode().blur();
117 | this.setState({
118 | focus: false
119 | });
120 |
121 | if (option) {
122 | var options = this._getAvailableOptions();
123 | var state = this._resetSearch(options);
124 | state.selected = option;
125 |
126 | this.setState(state);
127 |
128 | if (typeof this.props.onSelect === 'function') {
129 | this.props.onSelect(option);
130 | }
131 | }
132 | },
133 |
134 | _handleBlur: function _handleBlur(event) {
135 | event.preventDefault();
136 | if (this._optionsMouseDown === true) {
137 | this._optionsMouseDown = false;
138 | this.refs.input.getDOMNode().focus();
139 | event.stopPropagation();
140 | } else {
141 | this.setState({
142 | focus: false
143 | });
144 | }
145 | },
146 |
147 | _handleOptionsMouseDown: function _handleOptionsMouseDown() {
148 | this._optionsMouseDown = true;
149 | },
150 |
151 | componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
152 | if (nextProps.value !== this.props.value) {
153 | var options = this._getAvailableOptions();
154 |
155 | var selected = _.find(options, function (option) {
156 | return option[this.props.valueField] === nextProps.value;
157 | }, this);
158 |
159 | var state = this._resetSearch(options);
160 | state.value = selected ? selected[this.props.labelField] : nextProps.value;
161 | state.selected = selected;
162 |
163 | this.setState(state);
164 | }
165 | },
166 |
167 | componentDidUpdate: function componentDidUpdate(prevProps, prevState) {
168 | if (prevState.focus === false && this.state.focus === true) {
169 | this._updateScrollPosition();
170 | }
171 |
172 | // select selected text in input box
173 | if (this.state.selected && this.state.focus) {
174 | setTimeout((function () {
175 | if (this.isMounted()) {
176 | this.refs.input.getDOMNode().select();
177 | }
178 | }).bind(this), 50);
179 | }
180 | },
181 |
182 | render: function render() {
183 | var options = _.map(this.state.searchResults, function (option) {
184 | var valueField = this.props.valueField;
185 | var v = option[valueField];
186 |
187 | var child = _.find(this.props.children, function (c) {
188 | return c.props[valueField] === v;
189 | });
190 |
191 | var highlighted = this.state.highlighted && v === this.state.highlighted[valueField];
192 |
193 | child = cloneWithProps(child, { tokens: this.state.searchTokens });
194 |
195 | return React.createElement(
196 | OptionWrapper,
197 | { key: v,
198 | selected: highlighted,
199 | ref: highlighted ? 'highlighted' : null,
200 | option: option,
201 | onHover: this._handleOptionHover,
202 | onClick: this._handleOptionClick },
203 | child
204 | );
205 | }, this);
206 |
207 | var value = this.state.selected ? this.state.selected[this.props.valueField] : null;
208 | var label = this.state.selected ? this.state.selected[this.props.labelField] : this.state.value;
209 |
210 | var wrapperClasses = cx({
211 | 'react-choice-wrapper': true,
212 | 'react-choice-single': true,
213 | 'react-choice-single--in-focus': this.state.focus,
214 | 'react-choice-single--not-in-focus': !this.state.focus
215 | });
216 |
217 | var IconRenderer = this.props.icon || Icon;
218 |
219 | return React.createElement(
220 | 'div',
221 | { className: 'react-choice' },
222 | React.createElement('input', { type: 'hidden', name: this.props.name, value: value }),
223 | React.createElement(
224 | 'div',
225 | { className: wrapperClasses, onClick: this._handleClick },
226 | React.createElement('input', { type: 'text',
227 | className: 'react-choice-input react-choice-single__input',
228 | placeholder: this.props.placeholder,
229 | value: label,
230 |
231 | onKeyDown: this._handleInput,
232 | onChange: this._handleChange,
233 | onFocus: this._handleFocus,
234 | onBlur: this._handleBlur,
235 |
236 | autoComplete: 'off',
237 | role: 'combobox',
238 | 'aria-autocomplete': 'list',
239 | 'aria-expanded': this.state.focus,
240 | ref: 'input' })
241 | ),
242 | React.createElement(
243 | 'div',
244 | { className: 'react-choice-icon', onMouseDown: this._handleArrowClick },
245 | React.createElement(IconRenderer, { focused: this.state.focus })
246 | ),
247 | this.state.focus ? React.createElement(
248 | Options,
249 | { onMouseDown: this._handleOptionsMouseDown, ref: 'options' },
250 | options
251 | ) : null
252 | );
253 | }
254 | });
255 |
256 | module.exports = SingleChoice;
--------------------------------------------------------------------------------
/dist/text-highlight.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 | var _ = require('lodash');
5 |
6 | var humanizeString = require('./humanize-string');
7 |
8 | var TextHighlight = React.createClass({
9 | displayName: 'TextHighlight',
10 |
11 | propTypes: {
12 | tokens: React.PropTypes.array.isRequired, // array of search tokens
13 | children: React.PropTypes.string.isRequired // text to highlight
14 | },
15 |
16 | shouldComponentUpdate: function shouldComponentUpdate(nextProps) {
17 | if (!_.isEqual(nextProps, this.props)) {
18 | return true;
19 | }
20 | return false;
21 | },
22 |
23 | splitText: function splitText(splits, regex) {
24 | var _splits = [];
25 | _.each(splits, function (split) {
26 | if (split.match === false) {
27 | var match = split.text.match(regex);
28 | if (match) {
29 | var s = split.text.split(regex);
30 |
31 | _.each(s, function (_s, index) {
32 | _splits.push({
33 | text: _s,
34 | match: false
35 | });
36 |
37 | if (index !== s.length - 1) {
38 | var matchCharacter = match[0];
39 |
40 | var i = _splits.length - 1;
41 | if (_.isEmpty(_s) && i === 0 || _s.slice(-1) === ' ') {
42 | matchCharacter = humanizeString(matchCharacter);
43 | } else {
44 | matchCharacter = matchCharacter.toLowerCase();
45 | }
46 |
47 | _splits.push({
48 | text: matchCharacter,
49 | match: true
50 | });
51 | }
52 | });
53 | } else {
54 | _splits.push(split);
55 | }
56 | } else {
57 | _splits.push(split);
58 | }
59 | });
60 | return _splits;
61 | },
62 |
63 | render: function render() {
64 | var label = this.props.children;
65 | var tokens = this.props.tokens;
66 |
67 | var splits = [{
68 | text: label,
69 | match: false
70 | }];
71 |
72 | _.each(tokens, function (token) {
73 | splits = this.splitText(splits, token.regex);
74 | }, this);
75 |
76 | var output = _.map(splits, function (split, i) {
77 | var key = [split.text, split.match, i].join('.');
78 | if (split.match) {
79 | return React.createElement(
80 | 'span',
81 | { className: 'text-highlight__match', key: key },
82 | split.text
83 | );
84 | } else {
85 | return React.createElement(
86 | 'span',
87 | { key: key },
88 | split.text
89 | );
90 | }
91 | });
92 |
93 | return React.createElement(
94 | 'span',
95 | { className: 'text-highlight' },
96 | output
97 | );
98 | }
99 | });
100 |
101 | module.exports = TextHighlight;
--------------------------------------------------------------------------------
/example/base.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react/addons';
2 |
3 | export default class Base extends React.Component {
4 | render() {
5 | return (
6 |
7 |
8 | React Choice Demo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/build/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onefinestay/react-choice/5855ae446334d22a92669d1758d7d42a30a445b4/example/build/.gitkeep
--------------------------------------------------------------------------------
/example/code-snippets/custom-render.jsx:
--------------------------------------------------------------------------------
1 | var Choice = require('react-choice');
2 |
3 | var PokemonRenderer = React.createClass({
4 | // The option mixin provides proptypes that the component requires
5 | mixins: [Choice.OptionMixin],
6 |
7 | render: function() {
8 | var pokemon = this.props.pokemon;
9 |
10 | return (
11 |
12 |
13 |

14 |
15 |
16 |
17 | #{pokemon.national_id}
18 |
19 |
20 | {pokemon.name}
21 |
22 |
23 | HP: {pokemon.hp} | Height: {pokemon.height} m | Weight: {pokemon.weight} kg
24 |
25 |
26 |
27 | );
28 | }
29 | });
30 |
31 | var options = POKEMON.map(function(pokemon) {
32 | return (
33 |
34 | {pokemon.name}
35 |
36 | );
37 | });
38 |
39 | // Render component
40 |
41 | {options}
42 |
43 |
--------------------------------------------------------------------------------
/example/code-snippets/multiple.jsx:
--------------------------------------------------------------------------------
1 | var Choice = require('react-choice');
2 |
3 | var countries = [
4 | {"label": "Afghanistan", "value": "AF"},
5 | {"label": "Albania", "value": "AL"},
6 | {"label": "Algeria", "value": "DZ"},
7 | // etc...
8 | ];
9 |
10 | var options = countries.map(function(country) {
11 | return (
12 |
13 | {country.label}
14 |
15 | );
16 | });
17 |
18 | // Render component
19 |
20 | {options}
21 |
22 |
--------------------------------------------------------------------------------
/example/code-snippets/single.jsx:
--------------------------------------------------------------------------------
1 | var Choice = require('react-choice');
2 |
3 | var countries = [
4 | {"label": "Afghanistan", "value": "AF"},
5 | {"label": "Albania", "value": "AL"},
6 | {"label": "Algeria", "value": "DZ"},
7 | // etc...
8 | ];
9 |
10 | var options = countries.map(function(country) {
11 | return (
12 |
13 | {country.label}
14 |
15 | );
16 | });
17 |
18 | // Render component
19 |
20 | {options}
21 |
22 |
--------------------------------------------------------------------------------
/example/components/code-snippet.jsx:
--------------------------------------------------------------------------------
1 | /* global hljs */
2 | "use strict";
3 |
4 | var React = require('react/addons');
5 | var cx = React.addons.classSet;
6 |
7 | var CodeSnippet = React.createClass({
8 | propTypes: {
9 | language: React.PropTypes.string.isRequired,
10 | toggle: React.PropTypes.bool,
11 | visible: React.PropTypes.bool
12 | },
13 |
14 | getDefaultProps: function() {
15 | return {
16 | toggle: true,
17 | visible: false
18 | };
19 | },
20 |
21 | getInitialState: function() {
22 | return {
23 | visible: this.props.visible || !this.props.toggle
24 | };
25 | },
26 |
27 | handleClick: function(event) {
28 | event.preventDefault();
29 | var value = !this.state.visible;
30 |
31 | var self = this;
32 |
33 | this.setState({
34 | visible: value
35 | }, function() {
36 | if (value) {
37 | var el = self.refs.codeBlock.getDOMNode();
38 | hljs.highlightBlock(el);
39 | }
40 | });
41 | },
42 |
43 | componentDidMount: function() {
44 | if (this.state.visible) {
45 | var el = this.refs.codeBlock.getDOMNode();
46 | hljs.highlightBlock(el);
47 | }
48 | },
49 |
50 | render: function() {
51 | var arrowClasses = cx({
52 | 'code-snippet__arrow': true,
53 | 'code-snippet__arrow--right': !this.state.visible,
54 | 'code-snippet__arrow--up': this.state.visible,
55 | });
56 |
57 | return (
58 |
71 | );
72 | }
73 | });
74 |
75 | module.exports = CodeSnippet;
76 |
--------------------------------------------------------------------------------
/example/components/custom-render.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var React = require('react/addons');
5 | var CodeSnippet = require('./code-snippet');
6 | var Choice = require('../../');
7 | var _ = require('lodash');
8 |
9 | var POKEMON = require('../data/pokemon.js');
10 |
11 | var PokemonRenderer = React.createClass({
12 | mixins: [Choice.OptionMixin],
13 |
14 | render: function() {
15 | var pokemon = this.props.pokemon;
16 |
17 | var weight = pokemon.weight / 10;
18 | var height = pokemon.height / 10;
19 |
20 | return (
21 |
22 |
23 |

24 |
25 |
26 |
27 | #{pokemon.national_id}
28 |
29 |
30 | {pokemon.name}
31 |
32 |
33 | HP: {pokemon.hp} | Height: {height} m | Weight: {weight} kg
34 |
35 |
36 |
37 | );
38 | }
39 | });
40 |
41 | var customExample = fs.readFileSync(__dirname + '/../code-snippets/custom-render.jsx', 'utf8');
42 |
43 | var CustomRender = React.createClass({
44 | render: function() {
45 | var options = _.map(POKEMON, function(pokemon) {
46 | var value = pokemon.national_id;
47 | return (
48 |
49 | {pokemon.name}
50 |
51 | );
52 | });
53 |
54 | var sorter = function(list) {
55 | return _.sortBy(list, 'national_id');
56 | };
57 |
58 | return (
59 |
60 | Custom Renderer
61 |
62 |
63 | {options}
64 |
65 |
66 |
67 |
Creating a custom renderer for the options is easy:
68 |
69 | {customExample}
70 |
71 |
TextHighlight is a component that contains the logic
72 | to highlight the text from the search tokens that Sifter returns.
73 |
Pokemon data provided by Pokéapi
74 |
75 |
76 | );
77 | }
78 | });
79 |
80 | module.exports = CustomRender;
81 |
--------------------------------------------------------------------------------
/example/components/features.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 |
5 | var Features = React.createClass({
6 | render: function() {
7 | return (
8 |
9 |
Features
10 |
11 | -
12 | Search text highlighting
13 |
14 | -
15 | Single or multiple selection
16 |
17 | -
18 | Custom results rendering
19 |
20 |
21 |
22 | );
23 | }
24 | });
25 |
26 | module.exports = Features;
27 |
--------------------------------------------------------------------------------
/example/components/footer.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 |
5 | var Footer = React.createClass({
6 | render: function() {
7 | return (
8 |
16 | );
17 | }
18 | });
19 |
20 | var OFSCredit = React.createClass({
21 | render: function() {
22 | return (
23 |
29 | );
30 | }
31 | });
32 |
33 | module.exports = Footer;
34 |
--------------------------------------------------------------------------------
/example/components/github-ribbon.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 |
5 | var GithubRibbon = React.createClass({
6 | render: function() {
7 | var style = {
8 | position: 'absolute',
9 | top: 0,
10 | right: 0,
11 | border: 0
12 | };
13 |
14 | return (
15 |
16 |
17 |
18 | );
19 | }
20 | });
21 |
22 | module.exports = GithubRibbon;
23 |
--------------------------------------------------------------------------------
/example/components/header.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 |
5 | var Header = React.createClass({
6 | render: function() {
7 | return (
8 |
11 | );
12 | }
13 | });
14 |
15 | module.exports = Header;
16 |
--------------------------------------------------------------------------------
/example/components/install.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var CodeSnippet = require('./code-snippet');
5 |
6 | var Install = React.createClass({
7 | render: function() {
8 | return (
9 |
10 |
Install
11 |
12 | npm install react-choice
13 |
14 |
15 | );
16 | }
17 | });
18 |
19 | module.exports = Install;
20 |
--------------------------------------------------------------------------------
/example/css/_variables.scss:
--------------------------------------------------------------------------------
1 | $ofs-pink: #ff2558;
2 | $ofs-red: #d80033;
3 |
--------------------------------------------------------------------------------
/example/css/choice/_icon.scss:
--------------------------------------------------------------------------------
1 | .react-choice-icon {
2 | position: absolute;
3 | top: 0;
4 | bottom: 0;
5 | right: 0;
6 | width: 30px;
7 | cursor: pointer;
8 |
9 | $arrow-size: 6px;
10 | $arrow-colour: #222;
11 |
12 | &__arrow {
13 | display: inline-block;
14 | width: 0;
15 | height: 0;
16 | margin-left: 5px;
17 | margin-top: 50%;
18 |
19 | &--down {
20 | border-top: $arrow-size solid $arrow-colour;
21 | border-bottom: $arrow-size solid transparent;
22 |
23 | border-left: $arrow-size solid transparent;
24 | border-right: $arrow-size solid transparent;
25 | }
26 |
27 | &--up {
28 | margin-top: 30%;
29 |
30 | border-top: $arrow-size solid transparent;
31 | border-bottom: $arrow-size solid $arrow-colour;
32 |
33 | border-left: $arrow-size solid transparent;
34 | border-right: $arrow-size solid transparent;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/example/css/choice/_input.scss:
--------------------------------------------------------------------------------
1 | .react-choice-input {
2 | cursor: inherit;
3 |
4 | -webkit-appearance: none;
5 | -webkit-border-radius: 0;
6 | border-radius: 0;
7 | background-color: transparent;
8 |
9 | background-color: transparent;
10 | font-family: inherit;
11 | border: none;
12 | box-shadow: none;
13 | padding: 0;
14 | margin: 0;
15 |
16 | font-size: .875rem;
17 |
18 | &:focus {
19 | background: transparent;
20 | border: none;
21 | box-shadow: none;
22 | outline: none;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/css/choice/_multiple.scss:
--------------------------------------------------------------------------------
1 | .react-choice-multiple {
2 | cursor: text;
3 | padding-bottom: 3px;
4 |
5 | &:focus {
6 | outline: none;
7 | }
8 |
9 | &__input {
10 | float: left;
11 | margin-top: 5px;
12 | margin-bottom: 10px;
13 | width: 110px;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/css/choice/_option.scss:
--------------------------------------------------------------------------------
1 | .react-choice-option {
2 | cursor: pointer;
3 | padding: 6px 10px;
4 | font-size: 0.875rem;
5 |
6 | &--selected {
7 | background: #eee;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/css/choice/_options.scss:
--------------------------------------------------------------------------------
1 | .react-choice-options {
2 | position: absolute;
3 | z-index: 2;
4 | width: 100%;
5 | box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
6 | max-height: 305px;
7 | overflow: auto;
8 |
9 | &__list {
10 | background: #fff;
11 | border: 1px solid #D0D0D0;
12 | border-top: none;
13 | list-style-type: none;
14 | margin: 0;
15 | padding: 0;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/example/css/choice/_single.scss:
--------------------------------------------------------------------------------
1 | .react-choice-single {
2 | height: 2.3125rem;
3 |
4 | &--not-in-focus {
5 | cursor: pointer;
6 |
7 | background-color: #f9f9f9;
8 | background-image: -moz-linear-gradient(top, #fefefe, #f2f2f2);
9 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#f2f2f2));
10 | background-image: -webkit-linear-gradient(top, #fefefe, #f2f2f2);
11 | background-image: -o-linear-gradient(top, #fefefe, #f2f2f2);
12 | background-image: linear-gradient(to bottom, #fefefe, #f2f2f2);
13 | background-repeat: repeat-x;
14 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffefefe', endColorstr='#fff2f2f2', GradientType=0);
15 | -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.8);
16 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.8);
17 | }
18 |
19 | &--in-focus {
20 | background: #fafafa;
21 | border-color: #999;
22 | outline: 0;
23 |
24 | -webkit-box-shadow: 0 0 5px #999;
25 | -moz-box-shadow: 0 0 5px #999;
26 | box-shadow: 0 0 5px #999;
27 | border-color: #999;
28 | }
29 |
30 | &__input {
31 | width: 100%;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/example/css/choice/_text-highlight.scss:
--------------------------------------------------------------------------------
1 | .text-highlight {
2 | &__match {
3 | font-weight: bold;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/example/css/choice/_value.scss:
--------------------------------------------------------------------------------
1 | .react-choice-value {
2 | float: left;
3 | border: 1px solid #eee;
4 | margin-right: 5px;
5 | cursor: pointer;
6 | padding: 4px;
7 | margin-bottom: 5px;
8 |
9 | &--is-selected {
10 | background: #eee;
11 | }
12 |
13 | &__delete {
14 | padding-left: 5px;
15 | font-weight: bold;
16 | }
17 |
18 | &__children {
19 | display: inline-block;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/example/css/choice/_wrapper.scss:
--------------------------------------------------------------------------------
1 | .react-choice-wrapper {
2 | overflow: hidden;
3 | font-family: inherit;
4 | border: 1px solid #ccc;
5 |
6 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
7 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
8 |
9 | color: rgba(0, 0, 0, 0.75);
10 | display: block;
11 | font-size: .875rem;
12 | margin: 0;
13 | padding: .5rem;
14 | width: 100%;
15 | -moz-box-sizing: border-box;
16 | -webkit-box-sizing: border-box;
17 | box-sizing: border-box;
18 | -webkit-transition: -webkit-box-shadow .45s, border-color .45s ease-in-out;
19 | -moz-transition: -moz-box-shadow .45s, border-color .45s ease-in-out;
20 | transition: box-shadow .45s, border-color .45s ease-in-out;
21 | }
22 |
--------------------------------------------------------------------------------
/example/css/choice/index.css:
--------------------------------------------------------------------------------
1 | .react-choice-wrapper {
2 | overflow: hidden;
3 | font-family: inherit;
4 | border: 1px solid #ccc;
5 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
6 | color: rgba(0, 0, 0, 0.75);
7 | display: block;
8 | font-size: .875rem;
9 | margin: 0;
10 | padding: .5rem;
11 | width: 100%;
12 | box-sizing: border-box;
13 | -webkit-transition: box-shadow .45s, border-color .45s ease-in-out;
14 | transition: box-shadow .45s, border-color .45s ease-in-out; }
15 |
16 | .react-choice-input {
17 | cursor: inherit;
18 | -webkit-appearance: none;
19 | border-radius: 0;
20 | background-color: transparent;
21 | background-color: transparent;
22 | font-family: inherit;
23 | border: none;
24 | box-shadow: none;
25 | padding: 0;
26 | margin: 0;
27 | font-size: .875rem; }
28 | .react-choice-input:focus {
29 | background: transparent;
30 | border: none;
31 | box-shadow: none;
32 | outline: none; }
33 |
34 | .react-choice-single {
35 | height: 2.3125rem; }
36 | .react-choice-single--not-in-focus {
37 | cursor: pointer;
38 | background-color: #f9f9f9;
39 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#f2f2f2));
40 | background-image: -webkit-linear-gradient(top, #fefefe, #f2f2f2);
41 | background-image: linear-gradient(to bottom, #fefefe, #f2f2f2);
42 | background-repeat: repeat-x;
43 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffefefe', endColorstr='#fff2f2f2', GradientType=0);
44 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.8); }
45 | .react-choice-single--in-focus {
46 | background: #fafafa;
47 | border-color: #999;
48 | outline: 0;
49 | box-shadow: 0 0 5px #999;
50 | border-color: #999; }
51 | .react-choice-single__input {
52 | width: 100%; }
53 |
54 | .react-choice-multiple {
55 | cursor: text;
56 | padding-bottom: 3px; }
57 | .react-choice-multiple:focus {
58 | outline: none; }
59 | .react-choice-multiple__input {
60 | float: left;
61 | margin-top: 5px;
62 | margin-bottom: 10px;
63 | width: 110px; }
64 |
65 | .react-choice-icon {
66 | position: absolute;
67 | top: 0;
68 | bottom: 0;
69 | right: 0;
70 | width: 30px;
71 | cursor: pointer; }
72 | .react-choice-icon__arrow {
73 | display: inline-block;
74 | width: 0;
75 | height: 0;
76 | margin-left: 5px;
77 | margin-top: 50%; }
78 | .react-choice-icon__arrow--down {
79 | border-top: 6px solid #222;
80 | border-bottom: 6px solid transparent;
81 | border-left: 6px solid transparent;
82 | border-right: 6px solid transparent; }
83 | .react-choice-icon__arrow--up {
84 | margin-top: 30%;
85 | border-top: 6px solid transparent;
86 | border-bottom: 6px solid #222;
87 | border-left: 6px solid transparent;
88 | border-right: 6px solid transparent; }
89 |
90 | .react-choice-options {
91 | position: absolute;
92 | z-index: 2;
93 | width: 100%;
94 | box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
95 | max-height: 305px;
96 | overflow: auto; }
97 | .react-choice-options__list {
98 | background: #fff;
99 | border: 1px solid #D0D0D0;
100 | border-top: none;
101 | list-style-type: none;
102 | margin: 0;
103 | padding: 0; }
104 |
105 | .react-choice-option {
106 | cursor: pointer;
107 | padding: 6px 10px;
108 | font-size: 0.875rem; }
109 | .react-choice-option--selected {
110 | background: #eee; }
111 |
112 | .text-highlight__match {
113 | font-weight: bold; }
114 |
115 | .react-choice-value {
116 | float: left;
117 | border: 1px solid #eee;
118 | margin-right: 5px;
119 | cursor: pointer;
120 | padding: 4px;
121 | margin-bottom: 5px; }
122 | .react-choice-value--is-selected {
123 | background: #eee; }
124 | .react-choice-value__delete {
125 | padding-left: 5px;
126 | font-weight: bold; }
127 | .react-choice-value__children {
128 | display: inline-block; }
129 |
130 | .react-choice {
131 | position: relative; }
132 |
--------------------------------------------------------------------------------
/example/css/choice/index.scss:
--------------------------------------------------------------------------------
1 | @import "wrapper";
2 | @import "input";
3 | @import "single";
4 | @import "multiple";
5 | @import "icon";
6 | @import "options";
7 | @import "option";
8 | @import "text-highlight";
9 | @import "value";
10 |
11 | .react-choice {
12 | position: relative;
13 | }
14 |
--------------------------------------------------------------------------------
/example/css/components/_code-snippet.scss:
--------------------------------------------------------------------------------
1 | .code-snippet {
2 | margin-top: 1rem;
3 | font-size: 0.9rem;
4 |
5 | $arrow-size: 6px;
6 |
7 | &__arrow {
8 | display: inline-block;
9 | width: 0;
10 | height: 0;
11 |
12 | &--right {
13 | border-top: $arrow-size solid transparent;
14 | border-bottom: $arrow-size solid transparent;
15 |
16 | border-left: $arrow-size solid #AAAAAA;
17 | border-right: $arrow-size solid transparent;
18 | }
19 |
20 | &--up {
21 | margin-right: 6px;
22 | margin-bottom: 3px;
23 |
24 | border-top: $arrow-size solid transparent;
25 | border-bottom: $arrow-size solid #AAAAAA;
26 |
27 | border-left: $arrow-size solid transparent;
28 | border-right: $arrow-size solid transparent;
29 | }
30 | }
31 |
32 | &__toggle-button {
33 | color: $ofs-pink;
34 | text-decoration: none;
35 |
36 | &:hover {
37 | color: $ofs-red;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/example/css/components/_features.scss:
--------------------------------------------------------------------------------
1 | .features {
2 | overflow: hidden;
3 |
4 | &__point {
5 | margin-bottom: 0.5rem;
6 | font-weight: 200;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/example/css/components/_footer.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | margin: 20px 0;
3 | padding: 20px 0;
4 | border-top: 1px solid #ccc;
5 |
6 | &__links {
7 | text-align: center;
8 | }
9 |
10 | &__link {
11 | text-decoration: none;
12 | display: inline-block;
13 | margin-right: 10px;
14 | color: $ofs-pink;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/example/css/components/_header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | margin-top: 50px;
3 |
4 | &__logo {
5 | width: 200px;
6 | margin: 0 auto;
7 | display: block;
8 | }
9 |
10 | &__title {
11 | text-align: center;
12 | margin-bottom: 3rem;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/example/css/components/_ofs-credit.scss:
--------------------------------------------------------------------------------
1 | .ofs-credit {
2 | margin-bottom: 1rem;
3 |
4 | &__text {
5 | text-align: center;
6 | margin: 0;
7 | color: #999;
8 | margin-bottom: 0.5rem;
9 | }
10 |
11 | &__logo {
12 | display: block;
13 | width: 200px;
14 | margin: 0 auto;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/example/css/components/_pokemon.scss:
--------------------------------------------------------------------------------
1 | .pokemon {
2 | &__image {
3 | width: 40px;
4 | height: 40px;
5 | display: inline-block;
6 |
7 | img {
8 | width: 100%;
9 | }
10 | }
11 |
12 | &__number {
13 | color: #777;
14 | margin-right: 5px;
15 | }
16 |
17 | &__name {
18 | display: inline-block;
19 | padding-left: 20px;
20 | margin-top: 5px;
21 | vertical-align: top;
22 | }
23 |
24 | &__attributes {
25 | color: #777;
26 | font-size: 0.8rem;
27 | margin-top: 5px;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example/css/components/_tutorial.scss:
--------------------------------------------------------------------------------
1 | .tutorial {
2 | &__example {
3 | margin-bottom: 20px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/example/css/index.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | // Components
4 | @import "components/header";
5 | @import "components/footer";
6 | @import "components/code-snippet";
7 | @import "components/features";
8 | @import "components/ofs-credit";
9 | @import "components/tutorial";
10 | @import "components/pokemon";
11 |
12 | // Base
13 | body {
14 | font-family: "Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;
15 | font-size: 16px;
16 | width: 940px;
17 | margin: 0 auto;
18 | }
19 |
20 | h1,
21 | h2,
22 | h3,
23 | h4 {
24 | font-weight: lighter;
25 | letter-spacing: 1.2px;
26 | text-rendering: optimizeLegibility;
27 | color: $ofs-pink;
28 | }
29 |
30 | .example-single,
31 | .example-multiple {
32 | width: 450px;
33 | float: left;
34 |
35 | h2 {
36 | text-align: center;
37 | }
38 | }
39 |
40 | .example-single {
41 | padding-right: 9px;
42 | border-right: 1px solid #ccc;
43 | margin-right: 10px;
44 | padding-bottom: 20px;
45 |
46 | .code-snippet {
47 | margin-top: 23px;
48 | }
49 | }
50 |
51 | p.info {
52 | font-weight: 0.8rem;
53 | color: #777;
54 | }
55 |
--------------------------------------------------------------------------------
/example/data/countries.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {"label": "Afghanistan", "value": "AF"},
3 | {"label": "Albania", "value": "AL"},
4 | {"label": "Algeria", "value": "DZ"},
5 | {"label": "American Samoa", "value": "AS"},
6 | {"label": "Andorra", "value": "AD"},
7 | {"label": "Angola", "value": "AO"},
8 | {"label": "Anguilla", "value": "AI"},
9 | {"label": "Antarctica", "value": "AQ"},
10 | {"label": "Antigua and Barbuda", "value": "AG"},
11 | {"label": "Argentina", "value": "AR"},
12 | {"label": "Armenia", "value": "AM"},
13 | {"label": "Aruba", "value": "AW"},
14 | {"label": "Australia", "value": "AU"},
15 | {"label": "Austria", "value": "AT"},
16 | {"label": "Azerbaijan", "value": "AZ"},
17 | {"label": "Bahamas", "value": "BS"},
18 | {"label": "Bahrain", "value": "BH"},
19 | {"label": "Bangladesh", "value": "BD"},
20 | {"label": "Barbados", "value": "BB"},
21 | {"label": "Belarus", "value": "BY"},
22 | {"label": "Belgium", "value": "BE"},
23 | {"label": "Belize", "value": "BZ"},
24 | {"label": "Benin", "value": "BJ"},
25 | {"label": "Bermuda", "value": "BM"},
26 | {"label": "Bhutan", "value": "BT"},
27 | {"label": "Bolivia", "value": "BO"},
28 | {"label": "Bosnia and Herzegovina", "value": "BA"},
29 | {"label": "Botswana", "value": "BW"},
30 | {"label": "Bouvet Island", "value": "BV"},
31 | {"label": "Brazil", "value": "BR"},
32 | {"label": "British Indian Ocean Territory", "value": "IO"},
33 | {"label": "Brunei Darussalam", "value": "BN"},
34 | {"label": "Bulgaria", "value": "BG"},
35 | {"label": "Burkina Faso", "value": "BF"},
36 | {"label": "Burundi", "value": "BI"},
37 | {"label": "Cambodia", "value": "KH"},
38 | {"label": "Cameroon", "value": "CM"},
39 | {"label": "Canada", "value": "CA"},
40 | {"label": "Cape Verde", "value": "CV"},
41 | {"label": "Cayman Islands", "value": "KY"},
42 | {"label": "Central African Republic", "value": "CF"},
43 | {"label": "Chad", "value": "TD"},
44 | {"label": "Chile", "value": "CL"},
45 | {"label": "China", "value": "CN"},
46 | {"label": "Christmas Island", "value": "CX"},
47 | {"label": "Cocos (Keeling) Islands", "value": "CC"},
48 | {"label": "Colombia", "value": "CO"},
49 | {"label": "Comoros", "value": "KM"},
50 | {"label": "Congo", "value": "CG"},
51 | {"label": "Congo, The Democratic Republic of the", "value": "CD"},
52 | {"label": "Cook Islands", "value": "CK"},
53 | {"label": "Costa Rica", "value": "CR"},
54 | {"label": "Cote D\"Ivoire", "value": "CI"},
55 | {"label": "Croatia", "value": "HR"},
56 | {"label": "Cuba", "value": "CU"},
57 | {"label": "Cyprus", "value": "CY"},
58 | {"label": "Czech Republic", "value": "CZ"},
59 | {"label": "Denmark", "value": "DK"},
60 | {"label": "Djibouti", "value": "DJ"},
61 | {"label": "Dominica", "value": "DM"},
62 | {"label": "Dominican Republic", "value": "DO"},
63 | {"label": "Ecuador", "value": "EC"},
64 | {"label": "Egypt", "value": "EG"},
65 | {"label": "El Salvador", "value": "SV"},
66 | {"label": "Equatorial Guinea", "value": "GQ"},
67 | {"label": "Eritrea", "value": "ER"},
68 | {"label": "Estonia", "value": "EE"},
69 | {"label": "Ethiopia", "value": "ET"},
70 | {"label": "Falkland Islands (Malvinas)", "value": "FK"},
71 | {"label": "Faroe Islands", "value": "FO"},
72 | {"label": "Fiji", "value": "FJ"},
73 | {"label": "Finland", "value": "FI"},
74 | {"label": "France", "value": "FR"},
75 | {"label": "French Guiana", "value": "GF"},
76 | {"label": "French Polynesia", "value": "PF"},
77 | {"label": "French Southern Territories", "value": "TF"},
78 | {"label": "Gabon", "value": "GA"},
79 | {"label": "Gambia", "value": "GM"},
80 | {"label": "Georgia", "value": "GE"},
81 | {"label": "Germany", "value": "DE"},
82 | {"label": "Ghana", "value": "GH"},
83 | {"label": "Gibraltar", "value": "GI"},
84 | {"label": "Greece", "value": "GR"},
85 | {"label": "Greenland", "value": "GL"},
86 | {"label": "Grenada", "value": "GD"},
87 | {"label": "Guadeloupe", "value": "GP"},
88 | {"label": "Guam", "value": "GU"},
89 | {"label": "Guatemala", "value": "GT"},
90 | {"label": "Guernsey", "value": "GG"},
91 | {"label": "Guinea", "value": "GN"},
92 | {"label": "Guinea-Bissau", "value": "GW"},
93 | {"label": "Guyana", "value": "GY"},
94 | {"label": "Haiti", "value": "HT"},
95 | {"label": "Heard Island and Mcdonald Islands", "value": "HM"},
96 | {"label": "Holy See (Vatican City State)", "value": "VA"},
97 | {"label": "Honduras", "value": "HN"},
98 | {"label": "Hong Kong", "value": "HK"},
99 | {"label": "Hungary", "value": "HU"},
100 | {"label": "Iceland", "value": "IS"},
101 | {"label": "India", "value": "IN"},
102 | {"label": "Indonesia", "value": "ID"},
103 | {"label": "Iran, Islamic Republic Of", "value": "IR"},
104 | {"label": "Iraq", "value": "IQ"},
105 | {"label": "Ireland", "value": "IE"},
106 | {"label": "Isle of Man", "value": "IM"},
107 | {"label": "Israel", "value": "IL"},
108 | {"label": "Italy", "value": "IT"},
109 | {"label": "Jamaica", "value": "JM"},
110 | {"label": "Japan", "value": "JP"},
111 | {"label": "Jersey", "value": "JE"},
112 | {"label": "Jordan", "value": "JO"},
113 | {"label": "Kazakhstan", "value": "KZ"},
114 | {"label": "Kenya", "value": "KE"},
115 | {"label": "Kiribati", "value": "KI"},
116 | {"label": "Korea, Democratic People\"S Republic of", "value": "KP"},
117 | {"label": "Korea, Republic of", "value": "KR"},
118 | {"label": "Kuwait", "value": "KW"},
119 | {"label": "Kyrgyzstan", "value": "KG"},
120 | {"label": "Lao People\"S Democratic Republic", "value": "LA"},
121 | {"label": "Latvia", "value": "LV"},
122 | {"label": "Lebanon", "value": "LB"},
123 | {"label": "Lesotho", "value": "LS"},
124 | {"label": "Liberia", "value": "LR"},
125 | {"label": "Libyan Arab Jamahiriya", "value": "LY"},
126 | {"label": "Liechtenstein", "value": "LI"},
127 | {"label": "Lithuania", "value": "LT"},
128 | {"label": "Luxembourg", "value": "LU"},
129 | {"label": "Macao", "value": "MO"},
130 | {"label": "Macedonia, The Former Yugoslav Republic of", "value": "MK"},
131 | {"label": "Madagascar", "value": "MG"},
132 | {"label": "Malawi", "value": "MW"},
133 | {"label": "Malaysia", "value": "MY"},
134 | {"label": "Maldives", "value": "MV"},
135 | {"label": "Mali", "value": "ML"},
136 | {"label": "Malta", "value": "MT"},
137 | {"label": "Marshall Islands", "value": "MH"},
138 | {"label": "Martinique", "value": "MQ"},
139 | {"label": "Mauritania", "value": "MR"},
140 | {"label": "Mauritius", "value": "MU"},
141 | {"label": "Mayotte", "value": "YT"},
142 | {"label": "Mexico", "value": "MX"},
143 | {"label": "Micronesia, Federated States of", "value": "FM"},
144 | {"label": "Moldova, Republic of", "value": "MD"},
145 | {"label": "Monaco", "value": "MC"},
146 | {"label": "Mongolia", "value": "MN"},
147 | {"label": "Montserrat", "value": "MS"},
148 | {"label": "Morocco", "value": "MA"},
149 | {"label": "Mozambique", "value": "MZ"},
150 | {"label": "Myanmar", "value": "MM"},
151 | {"label": "Namibia", "value": "NA"},
152 | {"label": "Nauru", "value": "NR"},
153 | {"label": "Nepal", "value": "NP"},
154 | {"label": "Netherlands", "value": "NL"},
155 | {"label": "Netherlands Antilles", "value": "AN"},
156 | {"label": "New Caledonia", "value": "NC"},
157 | {"label": "New Zealand", "value": "NZ"},
158 | {"label": "Nicaragua", "value": "NI"},
159 | {"label": "Niger", "value": "NE"},
160 | {"label": "Nigeria", "value": "NG"},
161 | {"label": "Niue", "value": "NU"},
162 | {"label": "Norfolk Island", "value": "NF"},
163 | {"label": "Northern Mariana Islands", "value": "MP"},
164 | {"label": "Norway", "value": "NO"},
165 | {"label": "Oman", "value": "OM"},
166 | {"label": "Pakistan", "value": "PK"},
167 | {"label": "Palau", "value": "PW"},
168 | {"label": "Palestinian Territory, Occupied", "value": "PS"},
169 | {"label": "Panama", "value": "PA"},
170 | {"label": "Papua New Guinea", "value": "PG"},
171 | {"label": "Paraguay", "value": "PY"},
172 | {"label": "Peru", "value": "PE"},
173 | {"label": "Philippines", "value": "PH"},
174 | {"label": "Pitcairn", "value": "PN"},
175 | {"label": "Poland", "value": "PL"},
176 | {"label": "Portugal", "value": "PT"},
177 | {"label": "Puerto Rico", "value": "PR"},
178 | {"label": "Qatar", "value": "QA"},
179 | {"label": "Reunion", "value": "RE"},
180 | {"label": "Romania", "value": "RO"},
181 | {"label": "Russian Federation", "value": "RU"},
182 | {"label": "RWANDA", "value": "RW"},
183 | {"label": "Saint Helena", "value": "SH"},
184 | {"label": "Saint Kitts and Nevis", "value": "KN"},
185 | {"label": "Saint Lucia", "value": "LC"},
186 | {"label": "Saint Pierre and Miquelon", "value": "PM"},
187 | {"label": "Saint Vincent and the Grenadines", "value": "VC"},
188 | {"label": "Samoa", "value": "WS"},
189 | {"label": "San Marino", "value": "SM"},
190 | {"label": "Sao Tome and Principe", "value": "ST"},
191 | {"label": "Saudi Arabia", "value": "SA"},
192 | {"label": "Senegal", "value": "SN"},
193 | {"label": "Serbia and Montenegro", "value": "CS"},
194 | {"label": "Seychelles", "value": "SC"},
195 | {"label": "Sierra Leone", "value": "SL"},
196 | {"label": "Singapore", "value": "SG"},
197 | {"label": "Slovakia", "value": "SK"},
198 | {"label": "Slovenia", "value": "SI"},
199 | {"label": "Solomon Islands", "value": "SB"},
200 | {"label": "Somalia", "value": "SO"},
201 | {"label": "South Africa", "value": "ZA"},
202 | {"label": "South Georgia and the South Sandwich Islands", "value": "GS"},
203 | {"label": "Spain", "value": "ES"},
204 | {"label": "Sri Lanka", "value": "LK"},
205 | {"label": "Sudan", "value": "SD"},
206 | {"label": "Suriname", "value": "SR"},
207 | {"label": "Svalbard and Jan Mayen", "value": "SJ"},
208 | {"label": "Swaziland", "value": "SZ"},
209 | {"label": "Sweden", "value": "SE"},
210 | {"label": "Switzerland", "value": "CH"},
211 | {"label": "Syrian Arab Republic", "value": "SY"},
212 | {"label": "Taiwan, Province of China", "value": "TW"},
213 | {"label": "Tajikistan", "value": "TJ"},
214 | {"label": "Tanzania, United Republic of", "value": "TZ"},
215 | {"label": "Thailand", "value": "TH"},
216 | {"label": "Timor-Leste", "value": "TL"},
217 | {"label": "Togo", "value": "TG"},
218 | {"label": "Tokelau", "value": "TK"},
219 | {"label": "Tonga", "value": "TO"},
220 | {"label": "Trinidad and Tobago", "value": "TT"},
221 | {"label": "Tunisia", "value": "TN"},
222 | {"label": "Turkey", "value": "TR"},
223 | {"label": "Turkmenistan", "value": "TM"},
224 | {"label": "Turks and Caicos Islands", "value": "TC"},
225 | {"label": "Tuvalu", "value": "TV"},
226 | {"label": "Uganda", "value": "UG"},
227 | {"label": "Ukraine", "value": "UA"},
228 | {"label": "United Arab Emirates", "value": "AE"},
229 | {"label": "United Kingdom", "value": "GB"},
230 | {"label": "United States", "value": "US"},
231 | {"label": "United States Minor Outlying Islands", "value": "UM"},
232 | {"label": "Uruguay", "value": "UY"},
233 | {"label": "Uzbekistan", "value": "UZ"},
234 | {"label": "Vanuatu", "value": "VU"},
235 | {"label": "Venezuela", "value": "VE"},
236 | {"label": "Viet Nam", "value": "VN"},
237 | {"label": "Virgin Islands, British", "value": "VG"},
238 | {"label": "Virgin Islands, U.S.", "value": "VI"},
239 | {"label": "Wallis and Futuna", "value": "WF"},
240 | {"label": "Western Sahara", "value": "EH"},
241 | {"label": "Yemen", "value": "YE"},
242 | {"label": "Zambia", "value": "ZM"},
243 | {"label": "Zimbabwe", "value": "ZW"}
244 | ];
245 |
--------------------------------------------------------------------------------
/example/data/pokemon.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {"name":"Bulbasaur","national_id":1,"hp":45,"attack":49,"sp_atk":65,"sp_def":65,"speed":45,"height":"7","weight":"69","image":"http://pokeapi.co/media/img/1.png"},
3 | {"name":"Ivysaur","national_id":2,"hp":60,"attack":62,"sp_atk":80,"sp_def":80,"speed":60,"height":"10","weight":"130","image":"http://pokeapi.co/media/img/2.png"},
4 | {"name":"Venusaur","national_id":3,"hp":80,"attack":82,"sp_atk":100,"sp_def":100,"speed":80,"height":"20","weight":"1000","image":"http://pokeapi.co/media/img/3.png"},
5 | {"name":"Charmander","national_id":4,"hp":39,"attack":52,"sp_atk":60,"sp_def":50,"speed":65,"height":"6","weight":"85","image":"http://pokeapi.co/media/img/4.png"},
6 | {"name":"Charmeleon","national_id":5,"hp":58,"attack":64,"sp_atk":80,"sp_def":65,"speed":80,"height":"11","weight":"190","image":"http://pokeapi.co/media/img/5.png"},
7 | {"name":"Charizard","national_id":6,"hp":78,"attack":84,"sp_atk":109,"sp_def":85,"speed":100,"height":"17","weight":"905","image":"http://pokeapi.co/media/img/6.png"},
8 | {"name":"Squirtle","national_id":7,"hp":44,"attack":48,"sp_atk":50,"sp_def":64,"speed":43,"height":"5","weight":"90","image":"http://pokeapi.co/media/img/7.png"},
9 | {"name":"Wartortle","national_id":8,"hp":59,"attack":63,"sp_atk":65,"sp_def":80,"speed":58,"height":"10","weight":"225","image":"http://pokeapi.co/media/img/8.png"},
10 | {"name":"Blastoise","national_id":9,"hp":79,"attack":83,"sp_atk":85,"sp_def":105,"speed":78,"height":"16","weight":"855","image":"http://pokeapi.co/media/img/9.png"},
11 | {"name":"Caterpie","national_id":10,"hp":45,"attack":30,"sp_atk":20,"sp_def":20,"speed":45,"height":"3","weight":"29","image":"http://pokeapi.co/media/img/10.png"},
12 | {"name":"Metapod","national_id":11,"hp":50,"attack":20,"sp_atk":25,"sp_def":25,"speed":30,"height":"7","weight":"99","image":"http://pokeapi.co/media/img/11.png"},
13 | {"name":"Butterfree","national_id":12,"hp":60,"attack":45,"sp_atk":90,"sp_def":80,"speed":70,"height":"11","weight":"320","image":"http://pokeapi.co/media/img/12.png"},
14 | {"name":"Weedle","national_id":13,"hp":40,"attack":35,"sp_atk":20,"sp_def":20,"speed":50,"height":"3","weight":"32","image":"http://pokeapi.co/media/img/13.png"},
15 | {"name":"Kakuna","national_id":14,"hp":45,"attack":25,"sp_atk":25,"sp_def":25,"speed":35,"height":"6","weight":"100","image":"http://pokeapi.co/media/img/14.png"},
16 | {"name":"Beedrill","national_id":15,"hp":65,"attack":90,"sp_atk":45,"sp_def":80,"speed":75,"height":"10","weight":"295","image":"http://pokeapi.co/media/img/15.png"},
17 | {"name":"Pidgey","national_id":16,"hp":40,"attack":45,"sp_atk":35,"sp_def":35,"speed":56,"height":"3","weight":"18","image":"http://pokeapi.co/media/img/16.png"},
18 | {"name":"Pidgeotto","national_id":17,"hp":63,"attack":60,"sp_atk":50,"sp_def":50,"speed":71,"height":"11","weight":"300","image":"http://pokeapi.co/media/img/17.png"},
19 | {"name":"Pidgeot","national_id":18,"hp":83,"attack":80,"sp_atk":70,"sp_def":70,"speed":101,"height":"15","weight":"395","image":"http://pokeapi.co/media/img/18.png"},
20 | {"name":"Rattata","national_id":19,"hp":30,"attack":56,"sp_atk":25,"sp_def":35,"speed":72,"height":"3","weight":"35","image":"http://pokeapi.co/media/img/19.png"},
21 | {"name":"Raticate","national_id":20,"hp":55,"attack":81,"sp_atk":50,"sp_def":70,"speed":97,"height":"7","weight":"185","image":"http://pokeapi.co/media/img/20.png"},
22 | {"name":"Spearow","national_id":21,"hp":40,"attack":60,"sp_atk":31,"sp_def":31,"speed":70,"height":"3","weight":"20","image":"http://pokeapi.co/media/img/21.png"},
23 | {"name":"Fearow","national_id":22,"hp":65,"attack":90,"sp_atk":61,"sp_def":61,"speed":100,"height":"12","weight":"380","image":"http://pokeapi.co/media/img/22.png"},
24 | {"name":"Ekans","national_id":23,"hp":35,"attack":60,"sp_atk":40,"sp_def":54,"speed":55,"height":"20","weight":"69","image":"http://pokeapi.co/media/img/23.png"},
25 | {"name":"Arbok","national_id":24,"hp":60,"attack":85,"sp_atk":65,"sp_def":79,"speed":80,"height":"35","weight":"650","image":"http://pokeapi.co/media/img/24.png"},
26 | {"name":"Pikachu","national_id":25,"hp":35,"attack":55,"sp_atk":50,"sp_def":50,"speed":90,"height":"4","weight":"60","image":"http://pokeapi.co/media/img/25.png"},
27 | {"name":"Raichu","national_id":26,"hp":60,"attack":90,"sp_atk":90,"sp_def":80,"speed":110,"height":"8","weight":"300","image":"http://pokeapi.co/media/img/26.png"},
28 | {"name":"Sandshrew","national_id":27,"hp":50,"attack":75,"sp_atk":20,"sp_def":30,"speed":40,"height":"6","weight":"120","image":"http://pokeapi.co/media/img/27.png"},
29 | {"name":"Sandslash","national_id":28,"hp":75,"attack":100,"sp_atk":45,"sp_def":55,"speed":65,"height":"10","weight":"295","image":"http://pokeapi.co/media/img/28.png"},
30 | {"name":"Nidoran-f","national_id":29,"hp":55,"attack":47,"sp_atk":40,"sp_def":40,"speed":41,"height":"4","weight":"70","image":"http://pokeapi.co/media/img/29.png"},
31 | {"name":"Nidorina","national_id":30,"hp":70,"attack":62,"sp_atk":55,"sp_def":55,"speed":56,"height":"8","weight":"200","image":"http://pokeapi.co/media/img/30.png"},
32 | {"name":"Nidoqueen","national_id":31,"hp":90,"attack":92,"sp_atk":75,"sp_def":85,"speed":76,"height":"13","weight":"600","image":"http://pokeapi.co/media/img/31.png"},
33 | {"name":"Nidoran-m","national_id":32,"hp":46,"attack":57,"sp_atk":40,"sp_def":40,"speed":50,"height":"5","weight":"90","image":"http://pokeapi.co/media/img/32.png"},
34 | {"name":"Nidorino","national_id":33,"hp":61,"attack":72,"sp_atk":55,"sp_def":55,"speed":65,"height":"9","weight":"195","image":"http://pokeapi.co/media/img/33.png"},
35 | {"name":"Nidoking","national_id":34,"hp":81,"attack":102,"sp_atk":85,"sp_def":75,"speed":85,"height":"14","weight":"620","image":"http://pokeapi.co/media/img/34.png"},
36 | {"name":"Clefairy","national_id":35,"hp":70,"attack":45,"sp_atk":60,"sp_def":65,"speed":35,"height":"6","weight":"75","image":"http://pokeapi.co/media/img/35.png"},
37 | {"name":"Clefable","national_id":36,"hp":95,"attack":70,"sp_atk":95,"sp_def":90,"speed":60,"height":"13","weight":"400","image":"http://pokeapi.co/media/img/36.png"},
38 | {"name":"Vulpix","national_id":37,"hp":38,"attack":41,"sp_atk":50,"sp_def":65,"speed":65,"height":"6","weight":"99","image":"http://pokeapi.co/media/img/37.png"},
39 | {"name":"Ninetales","national_id":38,"hp":73,"attack":76,"sp_atk":81,"sp_def":100,"speed":100,"height":"11","weight":"199","image":"http://pokeapi.co/media/img/38.png"},
40 | {"name":"Jigglypuff","national_id":39,"hp":115,"attack":45,"sp_atk":45,"sp_def":25,"speed":20,"height":"5","weight":"55","image":"http://pokeapi.co/media/img/39.png"},
41 | {"name":"Wigglytuff","national_id":40,"hp":140,"attack":70,"sp_atk":85,"sp_def":50,"speed":45,"height":"10","weight":"120","image":"http://pokeapi.co/media/img/40.png"},
42 | {"name":"Zubat","national_id":41,"hp":40,"attack":45,"sp_atk":30,"sp_def":40,"speed":55,"height":"8","weight":"75","image":"http://pokeapi.co/media/img/41.png"},
43 | {"name":"Golbat","national_id":42,"hp":75,"attack":80,"sp_atk":65,"sp_def":75,"speed":90,"height":"16","weight":"550","image":"http://pokeapi.co/media/img/42.png"},
44 | {"name":"Oddish","national_id":43,"hp":45,"attack":50,"sp_atk":75,"sp_def":65,"speed":30,"height":"5","weight":"54","image":"http://pokeapi.co/media/img/43.png"},
45 | {"name":"Gloom","national_id":44,"hp":60,"attack":65,"sp_atk":85,"sp_def":75,"speed":40,"height":"8","weight":"86","image":"http://pokeapi.co/media/img/44.png"},
46 | {"name":"Vileplume","national_id":45,"hp":75,"attack":80,"sp_atk":110,"sp_def":90,"speed":50,"height":"12","weight":"186","image":"http://pokeapi.co/media/img/45.png"},
47 | {"name":"Paras","national_id":46,"hp":35,"attack":70,"sp_atk":45,"sp_def":55,"speed":25,"height":"3","weight":"54","image":"http://pokeapi.co/media/img/46.png"},
48 | {"name":"Parasect","national_id":47,"hp":60,"attack":95,"sp_atk":60,"sp_def":80,"speed":30,"height":"10","weight":"295","image":"http://pokeapi.co/media/img/47.png"},
49 | {"name":"Venonat","national_id":48,"hp":60,"attack":55,"sp_atk":40,"sp_def":55,"speed":45,"height":"10","weight":"300","image":"http://pokeapi.co/media/img/48.png"},
50 | {"name":"Venomoth","national_id":49,"hp":70,"attack":65,"sp_atk":90,"sp_def":75,"speed":90,"height":"15","weight":"125","image":"http://pokeapi.co/media/img/49.png"},
51 | {"name":"Diglett","national_id":50,"hp":10,"attack":55,"sp_atk":35,"sp_def":45,"speed":95,"height":"2","weight":"8","image":"http://pokeapi.co/media/img/50.png"},
52 | {"name":"Dugtrio","national_id":51,"hp":35,"attack":80,"sp_atk":50,"sp_def":70,"speed":120,"height":"7","weight":"333","image":"http://pokeapi.co/media/img/51.png"},
53 | {"name":"Meowth","national_id":52,"hp":40,"attack":45,"sp_atk":40,"sp_def":40,"speed":90,"height":"4","weight":"42","image":"http://pokeapi.co/media/img/52.png"},
54 | {"name":"Persian","national_id":53,"hp":65,"attack":70,"sp_atk":65,"sp_def":65,"speed":115,"height":"10","weight":"320","image":"http://pokeapi.co/media/img/53.png"},
55 | {"name":"Psyduck","national_id":54,"hp":50,"attack":52,"sp_atk":65,"sp_def":50,"speed":55,"height":"8","weight":"196","image":"http://pokeapi.co/media/img/54.png"},
56 | {"name":"Golduck","national_id":55,"hp":80,"attack":82,"sp_atk":95,"sp_def":80,"speed":85,"height":"17","weight":"766","image":"http://pokeapi.co/media/img/55.png"},
57 | {"name":"Mankey","national_id":56,"hp":40,"attack":80,"sp_atk":35,"sp_def":45,"speed":70,"height":"5","weight":"280","image":"http://pokeapi.co/media/img/56.png"},
58 | {"name":"Primeape","national_id":57,"hp":65,"attack":105,"sp_atk":60,"sp_def":70,"speed":95,"height":"10","weight":"320","image":"http://pokeapi.co/media/img/57.png"},
59 | {"name":"Growlithe","national_id":58,"hp":55,"attack":70,"sp_atk":70,"sp_def":50,"speed":60,"height":"7","weight":"190","image":"http://pokeapi.co/media/img/58.png"},
60 | {"name":"Arcanine","national_id":59,"hp":90,"attack":110,"sp_atk":100,"sp_def":80,"speed":95,"height":"19","weight":"1550","image":"http://pokeapi.co/media/img/59.png"},
61 | {"name":"Poliwag","national_id":60,"hp":40,"attack":50,"sp_atk":40,"sp_def":40,"speed":90,"height":"6","weight":"124","image":"http://pokeapi.co/media/img/60.png"},
62 | {"name":"Poliwhirl","national_id":61,"hp":65,"attack":65,"sp_atk":50,"sp_def":50,"speed":90,"height":"10","weight":"200","image":"http://pokeapi.co/media/img/61.png"},
63 | {"name":"Poliwrath","national_id":62,"hp":90,"attack":95,"sp_atk":70,"sp_def":90,"speed":70,"height":"13","weight":"540","image":"http://pokeapi.co/media/img/62.png"},
64 | {"name":"Abra","national_id":63,"hp":25,"attack":20,"sp_atk":105,"sp_def":55,"speed":90,"height":"9","weight":"195","image":"http://pokeapi.co/media/img/63.png"},
65 | {"name":"Kadabra","national_id":64,"hp":40,"attack":35,"sp_atk":120,"sp_def":70,"speed":105,"height":"13","weight":"565","image":"http://pokeapi.co/media/img/64.png"},
66 | {"name":"Alakazam","national_id":65,"hp":55,"attack":50,"sp_atk":135,"sp_def":95,"speed":120,"height":"15","weight":"480","image":"http://pokeapi.co/media/img/65.png"},
67 | {"name":"Machop","national_id":66,"hp":70,"attack":80,"sp_atk":35,"sp_def":35,"speed":35,"height":"8","weight":"195","image":"http://pokeapi.co/media/img/66.png"},
68 | {"name":"Machoke","national_id":67,"hp":80,"attack":100,"sp_atk":50,"sp_def":60,"speed":45,"height":"15","weight":"705","image":"http://pokeapi.co/media/img/67.png"},
69 | {"name":"Machamp","national_id":68,"hp":90,"attack":130,"sp_atk":65,"sp_def":85,"speed":55,"height":"16","weight":"1300","image":"http://pokeapi.co/media/img/68.png"},
70 | {"name":"Bellsprout","national_id":69,"hp":50,"attack":75,"sp_atk":70,"sp_def":30,"speed":40,"height":"7","weight":"40","image":"http://pokeapi.co/media/img/69.png"},
71 | {"name":"Weepinbell","national_id":70,"hp":65,"attack":90,"sp_atk":85,"sp_def":45,"speed":55,"height":"10","weight":"64","image":"http://pokeapi.co/media/img/70.png"},
72 | {"name":"Victreebel","national_id":71,"hp":80,"attack":105,"sp_atk":100,"sp_def":70,"speed":70,"height":"17","weight":"155","image":"http://pokeapi.co/media/img/71.png"},
73 | {"name":"Tentacool","national_id":72,"hp":40,"attack":40,"sp_atk":50,"sp_def":100,"speed":70,"height":"9","weight":"455","image":"http://pokeapi.co/media/img/72.png"},
74 | {"name":"Tentacruel","national_id":73,"hp":80,"attack":70,"sp_atk":80,"sp_def":120,"speed":100,"height":"16","weight":"550","image":"http://pokeapi.co/media/img/73.png"},
75 | {"name":"Geodude","national_id":74,"hp":40,"attack":80,"sp_atk":30,"sp_def":30,"speed":20,"height":"4","weight":"200","image":"http://pokeapi.co/media/img/74.png"},
76 | {"name":"Graveler","national_id":75,"hp":55,"attack":95,"sp_atk":45,"sp_def":45,"speed":35,"height":"10","weight":"1050","image":"http://pokeapi.co/media/img/75.png"},
77 | {"name":"Golem","national_id":76,"hp":80,"attack":120,"sp_atk":55,"sp_def":65,"speed":45,"height":"14","weight":"3000","image":"http://pokeapi.co/media/img/76.png"},
78 | {"name":"Ponyta","national_id":77,"hp":50,"attack":85,"sp_atk":65,"sp_def":65,"speed":90,"height":"10","weight":"300","image":"http://pokeapi.co/media/img/77.png"},
79 | {"name":"Rapidash","national_id":78,"hp":65,"attack":100,"sp_atk":80,"sp_def":80,"speed":105,"height":"17","weight":"950","image":"http://pokeapi.co/media/img/78.png"},
80 | {"name":"Slowpoke","national_id":79,"hp":90,"attack":65,"sp_atk":40,"sp_def":40,"speed":15,"height":"12","weight":"360","image":"http://pokeapi.co/media/img/79.png"},
81 | {"name":"Slowbro","national_id":80,"hp":95,"attack":75,"sp_atk":100,"sp_def":80,"speed":30,"height":"16","weight":"785","image":"http://pokeapi.co/media/img/80.png"},
82 | {"name":"Magnemite","national_id":81,"hp":25,"attack":35,"sp_atk":95,"sp_def":55,"speed":45,"height":"3","weight":"60","image":"http://pokeapi.co/media/img/81.png"},
83 | {"name":"Magneton","national_id":82,"hp":50,"attack":60,"sp_atk":120,"sp_def":70,"speed":70,"height":"10","weight":"600","image":"http://pokeapi.co/media/img/82.png"},
84 | {"name":"Farfetchd","national_id":83,"hp":52,"attack":65,"sp_atk":58,"sp_def":62,"speed":60,"height":"8","weight":"150","image":"http://pokeapi.co/media/img/83.png"},
85 | {"name":"Doduo","national_id":84,"hp":35,"attack":85,"sp_atk":35,"sp_def":35,"speed":75,"height":"14","weight":"392","image":"http://pokeapi.co/media/img/84.png"},
86 | {"name":"Dodrio","national_id":85,"hp":60,"attack":110,"sp_atk":60,"sp_def":60,"speed":100,"height":"18","weight":"852","image":"http://pokeapi.co/media/img/85.png"},
87 | {"name":"Seel","national_id":86,"hp":65,"attack":45,"sp_atk":45,"sp_def":70,"speed":45,"height":"11","weight":"900","image":"http://pokeapi.co/media/img/86.png"},
88 | {"name":"Dewgong","national_id":87,"hp":90,"attack":70,"sp_atk":70,"sp_def":95,"speed":70,"height":"17","weight":"1200","image":"http://pokeapi.co/media/img/87.png"},
89 | {"name":"Grimer","national_id":88,"hp":80,"attack":80,"sp_atk":40,"sp_def":50,"speed":25,"height":"9","weight":"300","image":"http://pokeapi.co/media/img/88.png"},
90 | {"name":"Muk","national_id":89,"hp":105,"attack":105,"sp_atk":65,"sp_def":100,"speed":50,"height":"12","weight":"300","image":"http://pokeapi.co/media/img/89.png"},
91 | {"name":"Shellder","national_id":90,"hp":30,"attack":65,"sp_atk":45,"sp_def":25,"speed":40,"height":"3","weight":"40","image":"http://pokeapi.co/media/img/90.png"},
92 | {"name":"Cloyster","national_id":91,"hp":50,"attack":95,"sp_atk":85,"sp_def":45,"speed":70,"height":"15","weight":"1325","image":"http://pokeapi.co/media/img/91.png"},
93 | {"name":"Gastly","national_id":92,"hp":30,"attack":35,"sp_atk":100,"sp_def":35,"speed":80,"height":"13","weight":"1","image":"http://pokeapi.co/media/img/92.png"},
94 | {"name":"Haunter","national_id":93,"hp":45,"attack":50,"sp_atk":115,"sp_def":55,"speed":95,"height":"16","weight":"1","image":"http://pokeapi.co/media/img/93.png"},
95 | {"name":"Gengar","national_id":94,"hp":60,"attack":65,"sp_atk":130,"sp_def":75,"speed":110,"height":"15","weight":"405","image":"http://pokeapi.co/media/img/94.png"},
96 | {"name":"Onix","national_id":95,"hp":35,"attack":45,"sp_atk":30,"sp_def":45,"speed":70,"height":"88","weight":"2100","image":"http://pokeapi.co/media/img/95.png"},
97 | {"name":"Drowzee","national_id":96,"hp":60,"attack":48,"sp_atk":43,"sp_def":90,"speed":42,"height":"10","weight":"324","image":"http://pokeapi.co/media/img/96.png"},
98 | {"name":"Hypno","national_id":97,"hp":85,"attack":73,"sp_atk":73,"sp_def":115,"speed":67,"height":"16","weight":"756","image":"http://pokeapi.co/media/img/97.png"},
99 | {"name":"Krabby","national_id":98,"hp":30,"attack":105,"sp_atk":25,"sp_def":25,"speed":50,"height":"4","weight":"65","image":"http://pokeapi.co/media/img/98.png"},
100 | {"name":"Kingler","national_id":99,"hp":55,"attack":130,"sp_atk":50,"sp_def":50,"speed":75,"height":"13","weight":"600","image":"http://pokeapi.co/media/img/99.png"},
101 | {"name":"Voltorb","national_id":100,"hp":40,"attack":30,"sp_atk":55,"sp_def":55,"speed":100,"height":"5","weight":"104","image":"http://pokeapi.co/media/img/100.png"},
102 | {"name":"Electrode","national_id":101,"hp":60,"attack":50,"sp_atk":80,"sp_def":80,"speed":140,"height":"12","weight":"666","image":"http://pokeapi.co/media/img/101.png"},
103 | {"name":"Exeggcute","national_id":102,"hp":60,"attack":40,"sp_atk":60,"sp_def":45,"speed":40,"height":"4","weight":"25","image":"http://pokeapi.co/media/img/102.png"},
104 | {"name":"Exeggutor","national_id":103,"hp":95,"attack":95,"sp_atk":125,"sp_def":65,"speed":55,"height":"20","weight":"1200","image":"http://pokeapi.co/media/img/103.png"},
105 | {"name":"Cubone","national_id":104,"hp":50,"attack":50,"sp_atk":40,"sp_def":50,"speed":35,"height":"4","weight":"65","image":"http://pokeapi.co/media/img/104.png"},
106 | {"name":"Marowak","national_id":105,"hp":60,"attack":80,"sp_atk":50,"sp_def":80,"speed":45,"height":"10","weight":"450","image":"http://pokeapi.co/media/img/105.png"},
107 | {"name":"Hitmonlee","national_id":106,"hp":50,"attack":120,"sp_atk":35,"sp_def":110,"speed":87,"height":"15","weight":"498","image":"http://pokeapi.co/media/img/106.png"},
108 | {"name":"Hitmonchan","national_id":107,"hp":50,"attack":105,"sp_atk":35,"sp_def":110,"speed":76,"height":"14","weight":"502","image":"http://pokeapi.co/media/img/107.png"},
109 | {"name":"Lickitung","national_id":108,"hp":90,"attack":55,"sp_atk":60,"sp_def":75,"speed":30,"height":"12","weight":"655","image":"http://pokeapi.co/media/img/108.png"},
110 | {"name":"Koffing","national_id":109,"hp":40,"attack":65,"sp_atk":60,"sp_def":45,"speed":35,"height":"6","weight":"10","image":"http://pokeapi.co/media/img/109.png"},
111 | {"name":"Weezing","national_id":110,"hp":65,"attack":90,"sp_atk":85,"sp_def":70,"speed":60,"height":"12","weight":"95","image":"http://pokeapi.co/media/img/110.png"},
112 | {"name":"Rhyhorn","national_id":111,"hp":80,"attack":85,"sp_atk":30,"sp_def":30,"speed":25,"height":"10","weight":"1150","image":"http://pokeapi.co/media/img/111.png"},
113 | {"name":"Rhydon","national_id":112,"hp":105,"attack":130,"sp_atk":45,"sp_def":45,"speed":40,"height":"19","weight":"1200","image":"http://pokeapi.co/media/img/112.png"},
114 | {"name":"Chansey","national_id":113,"hp":250,"attack":5,"sp_atk":35,"sp_def":105,"speed":50,"height":"11","weight":"346","image":"http://pokeapi.co/media/img/113.png"},
115 | {"name":"Tangela","national_id":114,"hp":65,"attack":55,"sp_atk":100,"sp_def":40,"speed":60,"height":"10","weight":"350","image":"http://pokeapi.co/media/img/114.png"},
116 | {"name":"Kangaskhan","national_id":115,"hp":105,"attack":95,"sp_atk":40,"sp_def":80,"speed":90,"height":"22","weight":"800","image":"http://pokeapi.co/media/img/115.png"},
117 | {"name":"Horsea","national_id":116,"hp":30,"attack":40,"sp_atk":70,"sp_def":25,"speed":60,"height":"4","weight":"80","image":"http://pokeapi.co/media/img/116.png"},
118 | {"name":"Seadra","national_id":117,"hp":55,"attack":65,"sp_atk":95,"sp_def":45,"speed":85,"height":"12","weight":"250","image":"http://pokeapi.co/media/img/117.png"},
119 | {"name":"Goldeen","national_id":118,"hp":45,"attack":67,"sp_atk":35,"sp_def":50,"speed":63,"height":"6","weight":"150","image":"http://pokeapi.co/media/img/118.png"},
120 | {"name":"Seaking","national_id":119,"hp":80,"attack":92,"sp_atk":65,"sp_def":80,"speed":68,"height":"13","weight":"390","image":"http://pokeapi.co/media/img/119.png"},
121 | {"name":"Staryu","national_id":120,"hp":30,"attack":45,"sp_atk":70,"sp_def":55,"speed":85,"height":"8","weight":"345","image":"http://pokeapi.co/media/img/120.png"},
122 | {"name":"Starmie","national_id":121,"hp":60,"attack":75,"sp_atk":100,"sp_def":85,"speed":115,"height":"11","weight":"800","image":"http://pokeapi.co/media/img/121.png"},
123 | {"name":"Mr-mime","national_id":122,"hp":40,"attack":45,"sp_atk":100,"sp_def":120,"speed":90,"height":"13","weight":"545","image":"http://pokeapi.co/media/img/122.png"},
124 | {"name":"Scyther","national_id":123,"hp":70,"attack":110,"sp_atk":55,"sp_def":80,"speed":105,"height":"15","weight":"560","image":"http://pokeapi.co/media/img/123.png"},
125 | {"name":"Jynx","national_id":124,"hp":65,"attack":50,"sp_atk":115,"sp_def":95,"speed":95,"height":"14","weight":"406","image":"http://pokeapi.co/media/img/124.png"},
126 | {"name":"Electabuzz","national_id":125,"hp":65,"attack":83,"sp_atk":95,"sp_def":85,"speed":105,"height":"11","weight":"300","image":"http://pokeapi.co/media/img/125.png"},
127 | {"name":"Magmar","national_id":126,"hp":65,"attack":95,"sp_atk":100,"sp_def":85,"speed":93,"height":"13","weight":"445","image":"http://pokeapi.co/media/img/126.png"},
128 | {"name":"Pinsir","national_id":127,"hp":65,"attack":125,"sp_atk":55,"sp_def":70,"speed":85,"height":"15","weight":"550","image":"http://pokeapi.co/media/img/127.png"},
129 | {"name":"Tauros","national_id":128,"hp":75,"attack":100,"sp_atk":40,"sp_def":70,"speed":110,"height":"14","weight":"884","image":"http://pokeapi.co/media/img/128.png"},
130 | {"name":"Magikarp","national_id":129,"hp":20,"attack":10,"sp_atk":15,"sp_def":20,"speed":80,"height":"9","weight":"100","image":"http://pokeapi.co/media/img/129.png"},
131 | {"name":"Gyarados","national_id":130,"hp":95,"attack":125,"sp_atk":60,"sp_def":100,"speed":81,"height":"65","weight":"2350","image":"http://pokeapi.co/media/img/130.png"},
132 | {"name":"Lapras","national_id":131,"hp":130,"attack":85,"sp_atk":85,"sp_def":95,"speed":60,"height":"25","weight":"2200","image":"http://pokeapi.co/media/img/131.png"},
133 | {"name":"Ditto","national_id":132,"hp":48,"attack":48,"sp_atk":48,"sp_def":48,"speed":48,"height":"3","weight":"40","image":"http://pokeapi.co/media/img/132.png"},
134 | {"name":"Eevee","national_id":133,"hp":55,"attack":55,"sp_atk":45,"sp_def":65,"speed":55,"height":"3","weight":"65","image":"http://pokeapi.co/media/img/133.png"},
135 | {"name":"Vaporeon","national_id":134,"hp":130,"attack":65,"sp_atk":110,"sp_def":95,"speed":65,"height":"10","weight":"290","image":"http://pokeapi.co/media/img/134.png"},
136 | {"name":"Jolteon","national_id":135,"hp":65,"attack":65,"sp_atk":110,"sp_def":95,"speed":130,"height":"8","weight":"245","image":"http://pokeapi.co/media/img/135.png"},
137 | {"name":"Flareon","national_id":136,"hp":65,"attack":130,"sp_atk":95,"sp_def":110,"speed":65,"height":"9","weight":"250","image":"http://pokeapi.co/media/img/136.png"},
138 | {"name":"Porygon","national_id":137,"hp":65,"attack":60,"sp_atk":85,"sp_def":75,"speed":40,"height":"8","weight":"365","image":"http://pokeapi.co/media/img/137.png"},
139 | {"name":"Omanyte","national_id":138,"hp":35,"attack":40,"sp_atk":90,"sp_def":55,"speed":35,"height":"4","weight":"75","image":"http://pokeapi.co/media/img/138.png"},
140 | {"name":"Omastar","national_id":139,"hp":70,"attack":60,"sp_atk":115,"sp_def":70,"speed":55,"height":"10","weight":"350","image":"http://pokeapi.co/media/img/139.png"},
141 | {"name":"Kabuto","national_id":140,"hp":30,"attack":80,"sp_atk":55,"sp_def":45,"speed":55,"height":"5","weight":"115","image":"http://pokeapi.co/media/img/140.png"},
142 | {"name":"Kabutops","national_id":141,"hp":60,"attack":115,"sp_atk":65,"sp_def":70,"speed":80,"height":"13","weight":"405","image":"http://pokeapi.co/media/img/141.png"},
143 | {"name":"Aerodactyl","national_id":142,"hp":80,"attack":105,"sp_atk":60,"sp_def":75,"speed":130,"height":"18","weight":"590","image":"http://pokeapi.co/media/img/142.png"},
144 | {"name":"Snorlax","national_id":143,"hp":160,"attack":110,"sp_atk":65,"sp_def":110,"speed":30,"height":"21","weight":"4600","image":"http://pokeapi.co/media/img/143.png"},
145 | {"name":"Articuno","national_id":144,"hp":90,"attack":85,"sp_atk":95,"sp_def":125,"speed":85,"height":"17","weight":"554","image":"http://pokeapi.co/media/img/144.png"},
146 | {"name":"Zapdos","national_id":145,"hp":90,"attack":90,"sp_atk":125,"sp_def":90,"speed":100,"height":"16","weight":"526","image":"http://pokeapi.co/media/img/145.png"},
147 | {"name":"Moltres","national_id":146,"hp":90,"attack":100,"sp_atk":125,"sp_def":85,"speed":90,"height":"20","weight":"600","image":"http://pokeapi.co/media/img/146.png"},
148 | {"name":"Dratini","national_id":147,"hp":41,"attack":64,"sp_atk":50,"sp_def":50,"speed":50,"height":"18","weight":"33","image":"http://pokeapi.co/media/img/147.png"},
149 | {"name":"Dragonair","national_id":148,"hp":61,"attack":84,"sp_atk":70,"sp_def":70,"speed":70,"height":"40","weight":"165","image":"http://pokeapi.co/media/img/148.png"},
150 | {"name":"Dragonite","national_id":149,"hp":91,"attack":134,"sp_atk":100,"sp_def":100,"speed":80,"height":"22","weight":"2100","image":"http://pokeapi.co/media/img/149.png"},
151 | {"name":"Mewtwo","national_id":150,"hp":106,"attack":110,"sp_atk":154,"sp_def":90,"speed":130,"height":"20","weight":"1220","image":"http://pokeapi.co/media/img/150.png"},
152 | {"name":"Mew","national_id":151,"hp":100,"attack":100,"sp_atk":100,"sp_def":100,"speed":100,"height":"4","weight":"40","image":"http://pokeapi.co/media/img/151.png"}
153 | ];
154 |
--------------------------------------------------------------------------------
/example/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onefinestay/react-choice/5855ae446334d22a92669d1758d7d42a30a445b4/example/img/logo.png
--------------------------------------------------------------------------------
/example/index.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var fs = require('fs');
5 | var _ = require('lodash');
6 |
7 | var Header = require('./components/header.jsx');
8 | var Footer = require('./components/footer.jsx');
9 | var GithubRibbon = require('./components/github-ribbon.jsx');
10 | var CodeSnippet = require('./components/code-snippet.jsx');
11 | var Install = require('./components/install.jsx');
12 | var Features = require('./components/features.jsx');
13 | var CustomRender = require('./components/custom-render.jsx');
14 |
15 | var Choice = require('../src');
16 |
17 | var singleExample = fs.readFileSync(__dirname + '/code-snippets/single.jsx', 'utf8');
18 | var multipleExample = fs.readFileSync(__dirname + '/code-snippets/multiple.jsx', 'utf8');
19 |
20 | var COUNTRIES = require('./data/countries.js');
21 |
22 | var Index = React.createClass({
23 | getDefaultProps: function() {
24 | return {};
25 | },
26 |
27 | render: function() {
28 | var options = _.map(COUNTRIES, function(option) {
29 | return (
30 |
31 | {option.label}
32 |
33 | );
34 | });
35 |
36 | return (
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
Single Choice
46 |
47 | {options}
48 |
49 |
50 |
51 | {singleExample}
52 |
53 |
54 |
55 |
56 |
57 |
58 |
Multiple Choice
59 |
60 | {options}
61 |
62 |
63 |
64 | {multipleExample}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
Tutorials
75 |
76 |
77 |
78 |
79 |
80 |
81 | );
82 | }
83 | });
84 |
85 | module.exports = Index;
86 |
--------------------------------------------------------------------------------
/example/js/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var Index = React.createFactory(require('../index.jsx'));
5 |
6 | window.React = React;
7 |
8 | React.render(
9 | Index(),
10 | document.getElementById('app')
11 | );
12 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.assign = require('object.assign');
4 |
5 | var fs = require('fs');
6 | var path = require('path');
7 | var gulp = require('gulp');
8 | var autoprefixer = require('gulp-autoprefixer');
9 | var extReplace = require('gulp-ext-replace');
10 | var watch = require('gulp-watch');
11 | var babel = require('gulp-babel');
12 | var connect = require('gulp-connect');
13 | var sass = require('gulp-sass');
14 | var deploy = require('gulp-gh-pages');
15 | var React = require('react');
16 | var webpack = require('webpack');
17 | var gulpWebpack = require('gulp-webpack');
18 |
19 | var PRODUCTION = (process.env.NODE_ENV === 'production');
20 |
21 | var gulpPlugins = [
22 | // Fix for moment including all locales
23 | // Ref: http://stackoverflow.com/a/25426019
24 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
25 | ];
26 |
27 | if (PRODUCTION) {
28 | gulpPlugins.push(new webpack.DefinePlugin({
29 | "process.env": {
30 | NODE_ENV: JSON.stringify("production")
31 | }
32 | }));
33 | gulpPlugins.push(new webpack.optimize.DedupePlugin());
34 | gulpPlugins.push(new webpack.optimize.UglifyJsPlugin({
35 | compress: true,
36 | mangle: true,
37 | sourceMap: true
38 | }));
39 | }
40 |
41 | var webpackConfig = {
42 | cache: true,
43 | debug: !PRODUCTION,
44 | devtool: PRODUCTION ? 'source-map' : 'eval-source-map',
45 | context: __dirname,
46 | output: {
47 | path: path.resolve('./example/build/'),
48 | filename: 'index.js'
49 | },
50 | module: {
51 | loaders: [
52 | {
53 | test: /\.jsx|.js$/,
54 | exclude: /node_modules\//,
55 | loaders: [
56 | 'babel-loader?stage=1'
57 | ]
58 | },
59 | ],
60 | postLoaders: [
61 | {
62 | loader: "transform/cacheable?brfs"
63 | }
64 | ]
65 | },
66 | resolve: {
67 | extensions: ['', '.js', '.jsx']
68 | },
69 | plugins: gulpPlugins
70 | };
71 |
72 | gulp.task('build-dist-js', function() {
73 | // build javascript files
74 | return gulp.src('src/**/*.{js,jsx}')
75 | .pipe(babel({
76 | stage: 1
77 | }))
78 | .pipe(extReplace('.js'))
79 | .pipe(gulp.dest('dist'));
80 | });
81 |
82 | gulp.task('build-example-js', function() {
83 | var compiler = gulpWebpack(webpackConfig, webpack);
84 |
85 | return gulp.src('./example/js/index.js')
86 | .pipe(compiler)
87 | .pipe(gulp.dest('./example/build'));
88 | });
89 |
90 | gulp.task('watch-example-js', function() {
91 | var compiler = gulpWebpack(Object.assign({}, {watch: true}, webpackConfig), webpack);
92 | return gulp.src('./example/js/index.js')
93 | .pipe(compiler)
94 | .pipe(gulp.dest('./example/build'));
95 | });
96 |
97 | gulp.task('build-example', function() {
98 | // setup babel hook
99 | require("babel/register")({
100 | stage: 1
101 | });
102 |
103 | var Index = React.createFactory(require('./example/base.jsx'));
104 | var markup = '' + React.renderToString(Index());
105 |
106 | // write file
107 | fs.writeFileSync('./example/index.html', markup);
108 | });
109 |
110 | gulp.task('build-example-scss', function() {
111 | gulp.src('./example/css/**/*.scss')
112 | .pipe(sass())
113 | .pipe(autoprefixer())
114 | .pipe(gulp.dest('./example/css'));
115 | });
116 |
117 | gulp.task('watch-example-scss', ['build-example-scss'], function() {
118 | watch('./example/**/*.scss', function(files, cb) {
119 | gulp.start('build-example-scss', cb);
120 | });
121 | });
122 |
123 | gulp.task('example-server', function() {
124 | connect.server({
125 | root: 'example',
126 | port: '9989'
127 | });
128 | });
129 |
130 | gulp.task('build', ['build-dist-js', 'build-example', 'build-example-js', 'build-example-scss']);
131 | gulp.task('develop', ['build-example', 'watch-example-js', 'watch-example-scss', 'example-server']);
132 |
133 | gulp.task('deploy-example', ['build'], function() {
134 | return gulp.src('./example/**/*')
135 | .pipe(deploy());
136 | });
137 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-choice",
3 | "version": "0.4.3",
4 | "description": "A React based customisable select box",
5 | "main": "dist/index.js",
6 | "author": "Jonathan Kim ",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/onefinestay/react-choice"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/onefinestay/react-choice/issues"
13 | },
14 | "license": "Apache 2.0",
15 | "peerDependencies": {
16 | "react": ">=0.12.0"
17 | },
18 | "dependencies": {
19 | "lodash": "^2.4.1",
20 | "sifter": "^0.4.1"
21 | },
22 | "devDependencies": {
23 | "babel": "^5.2.16",
24 | "babel-core": "^5.2.6",
25 | "babel-loader": "^5.0.0",
26 | "brfs": "^1.2.0",
27 | "gulp": "^3.8.9",
28 | "gulp-autoprefixer": "^2.1.0",
29 | "gulp-babel": "^5.1.0",
30 | "gulp-connect": "^2.0.6",
31 | "gulp-ext-replace": "^0.1.0",
32 | "gulp-gh-pages": "^0.4.0",
33 | "gulp-sass": "^1.2.2",
34 | "gulp-util": "^3.0.1",
35 | "gulp-watch": "^2.0.0",
36 | "gulp-webpack": "^1.4.0",
37 | "object.assign": "^1.1.1",
38 | "transform-loader": "^0.2.1",
39 | "webpack": "^1.5.3"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/humanize-string.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = function(string, upperCase) {
4 | if (typeof string == 'string') {
5 | var firstCharacter = string.charAt(0);
6 |
7 | if (typeof upperCase === 'undefined' || upperCase === true) {
8 | firstCharacter = firstCharacter.toUpperCase();
9 | }
10 |
11 | var display = firstCharacter + string.slice(1);
12 | return display.replace(/_|-/g, ' ');
13 | } else {
14 | return string;
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/src/icon.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var cx = React.addons.classSet;
5 |
6 | var Icon = React.createClass({
7 | propTypes: {
8 | focused: React.PropTypes.bool.isRequired
9 | },
10 |
11 | render: function() {
12 | var arrowClasses = cx({
13 | 'react-choice-icon__arrow': true,
14 | 'react-choice-icon__arrow--up': this.props.focused,
15 | 'react-choice-icon__arrow--down': !this.props.focused
16 | });
17 |
18 | return (
19 |
20 | );
21 | }
22 | });
23 |
24 | module.exports = Icon;
25 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'Select': require('./single'),
3 | 'SelectMultiple': require('./multiple'),
4 | 'Option': require('./option'),
5 | 'OptionMixin': require('./option-mixin'),
6 | 'TextHighlight': require('./text-highlight')
7 | };
8 |
--------------------------------------------------------------------------------
/src/multiple.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var _ = require('lodash');
5 | var cx = React.addons.classSet;
6 | var cloneWithProps = React.addons.cloneWithProps;
7 |
8 | var Options = require('./options');
9 | var OptionWrapper = require('./option-wrapper');
10 |
11 | var SearchMixin = require('./search-mixin');
12 |
13 | var ValueWrapper = React.createClass({
14 | propTypes: {
15 | onClick: React.PropTypes.func.isRequired,
16 | onDeleteClick: React.PropTypes.func.isRequired
17 | },
18 |
19 | onDeleteClick: function(event) {
20 | event.stopPropagation();
21 | this.props.onDeleteClick(event);
22 | },
23 |
24 | render: function() {
25 | var classes = cx({
26 | 'react-choice-value': true,
27 | 'react-choice-value--is-selected': this.props.selected
28 | });
29 |
30 | return (
31 |
32 |
{this.props.children}
33 |
x
34 |
35 | );
36 | }
37 | });
38 |
39 | var MultipleChoice = React.createClass({
40 | mixins: [SearchMixin],
41 |
42 | propTypes: {
43 | name: React.PropTypes.string, // name of input
44 | placeholder: React.PropTypes.string, // input placeholder
45 | values: React.PropTypes.array, // initial values
46 |
47 | children: React.PropTypes.array.isRequired,
48 |
49 | valueField: React.PropTypes.string, // value field name
50 | labelField: React.PropTypes.string, // label field name
51 |
52 | searchField: React.PropTypes.array, // array of search fields
53 |
54 | onSelect: React.PropTypes.func, // function called when option is selected
55 | onDelete: React.PropTypes.func, // function called when option is deleted
56 | allowDuplicates: React.PropTypes.bool // if true, the same values can be added multiple times
57 | },
58 |
59 | getDefaultProps: function() {
60 | return {
61 | values: [],
62 | valueField: 'value',
63 | labelField: 'children',
64 | searchField: ['children'],
65 | allowDuplicates: false
66 | };
67 | },
68 |
69 | getInitialState: function() {
70 | var props = this.props.searchField;
71 | props.push(this.props.valueField);
72 | props.push(this.props.searchField);
73 | props = _.uniq(props);
74 |
75 | var options = _.map(this.props.children, function(child) {
76 | // TODO Validation ?
77 | return _.pick(child.props, props);
78 | }, this);
79 |
80 | return {
81 | focus: false,
82 | searchResults: this._sort(options),
83 | values: this.props.values,
84 | initialOptions: options,
85 | highlighted: null,
86 | selected: null,
87 | selectedIndex: -1,
88 | searchTokens: []
89 | };
90 | },
91 |
92 | _handleContainerInput: function(event) {
93 | var keys = {
94 | 37: this._moveLeft,
95 | 39: this._moveRight,
96 | 8: this._removeSelectedContainer
97 | };
98 |
99 | if (typeof keys[event.keyCode] === 'function') {
100 | keys[event.keyCode](event);
101 | }
102 | },
103 |
104 | _handleContainerBlur: function() {
105 | if (this.state.selectedIndex) {
106 | this.setState({
107 | selectedIndex: -1
108 | });
109 | }
110 | },
111 |
112 | _selectOption: function(option) {
113 | if (option) {
114 | var values = this.state.values.slice(0); // copy
115 | var options = this._getAvailableOptions(values);
116 |
117 | // determine which item to highlight
118 | var valueField = this.props.valueField;
119 | var optionIndex = _.findIndex(options, function(o) {
120 | return option[valueField] === o[valueField];
121 | });
122 |
123 | values.push(option);
124 |
125 | options = this._getAvailableOptions(values);
126 | var state = this._resetSearch(options);
127 | state.values = values;
128 |
129 | var nextOption = options[optionIndex];
130 | if (_.isUndefined(nextOption)) {
131 | // at the end of the list so select previous one
132 | nextOption = options[optionIndex - 1];
133 | if (_.isUndefined(nextOption)) {
134 | // bail out
135 | nextOption = _.first(options);
136 | }
137 | }
138 |
139 | state.highlighted = nextOption;
140 |
141 | this.setState(state);
142 |
143 | if (typeof this.props.onSelect === 'function') {
144 | this.props.onSelect(option, values);
145 | }
146 | }
147 | },
148 |
149 | _getAvailableOptions: function(values) {
150 | var options = this.state.initialOptions;
151 | var valueField = this.props.valueField;
152 |
153 | if (this.props.allowDuplicates === false && values) {
154 | options = _.filter(options, function(option) {
155 | var found = _.find(values, function(value) {
156 | return value[valueField] === option[valueField];
157 | });
158 |
159 | return typeof found === 'undefined';
160 | });
161 | }
162 |
163 | return this._sort(options);
164 | },
165 |
166 | _moveLeft: function(event) {
167 | var input = this.refs.input.getDOMNode();
168 |
169 | if (!this.state.values.length) {
170 | return false;
171 | }
172 |
173 | if (
174 | event.target === input &&
175 | event.target.selectionStart === 0
176 | ) {
177 | event.preventDefault();
178 |
179 | // select stage
180 | this.setState({
181 | selectedIndex: this.state.values.length - 1
182 | });
183 |
184 | // focus on container
185 | this.refs.container.getDOMNode().focus();
186 | } else if (this.state.selectedIndex !== -1) {
187 | var nextIndex = this.state.selectedIndex - 1;
188 | if (nextIndex > -1) {
189 | this.setState({
190 | selectedIndex: nextIndex
191 | });
192 | }
193 | }
194 | },
195 |
196 | _moveRight: function() {
197 | var input = this.refs.input.getDOMNode();
198 |
199 | if (!this.state.values.length) {
200 | return false;
201 | }
202 |
203 | if (this.state.selectedIndex !== -1) {
204 | var nextIndex = this.state.selectedIndex + 1;
205 | if (nextIndex < this.state.values.length) {
206 | this.setState({
207 | selectedIndex: nextIndex
208 | });
209 | } else {
210 | // focus input box
211 | input.focus();
212 | this.setState({
213 | selectedIndex: -1
214 | });
215 | }
216 | }
217 | },
218 |
219 | _removeValue: function(index) {
220 | var values = this.state.values.slice(0); // copy
221 | var removedOption = values.splice(index, 1);
222 |
223 | var options = this._getAvailableOptions(values);
224 |
225 | var state = this._resetSearch(options);
226 | state.values = values;
227 |
228 | this.setState(state);
229 |
230 | if (typeof this.props.onDelete === 'function') {
231 | this.props.onDelete(removedOption, values);
232 | }
233 | },
234 |
235 | // removes last element
236 | _remove: function(event) {
237 | if (!this.state.value) {
238 | event.preventDefault();
239 |
240 | // remove last stage
241 | if (this.state.values.length) {
242 | this._removeValue(this.state.values.length - 1);
243 | }
244 | }
245 | },
246 |
247 | // called from within, removes selected element
248 | _removeSelectedContainer: function(event) {
249 | if (this.state.selectedIndex !== -1) {
250 | event.preventDefault();
251 |
252 | // move selection to the element before the removed one (gmail behavior)
253 | this.setState({
254 | selectedIndex: this.state.selectedIndex - 1
255 | });
256 |
257 | this._removeValue(this.state.selectedIndex);
258 | }
259 | },
260 |
261 | _removeDeletedContainer: function(index) {
262 | this._removeValue(index);
263 | },
264 |
265 | _selectValue: function(index, event) {
266 | if (event) {
267 | event.preventDefault();
268 | event.stopPropagation();
269 | }
270 |
271 | this.setState({
272 | selectedIndex: index
273 | });
274 |
275 | this.refs.container.getDOMNode().focus();
276 | },
277 |
278 | _handleBlur: function(event) {
279 | if (this._optionsMouseDown === true) {
280 | this._optionsMouseDown = false;
281 | this.refs.input.getDOMNode().focus();
282 | event.preventDefault();
283 | event.stopPropagation();
284 | } else {
285 | event.preventDefault();
286 | this.setState({
287 | focus: false
288 | });
289 | }
290 | },
291 |
292 | _handleOptionsMouseDown: function() {
293 | this._optionsMouseDown = true;
294 | },
295 |
296 | componentWillReceiveProps: function(nextProps) {
297 | if (_.isEqual(nextProps.values, this.props.values)) {
298 | var options = this._getAvailableOptions(nextProps.values);
299 |
300 | var state = this._resetSearch(options);
301 | state.values = nextProps.values;
302 | state.selected = null;
303 |
304 | this.setState(state);
305 | }
306 | },
307 |
308 | componentDidUpdate: function() {
309 | this._updateScrollPosition();
310 | },
311 |
312 | render: function() {
313 | var values = _.map(this.state.values, function(v, i) {
314 | var key = v[this.props.valueField];
315 |
316 | var selected = i === this.state.selectedIndex;
317 |
318 | var label = v[this.props.labelField];
319 |
320 | return (
321 |
325 | {label}
326 |
327 | );
328 | }, this);
329 |
330 | var options = _.map(this.state.searchResults, function(option) {
331 | var valueField = this.props.valueField;
332 | var v = option[valueField];
333 |
334 | var child = _.find(this.props.children, function(c) {
335 | return c.props[valueField] === v;
336 | });
337 |
338 | var highlighted = this.state.highlighted &&
339 | v === this.state.highlighted[valueField];
340 |
341 | child = cloneWithProps(child, { tokens: this.state.searchTokens });
342 |
343 | return (
344 |
350 | {child}
351 |
352 | );
353 | }, this);
354 |
355 | var value = this.state.value;
356 |
357 | var wrapperClasses = cx({
358 | 'react-choice-wrapper': true,
359 | 'react-choice-multiple': true,
360 | 'react-choice-multiple--in-focus': this.state.focus,
361 | 'react-choice-multiple--not-in-focus': !this.state.focus
362 | });
363 |
364 | return (
365 |
366 |
369 | {values}
370 |
385 |
386 |
387 | {this.state.focus ?
388 |
389 | {options}
390 | : null}
391 |
392 | );
393 | }
394 | });
395 |
396 | module.exports = MultipleChoice;
397 |
--------------------------------------------------------------------------------
/src/option-mixin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 |
5 | var OptionMixin = {
6 | propTypes: {
7 | tokens: React.PropTypes.array.isRequired,
8 | children: React.PropTypes.string.isRequired
9 | }
10 | };
11 |
12 | module.exports = OptionMixin;
13 |
--------------------------------------------------------------------------------
/src/option-wrapper.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var cx = React.addons.classSet;
5 |
6 | function isArray(test) {
7 | return Object.prototype.toString.call(test) === '[object Array]';
8 | }
9 |
10 | var OptionWrapper = React.createClass({
11 | render: function() {
12 | var classes = cx({
13 | 'react-choice-option': true,
14 | 'react-choice-option--selected': !!this.props.selected
15 | });
16 |
17 | return (
18 |
22 | {this.props.children}
23 |
24 | );
25 | }
26 | });
27 |
28 | module.exports = OptionWrapper;
29 |
--------------------------------------------------------------------------------
/src/option.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var cx = React.addons.classSet;
5 |
6 | var OptionMixin = require('./option-mixin');
7 | var TextHighlight = require('./text-highlight');
8 |
9 | //
10 | // Select option
11 | //
12 | var SelectOption = React.createClass({
13 | mixins: [OptionMixin],
14 |
15 | render: function() {
16 | return (
17 |
18 |
19 | {this.props.children}
20 |
21 |
22 | );
23 | }
24 | });
25 |
26 | module.exports = SelectOption;
27 |
--------------------------------------------------------------------------------
/src/options.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 |
5 | var Options = React.createClass({
6 | propTypes: {
7 | children: React.PropTypes.array.isRequired,
8 | onMouseDown: React.PropTypes.func.isRequired
9 | },
10 |
11 | _handleMouseDown: function(event) {
12 | this.props.onMouseDown(event);
13 | },
14 |
15 | render: function() {
16 | return (
17 |
20 |
21 | {this.props.children}
22 |
23 |
24 | );
25 | }
26 | });
27 |
28 | module.exports = Options;
29 |
--------------------------------------------------------------------------------
/src/search-mixin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('lodash');
4 | var Sifter = require('sifter');
5 |
6 | var SearchMixin = {
7 | //
8 | // Public methods
9 | //
10 | focus: function(openOptions) {
11 | this.refs.input.getDOMNode().focus();
12 | },
13 |
14 | _sort: function(list) {
15 | if (typeof this.props.sorter === 'function') {
16 | return this.props.sorter(list);
17 | }
18 | return _.sortBy(list, this.props.labelField);
19 | },
20 |
21 | _handleClick: function(event) {
22 | this.refs.input.getDOMNode().focus();
23 | },
24 |
25 | _handleInput: function(event) {
26 | var keys = {
27 | 13: this._enter,
28 | 37: this._moveLeft,
29 | 38: this._moveUp,
30 | 39: this._moveRight,
31 | 40: this._moveDown,
32 | 8: this._remove
33 | };
34 |
35 | if (typeof keys[event.keyCode] == 'function') {
36 | keys[event.keyCode](event);
37 | }
38 | },
39 |
40 | _handleChange: function(event) {
41 | event.preventDefault();
42 |
43 | var query = event.target.value;
44 |
45 | var options = this._getAvailableOptions();
46 |
47 | var searcher = new Sifter(options);
48 |
49 | var result = searcher.search(query, {
50 | fields: this.props.searchField
51 | });
52 |
53 | var searchResults = _.map(result.items, function(res) {
54 | return options[res.id];
55 | });
56 |
57 | var highlighted = _.first(searchResults);
58 |
59 | this.setState({
60 | value: query,
61 | searchResults: searchResults,
62 | searchTokens: result.tokens,
63 | highlighted: highlighted,
64 | selected: null,
65 | });
66 | },
67 |
68 | _handleFocus: function(event) {
69 | event.preventDefault();
70 |
71 | var highlighted;
72 | if (this.state.selected) {
73 | highlighted = _.find(this.state.searchResults, function(option) {
74 | return option[this.props.valueField] == this.state.selected[this.props.valueField];
75 | }, this);
76 | } else {
77 | highlighted = _.first(this.state.searchResults);
78 | }
79 |
80 | this.setState({
81 | focus: true,
82 | highlighted: highlighted
83 | });
84 | },
85 |
86 | _handleOptionHover: function(option, event) {
87 | event.preventDefault();
88 | this.setState({
89 | highlighted: option
90 | });
91 | },
92 |
93 | _handleOptionClick: function(option, event) {
94 | event.preventDefault();
95 | event.stopPropagation();
96 | this._selectOption(option);
97 | },
98 |
99 | _moveUp: function(event) {
100 | var options = this.state.searchResults;
101 | if (options.length > 0) {
102 | event.preventDefault();
103 | var index = _.indexOf(options, this.state.highlighted);
104 | if (!_.isUndefined(options[index - 1])) {
105 | this.setState({
106 | highlighted: options[index - 1]
107 | });
108 | }
109 | }
110 | },
111 |
112 | _moveDown: function(event) {
113 | var options = this.state.searchResults;
114 | if (options.length > 0) {
115 | event.preventDefault();
116 | var index = _.indexOf(options, this.state.highlighted);
117 | if (!_.isUndefined(options[index + 1])) {
118 | this.setState({
119 | highlighted: options[index + 1]
120 | });
121 | }
122 | }
123 | },
124 |
125 | _enter: function(event) {
126 | event.preventDefault();
127 | this._selectOption(this.state.highlighted);
128 | },
129 |
130 | _updateScrollPosition: function() {
131 | var highlighted = this.refs.highlighted;
132 | if (highlighted) {
133 | // find if highlighted option is not visible
134 | var el = highlighted.getDOMNode();
135 | var parent = this.refs.options.getDOMNode();
136 | var offsetTop = el.offsetTop + el.clientHeight - parent.scrollTop;
137 |
138 | // scroll down
139 | if (offsetTop > parent.clientHeight) {
140 | var diff = el.offsetTop + el.clientHeight - parent.clientHeight;
141 | parent.scrollTop = diff;
142 | } else if (offsetTop - el.clientHeight < 0) { // scroll up
143 | parent.scrollTop = el.offsetTop;
144 | }
145 | }
146 | },
147 |
148 | _resetSearch: function(options) {
149 | return {
150 | value: '',
151 | searchResults: options,
152 | searchTokens: [],
153 | highlighted: _.first(options)
154 | };
155 | },
156 | };
157 |
158 | module.exports = SearchMixin;
159 |
--------------------------------------------------------------------------------
/src/single.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var _ = require('lodash');
5 | var cx = React.addons.classSet;
6 | var cloneWithProps = React.addons.cloneWithProps;
7 |
8 | var Icon = require('./icon');
9 | var Options = require('./options');
10 | var OptionWrapper = require('./option-wrapper');
11 |
12 | var SearchMixin = require('./search-mixin');
13 |
14 | //
15 | // Auto complete select box
16 | //
17 | var SingleChoice = React.createClass({
18 | mixins: [SearchMixin],
19 |
20 | propTypes: {
21 | name: React.PropTypes.string, // name of input
22 | placeholder: React.PropTypes.string, // input placeholder
23 | value: React.PropTypes.string, // initial value for input field
24 | children: React.PropTypes.array.isRequired,
25 |
26 | valueField: React.PropTypes.string, // value field name
27 | labelField: React.PropTypes.string, // label field name
28 |
29 | searchField: React.PropTypes.array, // array of search fields
30 |
31 | icon: React.PropTypes.func, // icon render
32 |
33 | onSelect: React.PropTypes.func // function called when option is selected
34 | },
35 |
36 | getDefaultProps: function() {
37 | return {
38 | valueField: 'value',
39 | labelField: 'children',
40 | searchField: ['children']
41 | };
42 | },
43 |
44 | _getAvailableOptions: function() {
45 | var options = this.state.initialOptions;
46 |
47 | return this._sort(options);
48 | },
49 |
50 | getInitialState: function() {
51 | var selected = null;
52 |
53 | var props = this.props.searchField;
54 | props.push(this.props.valueField);
55 | props.push(this.props.searchField);
56 | props = _.uniq(props);
57 |
58 | var options = _.map(this.props.children, function(child) {
59 | // TODO Validation ?
60 | return _.pick(child.props, props);
61 | }, this);
62 |
63 | if (this.props.value) {
64 | // find selected value
65 | selected = _.find(options, function(option) {
66 | return option[this.props.valueField] === this.props.value;
67 | }, this);
68 | }
69 |
70 | return {
71 | value: selected ? selected[this.props.labelField] : this.props.value,
72 | focus: false,
73 | searchResults: this._sort(options),
74 | initialOptions: options,
75 | highlighted: null,
76 | selected: selected,
77 | searchTokens: []
78 | };
79 | },
80 |
81 | //
82 | // Public methods
83 | //
84 | getValue: function() {
85 | return this.state.selected ?
86 | this.state.selected[this.props.valueField] : null;
87 | },
88 |
89 | //
90 | // Events
91 | //
92 | _handleArrowClick: function(event) {
93 | if (this.state.focus) {
94 | this._handleBlur(event);
95 | this.refs.input.getDOMNode().blur();
96 | } else {
97 | this._handleFocus(event);
98 | this.refs.input.getDOMNode().focus();
99 | }
100 | },
101 |
102 | _remove: function(event) {
103 | if (this.state.selected) {
104 | event.preventDefault();
105 |
106 | var state = this._resetSearch(this.state.initialOptions);
107 | state.selected = null;
108 |
109 | this.setState(state);
110 | }
111 | },
112 |
113 | _selectOption: function(option) {
114 | this._optionsMouseDown = false;
115 | this.refs.input.getDOMNode().blur();
116 | this.setState({
117 | focus: false
118 | });
119 |
120 | if (option) {
121 | var options = this._getAvailableOptions();
122 | var state = this._resetSearch(options);
123 | state.selected = option;
124 |
125 | this.setState(state);
126 |
127 | if (typeof this.props.onSelect === 'function') {
128 | this.props.onSelect(option);
129 | }
130 | }
131 | },
132 |
133 | _handleBlur: function(event) {
134 | event.preventDefault();
135 | if (this._optionsMouseDown === true) {
136 | this._optionsMouseDown = false;
137 | this.refs.input.getDOMNode().focus();
138 | event.stopPropagation();
139 | } else {
140 | this.setState({
141 | focus: false
142 | });
143 | }
144 | },
145 |
146 | _handleOptionsMouseDown: function() {
147 | this._optionsMouseDown = true;
148 | },
149 |
150 | componentWillReceiveProps: function(nextProps) {
151 | if (nextProps.value !== this.props.value) {
152 | var options = this._getAvailableOptions();
153 |
154 | var selected = _.find(options, function(option) {
155 | return option[this.props.valueField] === nextProps.value;
156 | }, this);
157 |
158 | var state = this._resetSearch(options);
159 | state.value = selected ? selected[this.props.labelField] : nextProps.value;
160 | state.selected = selected;
161 |
162 | this.setState(state);
163 | }
164 | },
165 |
166 | componentDidUpdate: function(prevProps, prevState) {
167 | if (prevState.focus === false && this.state.focus === true) {
168 | this._updateScrollPosition();
169 | }
170 |
171 | // select selected text in input box
172 | if (this.state.selected && this.state.focus) {
173 | setTimeout(function() {
174 | if (this.isMounted()) {
175 | this.refs.input.getDOMNode().select();
176 | }
177 | }.bind(this), 50);
178 | }
179 | },
180 |
181 | render: function() {
182 | var options = _.map(this.state.searchResults, function(option) {
183 | var valueField = this.props.valueField;
184 | var v = option[valueField];
185 |
186 | var child = _.find(this.props.children, function(c) {
187 | return c.props[valueField] === v;
188 | });
189 |
190 | var highlighted = this.state.highlighted &&
191 | v === this.state.highlighted[valueField];
192 |
193 | child = cloneWithProps(child, { tokens: this.state.searchTokens });
194 |
195 | return (
196 |
202 | {child}
203 |
204 | );
205 | }, this);
206 |
207 | var value = this.state.selected ?
208 | this.state.selected[this.props.valueField] : null;
209 | var label = this.state.selected ?
210 | this.state.selected[this.props.labelField] : this.state.value;
211 |
212 | var wrapperClasses = cx({
213 | 'react-choice-wrapper': true,
214 | 'react-choice-single': true,
215 | 'react-choice-single--in-focus': this.state.focus,
216 | 'react-choice-single--not-in-focus': !this.state.focus
217 | });
218 |
219 | var IconRenderer = this.props.icon || Icon;
220 |
221 | return (
222 |
223 |
224 |
225 |
226 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | {this.state.focus ?
248 |
249 | {options}
250 | : null}
251 |
252 | );
253 | }
254 | });
255 |
256 | module.exports = SingleChoice;
257 |
--------------------------------------------------------------------------------
/src/text-highlight.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react/addons');
4 | var _ = require('lodash');
5 |
6 | var humanizeString = require('./humanize-string');
7 |
8 | var TextHighlight = React.createClass({
9 | propTypes: {
10 | tokens: React.PropTypes.array.isRequired, // array of search tokens
11 | children: React.PropTypes.string.isRequired // text to highlight
12 | },
13 |
14 | shouldComponentUpdate: function(nextProps) {
15 | if (!_.isEqual(nextProps, this.props)) {
16 | return true;
17 | }
18 | return false;
19 | },
20 |
21 | splitText: function(splits, regex) {
22 | var _splits = [];
23 | _.each(splits, function(split) {
24 | if (split.match === false) {
25 | var match = split.text.match(regex);
26 | if (match) {
27 | var s = split.text.split(regex);
28 |
29 | _.each(s, function(_s, index) {
30 | _splits.push({
31 | text: _s,
32 | match: false
33 | });
34 |
35 | if (index !== s.length - 1) {
36 | var matchCharacter = match[0];
37 |
38 | var i = _splits.length - 1;
39 | if ((_.isEmpty(_s) && i === 0) || _s.slice(-1) === ' ') {
40 | matchCharacter = humanizeString(matchCharacter);
41 | } else {
42 | matchCharacter = matchCharacter.toLowerCase();
43 | }
44 |
45 | _splits.push({
46 | text: matchCharacter,
47 | match: true
48 | });
49 | }
50 | });
51 | } else {
52 | _splits.push(split);
53 | }
54 | } else {
55 | _splits.push(split);
56 | }
57 | });
58 | return _splits;
59 | },
60 |
61 | render: function() {
62 | var label = this.props.children;
63 | var tokens = this.props.tokens;
64 |
65 | var splits = [{
66 | text: label,
67 | match: false
68 | }];
69 |
70 | _.each(tokens, function(token) {
71 | splits = this.splitText(splits, token.regex);
72 | }, this);
73 |
74 | var output = _.map(splits, function(split, i) {
75 | var key = [split.text, split.match, i].join('.');
76 | if (split.match) {
77 | return {split.text};
78 | } else {
79 | return {split.text};
80 | }
81 | });
82 |
83 | return (
84 |
85 | {output}
86 |
87 | );
88 | }
89 | });
90 |
91 | module.exports = TextHighlight;
92 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | cache: true,
3 | context: __dirname,
4 | entry: './example/js/index.js',
5 | output: {
6 | path: './example/build/',
7 | filename: 'index.js'
8 | },
9 | module: {
10 | loaders: [
11 | {
12 | test: /\.jsx?$/,
13 | exclude: /node_modules/,
14 | loaders: [
15 | 'jsx?harmony&sourceMap=true'
16 | ]
17 | }
18 | ],
19 | postLoaders: [
20 | {
21 | loader: "transform/cacheable?brfs"
22 | }
23 | ]
24 | },
25 | resolve: {
26 | extensions: ['', '.js', '.jsx']
27 | }
28 | };
29 |
--------------------------------------------------------------------------------