@etsoo/materialui 1.4.89 → 1.4.91

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")
@@ -88,17 +125,36 @@ function getItemField(app, field, data) {
88
125
  itemData = (0, GridDataFormat_1.GridDataFormat)(data[fieldData], dataType, renderProps);
89
126
  // Field label
90
127
  itemLabel =
91
- typeof fieldLabel === "function"
92
- ? fieldLabel(data)
93
- : fieldLabel != null
94
- ? app.get(fieldLabel) ?? fieldLabel
95
- : fieldLabel;
128
+ fieldLabel === ""
129
+ ? undefined
130
+ : fieldLabel == null && typeof fieldData === "string"
131
+ ? app.get(fieldData) ?? fieldData
132
+ : typeof fieldLabel === "function"
133
+ ? fieldLabel(data)
134
+ : fieldLabel != null
135
+ ? app.get(fieldLabel) ?? fieldLabel
136
+ : undefined;
96
137
  }
97
138
  else {
139
+ // Single field format
98
140
  itemData = formatItemData(app, data[field]);
99
141
  itemLabel = app.get(field) ?? field;
142
+ size = ViewPageSize.small;
143
+ gridProps = { size };
100
144
  }
101
- return [itemData, itemLabel, gridProps];
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;
156
+ }
157
+ return 12;
102
158
  }
