@applica-software-guru/react-admin 1.0.35 → 1.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +4 -4
- package/dist/AdminContext.d.ts.map +1 -1
- package/dist/components/MainCard.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/ra-forms/LongForm/{DispositionProps.d.ts → types.d.ts} +10 -4
- package/dist/components/ra-forms/LongForm/types.d.ts.map +1 -0
- package/dist/components/ra-forms/LongForm/useFormRootPath.d.ts +3 -3
- package/dist/components/ra-forms/LongForm/useFormRootPath.d.ts.map +1 -1
- package/dist/components/ra-forms/SimpleFormIterator.d.ts +5 -0
- package/dist/components/ra-forms/SimpleFormIterator.d.ts.map +1 -0
- package/dist/components/ra-forms/index.d.ts +2 -1
- package/dist/components/ra-forms/index.d.ts.map +1 -1
- package/dist/components/ra-inputs/AttachmentInput.d.ts +4 -1
- package/dist/components/ra-inputs/AttachmentInput.d.ts.map +1 -1
- package/dist/components/ra-inputs/AutocompleteInput.d.ts +1 -3
- package/dist/components/ra-inputs/AutocompleteInput.d.ts.map +1 -1
- package/dist/components/ra-inputs/LabeledInput.d.ts +2 -1
- package/dist/components/ra-inputs/LabeledInput.d.ts.map +1 -1
- package/dist/components/ra-inputs/SmartTextInput.d.ts.map +1 -1
- package/dist/contexts/ThemeConfigContext.d.ts.map +1 -1
- package/dist/hooks/useAppConfig.d.ts +3 -0
- package/dist/hooks/useAppConfig.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/react-admin.cjs.js +60 -57
- package/dist/react-admin.es.js +12795 -11451
- package/dist/react-admin.umd.js +60 -57
- package/dist/themes/overrides/Autocomplete.d.ts +4 -1
- package/dist/themes/overrides/Autocomplete.d.ts.map +1 -1
- package/package.json +1 -1
- package/playground/src/.prettierrc +8 -0
- package/playground/src/App.js +21 -21
- package/playground/src/components/pages/CustomPage.jsx +4 -4
- package/playground/src/components/pages/index.jsx +2 -2
- package/playground/src/components/ra-forms/DeviceForm.js +7 -14
- package/playground/src/components/ra-forms/UserForm.js +10 -24
- package/playground/src/components/ra-forms/index.js +4 -5
- package/playground/src/components/ra-lists/DeviceList.js +8 -16
- package/playground/src/components/ra-lists/UserList.js +17 -40
- package/playground/src/components/ra-lists/index.js +4 -4
- package/playground/src/contexts/index.js +1 -1
- package/playground/src/{resource → entities}/device.js +6 -6
- package/playground/src/{resource → entities}/i18n-message.js +1 -2
- package/playground/src/entities/index.js +4 -0
- package/playground/src/{resource → entities}/notification.js +0 -2
- package/playground/src/menu.js +18 -9
- package/playground/src/theme.js +10 -2
- package/react-admin.code-workspace +9 -0
- package/src/AdminContext.jsx +3 -5
- package/src/components/ActionsMenu.tsx +91 -0
- package/src/components/MainCard.jsx +29 -32
- package/src/components/index.jsx +21 -33
- package/src/components/ra-buttons/CreateInDialogButton.tsx +261 -0
- package/src/components/ra-custom/ListItem.tsx +147 -0
- package/src/components/ra-custom/index.tsx +2 -0
- package/src/components/ra-fields/AttachmentField.tsx +88 -0
- package/src/components/ra-fields/BaseAttachmentField.tsx +82 -0
- package/src/components/ra-forms/FormHeader.tsx +63 -0
- package/src/components/ra-forms/LongForm/LongForm.tsx +56 -0
- package/src/components/ra-forms/LongForm/LongFormSidebar.tsx +44 -0
- package/src/components/ra-forms/LongForm/{LongFormTab.jsx → LongFormTab.tsx} +47 -46
- package/src/components/ra-forms/LongForm/LongFormTabs.tsx +73 -0
- package/src/components/ra-forms/LongForm/LongFormView.tsx +129 -0
- package/{dist/components/ra-forms/LongForm/index.d.ts → src/components/ra-forms/LongForm/index.tsx} +1 -2
- package/src/components/ra-forms/LongForm/types.ts +15 -0
- package/src/components/ra-forms/LongForm/useFormRootPath.ts +26 -0
- package/src/components/ra-forms/SimpleFormIterator.jsx +14 -0
- package/src/components/ra-forms/index.jsx +2 -1
- package/src/components/ra-inputs/AttachmentInput.jsx +42 -25
- package/src/components/ra-inputs/AutocompleteInput.jsx +10 -5
- package/src/components/ra-inputs/LabeledInput.jsx +27 -32
- package/src/components/ra-inputs/SelectInput.jsx +11 -11
- package/src/components/ra-inputs/SmartTextInput.jsx +22 -26
- package/src/components/ra-inputs/TextInput.jsx +9 -9
- package/src/contexts/AppConfigContext.tsx +67 -0
- package/src/contexts/ThemeConfigContext.jsx +25 -29
- package/src/index.jsx +10 -8
- package/src/themes/overrides/Autocomplete.jsx +4 -1
- package/src/themes/overrides/index.jsx +1 -1
- package/src/utils/index.js +2 -2
- package/src/utils/lang.js +7 -7
- package/src/utils/time.js +7 -7
- package/dist/components/ActionsMenu.d.ts +0 -15
- package/dist/components/ActionsMenu.d.ts.map +0 -1
- package/dist/components/ra-buttons/CreateInDialogButton.d.ts +0 -37
- package/dist/components/ra-buttons/CreateInDialogButton.d.ts.map +0 -1
- package/dist/components/ra-fields/AttachmentField.d.ts +0 -28
- package/dist/components/ra-fields/AttachmentField.d.ts.map +0 -1
- package/dist/components/ra-fields/BaseAttachmentField.d.ts +0 -17
- package/dist/components/ra-fields/BaseAttachmentField.d.ts.map +0 -1
- package/dist/components/ra-forms/FormHeader.d.ts +0 -17
- package/dist/components/ra-forms/FormHeader.d.ts.map +0 -1
- package/dist/components/ra-forms/LongForm/DispositionProps.d.ts.map +0 -1
- package/dist/components/ra-forms/LongForm/LongForm.d.ts +0 -45
- package/dist/components/ra-forms/LongForm/LongForm.d.ts.map +0 -1
- package/dist/components/ra-forms/LongForm/LongFormHeader.d.ts +0 -20
- package/dist/components/ra-forms/LongForm/LongFormHeader.d.ts.map +0 -1
- package/dist/components/ra-forms/LongForm/LongFormTab.d.ts +0 -43
- package/dist/components/ra-forms/LongForm/LongFormTab.d.ts.map +0 -1
- package/dist/components/ra-forms/LongForm/LongFormTabs.d.ts +0 -21
- package/dist/components/ra-forms/LongForm/LongFormTabs.d.ts.map +0 -1
- package/dist/components/ra-forms/LongForm/LongFormView.d.ts +0 -29
- package/dist/components/ra-forms/LongForm/LongFormView.d.ts.map +0 -1
- package/dist/components/ra-forms/LongForm/index.d.ts.map +0 -1
- package/dist/contexts/AppConfigContext.d.ts +0 -22
- package/dist/contexts/AppConfigContext.d.ts.map +0 -1
- package/playground/src/resource/index.js +0 -4
- package/src/components/ActionsMenu.jsx +0 -77
- package/src/components/ra-buttons/CreateInDialogButton.jsx +0 -203
- package/src/components/ra-fields/AttachmentField.jsx +0 -82
- package/src/components/ra-fields/BaseAttachmentField.jsx +0 -72
- package/src/components/ra-forms/FormHeader.jsx +0 -42
- package/src/components/ra-forms/LongForm/DispositionProps.jsx +0 -10
- package/src/components/ra-forms/LongForm/LongForm.jsx +0 -38
- package/src/components/ra-forms/LongForm/LongFormHeader.jsx +0 -24
- package/src/components/ra-forms/LongForm/LongFormTabs.jsx +0 -63
- package/src/components/ra-forms/LongForm/LongFormView.jsx +0 -129
- package/src/components/ra-forms/LongForm/index.jsx +0 -2
- package/src/components/ra-forms/LongForm/useFormRootPath.jsx +0 -22
- package/src/contexts/AppConfigContext.jsx +0 -54
- /package/playground/src/{resource → entities}/user.js +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useRecordContext, useTranslate } from 'ra-core';
|
|
2
|
+
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { Typography } from '@mui/material';
|
|
5
|
+
import { get } from 'lodash';
|
|
6
|
+
import { styled } from '@mui/material/styles';
|
|
7
|
+
|
|
8
|
+
const Root = styled('div', {
|
|
9
|
+
name: 'ApplicaBaseAttachmentField',
|
|
10
|
+
overridesResolver: (props, styles) => styles.root
|
|
11
|
+
})({
|
|
12
|
+
display: 'inline-block'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const StyledList = styled('ul')({
|
|
16
|
+
display: 'inline-block'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type BaseAttachmentFieldProps = {
|
|
20
|
+
src: string;
|
|
21
|
+
title: string | ((record: any) => string) | JSX.Element;
|
|
22
|
+
target: string;
|
|
23
|
+
download: boolean | string;
|
|
24
|
+
ping: string;
|
|
25
|
+
rel: string;
|
|
26
|
+
emptyText: string;
|
|
27
|
+
source: string;
|
|
28
|
+
className: string;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
record: any;
|
|
31
|
+
onClick: (e: any) => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const BaseAttachmentField = (props: BaseAttachmentFieldProps): JSX.Element | any => {
|
|
35
|
+
const { className, emptyText, source, title, ...rest } = props;
|
|
36
|
+
const record = useRecordContext(props);
|
|
37
|
+
const sourceValue = get(record, source);
|
|
38
|
+
const translate = useTranslate();
|
|
39
|
+
|
|
40
|
+
if (!sourceValue) {
|
|
41
|
+
return emptyText ? (
|
|
42
|
+
<Typography component="span" variant="body2" className={className} {...rest}>
|
|
43
|
+
{emptyText && translate(emptyText, { _: emptyText })}
|
|
44
|
+
</Typography>
|
|
45
|
+
) : (
|
|
46
|
+
<Root className={className} {...rest} />
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (Array.isArray(sourceValue)) {
|
|
51
|
+
return (
|
|
52
|
+
<StyledList className={className} {...rest}>
|
|
53
|
+
{sourceValue.map((file, index) => {
|
|
54
|
+
const fileTitleValue = title instanceof String ? get(file, title as string)?.toString() : title;
|
|
55
|
+
return <li key={index}>{fileTitleValue}</li>;
|
|
56
|
+
})}
|
|
57
|
+
</StyledList>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const titleValue = title instanceof String ? get(record, title as string)?.toString() : title;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Root className={className} {...rest}>
|
|
65
|
+
{titleValue}
|
|
66
|
+
</Root>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
BaseAttachmentField.propTypes = {
|
|
71
|
+
className: PropTypes.string,
|
|
72
|
+
emptyText: PropTypes.string,
|
|
73
|
+
source: PropTypes.string.isRequired,
|
|
74
|
+
src: PropTypes.string,
|
|
75
|
+
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
|
76
|
+
target: PropTypes.string,
|
|
77
|
+
download: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
|
78
|
+
ping: PropTypes.string,
|
|
79
|
+
rel: PropTypes.string
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default BaseAttachmentField;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CardHeader, Divider } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
import { Fragment } from 'react';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
import { styled } from '@mui/material/styles';
|
|
6
|
+
import { useTranslate } from 'react-admin';
|
|
7
|
+
|
|
8
|
+
const StyledCardHeader = styled(CardHeader)(({ theme }) => ({
|
|
9
|
+
marginTop: theme.spacing(4),
|
|
10
|
+
marginLeft: 0,
|
|
11
|
+
marginRight: 0,
|
|
12
|
+
paddingLeft: 0,
|
|
13
|
+
paddingRight: 0
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const StyledDivider = styled(Divider)(({ theme }) => ({
|
|
17
|
+
marginLeft: `-${theme.spacing(2.5)}`,
|
|
18
|
+
marginRight: `-${theme.spacing(2.5)}`,
|
|
19
|
+
marginBottom: theme.spacing(2),
|
|
20
|
+
width: `calc(100% + ${theme.spacing(5)})`
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
export type FormHeaderProps = {
|
|
24
|
+
title: string;
|
|
25
|
+
divider?: boolean;
|
|
26
|
+
variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Componente che implementa l'header di una form.
|
|
31
|
+
* Può essere inserito all'interno di un CardForm o di un SimpleForm e mostra un titolo
|
|
32
|
+
* con divider che copre l'intera larghezza della form.
|
|
33
|
+
*
|
|
34
|
+
* @param {FormHeaderProps} props
|
|
35
|
+
* @returns {JSX.Element}
|
|
36
|
+
*/
|
|
37
|
+
const FormHeader = ({ title, divider, variant }: FormHeaderProps): JSX.Element => {
|
|
38
|
+
const translate = useTranslate();
|
|
39
|
+
return (
|
|
40
|
+
<Fragment>
|
|
41
|
+
<StyledCardHeader
|
|
42
|
+
title={translate(title, { _: title })}
|
|
43
|
+
titleTypographyProps={{
|
|
44
|
+
variant
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
{divider && <StyledDivider />}
|
|
48
|
+
</Fragment>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
FormHeader.propTypes = {
|
|
53
|
+
title: PropTypes.string.isRequired,
|
|
54
|
+
divider: PropTypes.bool,
|
|
55
|
+
variant: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
FormHeader.defaultProps = {
|
|
59
|
+
divider: true,
|
|
60
|
+
variant: 'h4'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default FormHeader;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Disposition, DispositionProps } from './types';
|
|
2
|
+
|
|
3
|
+
import { Form } from 'react-admin';
|
|
4
|
+
import LongFormSidebar from './LongFormSidebar';
|
|
5
|
+
import LongFormTab from './LongFormTab';
|
|
6
|
+
import LongFormView from './LongFormView';
|
|
7
|
+
import PropTypes from 'prop-types';
|
|
8
|
+
import useFormRootPath from './useFormRootPath';
|
|
9
|
+
import { useThemeConfig } from '../../../hooks';
|
|
10
|
+
|
|
11
|
+
export type LongFormProps = {
|
|
12
|
+
syncWithLocation?: boolean;
|
|
13
|
+
spacing?: number;
|
|
14
|
+
tabsDisposition?: Disposition;
|
|
15
|
+
contentDisposition?: Disposition;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Consente di disegnare un form di tipo LongForm secondo la documentazione di React-Admin.
|
|
21
|
+
*
|
|
22
|
+
* @link https://marmelab.com/react-admin/LongForm.html
|
|
23
|
+
*
|
|
24
|
+
* @param {LongFormProps}
|
|
25
|
+
* @returns {JSX.Element}
|
|
26
|
+
*/
|
|
27
|
+
const LongForm = ({ spacing: _spacing, ...props }: LongFormProps): JSX.Element => {
|
|
28
|
+
const formRootPathname = useFormRootPath();
|
|
29
|
+
const { spacing: _themeSpacing } = useThemeConfig();
|
|
30
|
+
const spacing = _spacing || _themeSpacing;
|
|
31
|
+
return (
|
|
32
|
+
<Form formRootPathname={formRootPathname} {...props}>
|
|
33
|
+
{/** @ts-ignore */}
|
|
34
|
+
<LongFormView formRootPathname={formRootPathname} {...props} spacing={spacing} />
|
|
35
|
+
</Form>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
LongForm.propTypes = {
|
|
40
|
+
syncWithLocation: PropTypes.bool,
|
|
41
|
+
spacing: PropTypes.number,
|
|
42
|
+
tabsDisposition: DispositionProps,
|
|
43
|
+
contentDisposition: DispositionProps
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
LongForm.defaultProps = {
|
|
47
|
+
syncWithLocation: false,
|
|
48
|
+
spacing: 2,
|
|
49
|
+
tabsDisposition: { xl: 3, lg: 3, md: 4, sm: 4, xs: 12 },
|
|
50
|
+
contentDisposition: { xl: 9, lg: 9, md: 8, sm: 8, xs: 12 }
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
LongForm.Tab = LongFormTab;
|
|
54
|
+
LongForm.Sidebar = LongFormSidebar;
|
|
55
|
+
|
|
56
|
+
export default LongForm;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import { styled } from '@mui/material/styles';
|
|
3
|
+
import { useMediaQuery } from '@mui/material';
|
|
4
|
+
|
|
5
|
+
const StyledSidebar = styled('div')(({ theme }) => ({
|
|
6
|
+
marginBottom: theme.spacing(2)
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
export type LongFormSidebarProps = {
|
|
10
|
+
visibility?: 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
|
11
|
+
position: 'top' | 'bottom';
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Consente di specificare, all'interno di un form di tipo LongForm, una sezione da visualizzare prima o dopo i tab di navigazione.
|
|
17
|
+
* @example
|
|
18
|
+
* <LongForm>
|
|
19
|
+
* <LongForm.Sidebar position="top">Contenuto prima della navigazione</LongForm.Sidebar>
|
|
20
|
+
* <LongForm.Tab>Tab 1</LongForm.Tab>
|
|
21
|
+
* <LongForm.Tab>Tab 2</LongForm.Tab>
|
|
22
|
+
* <LongForm.Sidebar position="bottom">Contenuto dopo la navigazione</LongForm.Sidebar>
|
|
23
|
+
* </LongForm>
|
|
24
|
+
*
|
|
25
|
+
* @param {LongFormSidebarProps}
|
|
26
|
+
* @returns {JSX.Element | null}
|
|
27
|
+
*/
|
|
28
|
+
const LongFormSidebar = ({ children, visibility }: LongFormSidebarProps): JSX.Element | null => {
|
|
29
|
+
const isVisible = useMediaQuery((theme: any) => theme.breakpoints.up(visibility));
|
|
30
|
+
return isVisible ? <StyledSidebar>{children}</StyledSidebar> : null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
LongFormSidebar.propTypes = {
|
|
34
|
+
visibility: PropTypes.oneOf(['xl', 'lg', 'md', 'sm', 'xs']),
|
|
35
|
+
position: PropTypes.oneOf(['top', 'bottom']).isRequired,
|
|
36
|
+
children: PropTypes.node.isRequired
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
LongFormSidebar.defaultProps = {
|
|
40
|
+
position: 'top',
|
|
41
|
+
visibility: 'md'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default LongFormSidebar;
|
|
@@ -1,19 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ListItemIcon,
|
|
5
|
-
ListItemSecondaryAction,
|
|
6
|
-
ListItemText,
|
|
7
|
-
Stack,
|
|
8
|
-
} from '@mui/material'
|
|
9
|
-
import { FormGroupContextProvider, useTranslate } from 'react-admin'
|
|
10
|
-
import React, { useCallback } from 'react'
|
|
1
|
+
import { Avatar, ListItemButton, ListItemIcon, ListItemSecondaryAction, ListItemText, Stack } from '@mui/material';
|
|
2
|
+
import { FormGroupContextProvider, useTranslate } from 'react-admin';
|
|
3
|
+
import React, { useCallback } from 'react';
|
|
11
4
|
|
|
12
|
-
import { Link } from 'react-router-dom'
|
|
13
|
-
import PropTypes from 'prop-types'
|
|
14
|
-
import { useLocation } from 'react-router'
|
|
5
|
+
import { Link } from 'react-router-dom';
|
|
6
|
+
import PropTypes from 'prop-types';
|
|
7
|
+
import { useLocation } from 'react-router';
|
|
15
8
|
|
|
16
|
-
const hiddenStyle = { display: 'none' }
|
|
9
|
+
const hiddenStyle = { display: 'none' };
|
|
10
|
+
|
|
11
|
+
export type LongFormTabProps = {
|
|
12
|
+
intent: 'header' | 'content' | 'sidebar';
|
|
13
|
+
className?: string;
|
|
14
|
+
contentClassName?: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
icon?: React.ReactNode;
|
|
17
|
+
label: string | React.ReactNode;
|
|
18
|
+
value: string | number;
|
|
19
|
+
syncWithLocation?: boolean;
|
|
20
|
+
onChange: (value: string | number) => void;
|
|
21
|
+
selected?: boolean;
|
|
22
|
+
hidden?: boolean;
|
|
23
|
+
unmountOnExit?: boolean;
|
|
24
|
+
children?: React.ReactNode;
|
|
25
|
+
badgeColor?: 'default' | 'error' | 'info' | 'primary' | 'secondary' | 'success' | 'warning';
|
|
26
|
+
badgeContent?: string | number;
|
|
27
|
+
};
|
|
17
28
|
|
|
18
29
|
const LongFormTab = ({
|
|
19
30
|
intent,
|
|
@@ -29,23 +40,19 @@ const LongFormTab = ({
|
|
|
29
40
|
badgeColor,
|
|
30
41
|
badgeContent,
|
|
31
42
|
...props
|
|
32
|
-
}) => {
|
|
33
|
-
const translate = useTranslate()
|
|
34
|
-
const location = useLocation()
|
|
43
|
+
}: LongFormTabProps) => {
|
|
44
|
+
const translate = useTranslate();
|
|
45
|
+
const location = useLocation();
|
|
35
46
|
const propsForLink = {
|
|
36
47
|
component: Link,
|
|
37
|
-
to: { ...location, pathname: value }
|
|
38
|
-
}
|
|
48
|
+
to: { ...location, pathname: value }
|
|
49
|
+
};
|
|
39
50
|
|
|
40
|
-
const tabLabel = React.isValidElement(label) ? label : translate(label, { _: label })
|
|
41
|
-
const handleChange = useCallback(() => onChange(value), [onChange, value])
|
|
51
|
+
const tabLabel = React.isValidElement(label) ? label : translate(label as string, { _: label });
|
|
52
|
+
const handleChange = useCallback(() => onChange(value), [onChange, value]);
|
|
42
53
|
|
|
43
54
|
const renderTab = () => (
|
|
44
|
-
<ListItemButton
|
|
45
|
-
selected={selected}
|
|
46
|
-
{...(syncWithLocation ? propsForLink : {})}
|
|
47
|
-
onClick={handleChange}
|
|
48
|
-
>
|
|
55
|
+
<ListItemButton selected={selected} {...(syncWithLocation ? propsForLink : {})} onClick={handleChange}>
|
|
49
56
|
{icon && <ListItemIcon>{icon}</ListItemIcon>}
|
|
50
57
|
<ListItemText primary={tabLabel} />
|
|
51
58
|
{badgeContent && (
|
|
@@ -55,8 +62,10 @@ const LongFormTab = ({
|
|
|
55
62
|
width: 24,
|
|
56
63
|
height: 24,
|
|
57
64
|
fontSize: 12,
|
|
65
|
+
// @ts-ignore
|
|
58
66
|
backgroundColor: (theme) => theme.palette[badgeColor]?.main,
|
|
59
|
-
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
color: (theme) => theme.palette[badgeColor]?.contrastText
|
|
60
69
|
}}
|
|
61
70
|
>
|
|
62
71
|
{badgeContent}
|
|
@@ -64,14 +73,14 @@ const LongFormTab = ({
|
|
|
64
73
|
</ListItemSecondaryAction>
|
|
65
74
|
)}
|
|
66
75
|
</ListItemButton>
|
|
67
|
-
)
|
|
76
|
+
);
|
|
68
77
|
|
|
69
78
|
const renderContent = () =>
|
|
70
79
|
unmountOnExit && hidden ? null : (
|
|
71
80
|
<FormGroupContextProvider name={value.toString()}>
|
|
72
81
|
<Stack
|
|
73
82
|
alignContent={'stretch'}
|
|
74
|
-
style={hidden ? hiddenStyle :
|
|
83
|
+
style={hidden ? hiddenStyle : {}}
|
|
75
84
|
id={`tabpanel-${value}`}
|
|
76
85
|
aria-labelledby={`tabheader-${value}`}
|
|
77
86
|
// Set undefined instead of false because WAI-ARIA Authoring Practices 1.1
|
|
@@ -82,10 +91,10 @@ const LongFormTab = ({
|
|
|
82
91
|
{children}
|
|
83
92
|
</Stack>
|
|
84
93
|
</FormGroupContextProvider>
|
|
85
|
-
)
|
|
94
|
+
);
|
|
86
95
|
|
|
87
|
-
return intent === 'header' ? renderTab() : renderContent()
|
|
88
|
-
}
|
|
96
|
+
return intent === 'header' ? renderTab() : renderContent();
|
|
97
|
+
};
|
|
89
98
|
|
|
90
99
|
LongFormTab.propTypes = {
|
|
91
100
|
intent: PropTypes.oneOf(['header', 'content', 'sidebar']),
|
|
@@ -101,21 +110,13 @@ LongFormTab.propTypes = {
|
|
|
101
110
|
hidden: PropTypes.bool,
|
|
102
111
|
unmountOnExit: PropTypes.bool,
|
|
103
112
|
children: PropTypes.node,
|
|
104
|
-
badgeColor: PropTypes.oneOf([
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
'info',
|
|
108
|
-
'primary',
|
|
109
|
-
'secondary',
|
|
110
|
-
'success',
|
|
111
|
-
'warning',
|
|
112
|
-
]),
|
|
113
|
-
badgeContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
114
|
-
}
|
|
113
|
+
badgeColor: PropTypes.oneOf(['default', 'error', 'info', 'primary', 'secondary', 'success', 'warning']),
|
|
114
|
+
badgeContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
115
|
+
};
|
|
115
116
|
|
|
116
117
|
LongFormTab.defaultProps = {
|
|
117
118
|
badgeColor: 'default',
|
|
118
|
-
unmountOnExit: false
|
|
119
|
-
}
|
|
119
|
+
unmountOnExit: false
|
|
120
|
+
};
|
|
120
121
|
|
|
121
|
-
export default LongFormTab
|
|
122
|
+
export default LongFormTab;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { matchPath, useLocation } from 'react-router';
|
|
2
|
+
|
|
3
|
+
import { List } from '@mui/material';
|
|
4
|
+
import MainCard from '../../MainCard';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { styled } from '@mui/system';
|
|
8
|
+
|
|
9
|
+
const StyledList = styled(List, {
|
|
10
|
+
name: 'RaLongFormTabs',
|
|
11
|
+
slot: 'Root'
|
|
12
|
+
})(({ theme }) => ({
|
|
13
|
+
p: 0,
|
|
14
|
+
|
|
15
|
+
'& .MuiListItemIcon-root': {
|
|
16
|
+
minWidth: 32,
|
|
17
|
+
color: theme.palette.grey[500]
|
|
18
|
+
},
|
|
19
|
+
'& .MuiListItemButton-root.Mui-selected': {
|
|
20
|
+
borderRight: `2px solid ${theme.palette.primary.main}`,
|
|
21
|
+
marginRight: `-2px`
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
export type LongFormTabsProps = {
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
syncWithLocation?: boolean;
|
|
28
|
+
value: string | number;
|
|
29
|
+
url: string;
|
|
30
|
+
onChange: (value: string | number) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const LongFormTabs = ({ children, syncWithLocation, value, url, onChange }: LongFormTabsProps) => {
|
|
34
|
+
const location = useLocation();
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
<MainCard>
|
|
39
|
+
{/** @ts-ignore */}
|
|
40
|
+
<StyledList component="nav">
|
|
41
|
+
{React.Children.map(children, (tab, index) => {
|
|
42
|
+
if (!React.isValidElement(tab)) return null;
|
|
43
|
+
const tabPath = getTabbedFormTabFullPath(tab, index);
|
|
44
|
+
const selected = syncWithLocation ? !!matchPath(`${url}/${tabPath}`, location.pathname) : index === value;
|
|
45
|
+
|
|
46
|
+
return React.cloneElement(tab, {
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
intent: 'header',
|
|
49
|
+
value: syncWithLocation ? tabPath : index,
|
|
50
|
+
syncWithLocation,
|
|
51
|
+
onChange,
|
|
52
|
+
url,
|
|
53
|
+
selected
|
|
54
|
+
});
|
|
55
|
+
})}
|
|
56
|
+
</StyledList>
|
|
57
|
+
</MainCard>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const getTabbedFormTabFullPath = (tab: any, index: number) =>
|
|
62
|
+
tab.props.path != null ? tab.props.path : index > 0 ? index.toString() : '';
|
|
63
|
+
|
|
64
|
+
LongFormTabs.propTypes = {
|
|
65
|
+
children: PropTypes.node,
|
|
66
|
+
url: PropTypes.string,
|
|
67
|
+
tabsWithErrors: PropTypes.arrayOf(PropTypes.string),
|
|
68
|
+
syncWithLocation: PropTypes.bool,
|
|
69
|
+
onChange: PropTypes.func,
|
|
70
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default LongFormTabs;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Disposition, DispositionProps } from './types';
|
|
2
|
+
import React, { ReactElement, useMemo, useState } from 'react';
|
|
3
|
+
import { Route, Routes, matchPath, useLocation, useResolvedPath } from 'react-router';
|
|
4
|
+
import { getTabbedFormTabFullPath, useResourceContext } from 'react-admin';
|
|
5
|
+
|
|
6
|
+
import { Grid } from '@mui/material';
|
|
7
|
+
import LongFormHeader from './LongFormSidebar';
|
|
8
|
+
import LongFormTab from './LongFormTab';
|
|
9
|
+
import LongFormTabs from './LongFormTabs';
|
|
10
|
+
import PropTypes from 'prop-types';
|
|
11
|
+
import { styled } from '@mui/material/styles';
|
|
12
|
+
|
|
13
|
+
const StyledGrid = styled(Grid, {
|
|
14
|
+
name: 'RaLongFormView',
|
|
15
|
+
slot: 'Root'
|
|
16
|
+
})(({ theme }) => ({
|
|
17
|
+
'& .MuiToolbar-root': {
|
|
18
|
+
marginTop: theme.spacing(2),
|
|
19
|
+
marginLeft: `-${theme.spacing(3)}`,
|
|
20
|
+
marginRight: `-${theme.spacing(3)}`,
|
|
21
|
+
marginBottom: `-${theme.spacing(2)}`,
|
|
22
|
+
borderTop: `1px solid ${theme.palette.divider}`
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const isSidebar = (child: any, position: string) => child && child.type === LongFormHeader && child.props.position === position;
|
|
27
|
+
const isTab = (child: any) => child && child.type === LongFormTab;
|
|
28
|
+
|
|
29
|
+
export type LongFormViewProps = {
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
formRootPathname: string;
|
|
32
|
+
syncWithLocation?: boolean;
|
|
33
|
+
spacing: number;
|
|
34
|
+
tabs: React.ReactElement;
|
|
35
|
+
tabsDisposition: Disposition;
|
|
36
|
+
contentDisposition: Disposition;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const LongFormView = ({
|
|
40
|
+
children,
|
|
41
|
+
formRootPathname,
|
|
42
|
+
syncWithLocation,
|
|
43
|
+
spacing,
|
|
44
|
+
tabs,
|
|
45
|
+
tabsDisposition,
|
|
46
|
+
contentDisposition,
|
|
47
|
+
...props
|
|
48
|
+
}: LongFormViewProps) => {
|
|
49
|
+
const [tabValue, setTabValue] = useState(0);
|
|
50
|
+
const resolvedPath = useResolvedPath('');
|
|
51
|
+
const resource = useResourceContext(props);
|
|
52
|
+
const location = useLocation();
|
|
53
|
+
const topSidebars = useMemo(() => React.Children.toArray(children).find((child) => isSidebar(child, 'top')), [children]);
|
|
54
|
+
const bottomSidebars = useMemo(() => React.Children.toArray(children).find((child) => isSidebar(child, 'bottom')), [children]);
|
|
55
|
+
const tabChildrens = useMemo(() => React.Children.toArray(children).filter((child) => isTab(child)), [children]);
|
|
56
|
+
|
|
57
|
+
const handleTabChange = (value: number) => {
|
|
58
|
+
if (!syncWithLocation) {
|
|
59
|
+
setTabValue(value);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const renderTabs = () =>
|
|
63
|
+
React.cloneElement(
|
|
64
|
+
tabs,
|
|
65
|
+
{
|
|
66
|
+
onChange: handleTabChange,
|
|
67
|
+
syncWithLocation,
|
|
68
|
+
url: formRootPathname,
|
|
69
|
+
value: tabValue,
|
|
70
|
+
intent: 'header'
|
|
71
|
+
},
|
|
72
|
+
tabChildrens
|
|
73
|
+
);
|
|
74
|
+
return (
|
|
75
|
+
<StyledGrid container spacing={spacing * 2}>
|
|
76
|
+
<Grid item {...tabsDisposition}>
|
|
77
|
+
{topSidebars}
|
|
78
|
+
{syncWithLocation ? (
|
|
79
|
+
<Routes>
|
|
80
|
+
<Route path="/*" element={renderTabs()} />
|
|
81
|
+
</Routes>
|
|
82
|
+
) : (
|
|
83
|
+
renderTabs()
|
|
84
|
+
)}
|
|
85
|
+
{bottomSidebars}
|
|
86
|
+
</Grid>
|
|
87
|
+
<Grid item {...contentDisposition}>
|
|
88
|
+
{/* All tabs are rendered (not only the one in focus), to allow validation
|
|
89
|
+
on tabs not in focus. The tabs receive a `hidden` property, which they'll
|
|
90
|
+
use to hide the tab using CSS if it's not the one in focus.
|
|
91
|
+
See https://github.com/marmelab/react-admin/issues/1866 */}
|
|
92
|
+
{React.Children.map(tabChildrens, (tab: any, index: number) => {
|
|
93
|
+
if (!tab) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const tabPath = getTabbedFormTabFullPath(tab, index);
|
|
97
|
+
const hidden = syncWithLocation ? !matchPath(`${resolvedPath.pathname}/${tabPath}`, location.pathname) : tabValue !== index;
|
|
98
|
+
|
|
99
|
+
return React.isValidElement(tab)
|
|
100
|
+
? React.cloneElement(tab, {
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
intent: 'content',
|
|
103
|
+
resource,
|
|
104
|
+
hidden,
|
|
105
|
+
value: syncWithLocation ? tabPath : index
|
|
106
|
+
})
|
|
107
|
+
: null;
|
|
108
|
+
})}
|
|
109
|
+
</Grid>
|
|
110
|
+
</StyledGrid>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
LongFormView.propTypes = {
|
|
115
|
+
children: PropTypes.node,
|
|
116
|
+
spacing: PropTypes.number,
|
|
117
|
+
syncWithLocation: PropTypes.bool,
|
|
118
|
+
tabs: PropTypes.element,
|
|
119
|
+
formRootPathname: PropTypes.string,
|
|
120
|
+
tabsDisposition: DispositionProps,
|
|
121
|
+
contentDisposition: DispositionProps
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
LongFormView.defaultProps = {
|
|
125
|
+
// @ts-ignore
|
|
126
|
+
tabs: <LongFormTabs />
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default LongFormView;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
export type Disposition = {
|
|
3
|
+
xl?: number;
|
|
4
|
+
lg?: number;
|
|
5
|
+
md?: number;
|
|
6
|
+
sm?: number;
|
|
7
|
+
xs?: number;
|
|
8
|
+
};
|
|
9
|
+
export const DispositionProps = PropTypes.shape({
|
|
10
|
+
xl: PropTypes.number,
|
|
11
|
+
lg: PropTypes.number,
|
|
12
|
+
md: PropTypes.number,
|
|
13
|
+
sm: PropTypes.number,
|
|
14
|
+
xs: PropTypes.number
|
|
15
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { matchPath, useLocation } from 'react-router-dom'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Restituisce il path di base del form in base ad una serie di specifici match di path che è possibile incontrare.
|
|
5
|
+
*/
|
|
6
|
+
const useFormRootPath = (): string => {
|
|
7
|
+
const location = useLocation()
|
|
8
|
+
// I possibili match sono ordinati per priorità (è fondamentale).
|
|
9
|
+
const allPossibleMatches = [
|
|
10
|
+
'entities/:resource/create/*',
|
|
11
|
+
':resource/create/*',
|
|
12
|
+
'entities/:resource/:id/*',
|
|
13
|
+
':resource/:id/*'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
for (const possibleMatch of allPossibleMatches) {
|
|
17
|
+
const match = matchPath(possibleMatch, location.pathname)
|
|
18
|
+
if (match) {
|
|
19
|
+
return match.pathnameBase
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return ''
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default useFormRootPath
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SimpleFormIterator as RaSimpleFormIterator } from 'ra-ui-materialui'
|
|
2
|
+
import { styled } from '@mui/material/styles'
|
|
3
|
+
|
|
4
|
+
export const SimpleFormIterator = styled(RaSimpleFormIterator, {
|
|
5
|
+
name: 'ApplicaSimpleFormIterator',
|
|
6
|
+
root: 'root'
|
|
7
|
+
})(({ theme }) => ({
|
|
8
|
+
'& .RaSimpleFormIterator-line': {
|
|
9
|
+
paddingTop: theme.spacing(1),
|
|
10
|
+
paddingBottom: theme.spacing(0.5),
|
|
11
|
+
}
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
export default SimpleFormIterator
|
|
@@ -3,6 +3,7 @@ import Edit from './Edit'
|
|
|
3
3
|
import FormHeader from './FormHeader'
|
|
4
4
|
import LongForm from './LongForm'
|
|
5
5
|
import SimpleForm from './SimpleForm'
|
|
6
|
+
import SimpleFormIterator from './SimpleFormIterator'
|
|
6
7
|
import TabbedForm from './TabbedForm'
|
|
7
8
|
import Toolbar from './Toolbar'
|
|
8
|
-
export { CardForm, Edit, FormHeader, LongForm, SimpleForm, TabbedForm, Toolbar }
|
|
9
|
+
export { CardForm, Edit, FormHeader, LongForm, SimpleForm, SimpleFormIterator, TabbedForm, Toolbar }
|