@rulab/adminjs-components 0.0.1
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/index.cjs +333 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +314 -0
- package/package.json +37 -0
- package/src/components/CustomSlug/CustomSlug.tsx +58 -0
- package/src/components/CustomSlug/index.ts +1 -0
- package/src/components/CustomSlug/styles.ts +18 -0
- package/src/components/StringList/SortableList/SortableList.tsx +98 -0
- package/src/components/StringList/SortableList/components/SortableItem/DragHandle.tsx +20 -0
- package/src/components/StringList/SortableList/components/SortableItem/SortableItem.tsx +59 -0
- package/src/components/StringList/SortableList/components/SortableItem/styles.ts +22 -0
- package/src/components/StringList/SortableList/components/SortableItem/types.ts +7 -0
- package/src/components/StringList/SortableList/components/index.ts +2 -0
- package/src/components/StringList/SortableList/index.ts +1 -0
- package/src/components/StringList/SortableList/styles.ts +9 -0
- package/src/components/StringList/StringList.tsx +125 -0
- package/src/components/StringList/index.ts +1 -0
- package/src/components/StringList/styles.ts +20 -0
- package/src/components/index.ts +2 -0
- package/src/index.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/slugifyImport.ts +4 -0
- package/src/utils/slugifyTitle.ts +11 -0
- package/tsconfig.json +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Roman Daud
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# adminjs-components
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
CustomSlug: () => CustomSlug_default,
|
|
34
|
+
StringList: () => StringList_default
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(src_exports);
|
|
37
|
+
|
|
38
|
+
// src/components/CustomSlug/CustomSlug.tsx
|
|
39
|
+
var import_react = __toESM(require("react"), 1);
|
|
40
|
+
var import_styled_components2 = require("styled-components");
|
|
41
|
+
var import_design_system2 = require("@adminjs/design-system");
|
|
42
|
+
|
|
43
|
+
// src/utils/slugifyImport.ts
|
|
44
|
+
var import_slugify = __toESM(require("slugify"), 1);
|
|
45
|
+
var slugifyImport_default = import_slugify.default;
|
|
46
|
+
|
|
47
|
+
// src/utils/slugifyTitle.ts
|
|
48
|
+
var slugifyTitle = (title) => {
|
|
49
|
+
return slugifyImport_default(title, {
|
|
50
|
+
replacement: "-",
|
|
51
|
+
remove: void 0,
|
|
52
|
+
lower: true,
|
|
53
|
+
locale: "vi",
|
|
54
|
+
trim: true
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/components/CustomSlug/styles.ts
|
|
59
|
+
var import_styled_components = require("@adminjs/design-system/styled-components");
|
|
60
|
+
var import_design_system = require("@adminjs/design-system");
|
|
61
|
+
var StyledInputWrapper = (0, import_styled_components.styled)(import_design_system.Box)`
|
|
62
|
+
display: flex;
|
|
63
|
+
margin-bottom: 40px;
|
|
64
|
+
`;
|
|
65
|
+
var StyledCustomInput = (0, import_styled_components.styled)(import_design_system.Input)`
|
|
66
|
+
width: 100%;
|
|
67
|
+
margin-right: 10px;
|
|
68
|
+
`;
|
|
69
|
+
var StyledGenerateButton = (0, import_styled_components.styled)(import_design_system.Button)`
|
|
70
|
+
white-space: nowrap;
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
// src/components/CustomSlug/CustomSlug.tsx
|
|
74
|
+
var CustomSlug = ({ property, record, onChange }) => {
|
|
75
|
+
const [inputValue, setInputValue] = (0, import_react.useState)(record.params.slug);
|
|
76
|
+
(0, import_react.useEffect)(() => {
|
|
77
|
+
onChange(property.path, inputValue);
|
|
78
|
+
}, [inputValue]);
|
|
79
|
+
return /* @__PURE__ */ import_react.default.createElement(import_styled_components2.ThemeProvider, { theme: import_design_system2.theme }, /* @__PURE__ */ import_react.default.createElement(import_design_system2.Label, { htmlFor: "customSlug" }, "Slug"), /* @__PURE__ */ import_react.default.createElement(StyledInputWrapper, null, /* @__PURE__ */ import_react.default.createElement(
|
|
80
|
+
StyledCustomInput,
|
|
81
|
+
{
|
|
82
|
+
id: property.path,
|
|
83
|
+
name: property.path,
|
|
84
|
+
value: inputValue,
|
|
85
|
+
onChange: handleInput
|
|
86
|
+
}
|
|
87
|
+
), /* @__PURE__ */ import_react.default.createElement(StyledGenerateButton, { variant: "outlined", onClick: generateSlug }, "Generate Slug")));
|
|
88
|
+
function handleInput(e) {
|
|
89
|
+
setInputValue(e.target.value);
|
|
90
|
+
}
|
|
91
|
+
function generateSlug(e) {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
const title = record.title;
|
|
94
|
+
setInputValue(slugifyTitle(title));
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var CustomSlug_default = CustomSlug;
|
|
98
|
+
|
|
99
|
+
// src/components/StringList/StringList.tsx
|
|
100
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
101
|
+
var import_styled_components6 = require("styled-components");
|
|
102
|
+
var import_design_system5 = require("@adminjs/design-system");
|
|
103
|
+
|
|
104
|
+
// src/components/StringList/styles.ts
|
|
105
|
+
var import_styled_components3 = require("@adminjs/design-system/styled-components");
|
|
106
|
+
var import_design_system3 = require("@adminjs/design-system");
|
|
107
|
+
var StyledWrapper = (0, import_styled_components3.styled)(import_design_system3.Box)`
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
`;
|
|
111
|
+
var StyledCustomInput2 = (0, import_styled_components3.styled)(import_design_system3.Input)`
|
|
112
|
+
width: 100%;
|
|
113
|
+
margin-right: 10px;
|
|
114
|
+
`;
|
|
115
|
+
var StyledInputWrapper2 = (0, import_styled_components3.styled)(import_design_system3.Box)`
|
|
116
|
+
display: flex;
|
|
117
|
+
`;
|
|
118
|
+
var StyledListWrapper = (0, import_styled_components3.styled)(import_design_system3.Box)`
|
|
119
|
+
margin-bottom: 15px;
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
// src/components/StringList/SortableList/SortableList.tsx
|
|
123
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
124
|
+
var import_core = require("@dnd-kit/core");
|
|
125
|
+
var import_sortable2 = require("@dnd-kit/sortable");
|
|
126
|
+
|
|
127
|
+
// src/components/StringList/SortableList/components/SortableItem/SortableItem.tsx
|
|
128
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
129
|
+
var import_sortable = require("@dnd-kit/sortable");
|
|
130
|
+
var import_design_system4 = require("@adminjs/design-system");
|
|
131
|
+
|
|
132
|
+
// src/components/StringList/SortableList/components/SortableItem/DragHandle.tsx
|
|
133
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
134
|
+
|
|
135
|
+
// src/components/StringList/SortableList/components/SortableItem/styles.ts
|
|
136
|
+
var import_styled_components4 = require("@adminjs/design-system/styled-components");
|
|
137
|
+
var StyledListItem = import_styled_components4.styled.li`
|
|
138
|
+
display: flex;
|
|
139
|
+
justify-content: space-between;
|
|
140
|
+
align-items: center;
|
|
141
|
+
background-color: #fff;
|
|
142
|
+
padding: 10px 20px 10px 15px;
|
|
143
|
+
box-shadow:
|
|
144
|
+
0 0 0 calc(1px / var(--scale-x, 1)) rgba(63, 63, 68, 0.05),
|
|
145
|
+
0 1px calc(3px / var(--scale-x, 1)) 0 rgba(34, 33, 81, 0.15);
|
|
146
|
+
border-radius: 5px;
|
|
147
|
+
list-style: none;
|
|
148
|
+
`;
|
|
149
|
+
var DragButton = import_styled_components4.styled.button`
|
|
150
|
+
padding: 3px;
|
|
151
|
+
margin-right: 15px;
|
|
152
|
+
cursor: move;
|
|
153
|
+
background: none;
|
|
154
|
+
border: none;
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
// src/components/StringList/SortableList/components/SortableItem/DragHandle.tsx
|
|
158
|
+
var DragHandle = ({ context }) => {
|
|
159
|
+
const { attributes, listeners, ref } = (0, import_react2.useContext)(context);
|
|
160
|
+
return /* @__PURE__ */ import_react2.default.createElement(DragButton, { ...attributes, ...listeners, ref }, /* @__PURE__ */ import_react2.default.createElement("svg", { viewBox: "0 0 20 20", width: "13" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" })));
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/components/StringList/SortableList/components/SortableItem/SortableItem.tsx
|
|
164
|
+
var SortableItemContext = (0, import_react3.createContext)({
|
|
165
|
+
attributes: {},
|
|
166
|
+
listeners: void 0,
|
|
167
|
+
ref() {
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
function SortableItem({
|
|
171
|
+
children,
|
|
172
|
+
id,
|
|
173
|
+
onDelete
|
|
174
|
+
}) {
|
|
175
|
+
const { attributes, isDragging, listeners, setNodeRef, setActivatorNodeRef } = (0, import_sortable.useSortable)({ id });
|
|
176
|
+
const context = (0, import_react3.useMemo)(
|
|
177
|
+
() => ({
|
|
178
|
+
attributes,
|
|
179
|
+
listeners,
|
|
180
|
+
ref: setActivatorNodeRef
|
|
181
|
+
}),
|
|
182
|
+
[attributes, listeners, setActivatorNodeRef]
|
|
183
|
+
);
|
|
184
|
+
const style = {
|
|
185
|
+
opacity: isDragging ? 0.4 : void 0
|
|
186
|
+
};
|
|
187
|
+
return /* @__PURE__ */ import_react3.default.createElement(SortableItemContext.Provider, { value: context }, /* @__PURE__ */ import_react3.default.createElement(StyledListItem, { ref: setNodeRef, style }, /* @__PURE__ */ import_react3.default.createElement("div", null, /* @__PURE__ */ import_react3.default.createElement(DragHandle, { context: SortableItemContext }), children), /* @__PURE__ */ import_react3.default.createElement(
|
|
188
|
+
import_design_system4.Button,
|
|
189
|
+
{
|
|
190
|
+
variant: "outlined",
|
|
191
|
+
color: "danger",
|
|
192
|
+
size: "icon",
|
|
193
|
+
onClick: (e) => onDelete(e, id)
|
|
194
|
+
},
|
|
195
|
+
/* @__PURE__ */ import_react3.default.createElement(import_design_system4.Icon, { icon: "X", color: "red" })
|
|
196
|
+
)));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/components/StringList/SortableList/styles.ts
|
|
200
|
+
var import_styled_components5 = require("@adminjs/design-system/styled-components");
|
|
201
|
+
var StyledListWrapper2 = import_styled_components5.styled.ul`
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: column;
|
|
204
|
+
gap: 12px;
|
|
205
|
+
padding: 0;
|
|
206
|
+
list-style: none;
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
// src/components/StringList/SortableList/SortableList.tsx
|
|
210
|
+
var dropAnimationConfig = {
|
|
211
|
+
sideEffects: (0, import_core.defaultDropAnimationSideEffects)({
|
|
212
|
+
styles: {
|
|
213
|
+
active: {
|
|
214
|
+
opacity: "0.4"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
};
|
|
219
|
+
function SortableList({
|
|
220
|
+
items,
|
|
221
|
+
onChange,
|
|
222
|
+
renderItem
|
|
223
|
+
}) {
|
|
224
|
+
const [active, setActive] = (0, import_react4.useState)(null);
|
|
225
|
+
const activeItem = (0, import_react4.useMemo)(
|
|
226
|
+
() => items.find((item) => item.id === active?.id),
|
|
227
|
+
[active, items]
|
|
228
|
+
);
|
|
229
|
+
const sensors = (0, import_core.useSensors)(
|
|
230
|
+
(0, import_core.useSensor)(import_core.PointerSensor),
|
|
231
|
+
(0, import_core.useSensor)(import_core.KeyboardSensor, {
|
|
232
|
+
coordinateGetter: import_sortable2.sortableKeyboardCoordinates
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
return /* @__PURE__ */ import_react4.default.createElement(
|
|
236
|
+
import_core.DndContext,
|
|
237
|
+
{
|
|
238
|
+
sensors,
|
|
239
|
+
onDragStart: ({ active: active2 }) => {
|
|
240
|
+
setActive(active2);
|
|
241
|
+
},
|
|
242
|
+
onDragEnd: ({ active: active2, over }) => {
|
|
243
|
+
if (over && active2.id !== over?.id) {
|
|
244
|
+
const activeIndex = items.findIndex(({ id }) => id === active2.id);
|
|
245
|
+
const overIndex = items.findIndex(({ id }) => id === over.id);
|
|
246
|
+
onChange((0, import_sortable2.arrayMove)(items, activeIndex, overIndex));
|
|
247
|
+
}
|
|
248
|
+
setActive(null);
|
|
249
|
+
},
|
|
250
|
+
onDragCancel: () => {
|
|
251
|
+
setActive(null);
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
/* @__PURE__ */ import_react4.default.createElement(import_sortable2.SortableContext, { items, strategy: import_sortable2.verticalListSortingStrategy }, /* @__PURE__ */ import_react4.default.createElement(StyledListWrapper2, { role: "application" }, items.map((item, index) => /* @__PURE__ */ import_react4.default.createElement(import_react4.Fragment, { key: index }, renderItem(item))))),
|
|
255
|
+
/* @__PURE__ */ import_react4.default.createElement(import_core.DragOverlay, { dropAnimation: dropAnimationConfig }, activeItem ? renderItem(activeItem) : null)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
SortableList.Item = SortableItem;
|
|
259
|
+
|
|
260
|
+
// src/components/StringList/StringList.tsx
|
|
261
|
+
var StringList = ({ record, onChange, property }) => {
|
|
262
|
+
const stringListValue = record.params?.[property.path] ?? property.props.value ?? "";
|
|
263
|
+
const initialList = stringListValue ? prepareDataForList(stringListValue) : [];
|
|
264
|
+
const [inputValue, setInputValue] = (0, import_react5.useState)("");
|
|
265
|
+
const [list, setList] = (0, import_react5.useState)(initialList);
|
|
266
|
+
const serializedData = prepareDataForDatabase(list);
|
|
267
|
+
(0, import_react5.useEffect)(() => {
|
|
268
|
+
onChange(property.path, serializedData);
|
|
269
|
+
}, [serializedData]);
|
|
270
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_styled_components6.ThemeProvider, { theme: import_design_system5.theme }, /* @__PURE__ */ import_react5.default.createElement(import_design_system5.Label, { htmlFor: "custom" }, "String List"), /* @__PURE__ */ import_react5.default.createElement(StyledWrapper, null, /* @__PURE__ */ import_react5.default.createElement(StyledListWrapper, null, /* @__PURE__ */ import_react5.default.createElement(
|
|
271
|
+
SortableList,
|
|
272
|
+
{
|
|
273
|
+
items: list,
|
|
274
|
+
onChange: setList,
|
|
275
|
+
renderItem: (item) => /* @__PURE__ */ import_react5.default.createElement(SortableList.Item, { id: item.id, onDelete: handleDeleteButton }, item.value)
|
|
276
|
+
}
|
|
277
|
+
)), /* @__PURE__ */ import_react5.default.createElement(StyledInputWrapper2, null, /* @__PURE__ */ import_react5.default.createElement(
|
|
278
|
+
import_design_system5.Input,
|
|
279
|
+
{
|
|
280
|
+
id: "stringList",
|
|
281
|
+
name: property.path,
|
|
282
|
+
value: serializedData,
|
|
283
|
+
hidden: true
|
|
284
|
+
}
|
|
285
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
286
|
+
StyledCustomInput2,
|
|
287
|
+
{
|
|
288
|
+
id: "custom",
|
|
289
|
+
name: "customInput",
|
|
290
|
+
value: inputValue,
|
|
291
|
+
onChange: handleInput,
|
|
292
|
+
onKeyPress: handleEnterPress
|
|
293
|
+
}
|
|
294
|
+
), /* @__PURE__ */ import_react5.default.createElement(import_design_system5.Button, { variant: "outlined", onClick: handleAddButton }, "Add"))));
|
|
295
|
+
function handleInput(e) {
|
|
296
|
+
setInputValue(e.target.value);
|
|
297
|
+
}
|
|
298
|
+
function handleEnterPress(e) {
|
|
299
|
+
if (e.key === "Enter") {
|
|
300
|
+
handleAddButton(e);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function handleAddButton(e) {
|
|
304
|
+
e.preventDefault();
|
|
305
|
+
if (Boolean(inputValue)) {
|
|
306
|
+
setList([...list, createListObject(inputValue)]);
|
|
307
|
+
setInputValue("");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function handleDeleteButton(e, id) {
|
|
311
|
+
e.preventDefault();
|
|
312
|
+
const newData = list.filter((item) => item.id !== id);
|
|
313
|
+
setList(newData);
|
|
314
|
+
}
|
|
315
|
+
function prepareDataForDatabase(list2) {
|
|
316
|
+
return list2.map(({ value }) => value).join("|");
|
|
317
|
+
}
|
|
318
|
+
function prepareDataForList(str) {
|
|
319
|
+
return str.split("|").map((item) => createListObject(item));
|
|
320
|
+
}
|
|
321
|
+
function createListObject(value) {
|
|
322
|
+
return {
|
|
323
|
+
id: `${Date.now()}-${Math.floor(Math.random() * 1e3)}`,
|
|
324
|
+
value
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
var StringList_default = StringList;
|
|
329
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
330
|
+
0 && (module.exports = {
|
|
331
|
+
CustomSlug,
|
|
332
|
+
StringList
|
|
333
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { EditPropertyProps } from 'adminjs';
|
|
3
|
+
|
|
4
|
+
type CustomSlugTypes = Omit<EditPropertyProps, "where" | "resource">;
|
|
5
|
+
declare const CustomSlug: FC<CustomSlugTypes>;
|
|
6
|
+
|
|
7
|
+
type StringListTypes = Omit<EditPropertyProps, "where" | "resource">;
|
|
8
|
+
declare const StringList: FC<StringListTypes>;
|
|
9
|
+
|
|
10
|
+
export { CustomSlug, StringList };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { EditPropertyProps } from 'adminjs';
|
|
3
|
+
|
|
4
|
+
type CustomSlugTypes = Omit<EditPropertyProps, "where" | "resource">;
|
|
5
|
+
declare const CustomSlug: FC<CustomSlugTypes>;
|
|
6
|
+
|
|
7
|
+
type StringListTypes = Omit<EditPropertyProps, "where" | "resource">;
|
|
8
|
+
declare const StringList: FC<StringListTypes>;
|
|
9
|
+
|
|
10
|
+
export { CustomSlug, StringList };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// src/components/CustomSlug/CustomSlug.tsx
|
|
2
|
+
import React, {
|
|
3
|
+
useEffect,
|
|
4
|
+
useState
|
|
5
|
+
} from "react";
|
|
6
|
+
import { ThemeProvider } from "styled-components";
|
|
7
|
+
import { theme, Label } from "@adminjs/design-system";
|
|
8
|
+
|
|
9
|
+
// src/utils/slugifyImport.ts
|
|
10
|
+
import slugify from "slugify";
|
|
11
|
+
var slugifyImport_default = slugify;
|
|
12
|
+
|
|
13
|
+
// src/utils/slugifyTitle.ts
|
|
14
|
+
var slugifyTitle = (title) => {
|
|
15
|
+
return slugifyImport_default(title, {
|
|
16
|
+
replacement: "-",
|
|
17
|
+
remove: void 0,
|
|
18
|
+
lower: true,
|
|
19
|
+
locale: "vi",
|
|
20
|
+
trim: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/components/CustomSlug/styles.ts
|
|
25
|
+
import { styled } from "@adminjs/design-system/styled-components";
|
|
26
|
+
import { Button, Box, Input } from "@adminjs/design-system";
|
|
27
|
+
var StyledInputWrapper = styled(Box)`
|
|
28
|
+
display: flex;
|
|
29
|
+
margin-bottom: 40px;
|
|
30
|
+
`;
|
|
31
|
+
var StyledCustomInput = styled(Input)`
|
|
32
|
+
width: 100%;
|
|
33
|
+
margin-right: 10px;
|
|
34
|
+
`;
|
|
35
|
+
var StyledGenerateButton = styled(Button)`
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
// src/components/CustomSlug/CustomSlug.tsx
|
|
40
|
+
var CustomSlug = ({ property, record, onChange }) => {
|
|
41
|
+
const [inputValue, setInputValue] = useState(record.params.slug);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
onChange(property.path, inputValue);
|
|
44
|
+
}, [inputValue]);
|
|
45
|
+
return /* @__PURE__ */ React.createElement(ThemeProvider, { theme }, /* @__PURE__ */ React.createElement(Label, { htmlFor: "customSlug" }, "Slug"), /* @__PURE__ */ React.createElement(StyledInputWrapper, null, /* @__PURE__ */ React.createElement(
|
|
46
|
+
StyledCustomInput,
|
|
47
|
+
{
|
|
48
|
+
id: property.path,
|
|
49
|
+
name: property.path,
|
|
50
|
+
value: inputValue,
|
|
51
|
+
onChange: handleInput
|
|
52
|
+
}
|
|
53
|
+
), /* @__PURE__ */ React.createElement(StyledGenerateButton, { variant: "outlined", onClick: generateSlug }, "Generate Slug")));
|
|
54
|
+
function handleInput(e) {
|
|
55
|
+
setInputValue(e.target.value);
|
|
56
|
+
}
|
|
57
|
+
function generateSlug(e) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
const title = record.title;
|
|
60
|
+
setInputValue(slugifyTitle(title));
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var CustomSlug_default = CustomSlug;
|
|
64
|
+
|
|
65
|
+
// src/components/StringList/StringList.tsx
|
|
66
|
+
import React5, {
|
|
67
|
+
useState as useState3,
|
|
68
|
+
useEffect as useEffect2
|
|
69
|
+
} from "react";
|
|
70
|
+
import { ThemeProvider as ThemeProvider2 } from "styled-components";
|
|
71
|
+
import { theme as theme2, Button as Button3, Input as Input3, Label as Label2 } from "@adminjs/design-system";
|
|
72
|
+
|
|
73
|
+
// src/components/StringList/styles.ts
|
|
74
|
+
import { styled as styled2 } from "@adminjs/design-system/styled-components";
|
|
75
|
+
import { Box as Box2, Input as Input2 } from "@adminjs/design-system";
|
|
76
|
+
var StyledWrapper = styled2(Box2)`
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
`;
|
|
80
|
+
var StyledCustomInput2 = styled2(Input2)`
|
|
81
|
+
width: 100%;
|
|
82
|
+
margin-right: 10px;
|
|
83
|
+
`;
|
|
84
|
+
var StyledInputWrapper2 = styled2(Box2)`
|
|
85
|
+
display: flex;
|
|
86
|
+
`;
|
|
87
|
+
var StyledListWrapper = styled2(Box2)`
|
|
88
|
+
margin-bottom: 15px;
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
// src/components/StringList/SortableList/SortableList.tsx
|
|
92
|
+
import React4, { Fragment, useMemo as useMemo2, useState as useState2 } from "react";
|
|
93
|
+
import {
|
|
94
|
+
DndContext,
|
|
95
|
+
KeyboardSensor,
|
|
96
|
+
PointerSensor,
|
|
97
|
+
useSensor,
|
|
98
|
+
useSensors,
|
|
99
|
+
DragOverlay,
|
|
100
|
+
defaultDropAnimationSideEffects
|
|
101
|
+
} from "@dnd-kit/core";
|
|
102
|
+
import {
|
|
103
|
+
SortableContext,
|
|
104
|
+
arrayMove,
|
|
105
|
+
sortableKeyboardCoordinates,
|
|
106
|
+
verticalListSortingStrategy
|
|
107
|
+
} from "@dnd-kit/sortable";
|
|
108
|
+
|
|
109
|
+
// src/components/StringList/SortableList/components/SortableItem/SortableItem.tsx
|
|
110
|
+
import React3, { createContext, useMemo } from "react";
|
|
111
|
+
import { useSortable } from "@dnd-kit/sortable";
|
|
112
|
+
import { Button as Button2, Icon } from "@adminjs/design-system";
|
|
113
|
+
|
|
114
|
+
// src/components/StringList/SortableList/components/SortableItem/DragHandle.tsx
|
|
115
|
+
import React2, { useContext } from "react";
|
|
116
|
+
|
|
117
|
+
// src/components/StringList/SortableList/components/SortableItem/styles.ts
|
|
118
|
+
import { styled as styled3 } from "@adminjs/design-system/styled-components";
|
|
119
|
+
var StyledListItem = styled3.li`
|
|
120
|
+
display: flex;
|
|
121
|
+
justify-content: space-between;
|
|
122
|
+
align-items: center;
|
|
123
|
+
background-color: #fff;
|
|
124
|
+
padding: 10px 20px 10px 15px;
|
|
125
|
+
box-shadow:
|
|
126
|
+
0 0 0 calc(1px / var(--scale-x, 1)) rgba(63, 63, 68, 0.05),
|
|
127
|
+
0 1px calc(3px / var(--scale-x, 1)) 0 rgba(34, 33, 81, 0.15);
|
|
128
|
+
border-radius: 5px;
|
|
129
|
+
list-style: none;
|
|
130
|
+
`;
|
|
131
|
+
var DragButton = styled3.button`
|
|
132
|
+
padding: 3px;
|
|
133
|
+
margin-right: 15px;
|
|
134
|
+
cursor: move;
|
|
135
|
+
background: none;
|
|
136
|
+
border: none;
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
// src/components/StringList/SortableList/components/SortableItem/DragHandle.tsx
|
|
140
|
+
var DragHandle = ({ context }) => {
|
|
141
|
+
const { attributes, listeners, ref } = useContext(context);
|
|
142
|
+
return /* @__PURE__ */ React2.createElement(DragButton, { ...attributes, ...listeners, ref }, /* @__PURE__ */ React2.createElement("svg", { viewBox: "0 0 20 20", width: "13" }, /* @__PURE__ */ React2.createElement("path", { d: "M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" })));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/components/StringList/SortableList/components/SortableItem/SortableItem.tsx
|
|
146
|
+
var SortableItemContext = createContext({
|
|
147
|
+
attributes: {},
|
|
148
|
+
listeners: void 0,
|
|
149
|
+
ref() {
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
function SortableItem({
|
|
153
|
+
children,
|
|
154
|
+
id,
|
|
155
|
+
onDelete
|
|
156
|
+
}) {
|
|
157
|
+
const { attributes, isDragging, listeners, setNodeRef, setActivatorNodeRef } = useSortable({ id });
|
|
158
|
+
const context = useMemo(
|
|
159
|
+
() => ({
|
|
160
|
+
attributes,
|
|
161
|
+
listeners,
|
|
162
|
+
ref: setActivatorNodeRef
|
|
163
|
+
}),
|
|
164
|
+
[attributes, listeners, setActivatorNodeRef]
|
|
165
|
+
);
|
|
166
|
+
const style = {
|
|
167
|
+
opacity: isDragging ? 0.4 : void 0
|
|
168
|
+
};
|
|
169
|
+
return /* @__PURE__ */ React3.createElement(SortableItemContext.Provider, { value: context }, /* @__PURE__ */ React3.createElement(StyledListItem, { ref: setNodeRef, style }, /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement(DragHandle, { context: SortableItemContext }), children), /* @__PURE__ */ React3.createElement(
|
|
170
|
+
Button2,
|
|
171
|
+
{
|
|
172
|
+
variant: "outlined",
|
|
173
|
+
color: "danger",
|
|
174
|
+
size: "icon",
|
|
175
|
+
onClick: (e) => onDelete(e, id)
|
|
176
|
+
},
|
|
177
|
+
/* @__PURE__ */ React3.createElement(Icon, { icon: "X", color: "red" })
|
|
178
|
+
)));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/components/StringList/SortableList/styles.ts
|
|
182
|
+
import { styled as styled4 } from "@adminjs/design-system/styled-components";
|
|
183
|
+
var StyledListWrapper2 = styled4.ul`
|
|
184
|
+
display: flex;
|
|
185
|
+
flex-direction: column;
|
|
186
|
+
gap: 12px;
|
|
187
|
+
padding: 0;
|
|
188
|
+
list-style: none;
|
|
189
|
+
`;
|
|
190
|
+
|
|
191
|
+
// src/components/StringList/SortableList/SortableList.tsx
|
|
192
|
+
var dropAnimationConfig = {
|
|
193
|
+
sideEffects: defaultDropAnimationSideEffects({
|
|
194
|
+
styles: {
|
|
195
|
+
active: {
|
|
196
|
+
opacity: "0.4"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
};
|
|
201
|
+
function SortableList({
|
|
202
|
+
items,
|
|
203
|
+
onChange,
|
|
204
|
+
renderItem
|
|
205
|
+
}) {
|
|
206
|
+
const [active, setActive] = useState2(null);
|
|
207
|
+
const activeItem = useMemo2(
|
|
208
|
+
() => items.find((item) => item.id === active?.id),
|
|
209
|
+
[active, items]
|
|
210
|
+
);
|
|
211
|
+
const sensors = useSensors(
|
|
212
|
+
useSensor(PointerSensor),
|
|
213
|
+
useSensor(KeyboardSensor, {
|
|
214
|
+
coordinateGetter: sortableKeyboardCoordinates
|
|
215
|
+
})
|
|
216
|
+
);
|
|
217
|
+
return /* @__PURE__ */ React4.createElement(
|
|
218
|
+
DndContext,
|
|
219
|
+
{
|
|
220
|
+
sensors,
|
|
221
|
+
onDragStart: ({ active: active2 }) => {
|
|
222
|
+
setActive(active2);
|
|
223
|
+
},
|
|
224
|
+
onDragEnd: ({ active: active2, over }) => {
|
|
225
|
+
if (over && active2.id !== over?.id) {
|
|
226
|
+
const activeIndex = items.findIndex(({ id }) => id === active2.id);
|
|
227
|
+
const overIndex = items.findIndex(({ id }) => id === over.id);
|
|
228
|
+
onChange(arrayMove(items, activeIndex, overIndex));
|
|
229
|
+
}
|
|
230
|
+
setActive(null);
|
|
231
|
+
},
|
|
232
|
+
onDragCancel: () => {
|
|
233
|
+
setActive(null);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
/* @__PURE__ */ React4.createElement(SortableContext, { items, strategy: verticalListSortingStrategy }, /* @__PURE__ */ React4.createElement(StyledListWrapper2, { role: "application" }, items.map((item, index) => /* @__PURE__ */ React4.createElement(Fragment, { key: index }, renderItem(item))))),
|
|
237
|
+
/* @__PURE__ */ React4.createElement(DragOverlay, { dropAnimation: dropAnimationConfig }, activeItem ? renderItem(activeItem) : null)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
SortableList.Item = SortableItem;
|
|
241
|
+
|
|
242
|
+
// src/components/StringList/StringList.tsx
|
|
243
|
+
var StringList = ({ record, onChange, property }) => {
|
|
244
|
+
const stringListValue = record.params?.[property.path] ?? property.props.value ?? "";
|
|
245
|
+
const initialList = stringListValue ? prepareDataForList(stringListValue) : [];
|
|
246
|
+
const [inputValue, setInputValue] = useState3("");
|
|
247
|
+
const [list, setList] = useState3(initialList);
|
|
248
|
+
const serializedData = prepareDataForDatabase(list);
|
|
249
|
+
useEffect2(() => {
|
|
250
|
+
onChange(property.path, serializedData);
|
|
251
|
+
}, [serializedData]);
|
|
252
|
+
return /* @__PURE__ */ React5.createElement(ThemeProvider2, { theme: theme2 }, /* @__PURE__ */ React5.createElement(Label2, { htmlFor: "custom" }, "String List"), /* @__PURE__ */ React5.createElement(StyledWrapper, null, /* @__PURE__ */ React5.createElement(StyledListWrapper, null, /* @__PURE__ */ React5.createElement(
|
|
253
|
+
SortableList,
|
|
254
|
+
{
|
|
255
|
+
items: list,
|
|
256
|
+
onChange: setList,
|
|
257
|
+
renderItem: (item) => /* @__PURE__ */ React5.createElement(SortableList.Item, { id: item.id, onDelete: handleDeleteButton }, item.value)
|
|
258
|
+
}
|
|
259
|
+
)), /* @__PURE__ */ React5.createElement(StyledInputWrapper2, null, /* @__PURE__ */ React5.createElement(
|
|
260
|
+
Input3,
|
|
261
|
+
{
|
|
262
|
+
id: "stringList",
|
|
263
|
+
name: property.path,
|
|
264
|
+
value: serializedData,
|
|
265
|
+
hidden: true
|
|
266
|
+
}
|
|
267
|
+
), /* @__PURE__ */ React5.createElement(
|
|
268
|
+
StyledCustomInput2,
|
|
269
|
+
{
|
|
270
|
+
id: "custom",
|
|
271
|
+
name: "customInput",
|
|
272
|
+
value: inputValue,
|
|
273
|
+
onChange: handleInput,
|
|
274
|
+
onKeyPress: handleEnterPress
|
|
275
|
+
}
|
|
276
|
+
), /* @__PURE__ */ React5.createElement(Button3, { variant: "outlined", onClick: handleAddButton }, "Add"))));
|
|
277
|
+
function handleInput(e) {
|
|
278
|
+
setInputValue(e.target.value);
|
|
279
|
+
}
|
|
280
|
+
function handleEnterPress(e) {
|
|
281
|
+
if (e.key === "Enter") {
|
|
282
|
+
handleAddButton(e);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function handleAddButton(e) {
|
|
286
|
+
e.preventDefault();
|
|
287
|
+
if (Boolean(inputValue)) {
|
|
288
|
+
setList([...list, createListObject(inputValue)]);
|
|
289
|
+
setInputValue("");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function handleDeleteButton(e, id) {
|
|
293
|
+
e.preventDefault();
|
|
294
|
+
const newData = list.filter((item) => item.id !== id);
|
|
295
|
+
setList(newData);
|
|
296
|
+
}
|
|
297
|
+
function prepareDataForDatabase(list2) {
|
|
298
|
+
return list2.map(({ value }) => value).join("|");
|
|
299
|
+
}
|
|
300
|
+
function prepareDataForList(str) {
|
|
301
|
+
return str.split("|").map((item) => createListObject(item));
|
|
302
|
+
}
|
|
303
|
+
function createListObject(value) {
|
|
304
|
+
return {
|
|
305
|
+
id: `${Date.now()}-${Math.floor(Math.random() * 1e3)}`,
|
|
306
|
+
value
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
var StringList_default = StringList;
|
|
311
|
+
export {
|
|
312
|
+
CustomSlug_default as CustomSlug,
|
|
313
|
+
StringList_default as StringList
|
|
314
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rulab/adminjs-components",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"private": false,
|
|
8
|
+
"type": "module",
|
|
9
|
+
"keywords": ["adminjs"],
|
|
10
|
+
"repository": {
|
|
11
|
+
"url": "https://github.com/rulab/adminjs-components"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
15
|
+
"lint": "tsc"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@adminjs/design-system": "^4.1.1",
|
|
19
|
+
"@dnd-kit/core": "^6.1.0",
|
|
20
|
+
"@dnd-kit/sortable": "^8.0.0",
|
|
21
|
+
"adminjs": "^7.8.1",
|
|
22
|
+
"react": "^18.2.0",
|
|
23
|
+
"slugify": "^1.6.6",
|
|
24
|
+
"styled-components": "^6.1.11"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.11.24",
|
|
28
|
+
"@types/react": "^18.2.61",
|
|
29
|
+
"eslint": "^8.57.0",
|
|
30
|
+
"tsup": "^8.1.0",
|
|
31
|
+
"typescript": "^5.3.2"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"packageManager": "pnpm@8.15.6"
|
|
37
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ChangeEvent,
|
|
3
|
+
FC,
|
|
4
|
+
SyntheticEvent,
|
|
5
|
+
useEffect,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { ThemeProvider } from "styled-components";
|
|
9
|
+
import { EditPropertyProps } from "adminjs";
|
|
10
|
+
|
|
11
|
+
import { theme, Label } from "@adminjs/design-system";
|
|
12
|
+
|
|
13
|
+
import { slugifyTitle } from "../../utils/index.js";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
StyledInputWrapper,
|
|
17
|
+
StyledCustomInput,
|
|
18
|
+
StyledGenerateButton,
|
|
19
|
+
} from "./styles.js";
|
|
20
|
+
|
|
21
|
+
type CustomSlugTypes = Omit<EditPropertyProps, "where" | "resource">;
|
|
22
|
+
|
|
23
|
+
const CustomSlug: FC<CustomSlugTypes> = ({ property, record, onChange }) => {
|
|
24
|
+
const [inputValue, setInputValue] = useState(record.params.slug);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
onChange(property.path, inputValue);
|
|
28
|
+
}, [inputValue]);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<ThemeProvider theme={theme}>
|
|
32
|
+
<Label htmlFor="customSlug">Slug</Label>
|
|
33
|
+
<StyledInputWrapper>
|
|
34
|
+
<StyledCustomInput
|
|
35
|
+
id={property.path}
|
|
36
|
+
name={property.path}
|
|
37
|
+
value={inputValue}
|
|
38
|
+
onChange={handleInput}
|
|
39
|
+
/>
|
|
40
|
+
<StyledGenerateButton variant="outlined" onClick={generateSlug}>
|
|
41
|
+
Generate Slug
|
|
42
|
+
</StyledGenerateButton>
|
|
43
|
+
</StyledInputWrapper>
|
|
44
|
+
</ThemeProvider>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
function handleInput(e: ChangeEvent<HTMLInputElement>) {
|
|
48
|
+
setInputValue(e.target.value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function generateSlug(e: SyntheticEvent<HTMLInputElement>) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
const title = record.title;
|
|
54
|
+
setInputValue(slugifyTitle(title));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default CustomSlug;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./CustomSlug.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import { styled } from "@adminjs/design-system/styled-components";
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { Button, Box, Input } from "@adminjs/design-system";
|
|
5
|
+
|
|
6
|
+
export const StyledInputWrapper = styled(Box)`
|
|
7
|
+
display: flex;
|
|
8
|
+
margin-bottom: 40px;
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
export const StyledCustomInput = styled(Input)`
|
|
12
|
+
width: 100%;
|
|
13
|
+
margin-right: 10px;
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
export const StyledGenerateButton = styled(Button)`
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
`;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React, { Fragment, useMemo, useState } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DndContext,
|
|
6
|
+
KeyboardSensor,
|
|
7
|
+
PointerSensor,
|
|
8
|
+
useSensor,
|
|
9
|
+
useSensors,
|
|
10
|
+
DragOverlay,
|
|
11
|
+
defaultDropAnimationSideEffects,
|
|
12
|
+
} from '@dnd-kit/core';
|
|
13
|
+
import {
|
|
14
|
+
SortableContext,
|
|
15
|
+
arrayMove,
|
|
16
|
+
sortableKeyboardCoordinates,
|
|
17
|
+
verticalListSortingStrategy,
|
|
18
|
+
} from '@dnd-kit/sortable';
|
|
19
|
+
|
|
20
|
+
import type { DropAnimation } from '@dnd-kit/core';
|
|
21
|
+
|
|
22
|
+
import { DragHandle, SortableItem } from './components/index.js';
|
|
23
|
+
import { StyledListWrapper } from './styles.js';
|
|
24
|
+
|
|
25
|
+
import type { Active, UniqueIdentifier } from '@dnd-kit/core';
|
|
26
|
+
|
|
27
|
+
interface BaseItem {
|
|
28
|
+
id: UniqueIdentifier;
|
|
29
|
+
value: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Props<T extends BaseItem> {
|
|
33
|
+
items: T[];
|
|
34
|
+
onChange(items: T[]): void;
|
|
35
|
+
renderItem(item: T): ReactNode;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const dropAnimationConfig: DropAnimation = {
|
|
39
|
+
sideEffects: defaultDropAnimationSideEffects({
|
|
40
|
+
styles: {
|
|
41
|
+
active: {
|
|
42
|
+
opacity: '0.4',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function SortableList<T extends BaseItem>({
|
|
49
|
+
items,
|
|
50
|
+
onChange,
|
|
51
|
+
renderItem,
|
|
52
|
+
}: Props<T>) {
|
|
53
|
+
const [active, setActive] = useState<Active | null>(null);
|
|
54
|
+
const activeItem = useMemo(
|
|
55
|
+
() => items.find((item) => item.id === active?.id),
|
|
56
|
+
[active, items],
|
|
57
|
+
);
|
|
58
|
+
const sensors = useSensors(
|
|
59
|
+
useSensor(PointerSensor),
|
|
60
|
+
useSensor(KeyboardSensor, {
|
|
61
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<DndContext
|
|
67
|
+
sensors={sensors}
|
|
68
|
+
onDragStart={({ active }) => {
|
|
69
|
+
setActive(active);
|
|
70
|
+
}}
|
|
71
|
+
onDragEnd={({ active, over }) => {
|
|
72
|
+
if (over && active.id !== over?.id) {
|
|
73
|
+
const activeIndex = items.findIndex(({ id }) => id === active.id);
|
|
74
|
+
const overIndex = items.findIndex(({ id }) => id === over.id);
|
|
75
|
+
|
|
76
|
+
onChange(arrayMove(items, activeIndex, overIndex));
|
|
77
|
+
}
|
|
78
|
+
setActive(null);
|
|
79
|
+
}}
|
|
80
|
+
onDragCancel={() => {
|
|
81
|
+
setActive(null);
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
|
85
|
+
<StyledListWrapper role="application">
|
|
86
|
+
{items.map((item, index) => (
|
|
87
|
+
<Fragment key={index}>{renderItem(item)}</Fragment>
|
|
88
|
+
))}
|
|
89
|
+
</StyledListWrapper>
|
|
90
|
+
</SortableContext>
|
|
91
|
+
<DragOverlay dropAnimation={dropAnimationConfig}>
|
|
92
|
+
{activeItem ? renderItem(activeItem) : null}
|
|
93
|
+
</DragOverlay>
|
|
94
|
+
</DndContext>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
SortableList.Item = SortableItem;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { Context, FC, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import { DragButton } from './styles.js';
|
|
4
|
+
import type { DragContext } from './types.js';
|
|
5
|
+
|
|
6
|
+
interface DragHandlePropsType {
|
|
7
|
+
context: Context<DragContext>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DragHandle: FC<DragHandlePropsType> = ({ context }) => {
|
|
11
|
+
const { attributes, listeners, ref } = useContext(context);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<DragButton {...attributes} {...listeners} ref={ref}>
|
|
15
|
+
<svg viewBox="0 0 20 20" width="13">
|
|
16
|
+
<path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z"></path>
|
|
17
|
+
</svg>
|
|
18
|
+
</DragButton>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React, { createContext, useMemo, ChangeEvent } from 'react';
|
|
2
|
+
import type { CSSProperties, PropsWithChildren } from 'react';
|
|
3
|
+
import { useSortable } from '@dnd-kit/sortable';
|
|
4
|
+
import { Button, Icon } from '@adminjs/design-system';
|
|
5
|
+
|
|
6
|
+
import { DragHandle } from './DragHandle.js';
|
|
7
|
+
import { StyledListItem } from './styles.js';
|
|
8
|
+
import type { DragContext } from './types.js';
|
|
9
|
+
|
|
10
|
+
interface SortableItemPropsType {
|
|
11
|
+
id: string;
|
|
12
|
+
onDelete: (e: ChangeEvent<HTMLInputElement>, id: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const SortableItemContext = createContext<DragContext>({
|
|
16
|
+
attributes: {},
|
|
17
|
+
listeners: undefined,
|
|
18
|
+
ref() {},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export function SortableItem({
|
|
22
|
+
children,
|
|
23
|
+
id,
|
|
24
|
+
onDelete,
|
|
25
|
+
}: PropsWithChildren<SortableItemPropsType>) {
|
|
26
|
+
const { attributes, isDragging, listeners, setNodeRef, setActivatorNodeRef } =
|
|
27
|
+
useSortable({ id });
|
|
28
|
+
const context = useMemo(
|
|
29
|
+
() => ({
|
|
30
|
+
attributes,
|
|
31
|
+
listeners,
|
|
32
|
+
ref: setActivatorNodeRef,
|
|
33
|
+
}),
|
|
34
|
+
[attributes, listeners, setActivatorNodeRef],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const style: CSSProperties = {
|
|
38
|
+
opacity: isDragging ? 0.4 : undefined,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<SortableItemContext.Provider value={context}>
|
|
43
|
+
<StyledListItem ref={setNodeRef} style={style}>
|
|
44
|
+
<div>
|
|
45
|
+
<DragHandle context={SortableItemContext} />
|
|
46
|
+
{children}
|
|
47
|
+
</div>
|
|
48
|
+
<Button
|
|
49
|
+
variant="outlined"
|
|
50
|
+
color="danger"
|
|
51
|
+
size="icon"
|
|
52
|
+
onClick={(e: ChangeEvent<HTMLInputElement>) => onDelete(e, id)}
|
|
53
|
+
>
|
|
54
|
+
<Icon icon="X" color="red" />
|
|
55
|
+
</Button>
|
|
56
|
+
</StyledListItem>
|
|
57
|
+
</SortableItemContext.Provider>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { styled } from '@adminjs/design-system/styled-components';
|
|
2
|
+
|
|
3
|
+
export const StyledListItem = styled.li`
|
|
4
|
+
display: flex;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
align-items: center;
|
|
7
|
+
background-color: #fff;
|
|
8
|
+
padding: 10px 20px 10px 15px;
|
|
9
|
+
box-shadow:
|
|
10
|
+
0 0 0 calc(1px / var(--scale-x, 1)) rgba(63, 63, 68, 0.05),
|
|
11
|
+
0 1px calc(3px / var(--scale-x, 1)) 0 rgba(34, 33, 81, 0.15);
|
|
12
|
+
border-radius: 5px;
|
|
13
|
+
list-style: none;
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
export const DragButton = styled.button`
|
|
17
|
+
padding: 3px;
|
|
18
|
+
margin-right: 15px;
|
|
19
|
+
cursor: move;
|
|
20
|
+
background: none;
|
|
21
|
+
border: none;
|
|
22
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SortableList } from './SortableList.js';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
ChangeEvent,
|
|
5
|
+
KeyboardEvent,
|
|
6
|
+
SyntheticEvent,
|
|
7
|
+
FC,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { ThemeProvider } from "styled-components";
|
|
10
|
+
import { EditPropertyProps } from "adminjs";
|
|
11
|
+
import { theme, Button, Input, Label } from "@adminjs/design-system";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
StyledWrapper,
|
|
15
|
+
StyledCustomInput,
|
|
16
|
+
StyledListWrapper,
|
|
17
|
+
StyledInputWrapper,
|
|
18
|
+
} from "./styles.js";
|
|
19
|
+
|
|
20
|
+
import { SortableList } from "./SortableList/SortableList.js";
|
|
21
|
+
|
|
22
|
+
type ListDataTypes = {
|
|
23
|
+
id: string;
|
|
24
|
+
value: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type StringListTypes = Omit<EditPropertyProps, "where" | "resource">;
|
|
28
|
+
|
|
29
|
+
const StringList: FC<StringListTypes> = ({ record, onChange, property }) => {
|
|
30
|
+
const stringListValue =
|
|
31
|
+
record.params?.[property.path] ?? property.props.value ?? "";
|
|
32
|
+
|
|
33
|
+
const initialList = stringListValue
|
|
34
|
+
? prepareDataForList(stringListValue)
|
|
35
|
+
: [];
|
|
36
|
+
|
|
37
|
+
const [inputValue, setInputValue] = useState("");
|
|
38
|
+
const [list, setList] = useState<ListDataTypes[]>(initialList);
|
|
39
|
+
|
|
40
|
+
const serializedData = prepareDataForDatabase(list);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
onChange(property.path, serializedData);
|
|
44
|
+
}, [serializedData]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<ThemeProvider theme={theme}>
|
|
48
|
+
<Label htmlFor="custom">String List</Label>
|
|
49
|
+
<StyledWrapper>
|
|
50
|
+
<StyledListWrapper>
|
|
51
|
+
<SortableList
|
|
52
|
+
items={list}
|
|
53
|
+
onChange={setList}
|
|
54
|
+
renderItem={(item) => (
|
|
55
|
+
<SortableList.Item id={item.id} onDelete={handleDeleteButton}>
|
|
56
|
+
{item.value}
|
|
57
|
+
</SortableList.Item>
|
|
58
|
+
)}
|
|
59
|
+
/>
|
|
60
|
+
</StyledListWrapper>
|
|
61
|
+
<StyledInputWrapper>
|
|
62
|
+
<Input
|
|
63
|
+
id="stringList"
|
|
64
|
+
name={property.path}
|
|
65
|
+
value={serializedData}
|
|
66
|
+
hidden
|
|
67
|
+
/>
|
|
68
|
+
<StyledCustomInput
|
|
69
|
+
id="custom"
|
|
70
|
+
name="customInput"
|
|
71
|
+
value={inputValue}
|
|
72
|
+
onChange={handleInput}
|
|
73
|
+
onKeyPress={handleEnterPress}
|
|
74
|
+
/>
|
|
75
|
+
<Button variant="outlined" onClick={handleAddButton}>
|
|
76
|
+
Add
|
|
77
|
+
</Button>
|
|
78
|
+
</StyledInputWrapper>
|
|
79
|
+
</StyledWrapper>
|
|
80
|
+
</ThemeProvider>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
function handleInput(e: ChangeEvent<HTMLInputElement>) {
|
|
84
|
+
setInputValue(e.target.value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function handleEnterPress(e: KeyboardEvent<HTMLInputElement>) {
|
|
88
|
+
if (e.key === "Enter") {
|
|
89
|
+
handleAddButton(e);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function handleAddButton(e: SyntheticEvent<HTMLInputElement>) {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
|
|
96
|
+
if (Boolean(inputValue)) {
|
|
97
|
+
setList([...list, createListObject(inputValue)]);
|
|
98
|
+
|
|
99
|
+
setInputValue("");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function handleDeleteButton(e: ChangeEvent<HTMLInputElement>, id: string) {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
const newData = list.filter((item: ListDataTypes) => item.id !== id);
|
|
106
|
+
setList(newData);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function prepareDataForDatabase(list: ListDataTypes[]) {
|
|
110
|
+
return list.map(({ value }) => value).join("|");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function prepareDataForList(str: string) {
|
|
114
|
+
return str.split("|").map((item) => createListObject(item));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function createListObject(value: string) {
|
|
118
|
+
return {
|
|
119
|
+
id: `${Date.now()}-${Math.floor(Math.random() * 1000)}`,
|
|
120
|
+
value: value,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default StringList;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./StringList.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { styled } from "@adminjs/design-system/styled-components";
|
|
2
|
+
import { Box, Input } from "@adminjs/design-system";
|
|
3
|
+
|
|
4
|
+
export const StyledWrapper = styled(Box)`
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export const StyledCustomInput = styled(Input)`
|
|
10
|
+
width: 100%;
|
|
11
|
+
margin-right: 10px;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export const StyledInputWrapper = styled(Box)`
|
|
15
|
+
display: flex;
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const StyledListWrapper = styled(Box)`
|
|
19
|
+
margin-bottom: 15px;
|
|
20
|
+
`;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./components/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { slugifyTitle } from "./slugifyTitle.js";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"esModuleInterop": true,
|
|
4
|
+
"skipLibCheck": true,
|
|
5
|
+
"target": "es2022",
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"moduleDetection": "force",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noUncheckedIndexedAccess": true,
|
|
11
|
+
"moduleResolution": "Bundler",
|
|
12
|
+
"module": "ESNext",
|
|
13
|
+
"lib": ["es2022", "dom", "dom.iterable"],
|
|
14
|
+
"jsx": "react",
|
|
15
|
+
"noEmit": true
|
|
16
|
+
}
|
|
17
|
+
}
|