@wipco/sdui 0.1.0 → 0.3.0
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/NOTICE +27 -0
- package/dist/chunk-4SU2KYPU.js +59 -0
- package/dist/chunk-56KKWLNI.js +186 -0
- package/dist/chunk-7Y4J7EOK.js +115 -0
- package/dist/chunk-APCXOCAO.js +110 -0
- package/dist/chunk-AROOQ5W6.js +119 -0
- package/dist/chunk-DGPRX4MU.js +251 -0
- package/dist/chunk-IL2KS4WS.js +25 -0
- package/dist/chunk-KADFN76X.js +541 -0
- package/dist/chunk-KBHI3IUR.js +40 -0
- package/dist/chunk-LZJTKVMO.js +767 -0
- package/dist/chunk-MOIU7MUA.js +282 -0
- package/dist/chunk-MSAV4J4I.js +77 -0
- package/dist/chunk-OOHHW4K4.js +159 -0
- package/dist/chunk-Q6SBH63R.js +190 -0
- package/dist/chunk-QZQDHSMD.js +78 -0
- package/dist/chunk-RH7ESUE6.js +309 -0
- package/dist/chunk-SEBPBUZR.js +260 -0
- package/dist/chunk-USKRM22W.js +39 -0
- package/dist/chunk-WNYWTFGW.js +61 -0
- package/dist/chunk-WVRCIVAD.js +72 -0
- package/dist/chunk-YJMD6MOE.js +404 -0
- package/dist/chunk-ZNITJ4KK.js +433 -0
- package/dist/chunk-ZQR3ULVH.js +193 -0
- package/dist/chunk-ZUCI34QY.js +1290 -0
- package/dist/index.d.ts +391 -79
- package/dist/index.js +277 -758
- package/dist/meta.d.ts +10 -2
- package/dist/meta.js +268 -27
- package/dist/rich.js +3 -2
- package/dist/styles.css +334 -13
- package/dist/widgets/AutocompleteField.d.ts +96 -0
- package/dist/widgets/AutocompleteField.js +3 -0
- package/dist/widgets/ColumnBoard.d.ts +47 -0
- package/dist/widgets/ColumnBoard.js +2 -0
- package/dist/widgets/DiffView.d.ts +30 -0
- package/dist/widgets/DiffView.js +2 -0
- package/dist/widgets/Disclosure.d.ts +20 -0
- package/dist/widgets/Disclosure.js +2 -0
- package/dist/widgets/EventCalendar.d.ts +59 -0
- package/dist/widgets/EventCalendar.js +2 -0
- package/dist/widgets/ImageLightbox.d.ts +25 -0
- package/dist/widgets/ImageLightbox.js +2 -0
- package/dist/widgets/InlineEdit.d.ts +23 -0
- package/dist/widgets/InlineEdit.js +2 -0
- package/dist/widgets/JsonField.d.ts +24 -0
- package/dist/widgets/JsonField.js +2 -0
- package/dist/widgets/LiveClock.d.ts +21 -0
- package/dist/widgets/LiveClock.js +1 -0
- package/dist/widgets/OrgChart.d.ts +32 -0
- package/dist/widgets/OrgChart.js +2 -0
- package/dist/widgets/PeriodFilter.d.ts +48 -0
- package/dist/widgets/PeriodFilter.js +2 -0
- package/dist/widgets/PrintSheet.d.ts +23 -0
- package/dist/widgets/PrintSheet.js +2 -0
- package/dist/widgets/SearchableMultiPicker.d.ts +29 -0
- package/dist/widgets/SearchableMultiPicker.js +3 -0
- package/dist/widgets/SecureScreen.d.ts +25 -0
- package/dist/widgets/SecureScreen.js +2 -0
- package/dist/widgets/StackedMeter.d.ts +26 -0
- package/dist/widgets/StackedMeter.js +1 -0
- package/dist/widgets/Strip.d.ts +20 -0
- package/dist/widgets/Strip.js +1 -0
- package/dist/widgets/SummaryTable.d.ts +31 -0
- package/dist/widgets/SummaryTable.js +1 -0
- package/dist/widgets/TagMultiSelect.d.ts +25 -0
- package/dist/widgets/TagMultiSelect.js +2 -0
- package/dist/widgets/Timeline.d.ts +30 -0
- package/dist/widgets/Timeline.js +3 -0
- package/dist/widgets/TreePicker.d.ts +21 -0
- package/dist/widgets/TreePicker.js +3 -0
- package/dist/widgets/TreeView.d.ts +26 -0
- package/dist/widgets/TreeView.js +2 -0
- package/dist/widgets/index.d.ts +76 -0
- package/dist/widgets/index.js +69 -0
- package/package.json +93 -3
- package/dist/chunk-AR2JYE75.js +0 -889
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { includesLooseSearch } from './chunk-KADFN76X.js';
|
|
2
|
+
import { Icon, Button, Input, Spinner } from './chunk-ZUCI34QY.js';
|
|
3
|
+
import { useId, useRef, useState, useEffect, useMemo } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function toAutocompleteOptions(payload) {
|
|
10
|
+
if (Array.isArray(payload)) return payload.filter(isRecord);
|
|
11
|
+
if (isRecord(payload)) {
|
|
12
|
+
for (const key of ["items", "data", "results", "rows"]) {
|
|
13
|
+
const inner = payload[key];
|
|
14
|
+
if (Array.isArray(inner)) return inner.filter(isRecord);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
function pickText(option, key) {
|
|
20
|
+
if (!key) return "";
|
|
21
|
+
const raw = option[key];
|
|
22
|
+
if (raw === null || raw === void 0) return "";
|
|
23
|
+
return String(raw);
|
|
24
|
+
}
|
|
25
|
+
var ellipsisStyle = {
|
|
26
|
+
display: "block",
|
|
27
|
+
overflow: "hidden",
|
|
28
|
+
textOverflow: "ellipsis",
|
|
29
|
+
whiteSpace: "nowrap"
|
|
30
|
+
};
|
|
31
|
+
var statusRowStyle = {
|
|
32
|
+
padding: "var(--wp-space-2) var(--wp-space-3)"
|
|
33
|
+
};
|
|
34
|
+
function AutocompleteField({
|
|
35
|
+
search,
|
|
36
|
+
options,
|
|
37
|
+
labelKey = "label",
|
|
38
|
+
valueKey = "value",
|
|
39
|
+
subKey,
|
|
40
|
+
value,
|
|
41
|
+
selectedLabel,
|
|
42
|
+
onChange,
|
|
43
|
+
placeholder = "\uAC80\uC0C9\uC5B4 \uC785\uB825",
|
|
44
|
+
minChars = 1,
|
|
45
|
+
debounceMs = 300,
|
|
46
|
+
invalid = false,
|
|
47
|
+
disabled = false
|
|
48
|
+
}) {
|
|
49
|
+
const listId = useId();
|
|
50
|
+
const hostRef = useRef(null);
|
|
51
|
+
const seqRef = useRef(0);
|
|
52
|
+
const optionsRef = useRef(options);
|
|
53
|
+
optionsRef.current = options;
|
|
54
|
+
const searchRef = useRef(search);
|
|
55
|
+
searchRef.current = search;
|
|
56
|
+
const hasSearch = search !== void 0;
|
|
57
|
+
const isControlled = value !== void 0;
|
|
58
|
+
const [internalValue, setInternalValue] = useState("");
|
|
59
|
+
const committed = (isControlled ? value : internalValue) ?? "";
|
|
60
|
+
const hasSelection = committed !== "";
|
|
61
|
+
const [picked, setPicked] = useState(null);
|
|
62
|
+
const [query, setQuery] = useState("");
|
|
63
|
+
const [open, setOpen] = useState(false);
|
|
64
|
+
const [status, setStatus] = useState("idle");
|
|
65
|
+
const [results, setResults] = useState([]);
|
|
66
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
67
|
+
const [focusPending, setFocusPending] = useState(false);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (disabled || hasSelection) return;
|
|
70
|
+
const keyword = query.trim();
|
|
71
|
+
if (keyword.length < minChars) {
|
|
72
|
+
seqRef.current += 1;
|
|
73
|
+
setStatus("idle");
|
|
74
|
+
setResults([]);
|
|
75
|
+
setOpen(false);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setStatus("loading");
|
|
79
|
+
setOpen(true);
|
|
80
|
+
const seq = ++seqRef.current;
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
const run = searchRef.current;
|
|
84
|
+
if (run) {
|
|
85
|
+
run(keyword, controller.signal).then((payload) => {
|
|
86
|
+
if (seqRef.current !== seq) return;
|
|
87
|
+
const next = Array.isArray(payload) ? { options: payload } : payload;
|
|
88
|
+
setResults(Array.isArray(next.options) ? next.options : []);
|
|
89
|
+
setActiveIndex(0);
|
|
90
|
+
setStatus(next.status === "offline" ? "offline" : "ready");
|
|
91
|
+
}).catch(() => {
|
|
92
|
+
if (controller.signal.aborted || seqRef.current !== seq) return;
|
|
93
|
+
setResults([]);
|
|
94
|
+
setStatus("error");
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const filtered = (optionsRef.current ?? []).filter(
|
|
99
|
+
(option) => includesLooseSearch(`${pickText(option, labelKey)} ${pickText(option, subKey)}`, keyword)
|
|
100
|
+
);
|
|
101
|
+
if (seqRef.current !== seq) return;
|
|
102
|
+
setResults(filtered);
|
|
103
|
+
setActiveIndex(0);
|
|
104
|
+
setStatus("ready");
|
|
105
|
+
}, debounceMs);
|
|
106
|
+
return () => {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
controller.abort();
|
|
109
|
+
};
|
|
110
|
+
}, [query, hasSearch, labelKey, subKey, minChars, debounceMs, disabled, hasSelection]);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!open) return;
|
|
113
|
+
const closeOnOutsideClick = (event) => {
|
|
114
|
+
if (event.target instanceof Node && hostRef.current?.contains(event.target)) return;
|
|
115
|
+
setOpen(false);
|
|
116
|
+
};
|
|
117
|
+
const closeOnEscape = (event) => {
|
|
118
|
+
if (event.key === "Escape") setOpen(false);
|
|
119
|
+
};
|
|
120
|
+
document.addEventListener("pointerdown", closeOnOutsideClick);
|
|
121
|
+
document.addEventListener("keydown", closeOnEscape);
|
|
122
|
+
return () => {
|
|
123
|
+
document.removeEventListener("pointerdown", closeOnOutsideClick);
|
|
124
|
+
document.removeEventListener("keydown", closeOnEscape);
|
|
125
|
+
};
|
|
126
|
+
}, [open]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!focusPending || hasSelection) return;
|
|
129
|
+
hostRef.current?.querySelector("input")?.focus();
|
|
130
|
+
setFocusPending(false);
|
|
131
|
+
}, [focusPending, hasSelection]);
|
|
132
|
+
const selectOption = (option) => {
|
|
133
|
+
const nextValue = pickText(option, valueKey);
|
|
134
|
+
setPicked(option);
|
|
135
|
+
if (!isControlled) setInternalValue(nextValue);
|
|
136
|
+
setQuery("");
|
|
137
|
+
setOpen(false);
|
|
138
|
+
setStatus("idle");
|
|
139
|
+
setResults([]);
|
|
140
|
+
onChange?.(nextValue, option);
|
|
141
|
+
};
|
|
142
|
+
const clearSelection = () => {
|
|
143
|
+
setPicked(null);
|
|
144
|
+
if (!isControlled) setInternalValue("");
|
|
145
|
+
setQuery("");
|
|
146
|
+
setStatus("idle");
|
|
147
|
+
setResults([]);
|
|
148
|
+
setFocusPending(true);
|
|
149
|
+
onChange?.(null);
|
|
150
|
+
};
|
|
151
|
+
const handleKeyDown = (event) => {
|
|
152
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
if (!open || results.length === 0) return;
|
|
155
|
+
setActiveIndex((index) => {
|
|
156
|
+
const delta = event.key === "ArrowDown" ? 1 : -1;
|
|
157
|
+
return (index + delta + results.length) % results.length;
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (event.key === "Enter") {
|
|
162
|
+
if (!open) return;
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
const active = status === "ready" ? results[activeIndex] : void 0;
|
|
165
|
+
if (active) selectOption(active);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (event.key === "Escape") setOpen(false);
|
|
169
|
+
};
|
|
170
|
+
const staticMatch = useMemo(() => {
|
|
171
|
+
if (!hasSelection) return null;
|
|
172
|
+
return (options ?? []).find((option) => pickText(option, valueKey) === committed) ?? null;
|
|
173
|
+
}, [options, valueKey, committed, hasSelection]);
|
|
174
|
+
const pickedMatches = picked !== null && pickText(picked, valueKey) === committed;
|
|
175
|
+
const cardOption = pickedMatches ? picked : staticMatch;
|
|
176
|
+
const cardLabel = (pickedMatches ? pickText(picked, labelKey) : "") || selectedLabel || (cardOption ? pickText(cardOption, labelKey) : "") || committed;
|
|
177
|
+
const cardSub = cardOption ? pickText(cardOption, subKey) : "";
|
|
178
|
+
if (hasSelection) {
|
|
179
|
+
return /* @__PURE__ */ jsxs(
|
|
180
|
+
"div",
|
|
181
|
+
{
|
|
182
|
+
ref: hostRef,
|
|
183
|
+
className: "wp-row wp-row--between",
|
|
184
|
+
style: {
|
|
185
|
+
border: `var(--wp-border-w) solid ${invalid ? "var(--wp-border-danger)" : "var(--wp-border-subtle)"}`,
|
|
186
|
+
borderRadius: "var(--wp-radius-control)",
|
|
187
|
+
background: "var(--wp-surface-card)",
|
|
188
|
+
padding: "var(--wp-space-2) var(--wp-space-3)"
|
|
189
|
+
},
|
|
190
|
+
children: [
|
|
191
|
+
/* @__PURE__ */ jsxs("div", { className: "wp-row", style: { minWidth: 0 }, children: [
|
|
192
|
+
/* @__PURE__ */ jsx(
|
|
193
|
+
Icon,
|
|
194
|
+
{
|
|
195
|
+
name: "circle-check",
|
|
196
|
+
size: 18,
|
|
197
|
+
title: "\uC120\uD0DD\uB428",
|
|
198
|
+
style: { color: "var(--wp-text-success)", flex: "none" }
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
/* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
|
|
202
|
+
/* @__PURE__ */ jsx("div", { className: "wp-body", style: ellipsisStyle, children: cardLabel }),
|
|
203
|
+
cardSub ? /* @__PURE__ */ jsx("div", { className: "wp-caption", style: ellipsisStyle, children: cardSub }) : null
|
|
204
|
+
] })
|
|
205
|
+
] }),
|
|
206
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled, onClick: clearSelection, children: "\uBCC0\uACBD" })
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const showActiveDescendant = open && status === "ready" && results.length > 0;
|
|
212
|
+
return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "erp-popover-host", children: [
|
|
213
|
+
/* @__PURE__ */ jsx(
|
|
214
|
+
Input,
|
|
215
|
+
{
|
|
216
|
+
role: "combobox",
|
|
217
|
+
"aria-expanded": open,
|
|
218
|
+
"aria-controls": listId,
|
|
219
|
+
"aria-autocomplete": "list",
|
|
220
|
+
"aria-activedescendant": showActiveDescendant ? `${listId}-opt-${activeIndex}` : void 0,
|
|
221
|
+
icon: "search",
|
|
222
|
+
invalid,
|
|
223
|
+
disabled,
|
|
224
|
+
placeholder,
|
|
225
|
+
value: query,
|
|
226
|
+
onChange: (event) => setQuery(event.target.value),
|
|
227
|
+
onFocus: () => {
|
|
228
|
+
if (query.trim().length >= minChars) setOpen(true);
|
|
229
|
+
},
|
|
230
|
+
onKeyDown: handleKeyDown
|
|
231
|
+
}
|
|
232
|
+
),
|
|
233
|
+
open ? /* @__PURE__ */ jsx("div", { className: "erp-popover erp-popover--left", style: { width: "100%" }, children: /* @__PURE__ */ jsx(
|
|
234
|
+
"div",
|
|
235
|
+
{
|
|
236
|
+
id: listId,
|
|
237
|
+
role: "listbox",
|
|
238
|
+
className: "erp-popover__body",
|
|
239
|
+
style: { maxHeight: 280, padding: "var(--wp-space-1)" },
|
|
240
|
+
children: status === "loading" ? /* @__PURE__ */ jsx("div", { className: "wp-row", style: statusRowStyle, children: /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "\uAC80\uC0C9 \uC911\u2026" }) }) : status === "offline" ? /* @__PURE__ */ jsxs("div", { className: "wp-row", style: statusRowStyle, children: [
|
|
241
|
+
/* @__PURE__ */ jsx(Icon, { name: "unplug", size: 16, style: { color: "var(--wp-text-muted)", flex: "none" } }),
|
|
242
|
+
/* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 \u2014 \uAC80\uC0C9 \uACB0\uACFC\uB97C \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4" })
|
|
243
|
+
] }) : status === "error" ? /* @__PURE__ */ jsxs("div", { className: "wp-row", style: statusRowStyle, children: [
|
|
244
|
+
/* @__PURE__ */ jsx(Icon, { name: "circle-alert", size: 16, style: { color: "var(--wp-text-danger)", flex: "none" } }),
|
|
245
|
+
/* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uAC80\uC0C9 \uC2E4\uD328 \u2014 \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694" })
|
|
246
|
+
] }) : results.length === 0 ? /* @__PURE__ */ jsx("div", { className: "wp-caption", style: statusRowStyle, children: "\uACB0\uACFC \uC5C6\uC74C" }) : results.map((option, index) => {
|
|
247
|
+
const optionSub = pickText(option, subKey);
|
|
248
|
+
return /* @__PURE__ */ jsxs(
|
|
249
|
+
"button",
|
|
250
|
+
{
|
|
251
|
+
id: `${listId}-opt-${index}`,
|
|
252
|
+
type: "button",
|
|
253
|
+
role: "option",
|
|
254
|
+
"aria-selected": index === activeIndex,
|
|
255
|
+
onClick: () => selectOption(option),
|
|
256
|
+
onPointerEnter: () => setActiveIndex(index),
|
|
257
|
+
style: {
|
|
258
|
+
display: "block",
|
|
259
|
+
width: "100%",
|
|
260
|
+
textAlign: "left",
|
|
261
|
+
padding: "var(--wp-space-2) var(--wp-space-3)",
|
|
262
|
+
border: 0,
|
|
263
|
+
borderRadius: "var(--wp-radius-sm)",
|
|
264
|
+
background: index === activeIndex ? "var(--wp-surface-hover)" : "transparent",
|
|
265
|
+
color: "var(--wp-text-default)",
|
|
266
|
+
font: "inherit",
|
|
267
|
+
cursor: "pointer"
|
|
268
|
+
},
|
|
269
|
+
children: [
|
|
270
|
+
/* @__PURE__ */ jsx("span", { className: "wp-body-sm", style: ellipsisStyle, children: pickText(option, labelKey) || pickText(option, valueKey) }),
|
|
271
|
+
optionSub ? /* @__PURE__ */ jsx("span", { className: "wp-caption", style: ellipsisStyle, children: optionSub }) : null
|
|
272
|
+
]
|
|
273
|
+
},
|
|
274
|
+
`${pickText(option, valueKey)}-${index}`
|
|
275
|
+
);
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
) }) : null
|
|
279
|
+
] });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export { AutocompleteField, toAutocompleteOptions };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Disclosure } from './chunk-WNYWTFGW.js';
|
|
2
|
+
import { Icon } from './chunk-ZUCI34QY.js';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var TYPE_META = {
|
|
6
|
+
create: { icon: "plus", tone: "success" },
|
|
7
|
+
update: { icon: "pencil", tone: "info" },
|
|
8
|
+
delete: { icon: "trash-2", tone: "danger" },
|
|
9
|
+
info: { icon: "info", tone: "neutral" }
|
|
10
|
+
};
|
|
11
|
+
var TIMELINE_CSS = `
|
|
12
|
+
.erp-timeline{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;min-width:0}
|
|
13
|
+
.erp-timeline__item{display:flex;gap:var(--wp-space-3);min-width:0}
|
|
14
|
+
.erp-timeline__rail{display:flex;flex-direction:column;align-items:center;flex:none;width:28px}
|
|
15
|
+
.erp-timeline__dot{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;border:var(--wp-border-w) solid transparent;flex:none}
|
|
16
|
+
.erp-timeline__dot--success{background:var(--wp-status-success-bg);border-color:var(--wp-status-success-border);color:var(--wp-status-success-fg)}
|
|
17
|
+
.erp-timeline__dot--info{background:var(--wp-status-info-bg);border-color:var(--wp-status-info-border);color:var(--wp-status-info-fg)}
|
|
18
|
+
.erp-timeline__dot--danger{background:var(--wp-status-danger-bg);border-color:var(--wp-status-danger-border);color:var(--wp-status-danger-fg)}
|
|
19
|
+
.erp-timeline__dot--neutral{background:var(--wp-status-neutral-bg);border-color:var(--wp-status-neutral-border);color:var(--wp-status-neutral-fg)}
|
|
20
|
+
.erp-timeline__line{flex:1 1 auto;width:2px;min-height:var(--wp-space-3);margin:var(--wp-space-1) 0;background:var(--wp-border-subtle);border-radius:var(--wp-radius-pill)}
|
|
21
|
+
.erp-timeline__body{flex:1 1 auto;min-width:0;padding-bottom:var(--wp-space-5)}
|
|
22
|
+
.erp-timeline__item:last-child .erp-timeline__body{padding-bottom:0}
|
|
23
|
+
.erp-timeline__head{display:flex;align-items:center;gap:var(--wp-space-2);min-height:28px}
|
|
24
|
+
.erp-timeline__title{flex:1 1 auto;min-width:0;font-size:var(--wp-size-sm);font-weight:var(--wp-weight-medium);color:var(--wp-text-strong);overflow-wrap:anywhere}
|
|
25
|
+
.erp-timeline__time{flex:none;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
26
|
+
.erp-timeline__desc{margin-top:var(--wp-space-1);font-size:var(--wp-size-sm);color:var(--wp-text-muted);overflow-wrap:anywhere}
|
|
27
|
+
.erp-timeline__payload{margin-top:var(--wp-space-1)}
|
|
28
|
+
.erp-timeline__pre{margin:0;padding:var(--wp-space-2) var(--wp-space-3);background:var(--wp-surface-sunken);border:var(--wp-border-w) solid var(--wp-border-subtle);border-radius:var(--wp-radius-sm);color:var(--wp-text-default);overflow-x:auto;white-space:pre}
|
|
29
|
+
.erp-timeline__actions{display:flex;justify-content:flex-end;align-items:center;gap:var(--wp-space-2);margin-top:var(--wp-space-2)}
|
|
30
|
+
`;
|
|
31
|
+
function TimelineStyles() {
|
|
32
|
+
return /* @__PURE__ */ jsx("style", { href: "erp-timeline", precedence: "medium", children: TIMELINE_CSS });
|
|
33
|
+
}
|
|
34
|
+
function formatPayload(payload) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.stringify(payload, null, 2) ?? String(payload);
|
|
37
|
+
} catch {
|
|
38
|
+
return String(payload);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function Timeline({ items, maxHeight }) {
|
|
42
|
+
if (items.length === 0) {
|
|
43
|
+
return /* @__PURE__ */ jsx("p", { className: "wp-caption", style: { margin: 0, padding: "var(--wp-space-2) 0" }, children: "\uD45C\uC2DC\uD560 \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." });
|
|
44
|
+
}
|
|
45
|
+
return /* @__PURE__ */ jsxs(
|
|
46
|
+
"ol",
|
|
47
|
+
{
|
|
48
|
+
className: "erp-timeline",
|
|
49
|
+
style: maxHeight !== void 0 ? { maxHeight, overflowY: "auto", paddingRight: "var(--wp-space-1)" } : void 0,
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx(TimelineStyles, {}),
|
|
52
|
+
items.map((item, index) => {
|
|
53
|
+
const meta = TYPE_META[item.type ?? "info"];
|
|
54
|
+
const isLast = index === items.length - 1;
|
|
55
|
+
const hasPayload = item.payload !== void 0 && item.payload !== null;
|
|
56
|
+
return /* @__PURE__ */ jsxs("li", { className: "erp-timeline__item", children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: "erp-timeline__rail", "aria-hidden": "true", children: [
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: `erp-timeline__dot erp-timeline__dot--${meta.tone}`, children: /* @__PURE__ */ jsx(Icon, { name: meta.icon, size: 16 }) }),
|
|
59
|
+
!isLast ? /* @__PURE__ */ jsx("span", { className: "erp-timeline__line" }) : null
|
|
60
|
+
] }),
|
|
61
|
+
/* @__PURE__ */ jsxs("div", { className: "erp-timeline__body", children: [
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "erp-timeline__head", children: [
|
|
63
|
+
/* @__PURE__ */ jsx("div", { className: "erp-timeline__title", children: item.title }),
|
|
64
|
+
item.time !== void 0 && item.time !== "" ? /* @__PURE__ */ jsx("span", { className: "wp-caption erp-timeline__time", children: item.time }) : null
|
|
65
|
+
] }),
|
|
66
|
+
item.description !== void 0 && item.description !== null ? /* @__PURE__ */ jsx("div", { className: "erp-timeline__desc", children: item.description }) : null,
|
|
67
|
+
hasPayload ? /* @__PURE__ */ jsx("div", { className: "erp-timeline__payload", children: /* @__PURE__ */ jsx(Disclosure, { summary: /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uD398\uC774\uB85C\uB4DC" }), children: /* @__PURE__ */ jsx("pre", { className: "wp-mono-xs erp-timeline__pre", children: formatPayload(item.payload) }) }) }) : null,
|
|
68
|
+
item.actions !== void 0 && item.actions !== null ? /* @__PURE__ */ jsx("div", { className: "erp-timeline__actions", children: item.actions }) : null
|
|
69
|
+
] })
|
|
70
|
+
] }, item.id);
|
|
71
|
+
})
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { Timeline };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { IconButton, Input } from './chunk-ZUCI34QY.js';
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var INLINE_EDIT_CSS = `
|
|
6
|
+
.erp-inline-edit{display:inline-flex;align-items:center;gap:var(--wp-space-1);max-width:100%;min-width:0;vertical-align:middle}
|
|
7
|
+
.erp-inline-edit__value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
8
|
+
.erp-inline-edit__pencil{opacity:0;transition:opacity var(--wp-duration-fast) var(--wp-ease-standard)}
|
|
9
|
+
.erp-inline-edit:hover .erp-inline-edit__pencil,
|
|
10
|
+
.erp-inline-edit:focus-within .erp-inline-edit__pencil{opacity:1}
|
|
11
|
+
@media (hover:none){.erp-inline-edit__pencil{opacity:1}}
|
|
12
|
+
.erp-inline-edit-editor{display:inline-flex;flex-direction:column;gap:var(--wp-space-half);max-width:100%;min-width:0;vertical-align:middle}
|
|
13
|
+
.erp-inline-edit-editor__row{display:inline-flex;align-items:center;gap:var(--wp-space-1);min-width:0}
|
|
14
|
+
.erp-inline-edit__spin svg{animation:erp-inline-edit-spin .8s linear infinite}
|
|
15
|
+
@keyframes erp-inline-edit-spin{to{transform:rotate(360deg)}}
|
|
16
|
+
`;
|
|
17
|
+
function saveErrorMessage(err) {
|
|
18
|
+
const status = err?.status;
|
|
19
|
+
if (status === 503) return "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 \uC0C1\uD0DC\uB77C \uC800\uC7A5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB370\uBAA8 \uBAA8\uB4DC)";
|
|
20
|
+
if (err instanceof Error && err.message) return err.message;
|
|
21
|
+
return "\uC800\uC7A5\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.";
|
|
22
|
+
}
|
|
23
|
+
function InlineEdit({
|
|
24
|
+
value,
|
|
25
|
+
onSave,
|
|
26
|
+
type = "text",
|
|
27
|
+
mono = false,
|
|
28
|
+
disabled = false,
|
|
29
|
+
emptyText = "\u2014"
|
|
30
|
+
}) {
|
|
31
|
+
const [editing, setEditing] = useState(false);
|
|
32
|
+
const [draft, setDraft] = useState(value);
|
|
33
|
+
const [saving, setSaving] = useState(false);
|
|
34
|
+
const [saveError, setSaveError] = useState(null);
|
|
35
|
+
const viewHostRef = useRef(null);
|
|
36
|
+
const wasEditingRef = useRef(false);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (wasEditingRef.current && !editing) {
|
|
39
|
+
viewHostRef.current?.querySelector("button")?.focus();
|
|
40
|
+
}
|
|
41
|
+
wasEditingRef.current = editing;
|
|
42
|
+
}, [editing]);
|
|
43
|
+
const startEdit = useCallback(() => {
|
|
44
|
+
setDraft(value);
|
|
45
|
+
setSaveError(null);
|
|
46
|
+
setEditing(true);
|
|
47
|
+
}, [value]);
|
|
48
|
+
const cancelEdit = useCallback(() => {
|
|
49
|
+
if (saving) return;
|
|
50
|
+
setSaveError(null);
|
|
51
|
+
setEditing(false);
|
|
52
|
+
}, [saving]);
|
|
53
|
+
const confirmEdit = useCallback(async () => {
|
|
54
|
+
if (saving) return;
|
|
55
|
+
if (draft === value) {
|
|
56
|
+
setSaveError(null);
|
|
57
|
+
setEditing(false);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setSaving(true);
|
|
61
|
+
setSaveError(null);
|
|
62
|
+
try {
|
|
63
|
+
await onSave(draft);
|
|
64
|
+
setEditing(false);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
setSaveError(saveErrorMessage(err));
|
|
67
|
+
} finally {
|
|
68
|
+
setSaving(false);
|
|
69
|
+
}
|
|
70
|
+
}, [saving, draft, value, onSave]);
|
|
71
|
+
const handleKeyDown = useCallback(
|
|
72
|
+
(e) => {
|
|
73
|
+
if (saving) return;
|
|
74
|
+
if (e.key === "Enter") {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
void confirmEdit();
|
|
77
|
+
} else if (e.key === "Escape") {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
cancelEdit();
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[saving, confirmEdit, cancelEdit]
|
|
84
|
+
);
|
|
85
|
+
const style = /* @__PURE__ */ jsx("style", { href: "erp-inline-edit", precedence: "default", children: INLINE_EDIT_CSS });
|
|
86
|
+
if (!editing) {
|
|
87
|
+
const isEmpty = value === "";
|
|
88
|
+
return /* @__PURE__ */ jsxs("span", { className: "erp-inline-edit", ref: viewHostRef, children: [
|
|
89
|
+
style,
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
"span",
|
|
92
|
+
{
|
|
93
|
+
className: "erp-inline-edit__value" + (mono ? " wp-mono" : ""),
|
|
94
|
+
style: isEmpty ? { color: "var(--wp-text-subtle)" } : void 0,
|
|
95
|
+
children: isEmpty ? emptyText : value
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
IconButton,
|
|
100
|
+
{
|
|
101
|
+
className: "erp-inline-edit__pencil",
|
|
102
|
+
icon: "pencil",
|
|
103
|
+
label: disabled ? "\uC218\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4" : "\uC218\uC815",
|
|
104
|
+
size: "xs",
|
|
105
|
+
variant: "ghost",
|
|
106
|
+
disabled,
|
|
107
|
+
onClick: startEdit
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
] });
|
|
111
|
+
}
|
|
112
|
+
return /* @__PURE__ */ jsxs("span", { className: "erp-inline-edit-editor", children: [
|
|
113
|
+
style,
|
|
114
|
+
/* @__PURE__ */ jsxs("span", { className: "erp-inline-edit-editor__row", children: [
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
Input,
|
|
117
|
+
{
|
|
118
|
+
size: "sm",
|
|
119
|
+
type,
|
|
120
|
+
mono,
|
|
121
|
+
value: draft,
|
|
122
|
+
autoFocus: true,
|
|
123
|
+
disabled: saving,
|
|
124
|
+
invalid: saveError !== null,
|
|
125
|
+
"aria-label": "\uAC12 \uC218\uC815",
|
|
126
|
+
onFocus: (e) => e.currentTarget.select(),
|
|
127
|
+
onChange: (e) => setDraft(e.target.value),
|
|
128
|
+
onKeyDown: handleKeyDown
|
|
129
|
+
}
|
|
130
|
+
),
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
IconButton,
|
|
133
|
+
{
|
|
134
|
+
className: saving ? "erp-inline-edit__spin" : "",
|
|
135
|
+
icon: saving ? "loader-circle" : "check",
|
|
136
|
+
label: saving ? "\uC800\uC7A5 \uC911\u2026" : "\uC800\uC7A5 (Enter)",
|
|
137
|
+
size: "xs",
|
|
138
|
+
variant: "ghost",
|
|
139
|
+
disabled: saving,
|
|
140
|
+
onClick: () => void confirmEdit()
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
IconButton,
|
|
145
|
+
{
|
|
146
|
+
icon: "x",
|
|
147
|
+
label: "\uCDE8\uC18C (Esc)",
|
|
148
|
+
size: "xs",
|
|
149
|
+
variant: "ghost",
|
|
150
|
+
disabled: saving,
|
|
151
|
+
onClick: cancelEdit
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
] }),
|
|
155
|
+
saveError !== null ? /* @__PURE__ */ jsx("span", { className: "wp-field__error", role: "alert", children: saveError }) : null
|
|
156
|
+
] });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { InlineEdit };
|