@burdenoff/microfe-theculturepost 2026.704.2 → 2026.705.2

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.
Files changed (91) hide show
  1. package/dist/theculturepost/TheCulturePostRoot.js +63 -45
  2. package/dist/theculturepost/TheCulturePostRoot.js.map +1 -1
  3. package/dist/theculturepost/TheCulturePostRoutes.js +17 -9
  4. package/dist/theculturepost/TheCulturePostRoutes.js.map +1 -1
  5. package/dist/theculturepost/components/AddCitationDialog.js +131 -102
  6. package/dist/theculturepost/components/AddCitationDialog.js.map +1 -1
  7. package/dist/theculturepost/components/ArchiveItemCard.js +56 -43
  8. package/dist/theculturepost/components/ArchiveItemCard.js.map +1 -1
  9. package/dist/theculturepost/components/AudioPlayer.js +74 -0
  10. package/dist/theculturepost/components/AudioPlayer.js.map +1 -0
  11. package/dist/theculturepost/components/CitationList.js +215 -105
  12. package/dist/theculturepost/components/CitationList.js.map +1 -1
  13. package/dist/theculturepost/components/CollectionCard.js +32 -32
  14. package/dist/theculturepost/components/CollectionCard.js.map +1 -1
  15. package/dist/theculturepost/components/CreateArchiveItemDialog.js +133 -93
  16. package/dist/theculturepost/components/CreateArchiveItemDialog.js.map +1 -1
  17. package/dist/theculturepost/components/CreateCollectionDialog.js +91 -70
  18. package/dist/theculturepost/components/CreateCollectionDialog.js.map +1 -1
  19. package/dist/theculturepost/components/CreateStoryDialog.js +311 -132
  20. package/dist/theculturepost/components/CreateStoryDialog.js.map +1 -1
  21. package/dist/theculturepost/components/DecorativeCover.js +78 -0
  22. package/dist/theculturepost/components/DecorativeCover.js.map +1 -0
  23. package/dist/theculturepost/components/DecorativeDivider.js +45 -0
  24. package/dist/theculturepost/components/DecorativeDivider.js.map +1 -0
  25. package/dist/theculturepost/components/DiscussionCard.js +75 -60
  26. package/dist/theculturepost/components/DiscussionCard.js.map +1 -1
  27. package/dist/theculturepost/components/HistoryTimeline.js +115 -0
  28. package/dist/theculturepost/components/HistoryTimeline.js.map +1 -0
  29. package/dist/theculturepost/components/LifecycleStepper.js +33 -0
  30. package/dist/theculturepost/components/LifecycleStepper.js.map +1 -0
  31. package/dist/theculturepost/components/OpenDiscussionDialog.js +66 -56
  32. package/dist/theculturepost/components/OpenDiscussionDialog.js.map +1 -1
  33. package/dist/theculturepost/components/OralHistoryCard.js +6 -73
  34. package/dist/theculturepost/components/OverlayBadge.js +22 -0
  35. package/dist/theculturepost/components/OverlayBadge.js.map +1 -0
  36. package/dist/theculturepost/components/PageHero.js +62 -0
  37. package/dist/theculturepost/components/PageHero.js.map +1 -0
  38. package/dist/theculturepost/components/RecordOralHistoryDialog.js +306 -131
  39. package/dist/theculturepost/components/RecordOralHistoryDialog.js.map +1 -1
  40. package/dist/theculturepost/components/ReplyThread.js +268 -139
  41. package/dist/theculturepost/components/ReplyThread.js.map +1 -1
  42. package/dist/theculturepost/components/StatusBadge.js +9 -9
  43. package/dist/theculturepost/components/StatusBadge.js.map +1 -1
  44. package/dist/theculturepost/components/StoryCard.js +54 -49
  45. package/dist/theculturepost/components/StoryCard.js.map +1 -1
  46. package/dist/theculturepost/components/TopicCard.js +60 -41
  47. package/dist/theculturepost/components/TopicCard.js.map +1 -1
  48. package/dist/theculturepost/components/Waveform.js +31 -0
  49. package/dist/theculturepost/components/Waveform.js.map +1 -0
  50. package/dist/theculturepost/components/categoryMeta.js +20 -0
  51. package/dist/theculturepost/components/categoryMeta.js.map +1 -0
  52. package/dist/theculturepost/components/index.js +41 -0
  53. package/dist/theculturepost/components/viz/BarChart.js +53 -0
  54. package/dist/theculturepost/components/viz/BarChart.js.map +1 -0
  55. package/dist/theculturepost/components/viz/Donut.js +58 -0
  56. package/dist/theculturepost/components/viz/Donut.js.map +1 -0
  57. package/dist/theculturepost/components/viz/StatTile.js +35 -0
  58. package/dist/theculturepost/components/viz/StatTile.js.map +1 -0
  59. package/dist/theculturepost/components/viz/index.js +3 -0
  60. package/dist/theculturepost/hooks/useFiles.js +1 -1
  61. package/dist/theculturepost/hooks/useFiles.js.map +1 -1
  62. package/dist/theculturepost/pages/HomePage.js +247 -124
  63. package/dist/theculturepost/pages/HomePage.js.map +1 -1
  64. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js +319 -127
  65. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js.map +1 -1
  66. package/dist/theculturepost/pages/archive/ArchivePage.js +265 -180
  67. package/dist/theculturepost/pages/archive/ArchivePage.js.map +1 -1
  68. package/dist/theculturepost/pages/archive/CollectionDetailPage.js +158 -143
  69. package/dist/theculturepost/pages/archive/CollectionDetailPage.js.map +1 -1
  70. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js +155 -93
  71. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js.map +1 -1
  72. package/dist/theculturepost/pages/discussions/DiscussionsPage.js +16 -16
  73. package/dist/theculturepost/pages/discussions/DiscussionsPage.js.map +1 -1
  74. package/dist/theculturepost/pages/governance/GovernancePage.js +293 -0
  75. package/dist/theculturepost/pages/governance/GovernancePage.js.map +1 -0
  76. package/dist/theculturepost/pages/stories/StoriesPage.js +18 -18
  77. package/dist/theculturepost/pages/stories/StoriesPage.js.map +1 -1
  78. package/dist/theculturepost/pages/stories/StoryDetailPage.js +603 -285
  79. package/dist/theculturepost/pages/stories/StoryDetailPage.js.map +1 -1
  80. package/dist/theculturepost/pages/timeline/TimelinePage.js +162 -0
  81. package/dist/theculturepost/pages/timeline/TimelinePage.js.map +1 -0
  82. package/dist/theculturepost/pages/topics/TopicDetailPage.js +239 -163
  83. package/dist/theculturepost/pages/topics/TopicDetailPage.js.map +1 -1
  84. package/dist/theculturepost/pages/topics/TopicsPage.js +76 -74
  85. package/dist/theculturepost/pages/topics/TopicsPage.js.map +1 -1
  86. package/dist/theculturepost/styles/injectDisplayFont.js +11 -0
  87. package/dist/theculturepost/styles/injectDisplayFont.js.map +1 -0
  88. package/dist/theculturepost/utils/formatters.js +6 -1
  89. package/dist/theculturepost/utils/formatters.js.map +1 -1
  90. package/package.json +1 -1
  91. package/dist/theculturepost/components/OralHistoryCard.js.map +0 -1
