From 503ab5c66ab524dfe36aed84a01899cd07ed2bc5 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 13 Dec 2017 15:53:19 +0100 Subject: Allow selection of result using keyboard --- src/client/react/components/container/Results.jsx | 15 +++++++++--- src/client/react/components/container/Search.jsx | 20 +++++++++++++++- .../react/components/presentational/Result.jsx | 10 ++++++-- src/client/react/reducers/search.js | 28 ++++++++++++++++++++++ src/client/style/index.scss | 4 ++++ 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/client/react/components/container/Results.jsx b/src/client/react/components/container/Results.jsx index 1c38c8b..9be2639 100644 --- a/src/client/react/components/container/Results.jsx +++ b/src/client/react/components/container/Results.jsx @@ -4,14 +4,14 @@ import { connect } from 'react-redux'; import classnames from 'classnames'; import Result from '../presentational/Result'; -const Results = (({ results }) => ( +const Results = (({ results, selectedResult }) => (
0, })} > {results.map(user => ( - + ))}
)); @@ -19,12 +19,21 @@ const Results = (({ results }) => ( Results.propTypes = { results: PropTypes.arrayOf(PropTypes.shape({ type: PropTypes.string, - value: PropTypes.value, + value: PropTypes.string, })).isRequired, + selectedResult: PropTypes.shape({ + type: PropTypes.string, + value: PropTypes.string, + }), +}; + +Results.defaultProps = { + selectedResult: null, }; const mapStateToProps = state => ({ results: state.search.results, + selectedResult: state.search.selectedResult, }); export default connect(mapStateToProps)(Results); diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index 7e33e84..50917dd 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -5,7 +5,7 @@ import classnames from 'classnames'; import SearchIcon from 'react-icons/lib/md/search'; -import { inputChange } from '../../actions/search'; +import { inputChange, changeSelectedResult } from '../../actions/search'; import Results from './Results'; import IconFromUserType from '../presentational/IconFromUserType'; @@ -25,6 +25,7 @@ class Search extends React.Component { this.onFocus = this.onFocus.bind(this); this.onBlur = this.onBlur.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); } onFocus() { @@ -39,6 +40,22 @@ class Search extends React.Component { }); } + onKeyDown(event) { + if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + event.preventDefault(); + switch (event.key) { + case 'ArrowUp': + this.props.dispatch(changeSelectedResult(-1)); + break; + case 'ArrowDown': + this.props.dispatch(changeSelectedResult(+1)); + break; + default: + throw new Error('This should never happen... pls?'); + } + } + } + render() { const { value, @@ -63,6 +80,7 @@ class Search extends React.Component { dispatch(inputChange(event.target.value))} + onKeyDown={this.onKeyDown} value={value} placeholder="Zoeken" onFocus={this.onFocus} diff --git a/src/client/react/components/presentational/Result.jsx b/src/client/react/components/presentational/Result.jsx index a4a0b8e..80f65d4 100644 --- a/src/client/react/components/presentational/Result.jsx +++ b/src/client/react/components/presentational/Result.jsx @@ -1,10 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; import IconFromUserType from './IconFromUserType'; -const Result = ({ user }) => ( -
+const Result = ({ user, selected }) => ( +
{user.value}
@@ -15,6 +20,7 @@ Result.propTypes = { value: PropTypes.string.isRequired, type: PropTypes.string.isRequired, }).isRequired, + selected: PropTypes.bool.isRequired, }; export default Result; diff --git a/src/client/react/reducers/search.js b/src/client/react/reducers/search.js index f566b49..6ef0f4d 100644 --- a/src/client/react/reducers/search.js +++ b/src/client/react/reducers/search.js @@ -48,6 +48,34 @@ const search = (state = DEFAULT_STATE, action) => { isExactMatch, }; } + + case 'SEARCH/CHANGE_SELECTED_RESULT': { + const { results, isExactMatch } = state; + const prevSelectedResult = state.selectedResult; + const prevSelectedResultIndex = results.indexOf(prevSelectedResult); + let nextSelectedResultIndex = + prevSelectedResultIndex + action.relativeChange; + + if (nextSelectedResultIndex < -1) { + nextSelectedResultIndex = results.length - 1; + } else if (nextSelectedResultIndex > results.length - 1) { + nextSelectedResultIndex = -1; + } + + let nextSelectedResult = + nextSelectedResultIndex === -1 + ? null + : results[nextSelectedResultIndex]; + + if (isExactMatch) { + nextSelectedResult = prevSelectedResult; + } + + return { + ...state, + selectedResult: nextSelectedResult, + }; + } default: return state; } diff --git a/src/client/style/index.scss b/src/client/style/index.scss index 8e1b4d0..7c83c77 100644 --- a/src/client/style/index.scss +++ b/src/client/style/index.scss @@ -48,6 +48,10 @@ body { &__result { display: flex; + &--selected { + background-color: lightgray; + } + &__text { padding: 15px; padding-left: 0px; -- cgit v1.1