Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
feat: loading indicator (#544)
Browse files Browse the repository at this point in the history
* chore(package.json): update yarn requirement

This is definitely something that we can skip...

* feat(loading-indicator): add information about stalled searches

With this implementation all the connectors can propagate the
information that the search is currently stalled (meaning that it seems
that there are slow network issues). Currently it is only implemented in
the searchbox.

* feat(loading-indicator): Make timeout for stalled search configurable

* feat(loading-indicator): add option to searchbox

* chore(ci): update yarn version in travis config

* chore: rename stalledSearchTimeout into stalledSearchDelay

For consistency with InstantSearch.js

* fix: revert behaviour change of `searching`

It was a mistake to try to fix the current behaviour of this flag. We
should keep it as legacy and remove it during the next major.

* feat(SearchBox): new API for the loading indicator

And fix tests

* fix: actually add the loading indicator

* chore(stories): add with custom loading indicator

* chore(doc): add doc for new API

* chore(createInstantSearchManager): test loading status

* chore: remove loading indicator DOM when not needed

* refactor(createInstantSearchManager): isSearchStalled should be in store

* refactoring(InstantSearch): stalledSearchDelay as a defaultProps

* refactor(SearchBox): isSearchStalled computed once

* chore: eslint:fix

* refactor(SearchBox): use default props for components

* chore: isSearchStalled should have a default value

* chore: update yarn version (consistency)

* chore: remove props since it's the same value as default

* chore(storybook): loading indicator as knob

* chore: udpate the behaviour and improve test

The search is now considered stalled before first results.

* chore: fix lint

* refactor: variable renaming in createConnector

* chore: syntactic sugar

* chore(SearchBox): test behaviour with stalled search

* refactor: consolidate destructuring of props
  • Loading branch information
bobylito authored and samouss committed Dec 8, 2017
1 parent b92019f commit 189659e
Show file tree
Hide file tree
Showing 18 changed files with 665 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ addons:
before_install:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.2.0
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
- export PATH=$HOME/.yarn/bin:$PATH
cache:
yarn: true
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
},
"engines": {
"node": "8.6.0",
"yarn": "1.2.0"
"yarn": "1.3.2"
},
"jest": {
"notify": false,
Expand Down
31 changes: 31 additions & 0 deletions packages/react-instantsearch-theme-algolia/styles/_SearchBox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,34 @@ $main-searchbox-config: (
.ais-SearchBox {
@include searchbox($main-searchbox-config...);
}

.ais-SearchBox__loading-indicator {
position: absolute;
top: 0;
right: inherit;
left: 0;
margin: 0;
border: 0;
border-radius: 4px 0 0 4px;
background-color: rgba(255, 255, 255, 0);
padding: 0;
width: 46px;
height: 100%;
vertical-align: middle;
text-align: center;
font-size: inherit;
}

.ais-SearchBox__loading-indicator::before {
display: inline-block;
margin-right: -4px;
height: 100%;
vertical-align: middle;
content: '';
}

.ais-SearchBox__loading-indicator > svg {
height: 1.3em;
width: 1.3em;
margin-top: 1em;
}
92 changes: 74 additions & 18 deletions packages/react-instantsearch/src/components/SearchBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,51 @@ import classNames from './classNames.js';

const cx = classNames('SearchBox');

const DefaultLoadingIndicator = () => (
<svg
width="18"
height="18"
viewBox="0 0 38 38"
xmlns="http://www.w3.org/2000/svg"
stroke="#BFC7D8"
>
<g fill="none" fillRule="evenodd">
<g transform="translate(1 1)" strokeWidth="2">
<circle strokeOpacity=".5" cx="18" cy="18" r="18" />
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"
/>
</path>
</g>
</g>
</svg>
);

const DefaultReset = () => (
<svg role="img" width="1em" height="1em">
<use xlinkHref="#sbx-icon-clear-3" />
</svg>
);

const DefaultSubmit = () => (
<svg role="img" width="1em" height="1em">
<use xlinkHref="#sbx-icon-search-13" />
</svg>
);

class SearchBox extends Component {
static propTypes = {
currentRefinement: PropTypes.string,
refine: PropTypes.func.isRequired,
translate: PropTypes.func.isRequired,

loadingIndicatorComponent: PropTypes.element,
resetComponent: PropTypes.element,
submitComponent: PropTypes.element,

Expand All @@ -25,6 +64,9 @@ class SearchBox extends Component {
onReset: PropTypes.func,
onChange: PropTypes.func,

isSearchStalled: PropTypes.bool,
showLoadingIndicator: PropTypes.bool,

// For testing purposes
__inputRef: PropTypes.func,
};
Expand All @@ -34,6 +76,11 @@ class SearchBox extends Component {
focusShortcuts: ['s', '/'],
autoFocus: false,
searchAsYouType: true,
showLoadingIndicator: false,
isSearchStalled: false,
loadingIndicatorComponent: <DefaultLoadingIndicator />,
submitComponent: <DefaultSubmit />,
resetComponent: <DefaultReset />,
};

constructor(props) {
Expand Down Expand Up @@ -153,25 +200,15 @@ class SearchBox extends Component {
};

render() {
const { translate, autoFocus } = this.props;
const {
translate,
autoFocus,
loadingIndicatorComponent,
resetComponent,
submitComponent,
} = this.props;
const query = this.getQuery();

const submitComponent = this.props.submitComponent ? (
this.props.submitComponent
) : (
<svg role="img" width="1em" height="1em">
<use xlinkHref="#sbx-icon-search-13" />
</svg>
);

const resetComponent = this.props.resetComponent ? (
this.props.resetComponent
) : (
<svg role="img" width="1em" height="1em">
<use xlinkHref="#sbx-icon-clear-3" />
</svg>
);

const searchInputEvents = Object.keys(this.props).reduce((props, prop) => {
if (
['onsubmit', 'onreset', 'onchange'].indexOf(prop.toLowerCase()) ===
Expand All @@ -184,6 +221,9 @@ class SearchBox extends Component {
return props;
}, {});

const isSearchStalled =
this.props.showLoadingIndicator && this.props.isSearchStalled;

/* eslint-disable max-len */
return (
<form
Expand Down Expand Up @@ -216,7 +256,10 @@ class SearchBox extends Component {
/>
</symbol>
</svg>
<div role="search" {...cx('wrapper')}>
<div
role="search"
{...cx('wrapper', isSearchStalled && 'stalled-search')}
>
<input
ref={this.onInputMount}
type="search"
Expand All @@ -233,9 +276,22 @@ class SearchBox extends Component {
{...searchInputEvents}
{...cx('input')}
/>
{this.props.showLoadingIndicator && (
<div
style={{
display: isSearchStalled ? 'block' : 'none',
}}
{...cx('loading-indicator')}
>
{loadingIndicatorComponent}
</div>
)}
<button
type="submit"
title={translate('submitTitle')}
style={{
display: isSearchStalled ? 'none' : 'block',
}}
{...cx('submit')}
>
{submitComponent}
Expand Down
15 changes: 15 additions & 0 deletions packages/react-instantsearch/src/components/SearchBox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('SearchBox', () => {
inst.update(<SearchBox refine={() => null} currentRefinement="QUERY2" />);
expect(inst.toJSON()).toMatchSnapshot();
});

it('lets you customize its theme', () => {
const tree = renderer
.create(
Expand Down Expand Up @@ -193,4 +194,18 @@ describe('SearchBox', () => {
expect(inputProps[eventName]).toBeCalled();
});
});

it('should render the loader if showLoadingIndicator is true', () => {
const treeWithoutLoadingIndicator = renderer
.create(<SearchBox refine={() => null} showLoadingIndicator />)
.toJSON();
expect(treeWithoutLoadingIndicator).toMatchSnapshot();

const treeWithLoadingIndicator = renderer
.create(
<SearchBox refine={() => null} showLoadingIndicator isSearchStalled />
)
.toJSON();
expect(treeWithLoadingIndicator).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ exports[`Menu Menu with search inside items but no search results 1`] = `
/>
<button
className="ais-SearchBox__submit"
style={
Object {
"display": "block",
}
}
title="Submit your search query."
type="submit"
>
Expand Down Expand Up @@ -230,6 +235,11 @@ exports[`Menu Menu with search inside items with search results 1`] = `
/>
<button
className="ais-SearchBox__submit"
style={
Object {
"display": "block",
}
}
title="Submit your search query."
type="submit"
>
Expand Down Expand Up @@ -352,6 +362,11 @@ exports[`Menu applies translations 1`] = `
/>
<button
className="ais-SearchBox__submit"
style={
Object {
"display": "block",
}
}
title="Submit your search query."
type="submit"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ exports[`RefinementList applies translations 1`] = `
/>
<button
className="ais-SearchBox__submit"
style={
Object {
"display": "block",
}
}
title="Submit your search query."
type="submit"
>
Expand Down Expand Up @@ -320,6 +325,11 @@ exports[`RefinementList refinement list with search inside items but no search r
/>
<button
className="ais-SearchBox__submit"
style={
Object {
"display": "block",
}
}
title="Submit your search query."
type="submit"
>
Expand Down Expand Up @@ -503,6 +513,11 @@ exports[`RefinementList refinement list with search inside items with search res
/>
<button
className="ais-SearchBox__submit"
style={
Object {
"display": "block",
}
}
title="Submit your search query."
type="submit"
>
Expand Down
Loading

0 comments on commit 189659e

Please sign in to comment.