@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.
@@ -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
+ }
@@ -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
+ }
@@ -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.35",
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.69",
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";