diff --git a/package.json b/package.json
index f94e6d89ffb..9391d89fdf2 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"@instructure/ui-core": "^4.8.0",
"@instructure/ui-menu": "^5.0.1",
"@instructure/ui-overlays": "^5.0.1",
- "@instructure/ui-themeable": "^4.8.0",
+ "@instructure/ui-themeable": "^5.4.0",
"@instructure/ui-themes": "^5.0.1",
"apollo-boost": "^0.1.4",
"axios": "^0.16.0",
@@ -193,6 +193,7 @@
"upgrade-and-dedupe": "rm -rf yarn.lock node_modules && yes 1 | yarn install --flat --production && git checkout package.json && yarn install && git add yarn.lock && git commit -m 'update npm deps'"
},
"resolutions": {
- "jquery": "https://github.com/ryankshaw/jquery.git#a755a3e9c99d5a70d8ea570836f94ae1ba56046d"
+ "jquery": "https://github.com/ryankshaw/jquery.git#a755a3e9c99d5a70d8ea570836f94ae1ba56046d",
+ "moment": "2.10.6"
}
}
\ No newline at end of file
diff --git a/packages/canvas-planner/package.json b/packages/canvas-planner/package.json
index 8b9e89fbcae..9e4e00ea7b7 100644
--- a/packages/canvas-planner/package.json
+++ b/packages/canvas-planner/package.json
@@ -41,8 +41,9 @@
"license": "AGPL-3.0",
"dependencies": {
"@instructure/ui-core": "^4.8.0",
+ "@instructure/ui-forms": "5.4.1-dev.0",
+ "@instructure/ui-themeable": "^5.4.0",
"@instructure/ui-toggle-details": "^5.0.0",
- "@instructure/ui-forms": "^5.2.0",
"axios": "^0.16.0",
"babel-plugin-inline-react-svg": "^0.4.0",
"change-case": "^3.0.1",
@@ -54,7 +55,6 @@
"instructure-icons": "^4.3.0",
"keycode": "^2.1.9",
"lodash": "^4",
- "moment": "^2.22.0",
"moment-timezone": "^0.5.13",
"parse-link-header": "^1.0.1",
"prop-types": "^15.5.9",
@@ -100,6 +100,9 @@
"npm-run-all": "^4",
"webpack": "^3"
},
+ "resolutions": {
+ "moment": "2.10.6"
+ },
"lint-staged": {
"*.js": "eslint"
}
diff --git a/packages/canvas-planner/src/actions/__tests__/actions.spec.js b/packages/canvas-planner/src/actions/__tests__/actions.spec.js
index ed6a9d6e8e7..2c046802515 100644
--- a/packages/canvas-planner/src/actions/__tests__/actions.spec.js
+++ b/packages/canvas-planner/src/actions/__tests__/actions.spec.js
@@ -28,6 +28,8 @@ jest.mock('../../utilities/apiUtils', () => ({
transformPlannerNoteApiToInternalItem: jest.fn(response => ({...response, transformedToInternal: true}))
}));
+const simpleItem = opts => Object.assign({some: 'data', date: moment('2018-03-28T13:14:00-04:00')}, opts);
+
const getBasicState = () => ({
courses: [],
groups: [],
@@ -241,7 +243,7 @@ describe('api actions', () => {
describe('savePlannerItem', () => {
it('dispatches saving and saved actions', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data'};
+ const plannerItem = simpleItem();
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
expect(isPromise(savePromise)).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith({type: 'SAVING_PLANNER_ITEM', payload: {item: plannerItem, isNewItem: true}});
@@ -250,7 +252,7 @@ describe('api actions', () => {
it('sets isNewItem to false if the item id exists', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data', id: '42'};
+ const plannerItem = simpleItem({id: '42'});
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
expect(isPromise(savePromise)).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith({type: 'SAVING_PLANNER_ITEM', payload: {item: plannerItem, isNewItem: false}});
@@ -259,7 +261,7 @@ describe('api actions', () => {
it('sends transformed data in the request', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data'};
+ const plannerItem = simpleItem();
Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosWait(request => {
expect(JSON.parse(request.config.data)).toMatchObject({some: 'data', transformedToApi: true});
@@ -268,7 +270,7 @@ describe('api actions', () => {
it('resolves the promise with transformed response data', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data'};
+ const plannerItem = simpleItem();
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
@@ -282,8 +284,8 @@ describe('api actions', () => {
});
it('does a post if the planner item is new (no id)', () => {
- const plannerItem = {some: 'data'};
- Actions.savePlannerItem(plannerItem)(() => {});
+ const plannerItem = simpleItem();
+ Actions.savePlannerItem(plannerItem)(() => {}, () => {return {timeZone: 'America/Halifax'};});
return moxiosWait((request) => {
expect(request.config.method).toBe('post');
expect(request.url).toBe('api/v1/planner_notes');
@@ -291,20 +293,24 @@ describe('api actions', () => {
});
});
- it('does set default time of 11:59 pm for planner date', () => {
- const plannerItem = {date: moment('2017-06-22T10:05:54').tz("Atlantic/Azores").toISOString()};
- Actions.savePlannerItem(plannerItem)(() => {});
+ it('does set default time of 11:59 pm for planner date at midnight', () => {
+ const TZ = "Atlantic/Azores";
+ const plannerItem = simpleItem({date: moment.tz(TZ).startOf('day').toISOString()});
+ Actions.savePlannerItem(plannerItem)(() => {}, () => {return {timeZone: TZ};});
return moxiosWait((request) => {
expect(request.config.method).toBe('post');
expect(request.url).toBe('api/v1/planner_notes');
expect(JSON.parse(request.config.data).transformedToApi).toBeTruthy();
- expect(moment(JSON.parse(request.config.data).date).tz("Atlantic/Azores").toISOString()).toBe(moment('2017-06-22T23:59:59').tz("Atlantic/Azores").toISOString());
+ const result = moment(JSON.parse(request.config.data).date).tz(TZ);
+ expect(result.hours()).toEqual(23);
+ expect(result.minutes()).toEqual(59);
+ expect(result.seconds()).toEqual(59);
});
});
it('does a put if the planner item exists (has id)', () => {
- const plannerItem = {id: '42', some: 'data'};
- Actions.savePlannerItem(plannerItem, )(() => {});
+ const plannerItem = simpleItem({id: '42'});
+ Actions.savePlannerItem(plannerItem, )(() => {}, () => {return {timeZone: 'America/Halifax'};});
return moxiosWait((request) => {
expect(request.config.method).toBe('put');
expect(request.url).toBe('api/v1/planner_notes/42');
@@ -319,7 +325,7 @@ describe('api actions', () => {
visualErrorCallback: fakeAlert
});
- const plannerItem = {some: 'data'};
+ const plannerItem = simpleItem();
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
@@ -333,7 +339,7 @@ describe('api actions', () => {
it('saves and restores the override data', () => {
const mockDispatch = jest.fn();
// a planner item with override data
- const plannerItem = {some: 'data', id: '42', overrideId: '17', completed: true};
+ const plannerItem = simpleItem({id: '42', overrideId: '17', completed: true});
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{some: 'data', id: '42'}, // notice the response has no override data
@@ -358,7 +364,7 @@ describe('api actions', () => {
describe('deletePlannerItem', () => {
it('dispatches deleting and deleted actions', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data'};
+ const plannerItem = simpleItem();
const deletePromise = Actions.deletePlannerItem(plannerItem)(mockDispatch, getBasicState);
expect(isPromise(deletePromise)).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith({type: 'DELETING_PLANNER_ITEM', payload: plannerItem});
@@ -366,7 +372,7 @@ describe('api actions', () => {
});
it('sends a delete request for the item id', () => {
- const plannerItem = {id: '42', some: 'data'};
+ const plannerItem = simpleItem({id: '42'});
Actions.deletePlannerItem(plannerItem, )(() => {});
return moxiosWait((request) => {
expect(request.config.method).toBe('delete');
@@ -376,7 +382,7 @@ describe('api actions', () => {
it('resolves the promise with transformed response data', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data'};
+ const plannerItem = simpleItem();
const deletePromise = Actions.deletePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
@@ -393,7 +399,7 @@ describe('api actions', () => {
visualErrorCallback: fakeAlert
});
- const plannerItem = { some: 'data' };
+ const plannerItem = simpleItem();
const deletePromise = Actions.deletePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
@@ -408,7 +414,7 @@ describe('api actions', () => {
describe('togglePlannerItemCompletion', () => {
it('dispatches saving and saved actions', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data'};
+ const plannerItem = simpleItem();
const savingItem = {...plannerItem, show: true, toggleAPIPending: true};
const savePromise = Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
expect(isPromise(savePromise)).toBe(true);
@@ -418,7 +424,7 @@ describe('api actions', () => {
it ('updates marked_complete and sends override data in the request', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data', marked_complete: null};
+ const plannerItem = simpleItem({marked_complete: null});
Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosWait(request => {
expect(JSON.parse(request.config.data)).toMatchObject({marked_complete: true, transformedToApiOverride: true});
@@ -427,7 +433,7 @@ describe('api actions', () => {
it('does a post if the planner override is new (no id)', () => {
const mockDispatch = jest.fn();
- const plannerItem = {id: '42', some: 'data'};
+ const plannerItem = simpleItem({id: '42'});
Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosWait((request) => {
expect(request.config.method).toBe('post');
@@ -438,7 +444,7 @@ describe('api actions', () => {
it('does a put if the planner override exists (has id)', () => {
const mockDispatch = jest.fn();
- const plannerItem = {id: '42', some: 'data', planner_override: {id: '5', marked_complete: true}};
+ const plannerItem = simpleItem({id: '42', planner_override: {id: '5', marked_complete: true}});
Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosWait((request) => {
expect(request.config.method).toBe('put');
@@ -449,7 +455,7 @@ describe('api actions', () => {
it ('resolves the promise with override response data in the item', () => {
const mockDispatch = jest.fn();
- const plannerItem = {some: 'data', planner_override: {id: 'override_id', marked_complete: true}};
+ const plannerItem = simpleItem({planner_override: {id: 'override_id', marked_complete: true}});
const togglePromise = Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{some: 'response data', id: 'override_id', marked_complete: false },
diff --git a/packages/canvas-planner/src/actions/index.js b/packages/canvas-planner/src/actions/index.js
index 5e774badaf3..a642778a251 100644
--- a/packages/canvas-planner/src/actions/index.js
+++ b/packages/canvas-planner/src/actions/index.js
@@ -17,11 +17,11 @@
*/
import { createActions } from 'redux-actions';
import axios from 'axios';
-import moment from 'moment-timezone';
import configureAxios from '../utilities/configureAxios';
import { alert } from '../utilities/alertUtils';
import formatMessage from '../format-message';
import parseLinkHeader from 'parse-link-header';
+import { makeEndOfDayIfMidnight } from '../utilities/dateUtils';
import {
transformInternalToApiItem,
@@ -146,9 +146,10 @@ export const dismissOpportunity = (id, plannerOverride) => {
};
export const savePlannerItem = (plannerItem) => {
- plannerItem.date = moment(plannerItem.date).endOf('day').format('YYYY-MM-DDTHH:mm:ssZ');
-
return (dispatch, getState) => {
+ plannerItem.date = makeEndOfDayIfMidnight(plannerItem.date, getState().timeZone);
+ plannerItem.date = plannerItem.date.toISOString();
+
const isNewItem = !plannerItem.id;
const overrideData = getOverrideDataOnItem(plannerItem);
dispatch(savingPlannerItem({item: plannerItem, isNewItem}));
diff --git a/packages/canvas-planner/src/components/PlannerItem/index.js b/packages/canvas-planner/src/components/PlannerItem/index.js
index b69f27aa60c..917604b283c 100644
--- a/packages/canvas-planner/src/components/PlannerItem/index.js
+++ b/packages/canvas-planner/src/components/PlannerItem/index.js
@@ -125,8 +125,7 @@ export class PlannerItem extends Component {
}
renderDateField = () => {
- if (this.props.date &&
- this.props.associated_item !== "To Do") {
+ if (this.props.date) {
if (this.props.associated_item === "Announcement") {
return this.props.date.format("LT");
diff --git a/packages/canvas-planner/src/components/UpdateItemTray/__tests__/UpdateItemTray.spec.js b/packages/canvas-planner/src/components/UpdateItemTray/__tests__/UpdateItemTray.spec.js
index 3a290ff07be..defff656e30 100644
--- a/packages/canvas-planner/src/components/UpdateItemTray/__tests__/UpdateItemTray.spec.js
+++ b/packages/canvas-planner/src/components/UpdateItemTray/__tests__/UpdateItemTray.spec.js
@@ -18,7 +18,7 @@
import moment from 'moment-timezone';
import React from 'react';
import { shallow, mount } from 'enzyme';
-import UpdateItemTray from '../index';
+import { UpdateItemTray } from '../index';
const defaultProps = {
onSavePlannerItem: () => {},
@@ -28,10 +28,16 @@ const defaultProps = {
courses: [],
};
+const simpleItem = (opts = {}) => Object.assign({title: '', date: moment('2017-04-28T11:00:00Z')}, opts);
+
+afterEach(()=> {
+ jest.restoreAllMocks();
+});
+
it('renders the item to update if provided', () => {
const noteItem = {
title: 'Planner Item',
- date: '2017-04-25 01:49:00-0700',
+ date: moment('2017-04-25 01:49:00-0700'),
context: {id: '1'},
details: "You made this item to remind you of something, but you forgot what."
};
@@ -44,11 +50,11 @@ it('renders the item to update if provided', () => {
});
it("doesn't re-render unless new item is provided", () => {
- const wrapper = shallow()
- const newProps = {...defaultProps, locale: 'fr'}
- wrapper.setProps(newProps)
- expect(wrapper.find('DateInput').props()['messages'].length).toBe(0)
-})
+ const wrapper = shallow();
+ const newProps = {...defaultProps, locale: 'fr'};
+ wrapper.setProps(newProps);
+ expect(wrapper.find('DateTimeInput').props()['messages'].length).toBe(0);
+});
it('renders Add To Do header when creating a new to do', () => {
const wrapper = mount(
@@ -78,43 +84,44 @@ it('shows details inputs', () => {
});
it('disables the save button when title is empty', () => {
- const item = { title: '', date: '2017-04-28' };
+ const item = simpleItem();
const wrapper = shallow();
const button = wrapper.find('Button[variant="primary"]');
expect(button.props().disabled).toBe(true);
});
it('handles courseid being none', () => {
- const item = { title: '', date: '2017-04-28' };
+ const item = simpleItem();
const wrapper = shallow();
wrapper.instance().handleCourseIdChange({target: {value: 'none'}});
expect(wrapper.instance().state.updates.courseId).toBe(undefined);
});
it('correctly updates id to null when courseid is none', () => {
- const item = { title: '', date: '2017-04-28' };
+ const item = simpleItem();
const mockCallback = jest.fn();
const wrapper = shallow();
wrapper.instance().handleCourseIdChange({target: {value: 'none'}});
wrapper.instance().handleSave();
expect(mockCallback).toHaveBeenCalledWith({
title: item.title,
- date: item.date,
+ date: item.date.toISOString(),
context: {
id: null
}
});
});
-it('sets default date when no date is provided', () => {
+it('sets default datetime to 11:50pm today when no date is provided', () => {
+ const now = moment.tz(defaultProps.timeZone).endOf('day');
const item = { title: 'an item', date: '' };
const wrapper = shallow();
- const datePicker = wrapper.find('DateInput');
- expect(!datePicker.props().dateValue.length).toBe(false);
+ const datePicker = wrapper.find('DateTimeInput');
+ expect(datePicker.props().value).toEqual(now.toISOString());
});
it('enables the save button when title and date are present', () => {
- const item = { title: 'an item', date: '2017-04-28' };
+ const item = simpleItem({ title: 'an item' });
const wrapper = shallow();
const button = wrapper.find('Button[variant="primary"]');
expect(button.props().disabled).toBe(false);
@@ -151,7 +158,7 @@ xit('does not set an initial error message on date', () => {
});
xit('sets error message on date field when date is set to blank', () => {
- const wrapper = shallow();
+ const wrapper = shallow();
wrapper.instance().handleDateChange({target: {value: ''}});
const dateInput = wrapper.find('TextInput').at(1);
const messages = dateInput.props().messages;
@@ -160,7 +167,7 @@ xit('sets error message on date field when date is set to blank', () => {
});
xit('clears the error message when a date is typed in', () => {
- const wrapper = shallow();
+ const wrapper = shallow();
wrapper.instance().handleTitleChange({target: {value: ''}});
wrapper.instance().handleTitleChange({target: {value: '2'}});
const dateInput = wrapper.find('TextInput').at(1);
@@ -168,50 +175,43 @@ xit('clears the error message when a date is typed in', () => {
});
it('respects the provided timezone', () => {
- const item = { title: '', date: '2017-04-25 12:00:00-0300' };
+ const item = simpleItem({date: moment('2017-04-25 12:00:00-0300')});
const wrapper = mount();
const d = wrapper.find('DateInput').find('TextInput').props().value;
expect(d).toEqual('April 26, 2017'); // timezone shift from -3 to +9 pushes it to the next day
});
it('changes state when new date is typed in', () => {
- const noteItem = {
- title: 'Planner Item',
- date: '2017-04-25',
- };
+ const noteItem = simpleItem({title: 'Planner Item'});
const mockCallback = jest.fn();
const wrapper = mount();
- const newDate = moment('2017-10-16');
+ const newDate = moment('2017-10-16T13:30:00');
wrapper.instance().handleDateChange({}, newDate.toISOString());
wrapper.instance().handleSave();
expect(mockCallback).toHaveBeenCalledWith({
title: noteItem.title,
date: newDate.toISOString(),
- context: {
- id: null
- }
+ context: {id: null}
});
});
it('updates state when new note is passed in', () => {
- const noteItem1 = {
+ const noteItem1 = simpleItem({
title: 'Planner Item 1',
- date: '2017-04-25',
context: {id: '1'},
details: "You made this item to remind you of something, but you forgot what."
- };
+ });
const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
- const noteItem2 = {
+ const noteItem2 = simpleItem({
title: 'Planner Item 2',
- date: '2017-12-25',
context: {id: '2'},
details: "This is another reminder"
- };
+ });
wrapper.setProps({noteItem: noteItem2});
expect(wrapper).toMatchSnapshot();
});
@@ -247,29 +247,38 @@ it('invokes save callback with updated data', () => {
const saveMock = jest.fn();
const wrapper = shallow();
wrapper.instance().handleTitleChange({target: {value: 'new title'}});
- wrapper.instance().handleDateChange({}, '2017-05-01');
+ wrapper.instance().handleDateChange({}, '2017-05-01T14:00:00Z');
wrapper.instance().handleCourseIdChange({target: {value: '43'}});
wrapper.instance().handleChange('details', 'new details');
wrapper.instance().handleSave();
expect(saveMock).toHaveBeenCalledWith({
- title: 'new title', date: moment('2017-05-01').toISOString(), context: {id: '43'}, details: 'new details',
+ title: 'new title', date: moment('2017-05-01T14:00:00Z').toISOString(), context: {id: '43'}, details: 'new details',
});
});
it('invokes the delete callback', () => {
+ const item = simpleItem({title: 'a title'});
const mockDelete = jest.fn();
const wrapper = shallow();
const confirmSpy = jest.spyOn(window, 'confirm').mockReturnValue(true);
wrapper.instance().handleDeleteClick();
expect(confirmSpy).toHaveBeenCalled();
- expect(mockDelete).toHaveBeenCalledWith({title: 'a title'});
- confirmSpy.mockRestore();
+ expect(mockDelete).toHaveBeenCalledWith(item);
+});
+
+it('invokes invalidDateTimeMessage when an invalid date is entered', () => {
+ const invalidCallbackSpy = jest.spyOn(UpdateItemTray.prototype, 'invalidDateTimeMessage');
+ const wrapper = mount();
+ const dateInput = wrapper.find('DateInput').find('input');
+ dateInput.simulate('change', {target: {value: 'xxxxx'}});
+ dateInput.simulate('blur');
+ expect(invalidCallbackSpy).toHaveBeenCalled();
});
diff --git a/packages/canvas-planner/src/components/UpdateItemTray/__tests__/__snapshots__/UpdateItemTray.spec.js.snap b/packages/canvas-planner/src/components/UpdateItemTray/__tests__/__snapshots__/UpdateItemTray.spec.js.snap
index 87f08f3e635..de2b5b9bedc 100644
--- a/packages/canvas-planner/src/components/UpdateItemTray/__tests__/__snapshots__/UpdateItemTray.spec.js.snap
+++ b/packages/canvas-planner/src/components/UpdateItemTray/__tests__/__snapshots__/UpdateItemTray.spec.js.snap
@@ -36,27 +36,30 @@ exports[`renders the item to update if provided 1`] = `
type="text"
value="Planner Item"
/>
-
+ The date and time this to do is due
+
+ }
disabled={false}
- format="LL"
- inline={false}
- inputRef={[Function]}
- invalidDateMessage={[Function]}
- label="Date"
+ invalidDateTimeMessage={[Function]}
layout="stacked"
locale="en"
+ messageFormat="LLL"
messages={Array []}
- nextLabel="Next Month"
- onDateChange={[Function]}
- placement="start"
- previousLabel="Previous Month"
+ onChange={[Function]}
readOnly={false}
required={true}
- size="medium"
+ timeLabel="Time"
+ timeStep={30}
timezone="Asia/Tokyo"
- validationFeedback={true}
+ value="2017-04-25T08:49:00.000Z"
/>
-
+ The date and time this to do is due
+
+ }
disabled={false}
- format="LL"
- inline={false}
- inputRef={[Function]}
- invalidDateMessage={[Function]}
- label="Date"
+ invalidDateTimeMessage={[Function]}
layout="stacked"
locale="en"
+ messageFormat="LLL"
messages={Array []}
- nextLabel="Next Month"
- onDateChange={[Function]}
- placement="start"
- previousLabel="Previous Month"
+ onChange={[Function]}
readOnly={false}
required={true}
- size="medium"
+ timeLabel="Time"
+ timeStep={30}
timezone="Asia/Tokyo"
- validationFeedback={true}
+ value="2017-04-28T11:00:00.000Z"
/>
-
+ The date and time this to do is due
+
+ }
disabled={false}
- format="LL"
- inline={false}
- inputRef={[Function]}
- invalidDateMessage={[Function]}
- label="Date"
+ invalidDateTimeMessage={[Function]}
layout="stacked"
locale="en"
+ messageFormat="LLL"
messages={Array []}
- nextLabel="Next Month"
- onDateChange={[Function]}
- placement="start"
- previousLabel="Previous Month"
+ onChange={[Function]}
readOnly={false}
required={true}
- size="medium"
+ timeLabel="Time"
+ timeStep={30}
timezone="Asia/Tokyo"
- validationFeedback={true}
+ value="2017-04-28T11:00:00.000Z"
/>