diff options
| author | Noah Loomans <noahloomans@gmail.com> | 2018-06-28 16:08:57 +0200 | 
|---|---|---|
| committer | Noah Loomans <noahloomans@gmail.com> | 2018-06-28 16:08:57 +0200 | 
| commit | 823aa5ad43126747cb071a0c347b200cc84df714 (patch) | |
| tree | 5705a3dfc8cc3a884d17cd177dc2515cf9e37fd6 /src/client/react/components/presentational | |
| parent | 41620ceb096a4c3d94bb83cf9a56077939d89a2c (diff) | |
Split search
Diffstat (limited to 'src/client/react/components/presentational')
| -rw-r--r-- | src/client/react/components/presentational/Search.js | 158 | ||||
| -rw-r--r-- | src/client/react/components/presentational/Search.scss | 42 | 
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; +      } +    } +  } +}  | 
