@applica-software-guru/react-admin 1.0.36 → 1.0.38

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.
Files changed (111) hide show
  1. package/.prettierrc +4 -4
  2. package/dist/components/MainCard.d.ts.map +1 -1
  3. package/dist/components/index.d.ts +1 -0
  4. package/dist/components/index.d.ts.map +1 -1
  5. package/dist/components/ra-forms/Edit.d.ts.map +1 -1
  6. package/dist/components/ra-forms/LongForm/{DispositionProps.d.ts → types.d.ts} +10 -4
  7. package/dist/components/ra-forms/LongForm/types.d.ts.map +1 -0
  8. package/dist/components/ra-forms/LongForm/useFormRootPath.d.ts +3 -3
  9. package/dist/components/ra-forms/LongForm/useFormRootPath.d.ts.map +1 -1
  10. package/dist/components/ra-inputs/AttachmentInput.d.ts +4 -1
  11. package/dist/components/ra-inputs/AttachmentInput.d.ts.map +1 -1
  12. package/dist/components/ra-inputs/LabeledInput.d.ts +2 -1
  13. package/dist/components/ra-inputs/LabeledInput.d.ts.map +1 -1
  14. package/dist/components/ra-inputs/SmartTextInput.d.ts.map +1 -1
  15. package/dist/contexts/ThemeConfigContext.d.ts.map +1 -1
  16. package/dist/hooks/useAppConfig.d.ts +3 -0
  17. package/dist/hooks/useAppConfig.d.ts.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/react-admin.cjs.js +49 -49
  20. package/dist/react-admin.es.js +7942 -7640
  21. package/dist/react-admin.umd.js +50 -50
  22. package/package.json +3 -2
  23. package/playground/src/.prettierrc +8 -0
  24. package/playground/src/App.js +21 -21
  25. package/playground/src/components/pages/CustomPage.jsx +4 -4
  26. package/playground/src/components/pages/index.jsx +2 -2
  27. package/playground/src/components/ra-forms/DeviceForm.js +7 -14
  28. package/playground/src/components/ra-forms/UserForm.js +10 -24
  29. package/playground/src/components/ra-forms/index.js +4 -5
  30. package/playground/src/components/ra-lists/DeviceList.js +8 -16
  31. package/playground/src/components/ra-lists/UserList.js +17 -40
  32. package/playground/src/components/ra-lists/index.js +4 -4
  33. package/playground/src/contexts/index.js +1 -1
  34. package/playground/src/{resource → entities}/device.js +6 -6
  35. package/playground/src/{resource → entities}/i18n-message.js +1 -2
  36. package/playground/src/entities/index.js +4 -0
  37. package/playground/src/{resource → entities}/notification.js +0 -2
  38. package/playground/src/menu.js +18 -9
  39. package/playground/src/theme.js +10 -2
  40. package/react-admin.code-workspace +9 -0
  41. package/src/components/ActionsMenu.tsx +91 -0
  42. package/src/components/MainCard.jsx +29 -32
  43. package/src/components/index.jsx +21 -33
  44. package/src/components/ra-buttons/CreateInDialogButton.tsx +261 -0
  45. package/src/components/ra-custom/ListItem.tsx +147 -0
  46. package/src/components/ra-custom/index.tsx +2 -0
  47. package/src/components/ra-fields/AttachmentField.tsx +88 -0
  48. package/src/components/ra-fields/BaseAttachmentField.tsx +82 -0
  49. package/src/components/ra-forms/Edit.jsx +12 -6
  50. package/src/components/ra-forms/FormHeader.tsx +63 -0
  51. package/src/components/ra-forms/LongForm/LongForm.tsx +59 -0
  52. package/src/components/ra-forms/LongForm/LongFormSidebar.tsx +44 -0
  53. package/src/components/ra-forms/LongForm/{LongFormTab.jsx → LongFormTab.tsx} +47 -46
  54. package/src/components/ra-forms/LongForm/LongFormTabs.tsx +72 -0
  55. package/src/components/ra-forms/LongForm/LongFormView.tsx +152 -0
  56. package/{dist/components/ra-forms/LongForm/index.d.ts → src/components/ra-forms/LongForm/index.tsx} +1 -2
  57. package/src/components/ra-forms/LongForm/types.ts +15 -0
  58. package/src/components/ra-forms/LongForm/useFormRootPath.ts +26 -0
  59. package/src/components/ra-forms/{SimpleForm.jsx → SimpleForm.tsx} +38 -27
  60. package/src/components/ra-inputs/AttachmentInput.jsx +42 -25
  61. package/src/components/ra-inputs/LabeledInput.jsx +27 -32
  62. package/src/components/ra-inputs/SelectInput.jsx +11 -11
  63. package/src/components/ra-inputs/SmartTextInput.jsx +22 -26
  64. package/src/components/ra-inputs/TextInput.jsx +9 -9
  65. package/src/contexts/AppConfigContext.tsx +67 -0
  66. package/src/contexts/ThemeConfigContext.jsx +25 -29
  67. package/src/index.jsx +13 -7
  68. package/src/utils/index.js +2 -2
  69. package/src/utils/lang.js +7 -7
  70. package/src/utils/time.js +7 -7
  71. package/dist/components/ActionsMenu.d.ts +0 -15
  72. package/dist/components/ActionsMenu.d.ts.map +0 -1
  73. package/dist/components/ra-buttons/CreateInDialogButton.d.ts +0 -37
  74. package/dist/components/ra-buttons/CreateInDialogButton.d.ts.map +0 -1
  75. package/dist/components/ra-fields/AttachmentField.d.ts +0 -28
  76. package/dist/components/ra-fields/AttachmentField.d.ts.map +0 -1
  77. package/dist/components/ra-fields/BaseAttachmentField.d.ts +0 -17
  78. package/dist/components/ra-fields/BaseAttachmentField.d.ts.map +0 -1
  79. package/dist/components/ra-forms/FormHeader.d.ts +0 -17
  80. package/dist/components/ra-forms/FormHeader.d.ts.map +0 -1
  81. package/dist/components/ra-forms/LongForm/DispositionProps.d.ts.map +0 -1
  82. package/dist/components/ra-forms/LongForm/LongForm.d.ts +0 -45
  83. package/dist/components/ra-forms/LongForm/LongForm.d.ts.map +0 -1
  84. package/dist/components/ra-forms/LongForm/LongFormHeader.d.ts +0 -20
  85. package/dist/components/ra-forms/LongForm/LongFormHeader.d.ts.map +0 -1
  86. package/dist/components/ra-forms/LongForm/LongFormTab.d.ts +0 -43
  87. package/dist/components/ra-forms/LongForm/LongFormTab.d.ts.map +0 -1
  88. package/dist/components/ra-forms/LongForm/LongFormTabs.d.ts +0 -21
  89. package/dist/components/ra-forms/LongForm/LongFormTabs.d.ts.map +0 -1
  90. package/dist/components/ra-forms/LongForm/LongFormView.d.ts +0 -29
  91. package/dist/components/ra-forms/LongForm/LongFormView.d.ts.map +0 -1
  92. package/dist/components/ra-forms/LongForm/index.d.ts.map +0 -1
  93. package/dist/components/ra-forms/SimpleForm.d.ts +0 -26
  94. package/dist/components/ra-forms/SimpleForm.d.ts.map +0 -1
  95. package/dist/contexts/AppConfigContext.d.ts +0 -22
  96. package/dist/contexts/AppConfigContext.d.ts.map +0 -1
  97. package/playground/src/resource/index.js +0 -4
  98. package/src/components/ActionsMenu.jsx +0 -77
  99. package/src/components/ra-buttons/CreateInDialogButton.jsx +0 -203
  100. package/src/components/ra-fields/AttachmentField.jsx +0 -82
  101. package/src/components/ra-fields/BaseAttachmentField.jsx +0 -72
  102. package/src/components/ra-forms/FormHeader.jsx +0 -42
  103. package/src/components/ra-forms/LongForm/DispositionProps.jsx +0 -10
  104. package/src/components/ra-forms/LongForm/LongForm.jsx +0 -38
  105. package/src/components/ra-forms/LongForm/LongFormHeader.jsx +0 -24
  106. package/src/components/ra-forms/LongForm/LongFormTabs.jsx +0 -63
  107. package/src/components/ra-forms/LongForm/LongFormView.jsx +0 -129
  108. package/src/components/ra-forms/LongForm/index.jsx +0 -2
  109. package/src/components/ra-forms/LongForm/useFormRootPath.jsx +0 -22
  110. package/src/contexts/AppConfigContext.jsx +0 -54
  111. /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;
