@etsoo/materialui 1.4.99 → 1.5.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/lib/cjs/ViewContainer.d.ts +107 -0
- package/lib/cjs/ViewContainer.js +230 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/pages/ViewPage.d.ts +2 -95
- package/lib/cjs/pages/ViewPage.js +10 -227
- package/lib/mjs/ViewContainer.d.ts +107 -0
- package/lib/mjs/ViewContainer.js +222 -0
- package/lib/mjs/index.d.ts +1 -0
- package/lib/mjs/index.js +1 -0
- package/lib/mjs/pages/ViewPage.d.ts +2 -95
- package/lib/mjs/pages/ViewPage.js +6 -221
- package/package.json +4 -4
- package/src/ViewContainer.tsx +436 -0
- package/src/index.ts +1 -0
- package/src/pages/ViewPage.tsx +18 -420
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { Breakpoint, Grid2, Grid2Props, Typography } from "@mui/material";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { MUGlobal } from "./MUGlobal";
|
|
4
|
+
import { DataTypes } from "@etsoo/shared";
|
|
5
|
+
import { useCurrentBreakpoint } from "./useCurrentBreakpoint";
|
|
6
|
+
import { GridColumnRenderProps, GridDataType } from "@etsoo/react";
|
|
7
|
+
import { GridDataFormat } from "./GridDataFormat";
|
|
8
|
+
import { ReactAppType, useRequiredAppContext } from "./app/ReactApp";
|
|
9
|
+
|
|
10
|
+
function formatItemData(
|
|
11
|
+
app: ReactAppType,
|
|
12
|
+
fieldData: unknown
|
|
13
|
+
): string | undefined {
|
|
14
|
+
if (fieldData == null) return undefined;
|
|
15
|
+
if (typeof fieldData === "string") return fieldData;
|
|
16
|
+
if (fieldData instanceof Date) return app.formatDate(fieldData, "d");
|
|
17
|
+
return `${fieldData}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getResp(singleRow: ViewPageRowType) {
|
|
21
|
+
const size =
|
|
22
|
+
typeof singleRow === "object"
|
|
23
|
+
? singleRow
|
|
24
|
+
: singleRow === "medium"
|
|
25
|
+
? ViewPageSize.medium
|
|
26
|
+
: singleRow === "large"
|
|
27
|
+
? ViewPageSize.large
|
|
28
|
+
: singleRow === true
|
|
29
|
+
? ViewPageSize.line
|
|
30
|
+
: singleRow === false
|
|
31
|
+
? ViewPageSize.smallLine
|
|
32
|
+
: ViewPageSize.small;
|
|
33
|
+
return size;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getItemField<T extends object>(
|
|
37
|
+
app: ReactAppType,
|
|
38
|
+
field: ViewPageFieldTypeNarrow<T>,
|
|
39
|
+
data: T
|
|
40
|
+
): [React.ReactNode, React.ReactNode, Grid2Props, ViewPageItemSize] {
|
|
41
|
+
// Item data and label
|
|
42
|
+
let itemData: React.ReactNode,
|
|
43
|
+
itemLabel: React.ReactNode,
|
|
44
|
+
gridProps: Grid2Props = {},
|
|
45
|
+
size: ViewPageItemSize;
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(field)) {
|
|
48
|
+
const [fieldData, fieldType, renderProps, singleRow = "small"] = field;
|
|
49
|
+
itemData = GridDataFormat(data[fieldData], fieldType, renderProps);
|
|
50
|
+
itemLabel = app.get<string>(fieldData) ?? fieldData;
|
|
51
|
+
size = getResp(singleRow);
|
|
52
|
+
gridProps = { size };
|
|
53
|
+
} else if (typeof field === "object") {
|
|
54
|
+
// Destruct
|
|
55
|
+
const {
|
|
56
|
+
data: fieldData,
|
|
57
|
+
dataType,
|
|
58
|
+
label: fieldLabel,
|
|
59
|
+
renderProps,
|
|
60
|
+
singleRow = "default",
|
|
61
|
+
...rest
|
|
62
|
+
} = field;
|
|
63
|
+
|
|
64
|
+
// Size
|
|
65
|
+
size = getResp(singleRow);
|
|
66
|
+
|
|
67
|
+
gridProps = {
|
|
68
|
+
...rest,
|
|
69
|
+
size
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Field data
|
|
73
|
+
if (typeof fieldData === "function") itemData = fieldData(data);
|
|
74
|
+
else if (dataType == null) itemData = formatItemData(app, data[fieldData]);
|
|
75
|
+
else itemData = GridDataFormat(data[fieldData], dataType, renderProps);
|
|
76
|
+
|
|
77
|
+
// Field label
|
|
78
|
+
itemLabel =
|
|
79
|
+
fieldLabel === ""
|
|
80
|
+
? undefined
|
|
81
|
+
: fieldLabel == null && typeof fieldData === "string"
|
|
82
|
+
? app.get<string>(fieldData) ?? fieldData
|
|
83
|
+
: typeof fieldLabel === "function"
|
|
84
|
+
? fieldLabel(data)
|
|
85
|
+
: fieldLabel != null
|
|
86
|
+
? app.get<string>(fieldLabel) ?? fieldLabel
|
|
87
|
+
: undefined;
|
|
88
|
+
} else {
|
|
89
|
+
// Single field format
|
|
90
|
+
itemData = formatItemData(app, data[field]);
|
|
91
|
+
itemLabel = app.get<string>(field) ?? field;
|
|
92
|
+
size = ViewPageSize.small;
|
|
93
|
+
gridProps = { size };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return [itemData, itemLabel, gridProps, size];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getItemSize(bp: Breakpoint, size: ViewPageItemSize) {
|
|
100
|
+
const v = size[bp];
|
|
101
|
+
if (v != null) return v;
|
|
102
|
+
|
|
103
|
+
const index = breakpoints.indexOf(bp);
|
|
104
|
+
for (let i = index; i >= 0; i--) {
|
|
105
|
+
const v = size[breakpoints[i]];
|
|
106
|
+
if (v != null) return v;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return 12;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* View page item size
|
|
114
|
+
*/
|
|
115
|
+
export type ViewPageItemSize = Record<Breakpoint, number | undefined>;
|
|
116
|
+
|
|
117
|
+
const breakpoints: Breakpoint[] = ["xs", "sm", "md", "lg", "xl"];
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* View page grid item size
|
|
121
|
+
*/
|
|
122
|
+
export namespace ViewPageSize {
|
|
123
|
+
export const large: ViewPageItemSize = {
|
|
124
|
+
xs: 12,
|
|
125
|
+
sm: 12,
|
|
126
|
+
md: 9,
|
|
127
|
+
lg: 6,
|
|
128
|
+
xl: 4
|
|
129
|
+
};
|
|
130
|
+
export const medium: ViewPageItemSize = {
|
|
131
|
+
xs: 12,
|
|
132
|
+
sm: 12,
|
|
133
|
+
md: 6,
|
|
134
|
+
lg: 4,
|
|
135
|
+
xl: 3
|
|
136
|
+
};
|
|
137
|
+
export const line: ViewPageItemSize = {
|
|
138
|
+
xs: 12,
|
|
139
|
+
sm: 12,
|
|
140
|
+
md: 12,
|
|
141
|
+
lg: 12,
|
|
142
|
+
xl: 12
|
|
143
|
+
};
|
|
144
|
+
export const small: ViewPageItemSize = { xs: 6, sm: 6, md: 4, lg: 3, xl: 2 };
|
|
145
|
+
export const smallLine: ViewPageItemSize = {
|
|
146
|
+
xs: 12,
|
|
147
|
+
sm: 6,
|
|
148
|
+
md: 4,
|
|
149
|
+
lg: 3,
|
|
150
|
+
xl: 2
|
|
151
|
+
};
|
|
152
|
+
export function matchSize(size: ViewPageItemSize) {
|
|
153
|
+
return Object.fromEntries(
|
|
154
|
+
Object.entries(size).map(([key, value]) => [
|
|
155
|
+
key,
|
|
156
|
+
value == null ? undefined : value === 12 ? 12 : 12 - value
|
|
157
|
+
])
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* View page row width type
|
|
164
|
+
*/
|
|
165
|
+
export type ViewPageRowType =
|
|
166
|
+
| boolean
|
|
167
|
+
| "default"
|
|
168
|
+
| "small"
|
|
169
|
+
| "medium"
|
|
170
|
+
| "large"
|
|
171
|
+
| ViewPageItemSize;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* View page grid item properties
|
|
175
|
+
*/
|
|
176
|
+
export type ViewPageGridItemProps = Grid2Props & {
|
|
177
|
+
data: React.ReactNode;
|
|
178
|
+
label?: React.ReactNode;
|
|
179
|
+
singleRow?: ViewPageRowType;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* View page grid item
|
|
184
|
+
* @param props Props
|
|
185
|
+
* @returns Result
|
|
186
|
+
*/
|
|
187
|
+
export function ViewPageGridItem(props: ViewPageGridItemProps) {
|
|
188
|
+
// Destruct
|
|
189
|
+
const { data, label, singleRow, ...gridProps } = props;
|
|
190
|
+
|
|
191
|
+
// Default options
|
|
192
|
+
let options = {};
|
|
193
|
+
if (gridProps.size == null) {
|
|
194
|
+
options = getResp(singleRow ?? "small");
|
|
195
|
+
} else if (singleRow != null) {
|
|
196
|
+
options = getResp(singleRow ?? "small");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Layout
|
|
200
|
+
return (
|
|
201
|
+
<Grid2 {...gridProps} {...options}>
|
|
202
|
+
{label != null && (
|
|
203
|
+
<Typography variant="caption" component="div">
|
|
204
|
+
{label}:
|
|
205
|
+
</Typography>
|
|
206
|
+
)}
|
|
207
|
+
{typeof data === "object" ? (
|
|
208
|
+
data
|
|
209
|
+
) : (
|
|
210
|
+
<Typography variant="subtitle2">{data}</Typography>
|
|
211
|
+
)}
|
|
212
|
+
</Grid2>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* View page display field
|
|
218
|
+
*/
|
|
219
|
+
export interface ViewPageField<T extends object> extends Grid2Props {
|
|
220
|
+
/**
|
|
221
|
+
* Data field
|
|
222
|
+
*/
|
|
223
|
+
data: (string & keyof T) | ((item: T) => React.ReactNode);
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Data type
|
|
227
|
+
*/
|
|
228
|
+
dataType?: GridDataType;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Label field
|
|
232
|
+
*/
|
|
233
|
+
label?: string | ((item: T) => React.ReactNode);
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Display as single row
|
|
237
|
+
*/
|
|
238
|
+
singleRow?: ViewPageRowType;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Render props
|
|
242
|
+
*/
|
|
243
|
+
renderProps?: GridColumnRenderProps;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
type ViewPageFieldTypeNarrow<T extends object> =
|
|
247
|
+
| (string & keyof T)
|
|
248
|
+
| [string & keyof T, GridDataType, GridColumnRenderProps?, ViewPageRowType?]
|
|
249
|
+
| ViewPageField<T>;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* View page field type
|
|
253
|
+
*/
|
|
254
|
+
export type ViewPageFieldType<T extends object> =
|
|
255
|
+
| ViewPageFieldTypeNarrow<T>
|
|
256
|
+
| ((
|
|
257
|
+
data: T,
|
|
258
|
+
refresh: () => Promise<void>
|
|
259
|
+
) => React.ReactNode | [React.ReactNode, ViewPageItemSize]);
|
|
260
|
+
|
|
261
|
+
export type ViewContainerProps<T extends DataTypes.StringRecord> = {
|
|
262
|
+
/**
|
|
263
|
+
* Data
|
|
264
|
+
*/
|
|
265
|
+
data: T;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Fields to display
|
|
269
|
+
*/
|
|
270
|
+
fields: ViewPageFieldType<T>[];
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Grid container reference
|
|
274
|
+
*/
|
|
275
|
+
gridRef?: React.Ref<HTMLDivElement>;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Left container
|
|
279
|
+
*/
|
|
280
|
+
leftContainer?: (data: T) => React.ReactNode;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Left container height in lines
|
|
284
|
+
*/
|
|
285
|
+
leftContainerLines?: number;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Left container properties
|
|
289
|
+
*/
|
|
290
|
+
leftContainerProps?: Omit<Grid2Props, "size"> & { size?: ViewPageItemSize };
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Refresh function
|
|
294
|
+
*/
|
|
295
|
+
refresh: () => Promise<void>;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Grid spacing
|
|
299
|
+
*/
|
|
300
|
+
spacing?: Record<string, string | number>;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export function ViewContainer<T extends DataTypes.StringRecord>(
|
|
304
|
+
props: ViewContainerProps<T>
|
|
305
|
+
) {
|
|
306
|
+
// Global app
|
|
307
|
+
const app = useRequiredAppContext();
|
|
308
|
+
|
|
309
|
+
// Destruct
|
|
310
|
+
const {
|
|
311
|
+
data,
|
|
312
|
+
fields,
|
|
313
|
+
gridRef,
|
|
314
|
+
leftContainer,
|
|
315
|
+
leftContainerLines = 3,
|
|
316
|
+
leftContainerProps = {},
|
|
317
|
+
refresh,
|
|
318
|
+
spacing = MUGlobal.half(MUGlobal.pagePaddings)
|
|
319
|
+
} = props;
|
|
320
|
+
|
|
321
|
+
// Left container
|
|
322
|
+
const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } =
|
|
323
|
+
leftContainerProps;
|
|
324
|
+
|
|
325
|
+
// Current breakpoint
|
|
326
|
+
const bp = useCurrentBreakpoint();
|
|
327
|
+
|
|
328
|
+
// Create fields
|
|
329
|
+
const fieldIndexRef = React.useRef(0);
|
|
330
|
+
const createFields = React.useCallback(
|
|
331
|
+
(data: T, maxItems: number = 0) => {
|
|
332
|
+
let validItems = 0;
|
|
333
|
+
const items: React.ReactNode[] = [];
|
|
334
|
+
let i: number = fieldIndexRef.current;
|
|
335
|
+
for (; i < fields.length; i++) {
|
|
336
|
+
const field = fields[i];
|
|
337
|
+
let oneSize: ViewPageItemSize;
|
|
338
|
+
let oneItem: React.ReactNode;
|
|
339
|
+
if (typeof field === "function") {
|
|
340
|
+
// Most flexible way, do whatever you want
|
|
341
|
+
const createdResult = field(data, refresh);
|
|
342
|
+
if (createdResult == null || createdResult === "") continue;
|
|
343
|
+
if (Array.isArray(createdResult)) {
|
|
344
|
+
const [created, size] = createdResult;
|
|
345
|
+
oneSize = size;
|
|
346
|
+
oneItem = created;
|
|
347
|
+
} else {
|
|
348
|
+
oneSize = ViewPageSize.line;
|
|
349
|
+
oneItem = createdResult;
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
const [itemData, itemLabel, gridProps, size] = getItemField(
|
|
353
|
+
app,
|
|
354
|
+
field,
|
|
355
|
+
data
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Some callback function may return '' instead of undefined
|
|
359
|
+
if (itemData == null || itemData === "") continue;
|
|
360
|
+
|
|
361
|
+
oneSize = size;
|
|
362
|
+
oneItem = (
|
|
363
|
+
<ViewPageGridItem
|
|
364
|
+
{...gridProps}
|
|
365
|
+
key={i}
|
|
366
|
+
data={itemData}
|
|
367
|
+
label={itemLabel}
|
|
368
|
+
/>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Max lines
|
|
373
|
+
if (maxItems > 0) {
|
|
374
|
+
const itemSize = getItemSize(bp, oneSize);
|
|
375
|
+
if (maxItems < validItems + itemSize) {
|
|
376
|
+
fieldIndexRef.current = i;
|
|
377
|
+
break;
|
|
378
|
+
} else {
|
|
379
|
+
items.push(oneItem);
|
|
380
|
+
validItems += itemSize;
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
items.push(oneItem);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (maxItems === 0) {
|
|
388
|
+
fieldIndexRef.current = 0;
|
|
389
|
+
} else {
|
|
390
|
+
fieldIndexRef.current = i;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return items;
|
|
394
|
+
},
|
|
395
|
+
[app, fields, data, bp]
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
let leftResult: React.ReactNode;
|
|
399
|
+
|
|
400
|
+
// Layout
|
|
401
|
+
return (
|
|
402
|
+
<Grid2
|
|
403
|
+
container
|
|
404
|
+
justifyContent="left"
|
|
405
|
+
className="ET-ViewContainer"
|
|
406
|
+
ref={gridRef}
|
|
407
|
+
spacing={spacing}
|
|
408
|
+
>
|
|
409
|
+
{leftContainer && (leftResult = leftContainer(data)) != null && (
|
|
410
|
+
<React.Fragment>
|
|
411
|
+
<Grid2
|
|
412
|
+
container
|
|
413
|
+
className="ET-ViewPage-LeftContainer"
|
|
414
|
+
spacing={spacing}
|
|
415
|
+
size={size}
|
|
416
|
+
{...leftContainerPropsRest}
|
|
417
|
+
>
|
|
418
|
+
{leftResult}
|
|
419
|
+
</Grid2>
|
|
420
|
+
<Grid2
|
|
421
|
+
container
|
|
422
|
+
className="ET-ViewPage-LeftOthers"
|
|
423
|
+
spacing={spacing}
|
|
424
|
+
size={ViewPageSize.matchSize(size)}
|
|
425
|
+
>
|
|
426
|
+
{createFields(
|
|
427
|
+
data,
|
|
428
|
+
leftContainerLines * (12 - getItemSize(bp, size))
|
|
429
|
+
)}
|
|
430
|
+
</Grid2>
|
|
431
|
+
</React.Fragment>
|
|
432
|
+
)}
|
|
433
|
+
{createFields(data)}
|
|
434
|
+
</Grid2>
|
|
435
|
+
);
|
|
436
|
+
}
|
package/src/index.ts
CHANGED