aboutsummaryrefslogtreecommitdiff
path: root/src/client/react/components/presentational
diff options
context:
space:
mode:
authorNoah Loomans <noahloomans@gmail.com>2018-06-28 16:08:57 +0200
committerNoah Loomans <noahloomans@gmail.com>2018-06-28 16:08:57 +0200
commit823aa5ad43126747cb071a0c347b200cc84df714 (patch)
tree5705a3dfc8cc3a884d17cd177dc2515cf9e37fd6 /src/client/react/components/presentational
parent41620ceb096a4c3d94bb83cf9a56077939d89a2c (diff)
Split search
Diffstat (limited to 'src/client/react/components/presentational')
-rw-r--r--src/client/react/components/presentational/Search.js158
-rw-r--r--src/client/react/components/presentational/Search.scss42
2 files changed, 200 insertions, 0 deletions
diff --git a/src/client/react/components/presentational/Search.js b/src/client/react/components/presentational/Search.js
new file mode 100644
index 0000000..8c90efb
--- /dev/null
+++ b/src/client/react/components/presentational/Search.js
@@ -0,0 +1,158 @@
+/**
+ * Copyright (C) 2018 Noah Loomans
+ *
+ * This file is part of rooster.hetmml.nl.
+ *
+ * rooster.hetmml.nl is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * rooster.hetmml.nl is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rooster.hetmml.nl. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import classnames from 'classnames';
+
+import SearchIcon from 'react-icons/lib/md/search';
+
+import { userFromMatch } from '../../lib/url';
+
+import users from '../../users';
+import Menu from '../container/Menu';
+import Results from '../container/Results';
+import IconFromUserType from './IconFromUserType';
+
+import './Search.scss';
+
+class Search extends React.Component {
+ static propTypes = {
+ selectedResult: PropTypes.string,
+ searchText: PropTypes.string.isRequired,
+ match: PropTypes.object.isRequired,
+ setUser: PropTypes.func.isRequired,
+ onInputChange: PropTypes.func.isRequired,
+ changeSelectedResult: PropTypes.func.isRequired,
+ };
+
+ static defaultProps = {
+ selectedResult: null,
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ hasFocus: false,
+ };
+
+ this.onFocus = this.onFocus.bind(this);
+ this.onBlur = this.onBlur.bind(this);
+ this.onKeyDown = this.onKeyDown.bind(this);
+ }
+
+ onFocus() {
+ this.setState({
+ hasFocus: true,
+ });
+ }
+
+ onBlur() {
+ this.setState({
+ hasFocus: false,
+ });
+ }
+
+ onKeyDown(event) {
+ const {
+ selectedResult,
+ match,
+ setUser,
+ changeSelectedResult,
+ } = this.props;
+
+ const urlUser = userFromMatch(match);
+
+ switch (event.key) {
+ case 'ArrowUp':
+ event.preventDefault();
+ changeSelectedResult(-1);
+ break;
+
+ case 'ArrowDown':
+ event.preventDefault();
+ changeSelectedResult(+1);
+ break;
+
+ case 'Escape':
+ event.preventDefault();
+ setUser(urlUser);
+ break;
+
+ case 'Enter':
+ event.preventDefault();
+ if (selectedResult) {
+ setUser(selectedResult);
+ }
+ break;
+
+ default:
+ // Do nothing
+ }
+ }
+
+ render() {
+ const {
+ searchText,
+ match,
+ onInputChange,
+ } = this.props;
+
+ const {
+ hasFocus,
+ } = this.state;
+
+ const urlUser = userFromMatch(match);
+
+ const isExactMatch = (
+ urlUser != null && searchText === users.byId[urlUser].value
+ );
+
+ return (
+ <div className="Search">
+ <div className={classnames('overflow', { hasFocus })}>
+ <div className="inputWrapper">
+ <div className="iconWrapper">
+ <IconFromUserType
+ userType={isExactMatch ? users.byId[urlUser].type : null}
+ defaultIcon={<SearchIcon />}
+ />
+ </div>
+ <input
+ id="searchInput"
+ onChange={event => onInputChange(event.target.value)}
+ onKeyDown={this.onKeyDown}
+ value={searchText}
+ placeholder="Zoeken"
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
+ autoComplete="off"
+ />
+ <Menu />
+ </div>
+ <Results />
+ </div>
+ </div>
+ );
+ }
+}
+
+export default Search;
diff --git a/src/client/react/components/presentational/Search.scss b/src/client/react/components/presentational/Search.scss
new file mode 100644
index 0000000..ef629c2
--- /dev/null
+++ b/src/client/react/components/presentational/Search.scss
@@ -0,0 +1,42 @@
+.Search {
+ height: 54px;
+ position: relative;
+
+ .overflow {
+ border-radius: 2px;
+ background-color: white;
+ position: absolute;
+ width: 100%;
+ box-shadow: 0 2px 2px 0 rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.08);
+
+ &.hasFocus {
+ box-shadow: 0 3px 8px 0 rgba(0,0,0,0.2), 0 0 0 1px rgba(0,0,0,0.08);
+ }
+
+ .inputWrapper {
+ display: flex;
+ height: 54px;
+
+ .iconWrapper {
+ height: 54px;
+ padding: 15px;
+
+ svg {
+ height: 24px;
+ width: 24px;
+ }
+ }
+
+ input {
+ border: 0;
+ background-color: transparent;
+ flex-grow: 1;
+ height: inherit;
+ padding: 16px;
+ padding-left: 0px;
+ font-size: 16px;
+ outline: none;
+ }
+ }
+ }
+}