@evoke-platform/ui-components 1.0.0-dev.190 → 1.0.0-dev.192

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.
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+ import { DateTimePickerProps as MUIDateTimePickerProps } from '@mui/x-date-pickers';
3
+ import { CalendarDate } from '../../../util';
4
+ export declare type DateTimePickerProps = Omit<MUIDateTimePickerProps<string | CalendarDate, CalendarDate>, 'renderInput'> & {
5
+ renderInput?: MUIDateTimePickerProps<string | CalendarDate, CalendarDate>['renderInput'];
6
+ };
7
+ declare const DateTimePicker: (props: DateTimePickerProps) => JSX.Element;
8
+ export default DateTimePicker;
@@ -0,0 +1,41 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import React from 'react';
13
+ import UIThemeProvider from '../../../theme';
14
+ import { DateTimePicker as MUIDateTimePicker, } from '@mui/x-date-pickers';
15
+ import TextField from '../TextField';
16
+ import { InvalidDate } from '../../../util';
17
+ import { LocalDateTime } from '@js-joda/core';
18
+ const DateTimePicker = (props) => {
19
+ var _a;
20
+ const { value: inputValue, onChange: handleChange } = props, rest = __rest(props, ["value", "onChange"]);
21
+ let value = null;
22
+ try {
23
+ value = typeof inputValue === 'string' ? LocalDateTime.parse(inputValue) : inputValue;
24
+ }
25
+ catch (err) {
26
+ // If we fail to parse the input string, log the error and continue as if no value was set.
27
+ console.error((_a = err.message) !== null && _a !== void 0 ? _a : err);
28
+ }
29
+ const onChange = (newValue, keyboardInputValue) => {
30
+ // Joda does not have a representation of an invalid date, so if the component fails to
31
+ // parse a date, the onChange receives an Error instead. Replace the error with our own
32
+ // invalid date marker.
33
+ if (newValue instanceof Error) {
34
+ newValue = new InvalidDate(keyboardInputValue);
35
+ }
36
+ handleChange(newValue, keyboardInputValue);
37
+ };
38
+ return (React.createElement(UIThemeProvider, null,
39
+ React.createElement(MUIDateTimePicker, Object.assign({ value: value, onChange: onChange, renderInput: (params) => React.createElement(TextField, Object.assign({}, params)) }, rest))));
40
+ };
41
+ export default DateTimePicker;
@@ -0,0 +1,2 @@
1
+ export * from './DateTimePicker';
2
+ export { default as DateTimePicker } from './DateTimePicker';
@@ -0,0 +1,2 @@
1
+ export * from './DateTimePicker';
2
+ export { default as DateTimePicker } from './DateTimePicker';
@@ -10,6 +10,7 @@ export { Checkbox } from './Checkbox';
10
10
  export { Chip } from './Chip';
11
11
  export { CircularProgress } from './CircularProgress';
12
12
  export * from './DatePicker';
13
+ export * from './DateTimePicker';
13
14
  export { Dialog, DialogTitle, DialogActions, DialogContent, DialogContentText } from './Dialog';
14
15
  export { Divider } from './Divider';
15
16
  export { Drawer } from './Drawer';
@@ -10,6 +10,7 @@ export { Checkbox } from './Checkbox';
10
10
  export { Chip } from './Chip';
11
11
  export { CircularProgress } from './CircularProgress';
12
12
  export * from './DatePicker';
13
+ export * from './DateTimePicker';
13
14
  export { Dialog, DialogTitle, DialogActions, DialogContent, DialogContentText } from './Dialog';
14
15
  export { Divider } from './Divider';
15
16
  export { Drawer } from './Drawer';
@@ -1,14 +1,15 @@
1
1
  import { Popover } from '@mui/material';
2
- import React, { useEffect, useState } from 'react';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
3
  import InputMask from 'react-input-mask';
4
- import { CancelOutlined } from '@mui/icons-material';
5
- import { Button, List, ListItem, MenuItem, TextField, Typography } from '../../../core';
4
+ import { List, MenuItem, TextField, Typography } from '../../../core';
6
5
  import { Box } from '../../../layout';
7
6
  const AddressFieldComponent = (props) => {
7
+ var _a;
8
8
  const { id, property, defaultValue, error, errorMessage, onBlur, readOnly, required, size, placeholder, mask, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, additionalProps, } = props;
9
9
  const [value, setValue] = useState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : '');
10
10
  const [selectOptions, setSelectOptions] = useState([]);
11
11
  const [anchorEl, setAnchorEl] = React.useState(null);
12
+ const textFieldRef = useRef(null);
12
13
  const open = Boolean(anchorEl);
