From 988ca3006dd833e102b94c9cb63adef506edadc9 Mon Sep 17 00:00:00 2001 From: Tom Stockwell <2060486+stocky37@users.noreply.github.com> Date: Wed, 13 Mar 2019 23:02:27 +1100 Subject: [PATCH] Implement auto-complete for hero select (#94) * Added naive impl of mui-downshift for hero select * Implemented styling for search to match in toolbar * Fixed bug when clearing hero autocomplete * Fixed default selected hero displaying correctly * Moved generic downshift code to standard AutoComplete component * Removed a lost console log * Added noop scrollIntoView to fix AutoComplete scrolling bug * Filter items if there is a default * Fixed broken noop test o.O * Temporary downgrade of nodejs to fix bug in 11.11 See https://github.com/facebook/jest/issues/8069 --- .travis.yml | 2 +- package.json | 1 + src/core/components/App/App.test.js | 4 -- .../components/AutoComplete/AutoComplete.js | 50 +++++++++++++++++++ src/core/components/AutoComplete/index.js | 3 ++ .../components/NewHeroSelect/index.js | 12 +++++ .../components/Toolbar/Toolbar.js | 34 ++++++++++--- src/gear-preview/components/Toolbar/index.js | 17 +++++-- .../selectors/getCompleteGearSets.js | 1 - yarn.lock | 41 +++++++++++++-- 10 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 src/core/components/AutoComplete/AutoComplete.js create mode 100644 src/core/components/AutoComplete/index.js create mode 100644 src/gear-preview/components/NewHeroSelect/index.js diff --git a/.travis.yml b/.travis.yml index bf990a8..53ffc7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "node" + - "11.10.1" cache: yarn: true directories: diff --git a/package.json b/package.json index d0b9680..7b3683b 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "classnames": "^2.2.6", "lodash.isempty": "^4.4.0", "lodash.merge": "^4.6.1", + "mui-downshift": "^1.4.1", "normalizr": "^3.3.0", "prop-types": "^15.6.2", "react": "^16.7.0", diff --git a/src/core/components/App/App.test.js b/src/core/components/App/App.test.js index 7136992..7a30eaa 100644 --- a/src/core/components/App/App.test.js +++ b/src/core/components/App/App.test.js @@ -1,5 +1 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; - it('renders without crashing', () => {}); diff --git a/src/core/components/AutoComplete/AutoComplete.js b/src/core/components/AutoComplete/AutoComplete.js new file mode 100644 index 0000000..f37a8f0 --- /dev/null +++ b/src/core/components/AutoComplete/AutoComplete.js @@ -0,0 +1,50 @@ +import React from 'react'; +import {Component} from 'react'; +import MuiDownshift from 'mui-downshift'; + +class AutoComplete extends Component { + static defaultProps = { + items: [], + }; + + constructor(props) { + super(props); + const {defaultSelectedItem} = props; + this.state = { + filteredItems: defaultSelectedItem + ? this.filterItems(defaultSelectedItem.label) + : this.props.items, + }; + } + + filterItems = filter => + this.props.items.filter(item => item.label.toLowerCase().includes(filter.toLowerCase())); + + handleStateChange = changes => { + if (typeof changes.inputValue === 'string') { + this.setState({filteredItems: this.filterItems(changes.inputValue)}); + } + if (this.input && this.props.blurOnSelect) { + this.input.blur(); + } + }; + + render() { + const {items, ...props} = this.props; + const {filteredItems} = this.state; + return ( + {}} // workaround for bug causing entire page to scroll + items={filteredItems} + onStateChange={this.handleStateChange} + getListItemKey={index => filteredItems[index].value} + {...props} + inputRef={node => { + this.input = node; + }} + /> + ); + } +} + +export default AutoComplete; diff --git a/src/core/components/AutoComplete/index.js b/src/core/components/AutoComplete/index.js new file mode 100644 index 0000000..fce6376 --- /dev/null +++ b/src/core/components/AutoComplete/index.js @@ -0,0 +1,3 @@ +import AutoComplete from './AutoComplete'; + +export default AutoComplete; diff --git a/src/gear-preview/components/NewHeroSelect/index.js b/src/gear-preview/components/NewHeroSelect/index.js new file mode 100644 index 0000000..c89f169 --- /dev/null +++ b/src/gear-preview/components/NewHeroSelect/index.js @@ -0,0 +1,12 @@ +import {connect} from 'react-redux'; +import AutoComplete from '../../../core/components/AutoComplete'; +import getHeroes from '../../../core/selectors/getHeroes'; + +const mapState = state => ({ + items: getHeroes(state).map(hero => ({ + label: hero.name, + value: hero._id, + })), +}); + +export default connect(mapState)(AutoComplete); diff --git a/src/gear-preview/components/Toolbar/Toolbar.js b/src/gear-preview/components/Toolbar/Toolbar.js index a2f0b40..2b99cb7 100644 --- a/src/gear-preview/components/Toolbar/Toolbar.js +++ b/src/gear-preview/components/Toolbar/Toolbar.js @@ -4,23 +4,32 @@ import withStyles from '@material-ui/core/styles/withStyles'; import MuiToolbar from '@material-ui/core/Toolbar'; import classNames from 'classnames'; import React from 'react'; -import HeroSelect from '../../../core/components/HeroSelect'; import AwakeningSelect from '../AwakeningSelect'; import ClearSnapshotButton from '../ClearSnapshotButton'; import LevelSelect from '../LevelSelect'; +import NewHeroSelect from '../NewHeroSelect'; import SnapshotButton from '../SnapshotButton'; const style = theme => ({ root: { background: theme.palette.primary, + zIndex: theme.zIndex.appBar - 1, }, toolbar: { display: 'flex', + padding: 0, }, heroSelect: { flexGrow: 1, - fontSize: '1.25em', + fontSize: theme.typography.pxToRem(16), marginRight: theme.spacing.unit * 2, + minHeight: 64, + }, + input: { + minHeight: 64, + padding: 0, + fontSize: 'inherit', + paddingLeft: theme.spacing.unit * 2, }, divider: { width: 1, @@ -36,20 +45,29 @@ const style = theme => ({ }, }); -const handleHeroChange = onHeroChange => event => { +const handleHeroChange = onHeroChange => change => { if (onHeroChange) { - onHeroChange(event.target.value); + onHeroChange(change ? change.value : null); } }; const Toolbar = ({className, classes, onHeroChange, hero, totalStats, ...props}) => ( - ({ + placeholder: 'Select a hero', + disableUnderline: true, + fullWidth: true, + classes: { + input: classes.input, + }, + })} + getRootProps={() => ({ + className: classes.heroSelect, + })} /> diff --git a/src/gear-preview/components/Toolbar/index.js b/src/gear-preview/components/Toolbar/index.js index cc07185..5c45755 100644 --- a/src/gear-preview/components/Toolbar/index.js +++ b/src/gear-preview/components/Toolbar/index.js @@ -2,13 +2,22 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import selectHero from '../../actions/selectHero'; import getHeroTotalStats from '../../selectors/getHeroTotalStats'; -import getSelectedHeroId from '../../selectors/getSelectedHeroId'; +import getSelectedHero from '../../selectors/getSelectedHero'; import Toolbar from './Toolbar'; const mapState = state => { - const hero = getSelectedHeroId(state); - const totalStats = hero ? getHeroTotalStats(state) : {}; - return {hero, totalStats}; + const hero = getSelectedHero(state); + if (!hero) { + return {hero: null, totalStats: {}}; + } + + return { + hero: { + value: hero.id, + label: hero.name, + }, + totalStats: getHeroTotalStats(state), + }; }; const mapDispatch = dispatch => bindActionCreators({onHeroChange: selectHero}, dispatch); diff --git a/src/gear-preview/selectors/getCompleteGearSets.js b/src/gear-preview/selectors/getCompleteGearSets.js index 9491cc9..40ec534 100644 --- a/src/gear-preview/selectors/getCompleteGearSets.js +++ b/src/gear-preview/selectors/getCompleteGearSets.js @@ -27,7 +27,6 @@ export default createSelector( } }); - console.log(completedSets); return completedSets; } ); diff --git a/yarn.lock b/yarn.lock index 9d60c86..551aade 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2287,7 +2287,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5, classnames@^2.2.6: +classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -2465,6 +2465,11 @@ compression@^1.5.2: safe-buffer "5.1.2" vary "~1.1.2" +compute-scroll-into-view@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.11.tgz#7ff0a57f9aeda6314132d8994cce7aeca794fecf" + integrity sha512-uUnglJowSe0IPmWOdDtrlHXof5CTIJitfJEyITHBW6zDVOGu9Pjk5puaLM73SLcwak0L4hEjO7Td88/a6P5i7A== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -3169,7 +3174,7 @@ dom-converter@~0.2: dependencies: utila "~0.4" -dom-helpers@^3.2.1, dom-helpers@^3.3.1: +"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.2.1, dom-helpers@^3.3.1: version "3.4.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== @@ -3248,6 +3253,14 @@ dotenv@6.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.0.0.tgz#24e37c041741c5f4b25324958ebbc34bca965935" integrity sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg== +downshift@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/downshift/-/downshift-2.2.3.tgz#85187568455134e72025fbddd40bb9cf96c55eed" + integrity sha512-SXFgGq5QYT9mxbaSsYdp4Ng0tP87F5z33PD+tZ2kyK0qIBYd1rcPe90+ykCOYqsWHsb/gcrjaAav2Jpa6qNbQg== + dependencies: + compute-scroll-into-view "^1.0.2" + prop-types "^15.6.0" + duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -6118,7 +6131,7 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6452,6 +6465,16 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +mui-downshift@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mui-downshift/-/mui-downshift-1.4.1.tgz#281cf75f8c11c5d201407580fefe39a6c43df5c3" + integrity sha512-8t0ZHbz1BvEiz2j2HPQD1DLHsSWdwAX0z36y+8nDSNXfWbzgKl1oIGNg8lywlARD8B2hXOYWHuv8rKAw4Xp37w== + dependencies: + classnames "^2.2.6" + downshift "^2.2.0" + prop-types "^15.7.2" + react-virtualized "^9.20.1" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -8272,6 +8295,18 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-virtualized@^9.20.1: + version "9.21.0" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.0.tgz#8267c40ffb48db35b242a36dea85edcf280a6506" + integrity sha512-duKD2HvO33mqld4EtQKm9H9H0p+xce1c++2D5xn59Ma7P8VT7CprfAe5hwjd1OGkyhqzOZiTMlTal7LxjH5yBQ== + dependencies: + babel-runtime "^6.26.0" + classnames "^2.2.3" + dom-helpers "^2.4.0 || ^3.0.0" + loose-envify "^1.3.0" + prop-types "^15.6.0" + react-lifecycles-compat "^3.0.4" + react@^16.7.0: version "16.8.2" resolved "https://registry.yarnpkg.com/react/-/react-16.8.2.tgz#83064596feaa98d9c2857c4deae1848b542c9c0c"