From d6e1c612a3649322ad6e11b85e490f863a5924ef Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Wed, 19 Aug 2020 14:36:33 +0200 Subject: [PATCH] Convert Form components to typescript Refs #4505 --- .../ra-core/src/form/FormWithRedirect.tsx | 21 +- .../src/form/{FormInput.js => FormInput.tsx} | 19 +- .../{FormTab.spec.js => FormTab.spec.tsx} | 2 +- .../src/form/{FormTab.js => FormTab.tsx} | 34 ++-- ...SimpleForm.spec.js => SimpleForm.spec.tsx} | 17 +- .../form/{SimpleForm.js => SimpleForm.tsx} | 94 +++++---- ...or.spec.js => SimpleFormIterator.spec.tsx} | 183 +++++++++++------- ...FormIterator.js => SimpleFormIterator.tsx} | 89 +++++---- ...TabbedForm.spec.js => TabbedForm.spec.tsx} | 2 +- .../form/{TabbedForm.js => TabbedForm.tsx} | 10 +- ...rmTabs.spec.js => TabbedFormTabs.spec.tsx} | 2 +- .../{TabbedFormTabs.js => TabbedFormTabs.tsx} | 36 +++- .../src/form/{Toolbar.js => Toolbar.tsx} | 9 +- ...itialValues.js => getFormInitialValues.ts} | 16 +- .../src/form/{index.js => index.tsx} | 0 .../ra-ui-materialui/src/input/ArrayInput.tsx | 11 +- 16 files changed, 328 insertions(+), 217 deletions(-) rename packages/ra-ui-materialui/src/form/{FormInput.js => FormInput.tsx} (79%) rename packages/ra-ui-materialui/src/form/{FormTab.spec.js => FormTab.spec.tsx} (98%) rename packages/ra-ui-materialui/src/form/{FormTab.js => FormTab.tsx} (79%) rename packages/ra-ui-materialui/src/form/{SimpleForm.spec.js => SimpleForm.spec.tsx} (88%) rename packages/ra-ui-materialui/src/form/{SimpleForm.js => SimpleForm.tsx} (72%) rename packages/ra-ui-materialui/src/form/{SimpleFormIterator.spec.js => SimpleFormIterator.spec.tsx} (63%) rename packages/ra-ui-materialui/src/form/{SimpleFormIterator.js => SimpleFormIterator.tsx} (75%) rename packages/ra-ui-materialui/src/form/{TabbedForm.spec.js => TabbedForm.spec.tsx} (98%) rename packages/ra-ui-materialui/src/form/{TabbedForm.js => TabbedForm.tsx} (97%) rename packages/ra-ui-materialui/src/form/{TabbedFormTabs.spec.js => TabbedFormTabs.spec.tsx} (100%) rename packages/ra-ui-materialui/src/form/{TabbedFormTabs.js => TabbedFormTabs.tsx} (78%) rename packages/ra-ui-materialui/src/form/{Toolbar.js => Toolbar.tsx} (96%) rename packages/ra-ui-materialui/src/form/{getFormInitialValues.js => getFormInitialValues.ts} (67%) rename packages/ra-ui-materialui/src/form/{index.js => index.tsx} (100%) diff --git a/packages/ra-core/src/form/FormWithRedirect.tsx b/packages/ra-core/src/form/FormWithRedirect.tsx index da85e6c62e6..2e5eb97615e 100644 --- a/packages/ra-core/src/form/FormWithRedirect.tsx +++ b/packages/ra-core/src/form/FormWithRedirect.tsx @@ -38,7 +38,9 @@ import { setAutomaticRefresh } from '../actions/uiActions'; * * @param {Prop} props */ -const FormWithRedirect: FC = ({ +const FormWithRedirect: FC< + FormWithRedirectOwnProps & Omit +> = ({ debug, decorators, defaultValue, @@ -150,7 +152,7 @@ const FormWithRedirect: FC = ({ export interface FormWithRedirectOwnProps { defaultValue?: any; record?: Record; - save: ( + save?: ( data: Partial, redirectTo: RedirectionSideEffect, options?: { @@ -158,9 +160,9 @@ export interface FormWithRedirectOwnProps { onFailure?: (error: any) => void; } ) => void; - redirect: RedirectionSideEffect; - saving: boolean; - version: number; + redirect?: RedirectionSideEffect; + saving?: boolean; + version?: number; warnWhenUnsavedChanges?: boolean; sanitizeEmptyValues?: boolean; } @@ -172,7 +174,12 @@ const defaultSubscription = { invalid: true, }; -const FormView = ({ render, warnWhenUnsavedChanges, ...props }) => { +const FormView = ({ + render, + warnWhenUnsavedChanges, + setRedirect, + ...props +}) => { // if record changes (after a getOne success or a refresh), the form must be updated useInitializeFormWithRecord(props.record); useWarnWhenUnsavedChanges(warnWhenUnsavedChanges); @@ -182,7 +189,7 @@ const FormView = ({ render, warnWhenUnsavedChanges, ...props }) => { dispatch(setAutomaticRefresh(props.pristine)); }, [dispatch, props.pristine]); - const { redirect, setRedirect, handleSubmit } = props; + const { redirect, handleSubmit } = props; /** * We want to let developers define the redirection target from inside the form, diff --git a/packages/ra-ui-materialui/src/form/FormInput.js b/packages/ra-ui-materialui/src/form/FormInput.tsx similarity index 79% rename from packages/ra-ui-materialui/src/form/FormInput.js rename to packages/ra-ui-materialui/src/form/FormInput.tsx index 3c48612bc40..647360e98db 100644 --- a/packages/ra-ui-materialui/src/form/FormInput.js +++ b/packages/ra-ui-materialui/src/form/FormInput.tsx @@ -1,9 +1,12 @@ import * as React from 'react'; +import { FC, HtmlHTMLAttributes, ReactElement } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { makeStyles } from '@material-ui/core/styles'; +import { Record } from 'ra-core'; import Labeled from '../input/Labeled'; +import { ClassesOverride } from '../types'; const sanitizeRestProps = ({ basePath, record, ...rest }) => rest; @@ -14,7 +17,7 @@ const useStyles = makeStyles( { name: 'RaFormInput' } ); -const FormInput = props => { +const FormInput: FC = props => { const { input, classes: classesOverride, ...rest } = props; const classes = useStyles(props); return input ? ( @@ -59,11 +62,21 @@ const FormInput = props => { }; FormInput.propTypes = { - className: PropTypes.string, classes: PropTypes.object, - input: PropTypes.object, + // @ts-ignore + input: PropTypes.node, }; +export interface FormInputProps extends HtmlHTMLAttributes { + basePath: string; + classes?: ClassesOverride; + input: ReactElement; + margin?: 'none' | 'normal' | 'dense'; + record: Record; + resource: string; + variant?: 'standard' | 'outlined' | 'filled'; +} + // wat? TypeScript looses the displayName if we don't set it explicitly FormInput.displayName = 'FormInput'; diff --git a/packages/ra-ui-materialui/src/form/FormTab.spec.js b/packages/ra-ui-materialui/src/form/FormTab.spec.tsx similarity index 98% rename from packages/ra-ui-materialui/src/form/FormTab.spec.js rename to packages/ra-ui-materialui/src/form/FormTab.spec.tsx index 3925c2c369c..f03fbf98b0c 100644 --- a/packages/ra-ui-materialui/src/form/FormTab.spec.js +++ b/packages/ra-ui-materialui/src/form/FormTab.spec.tsx @@ -42,7 +42,7 @@ describe('', () => { it('should render a TabbedForm with FormTabs having custom props without warnings', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); - const record = { name: 'foo' }; + const record = { id: 'gazebo', name: 'foo' }; const { container } = renderWithRedux( rest; - const hiddenStyle = { display: 'none' }; -const FormTab = ({ +const FormTab: FC = ({ basePath, className, contentClassName, @@ -46,7 +38,7 @@ const FormTab = ({ className={classnames('form-tab', className)} component={Link} to={{ ...location, pathname: value }} - {...sanitizeRestProps(rest)} + {...rest} /> ); @@ -54,7 +46,7 @@ const FormTab = ({ {React.Children.map( children, - input => + (input: ReactElement) => input && ( ', () => { }); it('should pass submitOnEnter to ', () => { - const handleSubmit = () => {}; - const Toolbar = ({ submitOnEnter }) => ( + const Toolbar = ({ submitOnEnter }: any): any => (