@@ -1,13 +1,19 @@
1
- import { Edit as RaEdit } from 'react-admin'
2
- import { styled } from '@mui/material/styles'
1
+ import { Edit as RaEdit } from 'react-admin';
2
+ import { styled } from '@mui/material/styles';
3
3
 
4
4
  const StyledEdit = styled(RaEdit, {
5
5
  name: 'RaApplicaEdit',
6
- slot: 'root',
6
+ slot: 'root'
7
7
  })(({ theme }) => ({
8
8
  '& .RaEdi-card, & > div > div > form': {
9
- backgroundColor: theme.palette.background.default,
9
+ backgroundColor: theme.palette.background.default
10
10
  },
11
- }))
11
+ '& .RaEdit-main>.MuiPaper-root:first-child': {
12
+ // Ci ho messo 4 ore per scrivere questa riga di codice e risolvere un problema con react-sticky-box ed i Long Form.
13
+ // L'overflow è di default 'hidden', a noi serve visibile per consentire a react-sticky-box di funzionare e gestire la visiblità con ancore
14
+ // di tutti gli elementi presenti all'interno della pagina.
15
+ overflow: 'visible'
16
+ }
17
+ }));
12
18
 
13
- export default StyledEdit
19
+ export default StyledEdit;
@@ -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,59 @@
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
+ sticky?: boolean;
18
+ };
19
+
20
+ /**
21
+ * Consente di disegnare un form di tipo LongForm secondo la documentazione di React-Admin.
22
+ *
23
+ * @link https://marmelab.com/react-admin/LongForm.html
24
+ *
25
+ * @param {LongFormProps}
26
+ * @returns {JSX.Element}
27
+ */
28
+ const LongForm = ({ spacing: _spacing, ...props }: LongFormProps): JSX.Element => {
29
+ const formRootPathname = useFormRootPath();
30
+ const { spacing: _themeSpacing } = useThemeConfig();
31
+ const spacing = _spacing || _themeSpacing;
32
+ return (
33
+ <Form formRootPathname={formRootPathname} {...props}>
34
+ {/** @ts-ignore */}
35
+ <LongFormView formRootPathname={formRootPathname} {...props} spacing={spacing} />
36
+ </Form>
37
+ );
38
+ };
39
+
40
+ LongForm.propTypes = {
41
+ syncWithLocation: PropTypes.bool,
42
+ spacing: PropTypes.number,
43
+ tabsDisposition: DispositionProps,
44
+ contentDisposition: DispositionProps,
45
+ sticky: PropTypes.bool
46
+ };
47
+
48
+ LongForm.defaultProps = {
49
+ syncWithLocation: false,
50
+ sticky: true,
51
+ spacing: 2,
52
+ tabsDisposition: { xl: 3, lg: 3, md: 4, sm: 4, xs: 12 },
53
+ contentDisposition: { xl: 9, lg: 9, md: 8, sm: 8, xs: 12 }
54
+ };
55
+
56
+ LongForm.Tab = LongFormTab;
57
+ LongForm.Sidebar = LongFormSidebar;
58
+
59
+ 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
- Avatar,
3
- ListItemButton,
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
- color: (theme) => theme.palette[badgeColor]?.contrastText,
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 : null}
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
- 'default',
106
- 'error',
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,72 @@
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
+ '& .MuiListItemIcon-root': {
15
+ minWidth: 32,
16
+ color: theme.palette.grey[500]
17
+ },
18
+ '& .MuiListItemButton-root.Mui-selected': {
19
+ borderRight: `2px solid ${theme.palette.primary.main}`,
20
+ marginRight: `-2px`
21
+ }
22
+ }));
23
+
24
+ export type LongFormTabsProps = {
25
+ children: React.ReactNode;
26
+ syncWithLocation?: boolean;
27
+ value: string | number;
28
+ url: string;
29
+ onChange: (value: string | number) => void;
30
+ };
31
+
32
+ const LongFormTabs = ({ children, syncWithLocation, value, url, onChange }: LongFormTabsProps) => {
33
+ const location = useLocation();
34
+
35
+ return (
36
+ // @ts-ignore
37
+ <MainCard>
38
+ {/** @ts-ignore */}
39
+ <StyledList component="nav">
40
+ {React.Children.map(children, (tab, index) => {
41
+ if (!React.isValidElement(tab)) return null;
42
+ const tabPath = getTabbedFormTabFullPath(tab, index);
43
+ const selected = syncWithLocation ? !!matchPath(`${url}/${tabPath}`, location.pathname) : index === value;
44
+
45
+ return React.cloneElement(tab, {
46
+ // @ts-ignore
47
+ intent: 'header',
48
+ value: syncWithLocation ? tabPath : index,
49
+ syncWithLocation,
50
+ onChange,
51
+ url,
52
+ selected
53
+ });
54
+ })}
55
+ </StyledList>
56
+ </MainCard>
57
+ );
58
+ };
59
+
60
+ export const getTabbedFormTabFullPath = (tab: any, index: number) =>
61
+ tab.props.path != null ? tab.props.path : index > 0 ? index.toString() : '';
62
+
63
+ LongFormTabs.propTypes = {
64
+ children: PropTypes.node,
65
+ url: PropTypes.string,
66
+ tabsWithErrors: PropTypes.arrayOf(PropTypes.string),
67
+ syncWithLocation: PropTypes.bool,
68
+ onChange: PropTypes.func,
69
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
70
+ };
71
+
72
+ export default LongFormTabs;
@@ -0,0 +1,152 @@
1
+ import { Disposition, DispositionProps } from './types';
2
+ import React, { Fragment, 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 StickyBox from 'react-sticky-box';
12
+ import { styled } from '@mui/material/styles';
13
+
14
+ const StyledGrid = styled(Grid, {
15
+ name: 'ApplicaLongFormView',
16
+ slot: 'Root'
17
+ })(({ theme }) => ({
18
+ '& .MuiToolbar-root': {
19
+ marginTop: theme.spacing(2),
20
+ marginLeft: `-${theme.spacing(2)}`,
21
+ marginRight: `-${theme.spacing(2.5)}`,
22
+ marginBottom: `-${theme.spacing(2)}`,
23
+ borderTop: `1px solid ${theme.palette.divider}`
24
+ },
25
+ '& form > .MuiToolbar-root': {
26
+ // È molto importante: questa regola serve per poter inserire form, all'interno di altri form, evitando
27
+ // che la toolbar del form interno recepisca gli stili della regola precedente (che genera un padding strano).
28
+ margin: 0
29
+ }
30
+ }));
31
+
32
+ const isSidebar = (child: any, position: string) => child && child.type === LongFormHeader && child.props.position === position;
33
+ const isTab = (child: any) => child && child.type === LongFormTab;
34
+
35
+ export type LongFormViewProps = {
36
+ children: React.ReactNode;
37
+ formRootPathname: string;
38
+ syncWithLocation?: boolean;
39
+ spacing: number;
40
+ tabs: React.ReactElement;
41
+ tabsDisposition: Disposition;
42
+ contentDisposition: Disposition;
43
+ sticky?: boolean;
44
+ };
45
+
46
+ const LongFormView = ({
47
+ children,
48
+ formRootPathname,
49
+ syncWithLocation,
50
+ spacing,
51
+ tabs,
52
+ tabsDisposition,
53
+ contentDisposition,
54
+ sticky,
55
+ ...props
56
+ }: LongFormViewProps) => {
57
+ const [tabValue, setTabValue] = useState(0);
58
+ const resolvedPath = useResolvedPath('');
59
+ const resource = useResourceContext(props);
60
+ const location = useLocation();
61
+ const topSidebars = useMemo(() => React.Children.toArray(children).find((child) => isSidebar(child, 'top')), [children]);
62
+ const bottomSidebars = useMemo(() => React.Children.toArray(children).find((child) => isSidebar(child, 'bottom')), [children]);
63
+ const tabChildrens = useMemo(() => React.Children.toArray(children).filter((child) => isTab(child)), [children]);
64
+
65
+ const handleTabChange = (value: number) => {
66
+ if (!syncWithLocation) {
67
+ setTabValue(value);
68
+ }
69
+ };
70
+ const renderTabs = () =>
71
+ React.cloneElement(
72
+ tabs,
73
+ {
74
+ onChange: handleTabChange,
75
+ syncWithLocation,
76
+ url: formRootPathname,
77
+ value: tabValue,
78
+ intent: 'header'
79
+ },
80
+ tabChildrens
81
+ );
82
+ const sidebarContent = useMemo(
83
+ () => (
84
+ <Fragment>
85
+ {topSidebars}
86
+ {syncWithLocation ? (
87
+ <Routes>
88
+ <Route path="/*" element={renderTabs()} />
89
+ </Routes>
90
+ ) : (
91
+ renderTabs()
92
+ )}
93
+ {bottomSidebars}
94
+ </Fragment>
95
+ ),
96
+ [topSidebars, syncWithLocation, renderTabs, bottomSidebars]
97
+ );
98
+ return (
99
+ <StyledGrid container spacing={spacing * 2}>
100
+ <Grid item {...tabsDisposition}>
101
+ {sticky ? (
102
+ <StickyBox offsetTop={74} offsetBottom={spacing * 2}>
103
+ {sidebarContent}
104
+ </StickyBox>
105
+ ) : (
106
+ sidebarContent
107
+ )}
108
+ </Grid>
109
+
110
+ <Grid item {...contentDisposition}>
111
+ {/* All tabs are rendered (not only the one in focus), to allow validation
112
+ on tabs not in focus. The tabs receive a `hidden` property, which they'll
113
+ use to hide the tab using CSS if it's not the one in focus.
114
+ See https://github.com/marmelab/react-admin/issues/1866 */}
115
+ {React.Children.map(tabChildrens, (tab: any, index: number) => {
116
+ if (!tab) {
117
+ return null;
118
+ }
119
+ const tabPath = getTabbedFormTabFullPath(tab, index);
120
+ const hidden = syncWithLocation ? !matchPath(`${resolvedPath.pathname}/${tabPath}`, location.pathname) : tabValue !== index;
121
+
122
+ return React.isValidElement(tab)
123
+ ? React.cloneElement(tab, {
124
+ // @ts-ignore
125
+ intent: 'content',
126
+ resource,
127
+ hidden,
128
+ value: syncWithLocation ? tabPath : index
129
+ })
130
+ : null;
131
+ })}
132
+ </Grid>
133
+ </StyledGrid>
134
+ );
135
+ };
136
+
137
+ LongFormView.propTypes = {
138
+ children: PropTypes.node,
139
+ spacing: PropTypes.number,
140
+ syncWithLocation: PropTypes.bool,
141
+ tabs: PropTypes.element,
142
+ formRootPathname: PropTypes.string,
143
+ tabsDisposition: DispositionProps,
144
+ contentDisposition: DispositionProps
145
+ };
146
+
147
+ LongFormView.defaultProps = {
148
+ // @ts-ignore
149
+ tabs: <LongFormTabs />
150
+ };
151
+
152
+ export default LongFormView;
@@ -1,3 +1,2 @@
1
- export default LongForm;
2
1
  import LongForm from './LongForm';
3
- //# sourceMappingURL=index.d.ts.map
2
+ export default LongForm;
@@ -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
+ });