@spothero/ui 15.0.0 → 15.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -3
- package/styles/v2/components/FormControl/FormControl.jsx +20 -2
- package/styles/v2/components/Radio/Radio.jsx +50 -0
- package/styles/v2/components/Radio/Radio.stories.js +155 -0
- package/styles/v2/components/Radio/RadioGroup.jsx +69 -0
- package/styles/v2/components/Radio/styles/index.js +52 -0
- package/styles/v2/components/index.js +1 -0
- package/styles/v2/components/styles.js +1 -0
- package/v2/index.js +1 -1
- package/v2/index.js.map +1 -1
- package/styles/Alert/Alert.jsx +0 -45
- package/styles/Alert/Alert.spec.js +0 -85
- package/styles/AutoSuggestInput/AutoSuggestInput.jsx +0 -429
- package/styles/AutoSuggestInput/AutoSuggestInput.spec.js +0 -132
- package/styles/AutoSuggestInput/AutoSuggestItem.jsx +0 -61
- package/styles/AutoSuggestInput/AutoSuggestList.jsx +0 -85
- package/styles/Badge/Badge.jsx +0 -24
- package/styles/Badge/Badge.spec.js +0 -43
- package/styles/Chart/Chart.jsx +0 -185
- package/styles/Chart/Chart.spec.js +0 -369
- package/styles/Checkbox/Checkbox.jsx +0 -159
- package/styles/Checkbox/Checkbox.spec.js +0 -142
- package/styles/DateTime/DatePicker.jsx +0 -281
- package/styles/DateTime/DatePicker.spec.js +0 -186
- package/styles/DateTime/DatePickerCalendar.jsx +0 -170
- package/styles/DateTime/DatePickerCalendarNavigation.jsx +0 -44
- package/styles/DateTime/DatePickerCalendarWithRange.jsx +0 -218
- package/styles/DateTime/DateTimePicker.jsx +0 -266
- package/styles/DateTime/DateTimePicker.spec.js +0 -60
- package/styles/DateTime/DateTimeRangePicker.jsx +0 -629
- package/styles/DateTime/DateTimeRangePicker.spec.js +0 -425
- package/styles/DateTime/TimePicker.jsx +0 -158
- package/styles/DateTime/TimePicker.spec.js +0 -148
- package/styles/DateTime/date-time-assertions.js +0 -89
- package/styles/DateTime/index.js +0 -6
- package/styles/ErrorBoundary/ErrorBoundary.jsx +0 -76
- package/styles/ErrorBoundary/ErrorBoundary.spec.js +0 -72
- package/styles/Flyout/Flyout.jsx +0 -147
- package/styles/Flyout/Flyout.spec.js +0 -117
- package/styles/Form/Form.jsx +0 -151
- package/styles/Form/Form.spec.js +0 -148
- package/styles/Form/FormElementError.jsx +0 -18
- package/styles/Form/FormGroup.jsx +0 -32
- package/styles/Form/FormGroupError.jsx +0 -24
- package/styles/Form/index.js +0 -4
- package/styles/GooglePlacesSearchInput/GooglePlacesSearchInput.jsx +0 -215
- package/styles/GooglePlacesSearchInput/GooglePlacesSearchInput.spec.js +0 -213
- package/styles/GooglePlacesSearchInput/PoweredByGoogle.jsx +0 -43
- package/styles/GooglePlacesSearchInput/index.js +0 -2
- package/styles/HorizontalRule/HorizontalRule.jsx +0 -36
- package/styles/HorizontalRule/HorizontalRule.spec.js +0 -94
- package/styles/Label/Label.jsx +0 -22
- package/styles/Label/Label.spec.js +0 -11
- package/styles/Notification/Notification.jsx +0 -117
- package/styles/Notification/Notification.spec.js +0 -154
- package/styles/Notification/NotificationContainer.jsx +0 -90
- package/styles/Notification/NotificationPropTypes.js +0 -20
- package/styles/Notification/index.js +0 -2
- package/styles/PasswordControl/PasswordControl.jsx +0 -197
- package/styles/PasswordControl/PasswordControl.spec.js +0 -236
- package/styles/Portal/Portal.jsx +0 -65
- package/styles/Portal/Portal.spec.js +0 -45
- package/styles/PulseLoader/PulseLoader.jsx +0 -71
- package/styles/PulseLoader/PulseLoader.spec.js +0 -63
- package/styles/Radio/Radio.jsx +0 -114
- package/styles/Radio/Radio.spec.js +0 -128
- package/styles/Radio/RadioGroup.jsx +0 -105
- package/styles/RenderInBody/RenderInBody.jsx +0 -56
- package/styles/RenderInBody/RenderInBody.spec.js +0 -24
- package/styles/Select/Select.jsx +0 -251
- package/styles/Select/Select.spec.js +0 -254
- package/styles/Select/SelectItemPropTypes.js +0 -19
- package/styles/Select/index.js +0 -2
- package/styles/SelectControlled/SelectControlled.jsx +0 -250
- package/styles/SelectControlled/SelectControlled.spec.js +0 -290
- package/styles/SelectControlled/index.js +0 -1
- package/styles/Sprite/Sprite.jsx +0 -16
- package/styles/Sprite/Sprite.spec.js +0 -11
- package/styles/Tabs/Tab.jsx +0 -38
- package/styles/Tabs/TabContent.jsx +0 -46
- package/styles/Tabs/TabNavigation.jsx +0 -64
- package/styles/Tabs/TabPanel.jsx +0 -30
- package/styles/Tabs/Tabs.jsx +0 -87
- package/styles/Tabs/Tabs.spec.js +0 -201
- package/styles/Tabs/index.js +0 -5
- package/styles/TextArea/TextArea.jsx +0 -137
- package/styles/TextArea/TextArea.spec.js +0 -111
- package/styles/TextInput/TextInput.jsx +0 -159
- package/styles/TextInput/TextInput.spec.js +0 -263
- package/styles/TextInput/TextInputPropTypes.js +0 -88
- package/styles/TextInput/index.js +0 -2
- package/styles/Tooltip/Tooltip.jsx +0 -230
- package/styles/Tooltip/Tooltip.spec.js +0 -170
- /package/styles/{Radio → v2/components/Radio}/index.js +0 -0
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
describe('<Radio />', () => {
|
|
2
|
-
context('Display', () => {
|
|
3
|
-
context('Default', () => {
|
|
4
|
-
it('Should have an input and some kind of label element', () => {
|
|
5
|
-
cy.visitStory('v1/Radio/Display', 'Default')
|
|
6
|
-
.get('.Radio')
|
|
7
|
-
.find('input[type=radio]')
|
|
8
|
-
.should('have.class', 'FormElement-item')
|
|
9
|
-
.and('not.have.attr', 'disabled')
|
|
10
|
-
.get('.Radio')
|
|
11
|
-
.find('.Label')
|
|
12
|
-
.contains('This is my label');
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
context('Disabled', () => {
|
|
17
|
-
it('Should have a disabled input', () => {
|
|
18
|
-
cy.visitStory('v1/Radio/Display', 'Disabled')
|
|
19
|
-
.get('.Radio')
|
|
20
|
-
.find('input[type=radio]')
|
|
21
|
-
.should('be.disabled');
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
context('Custom Classes', () => {
|
|
26
|
-
it('Should have custom class names', () => {
|
|
27
|
-
cy.visitStory('v1/Radio/Display', 'CustomClass')
|
|
28
|
-
.get('.Radio')
|
|
29
|
-
.should('have.class', 'custom-class1')
|
|
30
|
-
.and('have.class', 'custom-class2');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('<RadioGroup />', () => {
|
|
36
|
-
const radioValues = ['vanilla', 'chocolate', 'strawberry', 'pistachio'];
|
|
37
|
-
|
|
38
|
-
it('Default', () => {
|
|
39
|
-
cy.visitStory('v1/Radio/Group', 'Default')
|
|
40
|
-
// verify custom classes
|
|
41
|
-
.get('.RadioGroup')
|
|
42
|
-
.should('have.class', 'custom-class1')
|
|
43
|
-
.and('have.class', 'custom-class2')
|
|
44
|
-
// verify # of Radios and properties on each
|
|
45
|
-
.find('.Radio')
|
|
46
|
-
.should('have.length', 4)
|
|
47
|
-
.find('input[type=radio]')
|
|
48
|
-
.each(($el, index) => {
|
|
49
|
-
cy.wrap($el)
|
|
50
|
-
.should('have.attr', 'name', 'ice-cream')
|
|
51
|
-
.and('have.attr', 'value', radioValues[index])
|
|
52
|
-
.and('not.be.disabled')
|
|
53
|
-
.and('not.have.attr', 'required');
|
|
54
|
-
})
|
|
55
|
-
// verify that Radios are being checked
|
|
56
|
-
.get('.RadioGroup')
|
|
57
|
-
.find('input[value=chocolate]')
|
|
58
|
-
.should('have.checked', true)
|
|
59
|
-
.get('.RadioGroup')
|
|
60
|
-
.find('input[value=pistachio]')
|
|
61
|
-
.click()
|
|
62
|
-
.should('have.checked', true)
|
|
63
|
-
.get('.RadioGroup')
|
|
64
|
-
.find('input[value=vanilla]')
|
|
65
|
-
.click()
|
|
66
|
-
.get('.RadioGroup')
|
|
67
|
-
.find('input[name=ice-cream]:checked')
|
|
68
|
-
.should('have.value', 'vanilla');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('Disabled', () => {
|
|
72
|
-
cy.visitStory('v1/Radio/Group', 'Disabled')
|
|
73
|
-
.get('.RadioGroup')
|
|
74
|
-
.find('input[type=radio]')
|
|
75
|
-
.each(($el, index) => {
|
|
76
|
-
cy.wrap($el)
|
|
77
|
-
.and('be.disabled')
|
|
78
|
-
.and('not.have.attr', 'required');
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('Required', () => {
|
|
83
|
-
cy.visitStory('v1/Radio/Group', 'Required')
|
|
84
|
-
.get('.RadioGroup')
|
|
85
|
-
.find('input[type=radio]')
|
|
86
|
-
.each(($el, index) => {
|
|
87
|
-
cy.wrap($el)
|
|
88
|
-
.and('not.be.disabled')
|
|
89
|
-
.and('have.attr', 'required');
|
|
90
|
-
})
|
|
91
|
-
// try to submit the form
|
|
92
|
-
.get('.Button')
|
|
93
|
-
.click()
|
|
94
|
-
.get('.RadioGroup')
|
|
95
|
-
.find('.FormElementError')
|
|
96
|
-
.contains('Please select a flavor!')
|
|
97
|
-
// fix the error
|
|
98
|
-
.get('.RadioGroup')
|
|
99
|
-
.find('input[value=chocolate]')
|
|
100
|
-
.click()
|
|
101
|
-
.get('.RadioGroup')
|
|
102
|
-
.find('.FormElementError')
|
|
103
|
-
.should('not.exist');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('On Change', () => {
|
|
107
|
-
cy.visitStory('v1/Radio/Group', 'OnChange', {
|
|
108
|
-
onBeforeLoad: contentWindow => {
|
|
109
|
-
contentWindow.onRadioGroupChange = evt => {
|
|
110
|
-
console.log(`onRadioGroupChange`); // eslint-disable-line no-console
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
cy.stub(contentWindow, 'onRadioGroupChange').as(
|
|
114
|
-
'onRadioGroupChange'
|
|
115
|
-
);
|
|
116
|
-
},
|
|
117
|
-
})
|
|
118
|
-
.get('.RadioGroup')
|
|
119
|
-
.find('input[type=radio]')
|
|
120
|
-
.each(($el, index) => {
|
|
121
|
-
cy.wrap($el)
|
|
122
|
-
.click()
|
|
123
|
-
.get('@onRadioGroupChange')
|
|
124
|
-
.should('be.called');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
});
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import React, {Component, Children, cloneElement} from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import classNames from 'classnames';
|
|
4
|
-
|
|
5
|
-
export default class RadioGroup extends Component {
|
|
6
|
-
static propTypes = {
|
|
7
|
-
/** Additional class(es) to add to the component. */
|
|
8
|
-
className: PropTypes.string,
|
|
9
|
-
/** A unique ID which is used to identify this element for accessibility purposes on the client as well as the server. */
|
|
10
|
-
id: PropTypes.string.isRequired,
|
|
11
|
-
/** Children should only be made up of Radio components. */
|
|
12
|
-
children: PropTypes.arrayOf(PropTypes.element).isRequired,
|
|
13
|
-
/** A unique name which is used to identify this element. If belonging to a form this is used as the key for validation bindings and is required. The name will be passed down to child Radio components to set the proper value upon selection. */
|
|
14
|
-
name: PropTypes.string.isRequired,
|
|
15
|
-
/** Whether to disable all the Radio items in this group. */
|
|
16
|
-
disabled: PropTypes.bool,
|
|
17
|
-
/** A custom label (typically a Label component) to display next to the radio. */
|
|
18
|
-
label: PropTypes.element,
|
|
19
|
-
/** Whether this field is required during validation. */
|
|
20
|
-
required: PropTypes.bool,
|
|
21
|
-
/** A custom validation error (typically a FormElementError component) to display when this component is part of a form and is invalid. */
|
|
22
|
-
error: PropTypes.element,
|
|
23
|
-
/**
|
|
24
|
-
* Function to execute when the selected state changes.
|
|
25
|
-
*
|
|
26
|
-
* @param {SyntheticEvent} evt - The React `SyntheticEvent`.
|
|
27
|
-
*/
|
|
28
|
-
onChange: PropTypes.func,
|
|
29
|
-
/**
|
|
30
|
-
* Function to execute when the element is marked as invalid by a validation check.
|
|
31
|
-
*
|
|
32
|
-
* @param {ValidityState} validationState - ValidityState object for more fine grained control over error handling in a parent component if desired.
|
|
33
|
-
*/
|
|
34
|
-
onInvalid: PropTypes.func,
|
|
35
|
-
};
|
|
36
|
-
state = {
|
|
37
|
-
validationState: null,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
constructor(props) {
|
|
41
|
-
super(props);
|
|
42
|
-
|
|
43
|
-
if (!props.id) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`The prop 'id' is required to make RadioGroup accessible for users using assistive technologies such as screen readers. Check the render method of RadioGroup.`
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
_onValidationChange = validationState => {
|
|
51
|
-
const {onInvalid} = this.props;
|
|
52
|
-
|
|
53
|
-
this.setState({
|
|
54
|
-
validationState,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
if (onInvalid) {
|
|
58
|
-
onInvalid(validationState);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
render() {
|
|
63
|
-
const {
|
|
64
|
-
className,
|
|
65
|
-
id,
|
|
66
|
-
children,
|
|
67
|
-
name,
|
|
68
|
-
label,
|
|
69
|
-
required,
|
|
70
|
-
disabled,
|
|
71
|
-
error,
|
|
72
|
-
onChange,
|
|
73
|
-
} = this.props;
|
|
74
|
-
const {validationState} = this.state;
|
|
75
|
-
const isValid = validationState ? validationState.valid : true;
|
|
76
|
-
const classes = classNames(
|
|
77
|
-
'RadioGroup',
|
|
78
|
-
'FormElement',
|
|
79
|
-
{'FormElement-contains-error': !isValid},
|
|
80
|
-
className
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<fieldset className={classes} id={`RadioGroup-${id}`}>
|
|
85
|
-
<div
|
|
86
|
-
className="FormElement-control-container"
|
|
87
|
-
role="radiogroup"
|
|
88
|
-
>
|
|
89
|
-
{label}
|
|
90
|
-
{Children.map(children, (child, i) => {
|
|
91
|
-
return cloneElement(child, {
|
|
92
|
-
name,
|
|
93
|
-
disabled,
|
|
94
|
-
required,
|
|
95
|
-
onChange,
|
|
96
|
-
id: `${id}-item-${i}`,
|
|
97
|
-
onValidationChange: this._onValidationChange,
|
|
98
|
-
});
|
|
99
|
-
})}
|
|
100
|
-
</div>
|
|
101
|
-
{!isValid && error}
|
|
102
|
-
</fieldset>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React, {Component} from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import {unmountComponentAtNode, render as renderDOM} from 'react-dom';
|
|
4
|
-
import {Provider} from 'react-redux';
|
|
5
|
-
import DOMUtils from '@spothero/utils/dom';
|
|
6
|
-
|
|
7
|
-
export default class RenderInBody extends Component {
|
|
8
|
-
static propTypes = {
|
|
9
|
-
/** Additional class(es) to add to the component. */
|
|
10
|
-
className: PropTypes.string,
|
|
11
|
-
/** The element(s) to render. */
|
|
12
|
-
children: PropTypes.element.isRequired,
|
|
13
|
-
/** When using Redux, allows to pass the store down to the children so they have access to its data. */
|
|
14
|
-
store: PropTypes.object,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
componentDidMount() {
|
|
18
|
-
const {className} = this.props;
|
|
19
|
-
|
|
20
|
-
this._container = document.createElement('div');
|
|
21
|
-
|
|
22
|
-
if (className) {
|
|
23
|
-
DOMUtils.addClass(this._container, className);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
document.body.appendChild(this._container);
|
|
27
|
-
|
|
28
|
-
this._renderLayer();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
componentDidUpdate(prevProps, prevState) {
|
|
32
|
-
this._renderLayer();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
componentWillUnmount() {
|
|
36
|
-
unmountComponentAtNode(this._container);
|
|
37
|
-
document.body.removeChild(this._container);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
_renderLayer() {
|
|
41
|
-
const {store, children} = this.props;
|
|
42
|
-
|
|
43
|
-
if (store) {
|
|
44
|
-
renderDOM(
|
|
45
|
-
<Provider store={store}>{children}</Provider>,
|
|
46
|
-
this._container
|
|
47
|
-
);
|
|
48
|
-
} else {
|
|
49
|
-
renderDOM(children, this._container);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
render() {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
describe('<RenderInBody />', () => {
|
|
2
|
-
context('Display', () => {
|
|
3
|
-
context('Default', () => {
|
|
4
|
-
it('Renders properly and passes props through', () => {
|
|
5
|
-
cy.visitStory('v1/RenderInBody/Display', 'Default')
|
|
6
|
-
.get('.RenderInBody-div')
|
|
7
|
-
.contains(`I'm in the body!`)
|
|
8
|
-
.parent()
|
|
9
|
-
.parent()
|
|
10
|
-
.then($el => {
|
|
11
|
-
expect($el.prop('tagName').toLowerCase()).to.eq('body');
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
context('Redux', () => {
|
|
17
|
-
it('Passes values through from a Redux store properly', () => {
|
|
18
|
-
cy.visitStory('v1/RenderInBody/Display', 'Redux')
|
|
19
|
-
.get('.ReduxConsumer')
|
|
20
|
-
.contains('Redux items here!');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
});
|
package/styles/Select/Select.jsx
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import isEmpty from 'lodash/isEmpty';
|
|
2
|
-
import React, {Component, cloneElement} from 'react';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
|
-
import cn from 'classnames';
|
|
5
|
-
import IconChevronDown from '@spothero/icons/chevron-down';
|
|
6
|
-
import DOMUtils from '@spothero/utils/dom';
|
|
7
|
-
import Loader from 'v1/components/Loader/Loader';
|
|
8
|
-
import omit from 'lodash/omit';
|
|
9
|
-
// import {
|
|
10
|
-
// SelectItemPropTypes,
|
|
11
|
-
// OptgroupPropTypes
|
|
12
|
-
// } from './SelectItemPropTypes';
|
|
13
|
-
|
|
14
|
-
export default class Select extends Component {
|
|
15
|
-
static propTypes = {
|
|
16
|
-
/** Additional class(es) to add to the component. */
|
|
17
|
-
className: PropTypes.string,
|
|
18
|
-
/** A unique name which is used to identify this element. If belonging to a form this is used as the key for serialization and is required. */
|
|
19
|
-
name: PropTypes.string,
|
|
20
|
-
/** A custom label (typically a Label component) to display above the select. */
|
|
21
|
-
label: PropTypes.element,
|
|
22
|
-
/** Adds an initial, unselectable item to display in the dropdown. */
|
|
23
|
-
placeholder: PropTypes.string,
|
|
24
|
-
/** Whether this field is required during validation. */
|
|
25
|
-
required: PropTypes.bool,
|
|
26
|
-
/** Disallows user interaction. */
|
|
27
|
-
disabled: PropTypes.bool,
|
|
28
|
-
/** Whether to show a loading animation inside of the component. */
|
|
29
|
-
loading: PropTypes.bool,
|
|
30
|
-
/** An array of options or an array of option groups */
|
|
31
|
-
items: PropTypes.array.isRequired,
|
|
32
|
-
// items: PropTypes.oneOfType([
|
|
33
|
-
// PropTypes.arrayOf(PropTypes.shape(SelectItemPropTypes)),
|
|
34
|
-
// PropTypes.arrayOf(PropTypes.shape(OptgroupPropTypes))
|
|
35
|
-
// ]).isRequired,
|
|
36
|
-
/** A custom help element to place below the select. */
|
|
37
|
-
helpNode: PropTypes.node,
|
|
38
|
-
/** Applies proper styling to display this field inline in a form. */
|
|
39
|
-
inline: PropTypes.bool,
|
|
40
|
-
/** A custom icon (typically an Icon component) to show instead of the default arrow dropdown icon. */
|
|
41
|
-
dropdownIcon: PropTypes.element,
|
|
42
|
-
/** A custom icon (typically an Icon component) to show in the select field. */
|
|
43
|
-
icon: PropTypes.element,
|
|
44
|
-
/** If an icon is provided, whether it should appear on the left or right side of the input field. */
|
|
45
|
-
iconPosition: PropTypes.oneOf(['left', 'right']),
|
|
46
|
-
/** The default value to display in the select when mounted. */
|
|
47
|
-
defaultValue: PropTypes.string,
|
|
48
|
-
/** A custom validation error (typically a FormElementError component) to display when this component is part of a form and is invalid. */
|
|
49
|
-
error: PropTypes.element,
|
|
50
|
-
/**
|
|
51
|
-
* Function to execute when the select changes.
|
|
52
|
-
*
|
|
53
|
-
* @param {Object} item - The value/label pair of the selection.
|
|
54
|
-
*/
|
|
55
|
-
onChange: PropTypes.func,
|
|
56
|
-
/**
|
|
57
|
-
* Function to execute when the select is focused.
|
|
58
|
-
*
|
|
59
|
-
* @param {SyntheticEvent} evt - The React `SyntheticEvent`.
|
|
60
|
-
*/
|
|
61
|
-
onFocus: PropTypes.func,
|
|
62
|
-
/**
|
|
63
|
-
* Function to execute when the element is marked as invalid by a validation check.
|
|
64
|
-
*
|
|
65
|
-
* @param {ValidityState} validationState - ValidityState object for more fine grained control over error handling in a parent component if desired.
|
|
66
|
-
*/
|
|
67
|
-
onInvalid: PropTypes.func,
|
|
68
|
-
};
|
|
69
|
-
static defaultProps = {
|
|
70
|
-
iconPosition: 'left',
|
|
71
|
-
};
|
|
72
|
-
state = {
|
|
73
|
-
validationState: null,
|
|
74
|
-
};
|
|
75
|
-
_isFirstRender = true;
|
|
76
|
-
|
|
77
|
-
componentDidMount() {
|
|
78
|
-
// if a placeholder is present on first render, we want to use this for setting the placeholder class to avoid a flash
|
|
79
|
-
// in the coloring of the placeholder text in the field on initial load
|
|
80
|
-
this._isFirstRender = false;
|
|
81
|
-
this._select.addEventListener('invalid', this._onInvalid);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
componentDidUpdate(prevProps, prevState, snapshot) {
|
|
85
|
-
this._checkIfDisabledSelected();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
componentWillUnmount() {
|
|
89
|
-
this._select.removeEventListener('invalid', this._onInvalid);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
_onInvalid = evt => {
|
|
93
|
-
evt.preventDefault();
|
|
94
|
-
|
|
95
|
-
const {onInvalid} = this.props;
|
|
96
|
-
const validationState = evt.currentTarget.validity;
|
|
97
|
-
|
|
98
|
-
this.setState({
|
|
99
|
-
validationState,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (onInvalid) {
|
|
103
|
-
onInvalid(validationState);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
_onChange = evt => {
|
|
108
|
-
const {items, onChange} = this.props;
|
|
109
|
-
const selectedItem = items
|
|
110
|
-
.reduce((options, {optgroup, ...item}) => {
|
|
111
|
-
return optgroup
|
|
112
|
-
? [...options, ...optgroup]
|
|
113
|
-
: [...options, item];
|
|
114
|
-
}, [])
|
|
115
|
-
.find(({value}) => value === evt.target.value);
|
|
116
|
-
|
|
117
|
-
this.setState({
|
|
118
|
-
validationState: null,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (onChange) {
|
|
122
|
-
onChange(selectedItem);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
_checkIfDisabledSelected() {
|
|
127
|
-
// if a disabled option is selected at first, make it the same color as placeholder as thats typically why its selected
|
|
128
|
-
if (isEmpty(this._select.value)) {
|
|
129
|
-
DOMUtils.addClass(this._select, 'FormElement-item-placeholder');
|
|
130
|
-
} else {
|
|
131
|
-
DOMUtils.removeClass(this._select, 'FormElement-item-placeholder');
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
_renderOptgroup = ({label, optgroup}) => {
|
|
136
|
-
return (
|
|
137
|
-
<optgroup key={label} label={label}>
|
|
138
|
-
{optgroup.map(this._renderItem)}
|
|
139
|
-
</optgroup>
|
|
140
|
-
);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
_renderItem = ({disabled = false, label = '', value = ''}, i) => {
|
|
144
|
-
return (
|
|
145
|
-
<option key={i} value={value} disabled={disabled}>
|
|
146
|
-
{label}
|
|
147
|
-
</option>
|
|
148
|
-
);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Getter for the select DOM element.
|
|
153
|
-
*
|
|
154
|
-
* @public
|
|
155
|
-
* @returns {Element} - The select element.
|
|
156
|
-
*/
|
|
157
|
-
get node() {
|
|
158
|
-
return this._select;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
render() {
|
|
162
|
-
const {
|
|
163
|
-
className,
|
|
164
|
-
name,
|
|
165
|
-
items,
|
|
166
|
-
disabled,
|
|
167
|
-
loading,
|
|
168
|
-
error,
|
|
169
|
-
helpNode,
|
|
170
|
-
label,
|
|
171
|
-
dropdownIcon,
|
|
172
|
-
icon,
|
|
173
|
-
iconPosition,
|
|
174
|
-
required,
|
|
175
|
-
inline,
|
|
176
|
-
defaultValue,
|
|
177
|
-
placeholder,
|
|
178
|
-
onFocus,
|
|
179
|
-
...attrs
|
|
180
|
-
} = this.props;
|
|
181
|
-
|
|
182
|
-
const spreadProps = omit(attrs, ['onChange', 'onInvalid']);
|
|
183
|
-
const {validationState} = this.state;
|
|
184
|
-
const isValid = validationState ? validationState.valid : true;
|
|
185
|
-
const containerClasses = cn(
|
|
186
|
-
'Select',
|
|
187
|
-
'FormElement',
|
|
188
|
-
{'FormElement-inline': inline},
|
|
189
|
-
{'FormElement-with-icon': icon},
|
|
190
|
-
{[`FormElement-with-icon-${iconPosition}`]: icon},
|
|
191
|
-
{'FormElement-contains-error': !isValid},
|
|
192
|
-
className
|
|
193
|
-
);
|
|
194
|
-
const itemClasses = cn('FormElement-item', {
|
|
195
|
-
'FormElement-item-placeholder':
|
|
196
|
-
placeholder && !defaultValue && this._isFirstRender,
|
|
197
|
-
});
|
|
198
|
-
const customIconClasses = cn(
|
|
199
|
-
'Select-dropdown-icon',
|
|
200
|
-
dropdownIcon ? dropdownIcon.props.className : null
|
|
201
|
-
);
|
|
202
|
-
const newDefaultValue =
|
|
203
|
-
placeholder && !defaultValue ? '' : defaultValue;
|
|
204
|
-
|
|
205
|
-
return (
|
|
206
|
-
<div className={containerClasses}>
|
|
207
|
-
{label}
|
|
208
|
-
<div className="FormElement-control">
|
|
209
|
-
<select
|
|
210
|
-
ref={node => {
|
|
211
|
-
this._select = node;
|
|
212
|
-
}}
|
|
213
|
-
className={itemClasses}
|
|
214
|
-
name={name}
|
|
215
|
-
required={required}
|
|
216
|
-
disabled={disabled}
|
|
217
|
-
defaultValue={newDefaultValue}
|
|
218
|
-
onFocus={onFocus}
|
|
219
|
-
onChange={this._onChange}
|
|
220
|
-
{...spreadProps}
|
|
221
|
-
>
|
|
222
|
-
{placeholder && (
|
|
223
|
-
<option value="" disabled>
|
|
224
|
-
{placeholder}
|
|
225
|
-
</option>
|
|
226
|
-
)}
|
|
227
|
-
{items.map((item, i) => {
|
|
228
|
-
return item.optgroup
|
|
229
|
-
? this._renderOptgroup(item)
|
|
230
|
-
: this._renderItem(item, i);
|
|
231
|
-
})}
|
|
232
|
-
</select>
|
|
233
|
-
{loading ? (
|
|
234
|
-
<Loader size={16} borderWidth={2} />
|
|
235
|
-
) : dropdownIcon ? (
|
|
236
|
-
cloneElement(dropdownIcon, {
|
|
237
|
-
className: customIconClasses,
|
|
238
|
-
})
|
|
239
|
-
) : (
|
|
240
|
-
<IconChevronDown className="Select-dropdown-icon" />
|
|
241
|
-
)}
|
|
242
|
-
<span className="Select-icon">{icon}</span>
|
|
243
|
-
</div>
|
|
244
|
-
{helpNode && (
|
|
245
|
-
<div className="FormElement-help-text">{helpNode}</div>
|
|
246
|
-
)}
|
|
247
|
-
{!isValid && error}
|
|
248
|
-
</div>
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
}
|