Merge pull request #680 from sharetribe/singledatepicker

Singledatepicker
This commit is contained in:
Vesa Luusua 2018-01-31 15:02:43 +02:00 committed by GitHub
commit d6707b7f1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 815 additions and 17 deletions

View file

@ -1,6 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOMServer from 'react-dom/server';
// react-dates needs to be initialized before using any react-dates component
// https://github.com/airbnb/react-dates#initialize
// NOTE: Initializing it here will initialize it also for app.test.js
import 'react-dates/initialize';
import Helmet from 'react-helmet';
import { BrowserRouter, StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';

View file

@ -0,0 +1,241 @@
@import '../../marketplace.css';
:root {
--selectionHeight: 36px;
--hoveredOverlayColor: rgba(255, 255, 255, 0.2);
--datepickerDistanceToTop: 252px;
}
.inputRoot {
/* Contain repainting to this component only */
transform: translate3d(0, 0, 0);
/* Override react-dates default styles to match input styles */
& :global(.SingleDatePicker) {
display: block;
}
& :global(.SingleDatePicker_picker__directionLeft) {
/* !important is added to top because react-dates uses inline style for height */
/* Similar problem as in issue: https://github.com/airbnb/react-dates/issues/947 */
top: 36px !important;
width: 100%;
min-height: calc(100vh - var(--datepickerDistanceToTop));
background-color: var(--marketplaceColor);
@media (--viewportMedium) {
/* !important is added to top because react-dates uses inline style for height */
/* Similar problem as in issue: https://github.com/airbnb/react-dates/issues/947 */
top: 40px !important;
min-height: auto;
box-shadow: var(--boxShadowPopup);
border-radius: 0 0 2px 2px;
}
}
& :global(.DateInput_input) {
@apply --marketplaceDefaultFontStyles;
@apply --marketplaceInputStyles;
border-bottom-style: solid;
border-bottom-width: 2px;
border-bottom-color: var(--attentionColor);
transition: border-bottom-color var(--transitionStyle);
transition: all 0.15s ease-out;
background: none;
@media (--viewportMedium) {
padding: 4px 0 9px 0;
border-bottom-width: 3px;
}
}
& :global(.SingleDatePickerInput) {
width: 100%;
display: flex;
justify-content: space-between;
border: none;
background: none;
}
& :global(.DayPicker__horizontal) {
margin: 0 auto;
background-color: var(--marketplaceColor);
box-shadow: none;
}
& :global(.CalendarMonth),
& :global(.CalendarMonthGrid) {
background-color: transparent;
}
& :global(.DateInput) {
display: block;
width: 100%;
}
& :global(.DayPicker_weekHeader) {
color: var(--matterColorLight);
top: 57px;
}
& :global(.DayPicker_weekHeader_li) {
font-weight: 400;
}
& :global(.CalendarMonth_caption) {
color: var(--matterColorLight);
@apply --marketplaceH2FontStyles;
margin: 1px 0 14px;
font-weight: 400;
@media (--viewportMedium) {
margin-top: 0;
margin-bottom: 0;
}
}
& :global(.DayPickerNavigation_button) {
color: var(--matterColorLight);
border: 0;
}
& :global(.CalendarDay__default) {
background-color: var(--marketplaceColor);
border: 0;
padding: 0;
width: 100%;
height: 100%;
}
& :global(.CalendarDay) {
@apply --marketplaceH4FontStyles;
color: var(--matterColorLight);
border: 0;
margin-top: 0;
margin-bottom: 0;
@media (--viewportMedium) {
margin-top: 0;
margin-bottom: 0;
}
}
/* Add an underline for '.renderedDay' */
& :global(.CalendarDay__today .renderedDay) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: var(--selectionHeight);
background-image: url("data:image/svg+xml;utf8,<svg width='14' height='2' viewBox='0 0 14 2' xmlns='http://www.w3.org/2000/svg'><path d='M0 0h14v2H0z' fill='%23FFF' fill-rule='evenodd'/></svg>");
background-position: center 28px;
}
/* Remove default bg-color and use our extra span instead '.renderedDay' */
& :global(.CalendarDay__hovered_span),
& :global(.CalendarDay__selected_span) {
background-image: transparent;
background-color: transparent;
}
& :global(.CalendarDay__hovered_span .renderedDay) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: var(--selectionHeight);
background-color: var(--hoveredOverlayColor);
}
& :global(.CalendarDay__selected_span .renderedDay) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: var(--selectionHeight);
background-color: var(--successColor);
transition: all 0.2s ease-out;
}
/* Remove default bg-color and use our extra span instead '.renderedDay' */
& :global(.CalendarDay__selected) {
background-color: transparent;
background-image: none;
}
& :global(.CalendarDay__selected .renderedDay) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: var(--selectionHeight);
background-color: var(--successColor);
border-radius: calc(var(--selectionHeight) / 2);
}
/* Remove default bg-color and use our extra span instead '.renderedDay' */
& :global(.CalendarDay__after-hovered) {
background-color: transparent;
}
& :global(.CalendarDay__after-hovered .renderedDay) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: var(--selectionHeight);
background-color: var(--hoveredOverlayColor);
}
& :global(.CalendarDay:hover .renderedDay) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: var(--selectionHeight);
background-color: var(--hoveredOverlayColor);
}
/* Remove default bg-color and use our extra span instead '.renderedDay' */
& :global(.CalendarDay__blocked_out_of_range),
& :global(.CalendarDay__blocked_out_of_range:active),
& :global(.CalendarDay__blocked_out_of_range:hover) {
background-color: transparent;
color: var(--marketplaceColorDark);
border: 0;
}
& :global(.CalendarDay__blocked_out_of_range .renderedDay) {
background-color: transparent;
}
& :global(.DateInput_fang) {
display: none;
}
}
/**
* Mobile margins mean that labels, inputs (incl separate borders) get margins,
* but calendar popup doesn't.
*/
.withMobileMargins {
& :global(.SingleDatePickerInput) {
width: calc(100vw - 48px);
margin: 0 24px;
@media (--viewportMedium) {
width: 100%;
margin: 0;
}
}
& :global(.SingleDatePicker_picker__directionLeft) {
/* !important is added to top because react-dates uses inline style for height */
/* Similar problem as in issue: https://github.com/airbnb/react-dates/issues/947 */
top: 34px !important;
border-top: 2px solid var(--matterColorDark);
min-height: calc(100vh - var(--datepickerDistanceToTop) + 2px);
@media (--viewportMedium) {
/* !important is added to top because react-dates uses inline style for height */
/* Similar problem as in issue: https://github.com/airbnb/react-dates/issues/947 */
top: 40px !important;
border-top: 0;
min-height: auto;
}
}
/* Create gutter between inputs */
& :global(.DateInput) {
width: 100%;
background: none;
}
}
.rootNextMonthIcon,
.rootPreviousMonthIcon {
stroke: var(--matterColorLight);
fill: var(--matterColorLight);
}

