@campxdev/shared 0.4.1 → 0.5.0
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/package.json +1 -1
- package/src/components/ListItemButton.tsx +84 -91
- package/src/components/SideNav.tsx +145 -85
- package/src/utils/withRouteWrapper.tsx +0 -1
- package/todo.md +8 -0
package/package.json
CHANGED
|
@@ -1,101 +1,94 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {ChevronRight} from '@mui/icons-material'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from
|
|
10
|
-
import {
|
|
3
|
+
Box,
|
|
4
|
+
ListItemButton as MuiListItemButton,
|
|
5
|
+
ListItemButtonProps,
|
|
6
|
+
ListItemIcon,
|
|
7
|
+
ListItemText,
|
|
8
|
+
styled,
|
|
9
|
+
} from '@mui/material'
|
|
10
|
+
import {ReactNode} from 'react'
|
|
11
11
|
|
|
12
12
|
interface Props extends ListItemButtonProps {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
hasChildren?: boolean
|
|
14
|
+
isActive?: boolean
|
|
15
|
+
label: String
|
|
16
|
+
icon?: any
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const ListItemButton = ({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
isActive,
|
|
21
|
+
hasChildren,
|
|
22
|
+
onClick,
|
|
23
|
+
label,
|
|
24
|
+
icon: Icon,
|
|
25
25
|
}: Props) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{hasChildren ? (
|
|
38
|
-
<>
|
|
39
|
-
<StyledChevronIcon open={isActive} />
|
|
40
|
-
</>
|
|
41
|
-
) : null}
|
|
42
|
-
</MyListItemButton>
|
|
43
|
-
);
|
|
44
|
-
};
|
|
26
|
+
return (
|
|
27
|
+
<StyledListItemButton isActive={isActive} onClick={onClick}>
|
|
28
|
+
{Icon ? (
|
|
29
|
+
<ListItemIcon>{<Icon />}</ListItemIcon>
|
|
30
|
+
) : (
|
|
31
|
+
<Box minWidth={16}></Box>
|
|
32
|
+
)}
|
|
33
|
+
<ListItemText primary={label} />
|
|
34
|
+
</StyledListItemButton>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
45
37
|
|
|
46
|
-
const StyledChevronIcon = styled(ChevronRight)<{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
38
|
+
export const StyledChevronIcon = styled(ChevronRight)<{open: boolean}>(
|
|
39
|
+
({theme, open}) => ({
|
|
40
|
+
transform: open ? 'rotate(90deg)' : 'rotate(0deg)',
|
|
41
|
+
transition: 'transform 0.2s ease-in-out',
|
|
42
|
+
})
|
|
43
|
+
)
|
|
52
44
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
45
|
+
export const StyledListItemButton = styled(MuiListItemButton)<{
|
|
46
|
+
isActive: boolean
|
|
47
|
+
children: ReactNode
|
|
48
|
+
dropDown?: boolean
|
|
49
|
+
}>(({theme, isActive, dropDown = false}) => ({
|
|
50
|
+
gap: '10px',
|
|
51
|
+
fontWeight: 'bold',
|
|
52
|
+
backgroundColor: isActive && '#1d1d1d',
|
|
53
|
+
'&:hover': {
|
|
54
|
+
backgroundColor: isActive && '#1d1d1d',
|
|
55
|
+
},
|
|
56
|
+
'&:after': {
|
|
57
|
+
content: '""',
|
|
58
|
+
position: 'absolute',
|
|
59
|
+
height: '100%',
|
|
60
|
+
left: 0,
|
|
61
|
+
top: 0,
|
|
62
|
+
width: isActive ? '5px' : 0,
|
|
63
|
+
opacity: isActive ? (dropDown ? 0 : 1) : 0,
|
|
64
|
+
transition: theme.transitions.create(['opacity', 'width']),
|
|
65
|
+
background: theme.palette.common.yellow,
|
|
66
|
+
borderTopRightRadius: '3px',
|
|
67
|
+
borderBottomRightRadius: '3px',
|
|
68
|
+
},
|
|
69
|
+
'&.MuiButton-endIcon, &.MuiButton-startIcon': {
|
|
70
|
+
transition: theme.transitions.create(['color']),
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}))
|
|
72
|
+
'&.MuiSvgIcon-root': {
|
|
73
|
+
fontSize: 'inherit',
|
|
74
|
+
transition: 'none',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
'& .MuiSvgIcon-root': {
|
|
78
|
+
fontSize: theme.typography.pxToRem(20),
|
|
79
|
+
marginRight: theme.spacing(1),
|
|
80
|
+
// color: isActive
|
|
81
|
+
// ? theme.sidebar.menuItemColorActive
|
|
82
|
+
// : theme.sidebar.menuItemIconColor,
|
|
83
|
+
},
|
|
84
|
+
'& .MuiTypography-root': {
|
|
85
|
+
color: theme.palette.common.white,
|
|
86
|
+
},
|
|
87
|
+
'& svg': {
|
|
88
|
+
color: theme.palette.common.white,
|
|
89
|
+
},
|
|
90
|
+
'& .MuiListItemIcon-root': {
|
|
91
|
+
minWidth: 0,
|
|
92
|
+
maxWidth: '1rem',
|
|
93
|
+
},
|
|
94
|
+
}))
|
|
@@ -1,99 +1,159 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import {ChevronLeft} from '@mui/icons-material'
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Collapse,
|
|
5
|
+
List,
|
|
6
|
+
ListItemIcon,
|
|
7
|
+
ListItemText,
|
|
8
|
+
styled,
|
|
9
|
+
} from '@mui/material'
|
|
10
|
+
import {ReactNode, useState} from 'react'
|
|
11
|
+
import {Link, useMatch, useResolvedPath} from 'react-router-dom'
|
|
12
|
+
import {PermissionsStore} from '../permissions'
|
|
13
|
+
import {
|
|
14
|
+
ListItemButton,
|
|
15
|
+
StyledChevronIcon,
|
|
16
|
+
StyledListItemButton,
|
|
17
|
+
} from './ListItemButton'
|
|
18
|
+
const accessIfNoKey = process.env.NODE_ENV === 'development' ? false : false
|
|
7
19
|
const StyledLink = styled(Link)({
|
|
8
|
-
|
|
9
|
-
})
|
|
20
|
+
textDecoration: 'none',
|
|
21
|
+
})
|
|
10
22
|
|
|
11
23
|
export default function SideNav({
|
|
12
|
-
|
|
13
|
-
|
|
24
|
+
menuItems,
|
|
25
|
+
header,
|
|
14
26
|
}: {
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
menuItems: any[]
|
|
28
|
+
header?: ReactNode
|
|
17
29
|
}) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
return (
|
|
31
|
+
<Box
|
|
32
|
+
sx={{
|
|
33
|
+
background: (theme) => theme.palette.secondary.main,
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
{header ? header : null}
|
|
37
|
+
<Box>
|
|
38
|
+
{menuItems.map((item, index) => (
|
|
39
|
+
<RenderMenuItem key={index} menuItem={item} />
|
|
40
|
+
))}
|
|
41
|
+
</Box>
|
|
42
|
+
</Box>
|
|
43
|
+
)
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
const RenderMenuItem = ({
|
|
31
|
-
|
|
46
|
+
const RenderMenuItem = ({menuItem}) => {
|
|
47
|
+
const {path, title, children, icon, permissionKey} = menuItem
|
|
48
|
+
let resolved = useResolvedPath(path)
|
|
49
|
+
let match = useMatch({path: resolved.pathname, end: false})
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
const permissions = PermissionsStore.useState((s) => s).permissions
|
|
52
|
+
const hasAccess = permissionKey ? permissions[permissionKey] : accessIfNoKey
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
if (!hasAccess) return null
|
|
55
|
+
if (children?.length)
|
|
56
|
+
return (
|
|
57
|
+
<Box
|
|
58
|
+
sx={{
|
|
59
|
+
position: 'relative',
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<DropDownMenu
|
|
63
|
+
icon={icon}
|
|
64
|
+
menuItems={children}
|
|
65
|
+
path={path}
|
|
66
|
+
title={title}
|
|
67
|
+
permissionKey={permissionKey}
|
|
68
|
+
/>
|
|
69
|
+
</Box>
|
|
70
|
+
)
|
|
38
71
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
return (
|
|
73
|
+
<StyledLink to={path}>
|
|
74
|
+
<ListItemButton
|
|
75
|
+
hasChildren={false}
|
|
76
|
+
label={title}
|
|
77
|
+
isActive={!!match}
|
|
78
|
+
icon={icon}
|
|
79
|
+
/>
|
|
80
|
+
</StyledLink>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const DropDownMenu = ({path, title, icon, menuItems, permissionKey}) => {
|
|
85
|
+
const [open, setOpen] = useState(false)
|
|
86
|
+
const permissions = PermissionsStore.useState((s) => s).permissions
|
|
87
|
+
|
|
88
|
+
const validateDropdownAccess = () => {
|
|
89
|
+
if (accessIfNoKey && !permissions) return true
|
|
90
|
+
let arr = []
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < menuItems.length; i++) {
|
|
93
|
+
if (permissions[menuItems[i].permissionKey]) {
|
|
94
|
+
arr.push(true)
|
|
95
|
+
} else {
|
|
96
|
+
arr.push(accessIfNoKey)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
42
99
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
arr.push(true);
|
|
46
|
-
} else {
|
|
47
|
-
arr.push(accessIfNoKey);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
100
|
+
return arr.includes(true)
|
|
101
|
+
}
|
|
50
102
|
|
|
51
|
-
|
|
52
|
-
};
|
|
53
|
-
const hasAccess = permissionKey ? permissions[permissionKey] : accessIfNoKey;
|
|
54
|
-
const dropdownAccess = children?.length
|
|
55
|
-
? validateDropdownAccess()
|
|
56
|
-
: hasAccess;
|
|
103
|
+
const hasAccess = permissionKey ? permissions[permissionKey] : accessIfNoKey
|
|
57
104
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<Box
|
|
62
|
-
sx={{
|
|
63
|
-
position: "relative",
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
<ListItemButton
|
|
67
|
-
onClick={() => setOpen((prev) => !prev)}
|
|
68
|
-
label={title}
|
|
69
|
-
isActive={open}
|
|
70
|
-
hasChildren={true}
|
|
71
|
-
icon={icon}
|
|
72
|
-
/>
|
|
73
|
-
<Collapse in={open}>
|
|
74
|
-
<List>
|
|
75
|
-
{children?.map((child) => (
|
|
76
|
-
<RenderMenuItem
|
|
77
|
-
key={child.path}
|
|
78
|
-
menuItem={{
|
|
79
|
-
...child,
|
|
80
|
-
path: path + child.path,
|
|
81
|
-
}}
|
|
82
|
-
/>
|
|
83
|
-
))}
|
|
84
|
-
</List>
|
|
85
|
-
</Collapse>
|
|
86
|
-
</Box>
|
|
87
|
-
);
|
|
105
|
+
const dropdownAccess = menuItems?.length
|
|
106
|
+
? validateDropdownAccess()
|
|
107
|
+
: hasAccess
|
|
88
108
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
109
|
+
if (!dropdownAccess) return null
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<Box
|
|
113
|
+
sx={{
|
|
114
|
+
position: 'relative',
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<MenuDropDownButton
|
|
118
|
+
isActive={open}
|
|
119
|
+
onClick={() => setOpen((prev) => !prev)}
|
|
120
|
+
label={title}
|
|
121
|
+
icon={icon}
|
|
122
|
+
/>
|
|
123
|
+
<Collapse in={open}>
|
|
124
|
+
<List>
|
|
125
|
+
{menuItems?.map((child) => (
|
|
126
|
+
<RenderMenuItem
|
|
127
|
+
key={child.path}
|
|
128
|
+
menuItem={{
|
|
129
|
+
...child,
|
|
130
|
+
path: path + child.path,
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
</List>
|
|
135
|
+
</Collapse>
|
|
136
|
+
</Box>
|
|
137
|
+
</>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const MenuDropDownButton = ({icon: Icon, label, onClick, isActive}) => {
|
|
142
|
+
return (
|
|
143
|
+
<>
|
|
144
|
+
<StyledListItemButton
|
|
145
|
+
dropDown={true}
|
|
146
|
+
onClick={onClick}
|
|
147
|
+
isActive={isActive}
|
|
148
|
+
>
|
|
149
|
+
{Icon ? (
|
|
150
|
+
<ListItemIcon>{<Icon />}</ListItemIcon>
|
|
151
|
+
) : (
|
|
152
|
+
<Box minWidth={16}></Box>
|
|
153
|
+
)}
|
|
154
|
+
<ListItemText primary={label} />
|
|
155
|
+
<StyledChevronIcon open={isActive} />
|
|
156
|
+
</StyledListItemButton>
|
|
157
|
+
</>
|
|
158
|
+
)
|
|
159
|
+
}
|