diff --git a/docs/Inputs.md b/docs/Inputs.md index 07bb4b994f3..eff977f17cc 100644 --- a/docs/Inputs.md +++ b/docs/Inputs.md @@ -436,7 +436,7 @@ Refer to [Material UI Checkbox documentation](http://www.material-ui.com/#/compo ## `` -Ideal for editing dates, `` renders a standard browser [Date Picker](http://www.material-ui.com/#/components/date-picker). +Ideal for editing dates, `` renders a standard browser [Date Picker](https://material-ui.com/demos/pickers/#date-pickers), so the appearance depends on the browser (and falls back to a text input on safari). ```jsx import { DateInput } from 'react-admin'; @@ -446,6 +446,20 @@ import { DateInput } from 'react-admin'; ![DateInput](./img/date-input.gif) +**Tip**: For a material-ui styled `` component, check out [vascofg/react-admin-date-inputs](https://github.com/vascofg/react-admin-date-inputs). + +## `` + +An input for editing dates with time. `` renders a standard browser [Date and Time Picker](https://material-ui.com/demos/pickers/#date-amp-time-pickers), so the appearance depends on the browser (and falls back to a text input on safari). + +```jsx +import { DateTimeInput } from 'react-admin'; + + +``` + +**Tip**: For a material-ui styled `` component, check out [vascofg/react-admin-date-inputs](https://github.com/vascofg/react-admin-date-inputs). + ## `` When you want to display a record property in an `` form without letting users update it (such as for auto-incremented primary keys), use the ``: @@ -1328,6 +1342,17 @@ const SexInput = ({ source }) => ` props there. It's useful for instance if you need to set the [`format`](https://redux-form.com/7.4.2/docs/api/field.md/#-code-format-value-name-gt-formattedvalue-code-optional-) and [`parse`](https://redux-form.com/7.4.2/docs/api/field.md/#-code-parse-value-name-gt-parsedvalue-code-optional-) props of the field: + +```jsx +const parse = value => // ... +const format = value => // ... + +const MyDateInput = props => // ... + +export default addField(MyDateInput, { parse, format }); +``` + For more details on how to use redux-form's `` component, please refer to [the redux-form doc](http://redux-form.com/6.5.0/docs/api/Field.md/). Instead of HTML `input` elements or material-ui components, you can use react-admin input components, like `` for instance. React-admin components are already decorated by ``, and already include a label, so you don't need either `` or `` when using them: diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index a853b04e5df..da05ff78fe1 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -90,7 +90,8 @@ Adding Custom Headers
  • - Decorating a Data Provider + Decorating a Data + Provider
  • Writing Your Own Data Provider @@ -391,7 +392,8 @@ Writing your own field component
  • - Adding Label To Custom Fields + Adding Label To + Custom Fields
  • Conditional Field @@ -452,7 +454,8 @@ Customize Input Containers Styles
  • - User permissions + User + permissions
  • @@ -487,6 +490,11 @@ <DateInput> +
  • + + <DateTimeInput> + +
  • <DisabledInput> @@ -579,13 +587,16 @@ Adding a Logout Button
  • - Catching Authentication Errors On The API + Catching Authentication Errors On + The API
  • - Checking Credentials During Navigation + Checking Credentials During + Navigation
  • - Customizing The Login and Logout Components + Customizing The Login and Logout + Components
  • Restricting Access To A Custom Page @@ -601,13 +612,16 @@ Configuring the Auth Provider
  • - Restricting Access To Resources or Views + Restricting Access To Resources or + Views
  • - Restricting Access To Fields And Inputs + Restricting Access To Fields And + Inputs
  • - Restricting Access To Content In Custom Pages or Menus + Restricting Access To + Content In Custom Pages or Menus
  • @@ -668,7 +682,8 @@ Optimistic Rendering and Undo
  • - Altering the Form Values before Submitting + Altering the Form Values before + Submitting
  • Custom Sagas @@ -706,7 +721,8 @@ Translating Resource and Field names
  • - Mixing Interface and Domain Translations + Mixing Interface and Domain + Translations
  • Translating Your Own Components diff --git a/packages/ra-core/src/form/addField.js b/packages/ra-core/src/form/addField.js index 0f1dea26f06..a88cd82a1b8 100644 --- a/packages/ra-core/src/form/addField.js +++ b/packages/ra-core/src/form/addField.js @@ -1,9 +1,9 @@ import React from 'react'; import FormField from './FormField'; -export default BaseComponent => { +export default (BaseComponent, fieldProps = {}) => { const WithFormField = props => ( - + ); return WithFormField; }; diff --git a/packages/ra-ui-materialui/src/input/DateTimeInput.js b/packages/ra-ui-materialui/src/input/DateTimeInput.js new file mode 100644 index 00000000000..c79fc55280e --- /dev/null +++ b/packages/ra-ui-materialui/src/input/DateTimeInput.js @@ -0,0 +1,114 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import TextField from '@material-ui/core/TextField'; +import { addField, FieldTitle } from 'ra-core'; + +import sanitizeRestProps from './sanitizeRestProps'; + +const leftPad = (nb = 2) => value => ('0'.repeat(nb) + value).slice(-nb); +const leftPad4 = leftPad(4); +const leftPad2 = leftPad(2); + +/** + * @param {Date} v value to convert + * @returns {String} A standardized datetime (yyyy-MM-ddThh:mm), to be passed to an + */ +const convertDateToString = v => { + if (!(v instanceof Date) || isNaN(v)) return ''; + const yyyy = leftPad4(v.getFullYear()); + const MM = leftPad2(v.getMonth() + 1); + const dd = leftPad2(v.getDate()); + const hh = leftPad2(v.getHours()); + const mm = leftPad2(v.getMinutes()); + return `${yyyy}-${MM}-${dd}T${hh}:${mm}`; +}; + +// yyyy-MM-ddThh:mm +const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/; + +/** + * Converts a date from the Redux store, with timezone, to a date string + * without timezone for use in an . + * + * @param {Mixed} value date string or object + * @param {String} Date string, formatted as yyyy-MM-ddThh:mm + */ +const format = value => { + // null, undefined and empty string values should not go through convertDateToString + // otherwise, it returns undefined and will make the input an uncontrolled one. + if (value == null || value === '') { + return ''; + } + // valid dates should not be converted + if (dateTimeRegex.test(value)) { + return value; + } + + const finalValue = typeof value instanceof Date ? value : new Date(value); + return convertDateToString(finalValue); +}; + +/** + * Converts a datetime string without timezone to a date object + * with timezone, using the browser timezone. + * + * @param {String} value Date string, formatted as yyyy-MM-ddThh:mm + * @return {Date} + */ +const parse = value => new Date(value); + +/** + * Input component for entering a date and a time with timezone, using the browser locale + */ +export const DateTimeInput = ({ + className, + meta: { touched, error }, + input, + isRequired, + label, + options, + source, + resource, + ...rest +}) => ( + + } + InputLabelProps={{ + shrink: true, + }} + {...options} + {...sanitizeRestProps(rest)} + value={input.value} + /> +); + +DateTimeInput.propTypes = { + classes: PropTypes.object, + className: PropTypes.string, + input: PropTypes.object, + isRequired: PropTypes.bool, + label: PropTypes.string, + meta: PropTypes.object, + options: PropTypes.object, + resource: PropTypes.string, + source: PropTypes.string, +}; + +DateTimeInput.defaultProps = { + options: {}, +}; + +export default addField(DateTimeInput, { format, parse }); diff --git a/packages/ra-ui-materialui/src/input/index.js b/packages/ra-ui-materialui/src/input/index.js index b387eb4035e..867d7473bb0 100644 --- a/packages/ra-ui-materialui/src/input/index.js +++ b/packages/ra-ui-materialui/src/input/index.js @@ -4,6 +4,7 @@ export AutocompleteInput from './AutocompleteInput'; export BooleanInput from './BooleanInput'; export CheckboxGroupInput from './CheckboxGroupInput'; export DateInput from './DateInput'; +export DateTimeInput from './DateTimeInput'; export DisabledInput from './DisabledInput'; export FileInput from './FileInput'; export ImageInput from './ImageInput';