@instructure/ui-date-time-input 8.23.1-snapshot.7 → 8.24.0

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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [8.24.0](https://github.com/instructure/instructure-ui/compare/v8.23.0...v8.24.0) (2022-04-26)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **ui-date-time-input:** fix Date input to fill container in `DateTimeInput` ([4069706](https://github.com/instructure/instructure-ui/commit/40697060749bb4770120e822ef2b2466279b1497))
11
+
12
+ ### Features
13
+
14
+ - **ui-date-time-input:** allow to disable dates in the calendar.
15
+ - **ui-date-time-input:** add `rowSpacing` and `colSpacing` props to DateTimeInput ([5e829fa](https://github.com/instructure/instructure-ui/commit/5e829fae6b005f080497818df8b3fff19365aab3))
16
+
6
17
  # [8.23.0](https://github.com/instructure/instructure-ui/compare/v8.22.0...v8.23.0) (2022-04-07)
7
18
 
8
19
  **Note:** Version bump only for package @instructure/ui-date-time-input
@@ -254,6 +254,29 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_class2 = class DateTimeI
254
254
  }
255
255
 
256
256
  const newTimeSelectValue = (_this$state = this.state) !== null && _this$state !== void 0 && _this$state.timeSelectValue ? this.state.timeSelectValue : (_this$_timeInput = this._timeInput) === null || _this$_timeInput === void 0 ? void 0 : _this$_timeInput.getBaseDate().minute(parsed.minute()).hour(parsed.hour()).toISOString();
257
+
258
+ if (this.isDisabledDate(parsed)) {
259
+ let text = typeof this.props.disabledDateTimeMessage === 'function' ? this.props.disabledDateTimeMessage(parsed.toISOString(true)) : this.props.disabledDateTimeMessage;
260
+
261
+ if (!text) {
262
+ text = typeof this.props.invalidDateTimeMessage === 'function' ? this.props.invalidDateTimeMessage(parsed.toISOString(true)) : this.props.invalidDateTimeMessage;
263
+ }
264
+
265
+ errorMsg = text ? {
266
+ text,
267
+ type: 'error'
268
+ } : void 0;
269
+ return {
270
+ iso: parsed.clone(),
271
+ calendarSelectedDate: parsed.clone(),
272
+ dateInputTextChanged: false,
273
+ dateInputText: parsed.format(this.dateFormat),
274
+ message: errorMsg,
275
+ timeSelectValue: newTimeSelectValue,
276
+ renderedDate: parsed.clone()
277
+ };
278
+ }
279
+
257
280
  return {
258
281
  iso: parsed.clone(),
259
282
  calendarSelectedDate: parsed.clone(),
@@ -309,6 +332,26 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_class2 = class DateTimeI
309
332
 
310
333
  get dateFormat() {
311
334
  return this.props.dateFormat;
335
+ }
336
+
337
+ isDisabledDate(date) {
338
+ const disabledDates = this.props.disabledDates;
339
+
340
+ if (!disabledDates) {
341
+ return false;
342
+ }
343
+
344
+ if (Array.isArray(disabledDates)) {
345
+ for (const aDisabledDate of disabledDates) {
346
+ if (date.isSame(aDisabledDate, 'day')) {
347
+ return true;
348
+ }
349
+ }
350
+
351
+ return false;
352
+ }
353
+
354
+ return disabledDates(date.toISOString());
312
355
  } // Called when the user enters text into dateInput
313
356
 
314
357
 
@@ -369,7 +412,8 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_class2 = class DateTimeI
369
412
  isOutsideMonth: !date.isSame(renderedDate, 'month'),
370
413
  label: date.format('D MMMM YYYY') // used by screen readers
371
414
  ,
372
- onClick: this.handleDayClick
415
+ onClick: this.handleDayClick,
416
+ interaction: this.isDisabledDate(date) ? 'disabled' : 'enabled'
373
417
  }, date.format('DD'));
374
418
  });
375
419
  } // The default weekdays rendered in the calendar
@@ -439,18 +483,21 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_class2 = class DateTimeI
439
483
  timezone = _this$props2.timezone,
440
484
  messages = _this$props2.messages,
441
485
  layout = _this$props2.layout,
486
+ rowSpacing = _this$props2.rowSpacing,
487
+ colSpacing = _this$props2.colSpacing,
442
488
  isRequired = _this$props2.isRequired,