@@ -1,160 +1,289 @@
1
- import { ErrorView as e, LoadingView as t } from "./StateViews.js";
2
- import { formatEnumLabel as n, formatRelativeTime as r } from "../utils/formatters.js";
3
- import { StatusBadge as i } from "./StatusBadge.js";
4
- import { AuthorLabel as a } from "./AuthorLabel.js";
5
- import { useTcpModerateReply as o, useTcpPostReply as s, useTcpReplies as c } from "../hooks/useTcpApi.js";
1
+ import { useTheCulturePost as e } from "../context/TheCulturePostContext.js";
2
+ import { cn as t } from "../utils/cn.js";
3
+ import "../context/index.js";
4
+ import { ErrorView as n, LoadingView as r } from "./StateViews.js";
5
+ import { formatEnumLabel as i, formatRelativeTime as a } from "../utils/formatters.js";
6
+ import { StatusBadge as o } from "./StatusBadge.js";
7
+ import { AuthorLabel as s } from "./AuthorLabel.js";
8
+ import { useTcpModerateReply as c, useTcpPostReply as l, useTcpReplies as u } from "../hooks/useTcpApi.js";
6
9
  import "../hooks/index.js";
7
- import { describeError as l, notify as u } from "../lib/toast.js";
8
- import { useState as d } from "react";
9
- import { MessageSquare as f, Send as p, ShieldAlert as m, Sparkle as h } from "lucide-react";
10
- import { jsx as g, jsxs as _ } from "react/jsx-runtime";
11
- import { Button as v, Input as y, Select as b, SelectContent as x, SelectItem as S, SelectTrigger as C, SelectValue as w, Spinner as T, Textarea as E } from "@burdenoff/fe-libs/ui";
10
+ import { describeError as d, notify as f } from "../lib/toast.js";
11
+ import { useMemo as p, useState as m } from "react";
12
+ import { ArrowUpDown as h, CheckCircle2 as g, Feather as _, Flag as v, Loader2 as y, MessageSquare as b, ShieldCheck as x, Sparkle as S, Trash2 as C } from "lucide-react";
13
+ import { jsx as w, jsxs as T } from "react/jsx-runtime";
14
+ import { Button as E, Select as D, SelectContent as O, SelectItem as k, SelectTrigger as A, SelectValue as j, Spinner as M, Textarea as N } from "@burdenoff/fe-libs/ui";
12
15
  //#region src/theculturepost/components/ReplyThread.tsx
