@applica-software-guru/react-admin 1.1.113 → 1.2.115

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 (71) hide show
  1. package/bitbucket-pipelines.yml +14 -15
  2. package/dist/components/ra-forms/LongForm/BaseForm.d.ts +8 -0
  3. package/dist/components/ra-forms/LongForm/BaseForm.d.ts.map +1 -0
  4. package/dist/components/ra-forms/LongForm/Content.d.ts +8 -0
  5. package/dist/components/ra-forms/LongForm/Content.d.ts.map +1 -0
  6. package/dist/components/ra-forms/LongForm/Form.d.ts +11 -0
  7. package/dist/components/ra-forms/LongForm/Form.d.ts.map +1 -0
  8. package/dist/components/ra-forms/LongForm/NavMenu.d.ts +19 -0
  9. package/dist/components/ra-forms/LongForm/NavMenu.d.ts.map +1 -0
  10. package/dist/components/ra-forms/LongForm/Provider.d.ts +15 -0
  11. package/dist/components/ra-forms/LongForm/Provider.d.ts.map +1 -0
  12. package/dist/components/ra-forms/LongForm/Sidebar.d.ts +23 -0
  13. package/dist/components/ra-forms/LongForm/Sidebar.d.ts.map +1 -0
  14. package/dist/components/ra-forms/LongForm/Tab.d.ts +11 -0
  15. package/dist/components/ra-forms/LongForm/Tab.d.ts.map +1 -0
  16. package/dist/components/ra-forms/LongForm/hooks.d.ts +5 -0
  17. package/dist/components/ra-forms/LongForm/hooks.d.ts.map +1 -0
  18. package/dist/components/ra-forms/LongForm/index.d.ts +30 -2
  19. package/dist/components/ra-forms/LongForm/index.d.ts.map +1 -1
  20. package/dist/components/ra-forms/LongForm/types.d.ts +12 -9
  21. package/dist/components/ra-forms/LongForm/types.d.ts.map +1 -1
  22. package/dist/components/ra-forms/LongForm/utils.d.ts +8 -0
  23. package/dist/components/ra-forms/LongForm/utils.d.ts.map +1 -0
  24. package/dist/components/ra-forms/TableForm/TableFormIterator.d.ts +2 -0
  25. package/dist/components/ra-forms/TableForm/TableFormIterator.d.ts.map +1 -1
  26. package/dist/react-admin.cjs.js +65 -65
  27. package/dist/react-admin.cjs.js.map +1 -1
  28. package/dist/react-admin.es.js +9206 -9131
  29. package/dist/react-admin.es.js.map +1 -1
  30. package/dist/react-admin.umd.js +65 -65
  31. package/dist/react-admin.umd.js.map +1 -1
  32. package/dist/themes/overrides/ListItemText.d.ts +12 -0
  33. package/dist/themes/overrides/ListItemText.d.ts.map +1 -0
  34. package/dist/themes/overrides/index.d.ts.map +1 -1
  35. package/dist/types.d.ts +4 -0
  36. package/dist/types.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/components/ra-forms/LongForm/BaseForm.tsx +54 -0
  39. package/src/components/ra-forms/LongForm/Content.tsx +19 -0
  40. package/src/components/ra-forms/LongForm/Form.tsx +37 -0
  41. package/src/components/ra-forms/LongForm/NavMenu.tsx +128 -0
  42. package/src/components/ra-forms/LongForm/Provider.tsx +196 -0
  43. package/src/components/ra-forms/LongForm/Sidebar.tsx +71 -0
  44. package/src/components/ra-forms/LongForm/Tab.tsx +67 -0
  45. package/src/components/ra-forms/LongForm/hooks.tsx +25 -0
  46. package/src/components/ra-forms/LongForm/index.ts +52 -0
  47. package/src/components/ra-forms/LongForm/types.ts +13 -9
  48. package/src/components/ra-forms/LongForm/utils.ts +22 -0
  49. package/src/components/ra-forms/TableForm/TableFormIterator.tsx +32 -11
  50. package/src/themes/overrides/ListItemText.jsx +15 -0
  51. package/src/themes/overrides/index.jsx +2 -0
  52. package/src/types.ts +5 -0
  53. package/dist/components/ra-forms/LongForm/LongForm.d.ts +0 -98
  54. package/dist/components/ra-forms/LongForm/LongForm.d.ts.map +0 -1
  55. package/dist/components/ra-forms/LongForm/LongFormSidebar.d.ts +0 -34
  56. package/dist/components/ra-forms/LongForm/LongFormSidebar.d.ts.map +0 -1
  57. package/dist/components/ra-forms/LongForm/LongFormTab.d.ts +0 -45
  58. package/dist/components/ra-forms/LongForm/LongFormTab.d.ts.map +0 -1
  59. package/dist/components/ra-forms/LongForm/LongFormTabs.d.ts +0 -23
  60. package/dist/components/ra-forms/LongForm/LongFormTabs.d.ts.map +0 -1
  61. package/dist/components/ra-forms/LongForm/LongFormView.d.ts +0 -42
  62. package/dist/components/ra-forms/LongForm/LongFormView.d.ts.map +0 -1
  63. package/dist/components/ra-forms/LongForm/useFormRootPath.d.ts +0 -6
  64. package/dist/components/ra-forms/LongForm/useFormRootPath.d.ts.map +0 -1
  65. package/src/components/ra-forms/LongForm/LongForm.tsx +0 -59
  66. package/src/components/ra-forms/LongForm/LongFormSidebar.tsx +0 -44
  67. package/src/components/ra-forms/LongForm/LongFormTab.tsx +0 -122
  68. package/src/components/ra-forms/LongForm/LongFormTabs.tsx +0 -72
  69. package/src/components/ra-forms/LongForm/LongFormView.tsx +0 -161
  70. package/src/components/ra-forms/LongForm/index.tsx +0 -2
  71. package/src/components/ra-forms/LongForm/useFormRootPath.ts +0 -21
