@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.
@@ -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
@@ -122,3 +122,4 @@ export * from "./useCurrentBreakpoint";
122
122
  export * from "./TooltipClick";
123
123
  export * from "./UserAvatar";
124
124
  export * from "./UserAvatarEditor";
125
+ export * from "./ViewContainer";