Add reusable empty state

Add empty state for no starred documents
Add empty state for no search results
This commit is contained in:
Tom Moor
2017-10-17 23:21:22 -07:00
parent 0515a6233e
commit 05d1880556
6 changed files with 68 additions and 27 deletions

View File

@ -0,0 +1,21 @@
// @flow
import React from 'react';
import styled from 'styled-components';
import { color } from 'styles/constants';
type Props = {
children: string,
};
const Empty = (props: Props) => {
const { children, ...rest } = props;
return <Container {...rest}>{children}</Container>;
};
const Container = styled.div`
display: flex;
color: ${color.slate};
text-align: center;
`;
export default Empty;

View File

@ -0,0 +1,3 @@
// @flow
import Empty from './Empty';
export default Empty;

View File

@ -12,6 +12,7 @@ import { searchUrl } from 'utils/routeHelpers';
import styled from 'styled-components'; import styled from 'styled-components';
import ArrowKeyNavigation from 'boundless-arrow-key-navigation'; import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
import Empty from 'components/Empty';
import Flex from 'components/Flex'; import Flex from 'components/Flex';
import CenteredContent from 'components/CenteredContent'; import CenteredContent from 'components/CenteredContent';
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
@ -57,7 +58,7 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
firstDocument: HTMLElement; firstDocument: HTMLElement;
props: Props; props: Props;
@observable resultIds: Array<string> = []; // Document IDs @observable resultIds: string[] = []; // Document IDs
@observable searchTerm: ?string = null; @observable searchTerm: ?string = null;
@observable isFetching = false; @observable isFetching = false;
@ -131,18 +132,19 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
} }
render() { render() {
const { documents } = this.props; const { documents, notFound } = this.props;
const query = this.props.match.params.query; const query = this.props.match.params.query;
const hasResults = this.resultIds.length > 0; const hasResults = this.resultIds.length > 0;
const showEmpty = !this.isFetching && this.searchTerm && !hasResults;
return ( return (
<Container auto> <Container auto>
<PageTitle title={this.title} /> <PageTitle title={this.title} />
{this.isFetching && <LoadingIndicator />} {this.isFetching && <LoadingIndicator />}
{this.props.notFound && {notFound &&
<div> <div>
<h1>Not Found</h1> <h1>Not Found</h1>
<p>We're unable to find the page you're accessing.</p> <p>Were unable to find the page youre accessing.</p>
</div>} </div>}
<ResultsWrapper pinToTop={hasResults} column auto> <ResultsWrapper pinToTop={hasResults} column auto>
<SearchField <SearchField
@ -151,6 +153,7 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
onChange={this.updateQuery} onChange={this.updateQuery}
value={query || ''} value={query || ''}
/> />
{showEmpty && <Empty>Oop, no matching documents.</Empty>}
<ResultList visible={hasResults}> <ResultList visible={hasResults}>
<StyledArrowKeyNavigation <StyledArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL} mode={ArrowKeyNavigation.mode.VERTICAL}

View File

@ -5,22 +5,8 @@ import Flex from 'components/Flex';
import { color } from 'styles/constants'; import { color } from 'styles/constants';
import styled from 'styled-components'; import styled from 'styled-components';
const Field = styled.input`
width: 100%;
padding: 10px;
font-size: 48px;
font-weight: 400;
outline: none;
border: 0;
::-webkit-input-placeholder { color: ${color.slate}; }
:-moz-placeholder { color: ${color.slate}; }
::-moz-placeholder { color: ${color.slate}; }
:-ms-input-placeholder { color: ${color.slate}; }
`;
class SearchField extends Component { class SearchField extends Component {
input: HTMLElement; input: HTMLInputElement;
props: { props: {
onChange: Function, onChange: Function,
}; };
@ -33,23 +19,24 @@ class SearchField extends Component {
this.input.focus(); this.input.focus();
}; };
setRef = (ref: HTMLElement) => { setRef = (ref: HTMLInputElement) => {
this.input = ref; this.input = ref;
}; };
render() { render() {
return ( return (
<Flex align="center"> <Flex align="center">
<Icon <StyledIcon
type="Search" type="Search"
size={48} size={46}
color="#C9CFD6" color={color.slateLight}
onClick={this.focusInput} onClick={this.focusInput}
/> />
<Field <StyledInput
{...this.props} {...this.props}
innerRef={this.setRef} innerRef={this.setRef}
onChange={this.handleChange} onChange={this.handleChange}
spellCheck="false"
placeholder="Search…" placeholder="Search…"
autoFocus autoFocus
/> />
@ -58,4 +45,22 @@ class SearchField extends Component {
} }
} }
const StyledInput = styled.input`
width: 100%;
padding: 10px;
font-size: 48px;
font-weight: 400;
outline: none;
border: 0;
::-webkit-input-placeholder { color: ${color.slateLight}; }
:-moz-placeholder { color: ${color.slateLight}; }
::-moz-placeholder { color: ${color.slateLight}; }
:-ms-input-placeholder { color: ${color.slateLight}; }
`;
const StyledIcon = styled(Icon)`
top: 3px;
`;
export default SearchField; export default SearchField;

View File

@ -3,6 +3,7 @@ import React, { Component } from 'react';
import { observer, inject } from 'mobx-react'; import { observer, inject } from 'mobx-react';
import CenteredContent from 'components/CenteredContent'; import CenteredContent from 'components/CenteredContent';
import { ListPlaceholder } from 'components/LoadingPlaceholder'; import { ListPlaceholder } from 'components/LoadingPlaceholder';
import Empty from 'components/Empty';
import PageTitle from 'components/PageTitle'; import PageTitle from 'components/PageTitle';
import DocumentList from 'components/DocumentList'; import DocumentList from 'components/DocumentList';
import DocumentsStore from 'stores/DocumentsStore'; import DocumentsStore from 'stores/DocumentsStore';
@ -17,14 +18,17 @@ import DocumentsStore from 'stores/DocumentsStore';
} }
render() { render() {
const { isLoaded, isFetching } = this.props.documents; const { isLoaded, isFetching, starred } = this.props.documents;
const showLoading = !isLoaded && isFetching;
const showEmpty = isLoaded && !starred.length;
return ( return (
<CenteredContent column auto> <CenteredContent column auto>
<PageTitle title="Starred" /> <PageTitle title="Starred" />
<h1>Starred</h1> <h1>Starred</h1>
{!isLoaded && isFetching && <ListPlaceholder />} {showLoading && <ListPlaceholder />}
<DocumentList documents={this.props.documents.starred} /> {showEmpty && <Empty>No starred documents yet.</Empty>}
<DocumentList documents={starred} />
</CenteredContent> </CenteredContent>
); );
} }

View File

@ -1,6 +1,11 @@
// @flow // @flow
import { keyframes } from 'styled-components'; import { keyframes } from 'styled-components';
export const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`;
export const fadeAndScaleIn = keyframes` export const fadeAndScaleIn = keyframes`
from { from {
opacity: 0; opacity: 0;