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