@etsoo/materialui 1.4.90 → 1.4.92

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.
@@ -112,6 +112,7 @@ export * from "./TiplistPro";
112
112
  export * from "./TagList";
113
113
  export * from "./TagListPro";
114
114
  export * from "./TwoFieldInput";
115
+ export * from "./useCurrentBreakpoint";
115
116
  export * from "./TooltipClick";
116
117
  export * from "./UserAvatar";
117
118
  export * from "./UserAvatarEditor";
package/lib/cjs/index.js CHANGED
@@ -128,6 +128,7 @@ __exportStar(require("./TiplistPro"), exports);
128
128
  __exportStar(require("./TagList"), exports);
129
129
  __exportStar(require("./TagListPro"), exports);
130
130
  __exportStar(require("./TwoFieldInput"), exports);
131
+ __exportStar(require("./useCurrentBreakpoint"), exports);
131
132
  __exportStar(require("./TooltipClick"), exports);
132
133
  __exportStar(require("./UserAvatar"), exports);
133
134
  __exportStar(require("./UserAvatarEditor"), exports);
@@ -1,9 +1,25 @@
1
1
  import { GridColumnRenderProps, GridDataType } from "@etsoo/react";
2
2
  import { DataTypes } from "@etsoo/shared";
3
- import { Grid2Props } from "@mui/material";
3
+ import { Breakpoint, Grid2Props } from "@mui/material";
4
4
  import React from "react";
5
5
  import { CommonPageProps } from "./CommonPage";
6
6
  import type { OperationMessageHandlerAll } from "../messages/OperationMessageHandler";
7
+ /**
8
+ * View page item size
9
+ */
10
+ export type ViewPageItemSize = Record<Breakpoint, number | undefined>;
11
+ /**
12
+ * View page grid item size
13
+ */
14
+ export declare namespace ViewPageSize {
15
+ const medium: ViewPageItemSize;
16
+ const line: ViewPageItemSize;
17
+ const small: ViewPageItemSize;
18
+ const smallLine: ViewPageItemSize;
19
+ function matchSize(size: ViewPageItemSize): {
20
+ [k: string]: number | undefined;
21
+ };
22
+ }
7
23
  /**
8
24
  * View page grid item properties
9
25
  */
@@ -21,7 +37,7 @@ export declare function ViewPageGridItem(props: ViewPageGridItemProps): import("
21
37
  /**
22
38
  * View page row width type
23
39
  */
24
- export type ViewPageRowType = boolean | "default" | "small" | "medium" | object;
40
+ export type ViewPageRowType = boolean | "default" | "small" | "medium" | ViewPageItemSize;
25
41
  /**
26
42
  * View page display field
27
43
  */
@@ -51,7 +67,7 @@ type ViewPageFieldTypeNarrow<T extends object> = (string & keyof T) | [string &
51
67
  /**
52
68
  * View page field type
53
69
  */
54
- export type ViewPageFieldType<T extends object> = ViewPageFieldTypeNarrow<T> | ((data: T, refresh: () => Promise<void>) => React.ReactNode);
70
+ export type ViewPageFieldType<T extends object> = ViewPageFieldTypeNarrow<T> | ((data: T, refresh: () => Promise<void>) => React.ReactNode | [React.ReactNode, ViewPageItemSize]);
55
71
  /**
56
72
  * View page props
57
73
  */
@@ -95,6 +111,26 @@ export interface ViewPageProps<T extends DataTypes.StringRecord> extends Omit<Co
95
111
  id: number;
96
112
  types: string[];
97
113
  };
114
+ /**
115
+ * Title bar
116
+ * @param data Data to render
117
+ * @returns
118
+ */
119
+ titleBar?: (data: T) => React.ReactNode;
120
+ /**
121
+ * Left container
122
+ */
123
+ leftContainer?: (data: T) => React.ReactNode;
124
+ /**
125
+ * Left container height in lines
126
+ */
127
+ leftContainerLines?: number;
128
+ /**
129
+ * Left container properties
130
+ */
131
+ leftContainerProps?: Omit<Grid2Props, "size"> & {
132
+ size?: ViewPageItemSize;
133
+ };
98
134
  }
