├── .gitignore ├── .npmignore ├── src ├── Box.js ├── Menu.js ├── relect.css └── Relect.js ├── example ├── src │ ├── index.scss │ └── index.js └── index.html ├── README.md ├── package.json └── lib ├── relect.css └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_Store 4 | example/dist/ 5 | *.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | webpack.config.js 2 | .gitignore 3 | example 4 | .npmignore 5 | .idea 6 | node_modules 7 | .DS_Store 8 | package-lock.json 9 | yarn.lock 10 | build 11 | -------------------------------------------------------------------------------- /src/Box.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const BoxContent = props => { 4 | const { chosen, options, disabled } = props; 5 | if (typeof chosen === 'number' && options[chosen] !== void 0) { 6 | const clear = disabled ? null : ; 7 | return {options[chosen].text || options[chosen]}{clear}; 8 | } else { 9 | return {props.placeholder} 10 | } 11 | } 12 | 13 | const Box = props => { 14 | const className = 'relect-box' + (props.disabled ? ' relect-box-disabled' : ''); 15 | return ( 16 |
17 | {BoxContent(props)} 18 | 19 |
20 | ) 21 | } 22 | 23 | export default Box; 24 | -------------------------------------------------------------------------------- /src/Menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Menu extends React.Component { 4 | render() { 5 | const { props } = this; 6 | const style = { 7 | top : props.height - 1, 8 | display : props.showMenu && !props.disabled ? '' : 'none', 9 | lineHeight : props.optionHeight + 'px', 10 | maxHeight : props.optionHeight * 8 + 2 11 | }; 12 | 13 | const options = props.options.map((item, index) => { 14 | const className = index === props.focused ? 'relect-focused-option' : ''; 15 | return ( 16 |
  • 21 | {item.text || item} 22 |
  • 23 | ) 24 | }); 25 | 26 | return ; 27 | } 28 | } 29 | 30 | export default Menu; 31 | -------------------------------------------------------------------------------- /example/src/index.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 600px; 3 | height: 320px; 4 | margin: 0 auto; 5 | text-align: center; 6 | padding-top: 36px; 7 | box-sizing: border-box; 8 | border-radius: 4px; 9 | background: #fff; 10 | box-shadow: 0 1px 3px rgba(0, 0, 0, .35); 11 | } 12 | .title, 13 | .intro { 14 | color: #fff; 15 | text-align: center; 16 | text-shadow: 0 1px 2px rgba(0,0,0,0.2); 17 | } 18 | .title { 19 | font-size: 70px; 20 | margin: 0; 21 | @media (max-width: 600px) { 22 | font-size: 40px; 23 | } 24 | } 25 | .intro { 26 | font-size: 30px; 27 | margin: 15px 10px 40px; 28 | @media (max-width: 600px) { 29 | font-size: 20px; 30 | } 31 | } 32 | .btn { 33 | cursor: pointer; 34 | display: inline-block; 35 | width: 35%; 36 | color: #586481; 37 | font-size: 14px; 38 | background: #fff; 39 | line-height: 30px; 40 | border-radius: 2px; 41 | margin: 20px 15px 0; 42 | border: 1px solid #586481; 43 | transition: .2s background, .2s color; 44 | &:hover { 45 | color: #fff; 46 | background: #586481; 47 | } 48 | &:focus { 49 | outline: none; 50 | } 51 | } 52 | .btn-area { 53 | margin-top: 16px; 54 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Relect 2 | A Tiny React Single Select Component. 3 | [Example](http://chenjiahan.github.io/relect/) 4 | 5 | ## Install 6 | 7 | npm i relect -S 8 | 9 | ## Usage 10 | ``` javascript 11 | import React from 'react'; 12 | import Relect from 'relect'; 13 | 14 | // include styles 15 | import 'relect/lib/relect.css'; 16 | 17 | const options = [ 18 | { text: 'one', value: 1 }, 19 | { text: 'two', value: 2 } 20 | ]; 21 | 22 | class App extends React.Component { 23 | 24 | constructor(props) { 25 | super(props); 26 | this.state = { chosen : null } 27 | } 28 | 29 | onChange(index) { 30 | this.setState({ chosen : index }); 31 | } 32 | 33 | render() { 34 | return ( 35 | 39 | ) 40 | } 41 | } 42 | ``` 43 | ## Props 44 | 45 | Property|Type|Default|Description 46 | ---|---|---|--- 47 | width|number|240|width of select 48 | height|number|36|height of select 49 | options|array|/|options 50 | chosen|number|/|index of chosen option 51 | tabIndex|number|-1|tab order 52 | disabled|bool|false|whether to disable select 53 | autoBlur|bool|false|auto blur after selection 54 | placeholder|string|/|placeholder text 55 | optionHeight|number|30|height of option 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relect", 3 | "version": "1.2.1", 4 | "description": "A Tiny React Single Select Component.", 5 | "main": "lib/index.js", 6 | "devDependencies": { 7 | "autoprefixer-loader": "^3.1.0", 8 | "babel": "^6.1.18", 9 | "babel-core": "^6.3.21", 10 | "babel-loader": "^7.0.0", 11 | "babel-preset-es2015": "^6.24.1", 12 | "babel-preset-react": "^6.24.1", 13 | "babel-preset-stage-0": "^6.24.1", 14 | "babel-runtime": "^6.23.0", 15 | "babel-types": "^6.24.1", 16 | "css-loader": "^0.28.4", 17 | "node-sass": "^4.5.3", 18 | "react": "^16.2.0", 19 | "react-dom": "^16.2.0", 20 | "sass-loader": "^6.0.5", 21 | "shelljs": "^0.8.5", 22 | "style-loader": "^0.19.0", 23 | "webpack": "^3.10.0", 24 | "webpack-dev-server": "^3.1.11", 25 | "webpack-merge": "^4.1.0" 26 | }, 27 | "scripts": { 28 | "dev": "node_modules/.bin/webpack-dev-server --config ./build/webpack.dev.conf.js", 29 | "build": "webpack -p --hide-modules --config ./build/webpack.prod.conf.js && cp src/relect.css lib", 30 | "release": "npm run build && npm publish" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/chenjiahan/relect.git" 35 | }, 36 | "keywords": [ 37 | "react", 38 | "select", 39 | "react-select", 40 | "react-component" 41 | ], 42 | "author": "neverland", 43 | "license": "ISC", 44 | "bugs": { 45 | "url": "https://github.com/chenjiahan/relect/issues" 46 | }, 47 | "homepage": "https://github.com/chenjiahan/relect#readme", 48 | "dependencies": { 49 | "prop-types": "^15.5.10" 50 | }, 51 | "babel": { 52 | "presets": [ 53 | "es2015", 54 | "stage-0", 55 | "react" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Relect 6 | 7 | 46 | 47 | 48 |
    49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | // import Relect from '../../src/Relect'; 4 | import './index.scss'; 5 | import '../../lib/relect.css'; 6 | import Relect from '../../lib'; 7 | 8 | 9 | const objectOptions = [ 10 | { val: 0, text: 'Jon Snow' }, 11 | { val: 1, text: 'Ned Stark' }, 12 | { val: 2, text: 'Tywin' }, 13 | { val: 3, text: 'Robb' }, 14 | { val: 4, text: 'Sansa' }, 15 | { val: 5, text: 'Arya' }, 16 | { val: 6, text: 'Bran' }, 17 | { val: 7, text: 'Cersei' }, 18 | { val: 8, text: 'Jaime' }, 19 | { val: 9, text: 'Joffrey' }, 20 | { val: 10, text: 'Tyrion' }, 21 | { val: 11, text: 'Stannis' }, 22 | { val: 12, text: 'Melisandre' } 23 | ]; 24 | 25 | const arrayOptions = ['one', 'two', 'three', 'four', 'five', 'six']; 26 | 27 | class App extends React.Component { 28 | 29 | constructor(props) { 30 | super(props); 31 | 32 | this.state = { 33 | chosen : null, 34 | disabled : false, 35 | option : objectOptions 36 | } 37 | } 38 | 39 | handleChange(chosen) { 40 | this.setState({ chosen }); 41 | }; 42 | 43 | clear() { 44 | this.setState({ chosen: null }); 45 | } 46 | 47 | selectFirstOption() { 48 | this.setState({ chosen: 0 }); 49 | } 50 | 51 | setDisable() { 52 | this.setState({ disabled: !this.state.disabled }); 53 | } 54 | 55 | selectTywin() { 56 | for (let i = 0; i < objectOptions.length; i++) { 57 | if (objectOptions[i].text === 'Tywin') { 58 | this.setState({ chosen: i }); 59 | return; 60 | } 61 | } 62 | } 63 | 64 | simpleOption() { 65 | this.setState({ option : arrayOptions }); 66 | } 67 | 68 | objectOption() { 69 | this.setState({ option : objectOptions }); 70 | } 71 | 72 | render() { 73 | return ( 74 |
    75 |

    Relect

    76 |

    A Tiny React Single Select Component.

    77 |
    78 | 84 |
    85 | 86 | 87 | 88 | 89 | 90 | 91 |
    92 |
    93 |
    94 | ) 95 | } 96 | } 97 | 98 | ReactDOM.render( 99 | , 100 | document.getElementById('app') 101 | ); 102 | -------------------------------------------------------------------------------- /lib/relect.css: -------------------------------------------------------------------------------- 1 | /* -- relect container -- */ 2 | .relect { 3 | cursor: pointer; 4 | text-align: left; 5 | position: relative; 6 | display: inline-block; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | } 12 | .relect:hover .relect-box { 13 | border-color: #B9B9B9; 14 | box-shadow: 0 0 2px rgba(0, 0, 0, .14); 15 | } 16 | .relect:hover .relect-option { 17 | border-top-color: #B9B9B9; 18 | } 19 | .relect:focus { 20 | outline: none; 21 | } 22 | .relect:focus .relect-box { 23 | border-color: #29B6F6; 24 | box-shadow: 0 0 2px rgba(41, 182, 246, .3); 25 | } 26 | .relect:focus .relect-option { 27 | border-top-color: #29B6F6; 28 | } 29 | .relect:hover .relect-box-disabled, 30 | .relect:focus .relect-box-disabled { 31 | box-shadow: none; 32 | border-color: #D9D9D9; 33 | } 34 | 35 | /* -- box && option -- */ 36 | .relect-box, 37 | .relect-option { 38 | width: inherit; 39 | background: #fff; 40 | -webkit-box-sizing: border-box; 41 | -moz-box-sizing: border-box; 42 | box-sizing: border-box; 43 | } 44 | 45 | /* -- box -- */ 46 | .relect-box { 47 | padding: 0 10px; 48 | border-radius: 2px; 49 | display: inline-block; 50 | border: 1px solid #D9D9D9; 51 | box-shadow: 0 0 1px rgba(0, 0, 0, .14); 52 | -webkit-transition: border-color .2s, box-shadow .2s; 53 | transition: border-color .2s, box-shadow .2s; 54 | } 55 | .relect-box-disabled { 56 | color: #AAA; 57 | cursor: default; 58 | background: #f8f8f8; 59 | box-shadow: none; 60 | } 61 | .relect-placeholder { 62 | color: #999; 63 | display: inline-block; 64 | } 65 | 66 | /* -- option -- */ 67 | .relect-option { 68 | position: absolute; 69 | left: 0; 70 | margin: 0; 71 | padding: 0; 72 | z-index: 10; 73 | list-style: none; 74 | overflow-y: auto; 75 | border: 1px solid #D9D9D9; 76 | border-radius: 0 0 2px 2px; 77 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); 78 | -webkit-animation: slide-enter .2s both cubic-bezier(.8, 0, 0, 1); 79 | animation: slide-enter .2s both cubic-bezier(.8, 0, 0, 1); 80 | } 81 | .relect-option li { 82 | padding: 0 10px; 83 | } 84 | .relect-option::-webkit-scrollbar { 85 | width: 8px; 86 | border-left: 1px solid rgba(0, 0, 0, .1); 87 | } 88 | .relect-option::-webkit-scrollbar-thumb { 89 | border-radius: 8px; 90 | background: rgba(0, 0, 0, .1); 91 | } 92 | .relect-option::-webkit-scrollbar-thumb:hover { 93 | background: rgba(0, 0, 0, .2); 94 | } 95 | .relect-focused-option { 96 | color: #fff; 97 | background: #29B6F6; 98 | border-radius: 2px; 99 | box-shadow: 0 0 1px rgba(0,0,0,.14); 100 | } 101 | @-webkit-keyframes slide-enter { 102 | from { 103 | opacity: 0; 104 | -webkit-transform: scaleY(0); 105 | transform: scaleY(0); 106 | } 107 | from, to { 108 | -webkit-transform-origin: top; 109 | transform-origin: top; 110 | } 111 | to { 112 | opacity: 1; 113 | -webkit-transform: scaleY(1); 114 | transform: scaleY(1); 115 | } 116 | } 117 | @keyframes slide-enter { 118 | from { 119 | opacity: 0; 120 | -webkit-transform: scaleY(0); 121 | transform: scaleY(0); 122 | } 123 | from, to { 124 | -webkit-transform-origin: top; 125 | transform-origin: top; 126 | } 127 | to { 128 | opacity: 1; 129 | -webkit-transform: scaleY(1); 130 | transform: scaleY(1); 131 | } 132 | } 133 | 134 | /* -- arrow -- */ 135 | .relect-arrow { 136 | position: absolute; 137 | top: 50%; 138 | right: 12px; 139 | width: 0; 140 | height: 0; 141 | z-index: 1; 142 | font-size: 0; 143 | overflow: hidden; 144 | margin-top: -3px; 145 | border: solid 6px; 146 | border-color: #B9B9B9 transparent transparent; 147 | } 148 | .relect-arrow:hover { 149 | border-top-color: #A0A0A0; 150 | } 151 | .relect-box-disabled .relect-arrow:hover { 152 | border-top-color: #B9B9B9; 153 | } 154 | 155 | /* -- clear -- */ 156 | .relect-clear { 157 | position: absolute; 158 | cursor: pointer; 159 | top: 50%; 160 | right: 32px; 161 | width: 12px; 162 | height: 12px; 163 | margin-top: -6px; 164 | } 165 | .relect-clear:before, 166 | .relect-clear:after { 167 | position: absolute; 168 | content: ''; 169 | top: 50%; 170 | left: 0; 171 | width: 100%; 172 | height: 2px; 173 | margin-top: -1px; 174 | border-radius: 100%; 175 | background: #B9B9B9; 176 | -webkit-transition: background .2s; 177 | transition: background .2s; 178 | } 179 | .relect-clear:before { 180 | -webkit-transform: rotate(45deg); 181 | transform: rotate(45deg); 182 | } 183 | .relect-clear:after { 184 | -webkit-transform: rotate(-45deg); 185 | transform: rotate(-45deg); 186 | } 187 | .relect-clear:hover:before, 188 | .relect-clear:hover:after { 189 | background: #FF7D7D; 190 | } 191 | -------------------------------------------------------------------------------- /src/relect.css: -------------------------------------------------------------------------------- 1 | /* -- relect container -- */ 2 | .relect { 3 | cursor: pointer; 4 | text-align: left; 5 | position: relative; 6 | display: inline-block; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | } 12 | .relect:hover .relect-box { 13 | border-color: #B9B9B9; 14 | box-shadow: 0 0 2px rgba(0, 0, 0, .14); 15 | } 16 | .relect:hover .relect-option { 17 | border-top-color: #B9B9B9; 18 | } 19 | .relect:focus { 20 | outline: none; 21 | } 22 | .relect:focus .relect-box { 23 | border-color: #29B6F6; 24 | box-shadow: 0 0 2px rgba(41, 182, 246, .3); 25 | } 26 | .relect:focus .relect-option { 27 | border-top-color: #29B6F6; 28 | } 29 | .relect:hover .relect-box-disabled, 30 | .relect:focus .relect-box-disabled { 31 | box-shadow: none; 32 | border-color: #D9D9D9; 33 | } 34 | 35 | /* -- box && option -- */ 36 | .relect-box, 37 | .relect-option { 38 | width: inherit; 39 | background: #fff; 40 | -webkit-box-sizing: border-box; 41 | -moz-box-sizing: border-box; 42 | box-sizing: border-box; 43 | } 44 | 45 | /* -- box -- */ 46 | .relect-box { 47 | padding: 0 10px; 48 | border-radius: 2px; 49 | display: inline-block; 50 | border: 1px solid #D9D9D9; 51 | box-shadow: 0 0 1px rgba(0, 0, 0, .14); 52 | -webkit-transition: border-color .2s, box-shadow .2s; 53 | transition: border-color .2s, box-shadow .2s; 54 | } 55 | .relect-box-disabled { 56 | color: #AAA; 57 | cursor: default; 58 | background: #f8f8f8; 59 | box-shadow: none; 60 | } 61 | .relect-placeholder { 62 | color: #999; 63 | display: inline-block; 64 | } 65 | 66 | /* -- option -- */ 67 | .relect-option { 68 | position: absolute; 69 | left: 0; 70 | margin: 0; 71 | padding: 0; 72 | z-index: 10; 73 | list-style: none; 74 | overflow-y: auto; 75 | border: 1px solid #D9D9D9; 76 | border-radius: 0 0 2px 2px; 77 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); 78 | -webkit-animation: slide-enter .2s both cubic-bezier(.8, 0, 0, 1); 79 | animation: slide-enter .2s both cubic-bezier(.8, 0, 0, 1); 80 | } 81 | .relect-option li { 82 | padding: 0 10px; 83 | } 84 | .relect-option::-webkit-scrollbar { 85 | width: 8px; 86 | border-left: 1px solid rgba(0, 0, 0, .1); 87 | } 88 | .relect-option::-webkit-scrollbar-thumb { 89 | border-radius: 8px; 90 | background: rgba(0, 0, 0, .1); 91 | } 92 | .relect-option::-webkit-scrollbar-thumb:hover { 93 | background: rgba(0, 0, 0, .2); 94 | } 95 | .relect-focused-option { 96 | color: #fff; 97 | background: #29B6F6; 98 | border-radius: 2px; 99 | box-shadow: 0 0 1px rgba(0,0,0,.14); 100 | } 101 | @-webkit-keyframes slide-enter { 102 | from { 103 | opacity: 0; 104 | -webkit-transform: scaleY(0); 105 | transform: scaleY(0); 106 | } 107 | from, to { 108 | -webkit-transform-origin: top; 109 | transform-origin: top; 110 | } 111 | to { 112 | opacity: 1; 113 | -webkit-transform: scaleY(1); 114 | transform: scaleY(1); 115 | } 116 | } 117 | @keyframes slide-enter { 118 | from { 119 | opacity: 0; 120 | -webkit-transform: scaleY(0); 121 | transform: scaleY(0); 122 | } 123 | from, to { 124 | -webkit-transform-origin: top; 125 | transform-origin: top; 126 | } 127 | to { 128 | opacity: 1; 129 | -webkit-transform: scaleY(1); 130 | transform: scaleY(1); 131 | } 132 | } 133 | 134 | /* -- arrow -- */ 135 | .relect-arrow { 136 | position: absolute; 137 | top: 50%; 138 | right: 12px; 139 | width: 0; 140 | height: 0; 141 | z-index: 1; 142 | font-size: 0; 143 | overflow: hidden; 144 | margin-top: -3px; 145 | border: solid 6px; 146 | border-color: #B9B9B9 transparent transparent; 147 | } 148 | .relect-arrow:hover { 149 | border-top-color: #A0A0A0; 150 | } 151 | .relect-box-disabled .relect-arrow:hover { 152 | border-top-color: #B9B9B9; 153 | } 154 | 155 | /* -- clear -- */ 156 | .relect-clear { 157 | position: absolute; 158 | cursor: pointer; 159 | top: 50%; 160 | right: 32px; 161 | width: 12px; 162 | height: 12px; 163 | margin-top: -6px; 164 | } 165 | .relect-clear:before, 166 | .relect-clear:after { 167 | position: absolute; 168 | content: ''; 169 | top: 50%; 170 | left: 0; 171 | width: 100%; 172 | height: 2px; 173 | margin-top: -1px; 174 | border-radius: 100%; 175 | background: #B9B9B9; 176 | -webkit-transition: background .2s; 177 | transition: background .2s; 178 | } 179 | .relect-clear:before { 180 | -webkit-transform: rotate(45deg); 181 | transform: rotate(45deg); 182 | } 183 | .relect-clear:after { 184 | -webkit-transform: rotate(-45deg); 185 | transform: rotate(-45deg); 186 | } 187 | .relect-clear:hover:before, 188 | .relect-clear:hover:after { 189 | background: #FF7D7D; 190 | } 191 | -------------------------------------------------------------------------------- /src/Relect.js: -------------------------------------------------------------------------------- 1 | /* =============================== 2 | * Relect v1.1.0 3 | * https://github.com/chenjiahan/relect 4 | * =============================== */ 5 | 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | import PropTypes from 'prop-types'; 9 | import Menu from './Menu'; 10 | import Box from './Box'; 11 | 12 | class Relect extends React.Component { 13 | 14 | static propTypes = { 15 | width : PropTypes.number, 16 | height : PropTypes.number, 17 | chosen : PropTypes.any, 18 | options : PropTypes.array, 19 | tabIndex : PropTypes.number, 20 | autoBlur : PropTypes.bool, 21 | disabled : PropTypes.bool, 22 | placeholder : PropTypes.string, 23 | optionHeight : PropTypes.number 24 | }; 25 | 26 | static defaultProps = { 27 | width : 240, 28 | height : 36, 29 | options : [], 30 | tabIndex : -1, 31 | autoBlur : false, 32 | disabled : false, 33 | placeholder : '', 34 | optionHeight : 30 35 | }; 36 | 37 | state = { 38 | focused : null, // index of focused option 39 | showMenu : false // whether show option 40 | }; 41 | 42 | toggleMenu = () => { 43 | this.setState({ showMenu: !this.state.showMenu }); 44 | }; 45 | 46 | onChoose = index => { 47 | this.props.onChange(index); 48 | this.setState({ showMenu : false }); 49 | 50 | if (this.props.autoBlur) { 51 | this.relectDOM && this.relectDOM.blur(); 52 | } 53 | }; 54 | 55 | onClear = e => { 56 | e.stopPropagation(); 57 | this.setState({ showMenu : false }); 58 | this.props.onChange(null); 59 | }; 60 | 61 | onBlur = (e) => { 62 | if (e.target === e.currentTarget) { 63 | this.setState({ showMenu : false }) 64 | } 65 | }; 66 | 67 | onKeyDown = e => { 68 | switch (e.which) { 69 | case 8 : // Delete 70 | this.onClear(e); 71 | break; 72 | case 27: // Esc 73 | this.setState({ showMenu : false }); 74 | break; 75 | case 13: // Enter 76 | case 32: // Space 77 | if (this.state.showMenu && this.state.focused !== null) { 78 | this.onChoose(this.state.focused); 79 | } else { 80 | this.toggleMenu(); 81 | } 82 | break; 83 | case 38: // Up 84 | this.moveFocusedOption(-1); 85 | break; 86 | case 40: // Down 87 | this.moveFocusedOption(1); 88 | break; 89 | default: 90 | return; 91 | } 92 | e.preventDefault(); 93 | }; 94 | 95 | moveFocusedOption = move => { 96 | if (!this.state.showMenu) { 97 | this.setState({ showMenu : true }); 98 | return; 99 | } 100 | 101 | let { focused } = this.state; 102 | let { length } = this.props.options; 103 | focused = focused === null ? 0 : (focused + move + length) % length; 104 | this.focusOption(focused); 105 | }; 106 | 107 | focusOption = focused => { 108 | this.setState({ focused }); 109 | 110 | // calc offset 111 | // displays up to 8 options in the same time 112 | let length = this.props.options.length; 113 | if (length > 8) { 114 | let height = this.props.optionHeight; 115 | let current = this.menuDOM.scrollTop; 116 | let max = Math.min((length - 8) * height, focused * height); 117 | let min = Math.max(0, (focused - 7) * height); 118 | 119 | if (current > max) { 120 | this.menuDOM.scrollTop = max; 121 | } else if (current < min) { 122 | this.menuDOM.scrollTop = min; 123 | } 124 | } 125 | }; 126 | 127 | render() { 128 | const { props } = this; 129 | const { showMenu, focused } = this.state; 130 | const style = { 131 | width : props.width, 132 | lineHeight : props.height - 2 + 'px' 133 | }; 134 | 135 | return ( 136 |
    { this.relectDOM = node; }} 143 | > 144 | 149 | { this.menuDOM = node; }} 155 | /> 156 |
    157 | ) 158 | } 159 | } 160 | 161 | export default Relect; 162 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports=function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="lib/",t(t.s=1)}([function(e,t){e.exports=require("react")},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var i=Object.assign||function(e){for(var t=1;t8){var n=o.props.optionHeight,r=o.menuDOM.scrollTop,u=Math.min((t-8)*n,e*n),a=Math.max(0,(e-7)*n);r>u?o.menuDOM.scrollTop=u:r