Skip to content

Commit c05223c

Browse files
committed
Added async options to PF3 composite select.
1 parent 086f134 commit c05223c

File tree

6 files changed

+178
-47
lines changed

6 files changed

+178
-47
lines changed

packages/common/src/select/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const handleSelectChange = (option, simpleValue, isMulti, onChange) => {
2121

2222
class Select extends Component {
2323
render() {
24-
const { input, invalid, classNamePrefix, options, simpleValue, isMulti, pluckSingleValue, ...props } = this.props;
24+
const { input, invalid, classNamePrefix, simpleValue, isMulti, pluckSingleValue, options, ...props } = this.props;
2525
const { value, onChange, ...inputProps } = input;
2626

2727
const selectValue = pluckSingleValue ? isMulti ? value : Array.isArray(value) && value[0] ? value[0] : value : value;
@@ -33,8 +33,8 @@ class Select extends Component {
3333
}) }
3434
{ ...props }
3535
{ ...inputProps }
36-
classNamePrefix={ classNamePrefix }
3736
options={ options }
37+
classNamePrefix={ classNamePrefix }
3838
isMulti={ isMulti }
3939
value={ getSelectValue(selectValue, simpleValue, isMulti, options) }
4040
onChange={ option => handleSelectChange(option, simpleValue, isMulti, onChange) }
@@ -50,7 +50,7 @@ Select.propTypes = {
5050
invalid: PropTypes.bool,
5151
simpleValue: PropTypes.bool,
5252
isMulti: PropTypes.bool,
53-
pluckSingleValue: PropTypes.bool
53+
pluckSingleValue: PropTypes.bool,
5454
};
5555

5656
Select.defaultProps = {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const fnToString = (fn = '') => fn.toString().replace(/\s+/g, ' ');
2+
3+
export default fnToString;

packages/pf3-component-mapper/demo/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,36 @@ import wizardSchema from './demo-schemas/wizard-schema';
1010
import sandbox from './demo-schemas/sandbox';
1111
import Switch from "../src/form-fields/switch-field";
1212

13+
const loadOptions = () => new Promise((res) => {
14+
setTimeout(() => {
15+
fetch('https://dog.ceo/api/breeds/list/all')
16+
.then(data => data.json())
17+
.then(({ message: { bulldog } }) => bulldog.map(dog => ({ label: dog, value: dog })))
18+
.then(data => res(data))
19+
}, 250)
20+
})
21+
1322
const selectSchema = {
1423
fields: [{
24+
component: 'select-field',
25+
name: 'async-single',
26+
label: 'Async single',
27+
multi: true,
28+
loadOptions
29+
},{
30+
component: 'select-field',
31+
name: 'async-single-search',
32+
label: 'Async single search',
33+
isSearchable: true,
34+
loadOptions
35+
}, {
36+
component: 'select-field',
37+
name: 'async-multi-search',
38+
label: 'Async multi search',
39+
isSearchable: true,
40+
multi: true,
41+
loadOptions
42+
}, {
1543
component: 'select-field',
1644
name: 'select-single',
1745
label: 'Select single',

packages/pf3-component-mapper/src/form-fields/select/dropdown-indicator.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
3+
import clsx from 'clsx';
34

4-
// TODO find icon for loading spinner
5-
6-
const DropdownIndicator = ({ selectProps: { isFetching }}) => isFetching
7-
? <i className="ddorg__pf3-component-mapper__select__dropdown-indicator fa fa-angle-down" />
8-
: <i className="ddorg__pf3-component-mapper__select__dropdown-indicator fa fa-angle-down"/>;
5+
const DropdownIndicator = ({ selectProps: { isFetching, value }}) => isFetching
6+
? <i className={ clsx('ddorg__pf3-component-mapper__select__dropdown-indicator fa fa-circle-o-notch spin', {
7+
placeholder: value.length === 0,
8+
}) } />
9+
: <i className={ clsx('ddorg__pf3-component-mapper__select__dropdown-indicator fa fa-angle-down', {
10+
placeholder: value.length === 0,
11+
}) }/>;
912

1013
DropdownIndicator.propTypes = {
1114
selectProps: PropTypes.shape({
1215
isFetching: PropTypes.bool,
16+
value: PropTypes.any,
1317
}).isRequired,
1418
};
1519

packages/pf3-component-mapper/src/form-fields/select/index.js

Lines changed: 103 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@ import './react-select.scss';
1010
*/
1111

1212
import NewSelect from '@data-driven-forms/common/src/select';
13+
import { DropdownButton } from 'patternfly-react';
14+
import fnToString from '@data-driven-forms/common/src/utils/fn-to-string';
15+
import clsx from 'clsx';
1316
import Option from './option';
1417
import DropdownIndicator from './dropdown-indicator';
1518
import ClearIndicator from './clear-indicator';
16-
import { DropdownButton, FormControl } from 'patternfly-react';
17-
import clsx from 'clsx';
1819
import './react-select.scss';
1920

20-
const fnToString = (fn = '') => fn.toString().replace(/\s+/g, ' ');
21-
2221
const ValueContainer = ({ children, ...props }) => {
2322
if (props.isMulti) {
2423
return (
@@ -231,7 +230,7 @@ class SearchInput extends Component {
231230

232231
}
233232

234-
const SelectTitle = ({ title, classNamePrefix, isClearable, value, onClear }) => (
233+
const SelectTitle = ({ title, classNamePrefix, isClearable, value, onClear, isFetching, isDisabled }) => (
235234
<Fragment>
236235
<span key="searchable-select-value-label" className={ `${classNamePrefix}-value` }>{ title }</span>
237236
{ isClearable && value && (
@@ -244,24 +243,81 @@ const SelectTitle = ({ title, classNamePrefix, isClearable, value, onClear }) =>
244243
<i className="fa fa-times"/>
245244
</div>
246245
) }
246+
{ !isDisabled && isFetching && (
247+
<i className="ddorg__pf3-component-mapper__select__dropdown-indicator fa fa-circle-o-notch spin" />
248+
) }
249+
{ !isDisabled && !isFetching && (
250+
<i className="ddorg__pf3-component-mapper__select__dropdown-indicator fa fa-angle-down"/>
251+
) }
247252
</Fragment>
248253
);
249254

250-
export class P3Select extends Component {
251-
state = {
255+
export class P3Select extends Component { constructor(props){
256+
super(props);
257+
this.state = {
258+
isFetching: false,
252259
isOpen: false,
253-
}
260+
options: props.options || [],
261+
};
262+
}
254263
handleToggleOpen = () => this.setState(({ isOpen }) => ({ isOpen: !isOpen }))
255264

256-
componentDidUpdate(prevProps, prevState) {
257-
//console.log('root update', prevState, this.state);
265+
componentDidMount(){
266+
const { loadOptions } = this.props;
267+
if (loadOptions) {
268+
return this.updateOptions();
269+
}
270+
}
271+
272+
componentDidUpdate(prevProps) {
273+
if (!isEqual(this.props.options, prevProps.options)) {
274+
if (!this.props.options.map(({ value }) => value).includes(this.props.input.value)) {
275+
this.props.input.onChange(undefined);
276+
}
277+
278+
this.setState({ options: this.props.options });
279+
}
280+
281+
if (this.props.loadOptions && fnToString(this.props.loadOptions) !== fnToString(prevProps.loadOptions)){
282+
return this.updateOptions();
283+
}
284+
}
285+
286+
updateOptions = () => {
287+
const { loadOptions } = this.props;
288+
289+
this.setState({ isFetching: true });
290+
291+
return loadOptions()
292+
.then((data) => {
293+
if (!data.map(({ value }) => value).includes(this.props.input.value)) {
294+
this.props.input.onChange(undefined);
295+
}
296+
297+
return this.setState({
298+
options: data,
299+
isFetching: false,
300+
});
301+
});
258302
}
259303

260304
shouldComponentUpdate(nextProps, nextState) {
261305
if (nextState.isOpen !== this.state.isOpen) {
262306
return true;
263307
}
264308

309+
if (nextState.isFetching !== this.state.isFetching) {
310+
return true;
311+
}
312+
313+
if (isEqual(this.state.options, nextState.options)) {
314+
return true;
315+
}
316+
317+
if (this.props.loadOptions && fnToString(this.props.loadOptions) !== fnToString(nextProps.loadOptions)){
318+
return true;
319+
}
320+
265321
if (JSON.stringify(nextProps) !== JSON.stringify(this.props)) {
266322
return true;
267323
}
@@ -270,10 +326,10 @@ export class P3Select extends Component {
270326
}
271327

272328
render () {
273-
const { input, ...props } = this.props;
274-
const { isOpen } = this.state;
275-
const [ title, isPlaceholder ] = getDropdownText(input.value, props.placeholder, props.options);
329+
const { input, loadOptions, options: _options, ...props } = this.props;
330+
const { isOpen, options, isFetching } = this.state;
276331
if (props.isSearchable) {
332+
const [ title, isPlaceholder ] = getDropdownText(input.value, props.placeholder, options);
277333
const searchableInput = {
278334
...input,
279335
onChange: props.isMulti || props.multi
@@ -288,10 +344,12 @@ export class P3Select extends Component {
288344
<DropdownButton
289345
onToggle={ () => this.handleToggleOpen() }
290346
disabled={ props.isDisabled }
291-
noCaret={ props.isDisabled }
347+
noCaret
292348
open={ isOpen }
293349
id={ props.id || props.input.name }
294350
title={ <SelectTitle
351+
isDisabled={ props.isDisabled }
352+
isFetching={ isFetching }
295353
classNamePrefix={ this.props.classNamePrefix }
296354
value={ input.value }
297355
isClearable={ props.isClearable }
@@ -302,29 +360,31 @@ export class P3Select extends Component {
302360
'is-empty': isPlaceholder,
303361
}) }>
304362
{ isOpen &&
305-
<NewSelect
306-
input={ searchableInput }
307-
{ ...props }
308-
className={ clsx(props.classNamePrefix, {
309-
sercheable: props.isSearchable,
310-
}) }
311-
controlShouldRenderValue={ false }
312-
hideSelectedOptions={ false }
313-
isClearable={ false }
314-
tabSelectsValue={ false }
315-
menuIsOpen
316-
backspaceRemovesValue={ false }
317-
isMulti={ props.isMulti || props.multi }
318-
placeholder="Search..."
319-
components={{
320-
ClearIndicator,
321-
Option,
322-
DropdownIndicator: null,
323-
IndicatorSeparator: null,
324-
Placeholder: () => null,
325-
Input: ({ selectProps, cx, isHidden, isDisabled, innerRef, getStyles, ...props }) =>
326-
<SearchInput id={ this.props.input.name } { ...props } />,
327-
}} /> }
363+
<NewSelect
364+
isFetching={ isFetching }
365+
input={ searchableInput }
366+
{ ...props }
367+
options={ options }
368+
className={ clsx(props.classNamePrefix, {
369+
sercheable: props.isSearchable,
370+
}) }
371+
controlShouldRenderValue={ false }
372+
hideSelectedOptions={ false }
373+
isClearable={ false }
374+
tabSelectsValue={ false }
375+
menuIsOpen
376+
backspaceRemovesValue={ false }
377+
isMulti={ props.isMulti || props.multi }
378+
placeholder="Search..."
379+
components={{
380+
ClearIndicator,
381+
Option,
382+
DropdownIndicator: null,
383+
IndicatorSeparator: null,
384+
Placeholder: () => null,
385+
Input: ({ selectProps, cx, isHidden, isDisabled, innerRef, getStyles, ...props }) =>
386+
<SearchInput id={ this.props.input.name } { ...props } />,
387+
}} /> }
328388
</DropdownButton>
329389
</div>
330390
);
@@ -333,6 +393,8 @@ export class P3Select extends Component {
333393
return (
334394
<NewSelect
335395
{ ...this.props }
396+
isFetching={ isFetching }
397+
options={ options }
336398
input={ input }
337399
className={ props.classNamePrefix }
338400
components={{
@@ -345,3 +407,7 @@ export class P3Select extends Component {
345407

346408
}
347409
}
410+
411+
P3Select.defaultProps = {
412+
placeholder: 'Search...',
413+
};

packages/pf3-component-mapper/src/form-fields/select/react-select.scss

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ div.field-group {
2323
width: 100%;
2424
}
2525

26+
@keyframes rotate {
27+
0% {
28+
-webkit-transform: rotate(0deg);
29+
transform: rotate(0deg);
30+
}
31+
100% {
32+
-webkit-transform: rotate(360deg);
33+
transform: rotate(360deg);
34+
}
35+
}
36+
2637
.ddorg__pf3-component-mapper__select {
2738
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
2839
font-family: "Open Sans", Helvetica, Arial, sans-serif;
@@ -58,12 +69,21 @@ div.field-group {
5869
border-color: #bbbbbb;
5970
color: #4d5258;
6071
}
72+
&.ddorg__pf3-component-mapper__select__control--menu-is-open {
73+
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
74+
background-color: #f1f1f1;
75+
border-color: #bbb;
76+
.ddorg__pf3-component-mapper__select__placeholder {
77+
color: #4d5258;
78+
}
79+
}
6180
}
6281
.ddorg__pf3-component-mapper__select__menu {
6382
background: #fff;
6483
margin: 0;
65-
margin-top: 8px;
84+
margin-top: 1px;
6685
border-radius: 0;
86+
border-top: 0;
6787
padding-top: 5px;
6888
padding-bottom: 5px;
6989
}
@@ -98,7 +118,7 @@ div.field-group {
98118
}
99119
.ddorg__pf3-component-mapper__select__indicators {
100120
padding: 0;
101-
padding-right: 8px;
121+
padding-right: 6px;
102122
&:hover {
103123
color: rgb(77, 82, 88),
104124
};
@@ -154,6 +174,7 @@ div.field-group {
154174
.ddorg__pf3-component-mapper__select__placeholder {
155175
padding: 0;
156176
position: relative;
177+
color: rgb(153, 153, 153);
157178
}
158179
&.sercheable {
159180
border: none;
@@ -255,4 +276,13 @@ div.field-group {
255276
border-bottom: none;
256277
border-color: #ddd;
257278
}
279+
}
280+
281+
.ddorg__pf3-component-mapper__select__dropdown-indicator {
282+
&.spin {
283+
animation: rotate 1.1s infinite linear;
284+
}
285+
&.placeholder {
286+
color: rgb(153, 153, 153);
287+
}
258288
}

0 commit comments

Comments
 (0)