submitOnEnter: {submitOnEnter.toString()}

); const { queryByText, rerender } = renderWithRedux( - } - /> + } /> ); expect(queryByText('submitOnEnter: false')).not.toBeNull(); - rerender( - } - /> - ); + rerender(} />); expect(queryByText('submitOnEnter: true')).not.toBeNull(); }); diff --git a/packages/ra-ui-materialui/src/form/SimpleForm.js b/packages/ra-ui-materialui/src/form/SimpleForm.tsx similarity index 72% rename from packages/ra-ui-materialui/src/form/SimpleForm.js rename to packages/ra-ui-materialui/src/form/SimpleForm.tsx index 46d351cf7e1..740d5e60565 100644 --- a/packages/ra-ui-materialui/src/form/SimpleForm.js +++ b/packages/ra-ui-materialui/src/form/SimpleForm.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; -import { Children } from 'react'; +import { Children, FC, ReactElement, HtmlHTMLAttributes } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { FormWithRedirect } from 'ra-core'; +import { FormWithRedirect, Record, RedirectionSideEffect } from 'ra-core'; +import { FormProps, FormRenderProps } from 'react-final-form'; import FormInput from './FormInput'; import Toolbar from './Toolbar'; @@ -39,11 +40,11 @@ import CardContentInner from '../layout/CardContentInner'; * @prop {ReactElement} toolbar The element displayed at the bottom of the form, containing the SaveButton * @prop {string} variant Apply variant to all inputs. Possible values are 'standard', 'outlined', and 'filled' (default) * @prop {string} margin Apply variant to all inputs. Possible values are 'none', 'normal', and 'dense' (default) - * @prop {boolean} sanitizeEmptyValues Wether or not deleted record attributes should be recreated with a `null` value (default: true) + * @prop {boolean} sanitizeEmptyValues Whether or not deleted record attributes should be recreated with a `null` value (default: true) * * @param {Prop} props */ -const SimpleForm = props => ( +const SimpleForm: FC = props => ( } @@ -52,8 +53,8 @@ const SimpleForm = props => ( SimpleForm.propTypes = { children: PropTypes.node, - defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), // @deprecated initialValues: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + // @ts-ignore record: PropTypes.object, redirect: PropTypes.oneOfType([ PropTypes.string, @@ -70,7 +71,25 @@ SimpleForm.propTypes = { sanitizeEmptyValues: PropTypes.bool, }; -const SimpleFormView = ({ +export interface SimpleFormProps + extends Omit, + Omit, 'onSubmit' | 'children'> { + basePath?: string; + className?: string; + initialValues?: any; + margin?: 'none' | 'normal' | 'dense'; + record?: Record; + redirect?: RedirectionSideEffect; + resource?: string; + sanitizeEmptyValues?: boolean; + submitOnEnter?: boolean; + toolbar?: ReactElement; + undoable?: boolean; + variant?: 'standard' | 'outlined' | 'filled'; + warnWhenUnsavedChanges?: boolean; +} + +const SimpleFormView: FC = ({ basePath, children, className, @@ -96,7 +115,7 @@ const SimpleFormView = ({ {Children.map( children, - input => + (input: ReactElement) => input && ( void; + record?: Record; + redirect?: RedirectionSideEffect; + resource?: string; + save?: () => void; + saving?: boolean; + toolbar?: ReactElement; + undoable?: boolean; + variant?: 'standard' | 'outlined' | 'filled'; + submitOnEnter?: boolean; + __versions?: any; // react-final-form internal prop, missing in their type +} + SimpleFormView.defaultProps = { submitOnEnter: true, toolbar: , }; const sanitizeRestProps = ({ - anyTouched, - array, - asyncBlurFields, - asyncValidate, - asyncValidating, - autofill, - blur, - change, - clearAsyncError, - clearFields, - clearSubmit, - clearSubmitErrors, - destroy, + active, dirty, dirtyFields, dirtyFieldsSinceLastSubmit, dirtySinceLastSubmit, - dispatch, + error, + errors, form, - handleSubmit, hasSubmitErrors, hasValidationErrors, - initialize, - initialized, initialValues, + modified = null, modifiedSinceLastSubmit, - modifiedsincelastsubmit, - pristine, - pure, - redirect, - reset, - resetSection, - save, - setRedirect, - submit, + save = null, submitError, submitErrors, - submitAsSideEffect, submitFailed, submitSucceeded, submitting, - touch, - translate, - triggerSubmit, - undoable, - untouch, + touched = null, valid, - validate, validating, - __versions, + values, + visited = null, + __versions = null, ...props }) => props; diff --git a/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.js b/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx similarity index 63% rename from packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.js rename to packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx index ab740a83d5c..e02305adaeb 100644 --- a/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.js +++ b/packages/ra-ui-materialui/src/form/SimpleFormIterator.spec.tsx @@ -1,12 +1,16 @@ import { cleanup, fireEvent, wait, getByText } from '@testing-library/react'; import * as React from 'react'; import { renderWithRedux } from 'ra-core'; +import { ThemeProvider } from '@material-ui/core'; +import { createMuiTheme } from '@material-ui/core/styles'; import SimpleFormIterator from './SimpleFormIterator'; import TextInput from '../input/TextInput'; import { ArrayInput } from '../input'; import SimpleForm from './SimpleForm'; +const theme = createMuiTheme(); + describe('', () => { // bypass confirm leave form with unsaved changes let confirmSpy; @@ -48,13 +52,20 @@ describe('', () => { it('should not display remove button if disableRemove is truthy', () => { const { queryAllByText } = renderWithRedux( - - - x} disableRemove> - - - - + + + + x} disableRemove> + + + + + ); expect(queryAllByText('ra.action.remove').length).toBe(0); @@ -100,7 +111,9 @@ describe('', () => { ); expect( - inputElements.map(inputElement => ({ email: inputElement.value })) + inputElements.map((inputElement: HTMLInputElement) => ({ + email: inputElement.value, + })) ).toEqual([{ email: '' }, { email: '' }]); expect(queryAllByText('ra.action.remove').length).toBe(2); @@ -115,7 +128,7 @@ describe('', () => { x}> - + @@ -132,9 +145,11 @@ describe('', () => { const inputElements = queryAllByLabelText('CustomLabel'); - expect(inputElements.map(inputElement => inputElement.value)).toEqual([ - '', - ]); + expect( + inputElements.map( + (inputElement: HTMLInputElement) => inputElement.value + ) + ).toEqual(['']); expect(queryAllByText('ra.action.remove').length).toBe(1); }); @@ -148,7 +163,11 @@ describe('', () => { x}> - + @@ -165,9 +184,11 @@ describe('', () => { const inputElements = queryAllByLabelText('CustomLabel'); - expect(inputElements.map(inputElement => inputElement.value)).toEqual([ - '5', - ]); + expect( + inputElements.map( + (inputElement: HTMLInputElement) => inputElement.value + ) + ).toEqual(['5']); expect(queryAllByText('ra.action.remove').length).toBe(1); }); @@ -176,13 +197,15 @@ describe('', () => { const emails = [{ email: 'foo@bar.com' }, { email: 'bar@foo.com' }]; const { queryAllByLabelText } = renderWithRedux( - - - x}> - - - - + + + + x}> + + + + + ); const inputElements = queryAllByLabelText( @@ -190,7 +213,9 @@ describe('', () => { ); expect( - inputElements.map(inputElement => ({ email: inputElement.value })) + inputElements.map((inputElement: HTMLInputElement) => ({ + email: inputElement.value, + })) ).toEqual(emails); const removeFirstButton = getByText( @@ -205,7 +230,7 @@ describe('', () => { ); expect( - inputElements.map(inputElement => ({ + inputElements.map((inputElement: HTMLInputElement) => ({ email: inputElement.value, })) ).toEqual([{ email: 'bar@foo.com' }]); @@ -230,16 +255,20 @@ describe('', () => { it('should not display the default remove button if a custom remove button is passed', () => { const { queryAllByText } = renderWithRedux( - - - x} - removeButton={} - > - - - - + + + + x} + removeButton={} + > + + + + + ); expect(queryAllByText('ra.action.remove').length).toBe(0); @@ -264,16 +293,20 @@ describe('', () => { it('should display the custom remove button', () => { const { getByText } = renderWithRedux( - - - x} - removeButton={} - > - - - - + + + + x} + removeButton={} + > + + + + + ); expect(getByText('Custom Remove Button')).toBeDefined(); @@ -282,18 +315,22 @@ describe('', () => { it('should call the onClick method when the custom add button is clicked', async () => { const onClick = jest.fn(); const { getByText } = renderWithRedux( - - - x} - addButton={ - - } - > - - - - + + + + x} + addButton={ + + } + > + + + + + ); fireEvent.click(getByText('Custom Add Button')); expect(onClick).toHaveBeenCalled(); @@ -302,20 +339,24 @@ describe('', () => { it('should call the onClick method when the custom remove button is clicked', async () => { const onClick = jest.fn(); const { getByText } = renderWithRedux( - - - x} - removeButton={ - - } - > - - - - + + + + x} + removeButton={ + + } + > + + + + + ); fireEvent.click(getByText('Custom Remove Button')); expect(onClick).toHaveBeenCalled(); diff --git a/packages/ra-ui-materialui/src/form/SimpleFormIterator.js b/packages/ra-ui-materialui/src/form/SimpleFormIterator.tsx similarity index 75% rename from packages/ra-ui-materialui/src/form/SimpleFormIterator.js rename to packages/ra-ui-materialui/src/form/SimpleFormIterator.tsx index 6b7c430a33c..c3d87bc3ab4 100644 --- a/packages/ra-ui-materialui/src/form/SimpleFormIterator.js +++ b/packages/ra-ui-materialui/src/form/SimpleFormIterator.tsx @@ -1,5 +1,12 @@ import * as React from 'react'; -import { Children, cloneElement, isValidElement, useRef } from 'react'; +import { + Children, + cloneElement, + isValidElement, + useRef, + ReactElement, + FC, +} from 'react'; import PropTypes from 'prop-types'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import get from 'lodash/get'; @@ -12,7 +19,7 @@ import AddIcon from '@material-ui/icons/AddCircleOutline'; import { useTranslate, ValidationError } from 'ra-core'; import classNames from 'classnames'; -import FormInput from '../form/FormInput'; +import FormInput from './FormInput'; const useStyles = makeStyles( theme => ({ @@ -85,7 +92,7 @@ const DefaultRemoveButton = props => { ); }; -const SimpleFormIterator = props => { +const SimpleFormIterator: FC = props => { const { addButton = , removeButton = , @@ -187,41 +194,47 @@ const SimpleFormIterator = props => { {index + 1}
- {Children.map(children, (input, index2) => - isValidElement(input) ? ( - - ) : null + {Children.map( + children, + (input: ReactElement, index2) => + isValidElement(input) ? ( + + ) : null )}
{!disableRemoveField( diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.spec.js b/packages/ra-ui-materialui/src/form/TabbedForm.spec.tsx similarity index 98% rename from packages/ra-ui-materialui/src/form/TabbedForm.spec.js rename to packages/ra-ui-materialui/src/form/TabbedForm.spec.tsx index 866298afdc8..177b0796542 100644 --- a/packages/ra-ui-materialui/src/form/TabbedForm.spec.js +++ b/packages/ra-ui-materialui/src/form/TabbedForm.spec.tsx @@ -25,7 +25,7 @@ describe('', () => { }); it('should pass submitOnEnter to ', () => { - const Toolbar = ({ submitOnEnter }) => ( + const Toolbar = ({ submitOnEnter }: any) => (

submitOnEnter: {submitOnEnter.toString()}

); diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.js b/packages/ra-ui-materialui/src/form/TabbedForm.tsx similarity index 97% rename from packages/ra-ui-materialui/src/form/TabbedForm.js rename to packages/ra-ui-materialui/src/form/TabbedForm.tsx index 35a8c36566d..6ade98012d1 100644 --- a/packages/ra-ui-materialui/src/form/TabbedForm.js +++ b/packages/ra-ui-materialui/src/form/TabbedForm.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Children, isValidElement } from 'react'; +import { Children, isValidElement, FC, ReactElement } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { Route, useRouteMatch, useLocation } from 'react-router-dom'; @@ -78,7 +78,7 @@ import TabbedFormTabs, { getTabFullPath } from './TabbedFormTabs'; * * @param {Prop} props */ -const TabbedForm = props => ( +const TabbedForm: FC = props => ( } @@ -169,7 +169,7 @@ export const TabbedFormView = props => { See https://github.com/marmelab/react-admin/issues/1866 */} {Children.map( children, - (tab, index) => + (tab: ReactElement, index) => tab && ( { )} > {routeProps => - isValidElement(tab) + isValidElement(tab) ? React.cloneElement(tab, { intent: 'content', resource, @@ -308,7 +308,7 @@ const sanitizeRestProps = ({ }) => props; export const findTabsWithErrors = (children, errors) => { - return Children.toArray(children).reduce((acc, child) => { + return Children.toArray(children).reduce((acc: any[], child) => { if (!isValidElement(child)) { return acc; } diff --git a/packages/ra-ui-materialui/src/form/TabbedFormTabs.spec.js b/packages/ra-ui-materialui/src/form/TabbedFormTabs.spec.tsx similarity index 100% rename from packages/ra-ui-materialui/src/form/TabbedFormTabs.spec.js rename to packages/ra-ui-materialui/src/form/TabbedFormTabs.spec.tsx index c27cc933d94..0f397ff1850 100644 --- a/packages/ra-ui-materialui/src/form/TabbedFormTabs.spec.js +++ b/packages/ra-ui-materialui/src/form/TabbedFormTabs.spec.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { render, cleanup } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; import TabbedFormTabs from './TabbedFormTabs'; import FormTab from './FormTab'; -import { MemoryRouter } from 'react-router-dom'; describe('', () => { afterEach(cleanup); diff --git a/packages/ra-ui-materialui/src/form/TabbedFormTabs.js b/packages/ra-ui-materialui/src/form/TabbedFormTabs.tsx similarity index 78% rename from packages/ra-ui-materialui/src/form/TabbedFormTabs.js rename to packages/ra-ui-materialui/src/form/TabbedFormTabs.tsx index 9e670b1255d..297227f93f6 100644 --- a/packages/ra-ui-materialui/src/form/TabbedFormTabs.js +++ b/packages/ra-ui-materialui/src/form/TabbedFormTabs.tsx @@ -1,15 +1,16 @@ import * as React from 'react'; -import { Children, cloneElement, isValidElement } from 'react'; +import { + Children, + cloneElement, + isValidElement, + FC, + ReactElement, +} from 'react'; import PropTypes from 'prop-types'; -import Tabs from '@material-ui/core/Tabs'; +import Tabs, { TabsProps } from '@material-ui/core/Tabs'; import { useLocation } from 'react-router-dom'; -export const getTabFullPath = (tab, index, baseUrl) => - `${baseUrl}${ - tab.props.path ? `/${tab.props.path}` : index > 0 ? `/${index}` : '' - }`; - -const TabbedFormTabs = ({ +const TabbedFormTabs: FC = ({ children, classes, url, @@ -36,8 +37,8 @@ const TabbedFormTabs = ({ return ( - {Children.map(children, (tab, index) => { - if (!isValidElement(tab)) return null; + {Children.map(children, (tab: ReactElement, index) => { + if (!isValidElement(tab)) return null; // Builds the full tab tab which is the concatenation of the last matched route in the // TabbedShowLayout hierarchy (ex: '/posts/create', '/posts/12', , '/posts/12/show') @@ -66,4 +67,19 @@ TabbedFormTabs.propTypes = { tabsWithErrors: PropTypes.arrayOf(PropTypes.string), }; +export const getTabFullPath = ( + tab: ReactElement, + index: number, + baseUrl: string +): string => + `${baseUrl}${ + tab.props.path ? `/${tab.props.path}` : index > 0 ? `/${index}` : '' + }`; + +export interface TabbedFormTabsProps extends Omit { + classes?: any; + url?: string; + tabsWithErrors?: string[]; +} + export default TabbedFormTabs; diff --git a/packages/ra-ui-materialui/src/form/Toolbar.js b/packages/ra-ui-materialui/src/form/Toolbar.tsx similarity index 96% rename from packages/ra-ui-materialui/src/form/Toolbar.js rename to packages/ra-ui-materialui/src/form/Toolbar.tsx index ae1c25cf228..483be205427 100644 --- a/packages/ra-ui-materialui/src/form/Toolbar.js +++ b/packages/ra-ui-materialui/src/form/Toolbar.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Children, Fragment, isValidElement } from 'react'; +import { Children, Fragment, isValidElement, ReactElement, FC } from 'react'; import PropTypes from 'prop-types'; import MuiToolbar from '@material-ui/core/Toolbar'; import withWidth from '@material-ui/core/withWidth'; @@ -84,7 +84,7 @@ const valueOrDefault = (value, defaultValue) => * @prop {string} width Apply the mobile or the desktop classes depending on the width. Pass "xs" to display the mobile version. * */ -const Toolbar = props => { +const Toolbar: FC = props => { const { alwaysEnableSaveButton, basePath, @@ -134,7 +134,6 @@ const Toolbar = props => { invalid={invalid} redirect={redirect} saving={saving} - pristine={pristine} submitOnEnter={submitOnEnter} /> {record && typeof record.id !== 'undefined' && ( @@ -147,8 +146,8 @@ const Toolbar = props => { )} ) : ( - Children.map(children, button => - button && isValidElement(button) + Children.map(children, (button: ReactElement) => + button && isValidElement(button) ? React.cloneElement(button, { basePath, handleSubmit: valueOrDefault( diff --git a/packages/ra-ui-materialui/src/form/getFormInitialValues.js b/packages/ra-ui-materialui/src/form/getFormInitialValues.ts similarity index 67% rename from packages/ra-ui-materialui/src/form/getFormInitialValues.js rename to packages/ra-ui-materialui/src/form/getFormInitialValues.ts index 86be4438f2b..f2c81909979 100644 --- a/packages/ra-ui-materialui/src/form/getFormInitialValues.js +++ b/packages/ra-ui-materialui/src/form/getFormInitialValues.ts @@ -1,8 +1,10 @@ +import { Record } from 'ra-core'; + export default function getFormInitialValues( - initialValues, - defaultValue, - record -) { + initialValues: any, + defaultValue: DefaultValue, + record: Record +): any { let finalInitialValues = { ...initialValues, ...record, @@ -28,3 +30,9 @@ export default function getFormInitialValues( return finalInitialValues; } + +interface DefaultValueObject { + [key: string]: any; +} +type DefaultValueFunction = (record: Record) => DefaultValueObject; +type DefaultValue = DefaultValueObject | DefaultValueFunction; diff --git a/packages/ra-ui-materialui/src/form/index.js b/packages/ra-ui-materialui/src/form/index.tsx similarity index 100% rename from packages/ra-ui-materialui/src/form/index.js rename to packages/ra-ui-materialui/src/form/index.tsx diff --git a/packages/ra-ui-materialui/src/input/ArrayInput.tsx b/packages/ra-ui-materialui/src/input/ArrayInput.tsx index 441c79f7615..4da37544e74 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import { cloneElement, Children } from 'react'; +import { cloneElement, Children, FC, ReactElement } from 'react'; import PropTypes from 'prop-types'; -import { isRequired, FieldTitle, composeValidators } from 'ra-core'; +import { isRequired, FieldTitle, composeValidators, InputProps } from 'ra-core'; import { useFieldArray } from 'react-final-form-arrays'; import { InputLabel, FormControl } from '@material-ui/core'; @@ -48,7 +48,7 @@ import sanitizeRestProps from './sanitizeRestProps'; * * @see https://github.com/final-form/react-final-form-arrays */ -const ArrayInput = ({ +const ArrayInput: FC = ({ className, defaultValue, label, @@ -99,6 +99,7 @@ const ArrayInput = ({ }; ArrayInput.propTypes = { + // @ts-ignore children: PropTypes.node, className: PropTypes.string, defaultValue: PropTypes.any, @@ -118,4 +119,8 @@ ArrayInput.defaultProps = { options: {}, fullWidth: true, }; + +export interface ArrayInputProps extends InputProps { + children: ReactElement; +} export default ArrayInput;