@proteinjs/ui 2.0.2 → 2.0.3
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/.eslintrc.js +20 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +10 -19
- package/LICENSE +21 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/src/container/AccountIconButton.d.ts +1 -1
- package/dist/src/container/AccountIconButton.d.ts.map +1 -1
- package/dist/src/container/AccountIconButton.js +13 -6
- package/dist/src/container/AccountIconButton.js.map +1 -1
- package/dist/src/container/NavMenu.d.ts.map +1 -1
- package/dist/src/container/NavMenu.js +9 -9
- package/dist/src/container/NavMenu.js.map +1 -1
- package/dist/src/container/PageContainer.d.ts +1 -1
- package/dist/src/container/PageContainer.d.ts.map +1 -1
- package/dist/src/container/PageContainer.js +12 -7
- package/dist/src/container/PageContainer.js.map +1 -1
- package/dist/src/form/Field.d.ts.map +1 -1
- package/dist/src/form/Field.js +4 -2
- package/dist/src/form/Field.js.map +1 -1
- package/dist/src/form/Form.d.ts.map +1 -1
- package/dist/src/form/Form.js +75 -40
- package/dist/src/form/Form.js.map +1 -1
- package/dist/src/form/FormButton.d.ts.map +1 -1
- package/dist/src/form/FormButton.js +1 -1
- package/dist/src/form/FormButton.js.map +1 -1
- package/dist/src/form/container/FormPage.d.ts.map +1 -1
- package/dist/src/form/container/FormPage.js.map +1 -1
- package/dist/src/form/container/FormPaper.js +1 -1
- package/dist/src/form/container/FormPaper.js.map +1 -1
- package/dist/src/form/fields/TextField.d.ts.map +1 -1
- package/dist/src/form/fields/TextField.js +8 -6
- package/dist/src/form/fields/TextField.js.map +1 -1
- package/dist/src/list/NestedList.d.ts.map +1 -1
- package/dist/src/list/NestedList.js +10 -10
- package/dist/src/list/NestedList.js.map +1 -1
- package/dist/src/router/Page.d.ts.map +1 -1
- package/dist/src/router/Router.d.ts.map +1 -1
- package/dist/src/router/Router.js +11 -7
- package/dist/src/router/Router.js.map +1 -1
- package/dist/src/router/createUrlParams.d.ts.map +1 -1
- package/dist/src/router/createUrlParams.js +2 -1
- package/dist/src/router/createUrlParams.js.map +1 -1
- package/dist/src/router/withRouter.d.ts.map +1 -1
- package/dist/src/router/withRouter.js +1 -1
- package/dist/src/router/withRouter.js.map +1 -1
- package/dist/src/table/Table.d.ts +1 -1
- package/dist/src/table/Table.d.ts.map +1 -1
- package/dist/src/table/Table.js +31 -23
- package/dist/src/table/Table.js.map +1 -1
- package/dist/src/table/TableButton.d.ts.map +1 -1
- package/dist/src/table/TableLoader.d.ts.map +1 -1
- package/dist/src/table/TableToolbar.d.ts.map +1 -1
- package/dist/src/table/TableToolbar.js +10 -7
- package/dist/src/table/TableToolbar.js.map +1 -1
- package/generated/index.ts +1 -1
- package/index.ts +1 -1
- package/jest.config.js +8 -17
- package/package.json +56 -50
- package/src/container/AccountIconButton.tsx +103 -102
- package/src/container/NavMenu.tsx +20 -21
- package/src/container/PageContainer.tsx +110 -80
- package/src/form/Field.tsx +42 -35
- package/src/form/Form.tsx +370 -351
- package/src/form/FormButton.tsx +24 -22
- package/src/form/FunctionalForm.tsx +15 -15
- package/src/form/container/FormPage.tsx +16 -18
- package/src/form/container/FormPaper.tsx +12 -12
- package/src/form/fields/TextField.tsx +63 -61
- package/src/list/NestedList.tsx +18 -21
- package/src/router/Page.ts +18 -18
- package/src/router/Router.tsx +66 -52
- package/src/router/createUrlParams.ts +8 -7
- package/src/router/withRouter.tsx +6 -11
- package/src/table/Table.tsx +96 -80
- package/src/table/TableButton.ts +9 -9
- package/src/table/TableLoader.ts +6 -6
- package/src/table/TableToolbar.tsx +52 -59
- package/tsconfig.json +17 -17
|
@@ -7,139 +7,140 @@ import { Page } from '../router/Page';
|
|
|
7
7
|
import LogoutIcon from '@mui/icons-material/Logout';
|
|
8
8
|
|
|
9
9
|
export type AccountIconButtonProps = {
|
|
10
|
-
loginClicked: boolean
|
|
11
|
-
setLoginClicked: React.Dispatch<React.SetStateAction<boolean
|
|
10
|
+
loginClicked: boolean;
|
|
11
|
+
setLoginClicked: React.Dispatch<React.SetStateAction<boolean>>;
|
|
12
12
|
auth?: {
|
|
13
|
-
isLoggedIn: boolean
|
|
14
|
-
canViewPage: (page: Page) => boolean
|
|
13
|
+
isLoggedIn: boolean;
|
|
14
|
+
canViewPage: (page: Page) => boolean;
|
|
15
15
|
/** Either a dialog component, or a path to be redirected to */
|
|
16
|
-
login: LinkOrDialog
|
|
17
|
-
logout: () => Promise<string
|
|
18
|
-
}
|
|
19
|
-
profileMenuItems?: { menuItemChildren: React.ReactNode
|
|
20
|
-
}
|
|
16
|
+
login: LinkOrDialog;
|
|
17
|
+
logout: () => Promise<string>;
|
|
18
|
+
};
|
|
19
|
+
profileMenuItems?: { menuItemChildren: React.ReactNode; action: LinkOrDialog | (() => void) }[];
|
|
20
|
+
};
|
|
21
21
|
|
|
22
|
-
export const AccountIconButton = ({
|
|
22
|
+
export const AccountIconButton = ({
|
|
23
|
+
loginClicked,
|
|
24
|
+
setLoginClicked,
|
|
25
|
+
auth,
|
|
26
|
+
profileMenuItems,
|
|
27
|
+
}: AccountIconButtonProps) => {
|
|
23
28
|
const navigate = useNavigate();
|
|
24
29
|
const [selectedProfileMenuItem, setSelectedProfileMenuItem] = React.useState<number>(-1);
|
|
25
30
|
const [selectedIndex, setSelectedIndex] = React.useState<number>(1);
|
|
26
|
-
const [anchorEl, setAccountMenuAnchorEl] = React.useState<null|HTMLElement>(null);
|
|
31
|
+
const [anchorEl, setAccountMenuAnchorEl] = React.useState<null | HTMLElement>(null);
|
|
27
32
|
const open = Boolean(anchorEl);
|
|
28
33
|
|
|
29
|
-
if (!auth)
|
|
30
|
-
|
|
34
|
+
if (!auth) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
31
37
|
|
|
32
38
|
if (auth.isLoggedIn) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
else if (isFunction(profileMenuItem.action)) {
|
|
64
|
-
setAccountMenuAnchorEl(null);
|
|
65
|
-
profileMenuItem.action();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
else {
|
|
69
|
-
setSelectedIndex(index);
|
|
70
|
-
}
|
|
71
|
-
}}
|
|
72
|
-
selected={selectedIndex === index}
|
|
73
|
-
>
|
|
74
|
-
{profileMenuItem.menuItemChildren}
|
|
75
|
-
</MenuItem>
|
|
76
|
-
))
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
<IconButton
|
|
42
|
+
sx={{
|
|
43
|
+
'&:hover': {
|
|
44
|
+
color: '#fff',
|
|
45
|
+
backgroundColor: 'transparent',
|
|
46
|
+
},
|
|
47
|
+
backgroundColor: 'transparent',
|
|
48
|
+
}}
|
|
49
|
+
onClick={(event) => setAccountMenuAnchorEl(event.currentTarget)}
|
|
50
|
+
>
|
|
51
|
+
<AccountCircle />
|
|
52
|
+
</IconButton>
|
|
53
|
+
<Menu anchorEl={anchorEl} keepMounted open={open} onClose={() => setAccountMenuAnchorEl(null)}>
|
|
54
|
+
{profileMenuItems &&
|
|
55
|
+
profileMenuItems.map((profileMenuItem, index) => (
|
|
56
|
+
<MenuItem
|
|
57
|
+
key={index}
|
|
58
|
+
onClick={(event) => {
|
|
59
|
+
if (typeof profileMenuItem.action === 'string') {
|
|
60
|
+
navigate(qualifiedPath(profileMenuItem.action));
|
|
61
|
+
return;
|
|
62
|
+
} else if (isFunction(profileMenuItem.action)) {
|
|
63
|
+
setAccountMenuAnchorEl(null);
|
|
64
|
+
profileMenuItem.action();
|
|
65
|
+
} else {
|
|
66
|
+
setSelectedIndex(index);
|
|
77
67
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
68
|
+
}}
|
|
69
|
+
selected={selectedIndex === index}
|
|
70
|
+
>
|
|
71
|
+
{profileMenuItem.menuItemChildren}
|
|
72
|
+
</MenuItem>
|
|
73
|
+
))}
|
|
74
|
+
<MenuItem
|
|
75
|
+
key='logout'
|
|
76
|
+
onClick={async (event) => {
|
|
77
|
+
const redirectPath = await auth.logout();
|
|
78
|
+
navigate(qualifiedPath(redirectPath));
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<ListItemIcon>
|
|
82
|
+
<LogoutIcon />
|
|
83
|
+
</ListItemIcon>
|
|
84
|
+
<Typography>Logout</Typography>
|
|
85
|
+
</MenuItem>
|
|
86
|
+
</Menu>
|
|
87
|
+
<ProfileMenuItemAction />
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
95
90
|
}
|
|
96
91
|
|
|
97
92
|
return (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
</
|
|
93
|
+
<div>
|
|
94
|
+
<Button
|
|
95
|
+
color='inherit'
|
|
96
|
+
onClick={(event) =>
|
|
97
|
+
auth && typeof auth.login === 'string' ? navigate(qualifiedPath(auth.login)) : setLoginClicked(!loginClicked)
|
|
98
|
+
}
|
|
99
|
+
>
|
|
100
|
+
Login
|
|
101
|
+
</Button>
|
|
102
|
+
<Login />
|
|
103
|
+
</div>
|
|
107
104
|
);
|
|
108
105
|
|
|
109
106
|
function ProfileMenuItemAction() {
|
|
110
|
-
if (selectedProfileMenuItem == -1 || !auth || !profileMenuItems)
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
if (selectedProfileMenuItem == -1 || !auth || !profileMenuItems) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
113
111
|
const menuItem = profileMenuItems[selectedProfileMenuItem];
|
|
114
|
-
if (typeof menuItem.action === 'string')
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
if (typeof menuItem.action === 'string') {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
117
116
|
if (isDialog(menuItem.action)) {
|
|
118
|
-
|
|
117
|
+
return <menuItem.action onClose={() => setSelectedProfileMenuItem(-1)} />;
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
return null;
|
|
122
121
|
}
|
|
123
|
-
|
|
122
|
+
|
|
124
123
|
function Login() {
|
|
125
|
-
if (!loginClicked || !auth || typeof auth.login === 'string')
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
if (!loginClicked || !auth || typeof auth.login === 'string') {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
128
|
return <auth.login onClose={() => setLoginClicked(false)} />;
|
|
129
129
|
}
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
function qualifiedPath(path: string) {
|
|
132
|
-
if (path.startsWith('/'))
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
if (path.startsWith('/')) {
|
|
133
|
+
return path;
|
|
134
|
+
}
|
|
135
|
+
|
|
135
136
|
return `/${path}`;
|
|
136
137
|
}
|
|
137
|
-
}
|
|
138
|
+
};
|
|
138
139
|
|
|
139
140
|
function isDialog(prop: any): prop is React.ComponentType<{ onClose: () => void }> {
|
|
140
|
-
|
|
141
|
+
return typeof prop === 'function' && 'onClose' in prop.prototype;
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
function isFunction(prop: any): prop is () => void {
|
|
144
|
-
|
|
145
|
-
}
|
|
145
|
+
return typeof prop === 'function';
|
|
146
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Drawer, Toolbar, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
|
|
3
|
-
import { Navigate } from 'react-router-dom'
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Drawer, Toolbar, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
|
3
|
+
import { Navigate } from 'react-router-dom';
|
|
4
4
|
|
|
5
5
|
export type LinkOrDialog = string | React.ComponentType<{ onClose: () => void }>;
|
|
6
6
|
|
|
@@ -9,15 +9,15 @@ export type NavMenuItem = {
|
|
|
9
9
|
action: LinkOrDialog;
|
|
10
10
|
icon?: React.ComponentType;
|
|
11
11
|
/** Use to make this item a divider */
|
|
12
|
-
// isDivider?: boolean,
|
|
12
|
+
// isDivider?: boolean,
|
|
13
13
|
/** Use to make this item a nested menu */
|
|
14
|
-
// children?: NavMenuItems
|
|
14
|
+
// children?: NavMenuItems
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export type NavMenuProps = {
|
|
18
|
-
navMenuItems?: NavMenuItem[]
|
|
19
|
-
navMenuOpen: boolean
|
|
20
|
-
setNavMenuOpen: (open: boolean) => void
|
|
18
|
+
navMenuItems?: NavMenuItem[];
|
|
19
|
+
navMenuOpen: boolean;
|
|
20
|
+
setNavMenuOpen: (open: boolean) => void;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const drawerWidth = 240;
|
|
@@ -31,8 +31,9 @@ export const NavMenu: React.FC<NavMenuProps> = ({ navMenuItems, navMenuOpen, set
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
const NavMenuItemAction = () => {
|
|
34
|
-
if (selectedNavMenuItem == -1 || !navMenuItems)
|
|
34
|
+
if (selectedNavMenuItem == -1 || !navMenuItems) {
|
|
35
35
|
return null;
|
|
36
|
+
}
|
|
36
37
|
|
|
37
38
|
const menuItem = navMenuItems[selectedNavMenuItem];
|
|
38
39
|
|
|
@@ -40,16 +41,18 @@ export const NavMenu: React.FC<NavMenuProps> = ({ navMenuItems, navMenuOpen, set
|
|
|
40
41
|
setSelectedNavMenuItem(-1);
|
|
41
42
|
}, [selectedNavMenuItem]);
|
|
42
43
|
|
|
43
|
-
if (typeof menuItem.action === 'string')
|
|
44
|
+
if (typeof menuItem.action === 'string') {
|
|
44
45
|
return <Navigate to={qualifiedPath(menuItem.action)} />;
|
|
46
|
+
}
|
|
45
47
|
|
|
46
48
|
return <menuItem.action onClose={() => setSelectedNavMenuItem(-1)} />;
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
const qualifiedPath = (path: string) => {
|
|
50
|
-
if (path.startsWith('/'))
|
|
52
|
+
if (path.startsWith('/')) {
|
|
51
53
|
return path;
|
|
52
|
-
|
|
54
|
+
}
|
|
55
|
+
|
|
53
56
|
return `/${path}`;
|
|
54
57
|
};
|
|
55
58
|
|
|
@@ -63,22 +66,18 @@ export const NavMenu: React.FC<NavMenuProps> = ({ navMenuItems, navMenuOpen, set
|
|
|
63
66
|
variant='temporary'
|
|
64
67
|
anchor='left'
|
|
65
68
|
open={navMenuOpen}
|
|
66
|
-
onClose={(_, reason) =>
|
|
67
|
-
reason === 'backdropClick' && setNavMenuOpen(false)
|
|
68
|
-
}
|
|
69
|
+
onClose={(_, reason) => reason === 'backdropClick' && setNavMenuOpen(false)}
|
|
69
70
|
>
|
|
70
71
|
<Toolbar />
|
|
71
72
|
<List>
|
|
72
73
|
{navMenuItems?.map((navMenuItem, index) => (
|
|
73
74
|
<ListItem key={index}>
|
|
74
|
-
<ListItemButton
|
|
75
|
-
|
|
76
|
-
>
|
|
77
|
-
{navMenuItem.icon &&
|
|
75
|
+
<ListItemButton onClick={() => handleNavMenuItemClick(index)}>
|
|
76
|
+
{navMenuItem.icon && (
|
|
78
77
|
<ListItemIcon>
|
|
79
78
|
<navMenuItem.icon />
|
|
80
79
|
</ListItemIcon>
|
|
81
|
-
}
|
|
80
|
+
)}
|
|
82
81
|
<ListItemText primary={navMenuItem.name} />
|
|
83
82
|
</ListItemButton>
|
|
84
83
|
</ListItem>
|
|
@@ -88,4 +87,4 @@ export const NavMenu: React.FC<NavMenuProps> = ({ navMenuItems, navMenuOpen, set
|
|
|
88
87
|
<NavMenuItemAction />
|
|
89
88
|
</div>
|
|
90
89
|
);
|
|
91
|
-
}
|
|
90
|
+
};
|
|
@@ -8,100 +8,130 @@ import { LinkOrDialog, NavMenu, NavMenuItem } from './NavMenu';
|
|
|
8
8
|
import { AccountIconButton } from './AccountIconButton';
|
|
9
9
|
|
|
10
10
|
function qualifiedPath(path: string) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
if (path.startsWith('/')) {
|
|
12
|
+
return path;
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
return `/${path}`;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export type PageContainerProps = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
19
|
+
page: Page;
|
|
20
|
+
auth?: {
|
|
21
|
+
isLoggedIn: boolean;
|
|
22
|
+
canViewPage: (page: Page) => boolean;
|
|
23
|
+
login: LinkOrDialog;
|
|
24
|
+
logout: () => Promise<string>;
|
|
25
|
+
};
|
|
26
|
+
appName?: string;
|
|
27
|
+
toolbarChildren?: React.ReactNode;
|
|
28
|
+
/** An array of menu items, each containing a React node and an action triggered when selected, either a string, dialog component, or a function. */
|
|
29
|
+
profileMenuItems?: { menuItemChildren: React.ReactNode; action: LinkOrDialog | (() => void) }[];
|
|
30
|
+
navMenuItems?: NavMenuItem[];
|
|
31
|
+
appBarProps?: AppBarProps;
|
|
32
|
+
toolbarProps?: ToolbarProps;
|
|
33
|
+
};
|
|
33
34
|
|
|
34
|
-
const Page = React.memo(
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const Page = React.memo(
|
|
36
|
+
({
|
|
37
|
+
auth,
|
|
38
|
+
page,
|
|
39
|
+
navigate,
|
|
40
|
+
loginClicked,
|
|
41
|
+
setLoginClicked,
|
|
42
|
+
}: {
|
|
43
|
+
auth: PageContainerProps['auth'];
|
|
44
|
+
page: PageContainerProps['page'];
|
|
45
|
+
navigate: NavigateFunction;
|
|
46
|
+
loginClicked: boolean;
|
|
47
|
+
setLoginClicked: (loginClicked: boolean) => void;
|
|
48
|
+
}) => {
|
|
49
|
+
if (auth?.canViewPage(page)) {
|
|
50
|
+
return <page.component urlParams={createUrlParams()} navigate={navigate} />;
|
|
51
|
+
}
|
|
37
52
|
|
|
38
53
|
if (!auth?.isLoggedIn) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
if (!loginClicked) {
|
|
55
|
+
setLoginClicked(true);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
navigate('/');
|
|
46
62
|
return null;
|
|
47
|
-
}
|
|
63
|
+
}
|
|
64
|
+
);
|
|
48
65
|
|
|
49
66
|
export function PageContainer(props: PageContainerProps) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
React.useEffect(() => {
|
|
56
|
-
if (auth?.canViewPage(page))
|
|
57
|
-
return;
|
|
67
|
+
const navigate = useNavigate();
|
|
68
|
+
const { page, auth, navMenuItems, appName, toolbarChildren, profileMenuItems, appBarProps, toolbarProps } = props;
|
|
69
|
+
const [loginClicked, setLoginClicked] = React.useState(false);
|
|
70
|
+
const [navMenuOpen, setNavMenuOpen] = React.useState(false);
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
navigate(p);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}, [page]);
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
if (auth?.canViewPage(page)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
if (!auth?.isLoggedIn) {
|
|
78
|
+
console.log(`User not logged in, redirecting to login`);
|
|
79
|
+
if (typeof auth?.login === 'string') {
|
|
80
|
+
const p = qualifiedPath(auth.login);
|
|
81
|
+
navigate(p);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}, [page]);
|
|
73
85
|
|
|
74
|
-
|
|
86
|
+
return (
|
|
87
|
+
<Box
|
|
88
|
+
sx={(theme) => {
|
|
89
|
+
const defaultStyles = {};
|
|
90
|
+
if (!page.pageContainerSxProps) {
|
|
91
|
+
return defaultStyles;
|
|
92
|
+
}
|
|
75
93
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
94
|
+
const resolvedStyles = Object.assign({}, defaultStyles, page.pageContainerSxProps(theme));
|
|
95
|
+
|
|
96
|
+
return resolvedStyles;
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<AppBar position='static' {...appBarProps}>
|
|
100
|
+
<Toolbar {...toolbarProps}>
|
|
101
|
+
{navMenuItems && (
|
|
102
|
+
<IconButton
|
|
103
|
+
aria-label='menu'
|
|
104
|
+
onClick={() => setNavMenuOpen(!navMenuOpen)}
|
|
105
|
+
sx={(theme) => ({
|
|
106
|
+
marginRight: theme.spacing(2),
|
|
107
|
+
'&:hover': {
|
|
108
|
+
color: '#fff',
|
|
109
|
+
},
|
|
110
|
+
})}
|
|
111
|
+
style={{ backgroundColor: 'transparent' }}
|
|
112
|
+
>
|
|
113
|
+
<MenuIcon />
|
|
114
|
+
</IconButton>
|
|
115
|
+
)}
|
|
116
|
+
{appName && (
|
|
117
|
+
<Typography variant='h5' sx={{ flexGrow: 1, color: 'common.white' }}>
|
|
118
|
+
{appName}
|
|
119
|
+
</Typography>
|
|
120
|
+
)}
|
|
121
|
+
{toolbarChildren}
|
|
122
|
+
<div style={{ flexGrow: 1 }}></div>
|
|
123
|
+
<AccountIconButton
|
|
124
|
+
loginClicked={loginClicked}
|
|
125
|
+
setLoginClicked={setLoginClicked}
|
|
126
|
+
auth={auth}
|
|
127
|
+
{...(profileMenuItems ? { profileMenuItems } : {})}
|
|
128
|
+
/>
|
|
129
|
+
</Toolbar>
|
|
130
|
+
</AppBar>
|
|
131
|
+
{navMenuItems && (
|
|
132
|
+
<NavMenu navMenuItems={navMenuItems} navMenuOpen={navMenuOpen} setNavMenuOpen={setNavMenuOpen} />
|
|
133
|
+
)}
|
|
134
|
+
<Page auth={auth} page={page} navigate={navigate} loginClicked={loginClicked} setLoginClicked={setLoginClicked} />
|
|
135
|
+
</Box>
|
|
136
|
+
);
|
|
137
|
+
}
|
package/src/form/Field.tsx
CHANGED
|
@@ -2,50 +2,57 @@ import React from 'react';
|
|
|
2
2
|
import S from 'string';
|
|
3
3
|
|
|
4
4
|
export type Field<T, F extends Fields> = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
5
|
+
name: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
accessibility?: {
|
|
9
|
+
required?: boolean;
|
|
10
|
+
hidden?: boolean;
|
|
11
|
+
readonly?: boolean;
|
|
12
|
+
};
|
|
13
|
+
layout?: {
|
|
14
|
+
row: number;
|
|
15
|
+
column?: number;
|
|
16
|
+
width: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
|
17
|
+
};
|
|
18
|
+
value?: T;
|
|
19
|
+
displayValue?: (value: T) => string;
|
|
20
|
+
onLoad?: (fields: F) => Promise<void>;
|
|
21
|
+
onChange?: (value: T, fields: F, setFieldStatus: (message: string, isError: boolean) => void) => Promise<void>;
|
|
22
|
+
};
|
|
23
23
|
|
|
24
24
|
export type FieldComponentProps<T, F extends Fields> = {
|
|
25
|
+
field: Field<T, F>;
|
|
26
|
+
onChange: (
|
|
25
27
|
field: Field<T, F>,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
value: T,
|
|
29
|
+
setFieldStatus: (message: string, isError: boolean) => void
|
|
30
|
+
) => Promise<void>;
|
|
31
|
+
// focused: boolean
|
|
32
|
+
};
|
|
29
33
|
|
|
30
34
|
export type FieldComponent<T, F extends Fields> = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
35
|
+
field: Field<T, F>;
|
|
36
|
+
component: (props: FieldComponentProps<T, F>) => JSX.Element;
|
|
37
|
+
};
|
|
34
38
|
|
|
35
|
-
export abstract class Fields {
|
|
39
|
+
export abstract class Fields {
|
|
40
|
+
[name: string]: FieldComponent<any, any>;
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
export function fieldLabel(field: Field<any, any>) {
|
|
38
|
-
|
|
44
|
+
return field.label ? field.label : S(field.name).humanize().capitalize().s;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
export function fieldDisplayValue(field: Field<any, any>) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
let value = '';
|
|
49
|
+
if (field.value) {
|
|
50
|
+
if (field.displayValue) {
|
|
51
|
+
value = field.displayValue(field.value);
|
|
52
|
+
} else {
|
|
53
|
+
value = field.value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|