99
135
  /**
100
136
  * View page
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ViewPageSize = void 0;
6
7
  exports.ViewPageGridItem = ViewPageGridItem;
7
8
  exports.ViewPage = ViewPage;
8
9
  const react_1 = require("react");
@@ -19,6 +20,43 @@ const CommonPage_1 = require("./CommonPage");
19
20
  const MessageUtils_1 = require("../messages/MessageUtils");
20
21
  const OperationMessageContainer_1 = require("../messages/OperationMessageContainer");
21
22
  const ReactApp_1 = require("../app/ReactApp");
23
+ const useCurrentBreakpoint_1 = require("../useCurrentBreakpoint");
24
+ const breakpoints = ["xs", "sm", "md", "lg", "xl"];
25
+ /**
26
+ * View page grid item size
27
+ */
28
+ var ViewPageSize;
29
+ (function (ViewPageSize) {
30
+ ViewPageSize.medium = {
31
+ xs: 12,
32
+ sm: 12,
33
+ md: 6,
34
+ lg: 4,
35
+ xl: 3
36
+ };
37
+ ViewPageSize.line = {
38
+ xs: 12,
39
+ sm: 12,
40
+ md: 12,
41
+ lg: 12,
42
+ xl: 12
43
+ };
44
+ ViewPageSize.small = { xs: 6, sm: 6, md: 4, lg: 3, xl: 2 };
45
+ ViewPageSize.smallLine = {
46
+ xs: 12,
47
+ sm: 6,
48
+ md: 4,
49
+ lg: 3,
50
+ xl: 2
51
+ };
52
+ function matchSize(size) {
53
+ return Object.fromEntries(Object.entries(size).map(([key, value]) => [
54
+ key,
55
+ value == null ? undefined : value === 12 ? 12 : 12 - value
56
+ ]));
57
+ }
58
+ ViewPageSize.matchSize = matchSize;
59
+ })(ViewPageSize || (exports.ViewPageSize = ViewPageSize = {}));
22
60
  /**
23
61
  * View page grid item
24
62
  * @param props Props
@@ -51,33 +89,32 @@ function getResp(singleRow) {
51
89
  const size = typeof singleRow === "object"
52
90
  ? singleRow
53
91
  : singleRow === "medium"
54
- ? { xs: 12, sm: 12, md: 6, lg: 4, xl: 3 }
92
+ ? ViewPageSize.medium
55
93
  : singleRow === true
56
- ? { xs: 12, sm: 12, md: 12, lg: 12, xl: 12 }
57
- : {
58
- xs: singleRow === false ? 12 : 6,
59
- sm: 6,
60
- md: 4,
61
- lg: 3,
62
- xl: 2
63
- };
64
- return { size };
94
+ ? ViewPageSize.line
95
+ : singleRow === false
96
+ ? ViewPageSize.smallLine
97
+ : ViewPageSize.small;
98
+ return size;
65
99
  }
66
100
  function getItemField(app, field, data) {
67
101
  // Item data and label
68
- let itemData, itemLabel, gridProps = {};
102
+ let itemData, itemLabel, gridProps = {}, size;
69
103
  if (Array.isArray(field)) {
70
104
  const [fieldData, fieldType, renderProps, singleRow = "small"] = field;
71
105
  itemData = (0, GridDataFormat_1.GridDataFormat)(data[fieldData], fieldType, renderProps);
72
106
  itemLabel = app.get(fieldData) ?? fieldData;
73
- gridProps = { ...getResp(singleRow) };
107
+ size = getResp(singleRow);
108
+ gridProps = { size };
74
109
  }
75
110
  else if (typeof field === "object") {
76
111
  // Destruct
77
112
  const { data: fieldData, dataType, label: fieldLabel, renderProps, singleRow = "default", ...rest } = field;
113
+ // Size
114
+ size = getResp(singleRow);
78
115
  gridProps = {
79
116
  ...rest,
80
- ...getResp(singleRow)
117
+ size
81
118
  };
82
119
  // Field data
83
120
  if (typeof fieldData === "function")
@@ -102,8 +139,22 @@ function getItemField(app, field, data) {
102
139
  // Single field format
103
140
  itemData = formatItemData(app, data[field]);
104
141
  itemLabel = app.get(field) ?? field;
142
+ size = ViewPageSize.small;
143
+ gridProps = { size };
144
+ }
145
+ return [itemData, itemLabel, gridProps, size];
146
+ }
147
+ function getItemSize(bp, size) {
148
+ const v = size[bp];
149
+ if (v != null)
150
+ return v;
151
+ const index = breakpoints.indexOf(bp);
152
+ for (let i = index; i >= 0; i--) {
153
+ const v = size[breakpoints[i]];
154
+ if (v != null)
155
+ return v;
105
156
  }
106
- return [itemData, itemLabel, gridProps];
157
+ return 12;
107
158
  }
108
159
  /**
109
160
  * View page
@@ -113,19 +164,80 @@ function ViewPage(props) {
113
164
  // Global app
114
165
  const app = (0, ReactApp_1.useRequiredAppContext)();
115
166
  // Destruct
116
- const { actions, children, fields, loadData, paddings = MUGlobal_1.MUGlobal.pagePaddings, spacing = MUGlobal_1.MUGlobal.half(MUGlobal_1.MUGlobal.pagePaddings), supportRefresh = true, fabColumnDirection = true, fabTop = true, supportBack = true, pullToRefresh = true, gridRef, operationMessageHandler, ...rest } = props;
167
+ const { actions, children, fields, loadData, paddings = MUGlobal_1.MUGlobal.pagePaddings, spacing = MUGlobal_1.MUGlobal.half(MUGlobal_1.MUGlobal.pagePaddings), supportRefresh = true, fabColumnDirection = true, fabTop = true, supportBack = true, pullToRefresh = true, gridRef, operationMessageHandler, titleBar, leftContainer, leftContainerLines = 3, leftContainerProps = {}, ...rest } = props;
168
+ // Current breakpoint
169
+ const bp = (0, useCurrentBreakpoint_1.useCurrentBreakpoint)();
117
170
  // Data
118
171
  const [data, setData] = react_3.default.useState();
119
172
  // Labels
120
173
  const labels = Labels_1.Labels.CommonPage;
121
174
  // Container
122
175
  const pullContainer = "#page-container";
176
+ // Left container
177
+ const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } = leftContainerProps;
123
178
  // Load data
124
179
  const refresh = react_3.default.useCallback(async () => {
125
180
  const result = await loadData();
126
181
  // When failed or no data returned, show the loading bar
127
182
  setData(result);
128
183
  }, [loadData]);
184
+ // Create fields
185
+ const fieldIndexRef = react_3.default.useRef(0);
186
+ const createFields = react_3.default.useCallback((data, maxItems = 0) => {
187
+ let validItems = 0;
188
+ const items = [];
189
+ let i = fieldIndexRef.current;
190
+ for (; i < fields.length; i++) {
191
+ const field = fields[i];
192
+ let oneSize;
193
+ let oneItem;
194
+ if (typeof field === "function") {
195
+ // Most flexible way, do whatever you want
196
+ const createdResult = field(data, refresh);
197
+ if (createdResult == null || createdResult === "")
198
+ continue;
199
+ if (Array.isArray(createdResult)) {
200
+ const [created, size] = createdResult;
201
+ oneSize = size;
202
+ oneItem = created;
203
+ }
204
+ else {
205
+ oneSize = ViewPageSize.line;
206
+ oneItem = createdResult;
207
+ }
208
+ }
209
+ else {
210
+ const [itemData, itemLabel, gridProps, size] = getItemField(app, field, data);
211
+ // Some callback function may return '' instead of undefined
212
+ if (itemData == null || itemData === "")
213
+ continue;
214
+ oneSize = size;
215
+ oneItem = ((0, react_1.createElement)(ViewPageGridItem, { ...gridProps, key: i, data: itemData, label: itemLabel }));
216
+ }
217
+ // Max lines
218
+ if (maxItems > 0) {
219
+ const itemSize = getItemSize(bp, oneSize);
220
+ if (maxItems < validItems + itemSize) {
221
+ fieldIndexRef.current = i;
222
+ break;
223
+ }
224
+ else {
225
+ items.push(oneItem);
226
+ validItems += itemSize;
227
+ }
228
+ }
229
+ else {
230
+ items.push(oneItem);
231
+ }
232
+ }
233
+ if (maxItems === 0) {
234
+ fieldIndexRef.current = 0;
235
+ }
236
+ else {
237
+ fieldIndexRef.current = i;
238
+ }
239
+ return items;
240
+ }, [app, refresh, fields, data, bp]);
129
241
  react_3.default.useEffect(() => {
130
242
  const refreshHandler = async () => {
131
243
  await refresh();
@@ -135,29 +247,18 @@ function ViewPage(props) {
135
247
  MessageUtils_1.MessageUtils.offRefresh(refreshHandler);
136
248
  };
137
249
  }, [refresh]);
138
- return ((0, jsx_runtime_1.jsx)(CommonPage_1.CommonPage, { paddings: paddings, onRefresh: supportRefresh ? refresh : undefined, onUpdate: supportRefresh ? undefined : refresh, ...rest, scrollContainer: globalThis, fabColumnDirection: fabColumnDirection, fabTop: fabTop, supportBack: supportBack, children: data == null ? ((0, jsx_runtime_1.jsx)(material_1.LinearProgress, {})) : ((0, jsx_runtime_1.jsxs)(react_3.default.Fragment, { children: [operationMessageHandler && ((0, jsx_runtime_1.jsx)(OperationMessageContainer_1.OperationMessageContainer, { handler: "id" in operationMessageHandler
250
+ let leftResult;
251
+ return ((0, jsx_runtime_1.jsx)(CommonPage_1.CommonPage, { paddings: paddings, onRefresh: supportRefresh ? refresh : undefined, onUpdate: supportRefresh ? undefined : refresh, sx: {
252
+ ".MuiTypography-subtitle2": {
253
+ fontWeight: "bold"
254
+ }
255
+ }, ...rest, scrollContainer: globalThis, fabColumnDirection: fabColumnDirection, fabTop: fabTop, supportBack: supportBack, children: data == null ? ((0, jsx_runtime_1.jsx)(material_1.LinearProgress, {})) : ((0, jsx_runtime_1.jsxs)(react_3.default.Fragment, { children: [operationMessageHandler && ((0, jsx_runtime_1.jsx)(OperationMessageContainer_1.OperationMessageContainer, { handler: "id" in operationMessageHandler
139
256
  ? [
140
257
  operationMessageHandler.types,
141
258
  refresh,
142
259
  operationMessageHandler.id
143
260
  ]
144
- : operationMessageHandler })), (0, jsx_runtime_1.jsx)(material_1.Grid2, { container: true, justifyContent: "left", spacing: spacing, className: "ET-ViewPage", ref: gridRef, sx: {
145
- ".MuiTypography-subtitle2": {
146
- fontWeight: "bold"
147
- }
148
- }, children: fields.map((field, index) => {
149
- // Get data
150
- if (typeof field === "function") {
151
- // Most flexible way, do whatever you want
152
- return field(data, refresh);
153
- }
154
- const [itemData, itemLabel, gridProps] = getItemField(app, field, data);
155
- // Some callback function may return '' instead of undefined
156
- if (itemData == null || itemData === "")
157
- return undefined;
158
- // Layout
159
- return ((0, react_1.createElement)(ViewPageGridItem, { ...gridProps, key: index, data: itemData, label: itemLabel }));
160
- }) }), actions !== null && ((0, jsx_runtime_1.jsx)(material_1.Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: actions == null ? undefined : paddings, paddingBottom: paddings, gap: paddings, children: actions != null && shared_1.Utils.getResult(actions, data, refresh) })), shared_1.Utils.getResult(children, data, refresh), pullToRefresh && ((0, jsx_runtime_1.jsx)(PullToRefreshUI_1.PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
261
+ : operationMessageHandler })), titleBar && titleBar(data), (0, jsx_runtime_1.jsxs)(material_1.Grid2, { container: true, justifyContent: "left", className: "ET-ViewPage", ref: gridRef, spacing: spacing, children: [leftContainer && (leftResult = leftContainer(data)) != null && ((0, jsx_runtime_1.jsxs)(react_3.default.Fragment, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid2, { container: true, className: "ET-ViewPage-LeftContainer", spacing: spacing, size: size, ...leftContainerPropsRest, children: leftResult }), (0, jsx_runtime_1.jsx)(material_1.Grid2, { container: true, className: "ET-ViewPage-LeftOthers", spacing: spacing, size: ViewPageSize.matchSize(size), children: createFields(data, leftContainerLines * (12 - getItemSize(bp, size))) })] })), createFields(data)] }), actions !== null && ((0, jsx_runtime_1.jsx)(material_1.Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: actions == null ? undefined : paddings, paddingBottom: paddings, gap: paddings, children: actions != null && shared_1.Utils.getResult(actions, data, refresh) })), shared_1.Utils.getResult(children, data, refresh), pullToRefresh && ((0, jsx_runtime_1.jsx)(PullToRefreshUI_1.PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
161
262
  const container = document.querySelector(pullContainer);
162
263
  return !container?.scrollTop;
163
264
  } })), (0, jsx_runtime_1.jsx)(react_2.ScrollRestoration, {})] })) }));
@@ -0,0 +1,6 @@
1
+ import { Breakpoint } from "@mui/material";
2
+ /**
3
+ * Hook to get the current breakpoint
4
+ * @returns The current breakpoint
5
+ */
6
+ export declare function useCurrentBreakpoint(): Breakpoint;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCurrentBreakpoint = useCurrentBreakpoint;
4
+ const material_1 = require("@mui/material");
5
+ /**
6
+ * Hook to get the current breakpoint
7
+ * @returns The current breakpoint
8
+ */
9
+ function useCurrentBreakpoint() {
10
+ const theme = (0, material_1.useTheme)();
11
+ const items = [
12
+ (0, material_1.useMediaQuery)(theme.breakpoints.down("xs")) ? "xs" : null,
13
+ (0, material_1.useMediaQuery)(theme.breakpoints.between("xs", "sm")) ? "sm" : null,
14
+ (0, material_1.useMediaQuery)(theme.breakpoints.between("sm", "md")) ? "md" : null,
15
+ (0, material_1.useMediaQuery)(theme.breakpoints.between("md", "lg")) ? "lg" : null,
16
+ (0, material_1.useMediaQuery)(theme.breakpoints.up("lg")) ? "xl" : null
17
+ ];
18
+ return items.find((item) => item != null) ?? "lg";
19
+ }
@@ -112,6 +112,7 @@ export * from "./TiplistPro";
112
112
  export * from "./TagList";
