@proteinjs/ui 1.0.1
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/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/dist/generated/index.d.ts +17 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +40 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/src/container/AccountIconButton.d.ts +20 -0
- package/dist/src/container/AccountIconButton.d.ts.map +1 -0
- package/dist/src/container/AccountIconButton.js +112 -0
- package/dist/src/container/AccountIconButton.js.map +1 -0
- package/dist/src/container/NavMenu.d.ts +16 -0
- package/dist/src/container/NavMenu.d.ts.map +1 -0
- package/dist/src/container/NavMenu.js +51 -0
- package/dist/src/container/NavMenu.js.map +1 -0
- package/dist/src/container/PageContainer.d.ts +31 -0
- package/dist/src/container/PageContainer.d.ts.map +1 -0
- package/dist/src/container/PageContainer.js +75 -0
- package/dist/src/container/PageContainer.js.map +1 -0
- package/dist/src/form/Field.d.ts +34 -0
- package/dist/src/form/Field.d.ts.map +1 -0
- package/dist/src/form/Field.js +29 -0
- package/dist/src/form/Field.js.map +1 -0
- package/dist/src/form/Form.d.ts +46 -0
- package/dist/src/form/Form.d.ts.map +1 -0
- package/dist/src/form/Form.js +377 -0
- package/dist/src/form/Form.js.map +1 -0
- package/dist/src/form/FormButton.d.ts +28 -0
- package/dist/src/form/FormButton.d.ts.map +1 -0
- package/dist/src/form/FormButton.js +18 -0
- package/dist/src/form/FormButton.js.map +1 -0
- package/dist/src/form/FunctionalForm.d.ts +1 -0
- package/dist/src/form/FunctionalForm.d.ts.map +1 -0
- package/dist/src/form/FunctionalForm.js +328 -0
- package/dist/src/form/FunctionalForm.js.map +1 -0
- package/dist/src/form/container/FormPage.d.ts +4 -0
- package/dist/src/form/container/FormPage.d.ts.map +1 -0
- package/dist/src/form/container/FormPage.js +29 -0
- package/dist/src/form/container/FormPage.js.map +1 -0
- package/dist/src/form/container/FormPaper.d.ts +4 -0
- package/dist/src/form/container/FormPaper.d.ts.map +1 -0
- package/dist/src/form/container/FormPaper.js +27 -0
- package/dist/src/form/container/FormPaper.js.map +1 -0
- package/dist/src/form/fields/TextField.d.ts +6 -0
- package/dist/src/form/fields/TextField.d.ts.map +1 -0
- package/dist/src/form/fields/TextField.js +70 -0
- package/dist/src/form/fields/TextField.js.map +1 -0
- package/dist/src/list/NestedList.d.ts +14 -0
- package/dist/src/list/NestedList.d.ts.map +1 -0
- package/dist/src/list/NestedList.js +44 -0
- package/dist/src/list/NestedList.js.map +1 -0
- package/dist/src/router/Page.d.ts +28 -0
- package/dist/src/router/Page.d.ts.map +1 -0
- package/dist/src/router/Page.js +7 -0
- package/dist/src/router/Page.js.map +1 -0
- package/dist/src/router/Router.d.ts +14 -0
- package/dist/src/router/Router.d.ts.map +1 -0
- package/dist/src/router/Router.js +66 -0
- package/dist/src/router/Router.js.map +1 -0
- package/dist/src/router/createUrlParams.d.ts +4 -0
- package/dist/src/router/createUrlParams.d.ts.map +1 -0
- package/dist/src/router/createUrlParams.js +19 -0
- package/dist/src/router/createUrlParams.js.map +1 -0
- package/dist/src/router/withRouter.d.ts +7 -0
- package/dist/src/router/withRouter.d.ts.map +1 -0
- package/dist/src/router/withRouter.js +28 -0
- package/dist/src/router/withRouter.js.map +1 -0
- package/dist/src/table/Table.d.ts +14 -0
- package/dist/src/table/Table.d.ts.map +1 -0
- package/dist/src/table/Table.js +170 -0
- package/dist/src/table/Table.js.map +1 -0
- package/dist/src/table/TableButton.d.ts +12 -0
- package/dist/src/table/TableButton.d.ts.map +1 -0
- package/dist/src/table/TableButton.js +3 -0
- package/dist/src/table/TableButton.js.map +1 -0
- package/dist/src/table/TableLoader.d.ts +16 -0
- package/dist/src/table/TableLoader.d.ts.map +1 -0
- package/dist/src/table/TableLoader.js +57 -0
- package/dist/src/table/TableLoader.js.map +1 -0
- package/dist/src/table/TableToolbar.d.ts +10 -0
- package/dist/src/table/TableToolbar.d.ts.map +1 -0
- package/dist/src/table/TableToolbar.js +59 -0
- package/dist/src/table/TableToolbar.js.map +1 -0
- package/generated/index.ts +37 -0
- package/index.ts +20 -0
- package/jest.config.js +18 -0
- package/package.json +52 -0
- package/src/container/AccountIconButton.tsx +118 -0
- package/src/container/NavMenu.tsx +91 -0
- package/src/container/PageContainer.tsx +108 -0
- package/src/form/Field.tsx +51 -0
- package/src/form/Form.tsx +405 -0
- package/src/form/FormButton.tsx +30 -0
- package/src/form/FunctionalForm.tsx +364 -0
- package/src/form/container/FormPage.tsx +23 -0
- package/src/form/container/FormPaper.tsx +16 -0
- package/src/form/fields/TextField.tsx +71 -0
- package/src/list/NestedList.tsx +61 -0
- package/src/router/Page.ts +28 -0
- package/src/router/Router.tsx +76 -0
- package/src/router/createUrlParams.ts +16 -0
- package/src/router/withRouter.tsx +21 -0
- package/src/table/Table.tsx +195 -0
- package/src/table/TableButton.ts +12 -0
- package/src/table/TableLoader.ts +19 -0
- package/src/table/TableToolbar.tsx +99 -0
- package/tsconfig.json +18 -0
package/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export * from './src/router/Router';
|
|
2
|
+
export * from './src/router/Page';
|
|
3
|
+
|
|
4
|
+
export * from './src/form/Form';
|
|
5
|
+
export * from './src/form/Field';
|
|
6
|
+
export * from './src/form/FormButton';
|
|
7
|
+
|
|
8
|
+
export * from './src/form/fields/TextField';
|
|
9
|
+
|
|
10
|
+
export * from './src/form/container/FormPaper';
|
|
11
|
+
export * from './src/form/container/FormPage';
|
|
12
|
+
|
|
13
|
+
export * from './src/container/PageContainer';
|
|
14
|
+
export * from './src/container/NavMenu';
|
|
15
|
+
|
|
16
|
+
export * from './src/list/NestedList';
|
|
17
|
+
|
|
18
|
+
export * from './src/table/Table';
|
|
19
|
+
export * from './src/table/TableButton';
|
|
20
|
+
export * from './src/table/TableLoader';
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"roots": [
|
|
3
|
+
"<rootDir>/test"
|
|
4
|
+
],
|
|
5
|
+
"transform": {
|
|
6
|
+
"^.+\\.tsx?$": "ts-jest"
|
|
7
|
+
},
|
|
8
|
+
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
|
|
9
|
+
"moduleFileExtensions": [
|
|
10
|
+
"ts",
|
|
11
|
+
"tsx",
|
|
12
|
+
"js",
|
|
13
|
+
"jsx",
|
|
14
|
+
"json",
|
|
15
|
+
"node"
|
|
16
|
+
],
|
|
17
|
+
"testEnvironment": "node"
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@proteinjs/ui",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "UI libs",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/proteinjs/ui.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/proteinjs/ui/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/proteinjs/ui#readme",
|
|
16
|
+
"author": "Brent Bahry",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clean": "rm -rf dist/ generated/ node_modules/ package-lock.json",
|
|
20
|
+
"build": "reflection-build && tsc",
|
|
21
|
+
"watch": "reflection-watch"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@emotion/react": "11.11.1",
|
|
25
|
+
"@emotion/styled": "11.11.0",
|
|
26
|
+
"@mui/icons-material": "5.14.11",
|
|
27
|
+
"@mui/material": "5.14.11",
|
|
28
|
+
"@proteinjs/reflection": "1.0.2",
|
|
29
|
+
"@proteinjs/util": "1.0.18",
|
|
30
|
+
"history": "5.0.0",
|
|
31
|
+
"moment": "2.29.4",
|
|
32
|
+
"query-string": "6.13.2",
|
|
33
|
+
"react": "18.2.0",
|
|
34
|
+
"react-dom": "18.2.0",
|
|
35
|
+
"react-router": "6.16.0",
|
|
36
|
+
"react-router-dom": "6.16.0",
|
|
37
|
+
"string": "3.3.3"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@proteinjs/reflection-build": "1.0.5",
|
|
41
|
+
"@types/node": "14.0.13",
|
|
42
|
+
"@types/react": "18.2.23",
|
|
43
|
+
"@types/react-dom": "18.2.8",
|
|
44
|
+
"@types/react-router": "5.1.20",
|
|
45
|
+
"@types/react-router-dom": "5.3.3",
|
|
46
|
+
"@types/string": "0.0.30",
|
|
47
|
+
"typescript": "5.2.2"
|
|
48
|
+
},
|
|
49
|
+
"main": "./dist/generated/index.js",
|
|
50
|
+
"types": "./dist/generated/index.d.ts",
|
|
51
|
+
"gitHead": "14b3fefaaceda0824e4d0e09ac03db2adb2e1a8a"
|
|
52
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, IconButton, Menu, MenuItem } from '@mui/material';
|
|
3
|
+
import AccountCircle from '@mui/icons-material/AccountCircle';
|
|
4
|
+
import { useNavigate } from 'react-router-dom';
|
|
5
|
+
import { LinkOrDialog } from './NavMenu';
|
|
6
|
+
import { Page } from '../router/Page';
|
|
7
|
+
|
|
8
|
+
export type AccountIconButtonProps = {
|
|
9
|
+
loginClicked: boolean,
|
|
10
|
+
setLoginClicked: React.Dispatch<React.SetStateAction<boolean>>,
|
|
11
|
+
auth?: {
|
|
12
|
+
isLoggedIn: boolean,
|
|
13
|
+
canViewPage: (page: Page) => boolean,
|
|
14
|
+
/** Either a dialog component, or a path to be redirected to */
|
|
15
|
+
login: LinkOrDialog,
|
|
16
|
+
logout: () => Promise<string>
|
|
17
|
+
},
|
|
18
|
+
profileMenuItems?: { name: string, action: LinkOrDialog }[],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const AccountIconButton = ({ loginClicked, setLoginClicked, auth, profileMenuItems }: AccountIconButtonProps) => {
|
|
22
|
+
const navigate = useNavigate();
|
|
23
|
+
const [selectedProfileMenuItem, setSelectedProfileMenuItem] = React.useState<number>(-1);
|
|
24
|
+
const [selectedIndex, setSelectedIndex] = React.useState<number>(1);
|
|
25
|
+
const [anchorEl, setAccountMenuAnchorEl] = React.useState<null|HTMLElement>(null);
|
|
26
|
+
const open = Boolean(anchorEl);
|
|
27
|
+
|
|
28
|
+
if (!auth)
|
|
29
|
+
return null;
|
|
30
|
+
|
|
31
|
+
if (auth.isLoggedIn) {
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<IconButton
|
|
35
|
+
sx={{
|
|
36
|
+
'&:hover': {
|
|
37
|
+
color: '#fff',
|
|
38
|
+
backgroundColor: 'transparent',
|
|
39
|
+
},
|
|
40
|
+
backgroundColor: 'transparent',
|
|
41
|
+
}}
|
|
42
|
+
onClick={event => setAccountMenuAnchorEl(event.currentTarget)}
|
|
43
|
+
>
|
|
44
|
+
<AccountCircle />
|
|
45
|
+
</IconButton>
|
|
46
|
+
<Menu
|
|
47
|
+
anchorEl={anchorEl}
|
|
48
|
+
keepMounted
|
|
49
|
+
open={open}
|
|
50
|
+
onClose={() => setAccountMenuAnchorEl(null)}
|
|
51
|
+
>
|
|
52
|
+
{profileMenuItems &&
|
|
53
|
+
profileMenuItems.map((profileMenuItem, index) => (
|
|
54
|
+
<MenuItem
|
|
55
|
+
onClick={event => {
|
|
56
|
+
if (typeof profileMenuItem.action === 'string') {
|
|
57
|
+
navigate(qualifiedPath(profileMenuItem.action));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setSelectedIndex(index);
|
|
61
|
+
}}
|
|
62
|
+
selected={selectedIndex === index}
|
|
63
|
+
>
|
|
64
|
+
{profileMenuItem.name}
|
|
65
|
+
</MenuItem>
|
|
66
|
+
))
|
|
67
|
+
}
|
|
68
|
+
<MenuItem
|
|
69
|
+
onClick={async (event) => {
|
|
70
|
+
const redirectPath = await auth.logout();
|
|
71
|
+
navigate(qualifiedPath(redirectPath));
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
Logout
|
|
75
|
+
</MenuItem>
|
|
76
|
+
</Menu>
|
|
77
|
+
<ProfileMenuItemAction />
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div>
|
|
84
|
+
<Button
|
|
85
|
+
color='inherit'
|
|
86
|
+
onClick={event => auth && typeof auth.login === 'string' ? navigate(qualifiedPath(auth.login)) : setLoginClicked(!loginClicked)}
|
|
87
|
+
>
|
|
88
|
+
Login
|
|
89
|
+
</Button>
|
|
90
|
+
<Login />
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
function ProfileMenuItemAction() {
|
|
95
|
+
if (selectedProfileMenuItem == -1 || !auth || !profileMenuItems)
|
|
96
|
+
return null;
|
|
97
|
+
|
|
98
|
+
const menuItem = profileMenuItems[selectedProfileMenuItem];
|
|
99
|
+
if (typeof menuItem.action === 'string')
|
|
100
|
+
return null;
|
|
101
|
+
|
|
102
|
+
return <menuItem.action onClose={() => setSelectedProfileMenuItem(-1)} />;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function Login() {
|
|
106
|
+
if (!loginClicked || !auth || typeof auth.login === 'string')
|
|
107
|
+
return null;
|
|
108
|
+
|
|
109
|
+
return <auth.login onClose={() => setLoginClicked(false)} />;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function qualifiedPath(path: string) {
|
|
113
|
+
if (path.startsWith('/'))
|
|
114
|
+
return path;
|
|
115
|
+
|
|
116
|
+
return `/${path}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
|
|
5
|
+
export type LinkOrDialog = string | React.ComponentType<{ onClose: () => void }>;
|
|
6
|
+
|
|
7
|
+
export type NavMenuItem = {
|
|
8
|
+
name: string;
|
|
9
|
+
action: LinkOrDialog;
|
|
10
|
+
icon?: React.ComponentType;
|
|
11
|
+
/** Use to make this item a divider */
|
|
12
|
+
// isDivider?: boolean,
|
|
13
|
+
/** Use to make this item a nested menu */
|
|
14
|
+
// children?: NavMenuItems
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type NavMenuProps = {
|
|
18
|
+
navMenuItems?: NavMenuItem[],
|
|
19
|
+
navMenuOpen: boolean,
|
|
20
|
+
setNavMenuOpen: (open: boolean) => void,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const drawerWidth = 240;
|
|
24
|
+
|
|
25
|
+
export const NavMenu: React.FC<NavMenuProps> = ({ navMenuItems, navMenuOpen, setNavMenuOpen }) => {
|
|
26
|
+
const [selectedNavMenuItem, setSelectedNavMenuItem] = React.useState<number>(-1);
|
|
27
|
+
|
|
28
|
+
const handleNavMenuItemClick = (index: number) => {
|
|
29
|
+
setSelectedNavMenuItem(index);
|
|
30
|
+
setNavMenuOpen(false);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const NavMenuItemAction = () => {
|
|
34
|
+
if (selectedNavMenuItem == -1 || !navMenuItems)
|
|
35
|
+
return null;
|
|
36
|
+
|
|
37
|
+
const menuItem = navMenuItems[selectedNavMenuItem];
|
|
38
|
+
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
setSelectedNavMenuItem(-1);
|
|
41
|
+
}, [selectedNavMenuItem]);
|
|
42
|
+
|
|
43
|
+
if (typeof menuItem.action === 'string')
|
|
44
|
+
return <Navigate to={qualifiedPath(menuItem.action)} />;
|
|
45
|
+
|
|
46
|
+
return <menuItem.action onClose={() => setSelectedNavMenuItem(-1)} />;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const qualifiedPath = (path: string) => {
|
|
50
|
+
if (path.startsWith('/'))
|
|
51
|
+
return path;
|
|
52
|
+
|
|
53
|
+
return `/${path}`;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
<Drawer
|
|
59
|
+
sx={{
|
|
60
|
+
width: drawerWidth,
|
|
61
|
+
flexShrink: 0,
|
|
62
|
+
}}
|
|
63
|
+
variant='temporary'
|
|
64
|
+
anchor='left'
|
|
65
|
+
open={navMenuOpen}
|
|
66
|
+
onClose={(_, reason) =>
|
|
67
|
+
reason === 'backdropClick' && setNavMenuOpen(false)
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
<Toolbar />
|
|
71
|
+
<List>
|
|
72
|
+
{navMenuItems?.map((navMenuItem, index) => (
|
|
73
|
+
<ListItem key={index}>
|
|
74
|
+
<ListItemButton
|
|
75
|
+
onClick={() => handleNavMenuItemClick(index)}
|
|
76
|
+
>
|
|
77
|
+
{navMenuItem.icon &&
|
|
78
|
+
<ListItemIcon>
|
|
79
|
+
<navMenuItem.icon />
|
|
80
|
+
</ListItemIcon>
|
|
81
|
+
}
|
|
82
|
+
<ListItemText primary={navMenuItem.name} />
|
|
83
|
+
</ListItemButton>
|
|
84
|
+
</ListItem>
|
|
85
|
+
))}
|
|
86
|
+
</List>
|
|
87
|
+
</Drawer>
|
|
88
|
+
<NavMenuItemAction />
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { AppBar, Toolbar, Box, IconButton, Typography } from '@mui/material';
|
|
3
|
+
import MenuIcon from '@mui/icons-material/Menu';
|
|
4
|
+
import { NavigateFunction, useNavigate } from 'react-router-dom';
|
|
5
|
+
import { Page } from '../router/Page';
|
|
6
|
+
import { createUrlParams } from '../router/createUrlParams';
|
|
7
|
+
import { LinkOrDialog, NavMenu, NavMenuItem } from './NavMenu';
|
|
8
|
+
import { AccountIconButton } from './AccountIconButton';
|
|
9
|
+
|
|
10
|
+
export type PageContainerProps = {
|
|
11
|
+
appName: string,
|
|
12
|
+
page: Page,
|
|
13
|
+
toolbarChildren?: React.ReactNode,
|
|
14
|
+
auth?: {
|
|
15
|
+
isLoggedIn: boolean,
|
|
16
|
+
canViewPage: (page: Page) => boolean,
|
|
17
|
+
/** Either a dialog component, or a path to be redirected to */
|
|
18
|
+
login: LinkOrDialog,
|
|
19
|
+
logout: () => Promise<string>
|
|
20
|
+
},
|
|
21
|
+
profileMenuItems?: { name: string, action: LinkOrDialog }[],
|
|
22
|
+
navMenuItems?: NavMenuItem[],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Page = React.memo(({ auth, page, navigate, loginClicked, setLoginClicked }: { auth: PageContainerProps['auth'], page: PageContainerProps['page'], navigate: NavigateFunction, loginClicked: boolean, setLoginClicked: (loginClicked: boolean) => void }) => {
|
|
26
|
+
if (auth?.canViewPage(page))
|
|
27
|
+
return <page.component urlParams={createUrlParams()} navigate={navigate} />;
|
|
28
|
+
|
|
29
|
+
if (!auth?.isLoggedIn) {
|
|
30
|
+
if (!loginClicked)
|
|
31
|
+
setLoginClicked(true);
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
navigate('/');
|
|
37
|
+
return null;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export function PageContainer(props: PageContainerProps) {
|
|
41
|
+
const navigate = useNavigate();
|
|
42
|
+
const { appName, page, toolbarChildren, auth, profileMenuItems, navMenuItems } = props;
|
|
43
|
+
const [loginClicked, setLoginClicked] = React.useState(false);
|
|
44
|
+
const [navMenuOpen, setNavMenuOpen] = React.useState(false);
|
|
45
|
+
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
if (auth?.canViewPage(page))
|
|
48
|
+
return;
|
|
49
|
+
|
|
50
|
+
if (!auth?.isLoggedIn) {
|
|
51
|
+
console.log(`User not logged in, redirecting to login`);
|
|
52
|
+
if (typeof auth?.login === 'string') {
|
|
53
|
+
const p = qualifiedPath(auth.login);
|
|
54
|
+
navigate(p);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}, [page]);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Box sx={(theme) => {
|
|
61
|
+
const defaultStyles = {};
|
|
62
|
+
if (!page.pageContainerSxProps)
|
|
63
|
+
return defaultStyles;
|
|
64
|
+
|
|
65
|
+
let resolvedStyles;
|
|
66
|
+
if (typeof page.pageContainerSxProps === 'function')
|
|
67
|
+
resolvedStyles = Object.assign(defaultStyles, page.pageContainerSxProps(theme));
|
|
68
|
+
else
|
|
69
|
+
resolvedStyles = Object.assign(defaultStyles, page.pageContainerSxProps);
|
|
70
|
+
|
|
71
|
+
return resolvedStyles;
|
|
72
|
+
}}>
|
|
73
|
+
<AppBar position='static'>
|
|
74
|
+
<Toolbar>
|
|
75
|
+
{ navMenuItems &&
|
|
76
|
+
<IconButton
|
|
77
|
+
aria-label='menu'
|
|
78
|
+
onClick={event => setNavMenuOpen(!navMenuOpen)}
|
|
79
|
+
sx={(theme) => ({
|
|
80
|
+
marginRight: theme.spacing(2),
|
|
81
|
+
'&:hover': {
|
|
82
|
+
color: '#fff',
|
|
83
|
+
},
|
|
84
|
+
})}
|
|
85
|
+
style={{ backgroundColor: 'transparent' }}
|
|
86
|
+
>
|
|
87
|
+
<MenuIcon />
|
|
88
|
+
</IconButton>
|
|
89
|
+
}
|
|
90
|
+
<Typography variant='h5' sx={{ flexGrow: 1, color: 'common.white', }}>
|
|
91
|
+
{appName}
|
|
92
|
+
</Typography>
|
|
93
|
+
{toolbarChildren}
|
|
94
|
+
<AccountIconButton loginClicked={loginClicked} setLoginClicked={setLoginClicked} auth={auth} profileMenuItems={profileMenuItems} />
|
|
95
|
+
</Toolbar>
|
|
96
|
+
</AppBar>
|
|
97
|
+
<NavMenu navMenuItems={navMenuItems} navMenuOpen={navMenuOpen} setNavMenuOpen={setNavMenuOpen} />
|
|
98
|
+
<Page auth={auth} page={page} navigate={navigate} loginClicked={loginClicked} setLoginClicked={setLoginClicked} />
|
|
99
|
+
</Box>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
function qualifiedPath(path: string) {
|
|
103
|
+
if (path.startsWith('/'))
|
|
104
|
+
return path;
|
|
105
|
+
|
|
106
|
+
return `/${path}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import S from 'string';
|
|
3
|
+
|
|
4
|
+
export type Field<T, F extends Fields> = {
|
|
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
|
+
|
|
24
|
+
export type FieldComponentProps<T, F extends Fields> = {
|
|
25
|
+
field: Field<T, F>,
|
|
26
|
+
onChange: (field: Field<T, F>, value: T, setFieldStatus: (message: string, isError: boolean) => void) => Promise<void>,
|
|
27
|
+
// focused: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type FieldComponent<T, F extends Fields> = {
|
|
31
|
+
field: Field<T, F>,
|
|
32
|
+
component: (props: FieldComponentProps<T, F>) => JSX.Element,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export abstract class Fields { [name: string]: FieldComponent<any, any> }
|
|
36
|
+
|
|
37
|
+
export function fieldLabel(field: Field<any, any>) {
|
|
38
|
+
return field.label ? field.label : S(field.name).humanize().capitalize().s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function fieldDisplayValue(field: Field<any, any>) {
|
|
42
|
+
let value = '';
|
|
43
|
+
if (field.value) {
|
|
44
|
+
if (field.displayValue)
|
|
45
|
+
value = field.displayValue(field.value);
|
|
46
|
+
else
|
|
47
|
+
value = field.value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return value;
|
|
51
|
+
}
|