Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] Add <DateTimeInput> #2332

Merged
merged 2 commits into from
Sep 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion docs/Inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ Refer to [Material UI Checkbox documentation](http://www.material-ui.com/#/compo

## `<DateInput>`

Ideal for editing dates, `<DateInput>` renders a standard browser [Date Picker](http://www.material-ui.com/#/components/date-picker).
Ideal for editing dates, `<DateInput>` 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';
Expand All @@ -446,6 +446,20 @@ import { DateInput } from 'react-admin';

![DateInput](./img/date-input.gif)

**Tip**: For a material-ui styled `<DateInput>` component, check out [vascofg/react-admin-date-inputs](https://github.com/vascofg/react-admin-date-inputs).

## `<DateTimeInput>`

An input for editing dates with time. `<DateTimeInput>` 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';

<DateTimeInput source="published_at" />
```

**Tip**: For a material-ui styled `<DateTimeInput>` component, check out [vascofg/react-admin-date-inputs](https://github.com/vascofg/react-admin-date-inputs).

## `<DisabledInput>`

When you want to display a record property in an `<Edit>` form without letting users update it (such as for auto-incremented primary keys), use the `<DisabledInput>`:
Expand Down Expand Up @@ -1328,6 +1342,17 @@ const SexInput = ({ source }) => <Field name={source} component={renderSexInput}
export default SexInput;
```

**Tip**: `addField` takes a list of props as second argument, so you can set `<Field>` 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 `<Field>` 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 `<NumberInput>` for instance. React-admin components are already decorated by `<Field>`, and already include a label, so you don't need either `<Field>` or `<Labeled>` when using them:
Expand Down
38 changes: 27 additions & 11 deletions docs/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@
<a href="#adding-custom-headers">Adding Custom Headers</a>
</li>
<li class="chapter">
<a href="#decorating-your-data-provider-example-of-file-upload">Decorating a Data Provider</a>
<a href="#decorating-your-data-provider-example-of-file-upload">Decorating a Data
Provider</a>
</li>
<li class="chapter">
<a href="#writing-your-own-data-provider">Writing Your Own Data Provider</a>
Expand Down Expand Up @@ -391,7 +392,8 @@
<a href="#writing-your-own-field-component">Writing your own field component</a>
</li>
<li class="chapter">
<a href="#adding-label-to-custom-field-components-in-the-show-view">Adding Label To Custom Fields</a>
<a href="#adding-label-to-custom-field-components-in-the-show-view">Adding Label To
Custom Fields</a>
</li>
<li class="chapter">
<a href="#hiding-a-field-based-on-the-value-of-another">Conditional Field</a>
Expand Down Expand Up @@ -452,7 +454,8 @@
<a href="#customizing-input-container-styles">Customize Input Containers Styles</a>
</li>
<li class="chapter">
<a href="#displaying-fields-or-inputs-depending-on-the-user-permissions">User permissions</a>
<a href="#displaying-fields-or-inputs-depending-on-the-user-permissions">User
permissions</a>
</li>
</ul>
</li>
Expand Down Expand Up @@ -487,6 +490,11 @@
<code>&lt;DateInput&gt;</code>
</a>
</li>
<li class="chapter">
<a href="#datetimeinput">
<code>&lt;DateTimeInput&gt;</code>
</a>
</li>
<li class="chapter">
<a href="#disabledinput">
<code>&lt;DisabledInput&gt;</code>
Expand Down Expand Up @@ -579,13 +587,16 @@
<a href="#adding-a-logout-button">Adding a Logout Button</a>
</li>
<li class="chapter">
<a href="#catching-authentication-errors-on-the-api">Catching Authentication Errors On The API</a>
<a href="#catching-authentication-errors-on-the-api">Catching Authentication Errors On
The API</a>
</li>
<li class="chapter">
<a href="#checking-credentials-during-navigation">Checking Credentials During Navigation</a>
<a href="#checking-credentials-during-navigation">Checking Credentials During
Navigation</a>
</li>
<li class="chapter">
<a href="#customizing-the-login-and-logout-components">Customizing The Login and Logout Components</a>
<a href="#customizing-the-login-and-logout-components">Customizing The Login and Logout
Components</a>
</li>
<li class="chapter">
<a href="#restricting-access-to-a-custom-page">Restricting Access To A Custom Page</a>
Expand All @@ -601,13 +612,16 @@
<a href="#configuring-the-auth-provider">Configuring the Auth Provider</a>
</li>
<li class="chapter">
<a href="#restricting-access-to-resources-or-views">Restricting Access To Resources or Views</a>
<a href="#restricting-access-to-resources-or-views">Restricting Access To Resources or
Views</a>
</li>
<li class="chapter">
<a href="#restricting-access-to-fields-and-inputs">Restricting Access To Fields And Inputs</a>
<a href="#restricting-access-to-fields-and-inputs">Restricting Access To Fields And
Inputs</a>
</li>
<li class="chapter">
<a href="#restricting-access-to-content-in-custom-pages-or-menus">Restricting Access To Content In Custom Pages or Menus</a>
<a href="#restricting-access-to-content-in-custom-pages-or-menus">Restricting Access To
Content In Custom Pages or Menus</a>
</li>
</ul>
</li>
Expand Down Expand Up @@ -668,7 +682,8 @@
<a href="#optimistic-rendering-and-undo">Optimistic Rendering and Undo</a>
</li>
<li class="chapter">
<a href="#altering-the-form-values-before-submitting">Altering the Form Values before Submitting</a>
<a href="#altering-the-form-values-before-submitting">Altering the Form Values before
Submitting</a>
</li>
<li class="chapter">
<a href="#custom-sagas">Custom Sagas</a>
Expand Down Expand Up @@ -706,7 +721,8 @@
<a href="#translating-resource-and-field-names">Translating Resource and Field names</a>
</li>
<li class="chapter">
<a href="#mixing-interface-and-domain-translations">Mixing Interface and Domain Translations</a>
<a href="#mixing-interface-and-domain-translations">Mixing Interface and Domain
Translations</a>
</li>
<li class="chapter">
<a href="#translating-your-own-components">Translating Your Own Components</a>
Expand Down
4 changes: 2 additions & 2 deletions packages/ra-core/src/form/addField.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import FormField from './FormField';

export default BaseComponent => {
export default (BaseComponent, fieldProps = {}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be documented though

const WithFormField = props => (
<FormField component={BaseComponent} {...props} />
<FormField component={BaseComponent} {...fieldProps} {...props} />
);
return WithFormField;
};
114 changes: 114 additions & 0 deletions packages/ra-ui-materialui/src/input/DateTimeInput.js
Original file line number Diff line number Diff line change
@@ -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 <input type="datetime-local" />
*/
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 <input type="datetime-local" />.
*
* @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
}) => (
<TextField
{...input}
className={className}
type="datetime-local"
margin="normal"
error={!!(touched && error)}
helperText={touched && error}
label={
<FieldTitle
label={label}
source={source}
resource={resource}
isRequired={isRequired}
/>
}
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 });
1 change: 1 addition & 0 deletions packages/ra-ui-materialui/src/input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down