View file

@ -0,0 +1,219 @@
/**
* DateInput wraps SingleDatePicker from React-dates and gives a list of all default props we use.
* Styles for SingleDatePicker can be found from 'public/reactDates.css'.
*
* N.B. *isOutsideRange* in defaultProps is defining what dates are available to booking.
*/
import React, { Component } from 'react';
import { bool, func, instanceOf, shape, string } from 'prop-types';
import { SingleDatePicker, isInclusivelyAfterDay, isInclusivelyBeforeDay } from 'react-dates';
import { intlShape, injectIntl } from 'react-intl';
import classNames from 'classnames';
import moment from 'moment';
import config from '../../config';
import NextMonthIcon from './NextMonthIcon';
import PreviousMonthIcon from './PreviousMonthIcon';
import css from './DateInput.css';
export const HORIZONTAL_ORIENTATION = 'horizontal';
export const ANCHOR_LEFT = 'left';
// Possible configuration options of React-dates
const defaultProps = {
initialDate: null, // Possible initial date passed for the component
value: null, // Value should keep track of selected date.
// input related props
id: 'dateInput',
placeholder: null, // Handled inside component
disabled: false,
required: false,
readOnly: false,
screenReaderInputMessage: null, // Handled inside component
showClearDate: false,
customCloseIcon: null,
showDefaultInputIcon: false,
customInputIcon: null,
noBorder: true,
block: false,
small: false,
// calendar presentation and interaction related props
renderMonth: null,
orientation: HORIZONTAL_ORIENTATION,
anchorDirection: ANCHOR_LEFT,
horizontalMargin: 0,
withPortal: false,
withFullScreenPortal: false,
initialVisibleMonth: null,
firstDayOfWeek: config.i18n.firstDayOfWeek,
numberOfMonths: 1,
keepOpenOnDateSelect: false,
reopenPickerOnClearDate: false,
renderCalendarInfo: null,
hideKeyboardShortcutsPanel: true,
daySize: 38,
isRTL: false,
// navigation related props
navPrev: <PreviousMonthIcon />,
navNext: <NextMonthIcon />,
onPrevMonthClick() {},
onNextMonthClick() {},
onClose() {},
transitionDuration: 200, // milliseconds between next month changes etc.
// day presentation and interaction related props
renderCalendarDay: undefined, // If undefined, renders react-dates/lib/components/CalendarDay
// day presentation and interaction related props
renderDayContents: day => {
return <span className="renderedDay">{day.format('D')}</span>;
},
enableOutsideDays: false,
isDayBlocked: () => false,
// Stripe holds funds in a reserve for up to 90 days from charge creation.
// outside range -><- today ... today+89 days -><- outside range
isOutsideRange: day => {
const daysCountAvailableToBook = 90;
const endOfRange = daysCountAvailableToBook - 1;
return (
!isInclusivelyAfterDay(day, moment()) ||
!isInclusivelyBeforeDay(day, moment().add(endOfRange, 'days'))
);
},
isDayHighlighted: () => {},
// Internationalization props
// Multilocale support can be achieved with displayFormat like moment.localeData.longDateFormat('L')
// https://momentjs.com/
displayFormat: 'ddd, MMM D',
monthFormat: 'MMMM YYYY',
weekDayFormat: 'dd',
phrases: {
closeDatePicker: null, // Handled inside component
clearDate: null, // Handled inside component
},
};
class DateInputComponent extends Component {
constructor(props) {
super(props);
this.state = {
focused: false,
};
this.onDateChange = this.onDateChange.bind(this);
this.onFocusChange = this.onFocusChange.bind(this);
}
onDateChange(date) {
const selectedDate = date instanceof moment ? date.toDate() : null;
this.props.onChange({ date: selectedDate });
}
onFocusChange(values) {
const focused = values.focused;
// SingleDatePicker requires 'onFocusChange' function and 'focused' boolean
// but Fields of React-Form deals with onFocus & onBlur instead
this.setState({ focused });
if (focused) {
this.props.onFocus();
} else {
this.props.onBlur();
}
}
render() {
/* eslint-disable no-unused-vars */
const {
className,
initialDate,
intl,
name,
placeholderText,
onBlur,
onChange,
onFocus,
onDragStart,
onDrop,
phrases,
screenReaderInputMessage,
useMobileMargins,
value,
...datePickerProps
} = this.props;
/* eslint-enable no-unused-vars */
const initialMoment = initialDate ? moment(initialDate) : null;
const date = value && value.date instanceof Date ? moment(value.date) : initialMoment;
const placeholder = placeholderText || intl.formatMessage({ id: 'FieldDateInput.placeholder' });
const screenReaderInputText =
screenReaderInputMessage ||
intl.formatMessage({ id: 'FieldDateInput.screenReaderInputMessage' });
const closeDatePickerText = phrases.closeDatePicker
? phrases.closeDatePicker
: intl.formatMessage({ id: 'FieldDateInput.closeDatePicker' });
const clearDateText = phrases.clearDate
? phrases.clearDate
: intl.formatMessage({ id: 'FieldDateInput.clearDate' });
const classes = classNames(css.inputRoot, className, {
[css.withMobileMargins]: useMobileMargins,
});
return (
<div className={classes}>
<SingleDatePicker
{...datePickerProps}
focused={this.state.focused}
onFocusChange={this.onFocusChange}
date={date}
onDateChange={this.onDateChange}
placeholder={placeholder}
screenReaderInputMessage={screenReaderInputText}
phrases={{ closeDatePicker: closeDatePickerText, clearDate: clearDateText }}
/>
</div>
);
}
}
DateInputComponent.defaultProps = {
className: null,
useMobileMargins: false,
...defaultProps,
};
DateInputComponent.propTypes = {
className: string,
focused: bool,
initialDate: instanceOf(Date),
intl: intlShape.isRequired,
name: string.isRequired,
onChange: func.isRequired,
onBlur: func.isRequired,
onFocus: func.isRequired,
onDragStart: func.isRequired,
onDrop: func.isRequired,
phrases: shape({
closeDatePicker: string,
clearDate: string,
}),
useMobileMargins: bool,
placeholderText: string,
screenReaderInputMessage: string,
value: shape({
date: instanceOf(Date),
}),
};
export default injectIntl(DateInputComponent);

