Search improvements (#76)

* Search improvements

* Search results keyboard navigation
This commit is contained in:
Tom Moor
2017-05-31 20:23:09 -07:00
committed by GitHub
parent d853821186
commit 74d65aba67
9 changed files with 156 additions and 105 deletions

View File

@@ -1,13 +1,15 @@
// @flow
import React from 'react';
import ReactDOM from 'react-dom';
import { observer } from 'mobx-react';
import _ from 'lodash';
import { Flex } from 'reflexbox';
import { withRouter } from 'react-router';
import { searchUrl } from 'utils/routeHelpers';
import styled from 'styled-components';
import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
import SearchField from './components/SearchField';
import styles from './Search.scss';
import SearchStore from './SearchStore';
import Layout, { Title } from 'components/Layout';
@@ -21,7 +23,20 @@ type Props = {
notFound: ?boolean,
};
const Container = styled(CenteredContent)`
position: relative;
`;
const ResultsWrapper = styled(Flex)`
position: absolute;
transition: all 200ms ease-in-out;
top: ${props => (props.pinToTop ? '0%' : '50%')};
margin-top: ${props => (props.pinToTop ? '40px' : '-75px')};
width: 100%;
`;
@observer class Search extends React.Component {
firstDocument: HTMLElement;
props: Props;
store: SearchStore;
@@ -38,10 +53,20 @@ type Props = {
}
handleKeyDown = ev => {
// ESC
if (ev.which === 27) {
ev.preventDefault();
this.props.history.goBack();
}
// Down
if (ev.which === 40) {
ev.preventDefault();
if (this.firstDocument) {
const element = ReactDOM.findDOMNode(this.firstDocument);
// $FlowFixMe
if (element && element.focus) element.focus();
}
}
};
updateSearchResults = _.debounce(() => {
@@ -52,40 +77,46 @@ type Props = {
this.props.history.replace(searchUrl(query));
};
setFirstDocumentRef = ref => {
this.firstDocument = ref;
};
render() {
const query = this.props.match.params.query;
const title = <Title content="Search" />;
const hasResults = this.store.documents.length > 0;
return (
<Layout title={title} search={false} loading={this.store.isFetching}>
<PageTitle title="Search" />
<CenteredContent>
<Container auto>
{this.props.notFound &&
<div>
<h1>Not Found</h1>
<p>We're unable to find the page you're accessing.</p>
<hr />
</div>}
<Flex column auto>
<Flex auto>
<img
src={require('assets/icons/search.svg')}
className={styles.icon}
alt="Search"
/>
<SearchField
searchTerm={this.store.searchTerm}
onKeyDown={this.handleKeyDown}
onChange={this.updateQuery}
value={query}
/>
</Flex>
{this.store.documents.map(document => (
<DocumentPreview key={document.id} document={document} />
))}
</Flex>
</CenteredContent>
<ResultsWrapper pinToTop={hasResults} column auto>
<SearchField
searchTerm={this.store.searchTerm}
onKeyDown={this.handleKeyDown}
onChange={this.updateQuery}
value={query || ''}
/>
<ArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0}
>
{this.store.documents.map((document, index) => (
<DocumentPreview
innerRef={ref => index === 0 && this.setFirstDocumentRef(ref)}
key={document.id}
document={document}
/>
))}
</ArrowKeyNavigation>
</ResultsWrapper>
</Container>
</Layout>
);
}

View File

@@ -1,6 +0,0 @@
.icon {
width: 38px;
margin-bottom: -5px;
margin-right: 10px;
opacity: 0.15;
}

View File

@@ -1,8 +1,32 @@
// @flow
import React, { Component } from 'react';
import styles from './SearchField.scss';
import { Flex } from 'reflexbox';
import styled from 'styled-components';
import searchImg from 'assets/icons/search.svg';
const Field = styled.input`
width: 100%;
padding: 10px;
font-size: 48px;
font-weight: 400;
outline: none;
border: 0;
::-webkit-input-placeholder { color: #ccc; }
:-moz-placeholder { color: #ccc; }
::-moz-placeholder { color: #ccc; }
:-ms-input-placeholder { color: #ccc; }
`;
const Icon = styled.img`
width: 38px;
margin-bottom: -5px;
margin-right: 10px;
opacity: 0.15;
`;
class SearchField extends Component {
input: HTMLElement;
props: {
onChange: Function,
};
@@ -11,17 +35,26 @@ class SearchField extends Component {
this.props.onChange(ev.currentTarget.value ? ev.currentTarget.value : '');
};
focusInput = (ev: SyntheticEvent) => {
this.input.focus();
};
setRef = (ref: HTMLElement) => {
this.input = ref;
};
render() {
return (
<div className={styles.container}>
<input
<Flex>
<Icon src={searchImg} alt="Search" onClick={this.focusInput} />
<Field
{...this.props}
innerRef={this.setRef}
onChange={this.handleChange}
className={styles.field}
placeholder="Search"
autoFocus
/>
</div>
</Flex>
);
}
}

View File

@@ -1,31 +0,0 @@
.container {
padding: 40px 0;
}
.field {
width: 100%;
padding: 10px;
font-size: 48px;
font-weight: 400;
outline: none;
border: 0;
// border-bottom: 1px solid #ccc;
}
:global {
::-webkit-input-placeholder {
color: #ccc;
}
:-moz-placeholder {
color: #ccc;
}
::-moz-placeholder {
color: #ccc;
}
:-ms-input-placeholder {
color: #ccc;
}
}