@etsoo/react 1.4.38 → 1.4.42

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.
@@ -8,6 +8,7 @@ export declare namespace Labels {
8
8
  */
9
9
  const CommonPage: {
10
10
  add: string;
11
+ delete: string;
11
12
  more: string;
12
13
  pullToRefresh: string;
13
14
  refresh: string;
package/lib/app/Labels.js CHANGED
@@ -9,6 +9,7 @@ export var Labels;
9
9
  */
10
10
  Labels.CommonPage = {
11
11
  add: 'Add',
12
+ delete: 'Delete',
12
13
  more: 'More',
13
14
  pullToRefresh: 'Pull down to refresh',
14
15
  refresh: 'Refresh',
@@ -67,7 +67,7 @@ export var DataGridRenderers;
67
67
  if (value)
68
68
  return React.createElement(CheckIcon, { fontSize: "small" });
69
69
  else
70
- return React.createElement(ClearIcon, { fontSize: "small", color: "error" });
70
+ return React.createElement(ClearIcon, { fontSize: "small", color: "warning" });
71
71
  }
72
72
  // To string
73
73
  return new String(value);
@@ -1,3 +1,4 @@
1
+ import { Theme } from '@mui/material';
1
2
  export declare class MUGlobal {
2
3
  /**
3
4
  * Search field shrink
@@ -62,6 +63,20 @@ export declare class MUGlobal {
62
63
  * @returns Updated object
63
64
  */
64
65
  static adjustWithTheme(size: number, adjust: {}, updateFunc: (value: number) => string): {};
66
+ /**
67
+ * Break points defined
68
+ */
69
+ static breakpoints: readonly ["xs", "sm", "md", "lg", "xl"];
70
+ /**
71
+ * Get multple medias theme space
72
+ * Responsive values and Breakpoints as an object
73
+ * xs = theme.breakpoints.up('xs')
74
+ * https://mui.com/system/basics/
75
+ * @param spaces Spaces
76
+ * @param theme Theme
77
+ * @returns Result
78
+ */
79
+ static getSpace(spaces: {}, theme: Theme): number;
65
80
  /**
66
81
  * Update object number properties with theme
67
82
  * @param input Input object
@@ -70,6 +70,30 @@ export class MUGlobal {
70
70
  });
71
71
  return newObj;
72
72
  }
73
+ /**
74
+ * Get multple medias theme space
75
+ * Responsive values and Breakpoints as an object
76
+ * xs = theme.breakpoints.up('xs')
77
+ * https://mui.com/system/basics/
78
+ * @param spaces Spaces
79
+ * @param theme Theme
80
+ * @returns Result
81
+ */
82
+ static getSpace(spaces, theme) {
83
+ const start = this.breakpoints.length - 1;
84
+ for (let i = start; i >= 0; i--) {
85
+ const key = this.breakpoints[i];
86
+ const value = Reflect.get(spaces, key);
87
+ if (typeof value === 'number') {
88
+ const mediaRaw = theme.breakpoints.up(key);
89
+ const mediaQuery = mediaRaw.substring(mediaRaw.indexOf('('));
90
+ if (window.matchMedia(mediaQuery).matches) {
91
+ return parseInt(theme.spacing(value), 10);
92
+ }
93
+ }
94
+ }
95
+ return 0;
96
+ }
73
97
  /**
74
98
  * Update object number properties with theme
75
99
  * @param input Input object
@@ -118,3 +142,7 @@ MUGlobal.textFieldVariant = 'filled';
118
142
  * Page default paddings
119
143
  */
120
144
  MUGlobal.pagePaddings = { xs: 2, sm: 3 };
145
+ /**
146
+ * Break points defined
147
+ */
148
+ MUGlobal.breakpoints = ['xs', 'sm', 'md', 'lg', 'xl'];
@@ -1,4 +1,3 @@
1
- import { SxProps, Theme } from '@mui/material';
2
1
  import React from 'react';
3
2
  import { ListItemReact } from '../components/ListItemReact';
4
3
  import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
@@ -9,7 +8,7 @@ import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
9
8
  * @param renderer Renderer for card content
10
9
  * @returns Component
11
10
  */
12
- export declare function MobileListItemRenderer<T>({ data, itemHeight }: ScrollerListExInnerItemRendererProps<T>, margin: {} | [{}, boolean] | (() => SxProps<Theme>), renderer: (data: T) => [
11
+ export declare function MobileListItemRenderer<T>({ data, itemHeight, margins }: ScrollerListExInnerItemRendererProps<T>, renderer: (data: T) => [
13
12
  string,
14
13
  string | undefined,
15
14
  React.ReactNode | (ListItemReact | boolean)[],
@@ -1,7 +1,6 @@
1
1
  import { Card, CardContent, CardHeader, LinearProgress } from '@mui/material';
2
2
  import React from 'react';
3
3
  import { MoreFab } from './MoreFab';
4
- import { MUGlobal } from './MUGlobal';
5
4
  function getActions(input) {
6
5
  // Actions
7
6
  const actions = [];
@@ -19,48 +18,15 @@ function getActions(input) {
19
18
  * @param renderer Renderer for card content
20
19
  * @returns Component
21
20
  */
22
- export function MobileListItemRenderer({ data, itemHeight }, margin, renderer) {
21
+ export function MobileListItemRenderer({ data, itemHeight, margins }, renderer) {
23
22
  // Loading
24
23
  if (data == null)
25
24
  return React.createElement(LinearProgress, null);
26
25
  // Elements
27
26
  const [title, subheader, actions, children] = renderer(data);
28
- // Calculate margin
29
- const calculateMargin = () => {
30
- if (typeof margin === 'function')
31
- return margin();
32
- if (Array.isArray(margin)) {
33
- const marginValue = margin[0];
34
- const isNarrow = margin[1];
35
- const half = MUGlobal.half(marginValue);
36
- if (isNarrow) {
37
- return {
38
- marginLeft: 0,
39
- marginRight: 0,
40
- marginTop: half,
41
- marginBottom: half
42
- };
43
- }
44
- else {
45
- return {
46
- marginLeft: half,
47
- marginRight: half,
48
- marginTop: half,
49
- marginBottom: half
50
- };
51
- }
52
- }
53
- const half = MUGlobal.half(margin);
54
- return {
55
- marginLeft: margin,
56
- marginRight: margin,
57
- marginTop: half,
58
- marginBottom: half
59
- };
60
- };
61
27
  return (React.createElement(Card, { sx: {
62
- height: (theme) => MUGlobal.adjustWithTheme(itemHeight, margin, theme.spacing),
63
- ...calculateMargin()
28
+ height: itemHeight,
29
+ ...margins
64
30
  } },
65
31
  React.createElement(CardHeader, { sx: { paddingBottom: 0.5 }, action: Array.isArray(actions) ? (React.createElement(MoreFab, { iconButton: true, size: "small", anchorOrigin: {
66
32
  vertical: 'bottom',
@@ -6,7 +6,7 @@ import { GridColumn } from '../components/GridColumn';
6
6
  import { GridJsonData } from '../components/GridLoader';
7
7
  import { DataGridExProps } from './DataGridEx';
8
8
  import { GridMethodRef } from './GridMethodRef';
9
- import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
9
+ import { ScrollerListExInnerItemRendererProps, ScrollerListExItemSize } from './ScrollerListEx';
10
10
  /**
11
11
  * ResponsibleContainer props
12
12
  */
@@ -51,7 +51,7 @@ export interface ResponsibleContainerProps<T extends {}, F extends DataTypes.Bas
51
51
  /**
52
52
  * Item size, a function indicates its a variable size list
53
53
  */
54
- itemSize: ((index: number) => number) | number;
54
+ itemSize: ScrollerListExItemSize;
55
55
  /**
56
56
  * Load data callback
57
57
  */
@@ -13,11 +13,26 @@ export interface ScrollerListExInnerItemRendererProps<T> extends ListChildCompon
13
13
  * Item height
14
14
  */
15
15
  itemHeight: number;
16
+ /**
17
+ * Item space
18
+ */
19
+ space: number;
20
+ /**
21
+ * Default margins
22
+ */
23
+ margins: {};
16
24
  }
25
+ /**
26
+ * Extended ScrollerList ItemSize type
27
+ * 1. Callback function
28
+ * 2. Static sets
29
+ * 3. Dynamic calculation
30
+ */
31
+ export declare type ScrollerListExItemSize = ((index: number) => [number, number] | [number, number, {}]) | [number, number] | [number, {}, boolean?];
17
32
  /**
18
33
  * Extended ScrollerList Props
19
34
  */
20
- export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'itemRenderer'> {
35
+ export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'itemRenderer' | 'itemSize'> {
21
36
  /**
22
37
  * Alternating colors for odd/even rows
23
38
  */
@@ -34,6 +49,10 @@ export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'item
34
49
  * Item renderer
35
50
  */
36
51
  itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
52
+ /**
53
+ * Item size, a function indicates its a variable size list
54
+ */
55
+ itemSize: ScrollerListExItemSize;
37
56
  /**
38
57
  * On items select change
39
58
  */
@@ -1,7 +1,9 @@
1
1
  import { css } from '@emotion/css';
2
2
  import { DataTypes, Utils } from '@etsoo/shared';
3
+ import { useTheme } from '@mui/material';
3
4
  import React from 'react';
4
5
  import { ScrollerList } from '../components/ScrollerList';
6
+ import { MUGlobal } from './MUGlobal';
5
7
  // Scroll bar size
6
8
  const scrollbarSize = 16;
7
9
  // Selected class name
@@ -36,15 +38,44 @@ const createGridStyle = (alternatingColors, selectedColor) => {
36
38
  }
37
39
  });
38
40
  };
41
+ // Default margin
42
+ const defaultMargin = (margin, isNarrow) => {
43
+ const half = MUGlobal.half(margin);
44
+ if (isNarrow == null) {
45
+ const half = MUGlobal.half(margin);
46
+ return {
47
+ marginLeft: margin,
48
+ marginRight: margin,
49
+ marginTop: half,
50
+ marginBottom: half
51
+ };
52
+ }
53
+ if (isNarrow) {
54
+ return {
55
+ marginLeft: 0,
56
+ marginRight: 0,
57
+ marginTop: half,
58
+ marginBottom: half
59
+ };
60
+ }
61
+ return {
62
+ marginLeft: half,
63
+ marginRight: half,
64
+ marginTop: half,
65
+ marginBottom: half
66
+ };
67
+ };
39
68
  // Default itemRenderer
40
- function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, selected, style, itemHeight }) {
69
+ function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, selected, style, itemHeight, space, margins }) {
41
70
  // Child
42
71
  const child = innerItemRenderer({
43
72
  index,
44
73
  data,
45
74
  style,
46
75
  selected,
47
- itemHeight
76
+ itemHeight,
77
+ space,
78
+ margins
48
79
  });
49
80
  let rowClass = `ScrollerListEx-Row${index % 2}`;
50
81
  if (selected)
@@ -82,17 +113,53 @@ export function ScrollerListEx(props) {
82
113
  };
83
114
  // Destruct
84
115
  const { alternatingColors = [undefined, undefined], className, idField = 'id', innerItemRenderer, itemSize, itemKey = (index, data) => { var _a; return (_a = DataTypes.getIdValue(data, idField)) !== null && _a !== void 0 ? _a : index; }, itemRenderer = (itemProps) => {
85
- const itemHeight = typeof itemSize === 'function'
86
- ? itemSize(itemProps.index)
87
- : itemSize;
116
+ const [itemHeight, space, margins] = calculateItemSize(itemProps.index);
88
117
  return defaultItemRenderer({
89
118
  itemHeight,
90
119
  innerItemRenderer,
91
120
  onMouseDown,
121
+ space,
122
+ margins,
92
123
  selected: isSelected(itemProps.data),
93
124
  ...itemProps
94
125
  });
95
126
  }, onSelectChange, selectedColor = '#edf4fb', ...rest } = props;
127
+ // Theme
128
+ const theme = useTheme();
129
+ // Cache calculation
130
+ const itemSizeResult = React.useMemo(() => {
131
+ if (typeof itemSize === 'function')
132
+ return undefined;
133
+ const [size, spaces, isNarrow] = itemSize;
134
+ if (typeof spaces === 'number')
135
+ return [
136
+ size,
137
+ spaces,
138
+ defaultMargin(MUGlobal.pagePaddings, undefined)
139
+ ];
140
+ return [
141
+ size,
142
+ MUGlobal.getSpace(spaces, theme),
143
+ defaultMargin(spaces, isNarrow)
144
+ ];
145
+ }, [itemSize]);
146
+ // Calculate size
147
+ const calculateItemSize = (index) => {
148
+ // Callback function
149
+ if (typeof itemSize === 'function') {
150
+ const result = itemSize(index);
151
+ if (result.length == 2)
152
+ return [...result, defaultMargin(MUGlobal.pagePaddings)];
153
+ return result;
154
+ }
155
+ // Calculation
156
+ return itemSizeResult;
157
+ };
158
+ // Local item size
159
+ const itemSizeLocal = (index) => {
160
+ const [size, space] = calculateItemSize(index);
161
+ return size + space;
162
+ };
96
163
  // Layout
97
- return (React.createElement(ScrollerList, { className: Utils.mergeClasses('ScrollerListEx-Body', className, createGridStyle(alternatingColors, selectedColor)), itemKey: itemKey, itemRenderer: itemRenderer, itemSize: itemSize, ...rest }));
164
+ return (React.createElement(ScrollerList, { className: Utils.mergeClasses('ScrollerListEx-Body', className, createGridStyle(alternatingColors, selectedColor)), itemKey: itemKey, itemRenderer: itemRenderer, itemSize: itemSizeLocal, ...rest }));
98
165
  }
@@ -4,10 +4,18 @@ import { CommonPageProps } from './CommonPageProps';
4
4
  * Add / Edit page props
5
5
  */
6
6
  export interface EditPageProps extends Omit<CommonPageProps, 'onSubmit'> {
7
+ /**
8
+ * Is editing
9
+ */
10
+ isEditing?: boolean;
7
11
  /**
8
12
  * On form submit
9
13
  */
10
14
  onSubmit?: FormEventHandler<HTMLFormElement>;
15
+ /**
16
+ * On delete callback
17
+ */
18
+ onDelete?: () => Promise<void> | void;
11
19
  }
12
20
  /**
13
21
  * Add / Edit page
@@ -4,22 +4,24 @@ import { Labels } from '../../app/Labels';
4
4
  import { MUGlobal } from '../MUGlobal';
5
5
  import { CommonPage, CommonPageScrollContainer } from './CommonPage';
6
6
  import SaveIcon from '@mui/icons-material/Save';
7
+ import DeleteIcon from '@mui/icons-material/Delete';
7
8
  /**
8
9
  * Add / Edit page
9
10
  * @param props Props
10
11
  */
11
12
  export function EditPage(props) {
12
13
  // Destruct
13
- const { children, onSubmit, paddings = MUGlobal.pagePaddings, scrollContainer = CommonPageScrollContainer, ...rest } = props;
14
+ const { children, isEditing, onDelete, onSubmit, paddings = MUGlobal.pagePaddings, scrollContainer = CommonPageScrollContainer, ...rest } = props;
14
15
  // Labels
15
16
  const labels = Labels.CommonPage;
16
17
  return (React.createElement(CommonPage, { paddings: paddings, scrollContainer: scrollContainer, ...rest },
17
18
  React.createElement("form", { onSubmit: onSubmit },
18
19
  React.createElement(Grid, { container: true, justifyContent: "left", spacing: paddings, paddingTop: 1 }, children),
19
- React.createElement(Grid, { container: true, position: "sticky", sx: {
20
+ React.createElement(Grid, { container: true, position: "sticky", display: "flex", gap: paddings, sx: {
20
21
  top: 'auto',
21
22
  bottom: (theme) => MUGlobal.updateWithTheme(paddings, theme.spacing),
22
23
  paddingTop: paddings
23
24
  } },
24
- React.createElement(Button, { variant: "contained", type: "submit", fullWidth: true, startIcon: React.createElement(SaveIcon, null) }, labels.save)))));
25
+ isEditing && onDelete && (React.createElement(Button, { color: "primary", variant: "outlined", onClick: () => onDelete(), startIcon: React.createElement(DeleteIcon, { color: "warning" }) }, labels.delete)),
26
+ React.createElement(Button, { variant: "contained", type: "submit", startIcon: React.createElement(SaveIcon, null), sx: { flexGrow: 1 } }, labels.save)))));
25
27
  }
@@ -2,7 +2,7 @@
2
2
  import { DataTypes } from '@etsoo/shared';
3
3
  import { ListChildComponentProps } from 'react-window';
4
4
  import { GridMethodRef } from '../GridMethodRef';
5
- import { ScrollerListExInnerItemRendererProps } from '../ScrollerListEx';
5
+ import { ScrollerListExInnerItemRendererProps, ScrollerListExItemSize } from '../ScrollerListEx';
6
6
  import { DataGridPageProps } from './DataGridPageProps';
7
7
  /**
8
8
  * Response page props
@@ -23,7 +23,7 @@ export interface ResponsePageProps<T, F extends DataTypes.BasicTemplate> extends
23
23
  /**
24
24
  * Item size, a function indicates its a variable size list
25
25
  */
26
- itemSize: ((index: number) => number) | number;
26
+ itemSize: ScrollerListExItemSize;
27
27
  /**
28
28
  * Methods
29
29
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/react",
3
- "version": "1.4.38",
3
+ "version": "1.4.42",
4
4
  "description": "TypeScript ReactJs framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  "@emotion/react": "^11.7.1",
51
51
  "@emotion/style": "^0.8.0",
52
52
  "@emotion/styled": "^11.6.0",
53
- "@etsoo/appscript": "^1.2.22",
53
+ "@etsoo/appscript": "^1.2.23",
54
54
  "@etsoo/notificationbase": "^1.1.0",
55
55
  "@etsoo/shared": "^1.1.3",
56
56
  "@mui/icons-material": "^5.2.5",
package/src/app/Labels.ts CHANGED
@@ -9,6 +9,7 @@ export namespace Labels {
9
9
  */
10
10
  export const CommonPage = {
11
11
  add: 'Add',
12
+ delete: 'Delete',
12
13
  more: 'More',
13
14
  pullToRefresh: 'Pull down to refresh',
14
15
  refresh: 'Refresh',
@@ -103,7 +103,7 @@ export namespace DataGridRenderers {
103
103
  }
104
104
 
105
105
  if (value) return <CheckIcon fontSize="small" />;
106
- else return <ClearIcon fontSize="small" color="error" />;
106
+ else return <ClearIcon fontSize="small" color="warning" />;
107
107
  }
108
108
 
109
109
  // To string
@@ -1,4 +1,5 @@
1
1
  import { NumberUtils } from '@etsoo/shared';
2
+ import { Breakpoint, Theme } from '@mui/material';
2
3
 
3
4
  export class MUGlobal {
4
5
  /**
@@ -125,6 +126,37 @@ export class MUGlobal {
125
126
  return newObj;
126
127
  }
127
128
 
129
+ /**
130
+ * Break points defined
131
+ */
132
+ static breakpoints = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
133
+
134
+ /**
135
+ * Get multple medias theme space
136
+ * Responsive values and Breakpoints as an object
137
+ * xs = theme.breakpoints.up('xs')
138
+ * https://mui.com/system/basics/
139
+ * @param spaces Spaces
140
+ * @param theme Theme
141
+ * @returns Result
142
+ */
143
+ static getSpace(spaces: {}, theme: Theme) {
144
+ const start = this.breakpoints.length - 1;
145
+ for (let i = start; i >= 0; i--) {
146
+ const key = this.breakpoints[i];
147
+ const value = Reflect.get(spaces, key);
148
+ if (typeof value === 'number') {
149
+ const mediaRaw = theme.breakpoints.up(key as Breakpoint);
150
+ const mediaQuery = mediaRaw.substring(mediaRaw.indexOf('('));
151
+ if (window.matchMedia(mediaQuery).matches) {
152
+ return parseInt(theme.spacing(value), 10);
153
+ }
154
+ }
155
+ }
156
+
157
+ return 0;
158
+ }
159
+
128
160
  /**
129
161
  * Update object number properties with theme
130
162
  * @param input Input object
@@ -1,15 +1,7 @@
1
- import {
2
- Card,
3
- CardContent,
4
- CardHeader,
5
- LinearProgress,
6
- SxProps,
7
- Theme
8
- } from '@mui/material';
1
+ import { Card, CardContent, CardHeader, LinearProgress } from '@mui/material';
9
2
  import React from 'react';
10
3
  import { ListItemReact } from '../components/ListItemReact';
11
4
  import { MoreFab } from './MoreFab';
12
- import { MUGlobal } from './MUGlobal';
13
5
  import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
14
6
 
15
7
  function getActions(input: (ListItemReact | boolean)[]): ListItemReact[] {
@@ -30,8 +22,7 @@ function getActions(input: (ListItemReact | boolean)[]): ListItemReact[] {
30
22
  * @returns Component
31
23
  */
32
24
  export function MobileListItemRenderer<T>(
33
- { data, itemHeight }: ScrollerListExInnerItemRendererProps<T>,
34
- margin: {} | [{}, boolean] | (() => SxProps<Theme>),
25
+ { data, itemHeight, margins }: ScrollerListExInnerItemRendererProps<T>,
35
26
  renderer: (
36
27
  data: T
37
28
  ) => [
@@ -47,45 +38,11 @@ export function MobileListItemRenderer<T>(
47
38
  // Elements
48
39
  const [title, subheader, actions, children] = renderer(data);
49
40
 
50
- // Calculate margin
51
- const calculateMargin = () => {
52
- if (typeof margin === 'function') return margin();
53
- if (Array.isArray(margin)) {
54
- const marginValue = margin[0];
55
- const isNarrow = margin[1];
56
- const half = MUGlobal.half(marginValue);
57
- if (isNarrow) {
58
- return {
59
- marginLeft: 0,
60
- marginRight: 0,
61
- marginTop: half,
62
- marginBottom: half
63
- };
64
- } else {
65
- return {
66
- marginLeft: half,
67
- marginRight: half,
68
- marginTop: half,
69
- marginBottom: half
70
- };
71
- }
72
- }
73
-
74
- const half = MUGlobal.half(margin);
75
- return {
76
- marginLeft: margin,
77
- marginRight: margin,
78
- marginTop: half,
79
- marginBottom: half
80
- };
81
- };
82
-
83
41
  return (
84
42
  <Card
85
43
  sx={{
86
- height: (theme) =>
87
- MUGlobal.adjustWithTheme(itemHeight, margin, theme.spacing),
88
- ...calculateMargin()
44
+ height: itemHeight,
45
+ ...margins
89
46
  }}
90
47
  >
91
48
  <CardHeader
@@ -21,7 +21,8 @@ import { MUGlobal } from './MUGlobal';
21
21
  import { PullToRefreshUI } from './PullToRefreshUI';
22
22
  import {
23
23
  ScrollerListEx,
24
- ScrollerListExInnerItemRendererProps
24
+ ScrollerListExInnerItemRendererProps,
25
+ ScrollerListExItemSize
25
26
  } from './ScrollerListEx';
26
27
  import { SearchBar } from './SearchBar';
27
28
 
@@ -91,7 +92,7 @@ export interface ResponsibleContainerProps<
91
92
  /**
92
93
  * Item size, a function indicates its a variable size list
93
94
  */
94
- itemSize: ((index: number) => number) | number;
95
+ itemSize: ScrollerListExItemSize;
95
96
 
96
97
  /**
97
98
  * Load data callback
@@ -1,8 +1,10 @@
1
1
  import { css } from '@emotion/css';
2
2
  import { DataTypes, Utils } from '@etsoo/shared';
3
+ import { useTheme } from '@mui/material';
3
4
  import React from 'react';
4
5
  import { ListChildComponentProps } from 'react-window';
5
6
  import { ScrollerList, ScrollerListProps } from '../components/ScrollerList';
7
+ import { MUGlobal } from './MUGlobal';
6
8
 
7
9
  // Scroll bar size
8
10
  const scrollbarSize = 16;
@@ -44,6 +46,37 @@ const createGridStyle = (
44
46
  });
45
47
  };
46
48
 
49
+ // Default margin
50
+ const defaultMargin = (margin: {}, isNarrow?: boolean) => {
51
+ const half = MUGlobal.half(margin);
52
+
53
+ if (isNarrow == null) {
54
+ const half = MUGlobal.half(margin);
55
+ return {
56
+ marginLeft: margin,
57
+ marginRight: margin,
58
+ marginTop: half,
59
+ marginBottom: half
60
+ };
61
+ }
62
+
63
+ if (isNarrow) {
64
+ return {
65
+ marginLeft: 0,
66
+ marginRight: 0,
67
+ marginTop: half,
68
+ marginBottom: half
69
+ };
70
+ }
71
+
72
+ return {
73
+ marginLeft: half,
74
+ marginRight: half,
75
+ marginTop: half,
76
+ marginBottom: half
77
+ };
78
+ };
79
+
47
80
  /**
48
81
  * Extended ScrollerList inner item renderer props
49
82
  */
@@ -58,13 +91,34 @@ export interface ScrollerListExInnerItemRendererProps<T>
58
91
  * Item height
59
92
  */
60
93
  itemHeight: number;
94
+
95
+ /**
96
+ * Item space
97
+ */
98
+ space: number;
99
+
100
+ /**
101
+ * Default margins
102
+ */
103
+ margins: {};
61
104
  }
62
105
 
106
+ /**
107
+ * Extended ScrollerList ItemSize type
108
+ * 1. Callback function
109
+ * 2. Static sets
110
+ * 3. Dynamic calculation
111
+ */
112
+ export type ScrollerListExItemSize =
113
+ | ((index: number) => [number, number] | [number, number, {}])
114
+ | [number, number]
115
+ | [number, {}, boolean?];
116
+
63
117
  /**
64
118
  * Extended ScrollerList Props
65
119
  */
66
120
  export interface ScrollerListExProps<T>
67
- extends Omit<ScrollerListProps<T>, 'itemRenderer'> {
121
+ extends Omit<ScrollerListProps<T>, 'itemRenderer' | 'itemSize'> {
68
122
  /**
69
123
  * Alternating colors for odd/even rows
70
124
  */
@@ -87,6 +141,11 @@ export interface ScrollerListExProps<T>
87
141
  */
88
142
  itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
89
143
 
144
+ /**
145
+ * Item size, a function indicates its a variable size list
146
+ */
147
+ itemSize: ScrollerListExItemSize;
148
+
90
149
  /**
91
150
  * On items select change
92
151
  */
@@ -116,6 +175,16 @@ interface defaultItemRendererProps<T> extends ListChildComponentProps<T> {
116
175
  */
117
176
  itemHeight: number;
118
177
 
178
+ /**
179
+ * Item space
180
+ */
181
+ space: number;
182
+
183
+ /**
184
+ * Default margins
185
+ */
186
+ margins: {};
187
+
119
188
  /**
120
189
  * Item selected
121
190
  */
@@ -130,7 +199,9 @@ function defaultItemRenderer<T>({
130
199
  onMouseDown,
131
200
  selected,
132
201
  style,
133
- itemHeight
202
+ itemHeight,
203
+ space,
204
+ margins
134
205
  }: defaultItemRendererProps<T>) {
135
206
  // Child
136
207
  const child = innerItemRenderer({
@@ -138,7 +209,9 @@ function defaultItemRenderer<T>({
138
209
  data,
139
210
  style,
140
211
  selected,
141
- itemHeight
212
+ itemHeight,
213
+ space,
214
+ margins
142
215
  });
143
216
 
144
217
  let rowClass = `ScrollerListEx-Row${index % 2}`;
@@ -202,14 +275,15 @@ export function ScrollerListEx<T extends Record<string, unknown>>(
202
275
  itemKey = (index: number, data: T) =>
203
276
  DataTypes.getIdValue(data, idField) ?? index,
204
277
  itemRenderer = (itemProps) => {
205
- const itemHeight =
206
- typeof itemSize === 'function'
207
- ? itemSize(itemProps.index)
208
- : itemSize;
278
+ const [itemHeight, space, margins] = calculateItemSize(
279
+ itemProps.index
280
+ );
209
281
  return defaultItemRenderer({
210
282
  itemHeight,
211
283
  innerItemRenderer,
212
284
  onMouseDown,
285
+ space,
286
+ margins,
213
287
  selected: isSelected(itemProps.data),
214
288
  ...itemProps
215
289
  });
@@ -219,6 +293,49 @@ export function ScrollerListEx<T extends Record<string, unknown>>(
219
293
  ...rest
220
294
  } = props;
221
295
 
296
+ // Theme
297
+ const theme = useTheme();
298
+
299
+ // Cache calculation
300
+ const itemSizeResult = React.useMemo(():
301
+ | [number, number, {}]
302
+ | undefined => {
303
+ if (typeof itemSize === 'function') return undefined;
304
+ const [size, spaces, isNarrow] = itemSize;
305
+ if (typeof spaces === 'number')
306
+ return [
307
+ size,
308
+ spaces,
309
+ defaultMargin(MUGlobal.pagePaddings, undefined)
310
+ ];
311
+
312
+ return [
313
+ size,
314
+ MUGlobal.getSpace(spaces, theme),
315
+ defaultMargin(spaces, isNarrow)
316
+ ];
317
+ }, [itemSize]);
318
+
319
+ // Calculate size
320
+ const calculateItemSize = (index: number): [number, number, {}] => {
321
+ // Callback function
322
+ if (typeof itemSize === 'function') {
323
+ const result = itemSize(index);
324
+ if (result.length == 2)
325
+ return [...result, defaultMargin(MUGlobal.pagePaddings)];
326
+ return result;
327
+ }
328
+
329
+ // Calculation
330
+ return itemSizeResult!;
331
+ };
332
+
333
+ // Local item size
334
+ const itemSizeLocal = (index: number) => {
335
+ const [size, space] = calculateItemSize(index);
336
+ return size + space;
337
+ };
338
+
222
339
  // Layout
223
340
  return (
224
341
  <ScrollerList<T>
@@ -229,7 +346,7 @@ export function ScrollerListEx<T extends Record<string, unknown>>(
229
346
  )}
230
347
  itemKey={itemKey}
231
348
  itemRenderer={itemRenderer}
232
- itemSize={itemSize}
349
+ itemSize={itemSizeLocal}
233
350
  {...rest}
234
351
  />
235
352
  );
@@ -5,15 +5,26 @@ import { MUGlobal } from '../MUGlobal';
5
5
  import { CommonPage, CommonPageScrollContainer } from './CommonPage';
6
6
  import { CommonPageProps } from './CommonPageProps';
7
7
  import SaveIcon from '@mui/icons-material/Save';
8
+ import DeleteIcon from '@mui/icons-material/Delete';
8
9
 
9
10
  /**
10
11
  * Add / Edit page props
11
12
  */
12
13
  export interface EditPageProps extends Omit<CommonPageProps, 'onSubmit'> {
14
+ /**
15
+ * Is editing
16
+ */
17
+ isEditing?: boolean;
18
+
13
19
  /**
14
20
  * On form submit
15
21
  */
16
22
  onSubmit?: FormEventHandler<HTMLFormElement>;
23
+
24
+ /**
25
+ * On delete callback
26
+ */
27
+ onDelete?: () => Promise<void> | void;
17
28
  }
18
29
 
19
30
  /**
@@ -24,6 +35,8 @@ export function EditPage(props: EditPageProps) {
24
35
  // Destruct
25
36
  const {
26
37
  children,
38
+ isEditing,
39
+ onDelete,
27
40
  onSubmit,
28
41
  paddings = MUGlobal.pagePaddings,
29
42
  scrollContainer = CommonPageScrollContainer,
@@ -51,6 +64,8 @@ export function EditPage(props: EditPageProps) {
51
64
  <Grid
52
65
  container
53
66
  position="sticky"
67
+ display="flex"
68
+ gap={paddings}
54
69
  sx={{
55
70
  top: 'auto',
56
71
  bottom: (theme) =>
@@ -58,11 +73,21 @@ export function EditPage(props: EditPageProps) {
58
73
  paddingTop: paddings
59
74
  }}
60
75
  >
76
+ {isEditing && onDelete && (
77
+ <Button
78
+ color="primary"
79
+ variant="outlined"
80
+ onClick={() => onDelete()}
81
+ startIcon={<DeleteIcon color="warning" />}
82
+ >
83
+ {labels.delete}
84
+ </Button>
85
+ )}
61
86
  <Button
62
87
  variant="contained"
63
88
  type="submit"
64
- fullWidth
65
89
  startIcon={<SaveIcon />}
90
+ sx={{ flexGrow: 1 }}
66
91
  >
67
92
  {labels.save}
68
93
  </Button>
@@ -1,7 +1,10 @@
1
1
  import { DataTypes } from '@etsoo/shared';
2
2
  import { ListChildComponentProps } from 'react-window';
3
3
  import { GridMethodRef } from '../GridMethodRef';
4
- import { ScrollerListExInnerItemRendererProps } from '../ScrollerListEx';
4
+ import {
5
+ ScrollerListExInnerItemRendererProps,
6
+ ScrollerListExItemSize
7
+ } from '../ScrollerListEx';
5
8
  import { DataGridPageProps } from './DataGridPageProps';
6
9
 
7
10
  /**
@@ -32,7 +35,7 @@ export interface ResponsePageProps<T, F extends DataTypes.BasicTemplate>
32
35
  /**
33
36
  * Item size, a function indicates its a variable size list
34
37
  */
35
- itemSize: ((index: number) => number) | number;
38
+ itemSize: ScrollerListExItemSize;
36
39
 
37
40
  /**
38
41
  * Methods