View file

@ -0,0 +1,30 @@
@import '../../marketplace.css';
.fieldRoot {
}
.pickerSuccess {
& :global(.DateInput_input) {
border-bottom-color: var(--successColor);
}
}
.pickerError {
& :global(.DateInput_input) {
border-bottom-color: var(--failColor);
}
}
/**
* Mobile margins mean that labels, inputs (incl separate borders) get margins,
* but calendar popup doesn't.
*/
.mobileMargins {
width: calc(100% - 48px);
margin: 0 24px;
@media (--viewportMedium) {
width: 100%;
margin: 0;
}
}

View file

@ -0,0 +1,53 @@
/* eslint-disable no-console */
import React from 'react';
import { reduxForm, propTypes as formPropTypes } from 'redux-form';
import moment from 'moment';
import { Button } from '../../components';
import { required, bookingDateRequired } from '../../util/validators';
import FieldDateInput from './FieldDateInput';
const FormComponent = props => {
const { form, handleSubmit, pristine, submitting, dateInputProps } = props;
const submitDisabled = pristine || submitting;
return (
<form onSubmit={handleSubmit}>
<FieldDateInput {...dateInputProps} />
<Button type="submit" disabled={submitDisabled} style={{ marginTop: '24px' }}>
Select
</Button>
</form>
);
};
FormComponent.propTypes = formPropTypes;
const defaultFormName = 'Styleguide.DateInput.Form';
const Form = reduxForm({
form: defaultFormName,
})(FormComponent);
export const Empty = {
component: Form,
props: {
dateInputProps: {
name: 'bookingDate',
useMobileMargins: false,
id: `${defaultFormName}.bookingDate`,
label: 'Date',
placeholderText: moment().format('ddd, MMMM D'),
format: null,
validate: [required('Required'), bookingDateRequired('Date is not valid')],
onBlur: () => console.log('onBlur called from DateInput props.'),
onFocus: () => console.log('onFocus called from DateInput props.'),
},
onChange: values => {
const { date } = values;
console.log('Changed to', moment(date).format('L'));
},
onSubmit: values => {
console.log('Submitting a form with values:', values);
},
},
group: 'custom inputs',
};

