@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
package/styles/Form/Form.jsx
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import React, {Component} from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import classNames from 'classnames';
|
|
4
|
-
import serialize from 'form-serialize';
|
|
5
|
-
import AnimationUtils from '@spothero/utils/animation';
|
|
6
|
-
import DOMUtils from '@spothero/utils/dom';
|
|
7
|
-
|
|
8
|
-
export default class Form extends Component {
|
|
9
|
-
static propTypes = {
|
|
10
|
-
/** Additional class(es) to add to the component. */
|
|
11
|
-
className: PropTypes.string,
|
|
12
|
-
/** A unique ID which is used to identify this element for accessibility purposes on the client as well as the server. */
|
|
13
|
-
id: PropTypes.string.isRequired,
|
|
14
|
-
/** The form elements to display in the form. */
|
|
15
|
-
children: PropTypes.node.isRequired,
|
|
16
|
-
/** If the form is invalid and animateToInvalid() is called, this will be the element to scroll inside of to the first invalid form item. */
|
|
17
|
-
invalidElementToScroll: PropTypes.node,
|
|
18
|
-
/** The offset to add to the invalid element scroll action. Useful if there is fixed content in the parent and an offset needs to be accounted for. */
|
|
19
|
-
invalidElementScrollOffset: PropTypes.number,
|
|
20
|
-
/**
|
|
21
|
-
* The function to execute when the form is submitted.
|
|
22
|
-
*
|
|
23
|
-
* @param {SyntheticEvent} evt - The React `SyntheticEvent`.
|
|
24
|
-
*/
|
|
25
|
-
onSubmit: PropTypes.func,
|
|
26
|
-
};
|
|
27
|
-
static defaultProps = {
|
|
28
|
-
invalidElementScrollOffset: 0,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
constructor(props) {
|
|
32
|
-
super(props);
|
|
33
|
-
|
|
34
|
-
if (!props.id) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`The prop 'id' is required to make Form accessible for users using assistive technologies such as screen readers. Check the render method of Form.`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
_onSubmit = evt => {
|
|
42
|
-
evt.preventDefault();
|
|
43
|
-
|
|
44
|
-
const {onSubmit} = this.props;
|
|
45
|
-
const form = evt.currentTarget;
|
|
46
|
-
|
|
47
|
-
// for browsers that don't run validation checks on submit
|
|
48
|
-
if (form.checkValidity && !form.checkValidity()) {
|
|
49
|
-
return;
|
|
50
|
-
} else {
|
|
51
|
-
if (onSubmit) {
|
|
52
|
-
onSubmit(evt);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Animates the Form component to the first invalid field.
|
|
59
|
-
*
|
|
60
|
-
* @public
|
|
61
|
-
* @returns {void}
|
|
62
|
-
*/
|
|
63
|
-
animateToInvalid() {
|
|
64
|
-
if (this._form.checkValidity && !this._form.checkValidity()) {
|
|
65
|
-
const {
|
|
66
|
-
id,
|
|
67
|
-
invalidElementToScroll,
|
|
68
|
-
invalidElementScrollOffset,
|
|
69
|
-
} = this.props;
|
|
70
|
-
|
|
71
|
-
// setTimeout to ensure elements are rendered
|
|
72
|
-
setTimeout(() => {
|
|
73
|
-
const errorField = DOMUtils.el(
|
|
74
|
-
`#Form-${id} .FormElement-contains-error`
|
|
75
|
-
);
|
|
76
|
-
const firstInvalid =
|
|
77
|
-
errorField.length > 0 ? errorField[0] : errorField;
|
|
78
|
-
const yPos =
|
|
79
|
-
firstInvalid.offsetTop + invalidElementScrollOffset;
|
|
80
|
-
const elToScroll =
|
|
81
|
-
invalidElementToScroll || document.scrollingElement;
|
|
82
|
-
|
|
83
|
-
AnimationUtils.scrollTo(elToScroll, yPos);
|
|
84
|
-
}, 0);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Checks if the form is valid and ready for submission.
|
|
90
|
-
*
|
|
91
|
-
* @public
|
|
92
|
-
* @returns {Boolean} - Whether the form is valid or not.
|
|
93
|
-
*/
|
|
94
|
-
isValid() {
|
|
95
|
-
return this._form.checkValidity ? this._form.checkValidity() : true;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Resets user input from the form fields.
|
|
100
|
-
*
|
|
101
|
-
* @public
|
|
102
|
-
* @returns {void}
|
|
103
|
-
*/
|
|
104
|
-
reset() {
|
|
105
|
-
this._form.reset();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Provides the form data as an object for submission.
|
|
110
|
-
*
|
|
111
|
-
* @public
|
|
112
|
-
* @param {Boolean} [withEmpty=false] - Whether to serialize the empty fields.
|
|
113
|
-
* @returns {Object} - The form data to submit.
|
|
114
|
-
*/
|
|
115
|
-
submit(withEmpty = false) {
|
|
116
|
-
if (!this.isValid()) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return serialize(this._form, {hash: true, empty: withEmpty});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Getter for the form DOM element.
|
|
125
|
-
*
|
|
126
|
-
* @public
|
|
127
|
-
* @returns {Element} - The form element.
|
|
128
|
-
*/
|
|
129
|
-
get node() {
|
|
130
|
-
return this._form;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
render() {
|
|
134
|
-
const {className, id, children} = this.props;
|
|
135
|
-
const classes = classNames('Form', className);
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<form
|
|
139
|
-
ref={node => {
|
|
140
|
-
this._form = node;
|
|
141
|
-
}}
|
|
142
|
-
className={classes}
|
|
143
|
-
id={`Form-${id}`}
|
|
144
|
-
onSubmit={this._onSubmit}
|
|
145
|
-
action=""
|
|
146
|
-
>
|
|
147
|
-
{children}
|
|
148
|
-
</form>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
}
|
package/styles/Form/Form.spec.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
const name = 'Jane';
|
|
2
|
-
const email = 'jane@spothero.com';
|
|
3
|
-
const item = 'item-2';
|
|
4
|
-
|
|
5
|
-
describe('<Form />', () => {
|
|
6
|
-
context('Display', () => {
|
|
7
|
-
it('Renders, shows/clears errors, and submits proper data', () => {
|
|
8
|
-
cy.visitStory('v1/Form/Display', 'Default', {
|
|
9
|
-
onBeforeLoad: contentWindow => {
|
|
10
|
-
contentWindow.onFormSubmit = evt => {
|
|
11
|
-
console.log('onFormSubmit'); // eslint-disable-line no-console
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
cy.stub(contentWindow, 'onFormSubmit').as('formSubmit');
|
|
15
|
-
},
|
|
16
|
-
})
|
|
17
|
-
.get('.Button')
|
|
18
|
-
.as('submit')
|
|
19
|
-
// renders the form
|
|
20
|
-
.get('.Form')
|
|
21
|
-
.get('@submit')
|
|
22
|
-
.click()
|
|
23
|
-
// shows errors correctly
|
|
24
|
-
.get('.FormElement-contains-error')
|
|
25
|
-
.should('have.length', 3)
|
|
26
|
-
.get('.FormGroupError')
|
|
27
|
-
.should('exist')
|
|
28
|
-
// does not call the submit method
|
|
29
|
-
.get('@formSubmit')
|
|
30
|
-
.should('not.be.called')
|
|
31
|
-
// fill the form with data
|
|
32
|
-
.get('.TextInput')
|
|
33
|
-
.eq(0)
|
|
34
|
-
.as('name')
|
|
35
|
-
.fillTextInput({alias: 'name', text: name})
|
|
36
|
-
.get('.TextInput')
|
|
37
|
-
.eq(1)
|
|
38
|
-
.as('email')
|
|
39
|
-
.fillTextInput({alias: 'email', text: email})
|
|
40
|
-
.get('.Select')
|
|
41
|
-
.as('item')
|
|
42
|
-
.selectItem({alias: 'item', value: item})
|
|
43
|
-
// clears errors after fields are filled out
|
|
44
|
-
.get('.FormElement-contains-error')
|
|
45
|
-
.should('not.exist')
|
|
46
|
-
.get('.FormGroupError')
|
|
47
|
-
.should('not.exist')
|
|
48
|
-
.get('@submit')
|
|
49
|
-
.click()
|
|
50
|
-
// submits the form with the correct data
|
|
51
|
-
.get('@formSubmit')
|
|
52
|
-
.should('be.calledWith', {
|
|
53
|
-
name,
|
|
54
|
-
email,
|
|
55
|
-
item,
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
context('Methods', () => {
|
|
61
|
-
it('Public methods function properly', () => {
|
|
62
|
-
cy.visitStory('v1/Form/Methods', 'Methods', {
|
|
63
|
-
onBeforeLoad: contentWindow => {
|
|
64
|
-
contentWindow.onFormSubmit = evt => {
|
|
65
|
-
console.log('onFormSubmit'); // eslint-disable-line no-console
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
contentWindow.onFormSubmitEmpty = evt => {
|
|
69
|
-
console.log('onFormSubmitEmpty'); // eslint-disable-line no-console
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
contentWindow.onFormValidityCheck = evt => {
|
|
73
|
-
console.log('onFormValidityCheck'); // eslint-disable-line no-console
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
contentWindow.onFormNode = evt => {
|
|
77
|
-
console.log('onFormNode'); // eslint-disable-line no-console
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
cy.stub(contentWindow, 'onFormSubmit').as('formSubmit');
|
|
81
|
-
|
|
82
|
-
cy.stub(contentWindow, 'onFormSubmitEmpty').as(
|
|
83
|
-
'formSubmitEmpty'
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
cy.stub(contentWindow, 'onFormValidityCheck').as(
|
|
87
|
-
'formValidity'
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
cy.stub(contentWindow, 'onFormNode').as('formNode');
|
|
91
|
-
},
|
|
92
|
-
})
|
|
93
|
-
// alias validity button
|
|
94
|
-
.get('.FormMethods-check-validity')
|
|
95
|
-
.as('buttonValidity')
|
|
96
|
-
// check validity
|
|
97
|
-
.click()
|
|
98
|
-
.get('@formValidity')
|
|
99
|
-
.should('be.calledWith', false)
|
|
100
|
-
// fill form
|
|
101
|
-
.get('.TextInput')
|
|
102
|
-
.eq(0)
|
|
103
|
-
.as('name')
|
|
104
|
-
.fillTextInput({alias: 'name', text: name})
|
|
105
|
-
.get('.TextInput')
|
|
106
|
-
.eq(1)
|
|
107
|
-
.as('email')
|
|
108
|
-
.fillTextInput({alias: 'email', text: email})
|
|
109
|
-
// check validity after fields are filled
|
|
110
|
-
.get('@buttonValidity')
|
|
111
|
-
.click()
|
|
112
|
-
.get('@formValidity')
|
|
113
|
-
.should('be.calledWith', true)
|
|
114
|
-
// check submit with no empty fields
|
|
115
|
-
.get('.FormMethods-submit')
|
|
116
|
-
.click()
|
|
117
|
-
.get('@formSubmit')
|
|
118
|
-
.should('be.calledWith', {
|
|
119
|
-
name,
|
|
120
|
-
email,
|
|
121
|
-
})
|
|
122
|
-
// check submit with empty fields
|
|
123
|
-
.get('.FormMethods-submit-empty')
|
|
124
|
-
.click()
|
|
125
|
-
.get('@formSubmitEmpty')
|
|
126
|
-
.should('be.calledWith', {
|
|
127
|
-
name,
|
|
128
|
-
email,
|
|
129
|
-
position: '',
|
|
130
|
-
})
|
|
131
|
-
// reset and make sure fields were cleared
|
|
132
|
-
.get('.FormMethods-reset')
|
|
133
|
-
.click()
|
|
134
|
-
.confirmTextInput({alias: 'name', value: ''})
|
|
135
|
-
.confirmTextInput({alias: 'email', value: ''})
|
|
136
|
-
// check the form node returns correctly
|
|
137
|
-
.get('.FormMethods-node')
|
|
138
|
-
.click()
|
|
139
|
-
.get('@formNode')
|
|
140
|
-
.should('be.calledWithMatch', value => {
|
|
141
|
-
return (
|
|
142
|
-
value.nodeName &&
|
|
143
|
-
value.nodeName.toLowerCase() === 'form'
|
|
144
|
-
);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import classNames from 'classnames';
|
|
4
|
-
|
|
5
|
-
const FormElementError = ({className, children}) => {
|
|
6
|
-
const classes = classNames('FormElementError', className);
|
|
7
|
-
|
|
8
|
-
return <label className={classes}>{children}</label>;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
FormElementError.propTypes = {
|
|
12
|
-
/** Additional class(es) to add to the component. */
|
|
13
|
-
className: PropTypes.string,
|
|
14
|
-
/** The error node to display. */
|
|
15
|
-
children: PropTypes.node.isRequired,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default FormElementError;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import classNames from 'classnames';
|
|
4
|
-
|
|
5
|
-
const FormGroup = ({className, children, inline, error}) => {
|
|
6
|
-
const classes = classNames(
|
|
7
|
-
'FormGroup',
|
|
8
|
-
{'FormGroup-inline': inline},
|
|
9
|
-
{'FormGroup-contains-error': error},
|
|
10
|
-
className
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<div className={classes}>
|
|
15
|
-
{children}
|
|
16
|
-
{error}
|
|
17
|
-
</div>
|
|
18
|
-
);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
FormGroup.propTypes = {
|
|
22
|
-
/** Additional class(es) to add to the component. */
|
|
23
|
-
className: PropTypes.string,
|
|
24
|
-
/** The element(s) to render. */
|
|
25
|
-
children: PropTypes.node.isRequired,
|
|
26
|
-
/** Whether the elements in this group should be rendered inline. */
|
|
27
|
-
inline: PropTypes.bool,
|
|
28
|
-
/** A custom validation error (typically a FormGroupError component) to display when this component is part of a form and is invalid. */
|
|
29
|
-
error: PropTypes.element,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export default FormGroup;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import classNames from 'classnames';
|
|
4
|
-
import IconExclamationCircle from '@spothero/icons/exclamation-circle';
|
|
5
|
-
|
|
6
|
-
const FormGroupError = ({className, children}) => {
|
|
7
|
-
const classes = classNames('FormGroupError', className);
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<div className={classes}>
|
|
11
|
-
<IconExclamationCircle />
|
|
12
|
-
<div className="FormGroupError-content">{children}</div>
|
|
13
|
-
</div>
|
|
14
|
-
);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
FormGroupError.propTypes = {
|
|
18
|
-
/** Additional class(es) to add to the component. */
|
|
19
|
-
className: PropTypes.string,
|
|
20
|
-
/** The error node to display. */
|
|
21
|
-
children: PropTypes.node.isRequired,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export default FormGroupError;
|
package/styles/Form/index.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import throttle from 'lodash/throttle';
|
|
2
|
-
import React, {Component} from 'react';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
|
-
import classNames from 'classnames';
|
|
5
|
-
import IconSearch from '@spothero/icons/search';
|
|
6
|
-
import EnvironmentUtils from '@spothero/utils/environment';
|
|
7
|
-
import AutoSuggestInput from '../AutoSuggestInput/AutoSuggestInput';
|
|
8
|
-
import TextInputPropTypes from '../TextInput/TextInputPropTypes';
|
|
9
|
-
|
|
10
|
-
export default class GooglePlacesSearchInput extends Component {
|
|
11
|
-
static propTypes = {
|
|
12
|
-
...TextInputPropTypes,
|
|
13
|
-
/** Function to execute when a suggestion is selected. */
|
|
14
|
-
onSuggestionSelect: PropTypes.func.isRequired,
|
|
15
|
-
/** Optional function to execute when input field is cleared. */
|
|
16
|
-
onClear: PropTypes.func,
|
|
17
|
-
/** Used to limit the suggestion results by country. */
|
|
18
|
-
country: PropTypes.oneOfType([
|
|
19
|
-
PropTypes.string,
|
|
20
|
-
PropTypes.arrayOf(PropTypes.string),
|
|
21
|
-
]),
|
|
22
|
-
/** Used to limit the suggestion results by certain google place types. */
|
|
23
|
-
types: PropTypes.array,
|
|
24
|
-
/** Used to limit the suggestion results by lat/long bounds. */
|
|
25
|
-
bounds: PropTypes.object,
|
|
26
|
-
/** The `google maps` object for using the autocomplete service. */
|
|
27
|
-
googleMaps: PropTypes.object.isRequired,
|
|
28
|
-
/** A custom element (typically a PoweredByGoogle component) to display at the bottom of the suggestion list. */
|
|
29
|
-
poweredByGoogleFooter: PropTypes.element.isRequired,
|
|
30
|
-
/** An object containing the bias options */
|
|
31
|
-
biasOptions: PropTypes.shape({
|
|
32
|
-
/** The location (lat and long), to bias the search results around.
|
|
33
|
-
* * @see https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompletionRequest.location
|
|
34
|
-
*/
|
|
35
|
-
location: PropTypes.shape({
|
|
36
|
-
latitude: PropTypes.number,
|
|
37
|
-
longitude: PropTypes.number,
|
|
38
|
-
}),
|
|
39
|
-
/** The radius, in meters, to bias the search results within - required when providing a location for biasing */
|
|
40
|
-
radius: PropTypes.number,
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
static defaultProps = {
|
|
44
|
-
placeholder: 'Search Address or Venue',
|
|
45
|
-
country: 'us',
|
|
46
|
-
icon: <IconSearch />,
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
constructor(props) {
|
|
50
|
-
super(props);
|
|
51
|
-
|
|
52
|
-
const {googleMaps} = props;
|
|
53
|
-
const isBrowser = EnvironmentUtils.isBrowser();
|
|
54
|
-
|
|
55
|
-
this._suggestionFetch = throttle(this._searchSuggestions, 500);
|
|
56
|
-
this._gmaps = googleMaps;
|
|
57
|
-
this._autocompleteService = isBrowser
|
|
58
|
-
? new this._gmaps.places.AutocompleteService()
|
|
59
|
-
: null;
|
|
60
|
-
this._geocoder = isBrowser ? new this._gmaps.Geocoder() : null;
|
|
61
|
-
|
|
62
|
-
this.state = {
|
|
63
|
-
suggestions: [],
|
|
64
|
-
autocompleteSessionToken: isBrowser
|
|
65
|
-
? new this._gmaps.places.AutocompleteSessionToken()
|
|
66
|
-
: null,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_onChange = evt => {
|
|
71
|
-
const {onChange} = this.props;
|
|
72
|
-
const {autocompleteSessionToken} = this.state;
|
|
73
|
-
const value = evt.target.value;
|
|
74
|
-
|
|
75
|
-
if (!autocompleteSessionToken) {
|
|
76
|
-
this.setState({
|
|
77
|
-
autocompleteSessionToken: new this._gmaps.places.AutocompleteSessionToken(),
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!value || value.length < 3) {
|
|
82
|
-
this.setState({
|
|
83
|
-
suggestions: [],
|
|
84
|
-
});
|
|
85
|
-
} else {
|
|
86
|
-
this._suggestionFetch(value);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (onChange) {
|
|
90
|
-
onChange(evt);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
_onSuggestionSelect = suggestion => {
|
|
95
|
-
const {onSuggestionSelect} = this.props;
|
|
96
|
-
const {place_id: placeId, description} = suggestion;
|
|
97
|
-
|
|
98
|
-
if (suggestion.location) {
|
|
99
|
-
onSuggestionSelect(suggestion);
|
|
100
|
-
} else {
|
|
101
|
-
this._geocoder.geocode(
|
|
102
|
-
placeId ? {placeId} : {address: description},
|
|
103
|
-
(results, status) => {
|
|
104
|
-
if (status !== this._gmaps.GeocoderStatus.OK) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const gmaps = results[0];
|
|
109
|
-
const geoCodedLocation = gmaps.geometry.location;
|
|
110
|
-
|
|
111
|
-
suggestion.label = description;
|
|
112
|
-
suggestion.gmaps = gmaps;
|
|
113
|
-
suggestion.location = {
|
|
114
|
-
lat: geoCodedLocation.lat(),
|
|
115
|
-
lng: geoCodedLocation.lng(),
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
onSuggestionSelect(suggestion);
|
|
119
|
-
}
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
this.setState({
|
|
124
|
-
autocompleteSessionToken: null,
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
_searchSuggestions = value => {
|
|
129
|
-
const {country, types, bounds, biasOptions} = this.props;
|
|
130
|
-
const {autocompleteSessionToken} = this.state;
|
|
131
|
-
|
|
132
|
-
const options = {
|
|
133
|
-
input: value,
|
|
134
|
-
...(bounds && {bounds}),
|
|
135
|
-
...(country && {
|
|
136
|
-
componentRestrictions: {
|
|
137
|
-
country,
|
|
138
|
-
},
|
|
139
|
-
}),
|
|
140
|
-
...(types && {types}),
|
|
141
|
-
...(biasOptions && {
|
|
142
|
-
location: new this._gmaps.LatLng(
|
|
143
|
-
biasOptions?.location?.latitude,
|
|
144
|
-
biasOptions?.location?.longitude
|
|
145
|
-
),
|
|
146
|
-
radius: biasOptions?.radius,
|
|
147
|
-
origin: new this._gmaps.LatLng(
|
|
148
|
-
biasOptions?.location?.latitude,
|
|
149
|
-
biasOptions?.location?.longitude
|
|
150
|
-
),
|
|
151
|
-
}),
|
|
152
|
-
sessionToken: autocompleteSessionToken,
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
this._autocompleteService.getPlacePredictions(options, suggestions => {
|
|
156
|
-
if (suggestions) {
|
|
157
|
-
suggestions.map(suggestion => {
|
|
158
|
-
suggestion.label = suggestion.description;
|
|
159
|
-
|
|
160
|
-
if (!suggestion.id) {
|
|
161
|
-
suggestion.id = suggestion.place_id;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return suggestion;
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
this.setState({
|
|
169
|
-
suggestions,
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Getter for the input DOM element.
|
|
176
|
-
*
|
|
177
|
-
* @public
|
|
178
|
-
* @returns {Element} - The input element.
|
|
179
|
-
*/
|
|
180
|
-
get node() {
|
|
181
|
-
return this._autosuggest.node;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
render() {
|
|
185
|
-
const {
|
|
186
|
-
className,
|
|
187
|
-
poweredByGoogleFooter,
|
|
188
|
-
types,
|
|
189
|
-
country,
|
|
190
|
-
onClear,
|
|
191
|
-
...textInputProps
|
|
192
|
-
} = this.props;
|
|
193
|
-
const {suggestions} = this.state;
|
|
194
|
-
const classes = classNames('GooglePlacesSearchInput', className);
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<div className={classes}>
|
|
198
|
-
<AutoSuggestInput
|
|
199
|
-
ref={node => {
|
|
200
|
-
this._autosuggest = node;
|
|
201
|
-
}}
|
|
202
|
-
{...textInputProps}
|
|
203
|
-
onChange={this._onChange}
|
|
204
|
-
suggestions={suggestions}
|
|
205
|
-
types={types}
|
|
206
|
-
country={country}
|
|
207
|
-
suggestionBoxFooter={poweredByGoogleFooter}
|
|
208
|
-
onClear={onClear}
|
|
209
|
-
onSuggestionSelect={this._onSuggestionSelect}
|
|
210
|
-
autoActivateFirstSuggest
|
|
211
|
-
/>
|
|
212
|
-
</div>
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
}
|