Skip to content

Commit 1e83b46

Browse files
committed
+'d picker and action bar from project
1 parent 64813d6 commit 1e83b46

File tree

2 files changed

+301
-0
lines changed

2 files changed

+301
-0
lines changed

src/CustomActionBar.tsx

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as React from 'react';
2+
import Button from '@mui/material/Button';
3+
import DialogActions from '@mui/material/DialogActions';
4+
import { PickersActionBarProps } from '@mui/x-date-pickers/PickersActionBar';
5+
import { useLocaleText } from '@mui/x-date-pickers/internals';
6+
7+
interface CustomPickersActionBarProps extends PickersActionBarProps {
8+
handleChange: (value: any) => void;
9+
setFragileValue: React.Dispatch<React.SetStateAction<Date>>;
10+
}
11+
12+
const CustomActionBar = (props: CustomPickersActionBarProps) => {
13+
const { onAccept, onClear, onCancel, actions, sx, handleChange, setFragileValue } = props;
14+
const localeText = useLocaleText();
15+
16+
if (null == actions || 0 === actions.length) {
17+
return null;
18+
}
19+
20+
const actionButtons = actions?.map(actionType => {
21+
switch (actionType) {
22+
case 'clear':
23+
return (
24+
<Button
25+
data-mui-test="clear-action-button"
26+
onClick={() => {
27+
onClear();
28+
}}
29+
key={actionType}
30+
>
31+
{localeText.clearButtonLabel}
32+
</Button>
33+
);
34+
case 'cancel':
35+
return (
36+
<Button
37+
onClick={() => {
38+
onCancel();
39+
}}
40+
key={actionType}
41+
>
42+
{localeText.cancelButtonLabel}
43+
</Button>
44+
);
45+
case 'accept':
46+
return (
47+
<Button
48+
onClick={() => {
49+
onAccept();
50+
}}
51+
key={actionType}
52+
>
53+
{localeText.okButtonLabel}
54+
</Button>
55+
);
56+
case 'today':
57+
return (
58+
<Button
59+
data-mui-test="today-action-button"
60+
onClick={() => {
61+
const now = new Date();
62+
handleChange(now);
63+
setFragileValue(now);
64+
onAccept();
65+
}}
66+
key={actionType}
67+
>
68+
{localeText.todayButtonLabel}
69+
</Button>
70+
);
71+
default:
72+
return null;
73+
}
74+
});
75+
76+
return <DialogActions sx={sx}>{actionButtons}</DialogActions>;
77+
};
78+
79+
export default CustomActionBar;