113
113
  export * from "./TagListPro";
114
114
  export * from "./TwoFieldInput";
115
+ export * from "./useCurrentBreakpoint";
115
116
  export * from "./TooltipClick";
116
117
  export * from "./UserAvatar";
117
118
  export * from "./UserAvatarEditor";
package/lib/mjs/index.js CHANGED
@@ -112,6 +112,7 @@ export * from "./TiplistPro";
112
112
  export * from "./TagList";
113
113
  export * from "./TagListPro";
114
114
  export * from "./TwoFieldInput";
115
+ export * from "./useCurrentBreakpoint";
115
116
  export * from "./TooltipClick";
116
117
  export * from "./UserAvatar";
117
118
  export * from "./UserAvatarEditor";
@@ -1,9 +1,25 @@
1
1
  import { GridColumnRenderProps, GridDataType } from "@etsoo/react";
2
2
  import { DataTypes } from "@etsoo/shared";
3
- import { Grid2Props } from "@mui/material";
3
+ import { Breakpoint, Grid2Props } from "@mui/material";
4
4
  import React from "react";
5
5
  import { CommonPageProps } from "./CommonPage";
6
6
  import type { OperationMessageHandlerAll } from "../messages/OperationMessageHandler";
7
+ /**
8
+ * View page item size
9
+ */
10
+ export type ViewPageItemSize = Record<Breakpoint, number | undefined>;
11
+ /**
12
+ * View page grid item size
13
+ */
14
+ export declare namespace ViewPageSize {
15
+ const medium: ViewPageItemSize;
16
+ const line: ViewPageItemSize;
17
+ const small: ViewPageItemSize;
18
+ const smallLine: ViewPageItemSize;
19
+ function matchSize(size: ViewPageItemSize): {
20
+ [k: string]: number | undefined;
21
+ };
22
+ }
7
23
  /**
8
24
  * View page grid item properties
9
25
  */
