@blakesteve/roster 5.3.0 → 5.4.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/README.md +84 -1
- package/dist/components/molecules/MediaPreview/MediaPreview.cjs +1 -0
- package/dist/components/molecules/MediaPreview/MediaPreview.d.ts +27 -0
- package/dist/components/molecules/MediaPreview/MediaPreview.js +55 -0
- package/dist/components/organisms/Dialog/Dialog.cjs +1 -1
- package/dist/components/organisms/Dialog/Dialog.d.ts +9 -1
- package/dist/components/organisms/Dialog/Dialog.js +26 -24
- package/dist/components/organisms/ModerationQueue/ModerationQueue.cjs +1 -0
- package/dist/components/organisms/ModerationQueue/ModerationQueue.d.ts +73 -0
- package/dist/components/organisms/ModerationQueue/ModerationQueue.js +430 -0
- package/dist/components/organisms/ModerationQueue/QueueEditDialog.cjs +1 -0
- package/dist/components/organisms/ModerationQueue/QueueEditDialog.d.ts +8 -0
- package/dist/components/organisms/ModerationQueue/QueueEditDialog.js +37 -0
- package/dist/components/organisms/ModerationQueue/QueueEditSheet.cjs +1 -0
- package/dist/components/organisms/ModerationQueue/QueueEditSheet.d.ts +12 -0
- package/dist/components/organisms/ModerationQueue/QueueEditSheet.js +30 -0
- package/dist/components/organisms/ModerationQueue/queue-actions.cjs +1 -0
- package/dist/components/organisms/ModerationQueue/queue-actions.d.ts +157 -0
- package/dist/components/organisms/ModerationQueue/queue-actions.js +6 -0
- package/dist/index.d.ts +5 -0
- package/dist/meta.json +3 -3
- package/dist/roster.cjs +1 -1
- package/dist/roster.css +1 -1
- package/dist/roster.js +118 -108
- package/package.json +1 -1
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as y, jsx as c, Fragment as ye } from "react/jsx-runtime";
|
|
3
|
+
import { useId as Ke, useRef as p, useState as C, useCallback as ee, useLayoutEffect as te } from "react";
|
|
4
|
+
import { cn as H } from "../../../lib/utils.js";
|
|
5
|
+
import { Button as Se } from "../../atoms/Button/Button.js";
|
|
6
|
+
import { Card as Qe } from "../../atoms/Card/Card.js";
|
|
7
|
+
import { Spinner as ke } from "../../atoms/Spinner/Spinner.js";
|
|
8
|
+
import { Textarea as Ge } from "../../atoms/Textarea/Textarea.js";
|
|
9
|
+
import { Alert as re } from "../../molecules/Alert/Alert.js";
|
|
10
|
+
import { EmptyState as We } from "../../molecules/EmptyState/EmptyState.js";
|
|
11
|
+
import { RadioGroup as Je } from "../../molecules/RadioGroup/RadioGroup.js";
|
|
12
|
+
import { editAction as Ue } from "./queue-actions.js";
|
|
13
|
+
const be = {
|
|
14
|
+
primary: { colorScheme: "primary", variant: "solid" },
|
|
15
|
+
success: { colorScheme: "success", variant: "solid" },
|
|
16
|
+
danger: { colorScheme: "error", variant: "soft" },
|
|
17
|
+
neutral: { colorScheme: "neutral", variant: "outline" }
|
|
18
|
+
}, Ve = { edit: Ue }, I = (s) => s.edit ? s : null, Xe = (s) => s === 0 ? "None left." : `${s} left.`, Ye = (s) => Math.min(6, Math.max(1, s)), Ze = (s) => s instanceof Error && s.message ? s.message : "Something went wrong.";
|
|
19
|
+
function E(s, d) {
|
|
20
|
+
const m = { ...s };
|
|
21
|
+
return delete m[d], m;
|
|
22
|
+
}
|
|
23
|
+
function ne({
|
|
24
|
+
busy: s = !1,
|
|
25
|
+
blocked: d = !1,
|
|
26
|
+
onClick: m,
|
|
27
|
+
children: q,
|
|
28
|
+
className: b,
|
|
29
|
+
...S
|
|
30
|
+
}) {
|
|
31
|
+
const g = s || d;
|
|
32
|
+
return /* @__PURE__ */ c(
|
|
33
|
+
Se,
|
|
34
|
+
{
|
|
35
|
+
size: "lg",
|
|
36
|
+
...S,
|
|
37
|
+
"aria-disabled": g || void 0,
|
|
38
|
+
"aria-busy": s || void 0,
|
|
39
|
+
startIcon: s ? /* @__PURE__ */ c(ke, { size: "sm", variant: "current" }) : void 0,
|
|
40
|
+
className: H(d && !s && "rst:cursor-not-allowed rst:opacity-50", b),
|
|
41
|
+
onClick: (j) => {
|
|
42
|
+
if (g) {
|
|
43
|
+
j.preventDefault();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
m?.(j);
|
|
47
|
+
},
|
|
48
|
+
children: q
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function ft({
|
|
53
|
+
label: s,
|
|
54
|
+
hideLabel: d = !1,
|
|
55
|
+
headingLevel: m = 2,
|
|
56
|
+
itemHeadingLevel: q,
|
|
57
|
+
items: b,
|
|
58
|
+
getKey: S,
|
|
59
|
+
itemLabel: g,
|
|
60
|
+
renderMeta: j,
|
|
61
|
+
renderContent: qe,
|
|
62
|
+
actions: K,
|
|
63
|
+
status: se = "ready",
|
|
64
|
+
error: $e,
|
|
65
|
+
onRetry: oe,
|
|
66
|
+
empty: ce = { title: "Nothing waiting" },
|
|
67
|
+
density: ie = "comfortable",
|
|
68
|
+
footer: Ne,
|
|
69
|
+
className: we
|
|
70
|
+
}) {
|
|
71
|
+
const Ce = typeof K == "function" ? K(Ve) : K, B = Ke(), $ = p(null), N = b.map(S), M = p(/* @__PURE__ */ new Map());
|
|
72
|
+
M.current = new Map(b.map((e, t) => [N[t], e]));
|
|
73
|
+
const [Q, Ie] = C({}), w = p({}), ae = (e) => {
|
|
74
|
+
w.current = e(w.current), Ie(w.current);
|
|
75
|
+
}, [le, A] = C({}), [ue, Be] = C({}), h = p({}), v = (e, t) => {
|
|
76
|
+
h.current = t ? { ...h.current, [e]: t } : E(h.current, e), Be(h.current);
|
|
77
|
+
}, [G, Re] = C({ text: "", n: 0 }), W = p([]), R = p(null), de = ee((e) => {
|
|
78
|
+
W.current.push(e), !R.current && (R.current = setTimeout(() => {
|
|
79
|
+
R.current = null;
|
|
80
|
+
const t = W.current.join(" ");
|
|
81
|
+
W.current = [], Re((r) => ({ text: t, n: r.n + 1 }));
|
|
82
|
+
}, 0));
|
|
83
|
+
}, []);
|
|
84
|
+
te(
|
|
85
|
+
() => () => {
|
|
86
|
+
R.current && clearTimeout(R.current);
|
|
87
|
+
},
|
|
88
|
+
[]
|
|
89
|
+
);
|
|
90
|
+
const [Te, Fe] = C(0), J = p(null), k = ee((e) => {
|
|
91
|
+
J.current = e, Fe((t) => t + 1);
|
|
92
|
+
}, []), fe = ee((e) => {
|
|
93
|
+
const t = $.current;
|
|
94
|
+
if (!t) return null;
|
|
95
|
+
const r = "key" in e ? CSS.escape(e.key) : "";
|
|
96
|
+
switch (e.kind) {
|
|
97
|
+
case "item":
|
|
98
|
+
return t.querySelector(`[data-queue-item="${r}"]`);
|
|
99
|
+
case "empty":
|
|
100
|
+
return t.querySelector("[data-queue-empty]");
|
|
101
|
+
case "trigger":
|
|
102
|
+
return t.querySelector(`[data-queue-item="${r}"] [data-queue-action="${CSS.escape(e.actionId)}"]`);
|
|
103
|
+
case "step":
|
|
104
|
+
return t.querySelector(`[data-queue-item="${r}"] [data-queue-step]`);
|
|
105
|
+
}
|
|
106
|
+
}, []);
|
|
107
|
+
te(() => {
|
|
108
|
+
const e = J.current;
|
|
109
|
+
if (!e) return;
|
|
110
|
+
J.current = null, (fe(e) ?? $.current?.querySelector("[data-queue-item]") ?? $.current?.querySelector("[data-queue-empty]"))?.focus();
|
|
111
|
+
}, [Te, fe]);
|
|
112
|
+
const U = p(null), Ee = (e) => () => {
|
|
113
|
+
U.current = e;
|
|
114
|
+
}, je = (e) => {
|
|
115
|
+
e.relatedTarget && !$.current?.contains(e.relatedTarget) && (U.current = null);
|
|
116
|
+
}, O = (e) => {
|
|
117
|
+
if (U.current !== e) return !1;
|
|
118
|
+
const t = document.activeElement;
|
|
119
|
+
return !t || t === document.body ? !0 : $.current?.contains(t) ? !!t.closest(`[data-queue-item="${CSS.escape(e)}"]`) : !1;
|
|
120
|
+
}, T = p(null), P = (e, t) => {
|
|
121
|
+
T.current = t, v(e.key, { ...e, open: !1 });
|
|
122
|
+
}, me = p(N), V = p(/* @__PURE__ */ new Map()), [, Me] = C(0), X = p(/* @__PURE__ */ new Set()), pe = (e, t, r) => {
|
|
123
|
+
const o = t.indexOf(e), u = t.slice(o + 1).find((i) => r.has(i));
|
|
124
|
+
if (u) return { kind: "item", key: u };
|
|
125
|
+
const n = t.slice(0, o).reverse().find((i) => r.has(i));
|
|
126
|
+
return n ? { kind: "item", key: n } : { kind: "empty" };
|
|
127
|
+
};
|
|
128
|
+
te(() => {
|
|
129
|
+
const e = me.current;
|
|
130
|
+
me.current = N;
|
|
131
|
+
const t = new Set(N), r = V.current;
|
|
132
|
+
V.current = /* @__PURE__ */ new Map();
|
|
133
|
+
const o = [];
|
|
134
|
+
let u = !1;
|
|
135
|
+
for (const [n, i] of r) {
|
|
136
|
+
const a = t.has(n);
|
|
137
|
+
o.push(i.announce ?? i.sentence), a || (u = !0), X.current.delete(n);
|
|
138
|
+
const l = a ? { kind: "trigger", key: n, actionId: i.actionId } : e.includes(n) ? pe(n, e, t) : null, f = h.current[n];
|
|
139
|
+
if (f?.surface)
|
|
140
|
+
P(f, l ?? { kind: "empty" });
|
|
141
|
+
else {
|
|
142
|
+
const F = O(n);
|
|
143
|
+
f && v(n, null), l && F && k(l);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const n of e.filter((i) => !t.has(i))) {
|
|
147
|
+
if (r.has(n)) continue;
|
|
148
|
+
A((f) => n in f ? E(f, n) : f);
|
|
149
|
+
const i = pe(n, e, t), a = T.current;
|
|
150
|
+
if (a && "key" in a && a.key === n && (T.current = i), w.current[n]) {
|
|
151
|
+
X.current.add(n), O(n) && k(i);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const l = h.current[n];
|
|
155
|
+
if (l && !(l.surface && !l.open))
|
|
156
|
+
if (o.push(`${l.label} was decided elsewhere.`), u = !0, l.surface) P(l, i);
|
|
157
|
+
else {
|
|
158
|
+
const f = O(n);
|
|
159
|
+
v(n, null), f && k(i);
|
|
160
|
+
}
|
|
161
|
+
else !l && O(n) && k(i);
|
|
162
|
+
}
|
|
163
|
+
o.length && de(`${o.join(" ")}${u ? ` ${Xe(N.length)}` : ""}`);
|
|
164
|
+
});
|
|
165
|
+
const z = async (e, t, r, o) => {
|
|
166
|
+
const u = M.current.get(e) ?? t, n = g(u), i = r.edit ? I(r).run : r.run;
|
|
167
|
+
ae((a) => ({ ...a, [e]: r.id })), A((a) => E(a, e));
|
|
168
|
+
try {
|
|
169
|
+
const a = await i(u, o);
|
|
170
|
+
V.current.set(e, {
|
|
171
|
+
actionId: r.id,
|
|
172
|
+
sentence: `${r.done ?? r.label}: ${n}.`,
|
|
173
|
+
announce: a ? a.announce : void 0
|
|
174
|
+
}), Me((l) => l + 1);
|
|
175
|
+
} catch (a) {
|
|
176
|
+
const l = Ze(a);
|
|
177
|
+
de(`${r.label} didn't go through for ${n}. ${l}`);
|
|
178
|
+
const f = h.current[e];
|
|
179
|
+
X.current.delete(e) ? f?.surface ? P(f, { kind: "empty" }) : f && v(e, null) : f ? v(e, { ...f, error: l }) : A((F) => ({ ...F, [e]: l }));
|
|
180
|
+
} finally {
|
|
181
|
+
ae((a) => E(a, e));
|
|
182
|
+
}
|
|
183
|
+
}, Ae = (e, t) => {
|
|
184
|
+
const r = S(e), o = I(t);
|
|
185
|
+
if (!t.confirm && !t.reason && !o) {
|
|
186
|
+
z(r, e, t, {});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
A((u) => E(u, r)), v(r, {
|
|
190
|
+
key: r,
|
|
191
|
+
label: g(e),
|
|
192
|
+
item: e,
|
|
193
|
+
action: t,
|
|
194
|
+
draft: o ? o.edit.initial(e) : void 0,
|
|
195
|
+
reason: {},
|
|
196
|
+
surface: !!o?.edit.surface,
|
|
197
|
+
open: !0
|
|
198
|
+
}), o?.edit.surface || k({ kind: "step", key: r });
|
|
199
|
+
}, Y = (e) => {
|
|
200
|
+
const t = h.current[e];
|
|
201
|
+
if (!t || w.current[e]) return;
|
|
202
|
+
const r = { kind: "trigger", key: e, actionId: t.action.id };
|
|
203
|
+
t.surface ? P(t, r) : (v(e, null), k(r));
|
|
204
|
+
}, Oe = (e) => {
|
|
205
|
+
const t = h.current[e];
|
|
206
|
+
!t || w.current[e] || xe(t, M.current.get(e) ?? t.item) || (t.action.edit ? z(e, t.item, t.action, { draft: t.draft }) : t.action.reason ? z(e, t.item, t.action, { reason: _e(t.reason) }) : z(e, t.item, t.action, {}));
|
|
207
|
+
}, Pe = (e) => {
|
|
208
|
+
const t = T.current;
|
|
209
|
+
T.current = null, v(e, null), t && k(t);
|
|
210
|
+
}, ze = `h${m}`, ge = q ?? Ye(m + 1), Le = `h${ge}`, Z = `${B}-label`, he = (e, t) => {
|
|
211
|
+
const { action: r } = e, o = I(r), u = M.current.get(e.key) ?? e.item, n = !!Q[e.key], i = xe(e, u), a = `${B}-step-${t}-message`, l = /* @__PURE__ */ c(
|
|
212
|
+
ne,
|
|
213
|
+
{
|
|
214
|
+
...be[r.tone ?? "neutral"],
|
|
215
|
+
busy: n,
|
|
216
|
+
blocked: !!i,
|
|
217
|
+
"aria-describedby": i ? a : void 0,
|
|
218
|
+
onClick: () => Oe(e.key),
|
|
219
|
+
"data-queue-submit": "",
|
|
220
|
+
children: o ? o.edit.submitLabel : r.label
|
|
221
|
+
}
|
|
222
|
+
), f = /* @__PURE__ */ c(ne, { variant: "ghost", colorScheme: "neutral", blocked: n, onClick: () => Y(e.key), children: "Cancel" }), F = /* @__PURE__ */ y(ye, { children: [
|
|
223
|
+
r.reason && /* @__PURE__ */ c(
|
|
224
|
+
et,
|
|
225
|
+
{
|
|
226
|
+
id: `${B}-step-${t}`,
|
|
227
|
+
step: r.reason,
|
|
228
|
+
value: e.reason,
|
|
229
|
+
disabled: n,
|
|
230
|
+
onChange: (_) => v(e.key, { ...h.current[e.key], reason: _ })
|
|
231
|
+
}
|
|
232
|
+
),
|
|
233
|
+
o && /* @__PURE__ */ c(
|
|
234
|
+
o.edit.Form,
|
|
235
|
+
{
|
|
236
|
+
item: u,
|
|
237
|
+
value: e.draft,
|
|
238
|
+
onChange: (_) => v(e.key, { ...h.current[e.key], draft: _ })
|
|
239
|
+
}
|
|
240
|
+
),
|
|
241
|
+
i && /* @__PURE__ */ c("p", { id: a, className: "rst:text-sm rst:text-error-600 rst:dark:text-error-400", "data-queue-validation": "", children: i }),
|
|
242
|
+
e.error && /* @__PURE__ */ c(re, { colorScheme: "error", role: "group", "aria-live": "off", children: e.error })
|
|
243
|
+
] });
|
|
244
|
+
return { submitButton: l, cancelButton: f, fields: F };
|
|
245
|
+
}, De = (e, t) => {
|
|
246
|
+
const { submitButton: r, cancelButton: o, fields: u } = he(e, t), n = `${B}-step-${t}-prompt`, i = I(e.action), a = e.action.confirm?.prompt ?? e.action.reason?.prompt ?? i?.edit.title;
|
|
247
|
+
return /* @__PURE__ */ y(
|
|
248
|
+
"div",
|
|
249
|
+
{
|
|
250
|
+
role: "group",
|
|
251
|
+
"aria-labelledby": n,
|
|
252
|
+
tabIndex: -1,
|
|
253
|
+
"data-queue-step": "",
|
|
254
|
+
onKeyDown: (l) => {
|
|
255
|
+
l.key === "Escape" && (l.stopPropagation(), Y(e.key));
|
|
256
|
+
},
|
|
257
|
+
className: "rst:flex rst:flex-col rst:gap-3 rst:rounded-lg rst:border rst:border-[var(--roster-card-border)] rst:p-4 rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring",
|
|
258
|
+
children: [
|
|
259
|
+
/* @__PURE__ */ c("p", { id: n, className: "rst:text-sm rst:font-medium rst:text-[var(--roster-control-text)]", children: a }),
|
|
260
|
+
i?.edit.description && /* @__PURE__ */ c("p", { className: "rst:text-sm rst:text-gray-600 rst:dark:text-gray-300", children: i.edit.description }),
|
|
261
|
+
u,
|
|
262
|
+
/* @__PURE__ */ y("div", { className: "rst:flex rst:flex-wrap rst:gap-2", children: [
|
|
263
|
+
r,
|
|
264
|
+
o
|
|
265
|
+
] })
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
}, He = (e, t) => {
|
|
270
|
+
const r = Q[t];
|
|
271
|
+
return /* @__PURE__ */ c("div", { role: "group", "aria-label": `Decide: ${g(e)}`, className: "rst:flex rst:flex-wrap rst:gap-2", children: Ce.filter((o) => !o.hidden?.(e)).map((o) => /* @__PURE__ */ c(
|
|
272
|
+
ne,
|
|
273
|
+
{
|
|
274
|
+
...be[o.tone ?? "neutral"],
|
|
275
|
+
"data-queue-action": o.id,
|
|
276
|
+
busy: r === o.id,
|
|
277
|
+
blocked: !!r && r !== o.id,
|
|
278
|
+
onClick: () => Ae(e, o),
|
|
279
|
+
children: o.label
|
|
280
|
+
},
|
|
281
|
+
o.id
|
|
282
|
+
)) });
|
|
283
|
+
}, x = Object.values(ue).find((e) => e.surface) ?? null, L = x ? I(x.action) : null, ve = L?.edit.surface, D = x ? he(x, -1) : null;
|
|
284
|
+
return /* @__PURE__ */ y(
|
|
285
|
+
"section",
|
|
286
|
+
{
|
|
287
|
+
ref: $,
|
|
288
|
+
"aria-labelledby": Z,
|
|
289
|
+
onBlur: je,
|
|
290
|
+
className: H("rst:font-ui rst:flex rst:flex-col rst:gap-4", we),
|
|
291
|
+
children: [
|
|
292
|
+
/* @__PURE__ */ c(
|
|
293
|
+
ze,
|
|
294
|
+
{
|
|
295
|
+
id: Z,
|
|
296
|
+
className: H(
|
|
297
|
+
"rst:text-lg rst:font-semibold rst:text-[var(--roster-control-text)]",
|
|
298
|
+
d && "rst:sr-only"
|
|
299
|
+
),
|
|
300
|
+
children: s
|
|
301
|
+
}
|
|
302
|
+
),
|
|
303
|
+
/* @__PURE__ */ y(
|
|
304
|
+
"div",
|
|
305
|
+
{
|
|
306
|
+
role: "status",
|
|
307
|
+
"aria-live": "polite",
|
|
308
|
+
"aria-atomic": "true",
|
|
309
|
+
className: "rst:sr-only",
|
|
310
|
+
"data-queue-live": "",
|
|
311
|
+
"data-queue-live-n": G.n,
|
|
312
|
+
children: [
|
|
313
|
+
G.text,
|
|
314
|
+
G.n % 2 ? " " : ""
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
),
|
|
318
|
+
se === "loading" ? /* @__PURE__ */ c("div", { className: "rst:flex rst:justify-center rst:py-12", "aria-busy": "true", children: /* @__PURE__ */ c(ke, { size: "lg", label: `Loading ${s}` }) }) : se === "error" ? /* @__PURE__ */ c(re, { colorScheme: "error", children: /* @__PURE__ */ y("div", { className: "rst:flex rst:flex-col rst:items-start rst:gap-3", children: [
|
|
319
|
+
/* @__PURE__ */ c("div", { children: $e ?? "The queue couldn't load." }),
|
|
320
|
+
oe && /* @__PURE__ */ c(Se, { size: "lg", variant: "outline", colorScheme: "neutral", onClick: oe, children: "Retry" })
|
|
321
|
+
] }) }) : b.length === 0 ? /* @__PURE__ */ c(
|
|
322
|
+
We,
|
|
323
|
+
{
|
|
324
|
+
title: ce.title,
|
|
325
|
+
description: ce.description,
|
|
326
|
+
headingLevel: ge,
|
|
327
|
+
tabIndex: -1,
|
|
328
|
+
"data-queue-empty": "",
|
|
329
|
+
className: "rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring"
|
|
330
|
+
}
|
|
331
|
+
) : (
|
|
332
|
+
/* `role="list"` is restated because Safari drops a list's role once
|
|
333
|
+
its markers are styled away. */
|
|
334
|
+
/* @__PURE__ */ c(
|
|
335
|
+
"ul",
|
|
336
|
+
{
|
|
337
|
+
role: "list",
|
|
338
|
+
"aria-labelledby": Z,
|
|
339
|
+
className: "rst:m-0 rst:flex rst:list-none rst:flex-col rst:gap-3 rst:p-0",
|
|
340
|
+
children: b.map((e, t) => {
|
|
341
|
+
const r = N[t], o = `${B}-item-${t}`, u = j?.(e), n = ue[r];
|
|
342
|
+
return /* @__PURE__ */ c("li", { children: /* @__PURE__ */ c(
|
|
343
|
+
"article",
|
|
344
|
+
{
|
|
345
|
+
"data-queue-item": r,
|
|
346
|
+
tabIndex: -1,
|
|
347
|
+
"aria-labelledby": o,
|
|
348
|
+
onFocus: Ee(r),
|
|
349
|
+
className: "rst:rounded-xl rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-offset-2 rst:ring-offset-background",
|
|
350
|
+
children: /* @__PURE__ */ c(Qe, { padding: ie === "compact" ? "sm" : "md", children: /* @__PURE__ */ y("div", { className: H("rst:flex rst:flex-col", ie === "compact" ? "rst:gap-3" : "rst:gap-4"), children: [
|
|
351
|
+
/* @__PURE__ */ y("div", { className: "rst:flex rst:flex-wrap rst:items-baseline rst:gap-x-3 rst:gap-y-1", children: [
|
|
352
|
+
/* @__PURE__ */ c(Le, { id: o, className: "rst:text-base rst:font-semibold", children: g(e) }),
|
|
353
|
+
u && /* @__PURE__ */ c("div", { className: "rst:flex rst:flex-wrap rst:items-center rst:gap-2 rst:text-sm rst:text-gray-600 rst:dark:text-gray-300", children: u })
|
|
354
|
+
] }),
|
|
355
|
+
/* @__PURE__ */ c("div", { className: "rst:min-w-0", children: qe(e) }),
|
|
356
|
+
le[r] && /* @__PURE__ */ c(re, { colorScheme: "error", role: "group", "aria-live": "off", children: le[r] }),
|
|
357
|
+
n && !n.surface ? De(n, t) : He(e, r)
|
|
358
|
+
] }) })
|
|
359
|
+
}
|
|
360
|
+
) }, r);
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
),
|
|
365
|
+
Ne,
|
|
366
|
+
ve && x && L && D && /* @__PURE__ */ c(
|
|
367
|
+
ve,
|
|
368
|
+
{
|
|
369
|
+
open: x.open,
|
|
370
|
+
title: L.edit.title,
|
|
371
|
+
description: L.edit.description,
|
|
372
|
+
busy: !!Q[x.key],
|
|
373
|
+
onCancel: () => Y(x.key),
|
|
374
|
+
onAfterClose: () => Pe(x.key),
|
|
375
|
+
submitButton: D.submitButton,
|
|
376
|
+
cancelButton: D.cancelButton,
|
|
377
|
+
children: /* @__PURE__ */ c("div", { className: "rst:flex rst:flex-col rst:gap-4", children: D.fields })
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
]
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
function xe(s, d) {
|
|
385
|
+
const m = I(s.action);
|
|
386
|
+
if (m) return m.edit.validate?.(s.draft, d);
|
|
387
|
+
if (s.action.reason) {
|
|
388
|
+
if (s.action.reason.required && !s.reason.value) return "Pick a reason.";
|
|
389
|
+
if (s.action.reason.note === "required" && !s.reason.note?.trim()) return "Add a note.";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function _e(s) {
|
|
393
|
+
const d = s.note?.trim();
|
|
394
|
+
return { ...s.value ? { value: s.value } : {}, ...d ? { note: d } : {} };
|
|
395
|
+
}
|
|
396
|
+
function et({
|
|
397
|
+
id: s,
|
|
398
|
+
step: d,
|
|
399
|
+
value: m,
|
|
400
|
+
disabled: q,
|
|
401
|
+
onChange: b
|
|
402
|
+
}) {
|
|
403
|
+
const S = d.note ?? "optional";
|
|
404
|
+
return /* @__PURE__ */ y(ye, { children: [
|
|
405
|
+
d.options && d.options.length > 0 && /* @__PURE__ */ c(
|
|
406
|
+
Je,
|
|
407
|
+
{
|
|
408
|
+
label: d.required ? "Reason" : "Reason (optional)",
|
|
409
|
+
options: d.options,
|
|
410
|
+
value: m.value ?? "",
|
|
411
|
+
onChange: (g) => b({ ...m, value: g }),
|
|
412
|
+
disabled: q
|
|
413
|
+
}
|
|
414
|
+
),
|
|
415
|
+
S !== "none" && /* @__PURE__ */ c(
|
|
416
|
+
Ge,
|
|
417
|
+
{
|
|
418
|
+
id: `${s}-note`,
|
|
419
|
+
label: d.noteLabel ?? (S === "required" ? "Note" : "Note (optional)"),
|
|
420
|
+
rows: 3,
|
|
421
|
+
value: m.note ?? "",
|
|
422
|
+
onChange: (g) => b({ ...m, note: g.target.value }),
|
|
423
|
+
disabled: q
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
] });
|
|
427
|
+
}
|
|
428
|
+
export {
|
|
429
|
+
ft as ModerationQueue
|
|
430
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),c=require("../Dialog/Dialog.cjs");function d({open:s,title:i,description:t,busy:r,onCancel:l,onAfterClose:n,children:o,submitButton:u,cancelButton:a}){return e.jsx(c.Dialog,{isOpen:s,onClose:r?()=>{}:l,title:i,description:t,size:"lg",onAfterClose:n,children:e.jsxs("div",{className:"rst:flex rst:flex-col rst:gap-6",children:[o,e.jsxs("div",{className:"rst:flex rst:flex-wrap rst:justify-end rst:gap-2",children:[a,u]})]})})}exports.QueueEditDialog=d;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { QueueSurfaceProps } from './queue-actions';
|
|
2
|
+
/**
|
|
3
|
+
* Shows a queue's edit step in a Dialog, for a form with more than a field or
|
|
4
|
+
* two. Pass it as the edit's `surface`. A separate export, so a queue that
|
|
5
|
+
* never edits in a Dialog never ships one.
|
|
6
|
+
*/
|
|
7
|
+
declare function QueueEditDialog({ open, title, description, busy, onCancel, onAfterClose, children, submitButton, cancelButton, }: QueueSurfaceProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export { QueueEditDialog };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as f, jsxs as e } from "react/jsx-runtime";
|
|
3
|
+
import { Dialog as d } from "../Dialog/Dialog.js";
|
|
4
|
+
function m({
|
|
5
|
+
open: s,
|
|
6
|
+
title: r,
|
|
7
|
+
description: i,
|
|
8
|
+
busy: l,
|
|
9
|
+
onCancel: t,
|
|
10
|
+
onAfterClose: o,
|
|
11
|
+
children: n,
|
|
12
|
+
submitButton: a,
|
|
13
|
+
cancelButton: c
|
|
14
|
+
}) {
|
|
15
|
+
return /* @__PURE__ */ f(
|
|
16
|
+
d,
|
|
17
|
+
{
|
|
18
|
+
isOpen: s,
|
|
19
|
+
onClose: l ? () => {
|
|
20
|
+
} : t,
|
|
21
|
+
title: r,
|
|
22
|
+
description: i,
|
|
23
|
+
size: "lg",
|
|
24
|
+
onAfterClose: o,
|
|
25
|
+
children: /* @__PURE__ */ e("div", { className: "rst:flex rst:flex-col rst:gap-6", children: [
|
|
26
|
+
n,
|
|
27
|
+
/* @__PURE__ */ e("div", { className: "rst:flex rst:flex-wrap rst:justify-end rst:gap-2", children: [
|
|
28
|
+
c,
|
|
29
|
+
a
|
|
30
|
+
] })
|
|
31
|
+
] })
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
m as QueueEditDialog
|
|
37
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react/jsx-runtime"),S=require("../Sheet/Sheet.cjs");function l({open:e,title:t,description:u,busy:i,onCancel:n,onAfterClose:o,children:r,submitButton:s}){return c.jsx(S.Sheet,{isOpen:e,onClose:i?()=>{}:n,title:t,description:u,actions:s,onAfterClose:o,children:r})}exports.QueueEditSheet=l;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { QueueSurfaceProps } from './queue-actions';
|
|
2
|
+
/**
|
|
3
|
+
* Shows a queue's edit step in a Sheet, for a long form that wants the room
|
|
4
|
+
* on a phone. Pass it as the edit's `surface`. A separate export, so a queue
|
|
5
|
+
* that never edits in a Sheet never ships one.
|
|
6
|
+
*
|
|
7
|
+
* The submit button goes in the Sheet's header, where its actions sit, and
|
|
8
|
+
* Cancel is the Sheet's own close button, so there's one way out rather than
|
|
9
|
+
* two side by side.
|
|
10
|
+
*/
|
|
11
|
+
declare function QueueEditSheet({ open, title, description, busy, onCancel, onAfterClose, children, submitButton, }: QueueSurfaceProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export { QueueEditSheet };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as m } from "react/jsx-runtime";
|
|
3
|
+
import { Sheet as p } from "../Sheet/Sheet.js";
|
|
4
|
+
function h({
|
|
5
|
+
open: e,
|
|
6
|
+
title: t,
|
|
7
|
+
description: o,
|
|
8
|
+
busy: i,
|
|
9
|
+
onCancel: n,
|
|
10
|
+
onAfterClose: r,
|
|
11
|
+
children: s,
|
|
12
|
+
submitButton: u
|
|
13
|
+
}) {
|
|
14
|
+
return /* @__PURE__ */ m(
|
|
15
|
+
p,
|
|
16
|
+
{
|
|
17
|
+
isOpen: e,
|
|
18
|
+
onClose: i ? () => {
|
|
19
|
+
} : n,
|
|
20
|
+
title: t,
|
|
21
|
+
description: o,
|
|
22
|
+
actions: u,
|
|
23
|
+
onAfterClose: r,
|
|
24
|
+
children: s
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
h as QueueEditSheet
|
|
30
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function e(t){return t}exports.editAction=e;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* What an action's `run` may resolve with. Nothing, for the usual case; or a
|
|
4
|
+
* sentence that replaces the default announcement, for an outcome that isn't
|
|
5
|
+
* the decision itself, such as another reviewer having decided first.
|
|
6
|
+
*/
|
|
7
|
+
export type QueueRunResult = void | {
|
|
8
|
+
announce: string;
|
|
9
|
+
};
|
|
10
|
+
export type QueueTone = "primary" | "success" | "danger" | "neutral";
|
|
11
|
+
export type QueueHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
12
|
+
interface QueueActionBase<T> {
|
|
13
|
+
/** Unique among the queue's actions. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** The button's words, and the submit button's in its step: "Approve". */
|
|
16
|
+
label: string;
|
|
17
|
+
/** What the live region says when it lands: "Approved". Defaults to `label`. */
|
|
18
|
+
done?: string;
|
|
19
|
+
tone?: QueueTone;
|
|
20
|
+
/** Leave the action off this item, for instance no Edit on a kind that can't be edited. */
|
|
21
|
+
hidden?: (item: T) => boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface QueueReason {
|
|
24
|
+
/** The chosen option's `value`, when the step offers options. */
|
|
25
|
+
value?: string;
|
|
26
|
+
/** The note, when the step takes one. Trimmed; absent when empty. */
|
|
27
|
+
note?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface QueueReasonStep {
|
|
30
|
+
/** The question the step asks, and its accessible name. */
|
|
31
|
+
prompt: string;
|
|
32
|
+
/** A short list, shown as radios. */
|
|
33
|
+
options?: {
|
|
34
|
+
value: string;
|
|
35
|
+
label: string;
|
|
36
|
+
}[];
|
|
37
|
+
/** An option must be picked before submitting. */
|
|
38
|
+
required?: boolean;
|
|
39
|
+
/** Whether a free-text note is offered, and whether it's required. */
|
|
40
|
+
note?: "none" | "optional" | "required";
|
|
41
|
+
/** The note field's label. */
|
|
42
|
+
noteLabel?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Every action that isn't an edit: one click, or a step that asks first.
|
|
46
|
+
*
|
|
47
|
+
* - Neither `confirm` nor `reason`: the button runs the action.
|
|
48
|
+
* - `confirm`: asks in place, "Remove this post?", with the action and
|
|
49
|
+
* Cancel.
|
|
50
|
+
* - `reason`: asks why in place (options, a note, or both), and hands the
|
|
51
|
+
* answer to `run`. Takes precedence over `confirm`; its submit button is
|
|
52
|
+
* the confirmation.
|
|
53
|
+
*
|
|
54
|
+
* One type rather than three, so an action written as an object literal gets
|
|
55
|
+
* its parameters typed: TypeScript can't tell three shapes apart by whether
|
|
56
|
+
* an object property is present, and would leave `run` untyped.
|
|
57
|
+
*/
|
|
58
|
+
export interface QueuePlainAction<T> extends QueueActionBase<T> {
|
|
59
|
+
confirm?: {
|
|
60
|
+
prompt: string;
|
|
61
|
+
};
|
|
62
|
+
reason?: QueueReasonStep;
|
|
63
|
+
edit?: undefined;
|
|
64
|
+
/** `reason` is set when the action has a reason step. */
|
|
65
|
+
run: (item: T, input: {
|
|
66
|
+
reason?: QueueReason;
|
|
67
|
+
}) => Promise<QueueRunResult>;
|
|
68
|
+
}
|
|
69
|
+
/** What an edit step's form is handed. Controlled: the draft lives in the queue. */
|
|
70
|
+
export interface QueueFormProps<T, D> {
|
|
71
|
+
item: T;
|
|
72
|
+
value: D;
|
|
73
|
+
onChange: (next: D) => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* What a surface (a Dialog, a Sheet) is handed to show an edit step. The queue
|
|
77
|
+
* renders the form, its validation message and both buttons; a surface only
|
|
78
|
+
* places them.
|
|
79
|
+
*/
|
|
80
|
+
export interface QueueSurfaceProps {
|
|
81
|
+
open: boolean;
|
|
82
|
+
title: string;
|
|
83
|
+
description?: string;
|
|
84
|
+
/** A decision is in flight: the surface shouldn't close on its own. */
|
|
85
|
+
busy: boolean;
|
|
86
|
+
/** Escape, the close button, the backdrop. */
|
|
87
|
+
onCancel: () => void;
|
|
88
|
+
/** Once the surface has finished closing. */
|
|
89
|
+
onAfterClose: () => void;
|
|
90
|
+
/** The app's form, the validation message and any error. */
|
|
91
|
+
children: ReactNode;
|
|
92
|
+
submitButton: ReactNode;
|
|
93
|
+
cancelButton: ReactNode;
|
|
94
|
+
}
|
|
95
|
+
export type QueueSurface = ComponentType<QueueSurfaceProps>;
|
|
96
|
+
export interface QueueEdit<T, D> {
|
|
97
|
+
title: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
/** "Approve with edits", "Publish". */
|
|
100
|
+
submitLabel: string;
|
|
101
|
+
/** Inline, in the item, by default. Or `QueueEditDialog`, `QueueEditSheet`. */
|
|
102
|
+
surface?: QueueSurface;
|
|
103
|
+
/** The draft the form starts from, made each time the step opens. */
|
|
104
|
+
initial: (item: T) => D;
|
|
105
|
+
Form: ComponentType<QueueFormProps<T, D>>;
|
|
106
|
+
/** A message means the draft can't be submitted yet, and says why. */
|
|
107
|
+
validate?: (value: D, item: T) => string | undefined;
|
|
108
|
+
}
|
|
109
|
+
export interface QueueEditAction<T, D> extends QueueActionBase<T> {
|
|
110
|
+
edit: QueueEdit<T, D>;
|
|
111
|
+
confirm?: undefined;
|
|
112
|
+
reason?: undefined;
|
|
113
|
+
run: (item: T, input: {
|
|
114
|
+
draft: D;
|
|
115
|
+
}) => Promise<QueueRunResult>;
|
|
116
|
+
}
|
|
117
|
+
declare const typedEdit: unique symbol;
|
|
118
|
+
/**
|
|
119
|
+
* An edit action whose draft type was checked: made by `editAction` (or the
|
|
120
|
+
* `edit` an `actions` function is handed), and the only edit action `actions`
|
|
121
|
+
* accepts.
|
|
122
|
+
*
|
|
123
|
+
* Opaque on purpose. It names what a queue shows, the step's title and
|
|
124
|
+
* buttons, and hides what touches the draft: inside a list of mixed actions a
|
|
125
|
+
* draft could only be typed `any`, so `initial`, `Form`, `validate` and `run`
|
|
126
|
+
* are reachable only where `editAction` checked them against one another. A
|
|
127
|
+
* literal `{ edit: … }`, or a spread of a checked action with a new
|
|
128
|
+
* `initial`, doesn't compile.
|
|
129
|
+
*/
|
|
130
|
+
export interface QueueCheckedEditAction<T> extends QueueActionBase<T> {
|
|
131
|
+
readonly edit: Pick<QueueEdit<T, never>, "title" | "description" | "submitLabel" | "surface">;
|
|
132
|
+
readonly [typedEdit]: true;
|
|
133
|
+
confirm?: undefined;
|
|
134
|
+
reason?: undefined;
|
|
135
|
+
}
|
|
136
|
+
export type QueueAction<T> = QueuePlainAction<T> | QueueCheckedEditAction<T>;
|
|
137
|
+
/**
|
|
138
|
+
* What a function passed as `actions` is handed: the same `editAction`, bound
|
|
139
|
+
* to the queue's item type. Inside JSX that's the form to use, because the
|
|
140
|
+
* queue's item type isn't known yet when a free-standing `editAction(…)` in
|
|
141
|
+
* an array is checked, and its parameters would come out `unknown`.
|
|
142
|
+
*/
|
|
143
|
+
export interface QueueActionKit<T> {
|
|
144
|
+
edit: <D>(action: QueueEditAction<T, D>) => QueueCheckedEditAction<T>;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Makes an edit action, with its draft type checked end to end: `initial`
|
|
148
|
+
* says what a draft is, and the form, `validate` and `run` all get that type.
|
|
149
|
+
*
|
|
150
|
+
* editAction({ edit: { initial: (s) => ({ body: s.body }), … },
|
|
151
|
+
* run: (s, { draft }) => save(s.id, draft.body) })
|
|
152
|
+
*
|
|
153
|
+
* The other kinds of action are plain objects; this one goes through a
|
|
154
|
+
* function because TypeScript can only infer the draft's type from a call.
|
|
155
|
+
*/
|
|
156
|
+
export declare function editAction<T, D>(action: QueueEditAction<T, D>): QueueCheckedEditAction<T>;
|
|
157
|
+
export {};
|