13
14
  const popoverId = open ? `${id}-popover` : undefined;
14
15
  useEffect(() => {
@@ -25,23 +26,12 @@ const AddressFieldComponent = (props) => {
25
26
  setSelectOptions(addresses
26
27
  .filter((address) => address.address.line1)
27
28
  .map((address) => {
28
- let label = '', sublabel = '';
29
- if (address.address.line1)
30
- label = label.concat(address.address.line1);
31
- if (address.address.city)
32
- sublabel = sublabel.concat(`${address.address.city}`);
33
- if (address.address.county)
34
- sublabel = sublabel
35
- ? sublabel.concat(`, ${address.address.county}`)
36
- : address.address.county;
37
- if (address.address.state)
38
- sublabel = sublabel
39
- ? sublabel.concat(`, ${address.address.state}`)
40
- : address.address.state;
41
- if (address.address.zipCode)
42
- sublabel = sublabel
43
- ? sublabel.concat(` ${address.address.zipCode}`)
44
- : address.address.zipCode;
29
+ const { line1, city, county, state, zipCode } = address.address;
30
+ const label = line1 !== null && line1 !== void 0 ? line1 : '';
31
+ let sublabel = [city, county, state].filter(Boolean).join(', ');
32
+ if (zipCode) {
33
+ sublabel = sublabel ? `${sublabel} ${zipCode}` : zipCode;
34
+ }
45
35
  return { label, sublabel, value: address.address };
46
36
  }));
47
37
  });
@@ -56,10 +46,10 @@ const AddressFieldComponent = (props) => {
56
46
  setAnchorEl(null);
57
47
  };
58
48
  return (React.createElement(Box, null,
59
- !mask ? (React.createElement(TextField, Object.assign({ id: id, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size !== null && size !== void 0 ? size : 'medium', placeholder: placeholder, InputProps: {
49
+ !mask ? (React.createElement(TextField, Object.assign({ id: id, inputRef: textFieldRef, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size !== null && size !== void 0 ? size : 'medium', placeholder: placeholder, InputProps: {
60
50
  type: 'search',
61
51
  autoComplete: 'off',
62
- }, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined }, (additionalProps !== null && additionalProps !== void 0 ? additionalProps : {})))) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar !== null && inputMaskPlaceholderChar !== void 0 ? inputMaskPlaceholderChar : '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, () => (React.createElement(TextField, Object.assign({ id: id, sx: readOnly
52
+ }, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined }, (additionalProps !== null && additionalProps !== void 0 ? additionalProps : {})))) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar !== null && inputMaskPlaceholderChar !== void 0 ? inputMaskPlaceholderChar : '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, () => (React.createElement(TextField, Object.assign({ id: id, inputRef: textFieldRef, sx: readOnly
63
53
  ? {
64
54
  '& .MuiOutlinedInput-notchedOutline': {
65
55
  border: 'none',
@@ -78,23 +68,23 @@ const AddressFieldComponent = (props) => {
78
68
  horizontal: 'left',
79
69
  }, PaperProps: {
80
70
  sx: {
81
- width: '100%',
82
- height: '400px',
83
- maxWidth: '300px',
84
- boxShadow: 'none',
71
+ width: ((_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth)
72
+ ? `${textFieldRef.current.offsetWidth}px`
73
+ : '100%',
74
+ height: 'fit-content',
75
+ maxHeight: '400px',
76
+ borderRadius: '8px',
77
+ boxShadow: 'rgb(222, 218, 218) -2px 4px 5px -3px, rgb(150 150 150 / 14%) 0px 0px 10px 2px',
85
78
  zIndex: 2000,
86
79
  },
87
80
  } },
88
- React.createElement(List, null,
89
- selectOptions.map((option) => {
90
- return (React.createElement(MenuItem, { key: option.label + option.sublabel, onClick: () => handleClick(option) },
91
- React.createElement(Box, { sx: {
92
- flexGrow: 1,
93
- } },
94
- React.createElement(Typography, null, option.label),
95
- React.createElement(Typography, { sx: { color: '#586069' } }, option.sublabel))));
96
- }),
97
- React.createElement(ListItem, { sx: { justifyContent: 'center' } },
98
- React.createElement(Button, { fullWidth: true, size: "small", color: "primary", variant: "outlined", startIcon: React.createElement(CancelOutlined, null), onClick: () => handleClose() }, "Close")))))));
81
+ React.createElement(List, null, selectOptions.map((option, index) => {
82
+ return (React.createElement(MenuItem, { key: `${option.label}-${option.sublabel}-${index}`, onClick: () => handleClick(option) },
83
+ React.createElement(Box, { sx: {
84
+ flexGrow: 1,
85
+ } },
86
+ React.createElement(Typography, null, option.label),
87
+ React.createElement(Typography, { sx: { color: '#586069' } }, option.sublabel))));
88
+ }))))));
99
89
  };