@@ -21,7 +37,7 @@ export declare function ViewPageGridItem(props: ViewPageGridItemProps): import("
21
37
  /**
22
38
  * View page row width type
23
39
  */
24
- export type ViewPageRowType = boolean | "default" | "small" | "medium" | object;
40
+ export type ViewPageRowType = boolean | "default" | "small" | "medium" | ViewPageItemSize;
25
41
  /**
26
42
  * View page display field
27
43
  */
@@ -51,7 +67,7 @@ type ViewPageFieldTypeNarrow<T extends object> = (string & keyof T) | [string &
51
67
  /**
52
68
  * View page field type
53
69
  */
54
- export type ViewPageFieldType<T extends object> = ViewPageFieldTypeNarrow<T> | ((data: T, refresh: () => Promise<void>) => React.ReactNode);
70
+ export type ViewPageFieldType<T extends object> = ViewPageFieldTypeNarrow<T> | ((data: T, refresh: () => Promise<void>) => React.ReactNode | [React.ReactNode, ViewPageItemSize]);
55
71
  /**
56
72
  * View page props
57
73
  */
@@ -95,6 +111,26 @@ export interface ViewPageProps<T extends DataTypes.StringRecord> extends Omit<Co
95
111
  id: number;
96
112
  types: string[];
97
113
  };
114
+ /**
115
+ * Title bar
116
+ * @param data Data to render
117
+ * @returns
118
+ */
119
+ titleBar?: (data: T) => React.ReactNode;
120
+ /**
121
+ * Left container
122
+ */
123
+ leftContainer?: (data: T) => React.ReactNode;
124
+ /**
125
+ * Left container height in lines
126
+ */
127
+ leftContainerLines?: number;
128
+ /**
129
+ * Left container properties
130
+ */
131
+ leftContainerProps?: Omit<Grid2Props, "size"> & {
132
+ size?: ViewPageItemSize;
133
+ };
98
134
  }
