From c2cca93aecbd3f5d42d012cbf075af9583fbe02b Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Fri, 17 Nov 2017 17:06:01 +0100 Subject: [PATCH 01/16] Fixes based on aor-demo migration --- UPGRADE.md | 6 + docs/List.md | 38 ------ packages/react-admin/src/mui/Link.js | 2 +- .../react-admin/src/mui/input/DateInput.js | 10 +- .../src/mui/input/NullableBooleanInput.js | 114 ++++++++++++++---- .../mui/input/NullableBooleanInput.spec.js | 21 ++-- .../src/mui/layout/MenuItemLink.js | 8 +- 7 files changed, 117 insertions(+), 82 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 03f12f6d6ad..edb6a6c530e 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -106,6 +106,12 @@ module.exports = { Material-ui's implementation of the autocomplete input has radically changed. React-admin maintains backwards compatibility, except for the `filter` prop, which no longer makes sense in the new impementation. +## `` No Longer Accepts `options`, `headerOptions`, `bodyOptions`, and `rowOptions` props + +Material-ui's implementation of the `` component has reduced dramatically. Therefore, all the advanced features of the datagrid are no longer available from react-admin. + +If you need a fixed header, row hover, multi-row selection, or any other material-ui 0.x `
` feature, you'll need to implement your own `` alternative, e.g. using the library recommended by material-ui, [DevExtreme React Grid](https://devexpress.github.io/devextreme-reactive/react/grid/). + ## `` Stores a Date String Instead Of a Date Object The value of the `` used to be a `Date` object. It's now a `String`, i.e. a stringified date. If you used `format` and `parse` to convert a string to a `Date`, you can now remove these props: diff --git a/docs/List.md b/docs/List.md index ea260702e73..905e514456c 100644 --- a/docs/List.md +++ b/docs/List.md @@ -255,10 +255,6 @@ Here are all the props accepted by the component: * [`styles`](#custom-grid-style) * [`rowStyle`](#row-style-function) -* [`options`](#options) -* [`headerOptions`](#options) -* [`bodyOptions`](#options) -* [`rowOptions`](#options) It renders as many columns as it receives `` children. @@ -350,40 +346,6 @@ export const PostList = (props) => ( ); ``` -### `options`, `headerOptions`, `bodyOptions`, and `rowOptions` - -React-admin relies on [material-ui's `
` component](http://www.material-ui.com/#/components/table) for rendering the datagrid. The `options`, `headerOptions`, `bodyOptions`, and `rowOptions` props allow your to override the props of `
`, ``, ``, and ``. - -For instance, to get a fixed header on the table, override the `
` props with `options`: - -{% raw %} -```jsx -export const PostList = (props) => ( - - - ... - - -); -``` -{% endraw %} - -To enable striped rows and row hover, override the `` props with `bodyOptions`: - -{% raw %} -```jsx -export const PostList = (props) => ( - - - ... - - -); -``` -{% endraw %} - -For a list of all the possible props that you can override via these options, please refer to [the material-ui `
` component documentation](http://www.material-ui.com/#/components/table). - ## The `` component For mobile devices, a `` is often unusable - there is simply not enough space to display several columns. The convention in that case is to use a simple list, with only one column per row. The `` component serves that purpose, leveraging [material-ui's `` and `` components](http://www.material-ui.com/#/components/list). You can use it as `` or `` child: diff --git a/packages/react-admin/src/mui/Link.js b/packages/react-admin/src/mui/Link.js index 121ac8d4f77..ed1b54a9bf2 100644 --- a/packages/react-admin/src/mui/Link.js +++ b/packages/react-admin/src/mui/Link.js @@ -18,7 +18,7 @@ const Link = ({ to, children, className, classes }) => ( Link.propTypes = { className: PropTypes.string, classes: PropTypes.object, - to: PropTypes.string, + to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), }; export default withStyles(styles)(Link); diff --git a/packages/react-admin/src/mui/input/DateInput.js b/packages/react-admin/src/mui/input/DateInput.js index 7ce60c9ed52..3a5dc349d4a 100644 --- a/packages/react-admin/src/mui/input/DateInput.js +++ b/packages/react-admin/src/mui/input/DateInput.js @@ -27,7 +27,6 @@ export class DateInput extends Component { render() { const { - classes, input, isRequired, label, @@ -62,14 +61,13 @@ export class DateInput extends Component { InputLabelProps={{ shrink: true, }} - value={ + value={dateFormatter( input.value instanceof Date - ? dateFormatter(input.value) - : input.value - } + ? input.value + : new Date(input.value) + )} onChange={this.onChange} onBlur={this.onBlur} - classes={classes} style={elStyle} {...options} /> diff --git a/packages/react-admin/src/mui/input/NullableBooleanInput.js b/packages/react-admin/src/mui/input/NullableBooleanInput.js index 35452ff8684..ab6ade5c834 100644 --- a/packages/react-admin/src/mui/input/NullableBooleanInput.js +++ b/packages/react-admin/src/mui/input/NullableBooleanInput.js @@ -1,42 +1,104 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import SelectInput from './SelectInput'; +import TextField from 'material-ui/TextField'; +import { MenuItem } from 'material-ui/Menu'; +import { withStyles } from 'material-ui/styles'; import compose from 'recompose/compose'; +import FieldTitle from '../../util/FieldTitle'; import addField from '../form/addField'; import translate from '../../i18n/translate'; -export const NullableBooleanInput = ({ - input, - meta, - label, - source, - elStyle, - resource, - translate, -}) => ( - -); +const styles = theme => ({ + input: { width: theme.spacing.unit * 16 }, +}); + +export class NullableBooleanInput extends Component { + state = { + value: this.props.input.value, + }; + + componentWillReceiveProps(nextProps) { + if (nextProps.input.value !== this.props.input.value) { + this.setState({ value: nextProps.input.value }); + } + } + + handleChange = event => { + this.props.input.onChange( + this.getBooleanFromString(event.target.value) + ); + this.setState({ value: event.target.value }); + }; + + getBooleanFromString = value => { + if (value === 'true') return true; + if (value === 'false') return false; + return null; + }; + + getStringFromBoolean = value => { + if (value === true) return 'true'; + if (value === false) return 'false'; + return ''; + }; + + render() { + const { + classes, + elStyle, + isRequired, + label, + meta, + options, + resource, + source, + translate, + } = this.props; + const { touched, error } = meta; + return ( + + } + onChange={this.handleChange} + style={elStyle} + error={!!(touched && error)} + helperText={touched && error} + className={classes.input} + {...options} + > + + + {translate('ra.boolean.false')} + + {translate('ra.boolean.true')} + + ); + } +} NullableBooleanInput.propTypes = { + classes: PropTypes.object, elStyle: PropTypes.object, input: PropTypes.object, + isRequired: PropTypes.bool, label: PropTypes.string, meta: PropTypes.object, + options: PropTypes.object, resource: PropTypes.string, source: PropTypes.string, + translate: PropTypes.func.isRequired, }; -export default compose(addField, translate)(NullableBooleanInput); +const enhance = compose(addField, translate, withStyles(styles)); + +export default enhance(NullableBooleanInput); diff --git a/packages/react-admin/src/mui/input/NullableBooleanInput.spec.js b/packages/react-admin/src/mui/input/NullableBooleanInput.spec.js index be79712cf99..d96cd4404e4 100644 --- a/packages/react-admin/src/mui/input/NullableBooleanInput.spec.js +++ b/packages/react-admin/src/mui/input/NullableBooleanInput.spec.js @@ -1,4 +1,3 @@ -import assert from 'assert'; import { shallow } from 'enzyme'; import React from 'react'; @@ -15,11 +14,19 @@ describe('', () => { const wrapper = shallow( ); - const choices = wrapper.find('WithFormField').prop('choices'); - assert.deepEqual(choices, [ - { id: null, name: '' }, - { id: false, name: 'ra.boolean.false' }, - { id: true, name: 'ra.boolean.true' }, - ]); + const MenuItemElements = wrapper.find('withStyles(MenuItem)'); + expect(MenuItemElements.length).toEqual(3); + + const MenuItemElement1 = MenuItemElements.at(0); + expect(MenuItemElement1.prop('value')).toEqual(''); + expect(MenuItemElement1.childAt(0).text()).toEqual(''); + + const MenuItemElement2 = MenuItemElements.at(1); + expect(MenuItemElement2.prop('value')).toEqual('false'); + expect(MenuItemElement2.childAt(0).text()).toEqual('ra.boolean.false'); + + const MenuItemElement3 = MenuItemElements.at(2); + expect(MenuItemElement3.prop('value')).toEqual('true'); + expect(MenuItemElement3.childAt(0).text()).toEqual('ra.boolean.true'); }); }); diff --git a/packages/react-admin/src/mui/layout/MenuItemLink.js b/packages/react-admin/src/mui/layout/MenuItemLink.js index 88fb221a889..3590c987fc7 100644 --- a/packages/react-admin/src/mui/layout/MenuItemLink.js +++ b/packages/react-admin/src/mui/layout/MenuItemLink.js @@ -6,16 +6,16 @@ import { withRouter } from 'react-router'; const iconPaddingStyle = { paddingRight: '0.5em' }; -export class MenuItemLinkComponent extends Component { +export class MenuItemLink extends Component { static propTypes = { history: PropTypes.object.isRequired, - onClick: PropTypes.func.isRequired, + onClick: PropTypes.func, to: PropTypes.string.isRequired, }; handleMenuTap = () => { this.props.history.push(this.props.to); - this.props.onClick(); + this.props.onClick && this.props.onClick(); }; render() { @@ -38,4 +38,4 @@ export class MenuItemLinkComponent extends Component { } } -export default withRouter(MenuItemLinkComponent); +export default withRouter(MenuItemLink); From 9e96f98d68738c8a38832a3a0da3b5768fe286a3 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 16:08:40 +0700 Subject: [PATCH 02/16] Update action constants value to new naming scheme --- .../src/actions/accumulateActions.js | 2 +- .../react-admin/src/actions/authActions.js | 12 ++-- .../react-admin/src/actions/dataActions.js | 64 +++++++++---------- .../react-admin/src/actions/fetchActions.js | 8 +-- .../react-admin/src/actions/filterActions.js | 6 +- .../react-admin/src/actions/formActions.js | 2 +- .../react-admin/src/actions/listActions.js | 2 +- .../react-admin/src/actions/localeActions.js | 2 +- .../src/actions/notificationActions.js | 4 +- .../src/actions/resourcesActions.js | 2 +- packages/react-admin/src/actions/uiActions.js | 6 +- 11 files changed, 55 insertions(+), 55 deletions(-) diff --git a/packages/react-admin/src/actions/accumulateActions.js b/packages/react-admin/src/actions/accumulateActions.js index da78da46be4..e2a56e6ea2a 100644 --- a/packages/react-admin/src/actions/accumulateActions.js +++ b/packages/react-admin/src/actions/accumulateActions.js @@ -1,6 +1,6 @@ import { crudGetMany } from './dataActions'; -export const CRUD_GET_MANY_ACCUMULATE = 'AOR/CRUD_GET_MANY_ACCUMULATE'; +export const CRUD_GET_MANY_ACCUMULATE = 'RA/CRUD_GET_MANY_ACCUMULATE'; export const crudGetManyAccumulate = (resource, ids) => ({ type: CRUD_GET_MANY_ACCUMULATE, diff --git a/packages/react-admin/src/actions/authActions.js b/packages/react-admin/src/actions/authActions.js index 6eb665dfa43..fde8919c84e 100644 --- a/packages/react-admin/src/actions/authActions.js +++ b/packages/react-admin/src/actions/authActions.js @@ -1,7 +1,7 @@ -export const USER_LOGIN = 'AOR/USER_LOGIN'; -export const USER_LOGIN_LOADING = 'AOR/USER_LOGIN_LOADING'; -export const USER_LOGIN_FAILURE = 'AOR/USER_LOGIN_FAILURE'; -export const USER_LOGIN_SUCCESS = 'AOR/USER_LOGIN_SUCCESS'; +export const USER_LOGIN = 'RA/USER_LOGIN'; +export const USER_LOGIN_LOADING = 'RA/USER_LOGIN_LOADING'; +export const USER_LOGIN_FAILURE = 'RA/USER_LOGIN_FAILURE'; +export const USER_LOGIN_SUCCESS = 'RA/USER_LOGIN_SUCCESS'; export const userLogin = (payload, pathName) => ({ type: USER_LOGIN, @@ -9,7 +9,7 @@ export const userLogin = (payload, pathName) => ({ meta: { auth: true, pathName }, }); -export const USER_CHECK = 'AOR/USER_CHECK'; +export const USER_CHECK = 'RA/USER_CHECK'; export const userCheck = (payload, pathName) => ({ type: USER_CHECK, @@ -17,7 +17,7 @@ export const userCheck = (payload, pathName) => ({ meta: { auth: true, pathName }, }); -export const USER_LOGOUT = 'AOR/USER_LOGOUT'; +export const USER_LOGOUT = 'RA/USER_LOGOUT'; export const userLogout = () => ({ type: USER_LOGOUT, diff --git a/packages/react-admin/src/actions/dataActions.js b/packages/react-admin/src/actions/dataActions.js index add31cdc2f5..d0bfeea7da7 100644 --- a/packages/react-admin/src/actions/dataActions.js +++ b/packages/react-admin/src/actions/dataActions.js @@ -8,10 +8,10 @@ import { GET_MANY_REFERENCE, } from '../dataFetchActions'; -export const CRUD_GET_LIST = 'AOR/CRUD_GET_LIST'; -export const CRUD_GET_LIST_LOADING = 'AOR/CRUD_GET_LIST_LOADING'; -export const CRUD_GET_LIST_FAILURE = 'AOR/CRUD_GET_LIST_FAILURE'; -export const CRUD_GET_LIST_SUCCESS = 'AOR/CRUD_GET_LIST_SUCCESS'; +export const CRUD_GET_LIST = 'RA/CRUD_GET_LIST'; +export const CRUD_GET_LIST_LOADING = 'RA/CRUD_GET_LIST_LOADING'; +export const CRUD_GET_LIST_FAILURE = 'RA/CRUD_GET_LIST_FAILURE'; +export const CRUD_GET_LIST_SUCCESS = 'RA/CRUD_GET_LIST_SUCCESS'; export const crudGetList = ( resource, @@ -25,10 +25,10 @@ export const crudGetList = ( meta: { resource, fetch: GET_LIST, cancelPrevious }, }); -export const CRUD_GET_ONE = 'AOR/CRUD_GET_ONE'; -export const CRUD_GET_ONE_LOADING = 'AOR/CRUD_GET_ONE_LOADING'; -export const CRUD_GET_ONE_FAILURE = 'AOR/CRUD_GET_ONE_FAILURE'; -export const CRUD_GET_ONE_SUCCESS = 'AOR/CRUD_GET_ONE_SUCCESS'; +export const CRUD_GET_ONE = 'RA/CRUD_GET_ONE'; +export const CRUD_GET_ONE_LOADING = 'RA/CRUD_GET_ONE_LOADING'; +export const CRUD_GET_ONE_FAILURE = 'RA/CRUD_GET_ONE_FAILURE'; +export const CRUD_GET_ONE_SUCCESS = 'RA/CRUD_GET_ONE_SUCCESS'; export const crudGetOne = (resource, id, basePath, cancelPrevious = true) => ({ type: CRUD_GET_ONE, @@ -36,10 +36,10 @@ export const crudGetOne = (resource, id, basePath, cancelPrevious = true) => ({ meta: { resource, fetch: GET_ONE, cancelPrevious }, }); -export const CRUD_CREATE = 'AOR/CRUD_CREATE'; -export const CRUD_CREATE_LOADING = 'AOR/CRUD_CREATE_LOADING'; -export const CRUD_CREATE_FAILURE = 'AOR/CRUD_CREATE_FAILURE'; -export const CRUD_CREATE_SUCCESS = 'AOR/CRUD_CREATE_SUCCESS'; +export const CRUD_CREATE = 'RA/CRUD_CREATE'; +export const CRUD_CREATE_LOADING = 'RA/CRUD_CREATE_LOADING'; +export const CRUD_CREATE_FAILURE = 'RA/CRUD_CREATE_FAILURE'; +export const CRUD_CREATE_SUCCESS = 'RA/CRUD_CREATE_SUCCESS'; export const crudCreate = (resource, data, basePath, redirectTo = 'edit') => ({ type: CRUD_CREATE, @@ -47,10 +47,10 @@ export const crudCreate = (resource, data, basePath, redirectTo = 'edit') => ({ meta: { resource, fetch: CREATE, cancelPrevious: false }, }); -export const CRUD_UPDATE = 'AOR/CRUD_UPDATE'; -export const CRUD_UPDATE_LOADING = 'AOR/CRUD_UPDATE_LOADING'; -export const CRUD_UPDATE_FAILURE = 'AOR/CRUD_UPDATE_FAILURE'; -export const CRUD_UPDATE_SUCCESS = 'AOR/CRUD_UPDATE_SUCCESS'; +export const CRUD_UPDATE = 'RA/CRUD_UPDATE'; +export const CRUD_UPDATE_LOADING = 'RA/CRUD_UPDATE_LOADING'; +export const CRUD_UPDATE_FAILURE = 'RA/CRUD_UPDATE_FAILURE'; +export const CRUD_UPDATE_SUCCESS = 'RA/CRUD_UPDATE_SUCCESS'; export const crudUpdate = ( resource, @@ -65,10 +65,10 @@ export const crudUpdate = ( meta: { resource, fetch: UPDATE, cancelPrevious: false }, }); -export const CRUD_DELETE = 'AOR/CRUD_DELETE'; -export const CRUD_DELETE_LOADING = 'AOR/CRUD_DELETE_LOADING'; -export const CRUD_DELETE_FAILURE = 'AOR/CRUD_DELETE_FAILURE'; -export const CRUD_DELETE_SUCCESS = 'AOR/CRUD_DELETE_SUCCESS'; +export const CRUD_DELETE = 'RA/CRUD_DELETE'; +export const CRUD_DELETE_LOADING = 'RA/CRUD_DELETE_LOADING'; +export const CRUD_DELETE_FAILURE = 'RA/CRUD_DELETE_FAILURE'; +export const CRUD_DELETE_SUCCESS = 'RA/CRUD_DELETE_SUCCESS'; export const crudDelete = ( resource, @@ -82,10 +82,10 @@ export const crudDelete = ( meta: { resource, fetch: DELETE, cancelPrevious: false }, }); -export const CRUD_GET_MANY = 'AOR/CRUD_GET_MANY'; -export const CRUD_GET_MANY_LOADING = 'AOR/CRUD_GET_MANY_LOADING'; -export const CRUD_GET_MANY_FAILURE = 'AOR/CRUD_GET_MANY_FAILURE'; -export const CRUD_GET_MANY_SUCCESS = 'AOR/CRUD_GET_MANY_SUCCESS'; +export const CRUD_GET_MANY = 'RA/CRUD_GET_MANY'; +export const CRUD_GET_MANY_LOADING = 'RA/CRUD_GET_MANY_LOADING'; +export const CRUD_GET_MANY_FAILURE = 'RA/CRUD_GET_MANY_FAILURE'; +export const CRUD_GET_MANY_SUCCESS = 'RA/CRUD_GET_MANY_SUCCESS'; // Reference related actions @@ -95,10 +95,10 @@ export const crudGetMany = (resource, ids) => ({ meta: { resource, fetch: GET_MANY, cancelPrevious: false }, }); -export const CRUD_GET_MATCHING = 'AOR/CRUD_GET_MATCHING'; -export const CRUD_GET_MATCHING_LOADING = 'AOR/CRUD_GET_MATCHING_LOADING'; -export const CRUD_GET_MATCHING_FAILURE = 'AOR/CRUD_GET_MATCHING_FAILURE'; -export const CRUD_GET_MATCHING_SUCCESS = 'AOR/CRUD_GET_MATCHING_SUCCESS'; +export const CRUD_GET_MATCHING = 'RA/CRUD_GET_MATCHING'; +export const CRUD_GET_MATCHING_LOADING = 'RA/CRUD_GET_MATCHING_LOADING'; +export const CRUD_GET_MATCHING_FAILURE = 'RA/CRUD_GET_MATCHING_FAILURE'; +export const CRUD_GET_MATCHING_SUCCESS = 'RA/CRUD_GET_MATCHING_SUCCESS'; export const crudGetMatching = ( reference, @@ -117,13 +117,13 @@ export const crudGetMatching = ( }, }); -export const CRUD_GET_MANY_REFERENCE = 'AOR/CRUD_GET_MANY_REFERENCE'; +export const CRUD_GET_MANY_REFERENCE = 'RA/CRUD_GET_MANY_REFERENCE'; export const CRUD_GET_MANY_REFERENCE_LOADING = - 'AOR/CRUD_GET_MANY_REFERENCE_LOADING'; + 'RA/CRUD_GET_MANY_REFERENCE_LOADING'; export const CRUD_GET_MANY_REFERENCE_FAILURE = - 'AOR/CRUD_GET_MANY_REFERENCE_FAILURE'; + 'RA/CRUD_GET_MANY_REFERENCE_FAILURE'; export const CRUD_GET_MANY_REFERENCE_SUCCESS = - 'AOR/CRUD_GET_MANY_REFERENCE_SUCCESS'; + 'RA/CRUD_GET_MANY_REFERENCE_SUCCESS'; export const crudGetManyReference = ( reference, diff --git a/packages/react-admin/src/actions/fetchActions.js b/packages/react-admin/src/actions/fetchActions.js index 45a18e16f51..d203a25b033 100644 --- a/packages/react-admin/src/actions/fetchActions.js +++ b/packages/react-admin/src/actions/fetchActions.js @@ -1,4 +1,4 @@ -export const FETCH_START = 'AOR/FETCH_START'; -export const FETCH_END = 'AOR/FETCH_END'; -export const FETCH_ERROR = 'AOR/FETCH_ERROR'; -export const FETCH_CANCEL = 'AOR/FETCH_CANCEL'; +export const FETCH_START = 'RA/FETCH_START'; +export const FETCH_END = 'RA/FETCH_END'; +export const FETCH_ERROR = 'RA/FETCH_ERROR'; +export const FETCH_CANCEL = 'RA/FETCH_CANCEL'; diff --git a/packages/react-admin/src/actions/filterActions.js b/packages/react-admin/src/actions/filterActions.js index 8784ddcdd95..80e51a25ae7 100644 --- a/packages/react-admin/src/actions/filterActions.js +++ b/packages/react-admin/src/actions/filterActions.js @@ -1,6 +1,6 @@ -export const CRUD_SHOW_FILTER = 'AOR/CRUD_SHOW_FILTER'; -export const CRUD_HIDE_FILTER = 'AOR/CRUD_HIDE_FILTER'; -export const CRUD_SET_FILTER = 'AOR/CRUD_SET_FILTER'; +export const CRUD_SHOW_FILTER = 'RA/CRUD_SHOW_FILTER'; +export const CRUD_HIDE_FILTER = 'RA/CRUD_HIDE_FILTER'; +export const CRUD_SET_FILTER = 'RA/CRUD_SET_FILTER'; export const showFilter = (resource, field) => ({ type: CRUD_SHOW_FILTER, diff --git a/packages/react-admin/src/actions/formActions.js b/packages/react-admin/src/actions/formActions.js index 5a79af0a0ba..cc1498dec9b 100644 --- a/packages/react-admin/src/actions/formActions.js +++ b/packages/react-admin/src/actions/formActions.js @@ -1,4 +1,4 @@ -export const INITIALIZE_FORM = 'AOR/INITIALIZE_FORM'; +export const INITIALIZE_FORM = 'RA/INITIALIZE_FORM'; export const initializeForm = initialValues => ({ type: INITIALIZE_FORM, diff --git a/packages/react-admin/src/actions/listActions.js b/packages/react-admin/src/actions/listActions.js index ccb3e86f766..94704841abc 100644 --- a/packages/react-admin/src/actions/listActions.js +++ b/packages/react-admin/src/actions/listActions.js @@ -1,4 +1,4 @@ -export const CRUD_CHANGE_LIST_PARAMS = 'AOR/CRUD_CHANGE_LIST_PARAMS'; +export const CRUD_CHANGE_LIST_PARAMS = 'RA/CRUD_CHANGE_LIST_PARAMS'; export const changeListParams = (resource, params) => ({ type: CRUD_CHANGE_LIST_PARAMS, diff --git a/packages/react-admin/src/actions/localeActions.js b/packages/react-admin/src/actions/localeActions.js index 0eeaf82d214..fe8b404336d 100644 --- a/packages/react-admin/src/actions/localeActions.js +++ b/packages/react-admin/src/actions/localeActions.js @@ -1,4 +1,4 @@ -export const CHANGE_LOCALE = 'AOR/CHANGE_LOCALE'; +export const CHANGE_LOCALE = 'RA/CHANGE_LOCALE'; export const changeLocale = locale => ({ type: CHANGE_LOCALE, diff --git a/packages/react-admin/src/actions/notificationActions.js b/packages/react-admin/src/actions/notificationActions.js index 3160e77478b..5f8643c5793 100644 --- a/packages/react-admin/src/actions/notificationActions.js +++ b/packages/react-admin/src/actions/notificationActions.js @@ -1,11 +1,11 @@ -export const SHOW_NOTIFICATION = 'AOR/SHOW_NOTIFICATION'; +export const SHOW_NOTIFICATION = 'RA/SHOW_NOTIFICATION'; export const showNotification = (text, type = 'info', autoHideDuration) => ({ type: SHOW_NOTIFICATION, payload: { text, type, autoHideDuration }, }); -export const HIDE_NOTIFICATION = 'AOR/HIDE_NOTIFICATION'; +export const HIDE_NOTIFICATION = 'RA/HIDE_NOTIFICATION'; export const hideNotification = () => ({ type: HIDE_NOTIFICATION, diff --git a/packages/react-admin/src/actions/resourcesActions.js b/packages/react-admin/src/actions/resourcesActions.js index 49b32966890..67ed127a8e4 100644 --- a/packages/react-admin/src/actions/resourcesActions.js +++ b/packages/react-admin/src/actions/resourcesActions.js @@ -1,4 +1,4 @@ -export const DECLARE_RESOURCES = 'AOR/DECLARE_RESOURCES'; +export const DECLARE_RESOURCES = 'RA/DECLARE_RESOURCES'; export const declareResources = resources => ({ type: DECLARE_RESOURCES, diff --git a/packages/react-admin/src/actions/uiActions.js b/packages/react-admin/src/actions/uiActions.js index 322e6c07fcf..f4cb0b32285 100644 --- a/packages/react-admin/src/actions/uiActions.js +++ b/packages/react-admin/src/actions/uiActions.js @@ -1,17 +1,17 @@ -export const TOGGLE_SIDEBAR = 'AOR/TOGGLE_SIDEBAR'; +export const TOGGLE_SIDEBAR = 'RA/TOGGLE_SIDEBAR'; export const toggleSidebar = () => ({ type: TOGGLE_SIDEBAR, }); -export const SET_SIDEBAR_VISIBILITY = 'AOR/SET_SIDEBAR_VISIBILITY'; +export const SET_SIDEBAR_VISIBILITY = 'RA/SET_SIDEBAR_VISIBILITY'; export const setSidebarVisibility = isOpen => ({ type: SET_SIDEBAR_VISIBILITY, payload: isOpen, }); -export const REFRESH_VIEW = 'AOR/REFRESH_VIEW'; +export const REFRESH_VIEW = 'RA/REFRESH_VIEW'; export const refreshView = () => ({ type: REFRESH_VIEW, From dc6c52b404f5475310c8ba43a1862783033915d7 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 16:56:20 +0700 Subject: [PATCH 03/16] Improve auth components jsDoc --- packages/react-admin/src/auth/Permission.js | 36 +++++++++++++++++ packages/react-admin/src/auth/Restricted.js | 6 +-- .../react-admin/src/auth/Restricted.spec.js | 7 ++-- .../react-admin/src/auth/WithPermission.js | 39 ++++++++++++++++++- .../src/auth/resolvePermissions.spec.js | 15 ++++--- 5 files changed, 85 insertions(+), 18 deletions(-) diff --git a/packages/react-admin/src/auth/Permission.js b/packages/react-admin/src/auth/Permission.js index 45abe231e84..fdf08a04000 100644 --- a/packages/react-admin/src/auth/Permission.js +++ b/packages/react-admin/src/auth/Permission.js @@ -1,6 +1,42 @@ import React from 'react'; import PropTypes from 'prop-types'; +/** + * Render children only when the permissions match. + * + * To be used together with . + * + * The `Permission` component requires either : + * - a `value` prop with the permissions to check (could be a role, an array of roles, etc), or + * - a `resolve` function. + * + * An additional `exact` prop may be specified depending on your requirements. + * It determines whether the user must have **all** the required permissions or only some. + * If `false`, the default, we'll only check if the user has at least one of the required permissions. + * + * You may bypass the default logic by specifying a function as the `resolve` prop. + * This function may return `true` or `false` directly, or a promise + * resolving to either `true` or `false`. It will be called with an object + * having the following properties: + * - `permissions`: the result of the `authClient` call. + * - `value`: the value of the `value` prop if specified + * - `exact`: the value of the `exact` prop if specified + * + * @example + * + * + * + * + * + * + * + * + * + * + * + * + * + */ const Permission = () => ( <Permission> elements are for configuration only and should not be diff --git a/packages/react-admin/src/auth/Restricted.js b/packages/react-admin/src/auth/Restricted.js index a8d255b66ae..4e4fe10e1fc 100644 --- a/packages/react-admin/src/auth/Restricted.js +++ b/packages/react-admin/src/auth/Restricted.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { userCheck as userCheckAction } from '../actions/authActions'; +import { userCheck } from '../actions/authActions'; /** * Restrict access to children @@ -52,6 +52,4 @@ export class Restricted extends Component { } } -export default connect(null, { - userCheck: userCheckAction, -})(Restricted); +export default connect(null, { userCheck })(Restricted); diff --git a/packages/react-admin/src/auth/Restricted.spec.js b/packages/react-admin/src/auth/Restricted.spec.js index cd279302e69..f9573a4a475 100644 --- a/packages/react-admin/src/auth/Restricted.spec.js +++ b/packages/react-admin/src/auth/Restricted.spec.js @@ -1,5 +1,4 @@ import React from 'react'; -import assert from 'assert'; import { shallow, render } from 'enzyme'; import { html } from 'cheerio'; @@ -14,7 +13,7 @@ describe('', () => { ); - assert.equal(userCheck.mock.calls.length, 1); + expect(userCheck.mock.calls.length).toEqual(1); }); it('should call userCheck on update', () => { const userCheck = jest.fn(); @@ -24,7 +23,7 @@ describe('', () => { ); wrapper.setProps({ location: { pathname: 'foo' }, userCheck }); - assert.equal(userCheck.mock.calls.length, 2); + expect(userCheck.mock.calls.length).toEqual(2); }); it('should render its child by default', () => { const userCheck = jest.fn(); @@ -33,6 +32,6 @@ describe('', () => { ); - assert.equal(html(wrapper), '
Foo
'); + expect(html(wrapper)).toEqual('
Foo
'); }); }); diff --git a/packages/react-admin/src/auth/WithPermission.js b/packages/react-admin/src/auth/WithPermission.js index 30f22d4167b..c466349bde3 100644 --- a/packages/react-admin/src/auth/WithPermission.js +++ b/packages/react-admin/src/auth/WithPermission.js @@ -5,7 +5,42 @@ import getContext from 'recompose/getContext'; import { AUTH_GET_PERMISSIONS } from './types'; import { resolvePermission } from './resolvePermissions'; -export class WithPermissionComponent extends Component { +/** + * Render children only if the user has the required permissions + * + * Requires either: + * - a `value` prop with the permissions to check (could be a role, an array of roles, etc), or + * - a `resolve` function. + * + * An additional `exact` prop may be specified depending on your requirements. + * It determines whether the user must have **all** the required permissions or only some. + * If `false`, the default, we'll only check if the user has at least one of the required permissions. + * + * You may bypass the default logic by specifying a function as the `resolve` prop. + * This function may return `true` or `false` directly or a promise resolving to either `true` or `false`. + * It will be called with an object having the following properties: + * - `permissions`: the result of the `authClient` call. + * - `value`: the value of the `value` prop if specified + * - `exact`: the value of the `exact` prop if specified + * + * An optional `loading` prop may be specified on the `WithPermission` component + * to pass a component to display while checking for permissions. It defaults to `null`. + * + * Tip: Do not use the `WithPermission` component inside the others react-admin components. + * It is only meant to be used in custom pages or components. + * + * @example + * const Menu = ({ onMenuTap }) => ( + *
+ * + * + * + * + * + *
+ * ); + */ +export class WithPermission extends Component { static propTypes = { authClient: PropTypes.func, children: PropTypes.node.isRequired, @@ -103,4 +138,4 @@ export class WithPermissionComponent extends Component { export default getContext({ authClient: PropTypes.func, -})(WithPermissionComponent); +})(WithPermission); diff --git a/packages/react-admin/src/auth/resolvePermissions.spec.js b/packages/react-admin/src/auth/resolvePermissions.spec.js index 9322637fe3b..0d9275daafd 100644 --- a/packages/react-admin/src/auth/resolvePermissions.spec.js +++ b/packages/react-admin/src/auth/resolvePermissions.spec.js @@ -1,4 +1,3 @@ -import assert from 'assert'; import resolvePermissions from './resolvePermissions'; describe('resolvePermissions', () => { @@ -36,7 +35,7 @@ describe('resolvePermissions', () => { permissions: 'admin', }); - assert.equal(match.view, 'SingleValue'); + expect(match.view).toEqual('SingleValue'); }); it('returns a match with mapping having an array of permissions and available permissions is a single value', async () => { @@ -45,7 +44,7 @@ describe('resolvePermissions', () => { permissions: 'array_value', }); - assert.equal(match.view, 'ArrayValue'); + expect(match.view).toEqual('ArrayValue'); }); it('returns a match with mapping having an array of permissions, available permissions is an array and exact is falsy', async () => { @@ -54,7 +53,7 @@ describe('resolvePermissions', () => { permissions: ['array_value', 'foo'], }); - assert.equal(match.view, 'ArrayValue'); + expect(match.view).toEqual('ArrayValue'); }); it('returns a match with mapping having an array of permissions, available permissions is a single value and exact match requested', async () => { @@ -63,7 +62,7 @@ describe('resolvePermissions', () => { permissions: ['array_value_exact_1', 'array_value_exact_2'], }); - assert.equal(match.view, 'ArrayValueExactMatch'); + expect(match.view).toEqual('ArrayValueExactMatch'); }); it('returns a match with resolve being a function', async () => { @@ -73,8 +72,8 @@ describe('resolvePermissions', () => { permissions: 'function', }); - assert.equal(match.view, 'FunctionValue'); - assert.deepEqual(checker.mock.calls[0][0], { + expect(match.view).toEqual('FunctionValue'); + expect(checker.mock.calls[0][0]).toEqual({ permissions: 'function', resource: 'products', record: { category: 'announcements' }, @@ -89,6 +88,6 @@ describe('resolvePermissions', () => { permissions: 'an_unknown_permission', }); - assert.equal(match, undefined); + expect(match).toBeUndefined(); }); }); From 12cea8116125a8185ca627d3fae0a5a259734b5f Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 17:11:04 +0700 Subject: [PATCH 04/16] Improve jsDoc for Translation components --- .../react-admin/src/auth/SwitchPermissions.js | 25 ++++++++++++++ .../src/i18n/TranslationProvider.js | 34 +++++++++++++------ .../react-admin/src/i18n/TranslationUtils.js | 22 ++++++++++++ .../src/i18n/TranslationUtils.spec.js | 6 ++-- packages/react-admin/src/i18n/translate.js | 18 ++++++++++ .../react-admin/src/i18n/translate.spec.js | 3 +- 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/packages/react-admin/src/auth/SwitchPermissions.js b/packages/react-admin/src/auth/SwitchPermissions.js index 2860290a39a..27f5c231cae 100644 --- a/packages/react-admin/src/auth/SwitchPermissions.js +++ b/packages/react-admin/src/auth/SwitchPermissions.js @@ -5,6 +5,31 @@ import getContext from 'recompose/getContext'; import { AUTH_GET_PERMISSIONS } from './types'; import resolvePermissions from './resolvePermissions'; +/** + * Render different components depending on permissions. + * + * Each child of the component + * must be a component. + * + * If multiple `Permission` match, only the first one will be displayed. + * + * @see Permission + * + * @example + * + * + * + * + * + * + * + * + * + * + * + * + * + */ export class SwitchPermissions extends Component { static propTypes = { authClient: PropTypes.func, diff --git a/packages/react-admin/src/i18n/TranslationProvider.js b/packages/react-admin/src/i18n/TranslationProvider.js index c3a2a2e8c6c..b6a190e3982 100644 --- a/packages/react-admin/src/i18n/TranslationProvider.js +++ b/packages/react-admin/src/i18n/TranslationProvider.js @@ -6,6 +6,30 @@ import { compose, withContext } from 'recompose'; import defaultMessages from 'ra-language-english'; +/** + * Creates a translation context, available to its children + * + * Must be called withing a Redux app. + * + * @example + * const MyApp = () => ( + * + * + * + * + * + * ); + */ +const TranslationProvider = ({ children }) => Children.only(children); + +TranslationProvider.propTypes = { + locale: PropTypes.string.isRequired, + messages: PropTypes.object, + children: PropTypes.element, +}; + +const mapStateToProps = state => ({ locale: state.locale }); + const withI18nContext = withContext( { translate: PropTypes.func.isRequired, @@ -25,16 +49,6 @@ const withI18nContext = withContext( } ); -const TranslationProvider = ({ children }) => Children.only(children); - -TranslationProvider.propTypes = { - locale: PropTypes.string.isRequired, - messages: PropTypes.object, - children: PropTypes.element, -}; - -const mapStateToProps = state => ({ locale: state.locale }); - export default compose(connect(mapStateToProps), withI18nContext)( TranslationProvider ); diff --git a/packages/react-admin/src/i18n/TranslationUtils.js b/packages/react-admin/src/i18n/TranslationUtils.js index c5bd940f976..39dff885540 100644 --- a/packages/react-admin/src/i18n/TranslationUtils.js +++ b/packages/react-admin/src/i18n/TranslationUtils.js @@ -1,5 +1,27 @@ import { DEFAULT_LOCALE } from './index'; +/** + * Resolve the browser locale according to the value of the global window.navigator + * + * Use it to determine the locale at runtime. + * + * @example + * import React from 'react'; + * import { Admin, Resource, resolveBrowserLocale } from 'react-admin'; + * import englishMessages from 'ra-language-english'; + * import frenchMessages from 'ra-language-french'; + * const messages = { + * fr: frenchMessages, + * en: englishMessages, + * }; + * const App = () => ( + * + * ... + * + * ); + * + * @param {String} defaultLocale Defaults to 'en' + */ export const resolveBrowserLocale = (defaultLocale = DEFAULT_LOCALE) => { // from http://blog.ksol.fr/user-locale-detection-browser-javascript/ // Rely on the window.navigator object to determine user locale diff --git a/packages/react-admin/src/i18n/TranslationUtils.spec.js b/packages/react-admin/src/i18n/TranslationUtils.spec.js index d4ff0c06894..4b1690f452d 100644 --- a/packages/react-admin/src/i18n/TranslationUtils.spec.js +++ b/packages/react-admin/src/i18n/TranslationUtils.spec.js @@ -1,5 +1,3 @@ -import assert from 'assert'; - import { resolveBrowserLocale, DEFAULT_LOCALE } from './index'; describe('TranslationUtils', () => { @@ -10,12 +8,12 @@ describe('TranslationUtils', () => { it("should return default locale if there's no available locale in browser", () => { window.navigator = {}; - assert(resolveBrowserLocale(), DEFAULT_LOCALE); + expect(resolveBrowserLocale()).toEqual(DEFAULT_LOCALE); }); it('should splice browser language to take first two locale letters', () => { window.navigator = { language: 'en-US' }; - assert(resolveBrowserLocale(), 'en'); + expect(resolveBrowserLocale()).toEqual('en'); }); }); }); diff --git a/packages/react-admin/src/i18n/translate.js b/packages/react-admin/src/i18n/translate.js index d64b39874e7..10bfa1ffe9b 100644 --- a/packages/react-admin/src/i18n/translate.js +++ b/packages/react-admin/src/i18n/translate.js @@ -1,6 +1,24 @@ import PropTypes from 'prop-types'; import { getContext } from 'recompose'; +/** + * Higher-Order Component for getting access to the `translate` function in props. + * + * Requires that the app is decorated by the to inject + * the translation dictionaries and function in the context. + * + * @example + * import React from 'react'; + * import { translate } from 'react-admin'; + * + * const MyHelloButton = ({ translate }) => ( + * + * ); + * + * export default translate(MyHelloButton); + * + * @param {*} BaseComponent The component to decorate + */ const translate = BaseComponent => { const TranslatedComponent = getContext({ translate: PropTypes.func.isRequired, diff --git a/packages/react-admin/src/i18n/translate.spec.js b/packages/react-admin/src/i18n/translate.spec.js index 8a6454d2fc4..c0e12cc7899 100644 --- a/packages/react-admin/src/i18n/translate.spec.js +++ b/packages/react-admin/src/i18n/translate.spec.js @@ -1,4 +1,3 @@ -import assert from 'assert'; import React from 'react'; import translate from './translate'; @@ -9,6 +8,6 @@ describe('translate HOC', () => { Component.defaultProps = { foo: 'bar' }; const TranslatedComponent = translate(Component); - assert.deepEqual(TranslatedComponent.defaultProps, { foo: 'bar' }); + expect(TranslatedComponent.defaultProps).toEqual({ foo: 'bar' }); }); }); From 5264f997d9ca0cb741bd0724c65ff30202b71e9d Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 17:36:14 +0700 Subject: [PATCH 05/16] Remove non-breakable space in icon buttons --- .../src/mui/button/CreateButton.js | 6 +++-- .../src/mui/button/DeleteButton.js | 6 +++-- .../react-admin/src/mui/button/EditButton.js | 6 +++-- .../react-admin/src/mui/button/ListButton.js | 6 +++-- .../src/mui/button/RefreshButton.js | 15 ++++++++--- .../react-admin/src/mui/button/SaveButton.js | 26 ++++++++++++++++--- .../react-admin/src/mui/button/ShowButton.js | 6 +++-- 7 files changed, 54 insertions(+), 17 deletions(-) diff --git a/packages/react-admin/src/mui/button/CreateButton.js b/packages/react-admin/src/mui/button/CreateButton.js index 3871ccd47ed..506366f03db 100644 --- a/packages/react-admin/src/mui/button/CreateButton.js +++ b/packages/react-admin/src/mui/button/CreateButton.js @@ -26,6 +26,9 @@ const styles = { display: 'inline-flex', alignItems: 'center', }, + iconPaddingStyle: { + paddingRight: '0.5em', + }, }; const CreateButton = ({ @@ -48,8 +51,7 @@ const CreateButton = ({ medium={ diff --git a/packages/react-admin/src/mui/button/DeleteButton.js b/packages/react-admin/src/mui/button/DeleteButton.js index 8c7d87c2e16..2f9f414383e 100644 --- a/packages/react-admin/src/mui/button/DeleteButton.js +++ b/packages/react-admin/src/mui/button/DeleteButton.js @@ -14,6 +14,9 @@ const styles = { display: 'inline-flex', alignItems: 'center', }, + iconPaddingStyle: { + paddingRight: '0.5em', + }, }; const DeleteButton = ({ @@ -28,8 +31,7 @@ const DeleteButton = ({ to={`${linkToRecord(basePath, record.id)}/delete`} className={classes.link} > - -   + {label && translate(label)} diff --git a/packages/react-admin/src/mui/button/EditButton.js b/packages/react-admin/src/mui/button/EditButton.js index b561096d9a2..6a6648dd0d0 100644 --- a/packages/react-admin/src/mui/button/EditButton.js +++ b/packages/react-admin/src/mui/button/EditButton.js @@ -15,6 +15,9 @@ const styles = { display: 'inline-flex', alignItems: 'center', }, + iconPaddingStyle: { + paddingRight: '0.5em', + }, }; const EditButton = ({ @@ -26,8 +29,7 @@ const EditButton = ({ }) => ( diff --git a/packages/react-admin/src/mui/button/ListButton.js b/packages/react-admin/src/mui/button/ListButton.js index 700aa7ce9ad..6a5ee8de109 100644 --- a/packages/react-admin/src/mui/button/ListButton.js +++ b/packages/react-admin/src/mui/button/ListButton.js @@ -13,6 +13,9 @@ const styles = { display: 'inline-flex', alignItems: 'center', }, + iconPaddingStyle: { + paddingRight: '0.5em', + }, }; const ListButton = ({ @@ -23,8 +26,7 @@ const ListButton = ({ }) => ( diff --git a/packages/react-admin/src/mui/button/RefreshButton.js b/packages/react-admin/src/mui/button/RefreshButton.js index 8fca0861a03..8e3ca712f4a 100644 --- a/packages/react-admin/src/mui/button/RefreshButton.js +++ b/packages/react-admin/src/mui/button/RefreshButton.js @@ -4,11 +4,20 @@ import { connect } from 'react-redux'; import compose from 'recompose/compose'; import Button from 'material-ui/Button'; import NavigationRefresh from 'material-ui-icons/Refresh'; +import { withStyles } from 'material-ui/styles'; + import translate from '../../i18n/translate'; import { refreshView as refreshViewAction } from '../../actions/uiActions'; +const styles = { + iconPaddingStyle: { + paddingRight: '0.5em', + }, +}; + class RefreshButton extends Component { static propTypes = { + classes: PropTypes.object, label: PropTypes.string, translate: PropTypes.func.isRequired, refreshView: PropTypes.func.isRequired, @@ -24,12 +33,11 @@ class RefreshButton extends Component { }; render() { - const { label, translate } = this.props; + const { classes = {}, label, translate } = this.props; return ( ); @@ -38,6 +46,7 @@ class RefreshButton extends Component { const enhance = compose( connect(null, { refreshView: refreshViewAction }), + withStyles(styles), translate ); diff --git a/packages/react-admin/src/mui/button/SaveButton.js b/packages/react-admin/src/mui/button/SaveButton.js index b586ed62490..fe273e6798b 100644 --- a/packages/react-admin/src/mui/button/SaveButton.js +++ b/packages/react-admin/src/mui/button/SaveButton.js @@ -1,11 +1,20 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import compose from 'recompose/compose'; import Button from 'material-ui/Button'; import ContentSave from 'material-ui-icons/Save'; import { CircularProgress } from 'material-ui/Progress'; +import { withStyles } from 'material-ui/styles'; + import translate from '../../i18n/translate'; +const styles = { + iconPaddingStyle: { + paddingRight: '0.5em', + }, +}; + export class SaveButton extends Component { handleClick = e => { if (this.props.saving) { @@ -23,6 +32,7 @@ export class SaveButton extends Component { render() { const { + classes = {}, saving, label = 'ra.action.save', raised = true, @@ -43,11 +53,14 @@ export class SaveButton extends Component { }} > {saving && saving.redirect === redirect ? ( - + ) : ( - + )} -   {label && translate(label)} ); @@ -73,4 +86,9 @@ const mapStateToProps = state => ({ saving: state.admin.saving, }); -export default connect(mapStateToProps)(translate(SaveButton)); +const enhance = compose( + connect(mapStateToProps), + withStyles(styles), + translate +); +export default enhance(SaveButton); diff --git a/packages/react-admin/src/mui/button/ShowButton.js b/packages/react-admin/src/mui/button/ShowButton.js index 4dc71f1ec00..936f1fd6924 100644 --- a/packages/react-admin/src/mui/button/ShowButton.js +++ b/packages/react-admin/src/mui/button/ShowButton.js @@ -15,6 +15,9 @@ const styles = { display: 'inline-flex', alignItems: 'center', }, + iconPaddingStyle: { + paddingRight: '0.5em', + }, }; const ShowButton = ({ @@ -29,8 +32,7 @@ const ShowButton = ({ to={`${linkToRecord(basePath, record.id)}/show`} className={classes.link} > - -   + {label && translate(label)} From bb9b36e9936f1b53f8b1631deb5e7057c3624769 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 17:56:56 +0700 Subject: [PATCH 06/16] MAke Delete page navigation consistent with other pages --- packages/react-admin/src/mui/delete/Delete.js | 54 +++++++++++-------- .../src/mui/delete/DeleteActions.js | 41 ++++++++++++++ packages/react-admin/src/mui/delete/index.js | 2 + 3 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 packages/react-admin/src/mui/delete/DeleteActions.js create mode 100644 packages/react-admin/src/mui/delete/index.js diff --git a/packages/react-admin/src/mui/delete/Delete.js b/packages/react-admin/src/mui/delete/Delete.js index 99b0ddc1c54..ed1018f7186 100644 --- a/packages/react-admin/src/mui/delete/Delete.js +++ b/packages/react-admin/src/mui/delete/Delete.js @@ -1,29 +1,29 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import Card, { CardContent, CardActions } from 'material-ui/Card'; +import compose from 'recompose/compose'; +import inflection from 'inflection'; +import Card, { CardContent } from 'material-ui/Card'; import Toolbar from 'material-ui/Toolbar'; import Button from 'material-ui/Button'; import Typography from 'material-ui/Typography'; import { withStyles } from 'material-ui/styles'; import ActionCheck from 'material-ui-icons/CheckCircle'; import AlertError from 'material-ui-icons/ErrorOutline'; -import compose from 'recompose/compose'; -import inflection from 'inflection'; import Header from '../layout/Header'; import Title from '../layout/Title'; -import { ListButton } from '../button'; -import { - crudGetOne as crudGetOneAction, - crudDelete as crudDeleteAction, -} from '../../actions/dataActions'; +import { crudGetOne, crudDelete } from '../../actions/dataActions'; import translate from '../../i18n/translate'; +import DefaultActions from './DeleteActions'; const styles = theme => ({ button: { margin: theme.spacing.unit * 2, }, + iconPaddingStyle: { + paddingRight: '0.5em', + }, }); export class Delete extends Component { @@ -82,11 +82,15 @@ export class Delete extends Component { render() { const { - classes, + actions = , + classes = {}, title, id, data, isLoading, + hasEdit, + hasShow, + hasList, resource, translate, } = this.props; @@ -112,13 +116,15 @@ export class Delete extends Component {
- - - } + actions={actions} + actionProps={{ + basePath, + data, + hasEdit, + hasList, + hasShow, + }} /> -
@@ -132,8 +138,9 @@ export class Delete extends Component { color="primary" className={classes.button} > - -   + {translate('ra.action.delete')}   @@ -142,8 +149,9 @@ export class Delete extends Component { onClick={this.goBack} className={classes.button} > - -   + {translate('ra.action.cancel')} @@ -155,10 +163,14 @@ export class Delete extends Component { } Delete.propTypes = { + actions: PropTypes.element, classes: PropTypes.object, crudDelete: PropTypes.func.isRequired, crudGetOne: PropTypes.func.isRequired, data: PropTypes.object, + hasEdit: PropTypes.bool, + hasShow: PropTypes.bool, + hasList: PropTypes.bool, history: PropTypes.object.isRequired, id: PropTypes.string.isRequired, isLoading: PropTypes.bool.isRequired, @@ -183,8 +195,8 @@ function mapStateToProps(state, props) { const enhance = compose( connect(mapStateToProps, { - crudGetOne: crudGetOneAction, - crudDelete: crudDeleteAction, + crudGetOne, + crudDelete, }), withStyles(styles), translate diff --git a/packages/react-admin/src/mui/delete/DeleteActions.js b/packages/react-admin/src/mui/delete/DeleteActions.js new file mode 100644 index 00000000000..81ad6c9625d --- /dev/null +++ b/packages/react-admin/src/mui/delete/DeleteActions.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { CardActions } from 'material-ui/Card'; +import { withStyles } from 'material-ui/styles'; + +import { ListButton, ShowButton, EditButton } from '../button'; + +const styles = { + cardActionStyle: { + zIndex: 2, + display: 'flex', + justifyContent: 'flex-end', + flexWrap: 'wrap', + }, +}; + +const DeleteActions = ({ + basePath, + classes, + data, + hasEdit, + hasList, + hasShow, +}) => ( + + {hasList && } + {hasEdit && } + {hasShow && } + +); + +DeleteActions.propTypes = { + basePath: PropTypes.string, + classes: PropTypes.object, + data: PropTypes.object, + hasEdit: PropTypes.bool, + hasShow: PropTypes.bool, + hasList: PropTypes.bool, +}; + +export default withStyles(styles)(DeleteActions); diff --git a/packages/react-admin/src/mui/delete/index.js b/packages/react-admin/src/mui/delete/index.js new file mode 100644 index 00000000000..21e225cdcfb --- /dev/null +++ b/packages/react-admin/src/mui/delete/index.js @@ -0,0 +1,2 @@ +export Delete from './Delete'; +export DeleteActions from './DeleteActions'; From 176905e15ad2e1578578e4a12dffa44ba853cc71 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 17:58:39 +0700 Subject: [PATCH 07/16] Add jsDoc for Login and Logout components --- packages/react-admin/src/mui/auth/Login.js | 19 ++++++++++++++++++- packages/react-admin/src/mui/auth/Logout.js | 6 +++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/react-admin/src/mui/auth/Login.js b/packages/react-admin/src/mui/auth/Login.js index 8ba83801cc1..03a53899154 100644 --- a/packages/react-admin/src/mui/auth/Login.js +++ b/packages/react-admin/src/mui/auth/Login.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { propTypes, reduxForm, Field } from 'redux-form'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; - import Card, { CardActions } from 'material-ui/Card'; import Avatar from 'material-ui/Avatar'; import Button from 'material-ui/Button'; @@ -66,6 +65,24 @@ const renderInput = ({ /> ); +/** + * A standalone login page, to serve as authentication gate to the admin + * + * Expects the user to enter a login and a password, which will be checked + * by the `authClient` using the AUTH_LOGIN verb. Redirects to the root page + * (/) upon success, otherwise displays an authentication error message. + * + * Copy and adapt this component to implement your own login logic + * (e.g. to authenticate via email or facebook or anything else). + * + * @example + * import MyLoginPage from './MyLoginPage'; + * const App = () => ( + * + * ... + * + * ); + */ class Login extends Component { login = auth => this.props.userLogin( diff --git a/packages/react-admin/src/mui/auth/Logout.js b/packages/react-admin/src/mui/auth/Logout.js index 31b3849ce7d..73217e8a6e9 100644 --- a/packages/react-admin/src/mui/auth/Logout.js +++ b/packages/react-admin/src/mui/auth/Logout.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; - import { MenuItem } from 'material-ui/Menu'; import ExitIcon from 'material-ui-icons/PowerSettingsNew'; @@ -11,6 +10,11 @@ import { userLogout as userLogoutAction } from '../../actions/authActions'; const iconPaddingStyle = { paddingRight: '0.5em' }; +/** + * Logout button component, to be passed to the Admin component + * + * Used for the Logout Menu item in the sidebar + */ const Logout = ({ translate, userLogout }) => ( From bfd4f433fc90e7353bbbc3183eac3276f02a63d6 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 22:13:25 +0700 Subject: [PATCH 08/16] Refactor CardActions and add jsDoc to detail components --- docs/CreateEdit.md | 23 ++++---- docs/List.md | 19 ++++--- .../src/mui/button/SaveButton.spec.js | 27 +++++----- packages/react-admin/src/mui/delete/Delete.js | 47 ++++++++++++++-- .../src/mui/delete/DeleteActions.js | 53 +++++++++++-------- packages/react-admin/src/mui/detail/Create.js | 41 ++++++++++++++ .../src/mui/detail/CreateActions.js | 45 ++++++++++++---- packages/react-admin/src/mui/detail/Edit.js | 42 +++++++++++++++ .../react-admin/src/mui/detail/Edit.spec.js | 5 +- .../react-admin/src/mui/detail/EditActions.js | 49 +++++++++++++---- packages/react-admin/src/mui/detail/Show.js | 45 +++++++++++++++- .../react-admin/src/mui/detail/Show.spec.js | 5 +- .../react-admin/src/mui/detail/ShowActions.js | 49 +++++++++++++---- packages/react-admin/src/mui/index.js | 2 +- .../react-admin/src/mui/layout/CardActions.js | 20 +++++++ packages/react-admin/src/mui/layout/index.js | 1 + packages/react-admin/src/mui/list/List.js | 2 +- .../mui/list/{Actions.js => ListActions.js} | 13 ++--- 18 files changed, 378 insertions(+), 110 deletions(-) create mode 100644 packages/react-admin/src/mui/layout/CardActions.js rename packages/react-admin/src/mui/list/{Actions.js => ListActions.js} (82%) diff --git a/docs/CreateEdit.md b/docs/CreateEdit.md index 5d2e6d814d9..7b39b96f5cf 100644 --- a/docs/CreateEdit.md +++ b/docs/CreateEdit.md @@ -13,7 +13,7 @@ The Create and Edit views both display a form, initialized with an empty record ## The `` and `` components -The `` and `` components render the page title and actions, and fetch the record from the REST API. They are not responsible for rendering the actual form - that's the job of their child component (usually ``), to which they pass the `record` as prop. +The `` and `` components render the page title and actions, and fetch the record from the data provider. They are not responsible for rendering the actual form - that's the job of their child component (usually ``), to which they pass the `record` as prop. Here are all the props accepted by the `` and `` components: @@ -114,18 +114,17 @@ export const PostEdit = (props) => ( You can replace the list of default actions by your own element using the `actions` prop: ```jsx -import { CardActions } from 'material-ui/Card'; import Button from 'material-ui/Button'; -import { ListButton, ShowButton, DeleteButton, RefreshButton } from 'react-admin'; - -const cardActionStyle = { - zIndex: 2, - display: 'inline-block', - float: 'right', -}; - -const PostEditActions = ({ basePath, data, refresh }) => ( - +import { + CardActions, + ListButton, + ShowButton, + DeleteButton, + RefreshButton, +} from 'react-admin'; + +const PostEditActions = ({ basePath, data }) => ( + diff --git a/docs/List.md b/docs/List.md index 905e514456c..ef6804d8b88 100644 --- a/docs/List.md +++ b/docs/List.md @@ -82,20 +82,19 @@ The title can be either a string, or an element of your own. You can replace the list of default actions by your own element using the `actions` prop: ```jsx -import { CardActions } from 'material-ui/Card'; import Button from 'material-ui/Button'; import NavigationRefresh from 'material-ui-icons/Refresh'; -import { CreateButton, RefreshButton } from 'react-admin'; - -const cardActionStyle = { - zIndex: 2, - display: 'inline-block', - float: 'right', -}; +import { CardActions, CreateButton, RefreshButton } from 'react-admin'; const PostActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter }) => ( - - {filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) } + + {filters && React.cloneElement(filters, { + resource, + showFilter, + displayedFilters, + filterValues, + context: 'button', + }) } {/* Add your custom actions */} diff --git a/packages/react-admin/src/mui/button/SaveButton.spec.js b/packages/react-admin/src/mui/button/SaveButton.spec.js index 9b12423084f..8b602b34dee 100644 --- a/packages/react-admin/src/mui/button/SaveButton.spec.js +++ b/packages/react-admin/src/mui/button/SaveButton.spec.js @@ -1,4 +1,3 @@ -import assert from 'assert'; import { shallow } from 'enzyme'; import React from 'react'; @@ -12,8 +11,8 @@ describe('', () => { ); const ButtonElement = wrapper.find('withStyles(Button)'); - assert.equal(ButtonElement.length, 1); - assert.equal(ButtonElement.at(0).prop('raised'), true); + expect(ButtonElement.length).toEqual(1); + expect(ButtonElement.at(0).prop('raised')).toEqual(true); }); it('should render + * + * ); + * + * export const PostDelete = (props) => ( + * } {...props}> + * ... + * + * ); + */ +const DeleteActions = ({ basePath, data, hasEdit, hasList, hasShow }) => ( + {hasList && } {hasEdit && } {hasShow && } @@ -31,11 +41,10 @@ const DeleteActions = ({ DeleteActions.propTypes = { basePath: PropTypes.string, - classes: PropTypes.object, data: PropTypes.object, hasEdit: PropTypes.bool, hasShow: PropTypes.bool, hasList: PropTypes.bool, }; -export default withStyles(styles)(DeleteActions); +export default DeleteActions; diff --git a/packages/react-admin/src/mui/detail/Create.js b/packages/react-admin/src/mui/detail/Create.js index 8856110f36d..c290f96f823 100644 --- a/packages/react-admin/src/mui/detail/Create.js +++ b/packages/react-admin/src/mui/detail/Create.js @@ -11,6 +11,47 @@ import DefaultActions from './CreateActions'; import translate from '../../i18n/translate'; import withChildrenAsFunction from '../withChildrenAsFunction'; +/** + * Page component for the Create view + * + * The `` component renders the page title and actions. + * It is not responsible for rendering the actual form - + * that's the job of its child component (usually ``), + * to which it passes pass the `record` as prop. + * + * Here are all the props accepted by the ``component: + * + * - title + * - actions + * + * Both expect an element for value. + * + * @example + * // in src/posts.js + * import React from 'react'; + * import { Create, SimpleForm, TextInput } from 'react-admin'; + * + * export const PostCreate = (props) => ( + * + * + * + * + * + * ); + * + * // in src/App.js + * import React from 'react'; + * import { Admin, Resource } from 'react-admin'; + * + * import { PostCreate } from './posts'; + * + * const App = () => ( + * + * + * + * ); + * export default App; + */ class Create extends Component { getBasePath() { const { location } = this.props; diff --git a/packages/react-admin/src/mui/detail/CreateActions.js b/packages/react-admin/src/mui/detail/CreateActions.js index c9ef786cdd3..3e8d181f53f 100644 --- a/packages/react-admin/src/mui/detail/CreateActions.js +++ b/packages/react-admin/src/mui/detail/CreateActions.js @@ -1,18 +1,41 @@ import React from 'react'; -import { CardActions } from 'material-ui/Card'; -import { ListButton } from '../button'; +import PropTypes from 'prop-types'; -const cardActionStyle = { - zIndex: 2, - display: 'flex', - justifyContent: 'flex-end', - flexWrap: 'wrap', -}; +import CardActions from '../layout/CardActions'; +import { ListButton } from '../button'; +/** + * Action Toolbar for the Create view + * + * Internal component. If you want to add or remove actions for a Create view, + * write your own CreateActions Component. Then, in the component, + * use it in the `actions` prop to pas a custom element. + * + * @example + * import Button from 'material-ui/Button'; + * import { CardActions, Create, ListButton } from 'react-admin'; + * + * const PostCreateActions = ({ basePath }) => ( + * + * + * // Add your custom actions here // + * + * + * ); + * + * export const PostCreate = (props) => ( + * } {...props}> + * ... + * + * ); + */ const CreateActions = ({ basePath, hasList }) => ( - - {hasList && } - + {hasList && } ); +CreateActions.propTypes = { + basePath: PropTypes.string, + hasList: PropTypes.bool, +}; + export default CreateActions; diff --git a/packages/react-admin/src/mui/detail/Edit.js b/packages/react-admin/src/mui/detail/Edit.js index 3ea754fd4ba..e55c7fb6708 100644 --- a/packages/react-admin/src/mui/detail/Edit.js +++ b/packages/react-admin/src/mui/detail/Edit.js @@ -14,6 +14,48 @@ import DefaultActions from './EditActions'; import translate from '../../i18n/translate'; import withChildrenAsFunction from '../withChildrenAsFunction'; +/** + * Page component for the Edit view + * + * The `` component renders the page title and actions, + * fetches the record from the data provider. + * It is not responsible for rendering the actual form - + * that's the job of its child component (usually ``), + * to which it passes pass the `record` as prop. + * + * Here are all the props accepted by the ``component: + * + * - title + * - actions + * + * Both expect an element for value. + * + * @example + * // in src/posts.js + * import React from 'react'; + * import { Edit, SimpleForm, TextInput } from 'react-admin'; + * + * export const PostEdit = (props) => ( + * + * + * + * + * + * ); + * + * // in src/App.js + * import React from 'react'; + * import { Admin, Resource } from 'react-admin'; + * + * import { PostEdit } from './posts'; + * + * const App = () => ( + * + * + * + * ); + * export default App; + */ export class Edit extends Component { constructor(props) { super(props); diff --git a/packages/react-admin/src/mui/detail/Edit.spec.js b/packages/react-admin/src/mui/detail/Edit.spec.js index 3dc4b2268b9..8dbdd177591 100644 --- a/packages/react-admin/src/mui/detail/Edit.spec.js +++ b/packages/react-admin/src/mui/detail/Edit.spec.js @@ -1,5 +1,4 @@ import React from 'react'; -import assert from 'assert'; import { shallow } from 'enzyme'; import { Edit } from './Edit'; @@ -31,7 +30,7 @@ describe('', () => { ); const inner = wrapper.find('Foo'); - assert.equal(inner.length, 1); + expect(inner.length).toEqual(1); }); it('should display children inputs of SimpleForm', () => { @@ -44,6 +43,6 @@ describe('', () => { ); const inputs = wrapper.find('WithFormField'); - assert.deepEqual(inputs.map(i => i.prop('source')), ['foo', 'bar']); + expect(inputs.map(i => i.prop('source'))).toEqual(['foo', 'bar']); }); }); diff --git a/packages/react-admin/src/mui/detail/EditActions.js b/packages/react-admin/src/mui/detail/EditActions.js index 63e37b54dc3..f8cdf9c1938 100644 --- a/packages/react-admin/src/mui/detail/EditActions.js +++ b/packages/react-admin/src/mui/detail/EditActions.js @@ -1,16 +1,39 @@ import React from 'react'; -import { CardActions } from 'material-ui/Card'; -import { ListButton, ShowButton, DeleteButton, RefreshButton } from '../button'; +import PropTypes from 'prop-types'; -const cardActionStyle = { - zIndex: 2, - display: 'flex', - justifyContent: 'flex-end', - flexWrap: 'wrap', -}; +import { ListButton, ShowButton, DeleteButton, RefreshButton } from '../button'; +import CardActions from '../layout/CardActions'; +/** + * Action Toolbar for the Edit view + * + * Internal component. If you want to add or remove actions for a Edit view, + * write your own EditActions Component. Then, in the component, + * use it in the `actions` prop to pas a custom element. + * + * @example + * import Button from 'material-ui/Button'; + * import { CardActions, ListButton, ShowButton, DeleteButton, RefreshButton, Edit } from 'react-admin'; + * + * const PostEditActions = ({ basePath, data }) => ( + * + * + * + * + * + * // Add your custom actions here // + * + * + * ); + * + * export const PostEdit = (props) => ( + * } {...props}> + * ... + * + * ); + */ const EditActions = ({ basePath, data, hasDelete, hasShow, hasList }) => ( - + {hasShow && } {hasList && } {hasDelete && } @@ -18,4 +41,12 @@ const EditActions = ({ basePath, data, hasDelete, hasShow, hasList }) => ( ); +EditActions.propTypes = { + basePath: PropTypes.string, + data: PropTypes.object, + hasDelete: PropTypes.bool, + hasList: PropTypes.bool, + hasShow: PropTypes.bool, +}; + export default EditActions; diff --git a/packages/react-admin/src/mui/detail/Show.js b/packages/react-admin/src/mui/detail/Show.js index a362f6ceb62..48c13d61753 100644 --- a/packages/react-admin/src/mui/detail/Show.js +++ b/packages/react-admin/src/mui/detail/Show.js @@ -1,9 +1,10 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import Card from 'material-ui/Card'; import compose from 'recompose/compose'; import inflection from 'inflection'; +import Card from 'material-ui/Card'; + import Header from '../layout/Header'; import Title from '../layout/Title'; import { crudGetOne as crudGetOneAction } from '../../actions/dataActions'; @@ -11,6 +12,48 @@ import DefaultActions from './ShowActions'; import translate from '../../i18n/translate'; import withChildrenAsFunction from '../withChildrenAsFunction'; +/** + * Page component for the Show view + * + * The `` component renders the page title and actions, + * fetches the record from the data provider. + * It is not responsible for rendering the actual form - + * that's the job of its child component (usually ``), + * to which it passes pass the `record` as prop. + * + * Here are all the props accepted by the ``component: + * + * - title + * - actions + * + * Both expect an element for value. + * + * @example + * // in src/posts.js + * import React from 'react'; + * import { Show, SimpleShowLayout, TextField } from 'react-admin'; + * + * export const PostShow = (props) => ( + * + * + * + * + * + * ); + * + * // in src/App.js + * import React from 'react'; + * import { Admin, Resource } from 'react-admin'; + * + * import { PostShow } from './posts'; + * + * const App = () => ( + * + * + * + * ); + * export default App; + */ export class Show extends Component { componentDidMount() { this.updateData(); diff --git a/packages/react-admin/src/mui/detail/Show.spec.js b/packages/react-admin/src/mui/detail/Show.spec.js index 447ecbecd13..8a96485c261 100644 --- a/packages/react-admin/src/mui/detail/Show.spec.js +++ b/packages/react-admin/src/mui/detail/Show.spec.js @@ -1,5 +1,4 @@ import React from 'react'; -import assert from 'assert'; import { shallow } from 'enzyme'; import { Show } from './Show'; @@ -30,7 +29,7 @@ describe('', () => { ); const inner = wrapper.find('Foo'); - assert.equal(inner.length, 1); + expect(inner.length).toEqual(1); }); it('should display children inputs of SimpleShowLayout', () => { @@ -43,6 +42,6 @@ describe('', () => { ); const inputs = wrapper.find('pure(TextField)'); - assert.deepEqual(inputs.map(i => i.prop('source')), ['foo', 'bar']); + expect(inputs.map(i => i.prop('source'))).toEqual(['foo', 'bar']); }); }); diff --git a/packages/react-admin/src/mui/detail/ShowActions.js b/packages/react-admin/src/mui/detail/ShowActions.js index 9afde309d7f..9d1ab181691 100644 --- a/packages/react-admin/src/mui/detail/ShowActions.js +++ b/packages/react-admin/src/mui/detail/ShowActions.js @@ -1,16 +1,39 @@ import React from 'react'; -import { CardActions } from 'material-ui/Card'; -import { ListButton, EditButton, DeleteButton, RefreshButton } from '../button'; +import PropTypes from 'prop-types'; -const cardActionStyle = { - zIndex: 2, - display: 'flex', - justifyContent: 'flex-end', - flexWrap: 'wrap', -}; +import { ListButton, EditButton, DeleteButton, RefreshButton } from '../button'; +import CardActions from '../layout/CardActions'; +/** + * Action Toolbar for the Show view + * + * Internal component. If you want to add or remove actions for a Show view, + * write your own ShowActions Component. Then, in the component, + * use it in the `actions` prop to pas a custom element. + * + * @example + * import Button from 'material-ui/Button'; + * import { CardActions, ListButton, EditButton, DeleteButton, RefreshButton, Show } from 'react-admin'; + * + * const PostShowActions = ({ basePath, data }) => ( + * + * + * + * + * + * // Add your custom actions here // + * + * + * ); + * + * export const PostShow = (props) => ( + * } {...props}> + * ... + * + * ); + */ const ShowActions = ({ basePath, data, hasDelete, hasEdit, hasList }) => ( - + {hasEdit && } {hasList && } {hasDelete && } @@ -18,4 +41,12 @@ const ShowActions = ({ basePath, data, hasDelete, hasEdit, hasList }) => ( ); +ShowActions.propTypes = { + basePath: PropTypes.string, + data: PropTypes.object, + hasDelete: PropTypes.bool, + hasEdit: PropTypes.bool, + hasList: PropTypes.bool, +}; + export default ShowActions; diff --git a/packages/react-admin/src/mui/index.js b/packages/react-admin/src/mui/index.js index a1de953c805..23a3ac037be 100644 --- a/packages/react-admin/src/mui/index.js +++ b/packages/react-admin/src/mui/index.js @@ -5,6 +5,6 @@ export * from './field'; export * from './input'; export * from './layout'; export * from './list'; -export Delete from './delete/Delete'; +export * from './delete'; export Link from './Link'; export defaultTheme from './defaultTheme'; diff --git a/packages/react-admin/src/mui/layout/CardActions.js b/packages/react-admin/src/mui/layout/CardActions.js new file mode 100644 index 00000000000..491381ce7f6 --- /dev/null +++ b/packages/react-admin/src/mui/layout/CardActions.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { CardActions as MuiCardActions } from 'material-ui/Card'; +import { withStyles } from 'material-ui/styles'; + +const styles = { + cardActions: { + zIndex: 2, + display: 'flex', + justifyContent: 'flex-end', + flexWrap: 'wrap', + }, +}; + +const CardActions = ({ classes, children, ...rest }) => ( + + {children} + +); + +export default withStyles(styles)(CardActions); diff --git a/packages/react-admin/src/mui/layout/index.js b/packages/react-admin/src/mui/layout/index.js index ff34d9d9e23..b0eb01988b0 100644 --- a/packages/react-admin/src/mui/layout/index.js +++ b/packages/react-admin/src/mui/layout/index.js @@ -1,5 +1,6 @@ export AppBar from './AppBar'; export AppBarMobile from './AppBarMobile'; +export CardActions from './CardActions'; export DashboardMenuItem from './DashboardMenuItem'; export Header from './Header'; export Layout from './Layout'; diff --git a/packages/react-admin/src/mui/list/List.js b/packages/react-admin/src/mui/list/List.js index c266d7275ec..9204003ccf8 100644 --- a/packages/react-admin/src/mui/list/List.js +++ b/packages/react-admin/src/mui/list/List.js @@ -17,7 +17,7 @@ import queryReducer, { import Header from '../layout/Header'; import Title from '../layout/Title'; import DefaultPagination from './Pagination'; -import DefaultActions from './Actions'; +import DefaultActions from './ListActions'; import { crudGetList as crudGetListAction } from '../../actions/dataActions'; import { changeListParams as changeListParamsAction } from '../../actions/listActions'; import translate from '../../i18n/translate'; diff --git a/packages/react-admin/src/mui/list/Actions.js b/packages/react-admin/src/mui/list/ListActions.js similarity index 82% rename from packages/react-admin/src/mui/list/Actions.js rename to packages/react-admin/src/mui/list/ListActions.js index 8b82e439690..68b0f139e57 100644 --- a/packages/react-admin/src/mui/list/Actions.js +++ b/packages/react-admin/src/mui/list/ListActions.js @@ -1,15 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { CardActions } from 'material-ui/Card'; -import { CreateButton, RefreshButton } from '../button'; import onlyUpdateForKeys from 'recompose/onlyUpdateForKeys'; -const cardActionStyle = { - zIndex: 2, - display: 'flex', - justifyContent: 'flex-end', - flexWrap: 'wrap', -}; +import { CreateButton, RefreshButton } from '../button'; +import CardActions from '../layout/CardActions'; const Actions = ({ resource, @@ -21,7 +15,7 @@ const Actions = ({ showFilter, }) => { return ( - + {filters && React.cloneElement(filters, { resource, @@ -44,7 +38,6 @@ Actions.propTypes = { hasCreate: PropTypes.bool, resource: PropTypes.string, showFilter: PropTypes.func, - theme: PropTypes.object, }; export default onlyUpdateForKeys([ From b0e41509cebdf1c225413d776ef990fc0e20ae1d Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 22:14:49 +0700 Subject: [PATCH 09/16] improve permission jsDoc --- packages/react-admin/src/auth/Permission.js | 30 +++++++++++-------- packages/react-admin/src/auth/Restricted.js | 27 +++++++++++++---- .../react-admin/src/auth/SwitchPermissions.js | 30 +++++++++++-------- .../react-admin/src/auth/WithPermission.js | 4 ++- 4 files changed, 58 insertions(+), 33 deletions(-) diff --git a/packages/react-admin/src/auth/Permission.js b/packages/react-admin/src/auth/Permission.js index fdf08a04000..c29dff28d70 100644 --- a/packages/react-admin/src/auth/Permission.js +++ b/packages/react-admin/src/auth/Permission.js @@ -23,19 +23,23 @@ import PropTypes from 'prop-types'; * - `exact`: the value of the `exact` prop if specified * * @example - * - * - * - * - * - * - * - * - * - * - * - * - * + * import { SwitchPermission, Permission } from 'react-admin'; + * + * const Dashboard = () => ( + * + * + * + * + * + * + * + * + * + * + * + * + * + * ); */ const Permission = () => ( diff --git a/packages/react-admin/src/auth/Restricted.js b/packages/react-admin/src/auth/Restricted.js index 4e4fe10e1fc..5d18f5398aa 100644 --- a/packages/react-admin/src/auth/Restricted.js +++ b/packages/react-admin/src/auth/Restricted.js @@ -7,14 +7,29 @@ import { userCheck } from '../actions/authActions'; /** * Restrict access to children * - * Useful for Route components - used in CrudRoute + * Useful for Route components ; used internally by CrudRoute. + * Use it to decorate your custom page components to require + * authentication or a custom role. + * + * Pass the `location` from the `routeParams` as `location` prop. + * You can set additional `authParams` at will if your authClient + * requires it. * * @example - * - * - * - * - * } /> + * import { Restricted } from 'react-admin'; + * + * const CustomRoutes = [ + * + * + * + * + * } /> + * ]; + * const App = () => ( + * + * ... + * + * ); */ export class Restricted extends Component { static propTypes = { diff --git a/packages/react-admin/src/auth/SwitchPermissions.js b/packages/react-admin/src/auth/SwitchPermissions.js index 27f5c231cae..cf1cb2595b4 100644 --- a/packages/react-admin/src/auth/SwitchPermissions.js +++ b/packages/react-admin/src/auth/SwitchPermissions.js @@ -16,19 +16,23 @@ import resolvePermissions from './resolvePermissions'; * @see Permission * * @example - * - * - * - * - * - * - * - * - * - * - * - * - * + * import { SwitchPermission, Permission } from 'react-admin'; + * + * const Dashboard = () => ( + * + * + * + * + * + * + * + * + * + * + * + * + * + * ); */ export class SwitchPermissions extends Component { static propTypes = { diff --git a/packages/react-admin/src/auth/WithPermission.js b/packages/react-admin/src/auth/WithPermission.js index c466349bde3..51be58d10b1 100644 --- a/packages/react-admin/src/auth/WithPermission.js +++ b/packages/react-admin/src/auth/WithPermission.js @@ -10,7 +10,7 @@ import { resolvePermission } from './resolvePermissions'; * * Requires either: * - a `value` prop with the permissions to check (could be a role, an array of roles, etc), or - * - a `resolve` function. + * - a `resolve` function. * * An additional `exact` prop may be specified depending on your requirements. * It determines whether the user must have **all** the required permissions or only some. @@ -30,6 +30,8 @@ import { resolvePermission } from './resolvePermissions'; * It is only meant to be used in custom pages or components. * * @example + * import { WithPermission } from 'react-admin'; + * * const Menu = ({ onMenuTap }) => ( *
* From bb97301e1458dc83ef0fdf1df85b9d43ac6a6735 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 23:53:37 +0700 Subject: [PATCH 10/16] Refactor Tab to avoid child inspection --- .../src/mui/detail/SimpleShowLayout.js | 47 +++++- packages/react-admin/src/mui/detail/Tab.js | 143 ++++++++++++++---- 2 files changed, 156 insertions(+), 34 deletions(-) diff --git a/packages/react-admin/src/mui/detail/SimpleShowLayout.js b/packages/react-admin/src/mui/detail/SimpleShowLayout.js index e021195b63f..130b48f5714 100644 --- a/packages/react-admin/src/mui/detail/SimpleShowLayout.js +++ b/packages/react-admin/src/mui/detail/SimpleShowLayout.js @@ -1,16 +1,54 @@ import React, { Children } from 'react'; import PropTypes from 'prop-types'; +import { withStyles } from 'material-ui/styles'; + import Labeled from '../input/Labeled'; -const defaultStyle = { padding: '0 1em 1em 1em' }; +const styles = { + root: { padding: '0 1em 1em 1em' }, +}; + +/** + * Simple Layout for a Show view, showing fields in one column. + * + * Receives the current `record` from the parent `` component, + * and passes it to its childen. Children should be Field-like components. + * + * @example + * // in src/posts.js + * import React from 'react'; + * import { Show, SimpleShowLayout, TextField } from 'react-admin'; + * + * export const PostShow = (props) => ( + * + * + * + * + * + * ); + * + * // in src/App.js + * import React from 'react'; + * import { Admin, Resource } from 'react-admin'; + * + * import { PostShow } from './posts'; + * + * const App = () => ( + * + * + * + * ); + * export default App; + */ export const SimpleShowLayout = ({ basePath, children, + classes, record, resource, - style = defaultStyle, + style, }) => ( -
+
{Children.map(children, field => (
` component accepts the following props: + * + * - label: The string displayed for each tab + * - icon: The icon to show before the label (optional). Must be an element. + * + * @example + * // in src/posts.js + * import React from 'react'; + * import FavoriteIcon from 'material-ui-icons/Favorite'; + * import PersonPinIcon from 'material-ui-icons/PersonPin'; + * import { Show, TabbedShowLayout, Tab, TextField } from 'react-admin'; + * + * export const PostShow = (props) => ( + * + * + * }> + * + * + * + * }> + * + * + * + * + * ); + * + * // in src/App.js + * import React from 'react'; + * import { Admin, Resource } from 'react-admin'; + * + * import { PostShow } from './posts'; + * + * const App = () => ( + * + * + * + * ); + * export default App; + */ +class Tab extends Component { + renderHeader = ({ label, icon, value, translate, rest }) => ( + + ); -const Tab = ({ label, icon, children, ...rest }) => ( - - {React.Children.map( + renderContent = ({ children, rest }) => ( + + {React.Children.map( + children, + field => + field && ( +
+ {field.props.addLabel ? ( + + {field} + + ) : typeof field.type === 'string' ? ( + field + ) : ( + React.cloneElement(field, rest) + )} +
+ ) + )} +
+ ); + + render() { + const { children, - field => - field && ( -
- {field.props.addLabel ? ( - - {field} - - ) : typeof field.type === 'string' ? ( - field - ) : ( - React.cloneElement(field, rest) - )} -
- ) - )} -
-); + context, + icon, + label, + translate, + value, + ...rest + } = this.props; + return context === 'header' + ? this.renderHeader({ label, icon, value, translate, rest }) + : this.renderContent({ children, rest }); + } +} + +Tab.propTypes = { + context: PropTypes.oneOf(['header', 'content']), + icon: PropTypes.element, + label: PropTypes.string.isRequired, + translate: PropTypes.func.isRequired, + value: PropTypes.number, +}; -export default Tab; +export default translate(Tab); From ae8d8fe884b09407b9ec16cac74dcb7c3bf48080 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 23:54:18 +0700 Subject: [PATCH 11/16] Minor jsDoc changes --- packages/react-admin/src/mui/detail/Create.js | 2 +- packages/react-admin/src/mui/detail/Edit.js | 3 ++- packages/react-admin/src/mui/detail/Show.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react-admin/src/mui/detail/Create.js b/packages/react-admin/src/mui/detail/Create.js index c290f96f823..04cbf1c8e98 100644 --- a/packages/react-admin/src/mui/detail/Create.js +++ b/packages/react-admin/src/mui/detail/Create.js @@ -19,7 +19,7 @@ import withChildrenAsFunction from '../withChildrenAsFunction'; * that's the job of its child component (usually ``), * to which it passes pass the `record` as prop. * - * Here are all the props accepted by the ``component: + * The `` component accepts the following props: * * - title * - actions diff --git a/packages/react-admin/src/mui/detail/Edit.js b/packages/react-admin/src/mui/detail/Edit.js index e55c7fb6708..d1741c05540 100644 --- a/packages/react-admin/src/mui/detail/Edit.js +++ b/packages/react-admin/src/mui/detail/Edit.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import Card, { CardContent } from 'material-ui/Card'; import compose from 'recompose/compose'; import inflection from 'inflection'; + import Header from '../layout/Header'; import Title from '../layout/Title'; import { @@ -23,7 +24,7 @@ import withChildrenAsFunction from '../withChildrenAsFunction'; * that's the job of its child component (usually ``), * to which it passes pass the `record` as prop. * - * Here are all the props accepted by the ``component: + * The `` component accepts the following props: * * - title * - actions diff --git a/packages/react-admin/src/mui/detail/Show.js b/packages/react-admin/src/mui/detail/Show.js index 48c13d61753..6b5cb4de2a3 100644 --- a/packages/react-admin/src/mui/detail/Show.js +++ b/packages/react-admin/src/mui/detail/Show.js @@ -21,7 +21,7 @@ import withChildrenAsFunction from '../withChildrenAsFunction'; * that's the job of its child component (usually ``), * to which it passes pass the `record` as prop. * - * Here are all the props accepted by the ``component: + * The `` component accepts the following props: * * - title * - actions From 2d53770be3cb3931313d4a563b6d03019961816c Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 27 Nov 2017 23:55:20 +0700 Subject: [PATCH 12/16] Finish TabbedShowLayout refactoring --- .../src/mui/detail/TabbedShowLayout.js | 75 +++++++++++++------ 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/packages/react-admin/src/mui/detail/TabbedShowLayout.js b/packages/react-admin/src/mui/detail/TabbedShowLayout.js index 861341a2dcf..c3be29eb81d 100644 --- a/packages/react-admin/src/mui/detail/TabbedShowLayout.js +++ b/packages/react-admin/src/mui/detail/TabbedShowLayout.js @@ -1,15 +1,55 @@ -import React, { Component, Children } from 'react'; +import React, { Component, Children, cloneElement } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; -import Tabs, { Tab } from 'material-ui/Tabs'; +import Tabs from 'material-ui/Tabs'; import Divider from 'material-ui/Divider'; import { withStyles } from 'material-ui/styles'; import getDefaultValues from '../form/getDefaultValues'; -const styles = { tab: { padding: '0 1em 1em 1em' } }; +const styles = { + tab: { padding: '0 1em 1em 1em' }, +}; +/** + * Tabbed Layout for a Show view, showing fields grouped in tabs. + * + * Receives the current `record` from the parent `` component, + * and passes it to its childen. Children should be Tab components. + * + * @example + * // in src/posts.js + * import React from 'react'; + * import { Show, TabbedShowLayout, Tab, TextField } from 'react-admin'; + * + * export const PostShow = (props) => ( + * + * + * + * + * + * + * + * + * + * + * + * ); + * + * // in src/App.js + * import React from 'react'; + * import { Admin, Resource } from 'react-admin'; + * + * import { PostShow } from './posts'; + * + * const App = () => ( + * + * + * + * ); + * export default App; + */ export class TabbedShowLayout extends Component { constructor(props) { super(props); @@ -23,31 +63,18 @@ export class TabbedShowLayout extends Component { }; render() { - const { - children, - classes, - record, - resource, - basePath, - translate, - } = this.props; + const { children, classes, record, resource, basePath } = this.props; return (
{Children.map( children, (tab, index) => - tab ? ( - - ) : null + tab && + cloneElement(tab, { + context: 'header', + value: index, + }) )} @@ -57,7 +84,8 @@ export class TabbedShowLayout extends Component { (tab, index) => tab && this.state.value === index && - React.cloneElement(tab, { + cloneElement(tab, { + context: 'content', resource, record, basePath, @@ -76,7 +104,6 @@ TabbedShowLayout.propTypes = { record: PropTypes.object, resource: PropTypes.string, basePath: PropTypes.string, - translate: PropTypes.func, }; TabbedShowLayout.defaultProps = { From 1cb903b5d3f481592362babb65e9cb63eebe94bb Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Tue, 28 Nov 2017 00:03:21 +0700 Subject: [PATCH 13/16] Fix esLint warnings --- packages/react-admin/src/mui/auth/Login.js | 2 ++ packages/react-admin/src/mui/button/SaveButton.js | 7 ++++--- packages/react-admin/src/mui/detail/Create.js | 2 ++ packages/react-admin/src/mui/layout/CardActions.js | 5 +++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/react-admin/src/mui/auth/Login.js b/packages/react-admin/src/mui/auth/Login.js index 03a53899154..c3163cf097d 100644 --- a/packages/react-admin/src/mui/auth/Login.js +++ b/packages/react-admin/src/mui/auth/Login.js @@ -148,6 +148,8 @@ Login.propTypes = { ...propTypes, authClient: PropTypes.func, classes: PropTypes.object, + input: PropTypes.object, + meta: PropTypes.object; previousRoute: PropTypes.string, translate: PropTypes.func.isRequired, userLogin: PropTypes.func.isRequired, diff --git a/packages/react-admin/src/mui/button/SaveButton.js b/packages/react-admin/src/mui/button/SaveButton.js index fe273e6798b..266113a2a77 100644 --- a/packages/react-admin/src/mui/button/SaveButton.js +++ b/packages/react-admin/src/mui/button/SaveButton.js @@ -68,13 +68,14 @@ export class SaveButton extends Component { } SaveButton.propTypes = { + classes: PropTypes.object, + handleSubmitWithRedirect: PropTypes.func, label: PropTypes.string, + redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), raised: PropTypes.bool, saving: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]), - translate: PropTypes.func.isRequired, submitOnEnter: PropTypes.bool, - handleSubmitWithRedirect: PropTypes.func, - redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + translate: PropTypes.func.isRequired, }; SaveButton.defaultProps = { diff --git a/packages/react-admin/src/mui/detail/Create.js b/packages/react-admin/src/mui/detail/Create.js index 04cbf1c8e98..3191f9d142a 100644 --- a/packages/react-admin/src/mui/detail/Create.js +++ b/packages/react-admin/src/mui/detail/Create.js @@ -135,6 +135,8 @@ Create.propTypes = { actions: PropTypes.element, children: PropTypes.element, crudCreate: PropTypes.func.isRequired, + hasEdit: PropTypes.bool, + hasShow: PropTypes.bool, isLoading: PropTypes.bool.isRequired, location: PropTypes.object.isRequired, resource: PropTypes.string.isRequired, diff --git a/packages/react-admin/src/mui/layout/CardActions.js b/packages/react-admin/src/mui/layout/CardActions.js index 491381ce7f6..a998b3d23eb 100644 --- a/packages/react-admin/src/mui/layout/CardActions.js +++ b/packages/react-admin/src/mui/layout/CardActions.js @@ -1,4 +1,5 @@ import React from 'react'; +impoer PropTypes from 'prop-types'; import { CardActions as MuiCardActions } from 'material-ui/Card'; import { withStyles } from 'material-ui/styles'; @@ -17,4 +18,8 @@ const CardActions = ({ classes, children, ...rest }) => ( ); +CardActions.propTypes = { + classes: PropTypes.object, +}; + export default withStyles(styles)(CardActions); From 994ca6871db44b430f0fa07246f9cfd3262188e8 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Tue, 28 Nov 2017 00:03:33 +0700 Subject: [PATCH 14/16] defaultTheme is now empty --- packages/react-admin/src/mui/defaultTheme.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/react-admin/src/mui/defaultTheme.js b/packages/react-admin/src/mui/defaultTheme.js index e9bb70f558a..ff8b4c56321 100644 --- a/packages/react-admin/src/mui/defaultTheme.js +++ b/packages/react-admin/src/mui/defaultTheme.js @@ -1,10 +1 @@ -export default { - tabs: { - backgroundColor: 'white', - selectedTextColor: '#00bcd4', - textColor: '#757575', - }, - inkBar: { - backgroundColor: '#00bcd4', - }, -}; +export default {}; From 16542559f87cbcc3145ff96fb7880ba8b2f5d284 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Tue, 28 Nov 2017 00:05:11 +0700 Subject: [PATCH 15/16] Fix syntax errors --- packages/react-admin/src/mui/auth/Login.js | 2 +- packages/react-admin/src/mui/layout/CardActions.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-admin/src/mui/auth/Login.js b/packages/react-admin/src/mui/auth/Login.js index c3163cf097d..cbaa38c994f 100644 --- a/packages/react-admin/src/mui/auth/Login.js +++ b/packages/react-admin/src/mui/auth/Login.js @@ -149,7 +149,7 @@ Login.propTypes = { authClient: PropTypes.func, classes: PropTypes.object, input: PropTypes.object, - meta: PropTypes.object; + meta: PropTypes.object, previousRoute: PropTypes.string, translate: PropTypes.func.isRequired, userLogin: PropTypes.func.isRequired, diff --git a/packages/react-admin/src/mui/layout/CardActions.js b/packages/react-admin/src/mui/layout/CardActions.js index a998b3d23eb..287cdde37ee 100644 --- a/packages/react-admin/src/mui/layout/CardActions.js +++ b/packages/react-admin/src/mui/layout/CardActions.js @@ -1,5 +1,5 @@ import React from 'react'; -impoer PropTypes from 'prop-types'; +import PropTypes from 'prop-types'; import { CardActions as MuiCardActions } from 'material-ui/Card'; import { withStyles } from 'material-ui/styles'; From 5646a626bcb28c407060b9e59810c89daf6c302b Mon Sep 17 00:00:00 2001 From: gildas Date: Tue, 5 Dec 2017 10:15:09 +0100 Subject: [PATCH 16/16] Fix tests --- packages/react-admin/src/mui/input/NullableBooleanInput.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-admin/src/mui/input/NullableBooleanInput.spec.js b/packages/react-admin/src/mui/input/NullableBooleanInput.spec.js index d96cd4404e4..20de7206205 100644 --- a/packages/react-admin/src/mui/input/NullableBooleanInput.spec.js +++ b/packages/react-admin/src/mui/input/NullableBooleanInput.spec.js @@ -7,6 +7,7 @@ describe('', () => { const defaultProps = { input: {}, meta: {}, + classes: {}, translate: x => x, };