@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/dist/generated/index.d.ts +17 -0
  4. package/dist/generated/index.d.ts.map +1 -0
  5. package/dist/generated/index.js +40 -0
  6. package/dist/generated/index.js.map +1 -0
  7. package/dist/index.d.ts +15 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +31 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/src/container/AccountIconButton.d.ts +20 -0
  12. package/dist/src/container/AccountIconButton.d.ts.map +1 -0
  13. package/dist/src/container/AccountIconButton.js +112 -0
  14. package/dist/src/container/AccountIconButton.js.map +1 -0
  15. package/dist/src/container/NavMenu.d.ts +16 -0
  16. package/dist/src/container/NavMenu.d.ts.map +1 -0
  17. package/dist/src/container/NavMenu.js +51 -0
  18. package/dist/src/container/NavMenu.js.map +1 -0
  19. package/dist/src/container/PageContainer.d.ts +31 -0
  20. package/dist/src/container/PageContainer.d.ts.map +1 -0
  21. package/dist/src/container/PageContainer.js +75 -0
  22. package/dist/src/container/PageContainer.js.map +1 -0
  23. package/dist/src/form/Field.d.ts +34 -0
  24. package/dist/src/form/Field.d.ts.map +1 -0
  25. package/dist/src/form/Field.js +29 -0
  26. package/dist/src/form/Field.js.map +1 -0
  27. package/dist/src/form/Form.d.ts +46 -0
  28. package/dist/src/form/Form.d.ts.map +1 -0
  29. package/dist/src/form/Form.js +377 -0
  30. package/dist/src/form/Form.js.map +1 -0
  31. package/dist/src/form/FormButton.d.ts +28 -0
  32. package/dist/src/form/FormButton.d.ts.map +1 -0
  33. package/dist/src/form/FormButton.js +18 -0
  34. package/dist/src/form/FormButton.js.map +1 -0
  35. package/dist/src/form/FunctionalForm.d.ts +1 -0
  36. package/dist/src/form/FunctionalForm.d.ts.map +1 -0
  37. package/dist/src/form/FunctionalForm.js +328 -0
  38. package/dist/src/form/FunctionalForm.js.map +1 -0
  39. package/dist/src/form/container/FormPage.d.ts +4 -0
  40. package/dist/src/form/container/FormPage.d.ts.map +1 -0
  41. package/dist/src/form/container/FormPage.js +29 -0
  42. package/dist/src/form/container/FormPage.js.map +1 -0
  43. package/dist/src/form/container/FormPaper.d.ts +4 -0
  44. package/dist/src/form/container/FormPaper.d.ts.map +1 -0
  45. package/dist/src/form/container/FormPaper.js +27 -0
  46. package/dist/src/form/container/FormPaper.js.map +1 -0
  47. package/dist/src/form/fields/TextField.d.ts +6 -0
  48. package/dist/src/form/fields/TextField.d.ts.map +1 -0
  49. package/dist/src/form/fields/TextField.js +70 -0
  50. package/dist/src/form/fields/TextField.js.map +1 -0
  51. package/dist/src/list/NestedList.d.ts +14 -0
  52. package/dist/src/list/NestedList.d.ts.map +1 -0
  53. package/dist/src/list/NestedList.js +44 -0
  54. package/dist/src/list/NestedList.js.map +1 -0
  55. package/dist/src/router/Page.d.ts +28 -0
  56. package/dist/src/router/Page.d.ts.map +1 -0
  57. package/dist/src/router/Page.js +7 -0
  58. package/dist/src/router/Page.js.map +1 -0
  59. package/dist/src/router/Router.d.ts +14 -0
  60. package/dist/src/router/Router.d.ts.map +1 -0
  61. package/dist/src/router/Router.js +66 -0
  62. package/dist/src/router/Router.js.map +1 -0
  63. package/dist/src/router/createUrlParams.d.ts +4 -0
  64. package/dist/src/router/createUrlParams.d.ts.map +1 -0
  65. package/dist/src/router/createUrlParams.js +19 -0
  66. package/dist/src/router/createUrlParams.js.map +1 -0
  67. package/dist/src/router/withRouter.d.ts +7 -0
  68. package/dist/src/router/withRouter.d.ts.map +1 -0
  69. package/dist/src/router/withRouter.js +28 -0
  70. package/dist/src/router/withRouter.js.map +1 -0
  71. package/dist/src/table/Table.d.ts +14 -0
  72. package/dist/src/table/Table.d.ts.map +1 -0
  73. package/dist/src/table/Table.js +170 -0
  74. package/dist/src/table/Table.js.map +1 -0
  75. package/dist/src/table/TableButton.d.ts +12 -0
  76. package/dist/src/table/TableButton.d.ts.map +1 -0
  77. package/dist/src/table/TableButton.js +3 -0
  78. package/dist/src/table/TableButton.js.map +1 -0
  79. package/dist/src/table/TableLoader.d.ts +16 -0
  80. package/dist/src/table/TableLoader.d.ts.map +1 -0
  81. package/dist/src/table/TableLoader.js +57 -0
  82. package/dist/src/table/TableLoader.js.map +1 -0
  83. package/dist/src/table/TableToolbar.d.ts +10 -0
  84. package/dist/src/table/TableToolbar.d.ts.map +1 -0
  85. package/dist/src/table/TableToolbar.js +59 -0
  86. package/dist/src/table/TableToolbar.js.map +1 -0
  87. package/generated/index.ts +37 -0
  88. package/index.ts +20 -0
  89. package/jest.config.js +18 -0
  90. package/package.json +52 -0
  91. package/src/container/AccountIconButton.tsx +118 -0
  92. package/src/container/NavMenu.tsx +91 -0
  93. package/src/container/PageContainer.tsx +108 -0
  94. package/src/form/Field.tsx +51 -0
  95. package/src/form/Form.tsx +405 -0
  96. package/src/form/FormButton.tsx +30 -0
  97. package/src/form/FunctionalForm.tsx +364 -0
  98. package/src/form/container/FormPage.tsx +23 -0
  99. package/src/form/container/FormPaper.tsx +16 -0
  100. package/src/form/fields/TextField.tsx +71 -0
  101. package/src/list/NestedList.tsx +61 -0
  102. package/src/router/Page.ts +28 -0
  103. package/src/router/Router.tsx +76 -0
  104. package/src/router/createUrlParams.ts +16 -0
  105. package/src/router/withRouter.tsx +21 -0
  106. package/src/table/Table.tsx +195 -0
  107. package/src/table/TableButton.ts +12 -0
  108. package/src/table/TableLoader.ts +19 -0
  109. package/src/table/TableToolbar.tsx +99 -0
  110. package/tsconfig.json +18 -0
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { NavigateFunction, useNavigate } from 'react-router-dom';
3
+
4
+ export type WithRouterProps = {
5
+ navigate: NavigateFunction
6
+ }
7
+
8
+ export const withRouter = (Component: typeof React.Component) => {
9
+ const Wrapper = (props: any) => {
10
+ const navigate = useNavigate();
11
+
12
+ return (
13
+ <Component
14
+ navigate={navigate}
15
+ {...props}
16
+ />
17
+ );
18
+ };
19
+
20
+ return Wrapper;
21
+ };
@@ -0,0 +1,195 @@
1
+ import React from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
+ import S from 'string'
4
+ import { TableContainer, Table as MuiTable, TableBody, TableCell, TableHead, TablePagination, TableRow, Typography, Checkbox } from '@mui/material'
5
+ import moment from 'moment'
6
+ import { StringUtil } from '@proteinjs/util'
7
+ import { TableLoader } from './TableLoader'
8
+ import { TableButton } from './TableButton'
9
+ import { TableToolbar } from './TableToolbar'
10
+
11
+ export type TableProps<T> = {
12
+ title?: string,
13
+ description?: () => JSX.Element,
14
+ columns: (keyof T)[],
15
+ tableLoader: TableLoader<T>,
16
+ rowOnClickRedirectUrl?: (row: T) => Promise<string>,
17
+ defaultRowsPerPage?: number,
18
+ buttons?: TableButton<T>[],
19
+ }
20
+
21
+ export function Table<T>({ title, description, columns, tableLoader, rowOnClickRedirectUrl, defaultRowsPerPage = 10, buttons }: TableProps<T>) {
22
+ const [rowsPerPage, setRowsPerPage] = React.useState(defaultRowsPerPage);
23
+ const [page, setPage] = React.useState(0);
24
+ const [totalRows, setTotalRows] = React.useState(0);
25
+ const [rows, setRows] = React.useState<T[]>([]);
26
+ const [selectedRows, setSelectedRows] = React.useState<{[key: number]: T}>({});
27
+ const [selectAll, setSelectAll] = React.useState(false);
28
+ const navigate = useNavigate();
29
+
30
+ React.useEffect(() => {
31
+ const fetchData = async () => {
32
+ const startIndex = page * rowsPerPage;
33
+ const endIndex = startIndex + rowsPerPage;
34
+ const rowWindow = await tableLoader.load(startIndex, endIndex);
35
+ setRows(rowWindow.rows);
36
+ setTotalRows(rowWindow.totalCount);
37
+ };
38
+
39
+ fetchData();
40
+ }, [page, rowsPerPage, tableLoader]);
41
+
42
+ async function handleRowOnClick(row: T) {
43
+ if (!rowOnClickRedirectUrl)
44
+ return;
45
+
46
+ let redirectUrl = await rowOnClickRedirectUrl(row);
47
+ if (!redirectUrl.startsWith('/'))
48
+ redirectUrl = `/${redirectUrl}`;
49
+
50
+ navigate(redirectUrl);
51
+ }
52
+
53
+ function updateRowsPerPage(newValue: number) {
54
+ setRowsPerPage(newValue);
55
+ setPage(0);
56
+ }
57
+
58
+ function toggleSelectRow(rowIndex: number, row: T) {
59
+ const newSelectedRows = Object.assign({}, selectedRows);
60
+ if (newSelectedRows[rowIndex])
61
+ delete newSelectedRows[rowIndex];
62
+ else
63
+ newSelectedRows[rowIndex] = row;
64
+
65
+ setSelectedRows(newSelectedRows);
66
+
67
+ if (selectAll && Object.keys(selectedRows).length != rows.length)
68
+ setSelectAll(false);
69
+ else if (!selectAll && Object.keys(selectedRows).length == rows.length)
70
+ setSelectAll(true);
71
+ }
72
+
73
+ function toggleSelectAll(selected: boolean) {
74
+ if (selected) {
75
+ const newSelectedRows = Object.assign({}, selectedRows);
76
+ for (let i = 0; i < rows.length; i++) {
77
+ const index = rowsPerPage * page + i;
78
+ if (!newSelectedRows[index])
79
+ newSelectedRows[index] = rows[i];
80
+ }
81
+ setSelectedRows(newSelectedRows);
82
+ } else {
83
+ setSelectedRows({});
84
+ }
85
+
86
+ setSelectAll(selected);
87
+ }
88
+
89
+ function formatCellValue(value: any): string {
90
+ if (value == null)
91
+ return '';
92
+
93
+ if (typeof value === 'boolean')
94
+ return value ? 'True' : 'False';
95
+
96
+ if (moment.isMoment(value))
97
+ return value.format('ddd, MMM Do YY, h:mm:ss a')
98
+
99
+ if (typeof value === 'object')
100
+ return JSON.stringify(value);
101
+
102
+ return value.toString();
103
+ }
104
+
105
+ return (
106
+ <div style={{ overflow: 'auto', width: '100%' }}>
107
+ { (title || description || buttons && buttons.length > 0) &&
108
+ <TableToolbar
109
+ title={title}
110
+ description={description}
111
+ selectedRows={Object.values(selectedRows)}
112
+ buttons={buttons}
113
+ />
114
+ }
115
+ <TableContainer>
116
+ <MuiTable stickyHeader>
117
+ <TableHead>
118
+ <TableRow>
119
+ { buttons && buttons.length > 0 &&
120
+ <TableCell padding='checkbox'>
121
+ <Checkbox
122
+ checked={selectAll}
123
+ onChange={(event, selected) => toggleSelectAll(selected)}
124
+ inputProps={{
125
+ 'aria-label': 'Select all'
126
+ }}
127
+ />
128
+ </TableCell>
129
+ }
130
+ { columns.map((column, index) => (
131
+ <TableCell
132
+ key={index}
133
+ >
134
+ <Typography variant='h6'>
135
+ { StringUtil.humanizeCamel(column as string) }
136
+ </Typography>
137
+ </TableCell>
138
+ ))}
139
+ </TableRow>
140
+ </TableHead>
141
+ <TableBody>
142
+ { rows.map((row, index) => {
143
+ index = rowsPerPage * page + index;
144
+ return (
145
+ <TableRow
146
+ hover
147
+ role='checkbox'
148
+ tabIndex={-1}
149
+ key={index}
150
+ selected={typeof selectedRows[index] !== 'undefined'}
151
+ >
152
+ { buttons && buttons.length > 0 &&
153
+ <TableCell padding='checkbox'>
154
+ <Checkbox
155
+ checked={typeof selectedRows[index] !== 'undefined'}
156
+ onChange={(event, value) => toggleSelectRow(index, row)}
157
+ inputProps={{
158
+ 'aria-label': 'Select row'
159
+ }}
160
+ />
161
+ </TableCell>
162
+ }
163
+ { columns.map((column, index) => {
164
+ const cellValue = formatCellValue(row[column]);
165
+ return (
166
+ <TableCell
167
+ key={index}
168
+ onClick={(event: any) => handleRowOnClick(row)}
169
+ >
170
+ <Typography
171
+ variant='subtitle1'
172
+ >
173
+ {cellValue}
174
+ </Typography>
175
+ </TableCell>
176
+ );
177
+ })}
178
+ </TableRow>
179
+ );
180
+ })}
181
+ </TableBody>
182
+ </MuiTable>
183
+ </TableContainer>
184
+ <TablePagination
185
+ rowsPerPageOptions={[5, 10, 25, 50, 100, 200]}
186
+ component='div'
187
+ count={totalRows}
188
+ rowsPerPage={rowsPerPage}
189
+ page={page}
190
+ onPageChange={(event, newPage) => setPage(newPage)}
191
+ onRowsPerPageChange={event => updateRowsPerPage(parseInt(event.target.value))}
192
+ />
193
+ </div>
194
+ );
195
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react'
2
+ import { NavigateFunction } from 'react-router'
3
+
4
+ export type TableButton<T> = {
5
+ name: string,
6
+ icon: React.ComponentType,
7
+ visibility: {
8
+ showWhenRowsSelected: boolean,
9
+ showWhenNoRowsSelected: boolean,
10
+ },
11
+ onClick: (selectedRows: T[], navigate: NavigateFunction) => Promise<void>,
12
+ }
@@ -0,0 +1,19 @@
1
+ export type RowWindow<T> = {
2
+ rows: T[],
3
+ totalCount: number,
4
+ }
5
+
6
+ export type TableLoader<T> = {
7
+ load: (startIndex: number, endIndex: number) => Promise<RowWindow<T>>,
8
+ }
9
+
10
+ export class StaticTableLoader<T> implements TableLoader<T> {
11
+ constructor(private list: T[]) {}
12
+
13
+ async load(startIndex: number, endIndex: number) {
14
+ return {
15
+ rows: this.list.slice(startIndex, endIndex),
16
+ totalCount: this.list.length,
17
+ };
18
+ }
19
+ }
@@ -0,0 +1,99 @@
1
+ import React from 'react'
2
+ import { TableButton } from './TableButton'
3
+ import { IconButton, Toolbar, Tooltip, Typography, lighten } from '@mui/material'
4
+ import { useNavigate } from 'react-router'
5
+
6
+ export type TableToolbarProps = {
7
+ title?: string,
8
+ selectedRows: any[],
9
+ description?: () => JSX.Element,
10
+ buttons?: TableButton<any>[],
11
+ }
12
+
13
+ export const TableToolbar = (props : TableToolbarProps) => {
14
+ const { title, selectedRows, buttons } = props;
15
+ const navigate = useNavigate();
16
+ return (
17
+ <Toolbar
18
+ sx={(theme) => {
19
+ if (selectedRows.length > 0) {
20
+ return theme.palette.mode === 'light'
21
+ ? {
22
+ color: theme.palette.info.main,
23
+ backgroundColor: lighten(theme.palette.info.light, 0.85),
24
+ }
25
+ : {
26
+ color: theme.palette.info.light,
27
+ backgroundColor: theme.palette.info.dark,
28
+ };
29
+ }
30
+
31
+ return {
32
+ paddingLeft: theme.spacing(2),
33
+ paddingRight: theme.spacing(1),
34
+ };
35
+ }}
36
+ >
37
+ <div
38
+ style={{
39
+ marginLeft: 4,
40
+ flex: '0 0 auto',
41
+ }}
42
+ >
43
+ { selectedRows.length > 0 ? (
44
+ <Typography variant='subtitle1' color='inherit'>
45
+ {selectedRows.length} rows selected
46
+ </Typography>
47
+ ) : (
48
+ <div>
49
+ { typeof title !== 'undefined' &&
50
+ <Typography variant='h5'>
51
+ {title}
52
+ </Typography>
53
+ }
54
+ { typeof props.description !== 'undefined' &&
55
+ <props.description />
56
+ }
57
+ </div>
58
+ )}
59
+ </div>
60
+ <div
61
+ style={{
62
+ flex: '1 1 100%',
63
+ }}
64
+ />
65
+ <div>
66
+ <Buttons />
67
+ </div>
68
+ </Toolbar>
69
+ );
70
+
71
+ function Buttons() {
72
+ if (!buttons)
73
+ return null;
74
+
75
+ if (selectedRows.length > 0) {
76
+ return buttons.filter((button) => button.visibility.showWhenRowsSelected).map((button, index) => (
77
+ <Tooltip key={index} title={button.name}>
78
+ <IconButton
79
+ aria-label={button.name}
80
+ onClick={event => button.onClick(selectedRows, navigate)}
81
+ >
82
+ <button.icon />
83
+ </IconButton>
84
+ </Tooltip>
85
+ ));
86
+ }
87
+
88
+ return buttons.filter((button) => button.visibility.showWhenNoRowsSelected).map((button, index) => (
89
+ <Tooltip key={index} title={button.name}>
90
+ <IconButton
91
+ aria-label={button.name}
92
+ onClick={event => button.onClick([], navigate)}
93
+ >
94
+ <button.icon />
95
+ </IconButton>
96
+ </Tooltip>
97
+ ));
98
+ }
99
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./",
4
+ "target": "es5",
5
+ "module": "commonjs",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist/",
10
+ "strict": true,
11
+ "noImplicitAny": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "jsx": "react"
17
+ }
18
+ }