99
135
  /**
100
136
  * View page
@@ -12,6 +12,43 @@ import { CommonPage } from "./CommonPage";
12
12
  import { MessageUtils } from "../messages/MessageUtils";
13
13
  import { OperationMessageContainer } from "../messages/OperationMessageContainer";
14
14
  import { useRequiredAppContext } from "../app/ReactApp";
15
+ import { useCurrentBreakpoint } from "../useCurrentBreakpoint";
16
+ const breakpoints = ["xs", "sm", "md", "lg", "xl"];
17
+ /**
18
+ * View page grid item size
19
+ */
20
+ export var ViewPageSize;
21
+ (function (ViewPageSize) {
22
+ ViewPageSize.medium = {
23
+ xs: 12,
24
+ sm: 12,
25
+ md: 6,
26
+ lg: 4,
27
+ xl: 3
28
+ };
29
+ ViewPageSize.line = {
30
+ xs: 12,
31
+ sm: 12,
32
+ md: 12,
33
+ lg: 12,
34
+ xl: 12
35
+ };
36
+ ViewPageSize.small = { xs: 6, sm: 6, md: 4, lg: 3, xl: 2 };
37
+ ViewPageSize.smallLine = {
38
+ xs: 12,
39
+ sm: 6,
40
+ md: 4,
41
+ lg: 3,
42
+ xl: 2
43
+ };
44
+ function matchSize(size) {
45
+ return Object.fromEntries(Object.entries(size).map(([key, value]) => [
46
+ key,
47
+ value == null ? undefined : value === 12 ? 12 : 12 - value
48
+ ]));
49
+ }
50
+ ViewPageSize.matchSize = matchSize;
51
+ })(ViewPageSize || (ViewPageSize = {}));
15
52
  /**
16
53
  * View page grid item
17
54
  * @param props Props
@@ -44,33 +81,32 @@ function getResp(singleRow) {
44
81
  const size = typeof singleRow === "object"
45
82
  ? singleRow
46
83
  : singleRow === "medium"
47
- ? { xs: 12, sm: 12, md: 6, lg: 4, xl: 3 }
84
+ ? ViewPageSize.medium
48
85
  : singleRow === true
49
- ? { xs: 12, sm: 12, md: 12, lg: 12, xl: 12 }
50
- : {
51
- xs: singleRow === false ? 12 : 6,
52
- sm: 6,
53
- md: 4,
54
- lg: 3,
55
- xl: 2
56
- };
57
- return { size };
86
+ ? ViewPageSize.line
87
+ : singleRow === false
88
+ ? ViewPageSize.smallLine
89
+ : ViewPageSize.small;
90
+ return size;
58
91
  }
59
92
  function getItemField(app, field, data) {
60
93
  // Item data and label
61
- let itemData, itemLabel, gridProps = {};
94
+ let itemData, itemLabel, gridProps = {}, size;
62
95
  if (Array.isArray(field)) {
63
96
  const [fieldData, fieldType, renderProps, singleRow = "small"] = field;
64
97
  itemData = GridDataFormat(data[fieldData], fieldType, renderProps);
65
98
  itemLabel = app.get(fieldData) ?? fieldData;
66
- gridProps = { ...getResp(singleRow) };
99
+ size = getResp(singleRow);
100
+ gridProps = { size };
67
101
  }
68
102
  else if (typeof field === "object") {
69
103
  // Destruct
70
104
  const { data: fieldData, dataType, label: fieldLabel, renderProps, singleRow = "default", ...rest } = field;
105
+ // Size
106
+ size = getResp(singleRow);
71
107
  gridProps = {
72
108
  ...rest,
73
- ...getResp(singleRow)
109
+ size
74
110
  };
75
111
  // Field data
76
112
  if (typeof fieldData === "function")
@@ -95,8 +131,22 @@ function getItemField(app, field, data) {
95
131
  // Single field format
96
132
  itemData = formatItemData(app, data[field]);
97
133
  itemLabel = app.get(field) ?? field;
134
+ size = ViewPageSize.small;
135
+ gridProps = { size };
136
+ }
137
+ return [itemData, itemLabel, gridProps, size];
138
+ }
139
+ function getItemSize(bp, size) {
140
+ const v = size[bp];
141
+ if (v != null)
142
+ return v;
143
+ const index = breakpoints.indexOf(bp);
144
+ for (let i = index; i >= 0; i--) {
145
+ const v = size[breakpoints[i]];
146
+ if (v != null)
147
+ return v;
98
148
  }
99
- return [itemData, itemLabel, gridProps];
149
+ return 12;
100
150
  }
101
151
  /**
102
152
  * View page
@@ -106,19 +156,80 @@ export function ViewPage(props) {
106
156
  // Global app
107
157
  const app = useRequiredAppContext();
108
158
  // Destruct
109
- const { actions, children, fields, loadData, paddings = MUGlobal.pagePaddings, spacing = MUGlobal.half(MUGlobal.pagePaddings), supportRefresh = true, fabColumnDirection = true, fabTop = true, supportBack = true, pullToRefresh = true, gridRef, operationMessageHandler, ...rest } = props;
159
+ const { actions, children, fields, loadData, paddings = MUGlobal.pagePaddings, spacing = MUGlobal.half(MUGlobal.pagePaddings), supportRefresh = true, fabColumnDirection = true, fabTop = true, supportBack = true, pullToRefresh = true, gridRef, operationMessageHandler, titleBar, leftContainer, leftContainerLines = 3, leftContainerProps = {}, ...rest } = props;
160
+ // Current breakpoint
161
+ const bp = useCurrentBreakpoint();
110
162
  // Data
111
163
  const [data, setData] = React.useState();
112
164
  // Labels
113
165
  const labels = Labels.CommonPage;
114
166
  // Container
115
167
  const pullContainer = "#page-container";
168
+ // Left container
169
+ const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } = leftContainerProps;
116
170
  // Load data
117
171
  const refresh = React.useCallback(async () => {
118
172
  const result = await loadData();
119
173
  // When failed or no data returned, show the loading bar
120
174
  setData(result);
121
175
  }, [loadData]);
176
+ // Create fields
177
+ const fieldIndexRef = React.useRef(0);
178
+ const createFields = React.useCallback((data, maxItems = 0) => {
179
+ let validItems = 0;
180
+ const items = [];
181
+ let i = fieldIndexRef.current;
182
+ for (; i < fields.length; i++) {
183
+ const field = fields[i];
184
+ let oneSize;
185
+ let oneItem;
186
+ if (typeof field === "function") {
187
+ // Most flexible way, do whatever you want
188
+ const createdResult = field(data, refresh);
189
+ if (createdResult == null || createdResult === "")
190
+ continue;
191
+ if (Array.isArray(createdResult)) {
192
+ const [created, size] = createdResult;
193
+ oneSize = size;
194
+ oneItem = created;
195
+ }
196
+ else {
197
+ oneSize = ViewPageSize.line;
198
+ oneItem = createdResult;
199
+ }
200
+ }
201
+ else {
202
+ const [itemData, itemLabel, gridProps, size] = getItemField(app, field, data);
203
+ // Some callback function may return '' instead of undefined
204
+ if (itemData == null || itemData === "")
205
+ continue;
206
+ oneSize = size;
207
+ oneItem = (_createElement(ViewPageGridItem, { ...gridProps, key: i, data: itemData, label: itemLabel }));
208
+ }
209
+ // Max lines
210
+ if (maxItems > 0) {
211
+ const itemSize = getItemSize(bp, oneSize);
212
+ if (maxItems < validItems + itemSize) {
213
+ fieldIndexRef.current = i;
214
+ break;
215
+ }
216
+ else {
217
+ items.push(oneItem);
218
+ validItems += itemSize;
219
+ }
220
+ }
221
+ else {
222
+ items.push(oneItem);
223
+ }
224
+ }
225
+ if (maxItems === 0) {
226
+ fieldIndexRef.current = 0;
227
+ }
228
+ else {
229
+ fieldIndexRef.current = i;
230
+ }
231
+ return items;
232
+ }, [app, refresh, fields, data, bp]);
122
233
  React.useEffect(() => {
123
234
  const refreshHandler = async () => {
124
235
  await refresh();
@@ -128,29 +239,18 @@ export function ViewPage(props) {
128
239
  MessageUtils.offRefresh(refreshHandler);
129
240
  };
130
241
  }, [refresh]);
131
- return (_jsx(CommonPage, { paddings: paddings, onRefresh: supportRefresh ? refresh : undefined, onUpdate: supportRefresh ? undefined : refresh, ...rest, scrollContainer: globalThis, fabColumnDirection: fabColumnDirection, fabTop: fabTop, supportBack: supportBack, children: data == null ? (_jsx(LinearProgress, {})) : (_jsxs(React.Fragment, { children: [operationMessageHandler && (_jsx(OperationMessageContainer, { handler: "id" in operationMessageHandler
242
+ let leftResult;
243
+ return (_jsx(CommonPage, { paddings: paddings, onRefresh: supportRefresh ? refresh : undefined, onUpdate: supportRefresh ? undefined : refresh, sx: {
244
+ ".MuiTypography-subtitle2": {
245
+ fontWeight: "bold"
246
+ }
247
+ }, ...rest, scrollContainer: globalThis, fabColumnDirection: fabColumnDirection, fabTop: fabTop, supportBack: supportBack, children: data == null ? (_jsx(LinearProgress, {})) : (_jsxs(React.Fragment, { children: [operationMessageHandler && (_jsx(OperationMessageContainer, { handler: "id" in operationMessageHandler
132
248
  ? [
133
249
  operationMessageHandler.types,
134
250
  refresh,
135
251
  operationMessageHandler.id
136
252
  ]
137
- : operationMessageHandler })), _jsx(Grid2, { container: true, justifyContent: "left", spacing: spacing, className: "ET-ViewPage", ref: gridRef, sx: {
138
- ".MuiTypography-subtitle2": {
139
- fontWeight: "bold"
140
- }
141
- }, children: fields.map((field, index) => {
142
- // Get data
143
- if (typeof field === "function") {
144
- // Most flexible way, do whatever you want
145
- return field(data, refresh);
146
- }
147
- const [itemData, itemLabel, gridProps] = getItemField(app, field, data);
148
- // Some callback function may return '' instead of undefined
149
- if (itemData == null || itemData === "")
150
- return undefined;
151
- // Layout
152
- return (_createElement(ViewPageGridItem, { ...gridProps, key: index, data: itemData, label: itemLabel }));
153
- }) }), actions !== null && (_jsx(Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: actions == null ? undefined : paddings, paddingBottom: paddings, gap: paddings, children: actions != null && Utils.getResult(actions, data, refresh) })), Utils.getResult(children, data, refresh), pullToRefresh && (_jsx(PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
253
+ : operationMessageHandler })), titleBar && titleBar(data), _jsxs(Grid2, { container: true, justifyContent: "left", className: "ET-ViewPage", ref: gridRef, spacing: spacing, children: [leftContainer && (leftResult = leftContainer(data)) != null && (_jsxs(React.Fragment, { children: [_jsx(Grid2, { container: true, className: "ET-ViewPage-LeftContainer", spacing: spacing, size: size, ...leftContainerPropsRest, children: leftResult }), _jsx(Grid2, { container: true, className: "ET-ViewPage-LeftOthers", spacing: spacing, size: ViewPageSize.matchSize(size), children: createFields(data, leftContainerLines * (12 - getItemSize(bp, size))) })] })), createFields(data)] }), actions !== null && (_jsx(Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: actions == null ? undefined : paddings, paddingBottom: paddings, gap: paddings, children: actions != null && Utils.getResult(actions, data, refresh) })), Utils.getResult(children, data, refresh), pullToRefresh && (_jsx(PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
154
254
  const container = document.querySelector(pullContainer);
155
255
  return !container?.scrollTop;
156
256
  } })), _jsx(ScrollRestoration, {})] })) }));
@@ -0,0 +1,6 @@
1
+ import { Breakpoint } from "@mui/material";
2
+ /**
3
+ * Hook to get the current breakpoint
4
+ * @returns The current breakpoint
5
+ */
6
+ export declare function useCurrentBreakpoint(): Breakpoint;
@@ -0,0 +1,16 @@
1
+ import { useMediaQuery, useTheme } from "@mui/material";
2
+ /**
3
+ * Hook to get the current breakpoint
4
+ * @returns The current breakpoint
5
+ */
6
+ export function useCurrentBreakpoint() {
7
+ const theme = useTheme();
8
+ const items = [
9
+ useMediaQuery(theme.breakpoints.down("xs")) ? "xs" : null,
10
+ useMediaQuery(theme.breakpoints.between("xs", "sm")) ? "sm" : null,
11
+ useMediaQuery(theme.breakpoints.between("sm", "md")) ? "md" : null,
12
+ useMediaQuery(theme.breakpoints.between("md", "lg")) ? "lg" : null,
13
+ useMediaQuery(theme.breakpoints.up("lg")) ? "xl" : null
14
+ ];
15
+ return items.find((item) => item != null) ?? "lg";
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.4.90",
3
+ "version": "1.4.92",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -28,7 +28,6 @@ import {
28
28
  NotificationReactCallProps,
29
29
  UserAction,
30
30
  UserActionType,
31
- UserCalls,
32
31
  useRequiredContext,
33
32
  UserState
34
33
  } from "@etsoo/react";
package/src/index.ts CHANGED
@@ -117,6 +117,7 @@ export * from "./TiplistPro";
117
117
  export * from "./TagList";
118
118
  export * from "./TagListPro";
119
119
  export * from "./TwoFieldInput";
120
+ export * from "./useCurrentBreakpoint";
120
121
  export * from "./TooltipClick";
121
122
  export * from "./UserAvatar";
122
123
  export * from "./UserAvatarEditor";
@@ -5,6 +5,7 @@ import {
5
5
  } from "@etsoo/react";
6
6
  import { DataTypes, Utils } from "@etsoo/shared";
7
7
  import {
8
+ Breakpoint,
8
9
  Grid2,
9
10
  Grid2Props,
10
11
  LinearProgress,
@@ -22,6 +23,50 @@ import { MessageUtils } from "../messages/MessageUtils";
22
23
  import type { RefreshHandler } from "../messages/RefreshHandler";
23
24
  import { OperationMessageContainer } from "../messages/OperationMessageContainer";
24
25
  import { ReactAppType, useRequiredAppContext } from "../app/ReactApp";
26
+ import { useCurrentBreakpoint } from "../useCurrentBreakpoint";
27
+
28
+ /**
29
+ * View page item size
30
+ */
31
+ export type ViewPageItemSize = Record<Breakpoint, number | undefined>;
32
+
33
+ const breakpoints: Breakpoint[] = ["xs", "sm", "md", "lg", "xl"];
34
+
35
+ /**
36
+ * View page grid item size
37
+ */
38
+ export namespace ViewPageSize {
39
+ export const medium: ViewPageItemSize = {
40
+ xs: 12,
41
+ sm: 12,
42
+ md: 6,
43
+ lg: 4,
44
+ xl: 3
45
+ };
46
+ export const line: ViewPageItemSize = {
47
+ xs: 12,
48
+ sm: 12,
49
+ md: 12,
50
+ lg: 12,
51
+ xl: 12
52
+ };
53
+ export const small: ViewPageItemSize = { xs: 6, sm: 6, md: 4, lg: 3, xl: 2 };
54
+ export const smallLine: ViewPageItemSize = {
55
+ xs: 12,
56
+ sm: 6,
57
+ md: 4,
58
+ lg: 3,
59
+ xl: 2
60
+ };
61
+ export function matchSize(size: ViewPageItemSize) {
62
+ return Object.fromEntries(
63
+ Object.entries(size).map(([key, value]) => [
64
+ key,
65
+ value == null ? undefined : value === 12 ? 12 : 12 - value
66
+ ])
67
+ );
68
+ }
69
+ }
25
70
 
26
71
  /**
27
72
  * View page grid item properties
@@ -69,7 +114,12 @@ export function ViewPageGridItem(props: ViewPageGridItemProps) {
69
114
  /**
70
115
  * View page row width type
71
116
  */
72
- export type ViewPageRowType = boolean | "default" | "small" | "medium" | object;
117
+ export type ViewPageRowType =
118
+ | boolean
119
+ | "default"
120
+ | "small"
121
+ | "medium"
122
+ | ViewPageItemSize;
73
123
 
74
124
  /**
75
125
  * View page display field
@@ -111,7 +161,10 @@ type ViewPageFieldTypeNarrow<T extends object> =
111
161
  */
112
162
  export type ViewPageFieldType<T extends object> =
113
163
  | ViewPageFieldTypeNarrow<T>
114
- | ((data: T, refresh: () => Promise<void>) => React.ReactNode);
164
+ | ((
165
+ data: T,
166
+ refresh: () => Promise<void>
167
+ ) => React.ReactNode | [React.ReactNode, ViewPageItemSize]);
115
168
 
116
169
  /**
117
170
  * View page props
@@ -168,6 +221,28 @@ export interface ViewPageProps<T extends DataTypes.StringRecord>
168
221
  operationMessageHandler?:
169
222
  | OperationMessageHandlerAll
170
223
  | { id: number; types: string[] };
224
+
225
+ /**
226
+ * Title bar
227
+ * @param data Data to render
228
+ * @returns
229
+ */
230
+ titleBar?: (data: T) => React.ReactNode;
231
+
232
+ /**
233
+ * Left container
234
+ */
235
+ leftContainer?: (data: T) => React.ReactNode;
236
+
237
+ /**
238
+ * Left container height in lines
239
+ */
240
+ leftContainerLines?: number;
241
+
242
+ /**
243
+ * Left container properties
244
+ */
245
+ leftContainerProps?: Omit<Grid2Props, "size"> & { size?: ViewPageItemSize };
171
246
  }
172
247
 
173
248
  function formatItemData(
@@ -185,34 +260,32 @@ function getResp(singleRow: ViewPageRowType) {
185
260
  typeof singleRow === "object"
186
261
  ? singleRow
187
262
  : singleRow === "medium"
188
- ? { xs: 12, sm: 12, md: 6, lg: 4, xl: 3 }
263
+ ? ViewPageSize.medium
189
264
  : singleRow === true
190
- ? { xs: 12, sm: 12, md: 12, lg: 12, xl: 12 }
191
- : {
192
- xs: singleRow === false ? 12 : 6,
193
- sm: 6,
194
- md: 4,
195
- lg: 3,
196
- xl: 2
197
- };
198
- return { size };
265
+ ? ViewPageSize.line
266
+ : singleRow === false
267
+ ? ViewPageSize.smallLine
268
+ : ViewPageSize.small;
269
+ return size;
199
270
  }
200
271
 
201
272
  function getItemField<T extends object>(
202
273
  app: ReactAppType,
203
274
  field: ViewPageFieldTypeNarrow<T>,
204
275
  data: T
205
- ): [React.ReactNode, React.ReactNode, Grid2Props] {
276
+ ): [React.ReactNode, React.ReactNode, Grid2Props, ViewPageItemSize] {
206
277
  // Item data and label
207
278
  let itemData: React.ReactNode,
208
279
  itemLabel: React.ReactNode,
209
- gridProps: Grid2Props = {};
280
+ gridProps: Grid2Props = {},
281
+ size: ViewPageItemSize;
210
282
 
211
283
  if (Array.isArray(field)) {
212
284
  const [fieldData, fieldType, renderProps, singleRow = "small"] = field;
213
285
  itemData = GridDataFormat(data[fieldData], fieldType, renderProps);
214
286
  itemLabel = app.get<string>(fieldData) ?? fieldData;
215
- gridProps = { ...getResp(singleRow) };
287
+ size = getResp(singleRow);
288
+ gridProps = { size };
216
289
  } else if (typeof field === "object") {
217
290
  // Destruct
218
291
  const {
@@ -224,9 +297,12 @@ function getItemField<T extends object>(
224
297
  ...rest
225
298
  } = field;
226
299
 
300
+ // Size
301
+ size = getResp(singleRow);
302
+
227
303
  gridProps = {
228
304
  ...rest,
229
- ...getResp(singleRow)
305
+ size
230
306
  };
231
307
 
232
308
  // Field data
@@ -249,9 +325,24 @@ function getItemField<T extends object>(
249
325
  // Single field format
250
326
  itemData = formatItemData(app, data[field]);
251
327
  itemLabel = app.get<string>(field) ?? field;
328
+ size = ViewPageSize.small;
329
+ gridProps = { size };
330
+ }
331
+
332
+ return [itemData, itemLabel, gridProps, size];
333
+ }
334
+
335
+ function getItemSize(bp: Breakpoint, size: ViewPageItemSize) {
336
+ const v = size[bp];
337
+ if (v != null) return v;
338
+
339
+ const index = breakpoints.indexOf(bp);
340
+ for (let i = index; i >= 0; i--) {
341
+ const v = size[breakpoints[i]];
342
+ if (v != null) return v;
252
343
  }
253
344
 
254
- return [itemData, itemLabel, gridProps];
345
+ return 12;
255
346
  }
256
347
 
257
348
  /**
@@ -279,9 +370,16 @@ export function ViewPage<T extends DataTypes.StringRecord>(
279
370
  pullToRefresh = true,
280
371
  gridRef,
281
372
  operationMessageHandler,
373
+ titleBar,
374
+ leftContainer,
375
+ leftContainerLines = 3,
376
+ leftContainerProps = {},
282
377
  ...rest
283
378
  } = props;
284
379
 
380
+ // Current breakpoint
381
+ const bp = useCurrentBreakpoint();
382
+
285
383
  // Data
286
384
  const [data, setData] = React.useState<T>();
287
385
 
@@ -291,6 +389,10 @@ export function ViewPage<T extends DataTypes.StringRecord>(
291
389
  // Container
292
390
  const pullContainer = "#page-container";
293
391
 
392
+ // Left container
393
+ const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } =
394
+ leftContainerProps;
395
+
294
396
  // Load data
295
397
  const refresh = React.useCallback(async () => {
296
398
  const result = await loadData();
@@ -298,6 +400,76 @@ export function ViewPage<T extends DataTypes.StringRecord>(
298
400
  setData(result);
299
401
  }, [loadData]);
300
402
 
403
+ // Create fields
404
+ const fieldIndexRef = React.useRef(0);
405
+ const createFields = React.useCallback(
406
+ (data: T, maxItems: number = 0) => {
407
+ let validItems = 0;
408
+ const items: React.ReactNode[] = [];
409
+ let i: number = fieldIndexRef.current;
410
+ for (; i < fields.length; i++) {
411
+ const field = fields[i];
412
+ let oneSize: ViewPageItemSize;
413
+ let oneItem: React.ReactNode;
414
+ if (typeof field === "function") {
415
+ // Most flexible way, do whatever you want
416
+ const createdResult = field(data, refresh);
417
+ if (createdResult == null || createdResult === "") continue;
418
+ if (Array.isArray(createdResult)) {
419
+ const [created, size] = createdResult;
420
+ oneSize = size;
421
+ oneItem = created;
422
+ } else {
423
+ oneSize = ViewPageSize.line;
424
+ oneItem = createdResult;
425
+ }
426
+ } else {
427
+ const [itemData, itemLabel, gridProps, size] = getItemField(
428
+ app,
429
+ field,
430
+ data
431
+ );
432
+
433
+ // Some callback function may return '' instead of undefined
434
+ if (itemData == null || itemData === "") continue;
435
+
436
+ oneSize = size;
437
+ oneItem = (
438
+ <ViewPageGridItem
439
+ {...gridProps}
440
+ key={i}
441
+ data={itemData}
442
+ label={itemLabel}
443
+ />
444
+ );
445
+ }
446
+
447
+ // Max lines
448
+ if (maxItems > 0) {
449
+ const itemSize = getItemSize(bp, oneSize);
450
+ if (maxItems < validItems + itemSize) {
451
+ fieldIndexRef.current = i;
452
+ break;
453
+ } else {
454
+ items.push(oneItem);
455
+ validItems += itemSize;
456
+ }
457
+ } else {
458
+ items.push(oneItem);
459
+ }
460
+ }
461
+
462
+ if (maxItems === 0) {
463
+ fieldIndexRef.current = 0;
464
+ } else {
465
+ fieldIndexRef.current = i;
466
+ }
467
+
468
+ return items;
469
+ },
470
+ [app, refresh, fields, data, bp]
471
+ );
472
+
301
473
  React.useEffect(() => {
302
474
  const refreshHandler: RefreshHandler = async () => {
303
475
  await refresh();
@@ -309,11 +481,18 @@ export function ViewPage<T extends DataTypes.StringRecord>(
309
481
  };
310
482
  }, [refresh]);
311
483
 
484
+ let leftResult: React.ReactNode;
485
+
312
486
  return (
313
487
  <CommonPage
314
488
  paddings={paddings}
315
489
  onRefresh={supportRefresh ? refresh : undefined}
316
490
  onUpdate={supportRefresh ? undefined : refresh}
491
+ sx={{
492
+ ".MuiTypography-subtitle2": {
493
+ fontWeight: "bold"
494
+ }
495
+ }}
317
496
  {...rest}
318
497
  scrollContainer={globalThis}
319
498
  fabColumnDirection={fabColumnDirection}
@@ -337,44 +516,39 @@ export function ViewPage<T extends DataTypes.StringRecord>(
337
516
  }
338
517
  />
339
518
  )}
519
+ {titleBar && titleBar(data)}
340
520
  <Grid2
341
521
  container
342
522
  justifyContent="left"
343
- spacing={spacing}
344
523
  className="ET-ViewPage"
345
524
  ref={gridRef}
346
- sx={{
347
- ".MuiTypography-subtitle2": {
348
- fontWeight: "bold"
349
- }
350
- }}
525
+ spacing={spacing}
351
526
  >
352
- {fields.map((field, index) => {
353
- // Get data
354
- if (typeof field === "function") {
355
- // Most flexible way, do whatever you want
356
- return field(data, refresh);
357
- }
358
-
359
- const [itemData, itemLabel, gridProps] = getItemField(
360
- app,
361
- field,
362
- data
363
- );
364
-
365
- // Some callback function may return '' instead of undefined
366
- if (itemData == null || itemData === "") return undefined;
367
-
368
- // Layout
369
- return (
370
- <ViewPageGridItem
371
- {...gridProps}
372
- key={index}
373
- data={itemData}
374
- label={itemLabel}
375
- />
376
- );
377
- })}
527
+ {leftContainer && (leftResult = leftContainer(data)) != null && (
528
+ <React.Fragment>
529
+ <Grid2
530
+ container
531
+ className="ET-ViewPage-LeftContainer"
532
+ spacing={spacing}
533
+ size={size}
534
+ {...leftContainerPropsRest}
535
+ >
536
+ {leftResult}
537
+ </Grid2>
538
+ <Grid2
539
+ container
540
+ className="ET-ViewPage-LeftOthers"
541
+ spacing={spacing}
542
+ size={ViewPageSize.matchSize(size)}
543
+ >
544
+ {createFields(
545
+ data,
546
+ leftContainerLines * (12 - getItemSize(bp, size))
547
+ )}
548
+ </Grid2>
549
+ </React.Fragment>
550
+ )}
551
+ {createFields(data)}
378
552
  </Grid2>
379
553
  {actions !== null && (
380
554
  <Stack
@@ -0,0 +1,17 @@
1
+ import { Breakpoint, useMediaQuery, useTheme } from "@mui/material";
2
+
3
+ /**
4
+ * Hook to get the current breakpoint
5
+ * @returns The current breakpoint
6
+ */
7
+ export function useCurrentBreakpoint(): Breakpoint {
8
+ const theme = useTheme();
9
+ const items: (Breakpoint | null)[] = [
10
+ useMediaQuery(theme.breakpoints.down("xs")) ? "xs" : null,
11
+ useMediaQuery(theme.breakpoints.between("xs", "sm")) ? "sm" : null,
12
+ useMediaQuery(theme.breakpoints.between("sm", "md")) ? "md" : null,
13
+ useMediaQuery(theme.breakpoints.between("md", "lg")) ? "lg" : null,
14
+ useMediaQuery(theme.breakpoints.up("lg")) ? "xl" : null
15
+ ];
16
+ return items.find((item) => item != null) ?? "lg";
17
+ }