100
90
  export default AddressFieldComponent;
@@ -26,14 +26,14 @@ const asMonthDayYearFormat = (date) => {
26
26
  }
27
27
  };
28
28
  const DatePickerSelect = (props) => {
29
- const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, additionalProps } = props;
29
+ const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, onChange, additionalProps, } = props;
30
30
  const [value, setValue] = useState(asCalendarDate(defaultValue));
31
31
  useEffect(() => {
32
32
  setValue(asCalendarDate(defaultValue));
33
33
  }, [defaultValue]);
34
34
  const handleChange = (date) => {
35
35
  setValue(date);
36
- props.onChange(property.id, date, property);
36
+ onChange(property.id, date, property);
37
37
  };
38
38
  return readOnly ? (React.createElement(InputFieldComponent, Object.assign({}, Object.assign(Object.assign({}, props), { defaultValue: asMonthDayYearFormat(value) })))) : (React.createElement(LocalizationProvider, null,
39
39
  React.createElement(DatePicker, { value: value, onChange: handleChange, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, Object.assign({}, params, { id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size !== null && size !== void 0 ? size : 'medium' }, (additionalProps !== null && additionalProps !== void 0 ? additionalProps : {})))) })));
@@ -0,0 +1,6 @@
1
+ /// <reference types="react" />
2
+ import { FormFieldProps } from '../FormField';
3
+ declare const DateTimePickerSelect: (props: FormFieldProps & {
4
+ id: string;
5
+ }) => JSX.Element;
6
+ export default DateTimePickerSelect;
@@ -0,0 +1,41 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { DateTimePicker, LocalizationProvider, TextField } from '../../../core';
3
+ import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
4
+ import { LocalDate, LocalDateTime, LocalTime, nativeJs } from '@js-joda/core';
5
+ import { InvalidDate } from '../../../../util';
6
+ function asCalendarDate(value) {
7
+ if (!value) {
8
+ return null;
9
+ }
10
+ if (value instanceof LocalDateTime || value instanceof InvalidDate) {
11
+ return value;
12
+ }
13
+ if (value instanceof Date) {
14
+ if (isNaN(value.getTime())) {
15
+ // LocalDateTime cannot represent an invalid date, use InvalidDate instead.
16
+ return new InvalidDate();
17
+ }
18
+ return LocalDateTime.from(nativeJs(value));
19
+ }
20
+ return value.toString();
21
+ }
22
+ const DateTimePickerSelect = (props) => {
23
+ const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, additionalProps } = props;
24
+ const [value, setValue] = useState(asCalendarDate(defaultValue));
25
+ useEffect(() => {
26
+ setValue(asCalendarDate(defaultValue));
27
+ }, [defaultValue]);
28
+ const handleChange = (date) => {
29
+ // The date picker component initially returns a LocalDate
30
+ // before the time is picked, causing issues downstream.
31
+ // Convert it to a LocalDateTime to avoid issues.
32
+ if (date instanceof LocalDate) {
33
+ date = LocalDateTime.of(date, LocalTime.of(0));
34
+ }
35
+ setValue(date);
36
+ props.onChange(property.id, date, property);
37
+ };
38
+ return readOnly ? (React.createElement(InputFieldComponent, Object.assign({}, Object.assign(Object.assign({}, props), { defaultValue: value })))) : (React.createElement(LocalizationProvider, null,
39
+ React.createElement(DateTimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, Object.assign({}, params, { id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size !== null && size !== void 0 ? size : 'medium' }, (additionalProps !== null && additionalProps !== void 0 ? additionalProps : {})))) })));
40
+ };
41
+ export default DateTimePickerSelect;
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/extend-expect';
@@ -0,0 +1,80 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import '@testing-library/jest-dom/extend-expect';
11
+ import React from 'react';
12
+ import { render, screen, within } from '@testing-library/react';
13
+ import { userEvent, PointerEventsCheckLevel } from '@testing-library/user-event';
14
+ import DateTimePickerSelect from './DateTimePickerSelect';
15
+ import { LocalDateTime, Month } from '@js-joda/core';
16
+ import { InvalidDate } from '../../../../util';
17
+ // Right now an object property is required for this to function, but eventually this should go
18
+ // away.
19
+ const dateTimeProperty = {
20
+ id: 'testDateTime',
21
+ name: 'Date Time',
22
+ type: 'date-time',
23
+ };
24
+ beforeAll(() => {
25
+ // Set specific date for a repeatable test.
26
+ jest.useFakeTimers({
27
+ now: new Date('2024-02-11T05:25:25'),
28
+ // Only want to set current time, don't override timer functions as that will
29
+ // confuse @testing-library.
30
+ doNotFake: [
31
+ 'setImmediate',
32
+ 'clearImmediate',
33
+ 'setTimeout',
34
+ 'clearTimeout',
35
+ 'setInterval',
36
+ 'clearInterval',
37
+ 'nextTick',
38
+ 'queueMicrotask',
39
+ ],
40
+ });
41
+ });
42
+ afterAll(() => {
43
+ jest.useRealTimers();
44
+ });
45
+ test('returns selected date time', () => __awaiter(void 0, void 0, void 0, function* () {
46
+ const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never });
47
+ const onChangeMock = jest.fn((name, value, property) => { });
48
+ render(React.createElement(DateTimePickerSelect, { id: "pickDateTime", property: dateTimeProperty, onChange: onChangeMock }));
49
+ const calendarIcon = screen.getByRole('button', { name: /Choose date/i });
50
+ yield user.click(calendarIcon);
51
+ const calendar = yield screen.findByRole('dialog');
52
+ const feb29 = yield within(calendar).findByRole('gridcell', { name: '29' });
53
+ yield user.click(feb29);
54
+ // The test can't actually check that the correct time was picked due to issues
55
+ // testing the time picker,
56
+ // https://stackoverflow.com/questions/74812963/how-do-i-pick-the-time-in-an-mui-datetimepicker-using-testing-library-react-and.
57
+ // All it can do is check the date picker part works.
58
+ expect(onChangeMock).toBeCalledWith('testDateTime', LocalDateTime.of(2024, Month.FEBRUARY, 29, 0, 0, 0), dateTimeProperty);
59
+ }));
60
+ test('returns manually entered date time', () => __awaiter(void 0, void 0, void 0, function* () {
61
+ const user = userEvent.setup();
62
+ const onChangeMock = jest.fn((name, value, property) => { });
63
+ render(React.createElement(DateTimePickerSelect, { id: "pickDateTime", property: dateTimeProperty, onChange: onChangeMock }));
64
+ const input = screen.getByRole('textbox');
65
+ yield user.type(input, '03/27/2024 09:25 AM');
66
+ // component ignores '/', i.e. input could also have been '03272024'.
67
+ // It also treats the AM as one event since one a user types 'A' or 'P'
68
+ // the picker fills in the 'M'.
69
+ expect(onChangeMock).toBeCalledTimes(13);
70
+ expect(onChangeMock).lastCalledWith('testDateTime', LocalDateTime.of(2024, Month.MARCH, 27, 9, 25), dateTimeProperty);
71
+ }));
72
+ test('returns incomplete date times', () => __awaiter(void 0, void 0, void 0, function* () {
73
+ const user = userEvent.setup();
74
+ const onChangeMock = jest.fn((name, value, property) => { });
75
+ render(React.createElement(DateTimePickerSelect, { id: "pickDateTime", property: dateTimeProperty, onChange: onChangeMock }));
76
+ const input = screen.getByRole('textbox');
77
+ yield user.type(input, '08/20');
78
+ // Component automatically appends '/' as needed.
79
+ expect(onChangeMock).lastCalledWith('testDateTime', new InvalidDate('08/20/'), dateTimeProperty);
80
+ }));
@@ -0,0 +1 @@
1
+ export * from './DateTimePickerSelect';
@@ -0,0 +1 @@
1
+ export * from './DateTimePickerSelect';
@@ -6,6 +6,7 @@ import FileUploadControl from './FileUpload/FileUpload';
6
6
  import InputFieldComponent from './InputFieldComponent/InputFieldComponent';
7
7
  import Select from './Select/Select';
8
8
  import TimePickerSelect from './TimePickerSelect/TimePickerSelect';
9
+ import DateTimePickerSelect from './DateTimePickerSelect/DateTimePickerSelect';
9
10
  const FormField = (props) => {
10
11
  const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, } = props;
11
12
  let control;
@@ -41,6 +42,9 @@ const FormField = (props) => {
41
42
  case 'date':
42
43
  control = React.createElement(DatePickerSelect, Object.assign({}, commonProps));
43
44
  break;
45
+ case 'date-time':
46
+ control = React.createElement(DateTimePickerSelect, Object.assign({}, commonProps));
47
+ break;
44
48
  case 'time':
45
49
  control = React.createElement(TimePickerSelect, Object.assign({}, commonProps));
46
50
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.0.0-dev.190",
3
+ "version": "1.0.0-dev.192",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",