Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Adding support for a loading indicator
Browse files Browse the repository at this point in the history
Summary:
After running in a ZND, it became obvious that a loading indicator was necessary for this component so I modeled it (visually) after the `react-select` loading indicator.

GIF demo:
{F669626}

Test Plan:
npm run lint
npm run flow

Reviewers: riley, alex

Reviewed By: alex

Differential Revision: https://phabricator.khanacademy.org/D34646
  • Loading branch information
BrianGenisio committed Mar 29, 2017
1 parent b0e809a commit bc226be
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 14 deletions.
1 change: 1 addition & 0 deletions .storybook/config.js
Expand Up @@ -10,6 +10,7 @@ function loadStories() {
require('../src/stories/select-item.js');
require('../src/stories/select-panel.js');
require('../src/stories/dropdown.js');
require('../src/stories/loading-indicator.js');
}

configure(loadStories, module);
19 changes: 18 additions & 1 deletion src/dropdown.js
Expand Up @@ -6,6 +6,8 @@
*/
import React, {Component} from 'react';

import LoadingIndicator from './loading-indicator.js';

class Dropdown extends Component {

state = {
Expand All @@ -27,6 +29,7 @@ class Dropdown extends Component {
children?: Object,
contentComponent: Object,
contentProps: Object,
isLoading?: boolean,
}

wrapper: Object
Expand Down Expand Up @@ -72,8 +75,13 @@ class Dropdown extends Component {
}

toggleExpanded = (value: ?boolean) => {
const {isLoading} = this.props;
const {expanded} = this.state;

if (isLoading) {
return;
}

const newExpanded = value === undefined ? !expanded : !!value;

this.setState({expanded: newExpanded});
Expand All @@ -93,7 +101,7 @@ class Dropdown extends Component {

render() {
const {expanded, hasFocus} = this.state;
const {children} = this.props;
const {children, isLoading} = this.props;

const expandedHeaderStyle = expanded
? styles.dropdownHeaderExpanded
Expand Down Expand Up @@ -133,6 +141,9 @@ class Dropdown extends Component {
<span style={styles.dropdownChildren}>
{children}
</span>
<span style={styles.loadingContainer}>
{isLoading && <LoadingIndicator />}
</span>
<span style={styles.dropdownArrow}>
<span style={{
...arrowStyle,
Expand Down Expand Up @@ -231,6 +242,12 @@ const styles = {
borderBottomRightRadius: '0px',
borderBottomLeftRadius: '0px',
},
loadingContainer: {
cursor: 'pointer',
display: 'table-cell',
verticalAlign: 'middle',
width: '16px',
},
panelContainer: {
borderBottomRightRadius: '4px',
borderBottomLeftRadius: '4px',
Expand Down
4 changes: 4 additions & 0 deletions src/index.js
Expand Up @@ -10,6 +10,7 @@
* - values: The currently selected values []
* - onSelectedChanged: An event to notify the caller of new values
* - valueRenderer: A fn to support overriding the message in the component
* - isLoading: Show a loading indicator
*/
import React, {Component} from 'react';

Expand All @@ -31,6 +32,7 @@ class MultiSelect extends Component {
) => string,
ItemRenderer?: Function,
selectAllLabel: string,
isLoading?: boolean,
}

getSelectedText() {
Expand Down Expand Up @@ -81,9 +83,11 @@ class MultiSelect extends Component {
selected,
selectAllLabel,
onSelectedChanged,
isLoading,
} = this.props;

return <Dropdown
isLoading={isLoading}
contentComponent={SelectPanel}
contentProps={{
ItemRenderer,
Expand Down
78 changes: 78 additions & 0 deletions src/loading-indicator.js
@@ -0,0 +1,78 @@
// @flow
/**
* A simple loading indicator, modeled after react-select. Since react styles
* don't support animations, hack it so we inject the keyframe animation
* into the document.
*/
import React, {Component} from 'react';

const STYLESHEET_NAME = "__react-multi-select_style_inject__";


function findStylesheet(): ?CSSStyleSheet {
const styleSheet = Array.from(document.styleSheets)
.find(stylesheet => stylesheet.title === STYLESHEET_NAME);

// upcast as CSSStyleSheet
const cssStylesheet: ?CSSStyleSheet = (styleSheet: any);

return cssStylesheet;
}

function registerStylesheet(css) {
try {
if (findStylesheet()) {
return;
}

const style = document.createElement("style");
style.setAttribute("title", STYLESHEET_NAME);
document.head && document.head.appendChild(style);

const stylesheet = findStylesheet();
if (!stylesheet) {
// Someting bad happened. Abort!
return;
}

stylesheet.insertRule(css, 0);
} catch (e) {
}
}

class LoadingIndicator extends Component {
componentWillMount() {
// React styles don't support adding keyframe rules. Create a
// stylesheet and inject the keyframe animarion into it.
registerStylesheet(keyFrames);
}

render() {
return <span style={styles.loading} />;
}
}

const keyFrames = `
@keyframes react-multi-select_loading-spin {
to {
transform: rotate(1turn);
}
}
`;

const styles = {
loading: {
"animation": "react-multi-select_loading-spin 400ms infinite linear",
"width": "16px",
"height": "16px",
boxSizing: "border-box",
borderRadius: "50%",
border: "2px solid #ccc",
borderRightColor: "#333",
display: "inline-block",
position: "relative",
verticalAlign: "middle",
},
};

export default LoadingIndicator;
29 changes: 16 additions & 13 deletions src/stories/index.js
Expand Up @@ -115,6 +115,7 @@ class StatefulMultiSelect extends Component {
valueRenderer?: (values: Array<any>, options: Array<Option>) => string,
ItemRenderer?: Function,
selectAllLabel?: string,
isLoading?: boolean,
}

handleSelectedChanged(selected) {
Expand All @@ -127,6 +128,7 @@ class StatefulMultiSelect extends Component {
options,
selectAllLabel,
valueRenderer,
isLoading,
} = this.props;
const {selected} = this.state;

Expand All @@ -138,6 +140,7 @@ class StatefulMultiSelect extends Component {
valueRenderer={valueRenderer}
ItemRenderer={ItemRenderer}
selectAllLabel={selectAllLabel}
isLoading={isLoading}
/>

<h2>Selected:</h2>
Expand Down Expand Up @@ -188,21 +191,21 @@ storiesOf('MultiSelect', module)
.add('default view', () => <StatefulMultiSelect options={shortList} />)
.add('long list view', () => <StatefulMultiSelect options={longList} />)
.add('United States', () => <StatefulMultiSelect options={statesList} />)
.add('Custom Heading Renderer',
() => <StatefulMultiSelect
options={studentsList}
valueRenderer={studentValueRenderer}
selectAllLabel="All students"
/>
)
.add('Custom Heading Renderer', () => <StatefulMultiSelect
options={studentsList}
valueRenderer={studentValueRenderer}
selectAllLabel="All students"
/>)
.add('Tabbing test (accessibility)', () => <div>
<input/>
<StatefulMultiSelect options={shortList} />
<input type="checkbox" />
</div>)
.add('Item Renderer Override',
() => <StatefulMultiSelect
options={studentsList}
ItemRenderer={StudentItemRenderer}
/>
);
.add('Item Renderer Override', () => <StatefulMultiSelect
options={studentsList}
ItemRenderer={StudentItemRenderer}
/>)
.add('With loading indicator', () => <StatefulMultiSelect
options={[]}
isLoading={true}
/>);
6 changes: 6 additions & 0 deletions src/stories/loading-indicator.js
@@ -0,0 +1,6 @@
import React from 'react';
import {storiesOf} from '@kadira/storybook';
import LoadingIndicator from '../loading-indicator.js';

storiesOf('Loading Indicator', module)
.add('default view', () => <LoadingIndicator />);

0 comments on commit bc226be

Please sign in to comment.