View file

@ -0,0 +1,97 @@
/**
* Provides a date picker for Redux Forms (using https://github.com/airbnb/react-dates)
*
* NOTE: If you are using this component inside BookingDatesForm,
* you should convert value.date to start date and end date before submitting it to API
*/
import React, { Component } from 'react';
import { bool, object, string } from 'prop-types';
import { Field } from 'redux-form';
import classNames from 'classnames';
import { ValidationError } from '../../components';
import DateInput from './DateInput';
import css from './FieldDateInput.css';
class FieldDateInputComponent extends Component {
render() {
const {
className,
rootClassName,
id,
label,
input,
meta,
useMobileMargins,
...rest
} = this.props;
if (label && !id) {
throw new Error('id required when a label is given');
}
const { touched, invalid, error } = meta;
const value = input.value;
// If startDate is valid label changes color and bottom border changes color too
const dateIsValid = value && value.date instanceof Date;
// Error message and input error styles are only shown if the
// field has been touched and the validation has failed.
const hasError = touched && invalid && error;
const inputClasses = classNames({
[css.pickerSuccess]: dateIsValid,
[css.pickerError]: hasError,
});
const { onBlur, onFocus, ...restOfInput } = input;
const inputProps = {
onBlur: input.onBlur,
onFocus: input.onFocus,
useMobileMargins,
...restOfInput,
...rest,
};
const classes = classNames(rootClassName || css.fieldRoot, className);
const errorClasses = classNames({ [css.mobileMargins]: useMobileMargins });
return (
<div className={classes}>
{label ? (
<label className={classNames({ [css.mobileMargins]: useMobileMargins })} htmlFor={id}>
{label}
</label>
) : null}
<DateInput className={inputClasses} {...inputProps} />
<ValidationError className={errorClasses} fieldMeta={meta} />
</div>
);
}
}
FieldDateInputComponent.defaultProps = {
className: null,
rootClassName: null,
useMobileMargins: false,
id: null,
label: null,
placeholderText: null,
};
FieldDateInputComponent.propTypes = {
className: string,
rootClassName: string,
useMobileMargins: bool,
id: string,
label: string,
placeholderText: string,
input: object.isRequired,
meta: object.isRequired,
};
const FieldDateInput = props => {
return <Field component={FieldDateInputComponent} {...props} />;
};
export { DateInput };
export default FieldDateInput;