src/Picker.tsx

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import React, { useCallback, useState } from 'react';
2+
import PropTypes from 'prop-types';
3+
import clsx from 'clsx';
4+
import { useInput, FieldTitle, sanitizeInputRestProps, InputHelperText } from 'react-admin';
5+
import InputAdornment from '@mui/material/InputAdornment';
6+
import Event from '@mui/icons-material/Event';
7+
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
8+
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
9+
import CustomActionBar from './CustomActionBar';
10+
11+
// This code copied from WiXSL/react-admin-date-inputs/blob/mui-pickers-v3/ because the repo host key was not maintained
12+
13+
const leftPad =
14+
(nb = 2) =>
15+
value =>
16+
('0'.repeat(nb) + value).slice(-nb);
17+
const leftPad4 = leftPad(4);
18+
const leftPad2 = leftPad(2);
19+
20+
/**
21+
* @param {Date} value value to convert
22+
* @returns {String} A standardized datetime (yyyy-MM-ddThh:mm), to be passed to an <input type="datetime-local" />
23+
*/
24+
const convertDateToString = value => {
25+
if (!(value instanceof Date) || isNaN(value.getDate())) {
26+
return '';
27+
}
28+
29+
const yy = leftPad4(value.getFullYear());
30+
const MM = leftPad2(value.getMonth() + 1);
31+
const dd = leftPad2(value.getDate());
32+
const hh = leftPad2(value.getHours());
33+
const mm = leftPad2(value.getMinutes());
34+
return `${yy}-${MM}-${dd}T${hh}:${mm}`;
35+
};
36+
37+
// yyyy-MM-ddThh:mm
38+
const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/;
39+
40+
/**
41+
* Converts a date from the Redux store, with timezone, to a date string
42+
* without timezone for use in an <input type="datetime-local" />.
43+
*
44+
* @param {Date | String} value date string or object
45+
*/
46+
const formatDateTime = (value: string | Date) => {
47+
// null, undefined and empty string values should not go through convertDateToString
48+
// otherwise, it returns undefined and will make the input an uncontrolled one.
49+
if (null == value || '' === value) {
50+
return '';
51+
}
52+
53+
if (value instanceof Date) {
54+
return convertDateToString(value);
55+
}
56+
// valid dates should not be converted
57+
if (dateTimeRegex.test(value)) {
58+
return value;
59+
}
60+
61+
return convertDateToString(new Date(value));
62+
};
63+
64+
/**
65+
* Converts a datetime string without timezone to a date object
66+
* with timezone, using the browser timezone.
67+
*
68+
* @param {String} value Date string, formatted as yyyy-MM-ddThh:mm
69+
* @return {Date}
70+
*/
71+
72+
const parseDateTime = (value: string) => (value ? new Date(value) : null);
73+
74+
const Picker = props => {
75+
const {
76+
PickerComponent,
77+
removeClear,
78+
defaultValue,
79+
format,
80+
label,
81+
className,
82+
options,
83+
source,
84+
resource,
85+
helperText,
86+
fullWidth,
87+
inputSize,
88+
inputVariant,
89+
margin,
90+
onChange,
91+
onOpen,
92+
onClose,
93+
parse,
94+
validate,
95+
stringFormat,
96+
...rest
97+
} = props;
98+
99+
const {
100+
field,
101+
fieldState: { error, invalid, isTouched },
102+
formState: { isSubmitted },
103+
id,
104+
isRequired,
105+
} = useInput({
106+
defaultValue,
107+
format,
108+
parse,
109+
onChange,
110+
resource,
111+
source,
112+
validate,
113+
...rest,
114+
});
115+
116+
const [fragileValue, setFragileValue] = useState(field.value ? new Date(field.value) : null);
117+
118+
const handleChange = useCallback(
119+
value =>
120+
Date.parse(value)
121+
? field.onChange('ISO' === stringFormat ? value.toISOString() : value.toString())
122+
: field.onChange(null),
123+
[field, stringFormat]
124+
);
125+
126+
const hasError = (isTouched || isSubmitted) && invalid;
127+
128+
const renderHelperText = false !== helperText || ((isTouched || isSubmitted) && invalid);
129+
130+
const toolbarActions = removeClear ? ['cancel', 'today', 'accept'] : ['cancel', 'clear', 'today', 'accept'];
131+
132+
return (
133+
<LocalizationProvider dateAdapter={AdapterDateFns}>
134+
<PickerComponent
135+
{...sanitizeInputRestProps(rest)}
136+
{...field}
137+
{...options}
138+
id={id}
139+
label={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />}
140+
slots={{
141+
actionBar: CustomActionBar,
142+
}}
143+
slotProps={{
144+
textField: {
145+
variant: inputVariant,
146+
size: inputSize,
147+
margin,
148+
fullWidth,
149+
onBlur: field.onBlur,
150+
error: hasError,
151+
helperText: renderHelperText ? (
152+
<InputHelperText
153+
touched={isTouched || isSubmitted}
154+
error={error?.message}
155+
helperText={helperText}
156+
/>
157+
) : null,
158+
InputProps: {
159+
endAdornment: (
160+
<InputAdornment position="end">
161+
<Event />
162+
</InputAdornment>
163+
),
164+
},
165+
},
166+
actionBar: {
167+
actions: toolbarActions,
168+
handleChange,
169+
setFragileValue,
170+
},
171+
}}
172+
className={clsx('ra-input', `ra-input-${source}`, className)}
173+
value={field.value ? new Date(field.value) : null}
174+
onChange={(value, context) => setFragileValue(value)}
175+
onOpen={value => (onOpen && 'function' === typeof onOpen ? onOpen(value) : null)}
176+
onClose={value => (onClose && 'function' === typeof onClose ? onClose(value) : null)}
177+
onAccept={value => handleChange(fragileValue)}
178+
/>
179+
</LocalizationProvider>
180+
);
181+
};
182+
183+
Picker.propTypes = {
184+
PickerComponent: PropTypes.object.isRequired,
185+
removeClear: PropTypes.bool,
186+
fullWidth: PropTypes.bool,
187+
format: PropTypes.func,
188+
parse: PropTypes.func,
189+
label: PropTypes.string,
190+
onChange: PropTypes.func,
191+
onOpen: PropTypes.func,
192+
onClose: PropTypes.func,
193+
options: PropTypes.object,
194+
resource: PropTypes.string,
195+
source: PropTypes.string,
196+
inputSize: PropTypes.string,
197+
inputVariant: PropTypes.string,
198+
labelTime: PropTypes.string,
199+
margin: PropTypes.string,
200+
variant: PropTypes.string,
201+
className: PropTypes.string,
202+
stringFormat: PropTypes.string,
203+
};
204+
205+
Picker.defaultProps = {
206+
removeClear: false,
207+
fullWidth: false,
208+
inputSize: 'small',
209+
inputVariant: 'filled',
210+
margin: 'dense',
211+
stringFormat: 'ISO',
212+
format: formatDateTime,
213+
parse: parseDateTime,
214+
label: '',
215+
options: {},
216+
resource: '',
217+
source: '',
218+
labelTime: '',
219+
className: '',
220+
};
221+
222+
export default Picker;

0 commit comments

Comments
 (0)