aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNoah Loomans <noahloomans@gmail.com>2017-12-13 15:53:19 +0100
committerNoah Loomans <noahloomans@gmail.com>2017-12-13 15:53:19 +0100
commit503ab5c66ab524dfe36aed84a01899cd07ed2bc5 (patch)
tree68f3461d561ddc6a8e83898916aba06c193e6ffa
parentfe27a0819a60caaa69b059f0c86d95ab0c4084b7 (diff)
Allow selection of result using keyboard
-rw-r--r--src/client/react/components/container/Results.jsx15
-rw-r--r--src/client/react/components/container/Search.jsx20
-rw-r--r--src/client/react/components/presentational/Result.jsx10
-rw-r--r--src/client/react/reducers/search.js28
-rw-r--r--src/client/style/index.scss4
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 }) => (
<div
className={classnames('search__results', {
'search__results--has-results': results.length > 0,
})}
>
{results.map(user => (
- <Result key={user.value} user={user} />
+ <Result key={user.value} user={user} selected={user === selectedResult} />
))}
</div>
));
@@ -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 {
<input
id="search__input"
onChange={event => 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 }) => (
- <div className="search__result">
+const Result = ({ user, selected }) => (
+ <div
+ className={classnames('search__result', {
+ 'search__result--selected': selected,
+ })}
+ >
<div className="search__icon-wrapper"><IconFromUserType userType={user.type} /></div>
<div className="search__result__text">{user.value}</div>
</div>
@@ -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;