13
- var D = [
14
- "APPROVED",
15
- "FLAGGED",
16
- "REMOVED"
16
+ var P = "__none__", F = [
17
+ "Personal experience",
18
+ "Historical context",
19
+ "Scholarly reading",
20
+ "Community elder",
21
+ "Interfaith view",
22
+ "Philosophical reading",
23
+ "Regional tradition",
24
+ "A question"
25
+ ], I = [
26
+ {
27
+ status: "APPROVED",
28
+ label: "Approve",
29
+ icon: /* @__PURE__ */ w(g, {
30
+ className: "h-3.5 w-3.5",
31
+ "aria-hidden": "true"
32
+ }),
33
+ active: "border-status-success-text/40 bg-status-success-bg-subtle text-status-success-text"
34
+ },
35
+ {
36
+ status: "FLAGGED",
37
+ label: "Flag",
38
+ icon: /* @__PURE__ */ w(v, {
39
+ className: "h-3.5 w-3.5",
40
+ "aria-hidden": "true"
41
+ }),
42
+ active: "border-status-warning-text/40 bg-status-warning-bg-subtle text-status-warning-text"
43
+ },
44
+ {
45
+ status: "REMOVED",
46
+ label: "Remove",
47
+ icon: /* @__PURE__ */ w(C, {
48
+ className: "h-3.5 w-3.5",
49
+ "aria-hidden": "true"
50
+ }),
51
+ active: "border-status-error-text/40 bg-status-error-bg-subtle text-status-error-text"
52
+ }
17
53
  ];
18
- function O({ reply: e }) {
19
- let t = o(), s = async (r) => {
20
- try {
21
- await t.mutateAsync({
22
- id: e.id,
23
- status: r
24
- }), u.success("Reply moderated", n(r));
54
+ function L({ isSelf: e, label: n }) {
55
+ return /* @__PURE__ */ w("span", {
56
+ className: t("flex h-10 w-10 shrink-0 select-none items-center justify-center rounded-full text-sm font-semibold", e ? "bg-action-primary-bg text-action-primary-text" : "bg-gradient-to-br from-[var(--color-accent-soft)] to-bg-sunken text-text-primary"),
57
+ "aria-hidden": "true",
58
+ children: n.charAt(0)
59
+ });
60
+ }
61
+ function R({ reply: n }) {
62
+ let { currentUser: r } = e(), l = c(), u = !!(n.authorId && r?.id && n.authorId === r.id), p = u ? "You" : "Contributor", m = n.status === "REMOVED", h = async (e) => {
63
+ if (e !== n.status) try {
64
+ await l.mutateAsync({
65
+ id: n.id,
66
+ status: e
67
+ }), f.success("Reply moderated", i(e));
25
68
  } catch (e) {
26
- u.error("Could not moderate reply", l(e));
69
+ f.error("Could not moderate reply", d(e));
27
70
  }
28
- }, c = e.status === "REMOVED";
29
- return /* @__PURE__ */ _("li", {
30
- className: "rounded-xl border border-border-seam bg-bg-surface p-4",
31
- children: [
32
- /* @__PURE__ */ _("div", {
33
- className: "flex flex-wrap items-center justify-between gap-2",
34
- children: [/* @__PURE__ */ _("div", {
35
- className: "flex flex-wrap items-center gap-2",
36
- children: [
37
- /* @__PURE__ */ g(a, {
38
- actorId: e.authorId,
39
- className: "text-sm font-medium text-text-primary"
40
- }),
41
- /* @__PURE__ */ _("span", {
42
- className: "text-xs text-text-tertiary",
43
- children: ["· ", r(e.createdAt)]
44
- }),
45
- e.perspective && /* @__PURE__ */ _("span", {
46
- className: "inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary",
71
+ };
72
+ return /* @__PURE__ */ w("li", {
73
+ className: "rounded-2xl border border-border-seam bg-bg-surface p-4 sm:p-5",
74
+ children: /* @__PURE__ */ T("div", {
75
+ className: "flex gap-3 sm:gap-4",
76
+ children: [/* @__PURE__ */ w(L, {
77
+ isSelf: u,
78
+ label: p
79
+ }), /* @__PURE__ */ T("div", {
80
+ className: "min-w-0 flex-1",
81
+ children: [
82
+ /* @__PURE__ */ T("div", {
83
+ className: "flex flex-wrap items-center justify-between gap-2",
84
+ children: [/* @__PURE__ */ T("div", {
85
+ className: "flex flex-wrap items-center gap-x-2 gap-y-1",
47
86
  children: [
48
- /* @__PURE__ */ g(h, { className: "h-3 w-3" }),
49
- " ",
50
- e.perspective
87
+ /* @__PURE__ */ w(s, {
88
+ actorId: n.authorId,
89
+ className: "text-sm font-semibold text-text-primary"
90
+ }),
91
+ /* @__PURE__ */ w("span", {
92
+ className: "text-xs text-text-tertiary",
93
+ children: a(n.createdAt)
94
+ }),
95
+ n.perspective && /* @__PURE__ */ T("span", {
96
+ className: "inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary",
97
+ children: [
98
+ /* @__PURE__ */ w(S, {
99
+ className: "h-3 w-3",
100
+ "aria-hidden": "true"
101
+ }),
102
+ " ",
103
+ n.perspective
104
+ ]
105
+ })
51
106
  ]
107
+ }), /* @__PURE__ */ w(o, { status: n.status })]
108
+ }),
109
+ /* @__PURE__ */ w("p", {
110
+ className: m ? "mt-2 whitespace-pre-wrap text-sm italic text-text-tertiary" : "mt-2 whitespace-pre-wrap break-words text-sm leading-relaxed text-text-primary",
111
+ children: m ? "This reply has been removed by a moderator." : n.body
112
+ }),
113
+ /* @__PURE__ */ T("div", {
114
+ className: "mt-3 flex flex-wrap items-center gap-2 border-t border-border-seam pt-3",
115
+ children: [
116
+ /* @__PURE__ */ T("span", {
117
+ className: "inline-flex items-center gap-1.5 text-xs font-medium text-text-tertiary",
118
+ children: [/* @__PURE__ */ w(x, {
119
+ className: "h-4 w-4",
120
+ "aria-hidden": "true"
121
+ }), " Moderate"]
122
+ }),
123
+ I.map((e) => {
124
+ let r = n.status === e.status;
125
+ return /* @__PURE__ */ T("button", {
126
+ type: "button",
127
+ onClick: () => h(e.status),
128
+ disabled: l.isPending || r,
129
+ "aria-pressed": r,
130
+ "aria-label": `${e.label} reply`,
131
+ className: t("inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1 text-xs font-medium transition-colors disabled:cursor-default", r ? e.active : "border-border-seam bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-60"),
132
+ children: [e.icon, e.label]
133
+ }, e.status);
134
+ }),
135
+ l.isPending && /* @__PURE__ */ w(M, { size: "sm" })
136
+ ]
137
+ })
138
+ ]
139
+ })]
140
+ })
141
+ });
142
+ }
143
+ function z({ discussionId: e, isLocked: t }) {
144
+ let { data: i, isLoading: a, isError: o, refetch: s } = u(e), c = l(), [g, v] = m(""), [x, S] = m(P), [C, M] = m("oldest"), I = p(() => {
145
+ let e = i ? [...i] : [];
146
+ return e.sort((e, t) => {
147
+ let n = new Date(e.createdAt).getTime(), r = new Date(t.createdAt).getTime();
148
+ return C === "oldest" ? n - r : r - n;
149
+ }), e;
150
+ }, [i, C]), L = i?.length ?? 0;
151
+ return /* @__PURE__ */ T("div", {
152
+ className: "flex flex-col gap-4",
153
+ children: [
154
+ /* @__PURE__ */ T("div", {
155
+ className: "flex flex-wrap items-center justify-between gap-3",
156
+ children: [/* @__PURE__ */ T("h2", {
157
+ className: "font-display text-lg font-semibold text-text-primary",
158
+ children: ["Replies", i ? /* @__PURE__ */ T("span", {
159
+ className: "text-text-tertiary",
160
+ children: [
161
+ " (",
162
+ L,
163
+ ")"
164
+ ]
165
+ }) : null]
166
+ }), L > 1 && /* @__PURE__ */ T(D, {
167
+ value: C,
168
+ onValueChange: (e) => M(e),
169
+ children: [/* @__PURE__ */ w(A, {
170
+ className: "h-9 w-40",
171
+ "aria-label": "Sort replies",
172
+ children: /* @__PURE__ */ T("span", {
173
+ className: "flex items-center gap-1.5",
174
+ children: [/* @__PURE__ */ w(h, {
175
+ className: "h-3.5 w-3.5 text-text-tertiary",
176
+ "aria-hidden": "true"
177
+ }), /* @__PURE__ */ w(j, {})]
52
178
  })
53
- ]
54
- }), /* @__PURE__ */ g(i, { status: e.status })]
179
+ }), /* @__PURE__ */ T(O, { children: [/* @__PURE__ */ w(k, {
180
+ value: "oldest",
181
+ children: "Oldest first"
182
+ }), /* @__PURE__ */ w(k, {
183
+ value: "newest",
184
+ children: "Newest first"
185
+ })] })]
186
+ })]
55
187
  }),
56
- /* @__PURE__ */ g("p", {
57
- className: c ? "mt-2 whitespace-pre-wrap text-sm italic text-text-tertiary" : "mt-2 whitespace-pre-wrap break-words text-sm text-text-primary",
58
- children: c ? "This reply has been removed by a moderator." : e.body
188
+ a ? /* @__PURE__ */ w(r, { label: "Loading replies…" }) : o ? /* @__PURE__ */ w(n, {
189
+ message: "We couldn't load replies.",
190
+ onRetry: () => s()
191
+ }) : I.length === 0 ? /* @__PURE__ */ T("div", {
192
+ className: "flex flex-col items-center gap-2 rounded-2xl border border-dashed border-border-seam py-10 text-center",
193
+ children: [/* @__PURE__ */ w(b, {
194
+ className: "h-8 w-8 text-text-tertiary",
195
+ "aria-hidden": "true"
196
+ }), /* @__PURE__ */ T("p", {
197
+ className: "text-sm text-text-secondary",
198
+ children: [
199
+ "No replies yet.",
200
+ " ",
201
+ t ? "This discussion is closed." : "Share the first perspective."
202
+ ]
203
+ })]
204
+ }) : /* @__PURE__ */ w("ul", {
205
+ className: "flex flex-col gap-3",
206
+ children: I.map((e) => /* @__PURE__ */ w(R, { reply: e }, e.id))
59
207
  }),
60
- /* @__PURE__ */ _("div", {
61
- className: "mt-3 flex items-center gap-2 border-t border-border-seam pt-3",
208
+ t ? /* @__PURE__ */ w("div", {
209
+ className: "rounded-2xl border border-border-seam bg-bg-sunken/60 px-4 py-3 text-center text-sm text-text-secondary",
210
+ children: "This discussion is locked. New replies are closed."
211
+ }) : /* @__PURE__ */ T("div", {
212
+ className: "flex flex-col gap-4 rounded-2xl border border-border-seam bg-bg-surface p-4 sm:p-5",
62
213
  children: [
63
- /* @__PURE__ */ g(m, {
64
- className: "h-4 w-4 text-text-tertiary",
65
- "aria-hidden": "true"
66
- }),
67
- /* @__PURE__ */ g("span", {
68
- className: "text-xs text-text-secondary",
69
- children: "Moderate"
214
+ /* @__PURE__ */ w("h3", {
215
+ className: "font-display text-base font-semibold text-text-primary",
216
+ children: "Add your reply"
70
217
  }),
71
- /* @__PURE__ */ _(b, {
72
- value: e.status,
73
- onValueChange: (e) => s(e),
74
- children: [/* @__PURE__ */ g(C, {
75
- className: "h-8 w-36",
76
- "aria-label": "Moderate reply",
77
- children: /* @__PURE__ */ g(w, {})
78
- }), /* @__PURE__ */ g(x, { children: D.map((e) => /* @__PURE__ */ g(S, {
79
- value: e,
80
- children: n(e)
81
- }, e)) })]
218
+ /* @__PURE__ */ T("div", {
219
+ className: "grid gap-4 md:grid-cols-[1fr_16rem]",
220
+ children: [/* @__PURE__ */ T("div", {
221
+ className: "flex flex-col gap-1.5",
222
+ children: [/* @__PURE__ */ w("label", {
223
+ htmlFor: "reply-body",
224
+ className: "sr-only",
225
+ children: "Your reply"
226
+ }), /* @__PURE__ */ w(N, {
227
+ id: "reply-body",
228
+ value: g,
229
+ onChange: (e) => v(e.target.value),
230
+ placeholder: "Share your perspective…",
231
+ rows: 4
232
+ })]
233
+ }), /* @__PURE__ */ T("div", {
234
+ className: "flex flex-col gap-1.5",
235
+ children: [/* @__PURE__ */ w("label", {
236
+ htmlFor: "reply-perspective",
237
+ className: "text-xs font-medium text-text-secondary",
238
+ children: "Perspective (optional)"
239
+ }), /* @__PURE__ */ T(D, {
240
+ value: x,
241
+ onValueChange: S,
242
+ children: [/* @__PURE__ */ w(A, {
243
+ id: "reply-perspective",
244
+ "aria-label": "Perspective label",
245
+ children: /* @__PURE__ */ w(j, { placeholder: "Select a perspective label…" })
246
+ }), /* @__PURE__ */ T(O, { children: [/* @__PURE__ */ w(k, {
247
+ value: P,
248
+ children: "No label"
249
+ }), F.map((e) => /* @__PURE__ */ w(k, {
250
+ value: e,
251
+ children: e
252
+ }, e))] })]
253
+ })]
254
+ })]
82
255
  }),
83
- t.isPending && /* @__PURE__ */ g(T, { size: "sm" })
256
+ /* @__PURE__ */ w("div", {
257
+ className: "flex justify-end",
258
+ children: /* @__PURE__ */ T(E, {
259
+ onClick: async () => {
260
+ let t = g.trim();
261
+ if (t) try {
262
+ await c.mutateAsync({
263
+ discussionId: e,
264
+ body: t,
265
+ perspective: x === P ? null : x
266
+ }), v(""), S(P), f.success("Reply posted");
267
+ } catch (e) {
268
+ f.error("Could not post reply", d(e));
269
+ }
270
+ },
271
+ disabled: !g.trim() || c.isPending,
272
+ children: [c.isPending ? /* @__PURE__ */ w(y, {
273
+ className: "mr-1.5 h-4 w-4 animate-spin",
274
+ "aria-hidden": "true"
275
+ }) : /* @__PURE__ */ w(_, {
276
+ className: "mr-1.5 h-4 w-4",
277
+ "aria-hidden": "true"
278
+ }), "Post reply"]
279
+ })
280
+ })
84
281
  ]
85
282
  })
86
283
  ]
87
284
  });
88
285
  }
89
- function k({ discussionId: n, isLocked: r }) {
90
- let { data: i, isLoading: a, isError: o, refetch: m } = c(n), h = s(), [b, x] = d(""), [S, C] = d("");
91
- return /* @__PURE__ */ _("div", {
92
- className: "flex flex-col gap-4",
93
- children: [a ? /* @__PURE__ */ g(t, { label: "Loading replies…" }) : o ? /* @__PURE__ */ g(e, {
94
- message: "We couldn't load replies.",
95
- onRetry: () => m()
96
- }) : !i || i.length === 0 ? /* @__PURE__ */ _("div", {
97
- className: "flex flex-col items-center gap-2 rounded-xl border border-dashed border-border-seam py-8 text-center",
98
- children: [/* @__PURE__ */ g(f, { className: "h-8 w-8 text-text-tertiary" }), /* @__PURE__ */ _("p", {
99
- className: "text-sm text-text-secondary",
100
- children: [
101
- "No replies yet.",
102
- " ",
103
- r ? "This discussion is closed." : "Share the first perspective."
104
- ]
105
- })]
106
- }) : /* @__PURE__ */ g("ul", {
107
- className: "flex flex-col gap-3",
108
- children: i.map((e) => /* @__PURE__ */ g(O, { reply: e }, e.id))
109
- }), r ? /* @__PURE__ */ g("div", {
110
- className: "rounded-xl border border-border-seam bg-bg-sunken/60 px-4 py-3 text-center text-sm text-text-secondary",
111
- children: "This discussion is locked. New replies are closed."
112
- }) : /* @__PURE__ */ _("div", {
113
- className: "flex flex-col gap-2 rounded-xl border border-border-seam bg-bg-surface p-4",
114
- children: [
115
- /* @__PURE__ */ g("label", {
116
- htmlFor: "reply-body",
117
- className: "text-sm font-medium text-text-primary",
118
- children: "Add your reply"
119
- }),
120
- /* @__PURE__ */ g(E, {
121
- id: "reply-body",
122
- value: b,
123
- onChange: (e) => x(e.target.value),
124
- placeholder: "Contribute a thoughtful, respectful perspective…",
125
- rows: 3
126
- }),
127
- /* @__PURE__ */ _("div", {
128
- className: "flex flex-col gap-2 sm:flex-row sm:items-center",
129
- children: [/* @__PURE__ */ g(y, {
130
- value: S,
131
- onChange: (e) => C(e.target.value),
132
- placeholder: "Perspective (optional) — e.g. Historian, Elder",
133
- className: "sm:max-w-xs",
134
- "aria-label": "Perspective"
135
- }), /* @__PURE__ */ _(v, {
136
- onClick: async () => {
137
- let e = b.trim();
138
- if (e) try {
139
- await h.mutateAsync({
140
- discussionId: n,
141
- body: e,
142
- perspective: S.trim() || null
143
- }), x(""), C(""), u.success("Reply posted");
144
- } catch (e) {
145
- u.error("Could not post reply", l(e));
146
- }
147
- },
148
- disabled: !b.trim() || h.isPending,
149
- className: "sm:ml-auto",
150
- children: [/* @__PURE__ */ g(p, { className: "mr-1.5 h-4 w-4" }), " Post reply"]
151
- })]
152
- })
153
- ]
154
- })]
155
- });
156
- }
157
286
  //#endregion
158
- export { k as ReplyThread };
287
+ export { z as ReplyThread };
159
288
 
160
289
  //# sourceMappingURL=ReplyThread.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReplyThread.js","names":[],"sources":["../../../src/theculturepost/components/ReplyThread.tsx"],"sourcesContent":["/**\n * ReplyThread — moderated replies for a discussion.\n * @module theculturepost/components\n *\n * Reads `tcpListReplies(discussionId)`; writes `tcpPostReply` (with an optional\n * perspective label) and `tcpModerateReply` (approve / flag / remove) —\n * respectful-by-design controls. Author identity is id-only, so we render \"You\"\n * or \"Contributor\".\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, Send, ShieldAlert, Sparkle } from 'lucide-react';\nimport {\n Button,\n Input,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { AuthorLabel } from './AuthorLabel';\nimport { LoadingView, ErrorView } from './StateViews';\nimport { useTcpReplies, useTcpPostReply, useTcpModerateReply } from '../hooks';\nimport { formatEnumLabel, formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpModerationStatus, TcpReply } from '../types';\n\nconst MODERATION_ACTIONS: TcpModerationStatus[] = ['APPROVED', 'FLAGGED', 'REMOVED'];\n\nfunction ReplyRow({ reply }: { reply: TcpReply }) {\n const moderate = useTcpModerateReply();\n\n const doModerate = async (status: TcpModerationStatus) => {\n try {\n await moderate.mutateAsync({ id: reply.id, status });\n notify.success('Reply moderated', formatEnumLabel(status));\n } catch (err) {\n notify.error('Could not moderate reply', describeError(err));\n }\n };\n\n const removed = reply.status === 'REMOVED';\n\n return (\n <li className=\"rounded-xl border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <AuthorLabel actorId={reply.authorId} className=\"text-sm font-medium text-text-primary\" />\n <span className=\"text-xs text-text-tertiary\">\n · {formatRelativeTime(reply.createdAt)}\n </span>\n {reply.perspective && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary\">\n <Sparkle className=\"h-3 w-3\" /> {reply.perspective}\n </span>\n )}\n </div>\n <StatusBadge status={reply.status} />\n </div>\n\n <p\n className={\n removed\n ? 'mt-2 whitespace-pre-wrap text-sm italic text-text-tertiary'\n : 'mt-2 whitespace-pre-wrap break-words text-sm text-text-primary'\n }\n >\n {removed ? 'This reply has been removed by a moderator.' : reply.body}\n </p>\n\n <div className=\"mt-3 flex items-center gap-2 border-t border-border-seam pt-3\">\n <ShieldAlert className=\"h-4 w-4 text-text-tertiary\" aria-hidden=\"true\" />\n <span className=\"text-xs text-text-secondary\">Moderate</span>\n <Select value={reply.status} onValueChange={(v) => doModerate(v as TcpModerationStatus)}>\n <SelectTrigger className=\"h-8 w-36\" aria-label=\"Moderate reply\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {MODERATION_ACTIONS.map((s) => (\n <SelectItem key={s} value={s}>\n {formatEnumLabel(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {moderate.isPending && <Spinner size=\"sm\" />}\n </div>\n </li>\n );\n}\n\ninterface ReplyThreadProps {\n discussionId: string;\n isLocked: boolean;\n}\n\nexport function ReplyThread({ discussionId, isLocked }: ReplyThreadProps) {\n const { data: replies, isLoading, isError, refetch } = useTcpReplies(discussionId);\n const postReply = useTcpPostReply();\n const [body, setBody] = useState('');\n const [perspective, setPerspective] = useState('');\n\n const submit = async () => {\n const text = body.trim();\n if (!text) return;\n try {\n await postReply.mutateAsync({\n discussionId,\n body: text,\n perspective: perspective.trim() || null,\n });\n setBody('');\n setPerspective('');\n notify.success('Reply posted');\n } catch (err) {\n notify.error('Could not post reply', describeError(err));\n }\n };\n\n return (\n <div className=\"flex flex-col gap-4\">\n {isLoading ? (\n <LoadingView label=\"Loading replies…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load replies.\" onRetry={() => refetch()} />\n ) : !replies || replies.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 rounded-xl border border-dashed border-border-seam py-8 text-center\">\n <MessageSquare className=\"h-8 w-8 text-text-tertiary\" />\n <p className=\"text-sm text-text-secondary\">\n No replies yet.{' '}\n {isLocked ? 'This discussion is closed.' : 'Share the first perspective.'}\n </p>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-3\">\n {replies.map((reply) => (\n <ReplyRow key={reply.id} reply={reply} />\n ))}\n </ul>\n )}\n\n {isLocked ? (\n <div className=\"rounded-xl border border-border-seam bg-bg-sunken/60 px-4 py-3 text-center text-sm text-text-secondary\">\n This discussion is locked. New replies are closed.\n </div>\n ) : (\n <div className=\"flex flex-col gap-2 rounded-xl border border-border-seam bg-bg-surface p-4\">\n <label htmlFor=\"reply-body\" className=\"text-sm font-medium text-text-primary\">\n Add your reply\n </label>\n <Textarea\n id=\"reply-body\"\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Contribute a thoughtful, respectful perspective…\"\n rows={3}\n />\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <Input\n value={perspective}\n onChange={(e) => setPerspective(e.target.value)}\n placeholder=\"Perspective (optional) — e.g. Historian, Elder\"\n className=\"sm:max-w-xs\"\n aria-label=\"Perspective\"\n />\n <Button\n onClick={submit}\n disabled={!body.trim() || postReply.isPending}\n className=\"sm:ml-auto\"\n >\n <Send className=\"mr-1.5 h-4 w-4\" /> Post reply\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ReplyThread.js","names":[],"sources":["../../../src/theculturepost/components/ReplyThread.tsx"],"sourcesContent":["/**\n * ReplyThread — moderated replies for a discussion.\n * @module theculturepost/components\n *\n * Reads `tcpListReplies(discussionId)`; writes `tcpPostReply` (with an optional\n * perspective label) and `tcpModerateReply` (approve / flag / remove) —\n * respectful-by-design controls rendered as INLINE moderation buttons.\n *\n * Identity is id-only in this subgraph (no names / avatars), so each reply shows\n * \"You\" for the current actor or a neutral \"Contributor\", with a role-tinted\n * initial monogram — never a fabricated name or photo. There is no reactions /\n * likes field, so none is rendered.\n */\n\nimport { useMemo, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport {\n MessageSquare,\n Feather,\n ShieldCheck,\n CheckCircle2,\n Flag,\n Trash2,\n ArrowUpDown,\n Sparkle,\n Loader2,\n} from 'lucide-react';\nimport {\n Button,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { AuthorLabel } from './AuthorLabel';\nimport { LoadingView, ErrorView } from './StateViews';\nimport { useTcpReplies, useTcpPostReply, useTcpModerateReply } from '../hooks';\nimport { useTheCulturePost } from '../context';\nimport { formatEnumLabel, formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport { cn } from '../utils/cn';\nimport type { TcpModerationStatus, TcpReply } from '../types';\n\ntype SortOrder = 'oldest' | 'newest';\n\n/** Sentinel for \"no perspective label\" — Radix Select forbids an empty value. */\nconst NO_PERSPECTIVE = '__none__';\n\n/** Curated, tradition-neutral perspective labels (the field itself is a free String). */\nconst PERSPECTIVE_OPTIONS = [\n 'Personal experience',\n 'Historical context',\n 'Scholarly reading',\n 'Community elder',\n 'Interfaith view',\n 'Philosophical reading',\n 'Regional tradition',\n 'A question',\n] as const;\n\n// ---------------------------------------------------------------------------\n// Inline moderation control\n// ---------------------------------------------------------------------------\n\ninterface ModActionSpec {\n status: TcpModerationStatus;\n label: string;\n icon: ReactNode;\n /** Token classes when this action is the reply's current state. */\n active: string;\n}\n\nconst MOD_ACTIONS: ModActionSpec[] = [\n {\n status: 'APPROVED',\n label: 'Approve',\n icon: <CheckCircle2 className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />,\n active: 'border-status-success-text/40 bg-status-success-bg-subtle text-status-success-text',\n },\n {\n status: 'FLAGGED',\n label: 'Flag',\n icon: <Flag className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />,\n active: 'border-status-warning-text/40 bg-status-warning-bg-subtle text-status-warning-text',\n },\n {\n status: 'REMOVED',\n label: 'Remove',\n icon: <Trash2 className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />,\n active: 'border-status-error-text/40 bg-status-error-bg-subtle text-status-error-text',\n },\n];\n\n/** A round monogram derived only from the honest label (\"You\" / \"Contributor\"). */\nfunction ReplyMonogram({ isSelf, label }: { isSelf: boolean; label: string }) {\n return (\n <span\n className={cn(\n 'flex h-10 w-10 shrink-0 select-none items-center justify-center rounded-full text-sm font-semibold',\n isSelf\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-gradient-to-br from-[var(--color-accent-soft)] to-bg-sunken text-text-primary'\n )}\n aria-hidden=\"true\"\n >\n {label.charAt(0)}\n </span>\n );\n}\n\nfunction ReplyRow({ reply }: { reply: TcpReply }) {\n const { currentUser } = useTheCulturePost();\n const moderate = useTcpModerateReply();\n\n const isSelf = Boolean(reply.authorId && currentUser?.id && reply.authorId === currentUser.id);\n const authorLabel = isSelf ? 'You' : 'Contributor';\n const removed = reply.status === 'REMOVED';\n\n const doModerate = async (status: TcpModerationStatus) => {\n if (status === reply.status) return;\n try {\n await moderate.mutateAsync({ id: reply.id, status });\n notify.success('Reply moderated', formatEnumLabel(status));\n } catch (err) {\n notify.error('Could not moderate reply', describeError(err));\n }\n };\n\n return (\n <li className=\"rounded-2xl border border-border-seam bg-bg-surface p-4 sm:p-5\">\n <div className=\"flex gap-3 sm:gap-4\">\n <ReplyMonogram isSelf={isSelf} label={authorLabel} />\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <AuthorLabel\n actorId={reply.authorId}\n className=\"text-sm font-semibold text-text-primary\"\n />\n <span className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(reply.createdAt)}\n </span>\n {reply.perspective && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary\">\n <Sparkle className=\"h-3 w-3\" aria-hidden=\"true\" /> {reply.perspective}\n </span>\n )}\n </div>\n <StatusBadge status={reply.status} />\n </div>\n\n <p\n className={\n removed\n ? 'mt-2 whitespace-pre-wrap text-sm italic text-text-tertiary'\n : 'mt-2 whitespace-pre-wrap break-words text-sm leading-relaxed text-text-primary'\n }\n >\n {removed ? 'This reply has been removed by a moderator.' : reply.body}\n </p>\n\n {/* Inline moderation */}\n <div className=\"mt-3 flex flex-wrap items-center gap-2 border-t border-border-seam pt-3\">\n <span className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-tertiary\">\n <ShieldCheck className=\"h-4 w-4\" aria-hidden=\"true\" /> Moderate\n </span>\n {MOD_ACTIONS.map((action) => {\n const isActive = reply.status === action.status;\n return (\n <button\n key={action.status}\n type=\"button\"\n onClick={() => doModerate(action.status)}\n disabled={moderate.isPending || isActive}\n aria-pressed={isActive}\n aria-label={`${action.label} reply`}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1 text-xs font-medium transition-colors disabled:cursor-default',\n isActive\n ? action.active\n : 'border-border-seam bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-60'\n )}\n >\n {action.icon}\n {action.label}\n </button>\n );\n })}\n {moderate.isPending && <Spinner size=\"sm\" />}\n </div>\n </div>\n </div>\n </li>\n );\n}\n\ninterface ReplyThreadProps {\n discussionId: string;\n isLocked: boolean;\n}\n\nexport function ReplyThread({ discussionId, isLocked }: ReplyThreadProps) {\n const { data: replies, isLoading, isError, refetch } = useTcpReplies(discussionId);\n const postReply = useTcpPostReply();\n const [body, setBody] = useState('');\n const [perspective, setPerspective] = useState<string>(NO_PERSPECTIVE);\n const [sort, setSort] = useState<SortOrder>('oldest');\n\n const sorted = useMemo(() => {\n const list = replies ? [...replies] : [];\n list.sort((a, b) => {\n const ta = new Date(a.createdAt).getTime();\n const tb = new Date(b.createdAt).getTime();\n return sort === 'oldest' ? ta - tb : tb - ta;\n });\n return list;\n }, [replies, sort]);\n\n const count = replies?.length ?? 0;\n\n const submit = async () => {\n const text = body.trim();\n if (!text) return;\n try {\n await postReply.mutateAsync({\n discussionId,\n body: text,\n perspective: perspective === NO_PERSPECTIVE ? null : perspective,\n });\n setBody('');\n setPerspective(NO_PERSPECTIVE);\n notify.success('Reply posted');\n } catch (err) {\n notify.error('Could not post reply', describeError(err));\n }\n };\n\n return (\n <div className=\"flex flex-col gap-4\">\n {/* Section header + client-side sort */}\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"font-display text-lg font-semibold text-text-primary\">\n Replies{replies ? <span className=\"text-text-tertiary\"> ({count})</span> : null}\n </h2>\n {count > 1 && (\n <Select value={sort} onValueChange={(v) => setSort(v as SortOrder)}>\n <SelectTrigger className=\"h-9 w-40\" aria-label=\"Sort replies\">\n <span className=\"flex items-center gap-1.5\">\n <ArrowUpDown className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n <SelectValue />\n </span>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"oldest\">Oldest first</SelectItem>\n <SelectItem value=\"newest\">Newest first</SelectItem>\n </SelectContent>\n </Select>\n )}\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading replies…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load replies.\" onRetry={() => refetch()} />\n ) : sorted.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 rounded-2xl border border-dashed border-border-seam py-10 text-center\">\n <MessageSquare className=\"h-8 w-8 text-text-tertiary\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">\n No replies yet.{' '}\n {isLocked ? 'This discussion is closed.' : 'Share the first perspective.'}\n </p>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-3\">\n {sorted.map((reply) => (\n <ReplyRow key={reply.id} reply={reply} />\n ))}\n </ul>\n )}\n\n {/* Composer */}\n {isLocked ? (\n <div className=\"rounded-2xl border border-border-seam bg-bg-sunken/60 px-4 py-3 text-center text-sm text-text-secondary\">\n This discussion is locked. New replies are closed.\n </div>\n ) : (\n <div className=\"flex flex-col gap-4 rounded-2xl border border-border-seam bg-bg-surface p-4 sm:p-5\">\n <h3 className=\"font-display text-base font-semibold text-text-primary\">Add your reply</h3>\n <div className=\"grid gap-4 md:grid-cols-[1fr_16rem]\">\n <div className=\"flex flex-col gap-1.5\">\n <label htmlFor=\"reply-body\" className=\"sr-only\">\n Your reply\n </label>\n <Textarea\n id=\"reply-body\"\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Share your perspective…\"\n rows={4}\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <label\n htmlFor=\"reply-perspective\"\n className=\"text-xs font-medium text-text-secondary\"\n >\n Perspective (optional)\n </label>\n <Select value={perspective} onValueChange={setPerspective}>\n <SelectTrigger id=\"reply-perspective\" aria-label=\"Perspective label\">\n <SelectValue placeholder=\"Select a perspective label…\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NO_PERSPECTIVE}>No label</SelectItem>\n {PERSPECTIVE_OPTIONS.map((label) => (\n <SelectItem key={label} value={label}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"flex justify-end\">\n <Button onClick={submit} disabled={!body.trim() || postReply.isPending}>\n {postReply.isPending ? (\n <Loader2 className=\"mr-1.5 h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Feather className=\"mr-1.5 h-4 w-4\" aria-hidden=\"true\" />\n )}\n Post reply\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
2
  import { formatEnumLabel as t } from "../utils/formatters.js";
3
- import { jsx as n } from "react/jsx-runtime";
3
+ import { jsxs as n } from "react/jsx-runtime";
4
4
  //#region src/theculturepost/components/StatusBadge.tsx
5
5
  var r = {
6
6
  success: "bg-status-success-bg-subtle text-status-success-text",
@@ -22,14 +22,14 @@ var r = {
22
22
  PHOTO_STORY: "info",
23
23
  TIMELINE: "info",
24
24
  CULTURE_AND_IDENTITY: "info",
25
- FESTIVALS_AND_RITUALS: "info",
26
- HERITAGE_AND_ARCHIVES: "info",
27
- HISTORY: "info",
25
+ FESTIVALS_AND_RITUALS: "warning",
26
+ HERITAGE_AND_ARCHIVES: "secondary",
27
+ HISTORY: "warning",
28
28
  MIGRATION_AND_DIASPORA: "info",
29
- ORAL_HISTORIES: "info",
29
+ ORAL_HISTORIES: "success",
30
30
  PHILOSOPHY_AND_THOUGHT: "info",
31
- RELIGION_AND_FAITH: "info",
32
- VALUES_AND_ETHICS: "info",
31
+ RELIGION_AND_FAITH: "warning",
32
+ VALUES_AND_ETHICS: "success",
33
33
  APPROVED: "success",
34
34
  PENDING: "warning",
35
35
  FLAGGED: "warning",
@@ -57,9 +57,9 @@ var r = {
57
57
  CORROBORATED: "success",
58
58
  DISPUTED: "destructive",
59
59
  RETRACTED: "destructive"
60
- }, a = ({ status: a, className: o }) => /* @__PURE__ */ n("span", {
60
+ }, a = ({ status: a, className: o, icon: s }) => /* @__PURE__ */ n("span", {
61
61
  className: e("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", r[i[a.toUpperCase()] ?? "secondary"], o),
62
- children: t(a)
62
+ children: [s, t(a)]
63
63
  });
64
64
  //#endregion
65
65
  export { a as StatusBadge };
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/theculturepost/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module theculturepost/components\n *\n * A colored badge for TheCulturePost domain enums (story status/format, topic\n * category, moderation status, access level, archive-item kind, source type,\n * citation verification) with semantic color mapping. Accepts the raw server\n * enum value.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { formatEnumLabel } from '../utils/formatters';\n\ninterface StatusBadgeProps {\n status: string;\n className?: string;\n}\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Story status\n IDEA: 'secondary',\n DRAFT: 'secondary',\n IN_REVIEW: 'warning',\n PUBLISHED: 'success',\n ARCHIVED: 'outline',\n\n // Story format\n ARTICLE: 'info',\n ESSAY: 'info',\n EXPLAINER: 'info',\n ORAL_HISTORY: 'info',\n PHOTO_STORY: 'info',\n TIMELINE: 'info',\n\n // Topic category\n CULTURE_AND_IDENTITY: 'info',\n FESTIVALS_AND_RITUALS: 'info',\n HERITAGE_AND_ARCHIVES: 'info',\n HISTORY: 'info',\n MIGRATION_AND_DIASPORA: 'info',\n ORAL_HISTORIES: 'info',\n PHILOSOPHY_AND_THOUGHT: 'info',\n RELIGION_AND_FAITH: 'info',\n VALUES_AND_ETHICS: 'info',\n\n // Moderation status\n APPROVED: 'success',\n PENDING: 'warning',\n FLAGGED: 'warning',\n REMOVED: 'destructive',\n\n // Access level\n PUBLIC: 'success',\n COMMUNITY: 'info',\n RESTRICTED: 'warning',\n EMBARGOED: 'warning',\n\n // Archive item kind\n MANUSCRIPT: 'secondary',\n PHOTOGRAPH: 'secondary',\n AUDIO: 'secondary',\n VIDEO: 'secondary',\n MAP: 'secondary',\n OBJECT: 'secondary',\n DOCUMENT: 'secondary',\n\n // Source type\n ACADEMIC: 'info',\n ARCHIVAL_RECORD: 'info',\n ARTIFACT: 'info',\n BOOK: 'info',\n INTERVIEW: 'info',\n PERIODICAL: 'info',\n PRIMARY_DOCUMENT: 'info',\n WEB: 'secondary',\n\n // Verification status\n UNVERIFIED: 'secondary',\n CORROBORATED: 'success',\n DISPUTED: 'destructive',\n RETRACTED: 'destructive',\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, className }) => {\n const key = status.toUpperCase();\n const variant = statusVariantMap[key] ?? 'secondary';\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {formatEnumLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;;AAqBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;CACV,EAEK,IAAiD;CAErD,MAAM;CACN,OAAO;CACP,WAAW;CACX,WAAW;CACX,UAAU;CAGV,SAAS;CACT,OAAO;CACP,WAAW;CACX,cAAc;CACd,aAAa;CACb,UAAU;CAGV,sBAAsB;CACtB,uBAAuB;CACvB,uBAAuB;CACvB,SAAS;CACT,wBAAwB;CACxB,gBAAgB;CAChB,wBAAwB;CACxB,oBAAoB;CACpB,mBAAmB;CAGnB,UAAU;CACV,SAAS;CACT,SAAS;CACT,SAAS;CAGT,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,WAAW;CAGX,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,OAAO;CACP,KAAK;CACL,QAAQ;CACR,UAAU;CAGV,UAAU;CACV,iBAAiB;CACjB,UAAU;CACV,MAAM;CACN,WAAW;CACX,YAAY;CACZ,kBAAkB;CAClB,KAAK;CAGL,YAAY;CACZ,cAAc;CACd,UAAU;CACV,WAAW;CACZ,EAEY,KAAqC,EAAE,WAAQ,mBAKxD,kBAAC,QAAD;CACE,WAAW,EACT,mFACA,EANU,EADJ,EAAO,aAAa,KACS,cAOnC,EACD;WAEA,EAAgB,EAAO;CACnB,CAAA"}
1
+ {"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/theculturepost/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module theculturepost/components\n *\n * A colored badge for TheCulturePost domain enums (story status/format, topic\n * category, moderation status, access level, archive-item kind, source type,\n * citation verification) with semantic color mapping. Accepts the raw server\n * enum value.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { cn } from '../utils/cn';\nimport { formatEnumLabel } from '../utils/formatters';\n\ninterface StatusBadgeProps {\n status: string;\n className?: string;\n /** Optional leading icon (already sized, e.g. `<Sparkles className=\"h-3 w-3\" />`). */\n icon?: ReactNode;\n}\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Story status\n IDEA: 'secondary',\n DRAFT: 'secondary',\n IN_REVIEW: 'warning',\n PUBLISHED: 'success',\n ARCHIVED: 'outline',\n\n // Story format\n ARTICLE: 'info',\n ESSAY: 'info',\n EXPLAINER: 'info',\n ORAL_HISTORY: 'info',\n PHOTO_STORY: 'info',\n TIMELINE: 'info',\n\n // Topic category — spread across semantic variants (token-only) so categories\n // read apart. Kept in sync with CATEGORY_ACCENT in categoryMeta.\n CULTURE_AND_IDENTITY: 'info',\n FESTIVALS_AND_RITUALS: 'warning',\n HERITAGE_AND_ARCHIVES: 'secondary',\n HISTORY: 'warning',\n MIGRATION_AND_DIASPORA: 'info',\n ORAL_HISTORIES: 'success',\n PHILOSOPHY_AND_THOUGHT: 'info',\n RELIGION_AND_FAITH: 'warning',\n VALUES_AND_ETHICS: 'success',\n\n // Moderation status\n APPROVED: 'success',\n PENDING: 'warning',\n FLAGGED: 'warning',\n REMOVED: 'destructive',\n\n // Access level\n PUBLIC: 'success',\n COMMUNITY: 'info',\n RESTRICTED: 'warning',\n EMBARGOED: 'warning',\n\n // Archive item kind\n MANUSCRIPT: 'secondary',\n PHOTOGRAPH: 'secondary',\n AUDIO: 'secondary',\n VIDEO: 'secondary',\n MAP: 'secondary',\n OBJECT: 'secondary',\n DOCUMENT: 'secondary',\n\n // Source type\n ACADEMIC: 'info',\n ARCHIVAL_RECORD: 'info',\n ARTIFACT: 'info',\n BOOK: 'info',\n INTERVIEW: 'info',\n PERIODICAL: 'info',\n PRIMARY_DOCUMENT: 'info',\n WEB: 'secondary',\n\n // Verification status\n UNVERIFIED: 'secondary',\n CORROBORATED: 'success',\n DISPUTED: 'destructive',\n RETRACTED: 'destructive',\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, className, icon }) => {\n const key = status.toUpperCase();\n const variant = statusVariantMap[key] ?? 'secondary';\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {icon}\n {formatEnumLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;;AAuBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;CACV,EAEK,IAAiD;CAErD,MAAM;CACN,OAAO;CACP,WAAW;CACX,WAAW;CACX,UAAU;CAGV,SAAS;CACT,OAAO;CACP,WAAW;CACX,cAAc;CACd,aAAa;CACb,UAAU;CAIV,sBAAsB;CACtB,uBAAuB;CACvB,uBAAuB;CACvB,SAAS;CACT,wBAAwB;CACxB,gBAAgB;CAChB,wBAAwB;CACxB,oBAAoB;CACpB,mBAAmB;CAGnB,UAAU;CACV,SAAS;CACT,SAAS;CACT,SAAS;CAGT,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,WAAW;CAGX,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,OAAO;CACP,KAAK;CACL,QAAQ;CACR,UAAU;CAGV,UAAU;CACV,iBAAiB;CACjB,UAAU;CACV,MAAM;CACN,WAAW;CACX,YAAY;CACZ,kBAAkB;CAClB,KAAK;CAGL,YAAY;CACZ,cAAc;CACd,UAAU;CACV,WAAW;CACZ,EAEY,KAAqC,EAAE,WAAQ,cAAW,cAKnE,kBAAC,QAAD;CACE,WAAW,EACT,mFACA,EANU,EADJ,EAAO,aAAa,KACS,cAOnC,EACD;WALH,CAOG,GACA,EAAgB,EAAO,CACnB"}