@@ -0,0 +1,12 @@
1
+ export default function ListItemIcon(): {
2
+ MuiListItemText: {
3
+ styleOverrides: {
4
+ root: {
5
+ '&.MuiListItemText-inset': {
6
+ paddingLeft: number;
7
+ };
8
+ };
9
+ };
10
+ };
11
+ };
12
+ //# sourceMappingURL=ListItemText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListItemText.d.ts","sourceRoot":"","sources":["../../../../src/themes/overrides/ListItemText.jsx"],"names":[],"mappings":"AAEA;;;;;;;;;;EAYC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/themes/overrides/index.jsx"],"names":[],"mappings":"AAkDA,6DA+CC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/themes/overrides/index.jsx"],"names":[],"mappings":"AAmDA,6DAgDC"}
package/dist/types.d.ts CHANGED
@@ -51,4 +51,8 @@ export type MenuItemProps = {
51
51
  * Indica come deve essere configurato il menu dell'applicazione.
52
52
  */
53
53
  export type MenuProps = MenuItemProps[];
54
+ /**
55
+ * Consente di trasformare dei type rendendone opzionali solo alcune chiavi
56
+ */
57
+ export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
54
58
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IACpC;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IACpC;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;AAExC;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applica-software-guru/react-admin",
3
- "version": "1.1.113",
3
+ "version": "1.2.115",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,54 @@
1
+ import _ from 'lodash';
2
+ import { Form, FormProps } from 'react-admin';
3
+ import { Grid } from '@mui/material';
4
+ import { styled } from '@mui/material/styles';
5
+ import { useThemeConfig } from '../../../hooks';
6
+
7
+ type IBaseFormProps = FormProps & {
8
+ spacing?: number;
9
+ };
10
+
11
+ const StyledGrid = styled(Grid, {
12
+ name: 'ApplicaLongFormView',
13
+ slot: 'Root'
14
+ })(({ theme }) => ({
15
+ '& .MuiToolbar-root': {
16
+ position: 'initial',
17
+ marginTop: theme.spacing(2),
18
+ marginLeft: `-${theme.spacing(3)}`,
19
+ marginRight: `-${theme.spacing(3)}`,
20
+ marginBottom: `-${theme.spacing(2)}`,
21
+ borderTop: `1px solid ${theme.palette.divider}`,
22
+ [theme.breakpoints.down('sm')]: {
23
+ position: 'initial !important',
24
+ width: 'auto !important'
25
+ }
26
+ },
27
+ '& form > .MuiToolbar-root': {
28
+ // È molto importante: questa regola serve per poter inserire form, all'interno di altri form, evitando
29
+ // che la toolbar del form interno recepisca gli stili della regola precedente (che genera un padding strano).
30
+ margin: 0,
31
+ marginRight: `-${theme.spacing(0.5)}`,
32
+ [theme.breakpoints.down('sm')]: {
33
+ marginRight: theme.spacing(0.5)
34
+ }
35
+ }
36
+ }));
37
+
38
+ function BaseForm(props: IBaseFormProps) {
39
+ const { spacing: themeSpacing } = useThemeConfig(),
40
+ { spacing = themeSpacing } = props,
41
+ formProps = _.omit(props, ['spacing']);
42
+
43
+ return (
44
+ <Form {...formProps}>
45
+ <StyledGrid container spacing={spacing * 2}>
46
+ {props.children}
47
+ </StyledGrid>
48
+ </Form>
49
+ );
50
+ }
51
+
52
+ export type { IBaseFormProps };
53
+
54
+ export { BaseForm };
@@ -0,0 +1,19 @@
1
+ import _ from 'lodash';
2
+ import { Grid, GridProps } from '@mui/material';
3
+ import { Disposition } from './types';
4
+
5
+ type IContentProps = GridProps & {
6
+ disposition?: Disposition;
7
+ };
8
+
9
+ function Content(props: IContentProps) {
10
+ const { disposition = { xl: 9, lg: 9, md: 8, sm: 8, xs: 12 } } = props,
11
+ gridProps = _.omit(props, ['disposition']);
12
+ return (
13
+ <Grid {...gridProps} item {...disposition}>
14
+ {props.children}
15
+ </Grid>
16
+ );
17
+ }
18
+
19
+ export { Content };
@@ -0,0 +1,37 @@
1
+ import _ from 'lodash';
2
+ import { BaseForm, IBaseFormProps } from './BaseForm';
3
+ import { IProviderProps, Provider } from './Provider';
4
+ import { Disposition } from './types';
5
+ import { Sidebar, useSidebarChildren } from './Sidebar';
6
+ import { Content } from './Content';
7
+ import { NavMenu } from './NavMenu';
8
+ import { useBaseItemChildren } from './Tab';
9
+
10
+ type IFormProps = IProviderProps &
11
+ IBaseFormProps & {
12
+ spacing?: number;
13
+ tabsDisposition: Disposition;
14
+ contentDisposition: Disposition;
15
+ };
16
+
17
+ function Form(props: IFormProps) {
18
+ const { tabsDisposition, contentDisposition, spacing = 2 } = props,
19
+ providerProps = _.pick(props, ['syncWithLocation']),
20
+ baseFormProps = _.omit(props, ['tabsDisposition', 'contentDisposition', 'syncWithLocation']),
21
+ { top: sidebarTop, bottom: sidebarBottom } = useSidebarChildren(props),
22
+ contentChildren = useBaseItemChildren(props);
23
+ return (
24
+ <Provider {...providerProps}>
25
+ <BaseForm {...baseFormProps} spacing={spacing}>
26
+ <Sidebar disposition={tabsDisposition} spacing={spacing}>
27
+ {sidebarTop}
28
+ <NavMenu />
29
+ {sidebarBottom}
30
+ </Sidebar>
31
+ <Content disposition={contentDisposition}>{contentChildren}</Content>
32
+ </BaseForm>
33
+ </Provider>
34
+ );
35
+ }
36
+
37
+ export { Form };
@@ -0,0 +1,128 @@
1
+ import _ from 'lodash';
2
+ import { Avatar } from '../../@extended';
3
+ import { Card, Collapse, List, ListItem, ListItemProps, ListItemIcon, ListItemText, ListItemButton, ListItemAvatar } from '@mui/material';
4
+ import { useSetActiveItem, useSyncWithLocation } from './Provider';
5
+ import { IItem } from './types';
6
+ import { useCallback, useMemo, useState } from 'react';
7
+ import { useNavigate } from 'react-router';
8
+ import { useChildren, useIsActive } from './hooks';
9
+ import { ExpandLess, ExpandMore } from '@mui/icons-material';
10
+ import { getLevel } from './utils';
11
+ import { useThemeConfig } from '../../../hooks';
12
+
13
+ type IBaseNavMenuItemProps = React.PropsWithChildren<{
14
+ id: string;
15
+ onClick?: React.MouseEventHandler<HTMLDivElement>;
16
+ }>;
17
+ type INavMenuItemProps = IItem & ListItemProps & IBaseNavMenuItemProps;
18
+
19
+ function NavMenu() {
20
+ const items = useChildren();
21
+
22
+ return (
23
+ <Card variant="outlined">
24
+ <List
25
+ sx={{
26
+ '& .MuiListItemButton-root.Mui-selected': {
27
+ borderColor: 'palette.primary.main',
28
+ borderRightStyle: 'solid',
29
+ borderRightWidth: 2
30
+ },
31
+ '& .MuiListItemAvatar-root': {
32
+ minWidth: 24
33
+ }
34
+ }}
35
+ >
36
+ {_.map(items, (item, index) => {
37
+ return <NavMenuItem key={index} {...item} />;
38
+ })}
39
+ </List>
40
+ </Card>
41
+ );
42
+ }
43
+
44
+ function NavMenuItem(props: INavMenuItemProps) {
45
+ const navMenuItemProps = useNavMenuItem(props),
46
+ { selected, label, icon, onClick: _onClick, badge, items, isGroup, level } = navMenuItemProps,
47
+ { spacing } = useThemeConfig(),
48
+ [open, setOpen] = useState(selected),
49
+ onClick = useCallback(
50
+ (e: React.MouseEvent<HTMLDivElement>) => {
51
+ setOpen(!open);
52
+ _onClick(e);
53
+ },
54
+ [_onClick, open]
55
+ ),
56
+ hasIcon = icon !== undefined,
57
+ listItemProps = _.omit(props, ['label', 'icon', 'selected', 'badge']);
58
+
59
+ return (
60
+ <>
61
+ <ListItem {...listItemProps} disablePadding>
62
+ <ListItemButton selected={selected && !isGroup} onClick={onClick} sx={{ pl: spacing * level }}>
63
+ {hasIcon && <ListItemIcon>{icon}</ListItemIcon>}
64
+ <ListItemText inset={!hasIcon} primary={label} />
65
+ {badge && (
66
+ <ListItemAvatar>
67
+ <Avatar type="filled" size="xs" color={badge.color ?? 'default'}>
68
+ {badge.content}
69
+ </Avatar>
70
+ </ListItemAvatar>
71
+ )}
72
+ {isGroup && (open ? <ExpandLess /> : <ExpandMore />)}
73
+ </ListItemButton>
74
+ </ListItem>
75
+ {isGroup && (
76
+ <Collapse in={open} timeout="auto">
77
+ <List>
78
+ {_.map(items, (item: IItem, index: number) => {
79
+ return <NavMenuItem key={index} {...item} />;
80
+ })}
81
+ </List>
82
+ </Collapse>
83
+ )}
84
+ </>
85
+ );
86
+ }
87
+
88
+ function useNavMenuItem(
89
+ item: INavMenuItemProps
90
+ ): IItem & { selected: boolean; onClick: React.MouseEventHandler<HTMLDivElement>; items: Array<IItem>; isGroup: boolean; level: number } {
91
+ const { id } = item,
92
+ isActive = useIsActive(id),
93
+ level = getLevel(id),
94
+ navigate = useNavigate(),
95
+ syncWithLocation = useSyncWithLocation(),
96
+ setActiveItem = useSetActiveItem(),
97
+ items = useChildren(id),
98
+ isGroup = !_.isEmpty(items),
99
+ onClick = useCallback(
100
+ (e: React.MouseEvent<HTMLDivElement>) => {
101
+ if (!isGroup) {
102
+ if (syncWithLocation) {
103
+ navigate(item.id);
104
+ } else {
105
+ setActiveItem(item.id);
106
+ }
107
+ }
108
+ if (_.isFunction(item.onClick)) {
109
+ item.onClick(e);
110
+ }
111
+ },
112
+ [setActiveItem, navigate, item, syncWithLocation, isGroup]
113
+ ),
114
+ navMenuItem = useMemo(() => {
115
+ return {
116
+ ...item,
117
+ selected: isActive,
118
+ onClick: onClick,
119
+ items: items,
120
+ isGroup: isGroup,
121
+ level: level
122
+ };
123
+ }, [item, isActive, onClick, items, isGroup]);
124
+
125
+ return navMenuItem;
126
+ }
127
+
128
+ export { NavMenu, NavMenuItem, useNavMenuItem };
@@ -0,0 +1,196 @@
1
+ import { createContext, useCallback, useContext, useEffect, useMemo, useReducer } from 'react';
2
+ import _ from 'lodash';
3
+ import { IItem } from './types';
4
+ import { useLocation } from 'react-router';
5
+ import { getItemsIds } from './utils';
6
+
7
+ enum ActionType {
8
+ SET_SYNC_WITH_LOCATION = 'setSyncWithLocation',
9
+ SET_ACTIVE_ITEM = 'setActiveItem',
10
+ ADD_ITEM = 'addItem',
11
+ REMOVE_ITEM = 'removeItem'
12
+ }
13
+
14
+ type IProviderProps = React.PropsWithChildren<{
15
+ syncWithLocation?: boolean;
16
+ }>;
17
+ type IState = {
18
+ syncWithLocation: boolean;
19
+ items: Array<IItem>;
20
+ activeItem?: string;
21
+ };
22
+ type IAction =
23
+ | {
24
+ type: ActionType.SET_SYNC_WITH_LOCATION;
25
+ payload: boolean;
26
+ }
27
+ | {
28
+ type: ActionType.SET_ACTIVE_ITEM;
29
+ payload?: string;
30
+ }
31
+ | {
32
+ type: ActionType.ADD_ITEM;
33
+ payload: IItem;
34
+ }
35
+ | {
36
+ type: ActionType.REMOVE_ITEM;
37
+ payload: string | IItem;
38
+ };
39
+ type IDispatch = React.Dispatch<IAction>;
40
+ type IContext = {
41
+ state: IState;
42
+ dispatch: IDispatch;
43
+ };
44
+
45
+ function reducer(state: IState, action: IAction): IState {
46
+ const newState = _.clone(state),
47
+ { type, payload } = action;
48
+ switch (type) {
49
+ case ActionType.SET_SYNC_WITH_LOCATION:
50
+ return _.extend(newState, { syncWithLocation: payload });
51
+ case ActionType.SET_ACTIVE_ITEM:
52
+ return _.includes(getItemsIds(state.items), payload) ? _.extend(newState, { activeItem: payload }) : newState;
53
+ case ActionType.ADD_ITEM: {
54
+ const id = payload.id,
55
+ items = _.reject(newState.items, (item: IItem) => item.id === id);
56
+ items.push(payload);
57
+ _.extend(newState, { items: items });
58
+ if (newState.activeItem === undefined) {
59
+ _.extend(newState, { activeItem: payload.id });
60
+ }
61
+ return newState;
62
+ }
63
+ case ActionType.REMOVE_ITEM: {
64
+ const id = _.isString(payload) ? payload : payload.id,
65
+ items = _.reject(newState.items, (item: IItem) => item.id === id);
66
+ _.extend(newState, { items: items });
67
+ if (newState.activeItem === id) {
68
+ _.extend(newState, { activeItem: _.first(items)?.id ?? undefined });
69
+ }
70
+
71
+ return newState;
72
+ }
73
+ default:
74
+ return newState;
75
+ }
76
+ }
77
+
78
+ const DefaultState: IState = {
79
+ syncWithLocation: true,
80
+ items: [],
81
+ activeItem: undefined
82
+ };
83
+
84
+ const Context = createContext<IContext | undefined>(undefined);
85
+
86
+ function Provider(props: IProviderProps) {
87
+ const syncWithLocation = Boolean(props.syncWithLocation ?? true),
88
+ { pathname } = useLocation(),
89
+ [state, dispatch] = useReducer(reducer, _.cloneDeep(DefaultState)),
90
+ { items } = state,
91
+ value = useMemo(() => ({ state: state, dispatch: dispatch }), [state, dispatch]);
92
+
93
+ useEffect(() => {
94
+ dispatch({
95
+ type: ActionType.SET_SYNC_WITH_LOCATION,
96
+ payload: Boolean(syncWithLocation)
97
+ });
98
+ }, [syncWithLocation, dispatch]);
99
+
100
+ useEffect(() => {
101
+ if (syncWithLocation) {
102
+ const match = pathname.match(/([^/]+)$/);
103
+ if (match && match[0]) {
104
+ dispatch({
105
+ type: ActionType.SET_ACTIVE_ITEM,
106
+ payload: match[0]
107
+ });
108
+ }
109
+ }
110
+ }, [dispatch, pathname, syncWithLocation, items]);
111
+
112
+ return <Context.Provider value={value}>{props.children}</Context.Provider>;
113
+ }
114
+
115
+ function useFormContext(): IContext {
116
+ const context = useContext(Context);
117
+
118
+ if (context === undefined) {
119
+ throw new Error('[LongForm] useFormContext: context undefined. Please provide a valid Context using LongForm.Provider');
120
+ }
121
+
122
+ return context;
123
+ }
124
+
125
+ function useFormState(): IState {
126
+ const context = useFormContext();
127
+ return context.state;
128
+ }
129
+
130
+ function useFormDispatch(): IDispatch {
131
+ const context = useFormContext();
132
+ return context.dispatch;
133
+ }
134
+
135
+ function useActiveItem(): string | undefined {
136
+ const state = useFormState();
137
+ return state.activeItem;
138
+ }
139
+
140
+ function useItems(): Array<IItem> {
141
+ const state = useFormState();
142
+ return state.items;
143
+ }
144
+
145
+ function useSyncWithLocation(): boolean {
146
+ const state = useFormState();
147
+ return state.syncWithLocation;
148
+ }
149
+
150
+ function useSetActiveItem(): (activeItem: string) => void {
151
+ const dispatch = useFormDispatch(),
152
+ setActiveItem = useCallback(
153
+ (activeItem: string) => {
154
+ dispatch({
155
+ type: ActionType.SET_ACTIVE_ITEM,
156
+ payload: activeItem
157
+ });
158
+ },
159
+ [dispatch]
160
+ );
161
+
162
+ return setActiveItem;
163
+ }
164
+
165
+ function useAddItem(): (item: IItem) => void {
166
+ const dispatch = useFormDispatch(),
167
+ addItem = useCallback(
168
+ (item: IItem) => {
169
+ dispatch({
170
+ type: ActionType.ADD_ITEM,
171
+ payload: item
172
+ });
173
+ },
174
+ [dispatch]
175
+ );
176
+
177
+ return addItem;
178
+ }
179
+
180
+ function useRemoveItem(): (item: string | IItem) => void {
181
+ const dispatch = useFormDispatch(),
182
+ removeItem = useCallback(
183
+ (item: string | IItem) => {
184
+ dispatch({
185
+ type: ActionType.REMOVE_ITEM,
186
+ payload: item
187
+ });
188
+ },
189
+ [dispatch]
190
+ );
191
+
192
+ return removeItem;
193
+ }
194
+
195
+ export { Provider, useItems, useSyncWithLocation, useActiveItem, useSetActiveItem, useAddItem, useRemoveItem };
196
+ export type { IProviderProps };
@@ -0,0 +1,71 @@
1
+ import _ from 'lodash';
2
+ import { Children, isValidElement, useMemo } from 'react';
3
+ import StickyBox from 'react-sticky-box';
4
+ import { Disposition } from './types';
5
+ import { Box, Grid, GridProps, Stack } from '@mui/material';
6
+
7
+ enum SidebarSectionPosition {
8
+ TOP = 'top',
9
+ BOTTOM = 'bottom'
10
+ }
11
+
12
+ type ISidebarProps = {
13
+ sticky?: boolean;
14
+ disposition?: Disposition;
15
+ } & GridProps;
16
+
17
+ type ISidebarSectionProps = React.PropsWithChildren<{
18
+ position: SidebarSectionPosition;
19
+ }>;
20
+
21
+ function Sidebar(props: ISidebarProps) {
22
+ const { sticky = true, disposition = { xl: 3, lg: 3, md: 4, sm: 4, xs: 12 }, spacing = 2 } = props,
23
+ gridProps = _.omit(props, ['sticky', 'disposition', 'spacing']),
24
+ Wrapper = sticky ? StickyBox : Box,
25
+ wrapperProps = useMemo(() => {
26
+ return sticky ? { offsetTop: 74, offsetBottom: (spacing as number) * 2 } : {};
27
+ }, [sticky, spacing]);
28
+ return (
29
+ <Grid {...gridProps} item {...disposition}>
30
+ <Wrapper {...wrapperProps}>
31
+ <Stack spacing={spacing}>{props.children}</Stack>
32
+ </Wrapper>
33
+ </Grid>
34
+ );
35
+ }
36
+
37
+ function SidebarSection(props: ISidebarSectionProps) {
38
+ return <>{props.children}</>;
39
+ }
40
+
41
+ type ISidebarChildren = {
42
+ top: Array<React.ReactNode>;
43
+ bottom: Array<React.ReactNode>;
44
+ };
45
+
46
+ function useSidebarChildren(props: React.PropsWithChildren): ISidebarChildren {
47
+ const { children } = props,
48
+ result = useMemo<ISidebarChildren>(() => {
49
+ const result: ISidebarChildren = {
50
+ top: [],
51
+ bottom: []
52
+ };
53
+ Children.forEach<React.ReactNode>(children, (Child) => {
54
+ if (isValidElement(Child) && Child?.type === SidebarSection) {
55
+ const { position } = Child.props;
56
+ switch (position) {
57
+ case SidebarSectionPosition.TOP:
58
+ result.top.push(Child);
59
+ break;
60
+ case SidebarSectionPosition.BOTTOM:
61
+ result.bottom.push(Child);
62
+ break;
63
+ }
64
+ }
65
+ });
66
+ return result;
67
+ }, [children]);
68
+ return result;
69
+ }
70
+
71
+ export { Sidebar, SidebarSection, SidebarSectionPosition, useSidebarChildren };
@@ -0,0 +1,67 @@
1
+ import _ from 'lodash';
2
+ import { Box } from '@mui/material';
3
+ import React, { Children, cloneElement, isValidElement, useEffect, useMemo } from 'react';
4
+ import { useAddItem, useRemoveItem } from './Provider';
5
+ import { Optional } from 'src/types';
6
+ import { IItem } from './types';
7
+ import { useIsActive } from './hooks';
8
+ import { getId } from './utils';
9
+
10
+ type IBaseItemProps = React.PropsWithChildren<Optional<IItem, 'id'>>;
11
+ type ITabProps = IBaseItemProps;
12
+ type IGroupProps = IBaseItemProps;
13
+
14
+ function BaseItem(props: IBaseItemProps) {
15
+ const { label, icon, badge } = props,
16
+ id = getId(props),
17
+ addItem = useAddItem(),
18
+ removeItem = useRemoveItem(),
19
+ visible = useIsActive(id);
20
+
21
+ useEffect(() => {
22
+ addItem({
23
+ id: id,
24
+ label: label,
25
+ icon: icon,
26
+ badge: badge
27
+ });
28
+ return () => {
29
+ removeItem(id);
30
+ };
31
+ }, [addItem, removeItem, label, icon, id, badge]);
32
+
33
+ /* All tabs are rendered (not only the one in focus), to allow validation
34
+ on tabs not in focus. The tabs receive a `hidden` property, which they'll
35
+ use to hide the tab using CSS if it's not the one in focus.
36
+ See https://github.com/marmelab/react-admin/issues/1866 */
37
+ return <Box display={visible ? 'unset' : 'none'}>{props.children}</Box>;
38
+ }
39
+
40
+ function Group(props: IGroupProps) {
41
+ const filteredChildren = useBaseItemChildren(props),
42
+ groupId = getId(props);
43
+ return (
44
+ <BaseItem {...props} id={groupId}>
45
+ {Children.map(filteredChildren, (Child) => {
46
+ const childId = getId(Child.props);
47
+ return cloneElement(Child, { id: `${groupId}.${childId}` });
48
+ })}
49
+ </BaseItem>
50
+ );
51
+ }
52
+
53
+ function Tab(props: ITabProps) {
54
+ return <BaseItem {...props} />;
55
+ }
56
+
57
+ function useBaseItemChildren(props: React.PropsWithChildren): Array<React.FunctionComponentElement<IBaseItemProps>> {
58
+ const { children } = props,
59
+ result = useMemo<Array<React.FunctionComponentElement<IBaseItemProps>>>(
60
+ //@ts-ignore
61
+ () => _.filter(Children.toArray(children), (Child) => isValidElement(Child) && (Child?.type === Tab || Child?.type === Group)),
62
+ [children]
63
+ );
64
+ return result;
65
+ }
66
+
67
+ export { Tab, Group, useBaseItemChildren };
@@ -0,0 +1,25 @@
1
+ import _ from 'lodash';
2
+ import { useMemo } from 'react';
3
+ import { useActiveItem, useItems } from './Provider';
4
+ import { IItem } from './types';
5
+ import { getLevel, isChild } from './utils';
6
+
7
+ function useIsActive(id: string): boolean {
8
+ const activeItem = useActiveItem();
9
+ return isChild(id, activeItem ?? '');
10
+ }
11
+
12
+ function useChildren(id?: string): Array<IItem> {
13
+ const items = useItems(),
14
+ level = id !== undefined ? getLevel(id) : 0,
15
+ children = useMemo(() => {
16
+ return _.chain(items)
17
+ .filter((item) => (id !== undefined ? isChild(id, item.id) : true))
18
+ .filter((item) => getLevel(item.id) === level + 1)
19
+ .value();
20
+ }, [items, level, id]);
21
+
22
+ return children;
23
+ }
24
+
25
+ export { useChildren, useIsActive };