@etsoo/materialui 1.5.53 → 1.5.54

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.
@@ -3,7 +3,7 @@ import { DataTypes, IdType } from "@etsoo/shared";
3
3
  type DnDItemType = {
4
4
  id: IdType;
5
5
  };
6
- export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps, "chidren" | "onClick"> & {
6
+ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps, "chidren" | "onClick" | "value"> & {
7
7
  /**
8
8
  * Add items splitter
9
9
  */
@@ -40,7 +40,7 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps,
40
40
  /**
41
41
  * Load data
42
42
  */
43
- loadData: (ids?: D["id"][]) => Promise<D[]>;
43
+ loadData: () => Promise<D[]>;
44
44
  /**
45
45
  * On add handler
46
46
  * @param ids Ids
@@ -64,9 +64,9 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps,
64
64
  */
65
65
  required?: boolean;
66
66
  /**
67
- * Ids
67
+ * Value
68
68
  */
69
- ids?: D["id"][];
69
+ value?: D["id"][];
70
70
  };
71
71
  export declare function ButtonPopupCheckbox<D extends DnDItemType>(props: ButtonPopupCheckboxProps<D>): import("react/jsx-runtime").JSX.Element;
72
72
  export {};
@@ -23,7 +23,7 @@ const ReactApp_1 = require("./app/ReactApp");
23
23
  const FormLabel_1 = __importDefault(require("@mui/material/FormLabel"));
24
24
  function ButtonPopupList(props) {
25
25
  // Destruct
26
- const { addSplitter = /\s*[,;]\s*/, ids = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
26
+ const { addSplitter = /\s*[,;]\s*/, value = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
27
27
  // Methods
28
28
  const dndRef = react_1.default.createRef();
29
29
  // Ref
@@ -32,10 +32,10 @@ function ButtonPopupList(props) {
32
32
  const [selectedIds, setSelectedIds] = react_1.default.useState([]);
33
33
  react_1.default.useEffect(() => {
34
34
  // Sort items by ids for first load
35
- items.sortByProperty("id", ids);
35
+ items.sortByProperty("id", value);
36
36
  // Set selected ids
37
- setSelectedIds([...ids]);
38
- }, [ids]);
37
+ setSelectedIds([...value]);
38
+ }, [value]);
39
39
  return ((0, jsx_runtime_1.jsxs)(FlexBox_1.VBox, { gap: 2, children: [(0, jsx_runtime_1.jsx)(FormGroup_1.default, { children: (0, jsx_runtime_1.jsx)(Grid_1.default, { container: true, spacing: 0, children: (0, jsx_runtime_1.jsx)(DnDList_1.DnDList, { items: items, labelField: labelField, itemRenderer: (item, index, nodeRef, actionNodeRef) => ((0, jsx_runtime_1.jsxs)(Grid_1.default, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ...nodeRef, children: [(0, jsx_runtime_1.jsx)(IconButton_1.default, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ...actionNodeRef, children: (0, jsx_runtime_1.jsx)(DragIndicator_1.default, {}) }), (0, jsx_runtime_1.jsx)(FormControlLabel_1.default, { control: (0, jsx_runtime_1.jsx)(Checkbox_1.default, { name: "item", value: item.id, checked: selectedIds.includes(item.id), onChange: (e) => {
40
40
  const checked = e.target.checked;
41
41
  const newIds = [
@@ -73,7 +73,7 @@ function ButtonPopupCheckbox(props) {
73
73
  // App
74
74
  const app = (0, ReactApp_1.useRequiredAppContext)();
75
75
  // Destruct
76
- const { addSplitter, ids = [], inputName, label, labelEnd, labelFormatter = (data) => {
76
+ const { addSplitter, value = [], inputName, label, labelEnd, labelFormatter = (data) => {
77
77
  if (labelField in data) {
78
78
  return data[labelField];
79
79
  }
@@ -99,8 +99,8 @@ function ButtonPopupCheckbox(props) {
99
99
  }, []);
100
100
  react_1.default.useEffect(() => {
101
101
  // Set selected ids
102
- setSelectedIds(ids);
103
- }, [ids]);
102
+ setSelectedIds(value);
103
+ }, [value]);
104
104
  // Selected ids
105
105
  const tempSelectedIds = react_1.default.useRef();
106
106
  // Click handler
@@ -115,7 +115,7 @@ function ButtonPopupCheckbox(props) {
115
115
  setSelectedIds(ids);
116
116
  onValueChange?.(ids);
117
117
  },
118
- inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, ids: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
118
+ inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, value: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
119
119
  tempSelectedIds.current = ids;
120
120
  } })),
121
121
  fullScreen: app.smDown
@@ -125,6 +125,8 @@ function ButtonPopupCheckbox(props) {
125
125
  const item = items.find((item) => item.id === id);
126
126
  if (item == null)
127
127
  return null;
128
- return (0, jsx_runtime_1.jsx)(Chip_1.default, { size: "small", label: labelFormatter(item) }, id);
128
+ return ((0, jsx_runtime_1.jsx)(Chip_1.default, { sx: {
129
+ pointerEvents: "none"
130
+ }, size: "small", label: labelFormatter(item) }, id));
129
131
  }), labelEnd && (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "caption", children: labelEnd })] })] }));
130
132
  }
@@ -0,0 +1,72 @@
1
+ import { ButtonProps } from "@mui/material/Button";
2
+ import { DataTypes, IdType } from "@etsoo/shared";
3
+ type DnDItemType = {
4
+ id: IdType;
5
+ };
6
+ export type ButtonPopupRadioProps<D extends DnDItemType> = Omit<ButtonProps, "chidren" | "onClick" | "value"> & {
7
+ /**
8
+ * Add items splitter
9
+ */
10
+ addSplitter?: RegExp;
11
+ /**
12
+ * Input field name
13
+ */
14
+ inputName?: string;
15
+ /**
16
+ * Label
17
+ */
18
+ label?: string;
19
+ /**
20
+ * Label in the end
21
+ */
22
+ labelEnd?: string;
23
+ /**
24
+ * Label field in items
25
+ */
26
+ labelField: DataTypes.Keys<D>;
27
+ /**
28
+ * Label formatter
29
+ * @param item Item to be formatted
30
+ */
31
+ labelFormatter?: (item: D) => string;
32
+ /**
33
+ * Labels
34
+ */
35
+ labels?: {
36
+ dragIndicator?: string;
37
+ add?: string;
38
+ more?: string;
39
+ };
40
+ /**
41
+ * Load data
42
+ */
43
+ loadData: () => Promise<D[]>;
44
+ /**
45
+ * On add handler
46
+ * @param ids Ids
47
+ */
48
+ onAdd?: (ids: string[]) => Promise<false | D[]>;
49
+ /**
50
+ * On value change handler
51
+ * @param id Id
52
+ */
53
+ onValueChange?: (id: D["id"]) => void;
54
+ /**
55
+ * Popup title
56
+ */
57
+ popupTitle?: string;
58
+ /**
59
+ * Popup message
60
+ */
61
+ popupMessage?: string;
62
+ /**
63
+ * The field is required or not
64
+ */
65
+ required?: boolean;
66
+ /**
67
+ * Value
68
+ */
69
+ value?: D["id"];
70
+ };
71
+ export declare function ButtonPopupRadio<D extends DnDItemType>(props: ButtonPopupRadioProps<D>): import("react/jsx-runtime").JSX.Element;
72
+ export {};
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ButtonPopupRadio = ButtonPopupRadio;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const Button_1 = __importDefault(require("@mui/material/Button"));
9
+ const Chip_1 = __importDefault(require("@mui/material/Chip"));
10
+ const react_1 = __importDefault(require("react"));
11
+ const shared_1 = require("@etsoo/shared");
12
+ const Grid_1 = __importDefault(require("@mui/material/Grid"));
13
+ const Typography_1 = __importDefault(require("@mui/material/Typography"));
14
+ const Add_1 = __importDefault(require("@mui/icons-material/Add"));
15
+ const FormControlLabel_1 = __importDefault(require("@mui/material/FormControlLabel"));
16
+ const Radio_1 = __importDefault(require("@mui/material/Radio"));
17
+ const TextField_1 = __importDefault(require("@mui/material/TextField"));
18
+ const FormLabel_1 = __importDefault(require("@mui/material/FormLabel"));
19
+ const RadioGroup_1 = __importDefault(require("@mui/material/RadioGroup"));
20
+ const FlexBox_1 = require("./FlexBox");
21
+ const ReactApp_1 = require("./app/ReactApp");
22
+ function ButtonPopupList(props) {
23
+ // Destruct
24
+ const { addSplitter = /\s*[,;]\s*/, value, items, labelFormatter, labels, onAdd, onValueChange } = props;
25
+ // Ref
26
+ const inputRef = react_1.default.useRef(null);
27
+ // State
28
+ const [currentValue, setCurrentValue] = react_1.default.useState();
29
+ react_1.default.useEffect(() => {
30
+ setCurrentValue(value);
31
+ }, [value]);
32
+ return ((0, jsx_runtime_1.jsxs)(FlexBox_1.VBox, { gap: 2, children: [(0, jsx_runtime_1.jsx)(RadioGroup_1.default, { value: currentValue ?? "", name: "radio-buttons-group", onChange: (e, v) => {
33
+ const checked = e.target.checked;
34
+ const value = checked
35
+ ? typeof items[0].id === "number"
36
+ ? shared_1.NumberUtils.parse(v)
37
+ : v
38
+ : undefined;
39
+ setCurrentValue(value);
40
+ onValueChange(value);
41
+ }, children: (0, jsx_runtime_1.jsx)(Grid_1.default, { container: true, spacing: 0, children: items.map((item) => ((0, jsx_runtime_1.jsx)(Grid_1.default, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, children: (0, jsx_runtime_1.jsx)(FormControlLabel_1.default, { control: (0, jsx_runtime_1.jsx)(Radio_1.default, { value: item.id }), label: `${labelFormatter(item)}` }) }, item.id))) }) }), onAdd && ((0, jsx_runtime_1.jsxs)(FlexBox_1.HBox, { gap: 1, children: [(0, jsx_runtime_1.jsx)(TextField_1.default, { variant: "outlined", label: labels?.more, fullWidth: true, inputRef: inputRef }), (0, jsx_runtime_1.jsx)(Button_1.default, { sx: { width: "120px" }, variant: "contained", startIcon: (0, jsx_runtime_1.jsx)(Add_1.default, {}), size: "small", onClick: async () => {
42
+ if (inputRef.current == null)
43
+ return;
44
+ const input = inputRef.current.value.trim();
45
+ if (input === "") {
46
+ inputRef.current.focus();
47
+ return;
48
+ }
49
+ const inputIds = input
50
+ .split(addSplitter)
51
+ .filter((id) => !items.some((item) => item.id == id));
52
+ if (inputIds.length === 0) {
53
+ inputRef.current.focus();
54
+ return;
55
+ }
56
+ const result = await onAdd(inputIds);
57
+ if (result === false) {
58
+ inputRef.current.focus();
59
+ return;
60
+ }
61
+ inputRef.current.value = "";
62
+ inputRef.current.focus();
63
+ }, children: labels?.add })] }))] }));
64
+ }
65
+ function ButtonPopupRadio(props) {
66
+ // App
67
+ const app = (0, ReactApp_1.useRequiredAppContext)();
68
+ // Destruct
69
+ const { addSplitter, inputName, label, labelEnd, labelFormatter = (data) => {
70
+ if (labelField in data) {
71
+ return data[labelField];
72
+ }
73
+ return data.id.toString();
74
+ }, labelField, labels = {}, loadData, onAdd, onValueChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start", minHeight: "56px" }, value, variant = "outlined", ...rest } = props;
75
+ // Default labels
76
+ if (!labels.add)
77
+ labels.add = app.get("add");
78
+ if (!labels.dragIndicator)
79
+ labels.dragIndicator = app.get("dragIndicator");
80
+ if (!labels.more)
81
+ labels.more = app.get("more");
82
+ // State
83
+ const [items, setItems] = react_1.default.useState([]);
84
+ const [currentValue, setCurrentValue] = react_1.default.useState();
85
+ const item = currentValue
86
+ ? items.find((item) => item.id === currentValue)
87
+ : undefined;
88
+ react_1.default.useEffect(() => {
89
+ // Load data
90
+ loadData().then((data) => {
91
+ if (data != null) {
92
+ setItems(data);
93
+ }
94
+ });
95
+ }, []);
96
+ react_1.default.useEffect(() => {
97
+ setCurrentValue(value);
98
+ }, [value]);
99
+ // Selected id
100
+ const tempSelectedId = react_1.default.useRef();
101
+ // Click handler
102
+ const clickHandler = () => {
103
+ app.showInputDialog({
104
+ title: popupTitle,
105
+ message: popupMessage,
106
+ callback: (form) => {
107
+ if (form == null || tempSelectedId.current == null)
108
+ return;
109
+ const id = tempSelectedId.current;
110
+ setCurrentValue(id);
111
+ onValueChange?.(id);
112
+ },
113
+ inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, value: currentValue, items: items, labelFormatter: labelFormatter, labels: labels, onAdd: onAdd, onValueChange: (id) => {
114
+ tempSelectedId.current = id;
115
+ } })),
116
+ fullScreen: app.smDown
117
+ });
118
+ };
119
+ return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)("input", { type: "text", style: { position: "absolute", opacity: 0, width: 0 }, name: inputName, required: required, defaultValue: currentValue }), (0, jsx_runtime_1.jsxs)(Button_1.default, { variant: variant, sx: sx, onClick: () => clickHandler(), ...rest, disabled: !items || items.length === 0, children: [label && ((0, jsx_runtime_1.jsx)(FormLabel_1.default, { required: required, sx: { fontSize: (theme) => theme.typography.body2.fontSize }, children: label })), item ? ((0, jsx_runtime_1.jsx)(Chip_1.default, { sx: {
120
+ height: "auto",
121
+ pointerEvents: "none",
122
+ "& .MuiChip-label": {
123
+ display: "block",
124
+ whiteSpace: "normal"
125
+ }
126
+ }, size: "small", label: labelFormatter(item) })) : undefined, labelEnd && (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "caption", children: labelEnd })] })] }));
127
+ }
@@ -3,7 +3,7 @@ import { DataTypes, IdType } from "@etsoo/shared";
3
3
  type DnDItemType = {
4
4
  id: IdType;
5
5
  };
