Add reusable empty state
Add empty state for no starred documents Add empty state for no search results
This commit is contained in:
21
frontend/components/Empty/Empty.js
Normal file
21
frontend/components/Empty/Empty.js
Normal 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;
|
3
frontend/components/Empty/index.js
Normal file
3
frontend/components/Empty/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// @flow
|
||||||
|
import Empty from './Empty';
|
||||||
|
export default Empty;
|
@ -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>We’re unable to find the page you’re 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}
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user