├── .nvmrc ├── .eslintignore ├── examples ├── favicon.ico ├── multiple.js ├── basic.js ├── styling.js ├── optgroups.js ├── index.html └── assets │ └── main.css ├── .npmignore ├── bin └── deploy.sh ├── .editorconfig ├── .githooks ├── deploy └── pre-commit ├── webpack ├── webpack.config.prod.js └── webpack.config.dev.js ├── .travis.yml ├── .gitignore ├── CHANGELOG.md ├── lib ├── index.js ├── __snapshots__ │ └── index.test.js.snap └── index.test.js ├── README.md └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.min.js 3 | coverage/ 4 | dist/ 5 | pages 6 | -------------------------------------------------------------------------------- /examples/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springload/react-simpler-select/HEAD/examples/favicon.ico -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage 2 | server 3 | .babelrc 4 | examples 5 | docs 6 | lib 7 | .npmignore 8 | webpack 9 | .nvmrc 10 | .travis.yml 11 | .eslintignore 12 | .eslintrc 13 | .editorconfig 14 | .githooks 15 | .github 16 | bin 17 | .env 18 | pages 19 | webpack-stats.json 20 | *.test.js 21 | -------------------------------------------------------------------------------- /bin/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # From the project's root. 4 | # First make sure your master is up to date. 5 | # Then push the new changes 6 | git checkout -B gh-pages 7 | git add -f pages 8 | git commit -am "Rebuild website" --no-verify 9 | git filter-branch -f --prune-empty --subdirectory-filter pages 10 | git push -f origin gh-pages 11 | git checkout - 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Defines the coding style for different editors and IDEs. 2 | # http://editorconfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Rules for source code. 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | # Rules for tool configuration. 17 | [{package.json,*.yml, *.yaml}] 18 | indent_size = 2 19 | 20 | # Rules for markdown documents. 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.githooks/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # https://gist.github.com/apexskier/efb7c1aaa6e77e8127a8 4 | # Deploy hooks stored in your git repo to everyone! 5 | # 6 | # I keep this in $ROOT/$HOOK_DIR/deploy 7 | # From the top level of your git repo, run ./hook/deploy (or equivalent) after 8 | # cloning or adding a new hook. 9 | # No output is good output. 10 | 11 | BASE=`git rev-parse --git-dir` 12 | ROOT=`git rev-parse --show-toplevel` 13 | HOOK_DIR=.githooks 14 | HOOKS=$ROOT/$HOOK_DIR/* 15 | 16 | if [ ! -d "$ROOT/$HOOK_DIR" ] 17 | then 18 | echo "Couldn't find hooks dir." 19 | exit 1 20 | fi 21 | 22 | # Clean up existing hooks. 23 | rm -f $BASE/hooks/* 24 | 25 | # Synlink new hooks. 26 | for HOOK in $HOOKS 27 | do 28 | (cd $BASE/hooks ; ln -s $HOOK `basename $HOOK` || echo "Failed to link $HOOK to `basename $HOOK`.") 29 | done 30 | 31 | echo "Hooks deployed to $BASE/hooks." 32 | exit 0 33 | -------------------------------------------------------------------------------- /webpack/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const config = require('./webpack.config.dev'); 4 | 5 | config.watch = false; 6 | config.devtool = false; 7 | config.output.path = path.join(__dirname, '..', 'pages', 'assets'); 8 | 9 | config.plugins = [ 10 | new webpack.DefinePlugin({ 11 | 'process.env': { 12 | NODE_ENV: JSON.stringify('production'), 13 | }, 14 | }), 15 | new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.bundle.js' }), 16 | new webpack.optimize.UglifyJsPlugin({ 17 | compress: { 18 | screw_ie8: true, // React doesn't support IE8 19 | warnings: false, 20 | }, 21 | mangle: { 22 | screw_ie8: true, 23 | }, 24 | output: { 25 | comments: false, 26 | screw_ie8: true, 27 | }, 28 | }), 29 | ]; 30 | 31 | module.exports = config; 32 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check if this is the initial commit 4 | if git rev-parse --verify HEAD >/dev/null 2>&1 5 | then 6 | against=HEAD 7 | else 8 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 9 | fi 10 | 11 | # Use git diff-index to check for whitespace errors 12 | if ! git diff-index --check --cached $against 13 | then 14 | echo "Aborting commit due to whitespace errors." 15 | exit 1 16 | else 17 | # Fail on first line that fails. 18 | set -e 19 | 20 | NEW_FILES=$(git --no-pager diff --name-only --cached --diff-filter=d) 21 | JS_FILES=$(echo "$NEW_FILES" | { grep .js$ || true; }) 22 | SNAPSHOT_FILES=$(echo "$NEW_FILES" | { grep .snap$ || true; }) 23 | 24 | if [ -n "$JS_FILES" ]; 25 | then 26 | npm run linter:js -s -- $JS_FILES 27 | fi 28 | 29 | if [ -n "$JS_FILES" ] || [ -n "$SNAPSHOT_FILES" ]; 30 | then 31 | npm run test:coverage -s 32 | fi 33 | 34 | exit 0 35 | fi 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | script: 7 | - npm run test:ci 8 | after_success: 9 | - npm run coveralls 10 | branches: 11 | except: 12 | - gh-pages 13 | notifications: 14 | email: false 15 | slack: 16 | rooms: 17 | secure: cBEjOYxg1h7ya34vUPwJKkEeNdu4uI9Vw1oSdCZg+6TXAD65lUfWeHfTjB5i2C2YiWNoDiaf8W4BLF1C8gV1VBqHsoIKsLIBkCE7wVORjgDaSUbDsHkiHWOyroGi4l1fxvT6r0u/VexwjL6i25lxEKMSvi0Ou31mn1jo8fE6kmqwY/dCRCXciiE3aAv37vKKoG6ITz30E+uG0KnTeBCsDxt4IO9NWqz6aeGsxw+K5lV3QruDPnCDm//naXZKgiJL4AZe4/0GeRP4j2cNRNEXXZzKG+DjwimNcxco2mIcvugUIcC0GmCHbReGUXzDaxcraWrjLITS9ls5ktXVW4EUXdwV2kjDfYDsCABSk2Mz1CS6a8PmSLV/znUEziEwHhQrun7qX56Nh02CDxGhVB3W4ZO6M78sFaipqibOjWqMpo9rpgXveZIno2xkWE9AA6eH6/eSfN1nf/Im6yDQEjX5/uIkdrY9rMSLReYdsZHsGiU5OGNUPfyS3u8JIb9KzftIP4p3iVaPSLNc0hDJWqzkNhFV/R/wGVElplN+1dNSqkfbV6C9OwmLpDh21dVNmOP13Yu8RnFeZmvKMiQqYnj3mxLUvBj+sJkWLFYd8/Kz/8m3gLN2YioGI2F5MCwW6u21OHi78KuB+B4jA303DDMYovjXy4dCSykIVFoqCBaqQjA= 18 | -------------------------------------------------------------------------------- /examples/multiple.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Select from '../lib/index'; 4 | 5 | const options = [ 6 | { value: 'es', label: 'Spanish' }, 7 | { value: 'cs', label: 'Czech' }, 8 | { value: 'en', label: 'English' }, 9 | { value: 'fi', label: 'Finnish' }, 10 | { value: 'fr', label: 'French' }, 11 | ]; 12 | 13 | class App extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | values: [''], 19 | }; 20 | 21 | this.handleChange = this.handleChange.bind(this); 22 | } 23 | 24 | handleChange(newValues) { 25 | this.setState({ 26 | values: newValues, 27 | }); 28 | } 29 | 30 | render() { 31 | const { values } = this.state; 32 | 33 | return ( 34 | 42 | ); 43 | } 44 | } 45 | 46 | ReactDOM.render(, document.querySelector('[data-mount-basic]')); 47 | -------------------------------------------------------------------------------- /examples/styling.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Select from '../lib/index'; 4 | 5 | const options = [ 6 | { label: 'C', value: 'c' }, 7 | { label: 'C#', value: 'cs' }, 8 | { label: 'C++', value: 'cpp' }, 9 | { label: 'Clojure', value: 'clojure' }, 10 | { label: 'Elm', value: 'elm' }, 11 | { label: 'Go', value: 'go' }, 12 | { label: 'Haskell', value: 'haskell' }, 13 | { label: 'Java', value: 'java' }, 14 | { label: 'Javascript', value: 'javascript' }, 15 | { label: 'Perl', value: 'perl' }, 16 | { label: 'PHP', value: 'php' }, 17 | { label: 'Python', value: 'python' }, 18 | { label: 'Ruby', value: 'ruby' }, 19 | { label: 'Scala', value: 'scala' }, 20 | ]; 21 | 22 | // eslint-disable-next-line no-console 23 | const onChange = console.log.bind(console); 24 | 25 | const select = ( 26 |
27 | 49 | ); 50 | 51 | const mount = document.querySelector('[data-mount-optgroups]'); 52 | 53 | ReactDOM.render(select, mount); 54 | -------------------------------------------------------------------------------- /webpack/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | const stats = { 5 | // Add chunk information (setting this to `false` allows for a less verbose output) 6 | chunks: false, 7 | // Add the hash of the compilation 8 | hash: false, 9 | // `webpack --colors` equivalent 10 | colors: true, 11 | // Add information about the reasons why modules are included 12 | reasons: false, 13 | // Add webpack version information 14 | version: false, 15 | // Set the maximum number of modules to be shown 16 | maxModules: 0, 17 | }; 18 | 19 | module.exports = { 20 | // See http://webpack.github.io/docs/configuration.html#devtool 21 | devtool: 'inline-source-map', 22 | entry: { 23 | vendor: ['react', 'react-dom', 'prop-types'], 24 | basic: './examples/basic', 25 | optgroups: './examples/optgroups', 26 | styling: './examples/styling', 27 | multiple: './examples/multiple', 28 | }, 29 | output: { 30 | path: path.join(__dirname, '..', 'build'), 31 | filename: '[name].bundle.js', 32 | publicPath: '/assets/', 33 | }, 34 | plugins: [ 35 | new webpack.HotModuleReplacementPlugin(), 36 | new webpack.NoEmitOnErrorsPlugin(), 37 | new webpack.DefinePlugin({ 38 | 'process.env': { 39 | NODE_ENV: JSON.stringify('development'), 40 | }, 41 | }), 42 | new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.bundle.js' }), 43 | ], 44 | module: { 45 | rules: [ 46 | { 47 | test: /\.js$/, 48 | use: ['babel-loader'], 49 | exclude: /node_modules/, 50 | }, 51 | ], 52 | }, 53 | stats: stats, 54 | // Some libraries import Node modules but don't use them in the browser. 55 | // Tell Webpack to provide empty mocks for them so importing them works. 56 | node: { 57 | fs: 'empty', 58 | net: 'empty', 59 | tls: 'empty', 60 | }, 61 | devServer: { 62 | contentBase: path.join(__dirname, '..', 'examples'), 63 | compress: true, 64 | hot: true, 65 | port: 4000, 66 | overlay: true, 67 | stats: stats, 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # ------------------------------------------------- 4 | # OS files 5 | # ------------------------------------------------- 6 | .DS_Store 7 | .DS_Store? 8 | ._* 9 | .Spotlight-V100 10 | .Trashes 11 | ehthumbs.db 12 | Thumbs.db 13 | 14 | # ------------------------------------------------- 15 | # Logs and databases 16 | # ------------------------------------------------- 17 | logs 18 | *.log 19 | npm-debug.log* 20 | *.sql 21 | *.sqlite3 22 | 23 | # ------------------------------------------------- 24 | # Runtime data and caches 25 | # ------------------------------------------------- 26 | pids 27 | *.pid 28 | *.seed 29 | *.pyc 30 | *.pyo 31 | *.pot 32 | 33 | # ------------------------------------------------- 34 | # Instrumentation and tooling 35 | # ------------------------------------------------- 36 | lib-cov 37 | coverage 38 | .coverage 39 | .grunt 40 | .bundle 41 | docs/pattern-library/index.html 42 | webpack-stats.json 43 | 44 | # ------------------------------------------------- 45 | # Dependency directories 46 | # ------------------------------------------------- 47 | node_modules* 48 | python_modules* 49 | bower_components 50 | .venv 51 | venv 52 | $virtualenv.tar.gz 53 | $node_modules.tar.gz 54 | 55 | # ------------------------------------------------- 56 | # Users Environment 57 | # ------------------------------------------------- 58 | .lock-wscript 59 | .idea 60 | .installed.cfg 61 | .vagrant 62 | .anaconda 63 | Vagrantfile.local 64 | .env 65 | /local 66 | local.py 67 | *.sublime-project 68 | *.sublime-workspace 69 | .vscode 70 | 71 | # ------------------------------------------------- 72 | # Generated files 73 | # ------------------------------------------------- 74 | dist 75 | es 76 | build 77 | /var/static/ 78 | /var/media/ 79 | /docs/_build/ 80 | develop-eggs 81 | *.egg-info 82 | downloads 83 | media 84 | eggs 85 | parts 86 | lib64 87 | .sass-cache 88 | 89 | # ------------------------------------------------- 90 | # Your own project's ignores 91 | # ------------------------------------------------- 92 | core/static/js 93 | core/static/css 94 | core/svg.html 95 | core/static 96 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | > All notable changes to this project are documented in this file. This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 4 | 5 | ## Unreleased 6 | 7 | ## [[v3.0.0]](https://github.com/springload/react-simpler-select/releases/tag/v3.0.0) 8 | 9 | ### Added 10 | 11 | - Add `prop-types` as peerDependency ([#26](https://github.com/springload/react-simpler-select/pull/26)). 12 | - Add ES module target under `pkg.module` field to enable tree shaking. 13 | 14 | ### Changed 15 | 16 | - Convert all code to be compatible with React 15.5 and React 16 ([#26](https://github.com/springload/react-simpler-select/pull/26)). 17 | - Convert to stateless functional component for file size. 18 | 19 | ### Removed 20 | 21 | - Remove animationend vendor prefixing. 22 | - Remove support for React 0.14.7, 0.14.8, 15.1, 15.2. 23 | - Remove UMD build. 24 | - Start compiling only for latest browser versions and IE11. 25 | 26 | ## [[v2.0.1]](https://github.com/springload/react-simpler-select/releases/tag/v2.0.1) 27 | 28 | ### Added 29 | 30 | - Supports multi select (HTML `multiple` attribute). 31 | 32 | ### Changed 33 | 34 | - displayName is now `Select`. 35 | 36 | ## [[v2.0.0]](https://github.com/springload/react-simpler-select/releases/tag/v2.0.0) 37 | 38 | ### Added 39 | 40 | - `options` are now required (the array can be empty) 41 | - Supports React 15.2 42 | 43 | ### Fixed 44 | 45 | - `unknown-prop` warning with React 15.2 (https://github.com/springload/react-simpler-select/issues/23) 46 | 47 | ### Removed 48 | 49 | - Removed unused key for placeholder option (keys should not be relied upon anyway) 50 | 51 | ## [[v1.1.0]](https://github.com/springload/react-simpler-select/releases/tag/v1.1.0) 52 | 53 | ### Added 54 | 55 | - Support for React 15 56 | 57 | ## [[v1.0.0]](https://github.com/springload/react-simpler-select/releases/tag/v1.0.0) 58 | 59 | First stable release! 60 | 61 | ------------- 62 | 63 | ## [[x.y.z]](https://github.com/springload/react-simpler-select/releases/tag/x.y.z) (Template: http://keepachangelog.com/) 64 | 65 | ### Added 66 | 67 | - Something was added to the API / a new feature was introduced. 68 | 69 | ### Changed 70 | 71 | ### Fixed 72 | 73 | ### Removed 74 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // Filters props to remove before transmission to the select element. 5 | const filterProps = (props) => { 6 | return Object.keys(props) 7 | .filter(k => ['onChange', 'options', 'placeholder'].indexOf(k) === -1) 8 | .reduce((filtered, k) => { 9 | // eslint-disable-next-line no-param-reassign 10 | filtered[k] = props[k]; 11 | return filtered; 12 | }, {}); 13 | }; 14 | 15 | const renderOption = option => ( 16 | 23 | ); 24 | 25 | const renderOptions = (options, placeholder) => { 26 | const children = options.map((item) => { 27 | let child; 28 | 29 | if (item.options) { 30 | child = ( 31 | 32 | {item.options.map(renderOption)} 33 | 34 | ); 35 | } else { 36 | child = renderOption(item); 37 | } 38 | 39 | return child; 40 | }); 41 | 42 | if (placeholder) { 43 | children.unshift(renderOption({ label: placeholder, value: '', disabled: true })); 44 | } 45 | 46 | return children; 47 | }; 48 | 49 | const handleChange = (multiple, onChange, e) => { 50 | let value; 51 | 52 | if (multiple) { 53 | value = Array.prototype.slice.call(e.target.options) 54 | .filter(option => option.selected) 55 | .map(option => option.value); 56 | } else { 57 | value = e.target.value; 58 | } 59 | 60 | onChange(value); 61 | }; 62 | 63 | /** 64 | * The Select component. Renders to a plain HTML 89 | {renderOptions(options, placeholder)} 90 | 91 | ); 92 | }; 93 | 94 | Select.propTypes = propTypes; 95 | Select.defaultProps = defaultProps; 96 | 97 | export default Select; 98 | -------------------------------------------------------------------------------- /lib/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Select #options 1`] = ` 4 | 35 | `; 36 | 37 | exports[`Select #options multiple 1`] = ` 38 | 74 | `; 75 | 76 | exports[`Select #options optgroups 1`] = ` 77 | 121 | `; 122 | 123 | exports[`Select renders 1`] = ` 124 | 140 | `; 141 | -------------------------------------------------------------------------------- /lib/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import Select from './index'; 4 | 5 | const options = [ 6 | { value: 'es', label: 'Spanish' }, 7 | { value: 'cs', label: 'Czech' }, 8 | { value: 'en', label: 'English' }, 9 | { value: 'fi', label: 'Finnish' }, 10 | { value: 'fr', label: 'French' }, 11 | ]; 12 | 13 | const optgroups = [ 14 | { 15 | title: 'North Island', 16 | id: 'ni', 17 | options: [ 18 | { value: 'wgtn', label: 'Wellington' }, 19 | { value: 'gsb', label: 'Gisbourne' }, 20 | { value: 'oh', label: 'Ohakune' }, 21 | ], 22 | }, 23 | { 24 | title: 'South Island', 25 | id: 'si', 26 | options: [ 27 | { value: 'ch', label: 'Christchurch' }, 28 | { value: 'qt', label: 'Queenstown' }, 29 | { value: 'te', label: 'lake Tekapo' }, 30 | ], 31 | }, 32 | ]; 33 | 34 | describe('Select', () => { 35 | it('renders', () => { 36 | expect(shallow(( 37 | {}} 55 | /> 56 | ))).toMatchSnapshot(); 57 | }); 58 | 59 | it('#options optgroups', () => { 60 | expect(shallow(( 61 | {}} 75 | /> 76 | ))).toMatchSnapshot(); 77 | }); 78 | 79 | it('#onChange', () => { 80 | const handleChange = jest.fn(); 81 | const wrapper = shallow(( 82 | 104 | )); 105 | 106 | wrapper.find('select').simulate('change', { 107 | target: { 108 | options: [ 109 | { selected: true, value: 'es' }, 110 | { selected: true, value: 'en' }, 111 | ], 112 | }, 113 | }); 114 | 115 | expect(handleChange).toHaveBeenCalledWith(['es', 'en']); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [react-simpler-select](https://springload.github.io/react-simpler-select/) [![npm](https://img.shields.io/npm/v/react-simpler-select.svg)](https://www.npmjs.com/package/react-simpler-select) [![Build Status](https://travis-ci.org/springload/react-simpler-select.svg?branch=master)](https://travis-ci.org/springload/react-simpler-select) [![Coverage Status](https://coveralls.io/repos/github/springload/react-simpler-select/badge.svg)](https://coveralls.io/github/springload/react-simpler-select) 2 | 3 | > React component that renders a select. Supports optgroups, multiple selections. 4 | 5 | Check out the [online demo](https://springload.github.io/react-simpler-select/)! 6 | 7 | ## Features 8 | 9 | - Renders a plain, accessible HTML ` 37 | 38 | // Add props as you go, they will be transfered to the `select` element. 39 | 61 | 62 | // Supports `multiple` select. Just make the `value` prop an array, and get selected options as an array in the `onChange` callback. 63 |