6
- export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps, "chidren" | "onClick"> & {
6
+ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps, "chidren" | "onClick" | "value"> & {
7
7
  /**
8
8
  * Add items splitter
9
9
  */
@@ -40,7 +40,7 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps,
40
40
  /**
41
41
  * Load data
42
42
  */
43
- loadData: (ids?: D["id"][]) => Promise<D[]>;
43
+ loadData: () => Promise<D[]>;
44
44
  /**
45
45
  * On add handler
46
46
  * @param ids Ids
@@ -64,9 +64,9 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps,
64
64
  */
65
65
  required?: boolean;
66
66
  /**
67
- * Ids
67
+ * Value
68
68
  */
69
- ids?: D["id"][];
69
+ value?: D["id"][];
70
70
  };
71
71
  export declare function ButtonPopupCheckbox<D extends DnDItemType>(props: ButtonPopupCheckboxProps<D>): import("react/jsx-runtime").JSX.Element;
72
72
  export {};
@@ -17,7 +17,7 @@ import { useRequiredAppContext } from "./app/ReactApp";
17
17
  import FormLabel from "@mui/material/FormLabel";
18
18
  function ButtonPopupList(props) {
19
19
  // Destruct
20
- const { addSplitter = /\s*[,;]\s*/, ids = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
20
+ const { addSplitter = /\s*[,;]\s*/, value = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
21
21
  // Methods
22
22
  const dndRef = React.createRef();
23
23
  // Ref
@@ -26,10 +26,10 @@ function ButtonPopupList(props) {
26
26
  const [selectedIds, setSelectedIds] = React.useState([]);
27
27
  React.useEffect(() => {
28
28
  // Sort items by ids for first load
29
- items.sortByProperty("id", ids);
29
+ items.sortByProperty("id", value);
30
30
  // Set selected ids
31
- setSelectedIds([...ids]);
32
- }, [ids]);
31
+ setSelectedIds([...value]);
32
+ }, [value]);
33
33
  return (_jsxs(VBox, { gap: 2, children: [_jsx(FormGroup, { children: _jsx(Grid, { container: true, spacing: 0, children: _jsx(DnDList, { items: items, labelField: labelField, itemRenderer: (item, index, nodeRef, actionNodeRef) => (_jsxs(Grid, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ...nodeRef, children: [_jsx(IconButton, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ...actionNodeRef, children: _jsx(DragIndicatorIcon, {}) }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { name: "item", value: item.id, checked: selectedIds.includes(item.id), onChange: (e) => {
34
34
  const checked = e.target.checked;
35
35
  const newIds = [
@@ -67,7 +67,7 @@ export function ButtonPopupCheckbox(props) {
67
67
  // App
68
68
  const app = useRequiredAppContext();
69
69
  // Destruct
70
- const { addSplitter, ids = [], inputName, label, labelEnd, labelFormatter = (data) => {
70
+ const { addSplitter, value = [], inputName, label, labelEnd, labelFormatter = (data) => {
71
71
  if (labelField in data) {
72
72
  return data[labelField];
73
73
  }
@@ -93,8 +93,8 @@ export function ButtonPopupCheckbox(props) {
93
93
  }, []);
94
94
  React.useEffect(() => {
95
95
  // Set selected ids
96
- setSelectedIds(ids);
97
- }, [ids]);
96
+ setSelectedIds(value);
97
+ }, [value]);
98
98
  // Selected ids
99
99
  const tempSelectedIds = React.useRef();
100
100
  // Click handler
@@ -109,7 +109,7 @@ export function ButtonPopupCheckbox(props) {
109
109
  setSelectedIds(ids);
110
110
  onValueChange?.(ids);
111
111
  },
112
- inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, ids: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
112
+ inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, value: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
113
113
  tempSelectedIds.current = ids;
114
114
  } })),
115
115
  fullScreen: app.smDown
@@ -119,6 +119,8 @@ export function ButtonPopupCheckbox(props) {
119
119
  const item = items.find((item) => item.id === id);
120
120
  if (item == null)
121
121
  return null;
122
- return _jsx(Chip, { size: "small", label: labelFormatter(item) }, id);
122
+ return (_jsx(Chip, { sx: {
123
+ pointerEvents: "none"
124
+ }, size: "small", label: labelFormatter(item) }, id));
123
125
  }), labelEnd && _jsx(Typography, { variant: "caption", children: labelEnd })] })] }));
124
126
  }
@@ -0,0 +1,72 @@
1
+ import { ButtonProps } from "@mui/material/Button";
2
+ import { DataTypes, IdType } from "@etsoo/shared";
3
+ type DnDItemType = {
4
+ id: IdType;
5
+ };
6
+ export type ButtonPopupRadioProps<D extends DnDItemType> = Omit<ButtonProps, "chidren" | "onClick" | "value"> & {
7
+ /**
8
+ * Add items splitter
9
+ */
10
+ addSplitter?: RegExp;
11
+ /**
12
+ * Input field name
13
+ */
14
+ inputName?: string;
15
+ /**
16
+ * Label
17
+ */
18
+ label?: string;
19
+ /**
20
+ * Label in the end
21
+ */
22
+ labelEnd?: string;
23
+ /**
24
+ * Label field in items
25
+ */
26
+ labelField: DataTypes.Keys<D>;
27
+ /**
28
+ * Label formatter
29
+ * @param item Item to be formatted
30
+ */
31
+ labelFormatter?: (item: D) => string;
32
+ /**
33
+ * Labels
34
+ */
35
+ labels?: {
36
+ dragIndicator?: string;
37
+ add?: string;
38
+ more?: string;
39
+ };
40
+ /**
41
+ * Load data
42
+ */
43
+ loadData: () => Promise<D[]>;
44
+ /**
45
+ * On add handler
46
+ * @param ids Ids
47
+ */
48
+ onAdd?: (ids: string[]) => Promise<false | D[]>;
49
+ /**
50
+ * On value change handler
51
+ * @param id Id
52
+ */
53
+ onValueChange?: (id: D["id"]) => void;
54
+ /**
55
+ * Popup title
56
+ */
57
+ popupTitle?: string;
58
+ /**
59
+ * Popup message
60
+ */
61
+ popupMessage?: string;
62
+ /**
63
+ * The field is required or not
64
+ */
65
+ required?: boolean;
66
+ /**
67
+ * Value
68
+ */
69
+ value?: D["id"];
70
+ };
71
+ export declare function ButtonPopupRadio<D extends DnDItemType>(props: ButtonPopupRadioProps<D>): import("react/jsx-runtime").JSX.Element;
72
+ export {};
@@ -0,0 +1,121 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Button from "@mui/material/Button";
3
+ import Chip from "@mui/material/Chip";
4
+ import React from "react";
5
+ import { NumberUtils } from "@etsoo/shared";
6
+ import Grid from "@mui/material/Grid";
7
+ import Typography from "@mui/material/Typography";
8
+ import AddIcon from "@mui/icons-material/Add";
9
+ import FormControlLabel from "@mui/material/FormControlLabel";
10
+ import Radio from "@mui/material/Radio";
11
+ import TextField from "@mui/material/TextField";
12
+ import FormLabel from "@mui/material/FormLabel";
13
+ import RadioGroup from "@mui/material/RadioGroup";
14
+ import { HBox, VBox } from "./FlexBox";
15
+ import { useRequiredAppContext } from "./app/ReactApp";
16
+ function ButtonPopupList(props) {
17
+ // Destruct
18
+ const { addSplitter = /\s*[,;]\s*/, value, items, labelFormatter, labels, onAdd, onValueChange } = props;
19
+ // Ref
20
+ const inputRef = React.useRef(null);
21
+ // State
22
+ const [currentValue, setCurrentValue] = React.useState();
23
+ React.useEffect(() => {
24
+ setCurrentValue(value);
25
+ }, [value]);
26
+ return (_jsxs(VBox, { gap: 2, children: [_jsx(RadioGroup, { value: currentValue ?? "", name: "radio-buttons-group", onChange: (e, v) => {
27
+ const checked = e.target.checked;
28
+ const value = checked
29
+ ? typeof items[0].id === "number"
30
+ ? NumberUtils.parse(v)
31
+ : v
32
+ : undefined;
33
+ setCurrentValue(value);
34
+ onValueChange(value);
35
+ }, children: _jsx(Grid, { container: true, spacing: 0, children: items.map((item) => (_jsx(Grid, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, children: _jsx(FormControlLabel, { control: _jsx(Radio, { value: item.id }), label: `${labelFormatter(item)}` }) }, item.id))) }) }), onAdd && (_jsxs(HBox, { gap: 1, children: [_jsx(TextField, { variant: "outlined", label: labels?.more, fullWidth: true, inputRef: inputRef }), _jsx(Button, { sx: { width: "120px" }, variant: "contained", startIcon: _jsx(AddIcon, {}), size: "small", onClick: async () => {
36
+ if (inputRef.current == null)
37
+ return;
38
+ const input = inputRef.current.value.trim();
39
+ if (input === "") {
40
+ inputRef.current.focus();
41
+ return;
42
+ }
43
+ const inputIds = input
44
+ .split(addSplitter)
45
+ .filter((id) => !items.some((item) => item.id == id));
46
+ if (inputIds.length === 0) {
47
+ inputRef.current.focus();
48
+ return;
49
+ }
50
+ const result = await onAdd(inputIds);
51
+ if (result === false) {
52
+ inputRef.current.focus();
53
+ return;
54
+ }
55
+ inputRef.current.value = "";
56
+ inputRef.current.focus();
57
+ }, children: labels?.add })] }))] }));
58
+ }
59
+ export function ButtonPopupRadio(props) {
60
+ // App
61
+ const app = useRequiredAppContext();
62
+ // Destruct
63
+ const { addSplitter, inputName, label, labelEnd, labelFormatter = (data) => {
64
+ if (labelField in data) {
65
+ return data[labelField];
66
+ }
67
+ return data.id.toString();
68
+ }, labelField, labels = {}, loadData, onAdd, onValueChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start", minHeight: "56px" }, value, variant = "outlined", ...rest } = props;
69
+ // Default labels
70
+ if (!labels.add)
71
+ labels.add = app.get("add");
72
+ if (!labels.dragIndicator)
73
+ labels.dragIndicator = app.get("dragIndicator");
74
+ if (!labels.more)
75
+ labels.more = app.get("more");
76
+ // State
77
+ const [items, setItems] = React.useState([]);
78
+ const [currentValue, setCurrentValue] = React.useState();
79
+ const item = currentValue
80
+ ? items.find((item) => item.id === currentValue)
81
+ : undefined;
82
+ React.useEffect(() => {
83
+ // Load data
84
+ loadData().then((data) => {
85
+ if (data != null) {
86
+ setItems(data);
87
+ }
88
+ });
89
+ }, []);
90
+ React.useEffect(() => {
91
+ setCurrentValue(value);
92
+ }, [value]);
93
+ // Selected id
94
+ const tempSelectedId = React.useRef();
95
+ // Click handler
96
+ const clickHandler = () => {
97
+ app.showInputDialog({
98
+ title: popupTitle,
99
+ message: popupMessage,
100
+ callback: (form) => {
101
+ if (form == null || tempSelectedId.current == null)
102
+ return;
103
+ const id = tempSelectedId.current;
104
+ setCurrentValue(id);
105
+ onValueChange?.(id);
106
+ },
107
+ inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, value: currentValue, items: items, labelFormatter: labelFormatter, labels: labels, onAdd: onAdd, onValueChange: (id) => {
108
+ tempSelectedId.current = id;
109
+ } })),
110
+ fullScreen: app.smDown
111
+ });
112
+ };
113
+ return (_jsxs(React.Fragment, { children: [_jsx("input", { type: "text", style: { position: "absolute", opacity: 0, width: 0 }, name: inputName, required: required, defaultValue: currentValue }), _jsxs(Button, { variant: variant, sx: sx, onClick: () => clickHandler(), ...rest, disabled: !items || items.length === 0, children: [label && (_jsx(FormLabel, { required: required, sx: { fontSize: (theme) => theme.typography.body2.fontSize }, children: label })), item ? (_jsx(Chip, { sx: {
114
+ height: "auto",
115
+ pointerEvents: "none",
116
+ "& .MuiChip-label": {
117
+ display: "block",
118
+ whiteSpace: "normal"
119
+ }
120
+ }, size: "small", label: labelFormatter(item) })) : undefined, labelEnd && _jsx(Typography, { variant: "caption", children: labelEnd })] })] }));
121
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.5.53",
3
+ "version": "1.5.54",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -22,7 +22,7 @@ type DnDItemType = {
22
22
 
23
23
  export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<
24
24
  ButtonProps,
25
- "chidren" | "onClick"
25
+ "chidren" | "onClick" | "value"
26
26
  > & {
27
27
  /**
28
28
  * Add items splitter
@@ -67,7 +67,7 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<
67
67
  /**
68
68
  * Load data
69
69
  */
70
- loadData: (ids?: D["id"][]) => Promise<D[]>;
70
+ loadData: () => Promise<D[]>;
71
71
 
72
72
  /**
73
73
  * On add handler
@@ -97,14 +97,14 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<
97
97
  required?: boolean;
98
98
 
99
99
  /**
100
- * Ids
100
+ * Value
101
101
  */
102
- ids?: D["id"][];
102
+ value?: D["id"][];
103
103
  };
104
104
 
105
105
  type ButtonPopupListProps<D extends DnDItemType> = Pick<
106
106
  ButtonPopupCheckboxProps<D>,
107
- "addSplitter" | "labelField" | "labels" | "onAdd" | "ids"
107
+ "addSplitter" | "labelField" | "labels" | "onAdd" | "value"
108
108
  > &
109
109
  Required<Pick<ButtonPopupCheckboxProps<D>, "labelFormatter">> & {
110
110
  /**
@@ -125,7 +125,7 @@ function ButtonPopupList<D extends DnDItemType>(
125
125
  // Destruct
126
126
  const {
127
127
  addSplitter = /\s*[,;]\s*/,
128
- ids = [],
128
+ value = [],
129
129
  items,
130
130
  labelField,
131
131
  labelFormatter,
@@ -145,11 +145,11 @@ function ButtonPopupList<D extends DnDItemType>(
145
145
 
146
146
  React.useEffect(() => {
147
147
  // Sort items by ids for first load
148
- items.sortByProperty("id", ids);
148
+ items.sortByProperty("id", value);
149
149
 
150
150
  // Set selected ids
151
- setSelectedIds([...ids]);
152
- }, [ids]);
151
+ setSelectedIds([...value]);
152
+ }, [value]);
153
153
 
154
154
  return (
155
155
  <VBox gap={2}>
@@ -262,7 +262,7 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
262
262
  // Destruct
263
263
  const {
264
264
  addSplitter,
265
- ids = [],
265
+ value = [],
266
266
  inputName,
267
267
  label,
268
268
  labelEnd,
@@ -306,8 +306,8 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
306
306
 
307
307
  React.useEffect(() => {
308
308
  // Set selected ids
309
- setSelectedIds(ids);
310
- }, [ids]);
309
+ setSelectedIds(value);
310
+ }, [value]);
311
311
 
312
312
  // Selected ids
313
313
  const tempSelectedIds = React.useRef<D["id"][]>();
@@ -326,7 +326,7 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
326
326
  inputs: (
327
327
  <ButtonPopupList
328
328
  addSplitter={addSplitter}
329
- ids={selectedIds}
329
+ value={selectedIds}
330
330
  items={items}
331
331
  labelFormatter={labelFormatter}
332
332
  labelField={labelField}
@@ -369,7 +369,16 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
369
369
  const item = items.find((item) => item.id === id);
370
370
  if (item == null) return null;
371
371
 
372
- return <Chip key={id} size="small" label={labelFormatter(item)} />;
372
+ return (
373
+ <Chip
374
+ key={id}
375
+ sx={{
376
+ pointerEvents: "none"
377
+ }}
378
+ size="small"
379
+ label={labelFormatter(item)}
380
+ />
381
+ );
373
382
  })}
374
383
  {labelEnd && <Typography variant="caption">{labelEnd}</Typography>}
375
384
  </Button>
@@ -0,0 +1,358 @@
1
+ import Button, { ButtonProps } from "@mui/material/Button";
2
+ import Chip from "@mui/material/Chip";
3
+ import React from "react";
4
+ import { DataTypes, IdType, NumberUtils } from "@etsoo/shared";
5
+ import Grid from "@mui/material/Grid";
6
+ import Typography from "@mui/material/Typography";
7
+ import AddIcon from "@mui/icons-material/Add";
8
+ import FormControlLabel from "@mui/material/FormControlLabel";
9
+ import Radio from "@mui/material/Radio";
10
+ import TextField from "@mui/material/TextField";
11
+ import FormLabel from "@mui/material/FormLabel";
12
+ import RadioGroup from "@mui/material/RadioGroup";
13
+ import { HBox, VBox } from "./FlexBox";
14
+ import { useRequiredAppContext } from "./app/ReactApp";
15
+
16
+ type DnDItemType = {
17
+ id: IdType;
18
+ };
19
+
20
+ export type ButtonPopupRadioProps<D extends DnDItemType> = Omit<
21
+ ButtonProps,
22
+ "chidren" | "onClick" | "value"
23
+ > & {
24
+ /**
25
+ * Add items splitter
26
+ */
27
+ addSplitter?: RegExp;
28
+
29
+ /**
30
+ * Input field name
31
+ */
32
+ inputName?: string;
33
+
34
+ /**
35
+ * Label
36
+ */
37
+ label?: string;
38
+
39
+ /**
40
+ * Label in the end
41
+ */
42
+ labelEnd?: string;
43
+
44
+ /**
45
+ * Label field in items
46
+ */
47
+ labelField: DataTypes.Keys<D>;
48
+
49
+ /**
50
+ * Label formatter
51
+ * @param item Item to be formatted
52
+ */
53
+ labelFormatter?: (item: D) => string;
54
+
55
+ /**
56
+ * Labels
57
+ */
58
+ labels?: {
59
+ dragIndicator?: string;
60
+ add?: string;
61
+ more?: string;
62
+ };
63
+
64
+ /**
65
+ * Load data
66
+ */
67
+ loadData: () => Promise<D[]>;
68
+
69
+ /**
70
+ * On add handler
71
+ * @param ids Ids
72
+ */
73
+ onAdd?: (ids: string[]) => Promise<false | D[]>;
74
+
75
+ /**
76
+ * On value change handler
77
+ * @param id Id
78
+ */
79
+ onValueChange?: (id: D["id"]) => void;
80
+
81
+ /**
82
+ * Popup title
83
+ */
84
+ popupTitle?: string;
85
+
86
+ /**
87
+ * Popup message
88
+ */
89
+ popupMessage?: string;
90
+
91
+ /**
92
+ * The field is required or not
93
+ */
94
+ required?: boolean;
95
+
96
+ /**
97
+ * Value
98
+ */
99
+ value?: D["id"];
100
+ };
101
+
102
+ type ButtonPopupListProps<D extends DnDItemType> = Pick<
103
+ ButtonPopupRadioProps<D>,
104
+ "addSplitter" | "labels" | "onAdd" | "value"
105
+ > &
106
+ Required<Pick<ButtonPopupRadioProps<D>, "labelFormatter">> & {
107
+ /**
108
+ * Items to be displayed
109
+ */
110
+ items: D[];
111
+
112
+ /**
113
+ * On value change handler
114
+ * @param id Id
115
+ */
116
+ onValueChange: (id: D["id"]) => void;
117
+ };
118
+
119
+ function ButtonPopupList<D extends DnDItemType>(
120
+ props: ButtonPopupListProps<D>
121
+ ) {
122
+ // Destruct
123
+ const {
124
+ addSplitter = /\s*[,;]\s*/,
125
+ value,
126
+ items,
127
+ labelFormatter,
128
+ labels,
129
+ onAdd,
130
+ onValueChange
131
+ } = props;
132
+
133
+ // Ref
134
+ const inputRef = React.useRef<HTMLInputElement>(null);
135
+
136
+ // State
137
+ const [currentValue, setCurrentValue] = React.useState<D["id"]>();
138
+
139
+ React.useEffect(() => {
140
+ setCurrentValue(value);
141
+ }, [value]);
142
+
143
+ return (
144
+ <VBox gap={2}>
145
+ <RadioGroup
146
+ value={currentValue ?? ""}
147
+ name="radio-buttons-group"
148
+ onChange={(e, v) => {
149
+ const checked = e.target.checked;
150
+ const value = checked
151
+ ? typeof items[0].id === "number"
152
+ ? NumberUtils.parse(v)
153
+ : v
154
+ : undefined;
155
+ setCurrentValue(value);
156
+ onValueChange(value as D["id"]);
157
+ }}
158
+ >
159
+ <Grid container spacing={0}>
160
+ {items.map((item) => (
161
+ <Grid
162
+ size={{ xs: 12, md: 6, lg: 4 }}
163
+ display="flex"
164
+ justifyContent="flex-start"
165
+ alignItems="center"
166
+ gap={1}
167
+ key={item.id}
168
+ >
169
+ <FormControlLabel
170
+ control={<Radio value={item.id} />}
171
+ label={`${labelFormatter(item)}`}
172
+ />
173
+ </Grid>
174
+ ))}
175
+ </Grid>
176
+ </RadioGroup>
177
+ {onAdd && (
178
+ <HBox gap={1}>
179
+ <TextField
180
+ variant="outlined"
181
+ label={labels?.more}
182
+ fullWidth
183
+ inputRef={inputRef}
184
+ />
185
+ <Button
186
+ sx={{ width: "120px" }}
187
+ variant="contained"
188
+ startIcon={<AddIcon />}
189
+ size="small"
190
+ onClick={async () => {
191
+ if (inputRef.current == null) return;
192
+
193
+ const input = inputRef.current.value.trim();
194
+ if (input === "") {
195
+ inputRef.current.focus();
196
+ return;
197
+ }
198
+
199
+ const inputIds = input
200
+ .split(addSplitter)
201
+ .filter((id) => !items.some((item) => item.id == id));
202
+
203
+ if (inputIds.length === 0) {
204
+ inputRef.current.focus();
205
+ return;
206
+ }
207
+
208
+ const result = await onAdd(inputIds);
209
+ if (result === false) {
210
+ inputRef.current.focus();
211
+ return;
212
+ }
213
+
214
+ inputRef.current.value = "";
215
+ inputRef.current.focus();
216
+ }}
217
+ >
218
+ {labels?.add}
219
+ </Button>
220
+ </HBox>
221
+ )}
222
+ </VBox>
223
+ );
224
+ }
225
+
226
+ export function ButtonPopupRadio<D extends DnDItemType>(
227
+ props: ButtonPopupRadioProps<D>
228
+ ) {
229
+ // App
230
+ const app = useRequiredAppContext();
231
+
232
+ // Destruct
233
+ const {
234
+ addSplitter,
235
+ inputName,
236
+ label,
237
+ labelEnd,
238
+ labelFormatter = (data) => {
239
+ if (labelField in data) {
240
+ return data[labelField] as string;
241
+ }
242
+
243
+ return data.id.toString();
244
+ },
245
+ labelField,
246
+ labels = {},
247
+ loadData,
248
+ onAdd,
249
+ onValueChange,
250
+ popupTitle = label,
251
+ popupMessage,
252
+ required = false,
253
+ sx = { gap: 1, justifyContent: "flex-start", minHeight: "56px" },
254
+ value,
255
+ variant = "outlined",
256
+ ...rest
257
+ } = props;
258
+
259
+ // Default labels
260
+ if (!labels.add) labels.add = app.get("add");
261
+ if (!labels.dragIndicator) labels.dragIndicator = app.get("dragIndicator");
262
+ if (!labels.more) labels.more = app.get("more");
263
+
264
+ // State
265
+ const [items, setItems] = React.useState<D[]>([]);
266
+ const [currentValue, setCurrentValue] = React.useState<D["id"]>();
267
+
268
+ const item = currentValue
269
+ ? items.find((item) => item.id === currentValue)
270
+ : undefined;
271
+
272
+ React.useEffect(() => {
273
+ // Load data
274
+ loadData().then((data) => {
275
+ if (data != null) {
276
+ setItems(data);
277
+ }
278
+ });
279
+ }, []);
280
+
281
+ React.useEffect(() => {
282
+ setCurrentValue(value);
283
+ }, [value]);
284
+
285
+ // Selected id
286
+ const tempSelectedId = React.useRef<D["id"]>();
287
+
288
+ // Click handler
289
+ const clickHandler = () => {
290
+ app.showInputDialog({
291
+ title: popupTitle,
292
+ message: popupMessage,
293
+ callback: (form) => {
294
+ if (form == null || tempSelectedId.current == null) return;
295
+ const id = tempSelectedId.current;
296
+ setCurrentValue(id);
297
+ onValueChange?.(id);
298
+ },
299
+ inputs: (
300
+ <ButtonPopupList
301
+ addSplitter={addSplitter}
302
+ value={currentValue}
303
+ items={items}
304
+ labelFormatter={labelFormatter}
305
+ labels={labels}
306
+ onAdd={onAdd}
307
+ onValueChange={(id) => {
308
+ tempSelectedId.current = id;
309
+ }}
310
+ />
311
+ ),
312
+ fullScreen: app.smDown
313
+ });
314
+ };
315
+
316
+ return (
317
+ <React.Fragment>
318
+ <input
319
+ type="text"
320
+ style={{ position: "absolute", opacity: 0, width: 0 }}
321
+ name={inputName}
322
+ required={required}
323
+ defaultValue={currentValue}
324
+ />
325
+ <Button
326
+ variant={variant}
327
+ sx={sx}
328
+ onClick={() => clickHandler()}
329
+ {...rest}
330
+ disabled={!items || items.length === 0}
331
+ >
332
+ {label && (
333
+ <FormLabel
334
+ required={required}
335
+ sx={{ fontSize: (theme) => theme.typography.body2.fontSize }}
336
+ >
337
+ {label}
338
+ </FormLabel>
339
+ )}
340
+ {item ? (
341
+ <Chip
342
+ sx={{
343
+ height: "auto",
344
+ pointerEvents: "none",
345
+ "& .MuiChip-label": {
346
+ display: "block",
347
+ whiteSpace: "normal"
348
+ }
349
+ }}
350
+ size="small"
351
+ label={labelFormatter(item)}
352
+ />
353
+ ) : undefined}
354
+ {labelEnd && <Typography variant="caption">{labelEnd}</Typography>}
355
+ </Button>
356
+ </React.Fragment>
357
+ );
358
+ }