443
489
  interaction = _this$props2.interaction,
444
490
  renderWeekdayLabels = _this$props2.renderWeekdayLabels;
445
491
  return /*#__PURE__*/React.createElement(FormFieldGroup, {
446
492
  description: description,
447
- colSpacing: "medium",
448
- rowSpacing: "small",
449
493
  layout: layout,
494
+ rowSpacing: rowSpacing,
495
+ colSpacing: colSpacing,
450
496
  vAlign: "top",
451
497
  elementRef: this.handleRef,
452
498
  messages: [...(this.state.message ? [this.state.message] : []), ...(messages || [])]
453
499
  }, /*#__PURE__*/React.createElement(DateInput, {
500
+ display: "block",
454
501
  value: this.state.dateInputText,
455
502
  onChange: this.handleDateTextChange,
456
503
  onBlur: this.handleBlur,
@@ -489,6 +536,8 @@ let DateTimeInput = (_dec = testable(), _dec(_class = (_class2 = class DateTimeI
489
536
 
490
537
  }, _class2.displayName = "DateTimeInput", _class2.DEFAULT_MESSAGE_FORMAT = 'LLLL', _class2.propTypes = propTypes, _class2.allowedProps = allowedProps, _class2.defaultProps = {
491
538
  layout: 'inline',
539
+ colSpacing: 'medium',
540
+ rowSpacing: 'small',
492
541
  timeStep: 30,
493
542
  messageFormat: _class2.DEFAULT_MESSAGE_FORMAT,
494
543
  isRequired: false,
@@ -42,6 +42,8 @@ const propTypes = {
42
42
  messages: PropTypes.arrayOf(FormPropTypes.message),
43
43
  messageFormat: PropTypes.string,
44
44
  layout: PropTypes.oneOf(['stacked', 'columns', 'inline']),
45
+ rowSpacing: PropTypes.oneOf(['none', 'small', 'medium', 'large']),
46
+ colSpacing: PropTypes.oneOf(['none', 'small', 'medium', 'large']),
45
47
  value: controllable(I18nPropTypes.iso8601, 'onChange'),
46
48
  defaultValue: I18nPropTypes.iso8601,
47
49
  renderWeekdayLabels: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.node])),
@@ -49,7 +51,9 @@ const propTypes = {
49
51
  onChange: PropTypes.func,
50
52
  dateInputRef: PropTypes.func,
51
53
  timeInputRef: PropTypes.func,
52
- onBlur: PropTypes.func
54
+ onBlur: PropTypes.func,
55
+ disabledDates: PropTypes.oneOfType([PropTypes.func, PropTypes.arrayOf(PropTypes.string)]),
56
+ disabledDateTimeMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
53
57
  };
54
- const allowedProps = ['description', 'dateRenderLabel', 'prevMonthLabel', 'nextMonthLabel', 'datePlaceholder', 'dateFormat', 'interaction', 'timeRenderLabel', 'timeStep', 'timeFormat', 'locale', 'timezone', 'invalidDateTimeMessage', 'messages', 'messageFormat', 'layout', 'value', 'defaultValue', 'renderWeekdayLabels', 'isRequired', 'onChange', 'dateInputRef', 'timeInputRef', 'onBlur'];
58
+ const allowedProps = ['description', 'dateRenderLabel', 'prevMonthLabel', 'nextMonthLabel', 'datePlaceholder', 'dateFormat', 'interaction', 'timeRenderLabel', 'timeStep', 'timeFormat', 'locale', 'timezone', 'invalidDateTimeMessage', 'messages', 'messageFormat', 'layout', 'rowSpacing', 'colSpacing', 'value', 'defaultValue', 'renderWeekdayLabels', 'isRequired', 'onChange', 'dateInputRef', 'timeInputRef', 'onBlur', 'disabledDates', 'disabledDateTimeMessage'];
55
59
  export { propTypes, allowedProps };
@@ -260,6 +260,29 @@ let DateTimeInput = (_dec = (0, _testable.testable)(), _dec(_class = (_class2 =
260
260
  }
261
261
 
262
262
  const newTimeSelectValue = (_this$state = this.state) !== null && _this$state !== void 0 && _this$state.timeSelectValue ? this.state.timeSelectValue : (_this$_timeInput = this._timeInput) === null || _this$_timeInput === void 0 ? void 0 : _this$_timeInput.getBaseDate().minute(parsed.minute()).hour(parsed.hour()).toISOString();
263
+
264
+ if (this.isDisabledDate(parsed)) {
265
+ let text = typeof this.props.disabledDateTimeMessage === 'function' ? this.props.disabledDateTimeMessage(parsed.toISOString(true)) : this.props.disabledDateTimeMessage;
266
+
267
+ if (!text) {
268
+ text = typeof this.props.invalidDateTimeMessage === 'function' ? this.props.invalidDateTimeMessage(parsed.toISOString(true)) : this.props.invalidDateTimeMessage;
269
+ }
270
+
271
+ errorMsg = text ? {
272
+ text,
273
+ type: 'error'
274
+ } : void 0;
275
+ return {
276
+ iso: parsed.clone(),
277
+ calendarSelectedDate: parsed.clone(),
278
+ dateInputTextChanged: false,
279
+ dateInputText: parsed.format(this.dateFormat),
280
+ message: errorMsg,
281
+ timeSelectValue: newTimeSelectValue,
282
+ renderedDate: parsed.clone()
283
+ };
284
+ }
285
+
263
286
  return {
264
287
  iso: parsed.clone(),
265
288
  calendarSelectedDate: parsed.clone(),
@@ -315,6 +338,26 @@ let DateTimeInput = (_dec = (0, _testable.testable)(), _dec(_class = (_class2 =
315
338
 
316
339
  get dateFormat() {
317
340
  return this.props.dateFormat;
341
+ }
342
+
343
+ isDisabledDate(date) {
344
+ const disabledDates = this.props.disabledDates;
345
+
346
+ if (!disabledDates) {
347
+ return false;
348
+ }
349
+
350
+ if (Array.isArray(disabledDates)) {
351
+ for (const aDisabledDate of disabledDates) {
352
+ if (date.isSame(aDisabledDate, 'day')) {
353
+ return true;
354
+ }
355
+ }
356
+
357
+ return false;
358
+ }
359
+
360
+ return disabledDates(date.toISOString());
318
361
  } // Called when the user enters text into dateInput
319
362
 
320
363
 
@@ -378,7 +421,8 @@ let DateTimeInput = (_dec = (0, _testable.testable)(), _dec(_class = (_class2 =
378
421
  isOutsideMonth: !date.isSame(renderedDate, 'month'),
379
422
  label: date.format('D MMMM YYYY') // used by screen readers
380
423
  ,
381
- onClick: this.handleDayClick
424
+ onClick: this.handleDayClick,
425
+ interaction: this.isDisabledDate(date) ? 'disabled' : 'enabled'
382
426
  }, date.format('DD'));
383
427
  });
384
428
  } // The default weekdays rendered in the calendar
@@ -450,18 +494,21 @@ let DateTimeInput = (_dec = (0, _testable.testable)(), _dec(_class = (_class2 =
450
494
  timezone = _this$props2.timezone,
451
495
  messages = _this$props2.messages,
452
496
  layout = _this$props2.layout,
497
+ rowSpacing = _this$props2.rowSpacing,
498
+ colSpacing = _this$props2.colSpacing,
453
499
  isRequired = _this$props2.isRequired,
454
500
  interaction = _this$props2.interaction,
455
501
  renderWeekdayLabels = _this$props2.renderWeekdayLabels;
456
502
  return /*#__PURE__*/_react.default.createElement(_FormFieldGroup.FormFieldGroup, {
457
503
  description: description,
458
- colSpacing: "medium",
459
- rowSpacing: "small",
460
504
  layout: layout,
505
+ rowSpacing: rowSpacing,
506
+ colSpacing: colSpacing,
461
507
  vAlign: "top",
462
508
  elementRef: this.handleRef,
463
509
  messages: [...(this.state.message ? [this.state.message] : []), ...(messages || [])]
464
510
  }, /*#__PURE__*/_react.default.createElement(_DateInput.DateInput, {
511
+ display: "block",
465
512
  value: this.state.dateInputText,
466
513
  onChange: this.handleDateTextChange,
467
514
  onBlur: this.handleBlur,
@@ -500,6 +547,8 @@ let DateTimeInput = (_dec = (0, _testable.testable)(), _dec(_class = (_class2 =
500
547
 
501
548
  }, _class2.displayName = "DateTimeInput", _class2.DEFAULT_MESSAGE_FORMAT = 'LLLL', _class2.propTypes = _props.propTypes, _class2.allowedProps = _props.allowedProps, _class2.defaultProps = {
502
549
  layout: 'inline',
550
+ colSpacing: 'medium',
551
+ rowSpacing: 'small',
503
552
  timeStep: 30,
504
553
  messageFormat: _class2.DEFAULT_MESSAGE_FORMAT,
505
554
  isRequired: false,
@@ -55,6 +55,8 @@ const propTypes = {
55
55
  messages: _propTypes.default.arrayOf(_FormPropTypes.FormPropTypes.message),
56
56
  messageFormat: _propTypes.default.string,
57
57
  layout: _propTypes.default.oneOf(['stacked', 'columns', 'inline']),
58
+ rowSpacing: _propTypes.default.oneOf(['none', 'small', 'medium', 'large']),
59
+ colSpacing: _propTypes.default.oneOf(['none', 'small', 'medium', 'large']),
58
60
  value: (0, _controllable.controllable)(_I18nPropTypes.I18nPropTypes.iso8601, 'onChange'),
59
61
  defaultValue: _I18nPropTypes.I18nPropTypes.iso8601,
60
62
  renderWeekdayLabels: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node])),
@@ -62,8 +64,10 @@ const propTypes = {
62
64
  onChange: _propTypes.default.func,
63
65
  dateInputRef: _propTypes.default.func,
64
66
  timeInputRef: _propTypes.default.func,
65
- onBlur: _propTypes.default.func
67
+ onBlur: _propTypes.default.func,
68
+ disabledDates: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.arrayOf(_propTypes.default.string)]),
69
+ disabledDateTimeMessage: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.func])
66
70
  };
67
71
  exports.propTypes = propTypes;
68
- const allowedProps = ['description', 'dateRenderLabel', 'prevMonthLabel', 'nextMonthLabel', 'datePlaceholder', 'dateFormat', 'interaction', 'timeRenderLabel', 'timeStep', 'timeFormat', 'locale', 'timezone', 'invalidDateTimeMessage', 'messages', 'messageFormat', 'layout', 'value', 'defaultValue', 'renderWeekdayLabels', 'isRequired', 'onChange', 'dateInputRef', 'timeInputRef', 'onBlur'];
72
+ const allowedProps = ['description', 'dateRenderLabel', 'prevMonthLabel', 'nextMonthLabel', 'datePlaceholder', 'dateFormat', 'interaction', 'timeRenderLabel', 'timeStep', 'timeFormat', 'locale', 'timezone', 'invalidDateTimeMessage', 'messages', 'messageFormat', 'layout', 'rowSpacing', 'colSpacing', 'value', 'defaultValue', 'renderWeekdayLabels', 'isRequired', 'onChange', 'dateInputRef', 'timeInputRef', 'onBlur', 'disabledDates', 'disabledDateTimeMessage'];
69
73
  exports.allowedProps = allowedProps;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-date-time-input",
3
- "version": "8.23.1-snapshot.7+67fb0a5be",
3
+ "version": "8.24.0",
4
4
  "description": "A UI component that allows to select date and time.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -23,26 +23,26 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@instructure/ui-babel-preset": "8.23.1-snapshot.7+67fb0a5be",
27
- "@instructure/ui-test-locator": "8.23.1-snapshot.7+67fb0a5be",
28
- "@instructure/ui-test-utils": "8.23.1-snapshot.7+67fb0a5be"
26
+ "@instructure/ui-babel-preset": "8.24.0",
27
+ "@instructure/ui-test-locator": "8.24.0",
28
+ "@instructure/ui-test-utils": "8.24.0"
29
29
  },
30
30
  "dependencies": {
31
31
  "@babel/runtime": "^7.13.10",
32
- "@instructure/debounce": "8.23.1-snapshot.7+67fb0a5be",
33
- "@instructure/emotion": "8.23.1-snapshot.7+67fb0a5be",
34
- "@instructure/ui-a11y-content": "8.23.1-snapshot.7+67fb0a5be",
35
- "@instructure/ui-buttons": "8.23.1-snapshot.7+67fb0a5be",
36
- "@instructure/ui-calendar": "8.23.1-snapshot.7+67fb0a5be",
37
- "@instructure/ui-date-input": "8.23.1-snapshot.7+67fb0a5be",
38
- "@instructure/ui-form-field": "8.23.1-snapshot.7+67fb0a5be",
39
- "@instructure/ui-i18n": "8.23.1-snapshot.7+67fb0a5be",
40
- "@instructure/ui-icons": "8.23.1-snapshot.7+67fb0a5be",
41
- "@instructure/ui-prop-types": "8.23.1-snapshot.7+67fb0a5be",
42
- "@instructure/ui-react-utils": "8.23.1-snapshot.7+67fb0a5be",
43
- "@instructure/ui-testable": "8.23.1-snapshot.7+67fb0a5be",
44
- "@instructure/ui-time-select": "8.23.1-snapshot.7+67fb0a5be",
45
- "@instructure/ui-utils": "8.23.1-snapshot.7+67fb0a5be",
32
+ "@instructure/debounce": "8.24.0",
33
+ "@instructure/emotion": "8.24.0",
34
+ "@instructure/ui-a11y-content": "8.24.0",
35
+ "@instructure/ui-buttons": "8.24.0",
36
+ "@instructure/ui-calendar": "8.24.0",
37
+ "@instructure/ui-date-input": "8.24.0",
38
+ "@instructure/ui-form-field": "8.24.0",
39
+ "@instructure/ui-i18n": "8.24.0",
40
+ "@instructure/ui-icons": "8.24.0",
41
+ "@instructure/ui-prop-types": "8.24.0",
42
+ "@instructure/ui-react-utils": "8.24.0",
43
+ "@instructure/ui-testable": "8.24.0",
44
+ "@instructure/ui-time-select": "8.24.0",
45
+ "@instructure/ui-utils": "8.24.0",
46
46
  "prop-types": "^15"
47
47
  },
48
48
  "peerDependencies": {
@@ -51,6 +51,5 @@
51
51
  "publishConfig": {
52
52
  "access": "public"
53
53
  },
54
- "sideEffects": false,
55
- "gitHead": "67fb0a5bed0138c5152872999bf1138dd2fd8fd7"
54
+ "sideEffects": false
56
55
  }
@@ -5,13 +5,19 @@ describes: DateTimeInput
5
5
  A DateTimeInput component is used to enter a date-time value. It is built as a composition
6
6
  of the [DateInput](#DateInput) and [TimeSelect](#TimeSelect) components.
7
7
 
8
- #### Localization
8
+ The properties of DateTimeInput prefaced with _date_ are passed to the underlying [DateInput](#DateInput)
9
+ (e.g. _dateLabel_ is DateInput's _label_), while the properties prefaced
10
+ with _time_ are forwarded to the underlying
11
+ [TimeSelect](#TimeSelect). Others are either shared by both sub-components (e.g. locale), or
12
+ are unique to DateTimeInput (e.g. description).
13
+
14
+ ### Localization
9
15
 
10
16
  The component is localized via its `locale` and `timezone` parameters. Both are read from `props`, `context` and from the browser's locale in this priority order. `locale` determines the language and format dates and time are displayed in (e.g. month names, AM/PM or 24-hour format) and the beginning of the week (e.g. Monday in Germany, Sunday in the U.S.) in the dropdown calendar.
11
17
 
12
- #### Examples
18
+ ### Examples
13
19
 
14
- A DateTimeInput with `columns` layout and a default value:
20
+ #### A DateTimeInput with `columns` layout and a default value:
15
21
 
16
22
  ```js
17
23
  ---
@@ -21,7 +27,7 @@ render: false
21
27
  class Example extends React.Component {
22
28
  render () {
23
29
  return (
24
- <div style= { {height: 350,width: '40em'}}>
30
+ <div style= { {height: '10rem',width: '40em'}}>
25
31
  <DateTimeInput
26
32
  description="Pick a date and time"
27
33
  datePlaceholder="Choose a date"
@@ -39,7 +45,7 @@ class Example extends React.Component {
39
45
  render(<Example />)
40
46
  ```
41
47
 
42
- A required DateInput with `stacked` layout that warns if the value in the past:
48
+ #### A required DateInput with `stacked` layout that warns if the value in the past:
43
49
 
44
50
  ```js
45
51
  ---
@@ -69,9 +75,9 @@ class Example extends React.Component {
69
75
  render () {
70
76
  const text = this.state.value ? new Date(this.state.value).toString() : 'N/A'
71
77
  return (
72
- <div style={{width: '20em'}}>
78
+ <div style={{width: '25em'}}>
73
79
  <div style={{marginBottom: '1em', fontStyle: 'italic'}}>You entered:<br/>{text}</div>
74
- <div style={{height: 350}}>
80
+ <div style={{height: '14rem'}}>
75
81
  <DateTimeInput
76
82
  description={<ScreenReaderContent>Pick a date and time</ScreenReaderContent>}
77
83
  datePlaceholder="Choose"
@@ -94,7 +100,7 @@ class Example extends React.Component {
94
100
  render(<Example />)
95
101
  ```
96
102
 
97
- A disabled DateTimeInput:
103
+ #### A disabled DateTimeInput:
98
104
 
99
105
  ```js
100
106
  ---
@@ -115,7 +121,7 @@ render: true
115
121
  />
116
122
  ```
117
123
 
118
- A DateTimeInput in a different locale and timezone where these are set from the React `context`:
124
+ #### A DateTimeInput in a different locale and timezone where these are set from the React `context`:
119
125
 
120
126
  ```js
121
127
  ---
@@ -126,7 +132,7 @@ class Example extends React.Component {
126
132
  render() {
127
133
  return (
128
134
  <ApplyLocale locale="fr" timezone="Africa/Nairobi">
129
- <div style={{ height: 350, width: '40em' }}>
135
+ <div style={{ height: '12rem', width: '40em' }}>
130
136
  <DateTimeInput
131
137
  description="Pick a date and time"
132
138
  datePlaceholder="Choose a date"
@@ -147,8 +153,67 @@ class Example extends React.Component {
147
153
  render(<Example />)
148
154
  ```
149
155
 
150
- The properties of DateTimeInput prefaced with _date_ are passed to the underlying [DateInput](#DateInput)
151
- (e.g. _dateLabel_ is DateInput's _label_), while the properties prefaced
152
- with _time_ are forwarded to the underlying
153
- [TimeSelect](#TimeSelect). Others are either shared by both sub-components (e.g. locale), or
154
- are unique to DateTimeInput (e.g. description).
156
+ #### A `DateTimeInput` with some disabled dates that are supplied via a `string` array:
157
+
158
+ ```js
159
+ ---
160
+ example: true
161
+ render: true
162
+ ---
163
+ <DateTimeInput
164
+ description="Pick a date and time"
165
+ datePlaceholder="Choose a date"
166
+ dateRenderLabel="Date"
167
+ timeRenderLabel="Time"
168
+ invalidDateTimeMessage="Invalid date"
169
+ disabledDateTimeMessage="Disabled date"
170
+ prevMonthLabel="Previous month"
171
+ nextMonthLabel="Next month"
172
+ defaultValue="2022-04-08T13:30"
173
+ layout="columns"
174
+ disabledDates={['2022-04-01T13:30', '2022-04-03T13:30', '2022-04-04T13:30']}
175
+ locale="en-us"
176
+ timezone="America/Denver"
177
+ />
178
+ ```
179
+
180
+ #### A `DateTimeInput` with some disabled dates that are supplied via a `function`:
181
+
182
+ ```js
183
+ ---
184
+ example: true
185
+ render: false
186
+ ---
187
+ const locale = 'en-us'
188
+ const timezone = 'America/Denver'
189
+
190
+ class Example extends React.Component {
191
+ getDisabledDates(isoDateToCheck) {
192
+ const parsed = moment.tz(isoDateToCheck, [moment.ISO_8601], locale, timezone)
193
+ const now = moment().locale(locale).tz(timezone)
194
+ return parsed.isBefore(now)
195
+ }
196
+ render() {
197
+ return (
198
+ <div style= { {height: '12rem', width: '40em'}}>
199
+ <DateTimeInput
200
+ description="Pick a date and time"
201
+ datePlaceholder="Choose a date"
202
+ dateRenderLabel="Date"
203
+ timeRenderLabel="Time"
204
+ invalidDateTimeMessage={(rawDateValue) => 'Invalid date: ' + rawDateValue}
205
+ disabledDateTimeMessage={(rawDateValue) => 'Disabled date: ' + rawDateValue}
206
+ prevMonthLabel="Previous month"
207
+ nextMonthLabel="Next month"
208
+ defaultValue="2022-04-08T13:30"
209
+ layout="columns"
210
+ disabledDates={this.getDisabledDates}
211
+ locale={locale}
212
+ timezone={timezone}
213
+ />
214
+ </div>)
215
+ }
216
+ }
217
+
218
+ render(<Example />)
219
+ ```
@@ -56,6 +56,8 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
56
56
  static allowedProps = allowedProps
57
57
  static defaultProps = {
58
58
  layout: 'inline',
59
+ colSpacing: 'medium',
60
+ rowSpacing: 'small',
59
61
  timeStep: 30,
60
62
  messageFormat: DateTimeInput.DEFAULT_MESSAGE_FORMAT,
61
63
  isRequired: false,
@@ -141,6 +143,28 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
141
143
  .minute(parsed.minute())
142
144
  .hour(parsed.hour())
143
145
  .toISOString()
146
+ if (this.isDisabledDate(parsed)) {
147
+ let text =
148
+ typeof this.props.disabledDateTimeMessage === 'function'
149
+ ? this.props.disabledDateTimeMessage(parsed.toISOString(true))
150
+ : this.props.disabledDateTimeMessage
151
+ if (!text) {
152
+ text =
153
+ typeof this.props.invalidDateTimeMessage === 'function'
154
+ ? this.props.invalidDateTimeMessage(parsed.toISOString(true))
155
+ : this.props.invalidDateTimeMessage
156
+ }
157
+ errorMsg = text ? { text, type: 'error' } : undefined
158
+ return {
159
+ iso: parsed.clone(),
160
+ calendarSelectedDate: parsed.clone(),
161
+ dateInputTextChanged: false,
162
+ dateInputText: parsed.format(this.dateFormat),
163
+ message: errorMsg,
164
+ timeSelectValue: newTimeSelectValue,
165
+ renderedDate: parsed.clone()
166
+ }
167
+ }
144
168
  return {
145
169
  iso: parsed.clone(),
146
170
  calendarSelectedDate: parsed.clone(),
@@ -194,6 +218,22 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
194
218
  return this.props.dateFormat
195
219
  }
196
220
 
221
+ isDisabledDate(date: Moment) {
222
+ const disabledDates = this.props.disabledDates
223
+ if (!disabledDates) {
224
+ return false
225
+ }
226
+ if (Array.isArray(disabledDates)) {
227
+ for (const aDisabledDate of disabledDates) {
228
+ if (date.isSame(aDisabledDate, 'day')) {
229
+ return true
230
+ }
231
+ }
232
+ return false
233
+ }
234
+ return disabledDates(date.toISOString())
235
+ }
236
+
197
237
  // Called when the user enters text into dateInput
198
238
  handleDateTextChange = (_event: SyntheticEvent, date: { value: string }) => {
199
239
  const dateParsed = this.tryParseDate(date.value)
@@ -406,6 +446,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
406
446
  isOutsideMonth={!date.isSame(renderedDate, 'month')}
407
447
  label={date.format('D MMMM YYYY')} // used by screen readers
408
448
  onClick={this.handleDayClick}
449
+ interaction={this.isDisabledDate(date) ? 'disabled' : 'enabled'}
409
450
  >
410
451
  {date.format('DD')}
411
452
  </DateInput.Day>
@@ -491,16 +532,19 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
491
532
  timezone,
492
533
  messages,
493
534
  layout,
535
+ rowSpacing,
536
+ colSpacing,
494
537
  isRequired,
495
538
  interaction,
496
539
  renderWeekdayLabels
497
540
  } = this.props
541
+
498
542
  return (
499
543
  <FormFieldGroup
500
544
  description={description}
501
- colSpacing="medium"
502
- rowSpacing="small"
503
545
  layout={layout}
546
+ rowSpacing={rowSpacing}
547
+ colSpacing={colSpacing}
504
548
  vAlign="top"
505
549
  elementRef={this.handleRef}
506
550
  messages={[
@@ -509,6 +553,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
509
553
  ]}
510
554
  >
511
555
  <DateInput
556
+ display="block"
512
557
  value={this.state.dateInputText}
513
558
  onChange={this.handleDateTextChange}
514
559
  onBlur={this.handleBlur}
@@ -152,6 +152,14 @@ type DateTimeInputProps = {
152
152
  * See [FormFieldGroup](#FormFieldGroup) for details.
153
153
  **/
154
154
  layout?: 'stacked' | 'columns' | 'inline'
155
+ /**
156
+ * Controls the spacing between the inputs when they are in a vertical layout.
157
+ **/
158
+ rowSpacing?: 'none' | 'small' | 'medium' | 'large'
159
+ /**
160
+ * Controls the spacing between the inputs when they are in a horizontal layout.
161
+ **/
162
+ colSpacing?: 'none' | 'small' | 'medium' | 'large'
155
163
  /**
156
164
  * An ISO 8601 formatted date string representing the current date-time
157
165
  * (must be accompanied by an onChange prop).
@@ -197,7 +205,7 @@ type DateTimeInputProps = {
197
205
  * Called when the date-time value has changed.
198
206
  * The passed in parameters are:
199
207
  * - *event*: The triggering event (which may be from the underlying
200
- * ` DateInput` or `TimeSelect`)
208
+ * `DateInput` or `TimeSelect`)
201
209
  * - *isoValue*: The new date value in ISO8601 format, undefined if its invalid
202
210
  **/
203
211
  onChange?: (event: SyntheticEvent, isoValue?: string) => void
@@ -215,6 +223,17 @@ type DateTimeInputProps = {
215
223
  * component
216
224
  */
217
225
  onBlur?: (e: SyntheticEvent) => void
226
+ /*
227
+ * Specify which date(s) will be shown as disabled in the calendar.
228
+ * You can either supply an array of ISO8601 timeDate strings or
229
+ * a function that will be called for each date shown in the calendar.
230
+ */
231
+ disabledDates?: string[] | ((isoDateToCheck: string) => boolean)
232
+ /**
233
+ * Error message shown to the user if they enter a date that is disabled.
234
+ * If not specified the component will show the `invalidDateTimeMessage`.
235
+ */
236
+ disabledDateTimeMessage?: string | ((rawDateValue: string) => string)
218
237
  }
219
238
 
220
239
  type DateTimeInputState = {
@@ -264,6 +283,8 @@ const propTypes: PropValidators<PropKeys> = {
264
283
  messages: PropTypes.arrayOf(FormPropTypes.message),
265
284
  messageFormat: PropTypes.string,
266
285
  layout: PropTypes.oneOf(['stacked', 'columns', 'inline']),
286
+ rowSpacing: PropTypes.oneOf(['none', 'small', 'medium', 'large']),
287
+ colSpacing: PropTypes.oneOf(['none', 'small', 'medium', 'large']),
267
288
  value: controllable(I18nPropTypes.iso8601, 'onChange'),
268
289
  defaultValue: I18nPropTypes.iso8601,
269
290
  renderWeekdayLabels: PropTypes.arrayOf(
@@ -273,7 +294,15 @@ const propTypes: PropValidators<PropKeys> = {
273
294
  onChange: PropTypes.func,
274
295
  dateInputRef: PropTypes.func,
275
296
  timeInputRef: PropTypes.func,
276
- onBlur: PropTypes.func
297
+ onBlur: PropTypes.func,
298
+ disabledDates: PropTypes.oneOfType([
299
+ PropTypes.func,
300
+ PropTypes.arrayOf(PropTypes.string)
301
+ ]),
302
+ disabledDateTimeMessage: PropTypes.oneOfType([
303
+ PropTypes.string,
304
+ PropTypes.func
305
+ ])
277
306
  }
278
307
 
279
308
  const allowedProps: AllowedPropKeys = [
@@ -293,6 +322,8 @@ const allowedProps: AllowedPropKeys = [
293
322
  'messages',
294
323
  'messageFormat',
295
324
  'layout',
325
+ 'rowSpacing',
326
+ 'colSpacing',
296
327
  'value',
297
328
  'defaultValue',
298
329
  'renderWeekdayLabels',
@@ -300,7 +331,9 @@ const allowedProps: AllowedPropKeys = [
300
331
  'onChange',
301
332
  'dateInputRef',
302
333
  'timeInputRef',
303
- 'onBlur'
334
+ 'onBlur',
335
+ 'disabledDates',
336
+ 'disabledDateTimeMessage'
304
337
  ]
305
338
 
306
339
  export type { DateTimeInputProps, DateTimeInputState }