mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 12:43:11 +10:00
Merge pull request #687 from sharetribe/select-multiple-filter-component
Select multiple filter component
This commit is contained in:
commit
4f3652045b
17 changed files with 507 additions and 86 deletions
|
|
@ -1,37 +0,0 @@
|
|||
.filterTitle {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.toListingsButton {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
margin-left: -100px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
font-size: 1.4rem;
|
||||
padding: 0.5rem;
|
||||
color: #fff;
|
||||
background-color: #9b9b9b;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3em;
|
||||
padding: 1em;
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import React from 'react';
|
||||
import { NamedLink } from '../../components';
|
||||
import css from './FilterPanel.css';
|
||||
|
||||
const FilterPanel = () => (
|
||||
<div>
|
||||
<h1 className={css.filterTitle}>Filters</h1>
|
||||
<NamedLink className={css.toListingsButton} name="SearchListingsPage">
|
||||
See studios
|
||||
</NamedLink>
|
||||
<NamedLink className={css.close} name="SearchListingsPage">
|
||||
X
|
||||
</NamedLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default FilterPanel;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import React from 'react';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import FilterPanel from './FilterPanel';
|
||||
|
||||
describe('FilterPanel', () => {
|
||||
it('matches snapshot', () => {
|
||||
const tree = renderShallow(<FilterPanel />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FilterPanel matches snapshot 1`] = `
|
||||
<div>
|
||||
<h1>
|
||||
Filters
|
||||
</h1>
|
||||
<NamedLink
|
||||
name="SearchListingsPage"
|
||||
>
|
||||
See studios
|
||||
</NamedLink>
|
||||
<NamedLink
|
||||
name="SearchListingsPage"
|
||||
>
|
||||
X
|
||||
</NamedLink>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
const Form = props => {
|
||||
const { children, ...restProps } = props;
|
||||
const { children, contentRef, ...restProps } = props;
|
||||
|
||||
const formProps = {
|
||||
// These are mainly default values for the server
|
||||
|
|
@ -12,6 +12,9 @@ const Form = props => {
|
|||
method: 'post',
|
||||
action: '/',
|
||||
|
||||
// allow content ref function to be passed to the form
|
||||
ref: contentRef,
|
||||
|
||||
...restProps,
|
||||
};
|
||||
return <form {...formProps}>{children}</form>;
|
||||
|
|
@ -19,12 +22,14 @@ const Form = props => {
|
|||
|
||||
Form.defaultProps = {
|
||||
children: null,
|
||||
contentRef: null,
|
||||
};
|
||||
|
||||
const { node } = PropTypes;
|
||||
const { func, node } = PropTypes;
|
||||
|
||||
Form.propTypes = {
|
||||
children: node,
|
||||
contentRef: func,
|
||||
};
|
||||
|
||||
export default Form;
|
||||
|
|
|
|||
43
src/components/SelectMultipleFilter/SelectMultipleFilter.css
Normal file
43
src/components/SelectMultipleFilter/SelectMultipleFilter.css
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply --marketplaceButtonStylesSecondary;
|
||||
@apply --marketplaceSearchFilterLabelFontStyles;
|
||||
|
||||
padding: 9px 16px 10px 16px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background-color: var(--matterColorLight);
|
||||
border-color: transparent;
|
||||
text-decoration: none;
|
||||
box-shadow: var(--boxShadowFilterButton);
|
||||
}
|
||||
}
|
||||
|
||||
.labelSelected {
|
||||
@apply --marketplaceButtonStyles;
|
||||
@apply --marketplaceSearchFilterLabelFontStyles;
|
||||
font-weight: var(--fontWeightSemiBold);
|
||||
|
||||
padding: 9px 16px 10px 16px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--marketplaceColor);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border: 1px solid var(--marketplaceColorDark);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import SelectMultipleFilter from './SelectMultipleFilter';
|
||||
import { stringify, parse } from '../../util/urlHelpers';
|
||||
|
||||
const options = [
|
||||
{
|
||||
key: 'towels',
|
||||
label: 'Towels',
|
||||
},
|
||||
{
|
||||
key: 'bathroom',
|
||||
label: 'Bathroom',
|
||||
},
|
||||
{
|
||||
key: 'swimming_pool',
|
||||
label: 'Swimming pool',
|
||||
},
|
||||
{
|
||||
key: 'own_drinks',
|
||||
label: 'Own drinks allowed',
|
||||
},
|
||||
{
|
||||
key: 'jacuzzi',
|
||||
label: 'Jacuzzi',
|
||||
},
|
||||
{
|
||||
key: 'audiovisual_entertainment',
|
||||
label: 'Audiovisual entertainment',
|
||||
},
|
||||
{
|
||||
key: 'barbeque',
|
||||
label: 'Barbeque',
|
||||
},
|
||||
{
|
||||
key: 'own_food_allowed',
|
||||
label: 'Own food allowed',
|
||||
},
|
||||
];
|
||||
|
||||
const handleSubmit = (values, history) => {
|
||||
const queryParams = values ? `?${stringify({ pub_amenities: values.join(',') })}` : '';
|
||||
history.push(`${window.location.pathname}${queryParams}`);
|
||||
};
|
||||
|
||||
const AmenitiesFilterComponent = withRouter(props => {
|
||||
const { history, location } = props;
|
||||
|
||||
const params = parse(location.search);
|
||||
const amenities = params.pub_amenities;
|
||||
const initialValues = !!amenities ? amenities.split(',') : [];
|
||||
|
||||
return (
|
||||
<SelectMultipleFilter
|
||||
onSubmit={values => handleSubmit(values, history)}
|
||||
options={options}
|
||||
initialValues={initialValues}
|
||||
contentPlacementOffset={-14}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const AmenitiesFilter = {
|
||||
component: AmenitiesFilterComponent,
|
||||
props: {},
|
||||
group: 'misc',
|
||||
};
|
||||
178
src/components/SelectMultipleFilter/SelectMultipleFilter.js
Normal file
178
src/components/SelectMultipleFilter/SelectMultipleFilter.js
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import React, { Component } from 'react';
|
||||
import { array, arrayOf, func, number, string } from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { injectIntl, intlShape } from 'react-intl';
|
||||
import { toPairs } from 'lodash';
|
||||
|
||||
import SelectMultipleFilterForm from './SelectMultipleFilterForm';
|
||||
import css from './SelectMultipleFilter.css';
|
||||
|
||||
const KEY_CODE_ESCAPE = 27;
|
||||
|
||||
/**
|
||||
* Convert an array of option keys into
|
||||
* an object that can be passed to a redux
|
||||
* form as initial values.
|
||||
*/
|
||||
const keysToValues = keys => {
|
||||
return keys.reduce((map, key) => {
|
||||
map[key] = true;
|
||||
return map;
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert an object containing values received
|
||||
* from a redux form into an array of values.
|
||||
*/
|
||||
const valuesToKeys = values => {
|
||||
const entries = toPairs(values);
|
||||
return entries.filter(entry => entry[1] === true).map(entry => entry[0]);
|
||||
};
|
||||
|
||||
class SelectMultipleFilter extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { isOpen: false };
|
||||
this.filter = null;
|
||||
this.filterContent = null;
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClear = this.handleClear.bind(this);
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.toggleOpen = this.toggleOpen.bind(this);
|
||||
this.positionStyleForContent = this.positionStyleForContent.bind(this);
|
||||
}
|
||||
|
||||
handleSubmit(values) {
|
||||
const selectedKeys = valuesToKeys(values);
|
||||
this.setState({ isOpen: false });
|
||||
this.props.onSubmit(selectedKeys);
|
||||
}
|
||||
|
||||
handleClear() {
|
||||
this.setState({ isOpen: false });
|
||||
this.props.onSubmit(null);
|
||||
}
|
||||
|
||||
handleCancel() {
|
||||
const { onSubmit, initialValues } = this.props;
|
||||
this.setState({ isOpen: false });
|
||||
onSubmit(initialValues);
|
||||
}
|
||||
|
||||
handleBlur(event) {
|
||||
// FocusEvent is fired faster than the link elements native click handler
|
||||
// gets its own event. Therefore, we need to check the origin of this FocusEvent.
|
||||
if (!this.filter.contains(event.relatedTarget)) {
|
||||
this.setState({ isOpen: false });
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
// Gather all escape presses to close menu
|
||||
if (e.keyCode === KEY_CODE_ESCAPE) {
|
||||
this.toggleOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
toggleOpen(enforcedState) {
|
||||
if (enforcedState) {
|
||||
this.setState({ isOpen: enforcedState });
|
||||
} else {
|
||||
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
|
||||
}
|
||||
}
|
||||
|
||||
positionStyleForContent() {
|
||||
if (this.filter && this.filterContent) {
|
||||
// Render the filter content to the right from the menu
|
||||
// unless there's no space in which case it is rendered
|
||||
// to the left
|
||||
const distanceToRight = window.innerWidth - this.filter.getBoundingClientRect().right;
|
||||
const labelWidth = this.filter.offsetWidth;
|
||||
const contentWidth = this.filterContent.offsetWidth;
|
||||
const contentWidthBiggerThanLabel = contentWidth - labelWidth;
|
||||
const renderToRight = distanceToRight > contentWidthBiggerThanLabel;
|
||||
const contentPlacementOffset = this.props.contentPlacementOffset;
|
||||
|
||||
const offset = renderToRight
|
||||
? { left: contentPlacementOffset }
|
||||
: { right: contentPlacementOffset };
|
||||
// set a min-width if the content is narrower than the label
|
||||
const minWidth = contentWidth < labelWidth ? { minWidth: labelWidth } : null;
|
||||
|
||||
return { ...offset, ...minWidth };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { rootClassName, className, options, initialValues, intl } = this.props;
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
const hasInitialValues = initialValues.length > 0;
|
||||
const labelStyles = hasInitialValues ? css.labelSelected : css.label;
|
||||
const label = hasInitialValues
|
||||
? intl.formatMessage(
|
||||
{ id: 'SelectMultipleFilterMobile.labelSelected' },
|
||||
{ count: initialValues.length }
|
||||
)
|
||||
: intl.formatMessage({ id: 'SelectMultipleFilterMobile.label' });
|
||||
|
||||
const contentStyle = this.positionStyleForContent();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
ref={node => {
|
||||
this.filter = node;
|
||||
}}
|
||||
>
|
||||
<button className={labelStyles} onClick={() => this.toggleOpen()}>
|
||||
{label}
|
||||
</button>
|
||||
<SelectMultipleFilterForm
|
||||
onSubmit={this.handleSubmit}
|
||||
initialValues={keysToValues(initialValues)}
|
||||
enableReinitialize={true}
|
||||
onClear={this.handleClear}
|
||||
onCancel={this.handleCancel}
|
||||
options={options}
|
||||
isOpen={this.state.isOpen}
|
||||
intl={intl}
|
||||
contentRef={node => {
|
||||
this.filterContent = node;
|
||||
}}
|
||||
style={contentStyle}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectMultipleFilter.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
initialValues: [],
|
||||
contentPlacementOffset: 0,
|
||||
};
|
||||
|
||||
SelectMultipleFilter.propTypes = {
|
||||
rootClassName: string,
|
||||
className: string,
|
||||
onSubmit: func.isRequired,
|
||||
options: array.isRequired,
|
||||
initialValues: arrayOf(string),
|
||||
contentPlacementOffset: number,
|
||||
|
||||
// form injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(SelectMultipleFilter);
|
||||
102
src/components/SelectMultipleFilter/SelectMultipleFilterForm.css
Normal file
102
src/components/SelectMultipleFilter/SelectMultipleFilterForm.css
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
/* By default hide the content */
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
/* Position */
|
||||
position: absolute;
|
||||
z-index: var(--zIndexPopup);
|
||||
|
||||
/* Layout */
|
||||
margin-top: 7px;
|
||||
padding: 15px 40px 20px 30px;
|
||||
|
||||
/* Borders */
|
||||
background-color: var(--matterColorLight);
|
||||
border-top: 1px solid var(--matterColorNegative);
|
||||
box-shadow: var(--boxShadowPopup);
|
||||
border-radius: 4px;
|
||||
transition: var(--transitionStyleButton);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.isOpen {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.fieldGroup {
|
||||
margin-bottom: 35px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.buttonsWrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.clearButton {
|
||||
@apply --marketplaceH4FontStyles;
|
||||
font-weight: var(--fontWeightMedium);
|
||||
color: var(--matterColorAnti);
|
||||
|
||||
/* Layout */
|
||||
margin: 0 auto 0 0;
|
||||
padding: 0;
|
||||
|
||||
/* Override button styles */
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: var(--matterColor);
|
||||
transition: width var(--transitionStyleButton);
|
||||
}
|
||||
}
|
||||
|
||||
.cancelButton {
|
||||
@apply --marketplaceH4FontStyles;
|
||||
font-weight: var(--fontWeightMedium);
|
||||
color: var(--matterColorAnti);
|
||||
|
||||
/* Layout */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
/* Override button styles */
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: var(--matterColor);
|
||||
transition: width var(--transitionStyleButton);
|
||||
}
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
@apply --marketplaceH4FontStyles;
|
||||
font-weight: var(--fontWeightMedium);
|
||||
color: var(--marketplaceColor);
|
||||
|
||||
/* Layout */
|
||||
margin: 0 0 0 19px;
|
||||
padding: 0;
|
||||
|
||||
/* Override button styles */
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: var(--marketplaceColorDark);
|
||||
transition: width var(--transitionStyleButton);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import React from 'react';
|
||||
import { arrayOf, bool, func, node, object, shape, string } from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { compose } from 'redux';
|
||||
import { reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import { injectIntl, intlShape } from 'react-intl';
|
||||
|
||||
import { FieldGroupCheckbox, Form } from '../../components';
|
||||
import css from './SelectMultipleFilterForm.css';
|
||||
|
||||
const SelectMultipleFilterFormComponent = props => {
|
||||
const {
|
||||
form,
|
||||
destroy,
|
||||
reset,
|
||||
handleSubmit,
|
||||
onClear,
|
||||
onCancel,
|
||||
options,
|
||||
isOpen,
|
||||
contentRef,
|
||||
style,
|
||||
intl,
|
||||
} = props;
|
||||
const classes = classNames(css.root, { [css.isOpen]: isOpen });
|
||||
|
||||
const handleClear = () => {
|
||||
// clear the redux form state
|
||||
destroy();
|
||||
onClear();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
// reset the redux form to initialValues
|
||||
reset();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
const clear = intl.formatMessage({ id: 'SelectMultipleFilterMobile.clear' });
|
||||
const cancel = intl.formatMessage({ id: 'SelectMultipleFilterMobile.cancel' });
|
||||
const submit = intl.formatMessage({ id: 'SelectMultipleFilterMobile.submit' });
|
||||
|
||||
return (
|
||||
<Form
|
||||
className={classes}
|
||||
onSubmit={handleSubmit}
|
||||
tabIndex="0"
|
||||
contentRef={contentRef}
|
||||
style={style}
|
||||
>
|
||||
<FieldGroupCheckbox
|
||||
className={css.fieldGroup}
|
||||
id={`${form}.amenitiesFilter`}
|
||||
options={options}
|
||||
/>
|
||||
<div className={css.buttonsWrapper}>
|
||||
<button className={css.clearButton} type="button" onClick={handleClear}>
|
||||
{clear}
|
||||
</button>
|
||||
<button className={css.cancelButton} type="button" onClick={handleCancel}>
|
||||
{cancel}
|
||||
</button>
|
||||
<button className={css.submitButton} type="submit">
|
||||
{submit}
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
SelectMultipleFilterFormComponent.defaultProps = {
|
||||
contentRef: null,
|
||||
style: null,
|
||||
};
|
||||
|
||||
SelectMultipleFilterFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
onClear: func.isRequired,
|
||||
onCancel: func.isRequired,
|
||||
options: arrayOf(
|
||||
shape({
|
||||
key: string.isRequired,
|
||||
label: node.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
isOpen: bool.isRequired,
|
||||
contentRef: func,
|
||||
style: object,
|
||||
|
||||
// form injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const defaultFormName = 'SelectMultipleFilterForm';
|
||||
|
||||
export default compose(reduxForm({ form: defaultFormName }), injectIntl)(
|
||||
SelectMultipleFilterFormComponent
|
||||
);
|
||||
|
|
@ -29,7 +29,6 @@ export {
|
|||
export { default as EditListingWizard } from './EditListingWizard/EditListingWizard';
|
||||
export { default as ExpandingTextarea } from './ExpandingTextarea/ExpandingTextarea';
|
||||
export { default as ExternalLink } from './ExternalLink/ExternalLink';
|
||||
export { default as FilterPanel } from './FilterPanel/FilterPanel';
|
||||
export { default as FieldBirthdayInput } from './FieldBirthdayInput/FieldBirthdayInput';
|
||||
export { default as FieldCheckbox } from './FieldCheckbox/FieldCheckbox';
|
||||
export { default as FieldCurrencyInput } from './FieldCurrencyInput/FieldCurrencyInput';
|
||||
|
|
@ -108,6 +107,7 @@ export { default as SectionHowItWorks } from './SectionHowItWorks/SectionHowItWo
|
|||
export { default as SectionLocations } from './SectionLocations/SectionLocations';
|
||||
export { default as SectionThumbnailLinks } from './SectionThumbnailLinks/SectionThumbnailLinks';
|
||||
export { default as SelectField } from './SelectField/SelectField';
|
||||
export { default as SelectMultipleFilter } from './SelectMultipleFilter/SelectMultipleFilter';
|
||||
export { default as SelectSingleFilter } from './SelectSingleFilter/SelectSingleFilter';
|
||||
export {
|
||||
default as SelectSingleFilterMobile,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
exports[`BookingDatesForm matches snapshot without selected dates 1`] = `
|
||||
<Form
|
||||
className={null}
|
||||
contentRef={null}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<FieldDateRangeInput
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
exports[`EditListingLocationForm matches snapshot 1`] = `
|
||||
<Form
|
||||
className=""
|
||||
contentRef={null}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Component
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
exports[`EditListingPhotosForm matches snapshot 1`] = `
|
||||
<Form
|
||||
className=""
|
||||
contentRef={null}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<AddImages
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
exports[`EditListingPoliciesForm matches snapshot 1`] = `
|
||||
<Form
|
||||
className=""
|
||||
contentRef={null}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<TextInputField
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import * as ReviewRating from './components/ReviewRating/ReviewRating.example';
|
|||
import * as Reviews from './components/Reviews/Reviews.example';
|
||||
import * as SectionThumbnailLinks from './components/SectionThumbnailLinks/SectionThumbnailLinks.example';
|
||||
import * as SelectField from './components/SelectField/SelectField.example';
|
||||
import * as SelectMultipleFilter from './components/SelectMultipleFilter/SelectMultipleFilter.example';
|
||||
import * as StripeBankAccountTokenInputField from './components/StripeBankAccountTokenInputField/StripeBankAccountTokenInputField.example';
|
||||
import * as TabNav from './components/TabNav/TabNav.example';
|
||||
import * as TabNavHorizontal from './components/TabNavHorizontal/TabNavHorizontal.example';
|
||||
|
|
@ -136,6 +137,7 @@ export {
|
|||
Reviews,
|
||||
SectionThumbnailLinks,
|
||||
SelectField,
|
||||
SelectMultipleFilter,
|
||||
SendMessageForm,
|
||||
SignupForm,
|
||||
StripeBankAccountTokenInputField,
|
||||
|
|
|
|||
|
|
@ -518,6 +518,11 @@
|
|||
"SectionLocations.listingsInLocation": "Saunas in {location}",
|
||||
"SectionLocations.subtitle": "We have more than 1000 saunas around Finland. Here are some of our most popular locations.",
|
||||
"SectionLocations.title": "We have wooden saunas, electric saunas, and even tent saunas.",
|
||||
"SelectMultipleFilterMobile.label": "Amenities",
|
||||
"SelectMultipleFilterMobile.labelSelected": "Amenities • {count}",
|
||||
"SelectMultipleFilterMobile.clear": "Clear",
|
||||
"SelectMultipleFilterMobile.cancel": "Cancel",
|
||||
"SelectMultipleFilterMobile.submit": "Apply",
|
||||
"SelectSingleFilter.clear": "Clear",
|
||||
"SelectSingleFilterMobile.clear": "Clear",
|
||||
"SendMessageForm.sendFailed": "Failed to send. Please try again.",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue