@openrails/contentkit-ui 0.89.2 → 0.90.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -473,6 +473,61 @@ const content = useContentGroups(contentId); // content.set(memberships, kinds)
473
473
  show humanized. Covers render `cover.renditions`; `renderCover` stands in
474
474
  until group reads carry them.
475
475
 
476
+ ## Reference administration workbench
477
+
478
+ `ContentAdmin` is an optional library component composing the existing content,
479
+ group, moderation, poll and statistics managers. It is not a standalone server
480
+ portal, a sign-in page or a SaaS control plane. A host can continue to compose
481
+ any individual manager in its own administration area.
482
+
483
+ ```tsx
484
+ import { ContentAdmin } from "@openrails/contentkit-ui";
485
+ import { richTextEditor } from "@openrails/contentkit-ui/editor";
486
+ import "@openrails/contentkit-ui/styles.css";
487
+
488
+ <ContentAdmin
489
+ client={contentkit}
490
+ basePath="/admin/content"
491
+ navigation={{ pathname, search, navigate }}
492
+ appearance={{ theme: "inherit" }}
493
+ sections={["contents", "groups", "moderation", "polls", "statistics"]}
494
+ contentKinds={["article", "video"]}
495
+ labels={{ article: "Articles", video: "Videos" }}
496
+ content={{ renderEditor: richTextEditor }}
497
+ />;
498
+ ```
499
+
500
+ The client is an existing ContentKit client, or pass `baseUrl` and the host's
501
+ `auth` transport. `guard`, `language`, `messages` and `onError` follow the
502
+ individual managers' contracts below. Without `navigation`, the workbench uses
503
+ browser history; without `basePath` as well, navigation stays in memory.
504
+
505
+ `GET /kinds` supplies languages and configured content and group kinds. Omit
506
+ `contentKinds` to offer all content kinds; omit `groupKinds` to offer the
507
+ site-owned group kinds. Explicit lists restrict and order those choices, and
508
+ unknown kinds are omitted. The routes beneath `basePath` are
509
+ `contents/<kind>[/new|/<id>]`, `groups/<kind>[/new|/<id>]`, `moderation`,
510
+ `polls[/new|/<id>]` and `statistics`; each manager retains its own filters and
511
+ subviews. Unknown or excluded routes show an unavailable view and do not load
512
+ a manager. Only the selected manager mounts. Navigation entries describe
513
+ configured sections, not permission grants: a selected manager can show its
514
+ normal no-access state. Statistics uses its own read permission, independently
515
+ of content editing.
516
+
517
+ The optional `content` props are `renderEditor` and `contentHref`; `groups`
518
+ accepts `renderContent`; `moderation` accepts `itemHref`, `userHref` and
519
+ `sections`; `polls` accepts `freeText` and `maxOptions`. These are the existing
520
+ managers' extension points. Without `renderEditor`, body editing uses the
521
+ manager's HTML field; the rich text editor is an optional peer dependency.
522
+ Changing the client or the provider's viewer/access/language scope resets the
523
+ workbench's local navigation and unsaved forms. A URL already selected by the
524
+ host stays selected and is checked again under the new scope. When reusing
525
+ a transport across sign-ins, keep `ContentKitProvider`'s `viewer` and
526
+ `accessRevision` current, or remount the workbench. Media and
527
+ version editing remain in the content editor. Site-specific import,
528
+ review and publishing workflows can use the individual managers and media
529
+ components without adopting this workbench.
530
+
476
531
  ## Admin flows
477
532
 
478
533
  Drop-in staff screens for a host's own admin area: each makes its own reads
@@ -0,0 +1,28 @@
1
+ import { Labels } from '../groups/parts.js';
2
+ import { CommentModerationProps } from '../social/moderation.js';
3
+ import { ContentManagerProps } from './content-manager.js';
4
+ import { GroupManagerProps } from './group-manager.js';
5
+ import { PollManagerProps } from './poll-manager.js';
6
+ import { AdminFlowProps, AdminRouteProps } from './shell.js';
7
+ export type ContentAdminSection = "contents" | "groups" | "moderation" | "polls" | "statistics";
8
+ /** An optional reference workbench; each section remains usable as its own component. */
9
+ export interface ContentAdminProps extends AdminFlowProps, AdminRouteProps {
10
+ /** Sections in navigation order. Default all. Navigation does not grant access. */
11
+ sections?: readonly ContentAdminSection[];
12
+ /** Content kinds in picker order; default all configured kinds. Unknown kinds are omitted. */
13
+ contentKinds?: readonly string[];
14
+ /** Group kinds in picker order; default the configured site-owned kinds. Unknown kinds are omitted. */
15
+ groupKinds?: readonly string[];
16
+ labels?: Labels;
17
+ content?: Pick<ContentManagerProps, "renderEditor" | "contentHref">;
18
+ groups?: Pick<GroupManagerProps, "renderContent">;
19
+ moderation?: Pick<CommentModerationProps, "itemHref" | "userHref" | "sections">;
20
+ polls?: Pick<PollManagerProps, "freeText" | "maxOptions">;
21
+ }
22
+ /**
23
+ * Model-driven content, group, moderation, poll and statistics screens, with
24
+ * the host's transport, router and theme. Only the selected manager mounts;
25
+ * its native permissions and refusals remain authoritative. This component
26
+ * does not mount a server portal, sign users in or define a publishing workflow.
27
+ */
28
+ export declare function ContentAdmin({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentAdminProps): import('react').JSX.Element;
@@ -0,0 +1,191 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { useReadScope as t } from "../../react/context.js";
3
+ import { useKinds as n } from "../../react/groups.js";
4
+ import { Button as r } from "../../ui/button.js";
5
+ import { AdminRoot as i, LoadFailed as a, Rows as o } from "./shell.js";
6
+ import { useAdminLocation as s, useFlowClient as c } from "../../react/admin.js";
7
+ import { CommentModeration as l } from "../social/moderation.js";
8
+ import { labelOf as u } from "../groups/parts.js";
9
+ import { ContentManager as d } from "./content-manager.js";
10
+ import { GroupManager as f } from "./group-manager.js";
11
+ import { PollManager as p } from "./poll-manager.js";
12
+ import { StatsDashboard as m } from "./stats-dashboard.js";
13
+ import { useId as h, useState as g } from "react";
14
+ import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
15
+ //#region src/components/admin/content-admin.tsx
16
+ var b = [
17
+ "contents",
18
+ "groups",
19
+ "moderation",
20
+ "polls",
21
+ "statistics"
22
+ ], x = [
23
+ "comments",
24
+ "queue",
25
+ "bans",
26
+ "words",
27
+ "ips"
28
+ ];
29
+ function S({ client: e, baseUrl: n, auth: r, guard: a, appearance: o, className: s, language: l, messages: u, ...d }) {
30
+ let f = c({
31
+ client: e,
32
+ baseUrl: n,
33
+ auth: r
34
+ }), p = t(), [m, h] = g({
35
+ client: f,
36
+ scope: p,
37
+ revision: 0
38
+ });
39
+ return (m.client !== f || m.scope !== p) && h({
40
+ client: f,
41
+ scope: p,
42
+ revision: m.revision + 1
43
+ }), /* @__PURE__ */ v(i, {
44
+ flow: "content-admin",
45
+ guard: a,
46
+ appearance: o,
47
+ className: s,
48
+ language: l,
49
+ messages: u,
50
+ children: /* @__PURE__ */ v(D, {
51
+ ...d,
52
+ client: f,
53
+ guard: a
54
+ }, m.revision)
55
+ });
56
+ }
57
+ function C(e, t) {
58
+ return [...new Set(e ?? Object.keys(t).sort())].filter((e) => Object.hasOwn(t, e));
59
+ }
60
+ function w(e, t) {
61
+ return C(e ?? Object.keys(t.group_kinds).filter((e) => t.group_kinds[e]?.owner !== "user").sort(), t.group_kinds);
62
+ }
63
+ function T(e) {
64
+ if (e !== void 0) try {
65
+ return decodeURIComponent(e);
66
+ } catch {
67
+ return;
68
+ }
69
+ }
70
+ function E(e, t, n) {
71
+ return {
72
+ pathname: `${e}/${t}`,
73
+ search: n.query.size ? `?${n.query}` : "",
74
+ navigate(t, r) {
75
+ let i = new URL(t, "https://contentkit.invalid");
76
+ if (i.origin !== "https://contentkit.invalid") return;
77
+ let a = `${e}/`;
78
+ (i.pathname === e || i.pathname.startsWith(a)) && n.go(i.pathname.slice(a.length), Object.fromEntries(i.searchParams), r);
79
+ }
80
+ };
81
+ }
82
+ function D({ client: t, basePath: i, navigation: c, sections: g, contentKinds: S, groupKinds: D, labels: O, guard: k, onError: A, content: j, groups: M, moderation: N, polls: P }) {
83
+ let { t: F } = e(), I = h(), L = s(i, c), R = n({ client: t });
84
+ if (R.loading) return /* @__PURE__ */ v(o, {});
85
+ if (!R.kinds) return /* @__PURE__ */ v(a, {
86
+ error: R.error,
87
+ message: F("contentAdmin.modelFailed"),
88
+ retry: R.reload
89
+ });
90
+ let z = C(S, R.kinds.kinds), B = w(D, R.kinds), V = [...new Set(g ?? b)].filter((e) => b.includes(e) && (e !== "contents" && e !== "statistics" || z.length > 0) && (e !== "groups" || B.length > 0) && (e !== "moderation" || N?.sections?.length !== 0));
91
+ if (!V.length) return /* @__PURE__ */ v("p", {
92
+ role: "status",
93
+ className: "text-muted-foreground",
94
+ children: F("contentAdmin.empty")
95
+ });
96
+ let H = L.view.split("/"), U = L.view ? H[0] : V[0], W = H[1] === void 0 ? void 0 : T(H[1]), G = W ?? z[0], K = N?.sections ?? x, q = V.includes(U) && (U !== "contents" || (H[1] === void 0 || W !== void 0) && z.includes(G)) && (U !== "groups" || H[1] === void 0 || W !== void 0 && B.includes(W)) && (U !== "moderation" || !H[1] || K.includes(H[1])) && (U !== "statistics" || H.length === 1 || !L.view), J = i?.replace(/\/+$/, "") ?? "", Y = U === "contents" ? `contents/${encodeURIComponent(G)}` : U, X = L.view || Y, Z = E(J, U === "contents" && H[1] === void 0 ? Y : X, L), Q = {
97
+ client: t,
98
+ guard: k,
99
+ onError: A
100
+ }, $ = {
101
+ contents: F("contentManager.title"),
102
+ groups: F("contentAdmin.groups"),
103
+ moderation: F("moderation.title"),
104
+ polls: F("pollManager.title"),
105
+ statistics: F("stats.title")
106
+ }, ee = (e) => e === "contents" ? `contents/${encodeURIComponent(z[0])}` : e;
107
+ return /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("h1", {
108
+ className: "text-lg font-semibold",
109
+ children: F("contentAdmin.title")
110
+ }), /* @__PURE__ */ y("div", {
111
+ className: "grid min-w-0 gap-4 sm:grid-cols-[12rem_minmax(0,1fr)]",
112
+ children: [/* @__PURE__ */ v("nav", {
113
+ "aria-label": F("contentAdmin.navigation"),
114
+ className: "flex gap-1 overflow-x-auto sm:grid sm:content-start",
115
+ children: V.map((e) => /* @__PURE__ */ v(r, {
116
+ variant: U === e ? "secondary" : "ghost",
117
+ className: "justify-start whitespace-nowrap",
118
+ "aria-current": U === e ? "page" : void 0,
119
+ onClick: () => L.go(ee(e)),
120
+ children: $[e]
121
+ }, e))
122
+ }), /* @__PURE__ */ v("div", {
123
+ className: "grid min-w-0 content-start gap-4",
124
+ "data-ckui": "content-admin-view",
125
+ children: q ? U === "contents" ? /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ y("label", {
126
+ className: "grid justify-items-start gap-1 text-sm",
127
+ htmlFor: `${I}-kind`,
128
+ children: [F("contentAdmin.kind"), /* @__PURE__ */ v("select", {
129
+ id: `${I}-kind`,
130
+ className: "h-8 rounded-md border border-input bg-background px-2 text-sm",
131
+ value: G,
132
+ onChange: (e) => L.go(`contents/${encodeURIComponent(e.target.value)}`),
133
+ children: z.map((e) => /* @__PURE__ */ v("option", {
134
+ value: e,
135
+ children: u(O, e)
136
+ }, e))
137
+ })]
138
+ }), /* @__PURE__ */ v(d, {
139
+ ...Q,
140
+ ...j,
141
+ kind: G,
142
+ labels: O,
143
+ languages: R.kinds.languages,
144
+ basePath: `${J}/${Y}`,
145
+ navigation: Z
146
+ }, G)] }) : U === "groups" ? /* @__PURE__ */ v(f, {
147
+ ...Q,
148
+ ...M,
149
+ labels: O,
150
+ groupKinds: B,
151
+ basePath: `${J}/groups`,
152
+ navigation: Z
153
+ }) : U === "moderation" ? /* @__PURE__ */ v(l, {
154
+ ...Q,
155
+ ...N,
156
+ contentKinds: z.map((e) => ({
157
+ value: e,
158
+ label: u(O, e)
159
+ })),
160
+ basePath: `${J}/moderation`,
161
+ navigation: Z
162
+ }) : U === "polls" ? /* @__PURE__ */ v(p, {
163
+ ...Q,
164
+ ...P,
165
+ languages: R.kinds.languages,
166
+ basePath: `${J}/polls`,
167
+ navigation: Z
168
+ }) : /* @__PURE__ */ v(m, {
169
+ ...Q,
170
+ kinds: z.map((e) => ({
171
+ value: e,
172
+ label: u(O, e)
173
+ }))
174
+ }) : /* @__PURE__ */ y("div", {
175
+ className: "grid justify-items-start gap-2",
176
+ children: [/* @__PURE__ */ v("p", {
177
+ role: "status",
178
+ children: F("contentAdmin.unavailable")
179
+ }), /* @__PURE__ */ v(r, {
180
+ variant: "outline",
181
+ onClick: () => L.go(""),
182
+ children: F("contentAdmin.back")
183
+ })]
184
+ })
185
+ })]
186
+ })] });
187
+ }
188
+ //#endregion
189
+ export { S as ContentAdmin };
190
+
191
+ //# sourceMappingURL=content-admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-admin.js","names":[],"sources":["../../../src/components/admin/content-admin.tsx"],"sourcesContent":["import { useId, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentModel } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, type AdminLocation, type AdminNavigation } from \"../../react/admin.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport { useReadScope } from \"../../react/context.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\nimport { CommentModeration, type CommentModerationProps } from \"../social/moderation.js\";\nimport { ContentManager, type ContentManagerProps } from \"./content-manager.js\";\nimport { GroupManager, type GroupManagerProps } from \"./group-manager.js\";\nimport { PollManager, type PollManagerProps } from \"./poll-manager.js\";\nimport { AdminRoot, LoadFailed, Rows, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\nimport { StatsDashboard } from \"./stats-dashboard.js\";\n\nexport type ContentAdminSection = \"contents\" | \"groups\" | \"moderation\" | \"polls\" | \"statistics\";\n\n/** An optional reference workbench; each section remains usable as its own component. */\nexport interface ContentAdminProps extends AdminFlowProps, AdminRouteProps {\n /** Sections in navigation order. Default all. Navigation does not grant access. */\n sections?: readonly ContentAdminSection[];\n /** Content kinds in picker order; default all configured kinds. Unknown kinds are omitted. */\n contentKinds?: readonly string[];\n /** Group kinds in picker order; default the configured site-owned kinds. Unknown kinds are omitted. */\n groupKinds?: readonly string[];\n labels?: Labels;\n content?: Pick<ContentManagerProps, \"renderEditor\" | \"contentHref\">;\n groups?: Pick<GroupManagerProps, \"renderContent\">;\n moderation?: Pick<CommentModerationProps, \"itemHref\" | \"userHref\" | \"sections\">;\n polls?: Pick<PollManagerProps, \"freeText\" | \"maxOptions\">;\n}\n\nconst SECTIONS: readonly ContentAdminSection[] = [\"contents\", \"groups\", \"moderation\", \"polls\", \"statistics\"];\nconst MODERATION: readonly string[] = [\"comments\", \"queue\", \"bans\", \"words\", \"ips\"];\n\n/**\n * Model-driven content, group, moderation, poll and statistics screens, with\n * the host's transport, router and theme. Only the selected manager mounts;\n * its native permissions and refusals remain authoritative. This component\n * does not mount a server portal, sign users in or define a publishing workflow.\n */\nexport function ContentAdmin({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentAdminProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n const scope = useReadScope();\n const [boundary, setBoundary] = useState({ client, scope, revision: 0 });\n if (boundary.client !== client || boundary.scope !== scope) {\n setBoundary({ client, scope, revision: boundary.revision + 1 });\n }\n return (\n <AdminRoot flow=\"content-admin\" guard={guard} appearance={appearance} className={className} language={language} messages={messages}>\n <Workbench key={boundary.revision} {...p} client={client} guard={guard} />\n </AdminRoot>\n );\n}\n\ntype WorkbenchProps = Omit<ContentAdminProps, \"client\" | \"baseUrl\" | \"auth\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction knownKinds(wanted: readonly string[] | undefined, configured: object): string[] {\n return [...new Set(wanted ?? Object.keys(configured).sort())].filter((kind) => Object.hasOwn(configured, kind));\n}\n\nfunction groupKinds(wanted: readonly string[] | undefined, model: ContentModel): string[] {\n return knownKinds(wanted ?? Object.keys(model.group_kinds).filter((kind) => model.group_kinds[kind]?.owner !== \"user\").sort(), model.group_kinds);\n}\n\nfunction decode(part: string | undefined): string | undefined {\n if (part === undefined) return undefined;\n try { return decodeURIComponent(part); } catch { return undefined; }\n}\n\n/** Routes nested managers through the same outer history, including memory-only use. */\nfunction childNavigation(base: string, view: string, loc: AdminLocation): AdminNavigation {\n return {\n pathname: `${base}/${view}`,\n search: loc.query.size ? `?${loc.query}` : \"\",\n navigate(to, options) {\n const target = new URL(to, \"https://contentkit.invalid\");\n if (target.origin !== \"https://contentkit.invalid\") return;\n const prefix = `${base}/`;\n if (target.pathname !== base && !target.pathname.startsWith(prefix)) return;\n loc.go(target.pathname.slice(prefix.length), Object.fromEntries(target.searchParams), options);\n },\n };\n}\n\nfunction Workbench({ client, basePath, navigation, sections: wanted, contentKinds: requestedContents, groupKinds: requestedGroups, labels, guard, onError, content, groups, moderation, polls }: WorkbenchProps) {\n const { t } = useMessages();\n const id = useId();\n const loc = useAdminLocation(basePath, navigation);\n const model = useKinds({ client });\n if (model.loading) return <Rows />;\n if (!model.kinds) return <LoadFailed error={model.error} message={t(\"contentAdmin.modelFailed\")} retry={model.reload} />;\n\n const kinds = knownKinds(requestedContents, model.kinds.kinds);\n const managedGroups = groupKinds(requestedGroups, model.kinds);\n const sections = [...new Set(wanted ?? SECTIONS)].filter((section) =>\n SECTIONS.includes(section) &&\n (section !== \"contents\" && section !== \"statistics\" || kinds.length > 0) &&\n (section !== \"groups\" || managedGroups.length > 0) &&\n (section !== \"moderation\" || moderation?.sections?.length !== 0),\n );\n if (!sections.length) return <p role=\"status\" className=\"text-muted-foreground\">{t(\"contentAdmin.empty\")}</p>;\n\n const parts = loc.view.split(\"/\");\n const section = (loc.view ? parts[0] : sections[0]) as ContentAdminSection;\n const requestedKind = parts[1] === undefined ? undefined : decode(parts[1]);\n const kind = requestedKind ?? kinds[0]!;\n const moderationSections: readonly string[] = moderation?.sections ?? MODERATION;\n const valid = sections.includes(section) &&\n (section !== \"contents\" || (parts[1] === undefined || requestedKind !== undefined) && kinds.includes(kind)) &&\n (section !== \"groups\" || parts[1] === undefined || requestedKind !== undefined && managedGroups.includes(requestedKind)) &&\n (section !== \"moderation\" || !parts[1] || moderationSections.includes(parts[1])) &&\n (section !== \"statistics\" || parts.length === 1 || !loc.view);\n const base = basePath?.replace(/\\/+$/, \"\") ?? \"\";\n const prefix = section === \"contents\" ? `contents/${encodeURIComponent(kind)}` : section;\n const view = loc.view || prefix;\n // A contents root selects the first configured kind without changing the URL.\n const effectiveView = section === \"contents\" && parts[1] === undefined ? prefix : view;\n const nav = childNavigation(base, effectiveView, loc);\n const shared = { client, guard, onError };\n const names: Record<ContentAdminSection, string> = {\n contents: t(\"contentManager.title\"), groups: t(\"contentAdmin.groups\"), moderation: t(\"moderation.title\"), polls: t(\"pollManager.title\"), statistics: t(\"stats.title\"),\n };\n const destination = (s: ContentAdminSection) => s === \"contents\" ? `contents/${encodeURIComponent(kinds[0]!)}` : s;\n\n return (\n <>\n <h1 className=\"text-lg font-semibold\">{t(\"contentAdmin.title\")}</h1>\n <div className=\"grid min-w-0 gap-4 sm:grid-cols-[12rem_minmax(0,1fr)]\">\n <nav aria-label={t(\"contentAdmin.navigation\")} className=\"flex gap-1 overflow-x-auto sm:grid sm:content-start\">\n {sections.map((s) => (\n <Button key={s} variant={section === s ? \"secondary\" : \"ghost\"} className=\"justify-start whitespace-nowrap\" aria-current={section === s ? \"page\" : undefined} onClick={() => loc.go(destination(s))}>\n {names[s]}\n </Button>\n ))}\n </nav>\n <div className=\"grid min-w-0 content-start gap-4\" data-ckui=\"content-admin-view\">\n {!valid ? (\n <div className=\"grid justify-items-start gap-2\">\n <p role=\"status\">{t(\"contentAdmin.unavailable\")}</p>\n <Button variant=\"outline\" onClick={() => loc.go(\"\")}>{t(\"contentAdmin.back\")}</Button>\n </div>\n ) : section === \"contents\" ? (\n <>\n <label className=\"grid justify-items-start gap-1 text-sm\" htmlFor={`${id}-kind`}>\n {t(\"contentAdmin.kind\")}\n <select id={`${id}-kind`} className=\"h-8 rounded-md border border-input bg-background px-2 text-sm\" value={kind} onChange={(event) => loc.go(`contents/${encodeURIComponent(event.target.value)}`)}>\n {kinds.map((k) => <option key={k} value={k}>{labelOf(labels, k)}</option>)}\n </select>\n </label>\n <ContentManager key={kind} {...shared} {...content} kind={kind} labels={labels} languages={model.kinds.languages} basePath={`${base}/${prefix}`} navigation={nav} />\n </>\n ) : section === \"groups\" ? (\n <GroupManager {...shared} {...groups} labels={labels} groupKinds={managedGroups} basePath={`${base}/groups`} navigation={nav} />\n ) : section === \"moderation\" ? (\n <CommentModeration {...shared} {...moderation} contentKinds={kinds.map((value) => ({ value, label: labelOf(labels, value) }))} basePath={`${base}/moderation`} navigation={nav} />\n ) : section === \"polls\" ? (\n <PollManager {...shared} {...polls} languages={model.kinds.languages} basePath={`${base}/polls`} navigation={nav} />\n ) : (\n <StatsDashboard {...shared} kinds={kinds.map((value) => ({ value, label: labelOf(labels, value) }))} />\n )}\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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"}
@@ -331,7 +331,7 @@ function j(l) {
331
331
  "aria-hidden": !0
332
332
  }), /* @__PURE__ */ m("div", {
333
333
  ref: L,
334
- className: "relative w-full bg-background pt-1.5 text-foreground shadow-lg",
334
+ className: "relative w-full origin-top animate-in fade-in-0 slide-in-from-top-2 bg-background pt-1.5 text-foreground shadow-lg duration-200 ease-out motion-reduce:animate-none",
335
335
  "data-ckui": "search-panel",
336
336
  onKeyDown: Q,
337
337
  children: /* @__PURE__ */ m("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":[],"sources":["../../../src/components/content/search.tsx"],"sourcesContent":["import { Cancel01Icon, Clock01Icon, File01Icon, Folder01Icon, Image01Icon, Search01Icon, TagsIcon, UserIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type RefObject, type KeyboardEvent } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Card, GroupCard } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useRecentSearches, useTypeahead } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Input } from \"#ckui/ui/input\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\n\nexport interface SearchBoxProps {\n /** Submits the text: the host shows its search page. */\n onSearch: (q: string) => void;\n /** Suggest contents of this kind only. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n groupKinds?: readonly string[];\n defaultValue?: string;\n placeholder?: string;\n /** A suggestion's link; default its canonical path. */\n href?: (item: Card | GroupCard) => string | undefined;\n labels?: Labels;\n /** The reader's recent searches while the box is empty. Default true. */\n recent?: boolean;\n /** Header-wide dropdown under the nearest header/banner; default stays inline. */\n presentation?: \"inline\" | \"header\";\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ntype Suggestion = { type: \"content\"; card: Card } | { type: \"group\"; group: GroupCard } | { type: \"recent\"; query: string; searchedAt?: string };\n\nconst TYPE_ICONS: Record<string, { icon: IconSvgElement; color: string }> = {\n artist: { icon: UserIcon, color: \"text-blue-500 dark:text-blue-400\" },\n series: { icon: Folder01Icon, color: \"text-yellow-600 dark:text-yellow-400\" },\n gallery: { icon: Image01Icon, color: \"text-green-600 dark:text-green-400\" },\n post: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n blog: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n poll: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n character: { icon: UserIcon, color: \"text-purple-500 dark:text-purple-400\" },\n tag: { icon: TagsIcon, color: \"text-orange-500 dark:text-orange-400\" },\n};\n\nfunction useHeaderTop(open: boolean, enabled: boolean, root: RefObject<HTMLDivElement | null>, input: RefObject<HTMLInputElement | null>): number {\n const [top, setTop] = useState(0);\n useLayoutEffect(() => {\n if (!open || !enabled || !root.current) return;\n const anchor = root.current.closest('header, [role=\"banner\"]') ?? input.current ?? root.current;\n const view = root.current.ownerDocument.defaultView;\n if (!view) return;\n const measure = () => setTop(Math.max(0, Math.min(view.innerHeight, anchor.getBoundingClientRect().bottom)));\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(measure);\n observer?.observe(anchor);\n view.addEventListener(\"resize\", measure);\n view.addEventListener(\"scroll\", measure, true);\n view.visualViewport?.addEventListener(\"resize\", measure);\n view.visualViewport?.addEventListener(\"scroll\", measure);\n return () => {\n observer?.disconnect();\n view.removeEventListener(\"resize\", measure);\n view.removeEventListener(\"scroll\", measure, true);\n view.visualViewport?.removeEventListener(\"resize\", measure);\n view.visualViewport?.removeEventListener(\"scroll\", measure);\n };\n }, [open, enabled, root, input]);\n return top;\n}\n\nfunction SearchTypeIcon({ kind }: { kind: string }) {\n const type = TYPE_ICONS[kind];\n return <HugeiconsIcon icon={type?.icon ?? Search01Icon} strokeWidth={2} className={cn(\"size-4 shrink-0\", type?.color ?? \"text-muted-foreground\")} data-search-kind={kind} aria-hidden />;\n}\n\nfunction SearchDate({ value, formatter }: { value?: string; formatter: Intl.DateTimeFormat }) {\n if (!value || Number.isNaN(Date.parse(value))) return null;\n return <time dateTime={value} className=\"shrink-0 text-xs text-muted-foreground\">{formatter.format(new Date(value))}</time>;\n}\n\n/**\n * A search field suggesting contents and groups as the reader types, and\n * recent searches while it is empty; Enter searches, or opens the suggestion chosen.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t, error, language } = useMessages();\n const id = useId();\n const navigate = useContext(ContentKitContext)?.navigate;\n const [text, setText] = useState(p.defaultValue ?? \"\");\n const [open, setOpen] = useState(false);\n const [active, setActive] = useState(-1);\n const root = useRef<HTMLDivElement>(null);\n const panel = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const header = p.presentation === \"header\";\n const top = useHeaderTop(open, header, root, input);\n const dates = useMemo(() => new Intl.DateTimeFormat(language, { dateStyle: \"short\" }), [language]);\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, group_kinds: p.groupKinds, client: p.client });\n const recent = useRecentSearches({ enabled: p.recent !== false && open && empty, client: p.client });\n const items = useMemo<Suggestion[]>(\n () =>\n empty\n ? recent.searches.map((r) => ({ type: \"recent\" as const, query: r.query, searchedAt: r.searched_at }))\n : [...found.contents.map((card) => ({ type: \"content\" as const, card })), ...found.groups.map((group) => ({ type: \"group\" as const, group }))],\n [empty, recent.searches, found.contents, found.groups],\n );\n const current = empty || found.query === text.trim();\n const visible = current ? items : [];\n const selected = visible[active];\n const loading = empty ? recent.loading : !current || found.loading;\n const showRecent = empty && p.recent !== false;\n const emptyMessage = empty ? !header && showRecent && !recent.error ? \"contents.noRecentSearches\" : \"contents.searchHint\" : \"contents.noResults\";\n useEffect(() => {\n if (!open) return;\n const outside = (event: PointerEvent) => {\n if (!root.current?.contains(event.target as Node) && !panel.current?.contains(event.target as Node)) setOpen(false);\n };\n document.addEventListener(\"pointerdown\", outside);\n return () => document.removeEventListener(\"pointerdown\", outside);\n }, [open]);\n const reveal = () => {\n setOpen(true);\n };\n const search = (q: string) => {\n setOpen(false);\n if (q) p.onSearch(q);\n };\n const go = (s: Suggestion) => {\n if (s.type === \"recent\") {\n setText(s.query);\n return search(s.query);\n }\n const url = p.href ? p.href(s.type === \"content\" ? s.card : s.group) : s.type === \"content\" ? s.card.path : s.group.path;\n setOpen(false);\n if (!url) return;\n if (navigate) navigate(url);\n else location.assign(url);\n };\n const panelKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n input.current?.focus();\n setOpen(false);\n setActive(-1);\n };\n const results = (<>\n {showRecent && (\n <div className={cn(\"flex items-center justify-between gap-2 text-muted-foreground\", header ? \"mb-4 text-base font-semibold\" : \"px-2 py-1 text-xs\")}>\n <span id={`${id}-recent`} className=\"flex items-center gap-2\">{header && <HugeiconsIcon icon={Clock01Icon} strokeWidth={2} className=\"size-5\" aria-hidden />}{t(\"contents.recentSearches\")}</span>\n {visible.length > 0 && <button\n type=\"button\"\n className=\"rounded px-1 hover:text-foreground\"\n onMouseDown={(e) => {\n e.preventDefault();\n }}\n onClick={() => void recent.clear()}\n >\n {t(header ? \"contents.clearAllRecent\" : \"contents.clearRecent\")}\n </button>}\n </div>\n )}\n <ul id={`${id}-list`} role=\"listbox\" aria-label={empty ? t(\"contents.recentSearches\") : t(\"contents.suggestions\")} className={cn(\"grid\", header && \"gap-2\")}>\n {visible.map((s, i) => (\n <li\n key={s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`}\n id={`${id}-${i}`}\n role=\"option\"\n aria-selected={i === active}\n data-ckui=\"suggestion\"\n data-type={s.type}\n className={cn(\"flex cursor-pointer items-center\", header ? s.type === \"recent\" ? \"gap-2 rounded-md p-3 text-base hover:bg-foreground/10\" : \"gap-3 rounded-md px-4 py-1 text-base font-medium hover:bg-foreground/10\" : \"gap-2 px-2 py-1.5\", i === active && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(i)}\n onMouseDown={(e) => {\n e.preventDefault();\n go(s);\n }}\n >\n {header && s.type !== \"recent\" ? (\n <>\n <SearchTypeIcon kind={s.type === \"content\" ? s.card.kind : s.group.kind} />\n <span className=\"sr-only\">{labelOf(p.labels, s.type === \"content\" ? s.card.kind : s.group.kind)}</span>\n <span className=\"min-w-0 flex-1 truncate\">{s.type === \"content\" ? s.card.title || s.card.code : s.group.name || s.group.slug}</span>\n </>\n ) : s.type === \"content\" ? (\n <>\n <span className=\"h-10 w-8 shrink-0 overflow-hidden rounded bg-muted\">\n <RenditionImg outputs={s.card.cover} alt=\"\" fit=\"cover\" className=\"size-full\" />\n </span>\n <span className=\"min-w-0 truncate\">{s.card.title || s.card.code}</span>\n </>\n ) : s.type === \"group\" ? (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{s.group.name || s.group.slug}</span>\n <span className=\"text-xs text-muted-foreground\">{labelOf(p.labels, s.group.kind)}</span>\n </>\n ) : (\n <>\n <HugeiconsIcon icon={header ? Search01Icon : Clock01Icon} strokeWidth={2} className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 truncate\">{s.query}</span>\n {header && <SearchDate value={s.searchedAt} formatter={dates} />}\n <button\n type=\"button\"\n className=\"rounded p-0.5 text-muted-foreground hover:text-foreground\"\n aria-label={t(\"contents.removeRecent\", { q: s.query })}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n onClick={() => void recent.clear(s.query)}\n >\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n </button>\n </>\n )}\n </li>\n ))}\n </ul>\n {loading ? <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex justify-center py-4\" : \"px-3 py-2\")}>{header ? <><span className=\"size-6 animate-spin rounded-full border-2 border-muted border-t-foreground motion-reduce:animate-none\" aria-hidden /><span className=\"sr-only\">{t(\"contents.loading\")}</span></> : t(\"contents.loading\")}</div>\n : !empty && found.error ? <p role=\"alert\" className={cn(\"px-3 py-2 text-destructive\", header && \"text-center\")}>{error(found.error)}</p>\n : visible.length === 0 && <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex flex-col items-center gap-2 py-8 text-center\" : \"px-3 py-2\")}>{header && <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-8\" aria-hidden />}<p>{t(emptyMessage, { q: text.trim() })}</p></div>}\n </>);\n return (\n <ContentKitUiRoot ref={root} appearance={p.appearance} className={cn(\"relative text-sm\", p.className)} data-ckui=\"search-box\" onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget) && !panel.current?.contains(event.relatedTarget)) setOpen(false); }}>\n <form\n role=\"search\"\n className=\"relative\"\n onSubmit={(e) => {\n e.preventDefault();\n if (open && selected) return go(selected);\n search(text.trim());\n }}\n >\n {header ? <button type=\"submit\" className=\"absolute top-1/2 left-2.5 -translate-y-1/2 rounded text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\" aria-label={t(\"contents.search\")}><HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n : <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\" aria-hidden />}\n <Input\n ref={input}\n type={header ? \"text\" : \"search\"}\n role=\"combobox\"\n aria-label={t(\"contents.search\")}\n aria-expanded={open}\n aria-controls={`${id}-list`}\n aria-activedescendant={open && selected ? `${id}-${active}` : undefined}\n aria-autocomplete=\"list\"\n autoComplete=\"off\"\n className={cn(\"h-9 ps-8\", header && \"rounded-full border-border/50 bg-foreground/10 pe-9 text-base shadow-none md:text-base\")}\n value={text}\n placeholder={p.placeholder ?? t(\"contents.searchPlaceholder\")}\n onChange={(e) => {\n setText(e.target.value);\n setOpen(true);\n setActive(-1);\n }}\n onFocus={reveal}\n onClick={reveal}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n setActive(-1);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n reveal();\n setActive((a) => open ? Math.min(visible.length - 1, a + 1) : 0);\n } else if (e.key === \"ArrowUp\" && open) {\n e.preventDefault();\n setActive((a) => Math.max(-1, a - 1));\n }\n }}\n />\n {header && (text || open) && (\n <button\n type=\"button\"\n className=\"absolute top-1/2 right-2.5 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\"\n aria-label={text ? t(\"contents.clearSearch\") : t(\"common.close\")}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n input.current?.focus();\n setActive(-1);\n if (text) { setText(\"\"); setOpen(true); }\n else setOpen(false);\n }}\n ><HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n )}\n </form>\n {open && (header ? createPortal(\n <ContentKitUiRoot appearance={p.appearance} className=\"fixed inset-x-0 bottom-0 z-[70] text-sm\" style={{ top }} data-ckui=\"search-layer\">\n <div className=\"absolute inset-0 bg-black/30 backdrop-blur-sm\" data-ckui=\"search-backdrop\" aria-hidden />\n <div ref={panel} className=\"relative w-full bg-background pt-1.5 text-foreground shadow-lg\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>\n <div className=\"mx-auto w-full max-w-[71rem] px-4 pt-5 md:px-[10%]\">\n <div className=\"[--search-max-height:70vh] overflow-y-auto pb-7 md:[--search-max-height:60vh]\" style={{ maxHeight: `min(var(--search-max-height), calc(100dvh - ${top}px - 1.875rem))` }}>\n <div className={empty ? \"px-4 py-4\" : \"px-4\"}>{results}</div>\n </div>\n </div>\n </div>\n </ContentKitUiRoot>, document.body,\n ) : <div ref={panel} className=\"absolute top-full z-20 mt-1 grid max-h-96 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>{results}</div>)}\n\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../../src/components/content/search.tsx"],"sourcesContent":["import { Cancel01Icon, Clock01Icon, File01Icon, Folder01Icon, Image01Icon, Search01Icon, TagsIcon, UserIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type RefObject, type KeyboardEvent } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Card, GroupCard } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useRecentSearches, useTypeahead } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Input } from \"#ckui/ui/input\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\n\nexport interface SearchBoxProps {\n /** Submits the text: the host shows its search page. */\n onSearch: (q: string) => void;\n /** Suggest contents of this kind only. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n groupKinds?: readonly string[];\n defaultValue?: string;\n placeholder?: string;\n /** A suggestion's link; default its canonical path. */\n href?: (item: Card | GroupCard) => string | undefined;\n labels?: Labels;\n /** The reader's recent searches while the box is empty. Default true. */\n recent?: boolean;\n /** Header-wide dropdown under the nearest header/banner; default stays inline. */\n presentation?: \"inline\" | \"header\";\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ntype Suggestion = { type: \"content\"; card: Card } | { type: \"group\"; group: GroupCard } | { type: \"recent\"; query: string; searchedAt?: string };\n\nconst TYPE_ICONS: Record<string, { icon: IconSvgElement; color: string }> = {\n artist: { icon: UserIcon, color: \"text-blue-500 dark:text-blue-400\" },\n series: { icon: Folder01Icon, color: \"text-yellow-600 dark:text-yellow-400\" },\n gallery: { icon: Image01Icon, color: \"text-green-600 dark:text-green-400\" },\n post: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n blog: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n poll: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n character: { icon: UserIcon, color: \"text-purple-500 dark:text-purple-400\" },\n tag: { icon: TagsIcon, color: \"text-orange-500 dark:text-orange-400\" },\n};\n\nfunction useHeaderTop(open: boolean, enabled: boolean, root: RefObject<HTMLDivElement | null>, input: RefObject<HTMLInputElement | null>): number {\n const [top, setTop] = useState(0);\n useLayoutEffect(() => {\n if (!open || !enabled || !root.current) return;\n const anchor = root.current.closest('header, [role=\"banner\"]') ?? input.current ?? root.current;\n const view = root.current.ownerDocument.defaultView;\n if (!view) return;\n const measure = () => setTop(Math.max(0, Math.min(view.innerHeight, anchor.getBoundingClientRect().bottom)));\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(measure);\n observer?.observe(anchor);\n view.addEventListener(\"resize\", measure);\n view.addEventListener(\"scroll\", measure, true);\n view.visualViewport?.addEventListener(\"resize\", measure);\n view.visualViewport?.addEventListener(\"scroll\", measure);\n return () => {\n observer?.disconnect();\n view.removeEventListener(\"resize\", measure);\n view.removeEventListener(\"scroll\", measure, true);\n view.visualViewport?.removeEventListener(\"resize\", measure);\n view.visualViewport?.removeEventListener(\"scroll\", measure);\n };\n }, [open, enabled, root, input]);\n return top;\n}\n\nfunction SearchTypeIcon({ kind }: { kind: string }) {\n const type = TYPE_ICONS[kind];\n return <HugeiconsIcon icon={type?.icon ?? Search01Icon} strokeWidth={2} className={cn(\"size-4 shrink-0\", type?.color ?? \"text-muted-foreground\")} data-search-kind={kind} aria-hidden />;\n}\n\nfunction SearchDate({ value, formatter }: { value?: string; formatter: Intl.DateTimeFormat }) {\n if (!value || Number.isNaN(Date.parse(value))) return null;\n return <time dateTime={value} className=\"shrink-0 text-xs text-muted-foreground\">{formatter.format(new Date(value))}</time>;\n}\n\n/**\n * A search field suggesting contents and groups as the reader types, and\n * recent searches while it is empty; Enter searches, or opens the suggestion chosen.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t, error, language } = useMessages();\n const id = useId();\n const navigate = useContext(ContentKitContext)?.navigate;\n const [text, setText] = useState(p.defaultValue ?? \"\");\n const [open, setOpen] = useState(false);\n const [active, setActive] = useState(-1);\n const root = useRef<HTMLDivElement>(null);\n const panel = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const header = p.presentation === \"header\";\n const top = useHeaderTop(open, header, root, input);\n const dates = useMemo(() => new Intl.DateTimeFormat(language, { dateStyle: \"short\" }), [language]);\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, group_kinds: p.groupKinds, client: p.client });\n const recent = useRecentSearches({ enabled: p.recent !== false && open && empty, client: p.client });\n const items = useMemo<Suggestion[]>(\n () =>\n empty\n ? recent.searches.map((r) => ({ type: \"recent\" as const, query: r.query, searchedAt: r.searched_at }))\n : [...found.contents.map((card) => ({ type: \"content\" as const, card })), ...found.groups.map((group) => ({ type: \"group\" as const, group }))],\n [empty, recent.searches, found.contents, found.groups],\n );\n const current = empty || found.query === text.trim();\n const visible = current ? items : [];\n const selected = visible[active];\n const loading = empty ? recent.loading : !current || found.loading;\n const showRecent = empty && p.recent !== false;\n const emptyMessage = empty ? !header && showRecent && !recent.error ? \"contents.noRecentSearches\" : \"contents.searchHint\" : \"contents.noResults\";\n useEffect(() => {\n if (!open) return;\n const outside = (event: PointerEvent) => {\n if (!root.current?.contains(event.target as Node) && !panel.current?.contains(event.target as Node)) setOpen(false);\n };\n document.addEventListener(\"pointerdown\", outside);\n return () => document.removeEventListener(\"pointerdown\", outside);\n }, [open]);\n const reveal = () => {\n setOpen(true);\n };\n const search = (q: string) => {\n setOpen(false);\n if (q) p.onSearch(q);\n };\n const go = (s: Suggestion) => {\n if (s.type === \"recent\") {\n setText(s.query);\n return search(s.query);\n }\n const url = p.href ? p.href(s.type === \"content\" ? s.card : s.group) : s.type === \"content\" ? s.card.path : s.group.path;\n setOpen(false);\n if (!url) return;\n if (navigate) navigate(url);\n else location.assign(url);\n };\n const panelKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n input.current?.focus();\n setOpen(false);\n setActive(-1);\n };\n const results = (<>\n {showRecent && (\n <div className={cn(\"flex items-center justify-between gap-2 text-muted-foreground\", header ? \"mb-4 text-base font-semibold\" : \"px-2 py-1 text-xs\")}>\n <span id={`${id}-recent`} className=\"flex items-center gap-2\">{header && <HugeiconsIcon icon={Clock01Icon} strokeWidth={2} className=\"size-5\" aria-hidden />}{t(\"contents.recentSearches\")}</span>\n {visible.length > 0 && <button\n type=\"button\"\n className=\"rounded px-1 hover:text-foreground\"\n onMouseDown={(e) => {\n e.preventDefault();\n }}\n onClick={() => void recent.clear()}\n >\n {t(header ? \"contents.clearAllRecent\" : \"contents.clearRecent\")}\n </button>}\n </div>\n )}\n <ul id={`${id}-list`} role=\"listbox\" aria-label={empty ? t(\"contents.recentSearches\") : t(\"contents.suggestions\")} className={cn(\"grid\", header && \"gap-2\")}>\n {visible.map((s, i) => (\n <li\n key={s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`}\n id={`${id}-${i}`}\n role=\"option\"\n aria-selected={i === active}\n data-ckui=\"suggestion\"\n data-type={s.type}\n className={cn(\"flex cursor-pointer items-center\", header ? s.type === \"recent\" ? \"gap-2 rounded-md p-3 text-base hover:bg-foreground/10\" : \"gap-3 rounded-md px-4 py-1 text-base font-medium hover:bg-foreground/10\" : \"gap-2 px-2 py-1.5\", i === active && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(i)}\n onMouseDown={(e) => {\n e.preventDefault();\n go(s);\n }}\n >\n {header && s.type !== \"recent\" ? (\n <>\n <SearchTypeIcon kind={s.type === \"content\" ? s.card.kind : s.group.kind} />\n <span className=\"sr-only\">{labelOf(p.labels, s.type === \"content\" ? s.card.kind : s.group.kind)}</span>\n <span className=\"min-w-0 flex-1 truncate\">{s.type === \"content\" ? s.card.title || s.card.code : s.group.name || s.group.slug}</span>\n </>\n ) : s.type === \"content\" ? (\n <>\n <span className=\"h-10 w-8 shrink-0 overflow-hidden rounded bg-muted\">\n <RenditionImg outputs={s.card.cover} alt=\"\" fit=\"cover\" className=\"size-full\" />\n </span>\n <span className=\"min-w-0 truncate\">{s.card.title || s.card.code}</span>\n </>\n ) : s.type === \"group\" ? (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{s.group.name || s.group.slug}</span>\n <span className=\"text-xs text-muted-foreground\">{labelOf(p.labels, s.group.kind)}</span>\n </>\n ) : (\n <>\n <HugeiconsIcon icon={header ? Search01Icon : Clock01Icon} strokeWidth={2} className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 truncate\">{s.query}</span>\n {header && <SearchDate value={s.searchedAt} formatter={dates} />}\n <button\n type=\"button\"\n className=\"rounded p-0.5 text-muted-foreground hover:text-foreground\"\n aria-label={t(\"contents.removeRecent\", { q: s.query })}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n onClick={() => void recent.clear(s.query)}\n >\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n </button>\n </>\n )}\n </li>\n ))}\n </ul>\n {loading ? <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex justify-center py-4\" : \"px-3 py-2\")}>{header ? <><span className=\"size-6 animate-spin rounded-full border-2 border-muted border-t-foreground motion-reduce:animate-none\" aria-hidden /><span className=\"sr-only\">{t(\"contents.loading\")}</span></> : t(\"contents.loading\")}</div>\n : !empty && found.error ? <p role=\"alert\" className={cn(\"px-3 py-2 text-destructive\", header && \"text-center\")}>{error(found.error)}</p>\n : visible.length === 0 && <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex flex-col items-center gap-2 py-8 text-center\" : \"px-3 py-2\")}>{header && <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-8\" aria-hidden />}<p>{t(emptyMessage, { q: text.trim() })}</p></div>}\n </>);\n return (\n <ContentKitUiRoot ref={root} appearance={p.appearance} className={cn(\"relative text-sm\", p.className)} data-ckui=\"search-box\" onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget) && !panel.current?.contains(event.relatedTarget)) setOpen(false); }}>\n <form\n role=\"search\"\n className=\"relative\"\n onSubmit={(e) => {\n e.preventDefault();\n if (open && selected) return go(selected);\n search(text.trim());\n }}\n >\n {header ? <button type=\"submit\" className=\"absolute top-1/2 left-2.5 -translate-y-1/2 rounded text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\" aria-label={t(\"contents.search\")}><HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n : <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\" aria-hidden />}\n <Input\n ref={input}\n type={header ? \"text\" : \"search\"}\n role=\"combobox\"\n aria-label={t(\"contents.search\")}\n aria-expanded={open}\n aria-controls={`${id}-list`}\n aria-activedescendant={open && selected ? `${id}-${active}` : undefined}\n aria-autocomplete=\"list\"\n autoComplete=\"off\"\n className={cn(\"h-9 ps-8\", header && \"rounded-full border-border/50 bg-foreground/10 pe-9 text-base shadow-none md:text-base\")}\n value={text}\n placeholder={p.placeholder ?? t(\"contents.searchPlaceholder\")}\n onChange={(e) => {\n setText(e.target.value);\n setOpen(true);\n setActive(-1);\n }}\n onFocus={reveal}\n onClick={reveal}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n setActive(-1);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n reveal();\n setActive((a) => open ? Math.min(visible.length - 1, a + 1) : 0);\n } else if (e.key === \"ArrowUp\" && open) {\n e.preventDefault();\n setActive((a) => Math.max(-1, a - 1));\n }\n }}\n />\n {header && (text || open) && (\n <button\n type=\"button\"\n className=\"absolute top-1/2 right-2.5 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\"\n aria-label={text ? t(\"contents.clearSearch\") : t(\"common.close\")}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n input.current?.focus();\n setActive(-1);\n if (text) { setText(\"\"); setOpen(true); }\n else setOpen(false);\n }}\n ><HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n )}\n </form>\n {open && (header ? createPortal(\n <ContentKitUiRoot appearance={p.appearance} className=\"fixed inset-x-0 bottom-0 z-[70] text-sm\" style={{ top }} data-ckui=\"search-layer\">\n <div className=\"absolute inset-0 bg-black/30 backdrop-blur-sm\" data-ckui=\"search-backdrop\" aria-hidden />\n <div ref={panel} className=\"relative w-full origin-top animate-in fade-in-0 slide-in-from-top-2 bg-background pt-1.5 text-foreground shadow-lg duration-200 ease-out motion-reduce:animate-none\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>\n <div className=\"mx-auto w-full max-w-[71rem] px-4 pt-5 md:px-[10%]\">\n <div className=\"[--search-max-height:70vh] overflow-y-auto pb-7 md:[--search-max-height:60vh]\" style={{ maxHeight: `min(var(--search-max-height), calc(100dvh - ${top}px - 1.875rem))` }}>\n <div className={empty ? \"px-4 py-4\" : \"px-4\"}>{results}</div>\n </div>\n </div>\n </div>\n </ContentKitUiRoot>, document.body,\n ) : <div ref={panel} className=\"absolute top-full z-20 mt-1 grid max-h-96 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>{results}</div>)}\n\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export { MediaReadinessNotice, type MediaReadinessNoticeProps } from './componen
21
21
  export * from './components/social/index.js';
22
22
  export * from './components/groups/index.js';
23
23
  export * from './components/content/index.js';
24
+ export { ContentAdmin, type ContentAdminProps, type ContentAdminSection } from './components/admin/content-admin.js';
24
25
  export { UserCommentStanding, useUserCommentStandingAccess, type UserCommentStandingProps } from './components/admin/user-comment-standing.js';
25
26
  export { PollManager, pollState, usePollManagerAccess, type PollManagerProps } from './components/admin/poll-manager.js';
26
27
  export { ContentEditor, ContentManager, contentStatus, useContentManagerAccess, type BodyEditorProps, type ContentEditorProps, type ContentManagerProps, type ContentStatus } from './components/admin/content-manager.js';