View file

@ -0,0 +1,27 @@
import React from 'react';
// react-dates needs to be initialized before using any react-dates component
// Since this is currently only component using react-dates we can do it here
// https://github.com/airbnb/react-dates#initialize
import 'react-dates/initialize';
import { renderDeep } from '../../util/test-helpers';
import { DateInput } from './FieldDateInput';
const noop = () => null;
describe('DateInput', () => {
it('matches snapshot', () => {
const props = {
name: 'bookingDate',
onBlur: noop,
onChange: noop,
onFocus: noop,
onDragStart: noop,
onDrop: noop,
id: 'bookingDate',
placeholderText: 'today',
};
const tree = renderDeep(<DateInput {...props} />);
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import css from './DateInput.css';
const NextMonthIcon = props => {
const { className, rootClassName } = props;
const classes = classNames(rootClassName || css.rootNextMonthIcon, className);
return (
<svg
className={classes}
width="9"
height="13"
viewBox="0 0 9 13"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.472 6.97c.26-.26.26-.68 0-.94L2.14.694c-.263-.26-.684-.26-.944 0-.26.26-.26.683 0 .943L6.056 6.5l-4.86 4.862c-.26.26-.26.683 0 .943.26.26.68.26.943 0L7.47 6.97z"
fillRule="evenodd"
/>
</svg>
);
};
const { string } = PropTypes;
NextMonthIcon.defaultProps = {
className: null,
rootClassName: null,
};
NextMonthIcon.propTypes = {
className: string,
rootClassName: string,
};
export default NextMonthIcon;

View file

@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import css from './DateInput.css';
const PreviousMonthIcon = props => {
const { className, rootClassName } = props;
const classes = classNames(rootClassName || css.rootPreviousMonthIcon, className);
return (
<svg
className={classes}
width="9"
height="13"
viewBox="0 0 9 13"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.195 6.03c-.26.26-.26.68 0 .94l5.333 5.335c.262.26.683.26.943 0 .262-.26.262-.683 0-.943L2.61 6.5l4.86-4.862c.262-.26.262-.683 0-.943-.26-.26-.68-.26-.942 0L1.195 6.03z"
fillRule="evenodd"
/>
</svg>
);
};
const { string } = PropTypes;
PreviousMonthIcon.defaultProps = {
className: null,
rootClassName: null,
};
PreviousMonthIcon.propTypes = {
className: string,
rootClassName: string,
};
export default PreviousMonthIcon;

View file

@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DateInput matches snapshot 1`] = `
<div
className=""
>
<div
className="SingleDatePicker SingleDatePicker_1"
>
<div>
<div
className="SingleDatePickerInput SingleDatePickerInput_1"
>
<div
className="DateInput DateInput_1"
>
<input
aria-describedby="DateInput__screen-reader-message-bookingDate"
aria-label="today"
autoComplete="off"
className="DateInput_input DateInput_input_1"
disabled={false}
id="bookingDate"
name="bookingDate"
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="today"
readOnly={false}
required={false}
type="text"
value=""
/>
<p
className="DateInput_screenReaderMessage DateInput_screenReaderMessage_1"
id="DateInput__screen-reader-message-bookingDate"
>
FieldDateInput.screenReaderInputMessage
</p>
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -23,12 +23,6 @@
min-height: calc(100vh - var(--datepickerDistanceToTop));
background-color: var(--marketplaceColor);
@media (--viewportMedium) {
/* !important is added to top because react-dates uses inline style for height */
/* Similar problem as in issue: https://github.com/airbnb/react-dates/issues/947 */
top: 45px !important;
}
@media (--viewportMedium) {
/* !important is added to top because react-dates uses inline style for height */
/* Similar problem as in issue: https://github.com/airbnb/react-dates/issues/947 */

View file

@ -5,12 +5,7 @@
* N.B. *isOutsideRange* in defaultProps is defining what dates are available to booking.
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
// react-dates needs to be initialized before using any react-dates component
// Since this is currently only component using react-dates we can do it here
// https://github.com/airbnb/react-dates#initialize
import 'react-dates/initialize';
import { bool, func, instanceOf, oneOf, shape, string } from 'prop-types';
import { DateRangePicker, isInclusivelyAfterDay, isInclusivelyBeforeDay } from 'react-dates';
import { intlShape, injectIntl } from 'react-intl';
import classNames from 'classnames';
@ -271,8 +266,6 @@ DateRangeInputComponent.defaultProps = {
...defaultProps,
};
const { bool, func, instanceOf, oneOf, shape, string } = PropTypes;
DateRangeInputComponent.propTypes = {
className: string,
unitType: propTypes.bookingUnitType.isRequired,

View file

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bool, func, object, oneOf, string } from 'prop-types';
import { Field } from 'redux-form';
import classNames from 'classnames';
import { START_DATE, END_DATE } from '../../util/dates';
@ -153,8 +153,6 @@ FieldDateRangeInputComponent.defaultProps = {
onFocusedInputChange: null,
};
const { bool, func, object, oneOf, string } = PropTypes;
FieldDateRangeInputComponent.propTypes = {
className: string,
rootClassName: string,

View file

@ -1,4 +1,9 @@
import React from 'react';
// react-dates needs to be initialized before using any react-dates component
// Since this is currently only component using react-dates we can do it here
// https://github.com/airbnb/react-dates#initialize
import 'react-dates/initialize';
import { renderDeep } from '../../util/test-helpers';
import { LINE_ITEM_NIGHT } from '../../util/types';
import { DateRangeInput } from './FieldDateRangeInput';

View file

@ -33,6 +33,7 @@ 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';
export { default as FieldDateInput } from './FieldDateInput/FieldDateInput';
export { default as FieldDateRangeInput } from './FieldDateRangeInput/FieldDateRangeInput';
export { default as FieldGroupCheckbox } from './FieldGroupCheckbox/FieldGroupCheckbox';
export { default as FieldReviewRating } from './FieldReviewRating/FieldReviewRating';

View file

@ -8,6 +8,7 @@ import * as ExpandingTextarea from './components/ExpandingTextarea/ExpandingText
import * as FieldBirthdayInput from './components/FieldBirthdayInput/FieldBirthdayInput.example';
import * as FieldCheckbox from './components/FieldCheckbox/FieldCheckbox.example';
import * as FieldCurrencyInput from './components/FieldCurrencyInput/FieldCurrencyInput.example';
import * as FieldDateInput from './components/FieldDateInput/FieldDateInput.example';
import * as FieldDateRangeInput from './components/FieldDateRangeInput/FieldDateRangeInput.example';
import * as FieldGroupCheckbox from './components/FieldGroupCheckbox/FieldGroupCheckbox.example';
import * as FieldReviewRating from './components/FieldReviewRating/FieldReviewRating.example';
@ -93,6 +94,7 @@ export {
FieldBirthdayInput,
FieldCheckbox,
FieldCurrencyInput,
FieldDateInput,
FieldDateRangeInput,
FieldGroupCheckbox,
FieldReviewRating,

View file

@ -192,6 +192,11 @@
"EnquiryForm.messageRequired": "A message is required",
"EnquiryForm.sendEnquiryError": "Whoops, something went wrong. Please try again.",
"EnquiryForm.submitButtonText": "Send enquiry",
"FieldDateInput.clearDate": "Clear Date",
"FieldDateInput.closeDatePicker": "Close",
"FieldDateInput.invalidDate": "The start date is not valid",
"FieldDateInput.screenReaderInputMessage": "Date input",
"FieldDateInput.placeholderText": "Add start date",
"FieldDateRangeInput.clearDate": "Clear Date",
"FieldDateRangeInput.closeDatePicker": "Close",
"FieldDateRangeInput.endDatePlaceholderText": "Add end date",

View file

@ -46,6 +46,11 @@ export const autocompletePlaceSelected = message => value => {
return selectedPlaceIsValid ? VALID : message;
};
export const bookingDateRequired = inValidDateMessage => value => {
const dateIsValid = value && value.date instanceof Date;
return !dateIsValid ? inValidDateMessage : VALID;
};
export const bookingDatesRequired = (inValidStartDateMessage, inValidEndDateMessage) => value => {
const startDateIsValid = value && value.startDate instanceof Date;
const endDateIsValid = value && value.endDate instanceof Date;