103
159
  /**
104
160
  * View page
@@ -108,19 +164,80 @@ function ViewPage(props) {
108
164
  // Global app
109
165
  const app = (0, ReactApp_1.useRequiredAppContext)();
110
166
  // Destruct
111
- 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)();
112
170
  // Data
113
171
  const [data, setData] = react_3.default.useState();
114
172
  // Labels
115
173
  const labels = Labels_1.Labels.CommonPage;
116
174
  // Container
117
175
  const pullContainer = "#page-container";
176
+ // Left container
177
+ const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } = leftContainerProps;
118
178
  // Load data
119
179
  const refresh = react_3.default.useCallback(async () => {
120
180
  const result = await loadData();
121
181
  // When failed or no data returned, show the loading bar
122
182
  setData(result);
123
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]);
124
241
  react_3.default.useEffect(() => {
125
242
  const refreshHandler = async () => {
126
243
  await refresh();
@@ -136,23 +253,11 @@ function ViewPage(props) {
136
253
  refresh,
137
254
  operationMessageHandler.id
138
255
  ]
139
- : operationMessageHandler })), (0, jsx_runtime_1.jsx)(material_1.Grid2, { container: true, justifyContent: "left", spacing: spacing, className: "ET-ViewPage", ref: gridRef, sx: {
256
+ : operationMessageHandler })), titleBar && titleBar(data), (0, jsx_runtime_1.jsxs)(material_1.Grid2, { container: true, justifyContent: "left", className: "ET-ViewPage", ref: gridRef, spacing: spacing, sx: {
140
257
  ".MuiTypography-subtitle2": {
141
258
  fontWeight: "bold"
142
259
  }
143
- }, children: fields.map((field, index) => {
144
- // Get data
145
- if (typeof field === "function") {
146
- // Most flexible way, do whatever you want
147
- return field(data, refresh);
148
- }
149
- const [itemData, itemLabel, gridProps] = getItemField(app, field, data);
150
- // Some callback function may return '' instead of undefined
151
- if (itemData == null || itemData === "")
152
- return undefined;
153
- // Layout
154
- return ((0, react_1.createElement)(ViewPageGridItem, { ...gridProps, key: index, data: itemData, label: itemLabel }));
155
- }) }), 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: () => {
260
+ }, children: [leftContainer && ((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: leftContainer(data) }), (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: () => {
156
261
  const container = document.querySelector(pullContainer);
157
262
  return !container?.scrollTop;
158
263
  } })), (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")
@@ -81,17 +117,36 @@ function getItemField(app, field, data) {
81
117
  itemData = GridDataFormat(data[fieldData], dataType, renderProps);
82
118
  // Field label
83
119
  itemLabel =
84
- typeof fieldLabel === "function"
85
- ? fieldLabel(data)
86
- : fieldLabel != null
87
- ? app.get(fieldLabel) ?? fieldLabel
88
- : fieldLabel;
120
+ fieldLabel === ""
121
+ ? undefined
122
+ : fieldLabel == null && typeof fieldData === "string"
123
+ ? app.get(fieldData) ?? fieldData
124
+ : typeof fieldLabel === "function"
125
+ ? fieldLabel(data)
126
+ : fieldLabel != null
127
+ ? app.get(fieldLabel) ?? fieldLabel
128
+ : undefined;
89
129
  }
90
130
  else {
131
+ // Single field format
91
132
  itemData = formatItemData(app, data[field]);
92
133
  itemLabel = app.get(field) ?? field;
134
+ size = ViewPageSize.small;
135
+ gridProps = { size };
93
136
  }
94
- return [itemData, itemLabel, gridProps];
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;
148
+ }
149
+ return 12;
95
150
  }
96
151
  /**
97
152
  * View page
@@ -101,19 +156,80 @@ export function ViewPage(props) {
101
156
  // Global app
102
157
  const app = useRequiredAppContext();
103
158
  // Destruct
104
- 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();
105
162
  // Data
106
163
  const [data, setData] = React.useState();
107
164
  // Labels
108
165
  const labels = Labels.CommonPage;
109
166
  // Container
110
167
  const pullContainer = "#page-container";
168
+ // Left container
169
+ const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } = leftContainerProps;
111
170
  // Load data
112
171
  const refresh = React.useCallback(async () => {
113
172
  const result = await loadData();
114
173
  // When failed or no data returned, show the loading bar
115
174
  setData(result);
116
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]);
117
233
  React.useEffect(() => {
118
234
  const refreshHandler = async () => {
119
235
  await refresh();
@@ -129,23 +245,11 @@ export function ViewPage(props) {
129
245
  refresh,
130
246
  operationMessageHandler.id
131
247
  ]
132
- : operationMessageHandler })), _jsx(Grid2, { container: true, justifyContent: "left", spacing: spacing, className: "ET-ViewPage", ref: gridRef, sx: {
248
+ : operationMessageHandler })), titleBar && titleBar(data), _jsxs(Grid2, { container: true, justifyContent: "left", className: "ET-ViewPage", ref: gridRef, spacing: spacing, sx: {
133
249
  ".MuiTypography-subtitle2": {
134
250
  fontWeight: "bold"
135
251
  }
136
- }, children: fields.map((field, index) => {
137
- // Get data
138
- if (typeof field === "function") {
139
- // Most flexible way, do whatever you want
140
- return field(data, refresh);
141
- }
142
- const [itemData, itemLabel, gridProps] = getItemField(app, field, data);
143
- // Some callback function may return '' instead of undefined
144
- if (itemData == null || itemData === "")
145
- return undefined;
146
- // Layout
147
- return (_createElement(ViewPageGridItem, { ...gridProps, key: index, data: itemData, label: itemLabel }));
148
- }) }), 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: () => {
252
+ }, children: [leftContainer && (_jsxs(React.Fragment, { children: [_jsx(Grid2, { container: true, className: "ET-ViewPage-LeftContainer", spacing: spacing, size: size, ...leftContainerPropsRest, children: leftContainer(data) }), _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: () => {
149
253
  const container = document.querySelector(pullContainer);
150
254
  return !container?.scrollTop;
151
255
  } })), _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.89",
3
+ "version": "1.4.91",
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
@@ -236,17 +312,37 @@ function getItemField<T extends object>(
236
312
 
237
313
  // Field label
238
314
  itemLabel =
239
- typeof fieldLabel === "function"
315
+ fieldLabel === ""
316
+ ? undefined
317
+ : fieldLabel == null && typeof fieldData === "string"
318
+ ? app.get<string>(fieldData) ?? fieldData
319
+ : typeof fieldLabel === "function"
240
320
  ? fieldLabel(data)
241
321
  : fieldLabel != null
242
322
  ? app.get<string>(fieldLabel) ?? fieldLabel
243
- : fieldLabel;
323
+ : undefined;
244
324
  } else {
325
+ // Single field format
245
326
  itemData = formatItemData(app, data[field]);
246
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;
247
343
  }
248
344
 
249
- return [itemData, itemLabel, gridProps];
345
+ return 12;
250
346
  }
251
347
 
252
348
  /**
@@ -274,9 +370,16 @@ export function ViewPage<T extends DataTypes.StringRecord>(
274
370
  pullToRefresh = true,
275
371
  gridRef,
276
372
  operationMessageHandler,
373
+ titleBar,
374
+ leftContainer,
375
+ leftContainerLines = 3,
376
+ leftContainerProps = {},
277
377
  ...rest
278
378
  } = props;
279
379
 
380
+ // Current breakpoint
381
+ const bp = useCurrentBreakpoint();
382
+
280
383
  // Data
281
384
  const [data, setData] = React.useState<T>();
282
385
 
@@ -286,6 +389,10 @@ export function ViewPage<T extends DataTypes.StringRecord>(
286
389
  // Container
287
390
  const pullContainer = "#page-container";
288
391
 
392
+ // Left container
393
+ const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } =
394
+ leftContainerProps;
395
+
289
396
  // Load data
290
397
  const refresh = React.useCallback(async () => {
291
398
  const result = await loadData();
@@ -293,6 +400,76 @@ export function ViewPage<T extends DataTypes.StringRecord>(
293
400
  setData(result);
294
401
  }, [loadData]);
295
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
+
296
473
  React.useEffect(() => {
297
474
  const refreshHandler: RefreshHandler = async () => {
298
475
  await refresh();
@@ -332,44 +509,44 @@ export function ViewPage<T extends DataTypes.StringRecord>(
332
509
  }
333
510
  />
334
511
  )}
512
+ {titleBar && titleBar(data)}
335
513
  <Grid2
336
514
  container
337
515
  justifyContent="left"
338
- spacing={spacing}
339
516
  className="ET-ViewPage"
340
517
  ref={gridRef}
518
+ spacing={spacing}
341
519
  sx={{
342
520
  ".MuiTypography-subtitle2": {
343
521
  fontWeight: "bold"
344
522
  }
345
523
  }}
346
524
  >
347
- {fields.map((field, index) => {
348
- // Get data
349
- if (typeof field === "function") {
350
- // Most flexible way, do whatever you want
351
- return field(data, refresh);
352
- }
353
-
354
- const [itemData, itemLabel, gridProps] = getItemField(
355
- app,
356
- field,
357
- data
358
- );
359
-
360
- // Some callback function may return '' instead of undefined
361
- if (itemData == null || itemData === "") return undefined;
362
-
363
- // Layout
364
- return (
365
- <ViewPageGridItem
366
- {...gridProps}
367
- key={index}
368
- data={itemData}
369
- label={itemLabel}
370
- />
371
- );
372
- })}
525
+ {leftContainer && (
526
+ <React.Fragment>
527
+ <Grid2
528
+ container
529
+ className="ET-ViewPage-LeftContainer"
530
+ spacing={spacing}
531
+ size={size}
532
+ {...leftContainerPropsRest}
533
+ >
534
+ {leftContainer(data)}
535
+ </Grid2>
536
+ <Grid2
537
+ container
538
+ className="ET-ViewPage-LeftOthers"
539
+ spacing={spacing}
540
+ size={ViewPageSize.matchSize(size)}
541
+ >
542
+ {createFields(
543
+ data,
544
+ leftContainerLines * (12 - getItemSize(bp, size))
545
+ )}
546
+ </Grid2>
547
+ </React.Fragment>
548
+ )}
549
+ {createFields(data)}
373
550
  </Grid2>
374
551
  {actions !== null && (
375
552
  <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
+ }