@openrails/contentkit-ui 0.104.0-rc.1 → 0.104.0-rc.3

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 (217) hide show
  1. package/README.md +6 -6
  2. package/dist/client/beacons.js +4 -1
  3. package/dist/client/beacons.js.map +1 -1
  4. package/dist/client/contents.d.ts +9 -1
  5. package/dist/client/contents.js +73 -55
  6. package/dist/client/contents.js.map +1 -1
  7. package/dist/client/discovery.d.ts +6 -2
  8. package/dist/client/discovery.js +1 -1
  9. package/dist/client/discovery.js.map +1 -1
  10. package/dist/client/generated/error-codes.d.ts +1 -1
  11. package/dist/client/generated/error-codes.js +4 -0
  12. package/dist/client/generated/error-codes.js.map +1 -1
  13. package/dist/client/generated/routes.d.ts +43 -30
  14. package/dist/client/generated/routes.js +70 -63
  15. package/dist/client/generated/routes.js.map +1 -1
  16. package/dist/client/generated/wire.d.ts +36 -74
  17. package/dist/client/generated/wire.js.map +1 -1
  18. package/dist/client/index.d.ts +1 -1
  19. package/dist/client/media/windows.d.ts +2 -0
  20. package/dist/client/media/windows.js +4 -1
  21. package/dist/client/media/windows.js.map +1 -1
  22. package/dist/client/stats.d.ts +4 -20
  23. package/dist/client/stats.js +0 -11
  24. package/dist/client/stats.js.map +1 -1
  25. package/dist/client/versions.d.ts +8 -6
  26. package/dist/client/versions.js +15 -10
  27. package/dist/client/versions.js.map +1 -1
  28. package/dist/components/admin/bulk-drafts.d.ts +17 -0
  29. package/dist/components/admin/bulk-drafts.js +41 -0
  30. package/dist/components/admin/bulk-drafts.js.map +1 -0
  31. package/dist/components/admin/content-admin.d.ts +1 -1
  32. package/dist/components/admin/content-admin.js.map +1 -1
  33. package/dist/components/admin/content-manager.d.ts +11 -3
  34. package/dist/components/admin/content-manager.js +739 -380
  35. package/dist/components/admin/content-manager.js.map +1 -1
  36. package/dist/components/admin/group-manager.d.ts +8 -3
  37. package/dist/components/admin/group-manager.js +218 -146
  38. package/dist/components/admin/group-manager.js.map +1 -1
  39. package/dist/components/admin/page-notices.d.ts +16 -0
  40. package/dist/components/admin/page-notices.js +113 -0
  41. package/dist/components/admin/page-notices.js.map +1 -0
  42. package/dist/components/admin/page-picker.d.ts +38 -0
  43. package/dist/components/admin/page-picker.js +159 -0
  44. package/dist/components/admin/page-picker.js.map +1 -0
  45. package/dist/components/admin/reader-content-editor.js +537 -500
  46. package/dist/components/admin/reader-content-editor.js.map +1 -1
  47. package/dist/components/admin/reader-manifest.js +181 -179
  48. package/dist/components/admin/reader-manifest.js.map +1 -1
  49. package/dist/components/admin/shell.d.ts +10 -2
  50. package/dist/components/admin/shell.js +114 -42
  51. package/dist/components/admin/shell.js.map +1 -1
  52. package/dist/components/admin/stats-dashboard.d.ts +4 -9
  53. package/dist/components/admin/stats-dashboard.js +300 -202
  54. package/dist/components/admin/stats-dashboard.js.map +1 -1
  55. package/dist/components/admin/upload-queue.d.ts +14 -0
  56. package/dist/components/admin/upload-queue.js +115 -98
  57. package/dist/components/admin/upload-queue.js.map +1 -1
  58. package/dist/components/content/card.d.ts +31 -3
  59. package/dist/components/content/card.js +324 -158
  60. package/dist/components/content/card.js.map +1 -1
  61. package/dist/components/content/daily-releases.js +14 -12
  62. package/dist/components/content/daily-releases.js.map +1 -1
  63. package/dist/components/content/editor-save.js +27 -25
  64. package/dist/components/content/editor-save.js.map +1 -1
  65. package/dist/components/content/editors.js +274 -253
  66. package/dist/components/content/editors.js.map +1 -1
  67. package/dist/components/content/favorites.d.ts +1 -1
  68. package/dist/components/content/favorites.js +36 -22
  69. package/dist/components/content/favorites.js.map +1 -1
  70. package/dist/components/content/grid.d.ts +11 -4
  71. package/dist/components/content/grid.js +52 -43
  72. package/dist/components/content/grid.js.map +1 -1
  73. package/dist/components/content/history.js +83 -63
  74. package/dist/components/content/history.js.map +1 -1
  75. package/dist/components/content/index.d.ts +5 -3
  76. package/dist/components/content/listing.d.ts +14 -3
  77. package/dist/components/content/listing.js +65 -56
  78. package/dist/components/content/listing.js.map +1 -1
  79. package/dist/components/content/offer-fields.js +6 -6
  80. package/dist/components/content/paged-grid.d.ts +10 -0
  81. package/dist/components/content/paged-grid.js +116 -0
  82. package/dist/components/content/paged-grid.js.map +1 -0
  83. package/dist/components/content/pagination.d.ts +19 -0
  84. package/dist/components/content/pagination.js +97 -0
  85. package/dist/components/content/pagination.js.map +1 -0
  86. package/dist/components/content/paywall.d.ts +9 -0
  87. package/dist/components/content/paywall.js +27 -3
  88. package/dist/components/content/paywall.js.map +1 -1
  89. package/dist/components/content/post-list.d.ts +37 -0
  90. package/dist/components/content/post-list.js +203 -0
  91. package/dist/components/content/post-list.js.map +1 -0
  92. package/dist/components/content/reader-settings.d.ts +2 -0
  93. package/dist/components/content/reader-settings.js +1 -1
  94. package/dist/components/content/reader-settings.js.map +1 -1
  95. package/dist/components/groups/banner.d.ts +6 -2
  96. package/dist/components/groups/banner.js +79 -61
  97. package/dist/components/groups/banner.js.map +1 -1
  98. package/dist/components/groups/directory.d.ts +12 -2
  99. package/dist/components/groups/directory.js +168 -105
  100. package/dist/components/groups/directory.js.map +1 -1
  101. package/dist/components/groups/editor.js +10 -10
  102. package/dist/components/groups/editor.js.map +1 -1
  103. package/dist/components/groups/entity-chips.d.ts +61 -0
  104. package/dist/components/groups/entity-chips.js +382 -0
  105. package/dist/components/groups/entity-chips.js.map +1 -0
  106. package/dist/components/groups/group-members.d.ts +29 -0
  107. package/dist/components/groups/group-members.js +474 -0
  108. package/dist/components/groups/group-members.js.map +1 -0
  109. package/dist/components/groups/header.js +53 -45
  110. package/dist/components/groups/header.js.map +1 -1
  111. package/dist/components/groups/index.d.ts +3 -1
  112. package/dist/components/groups/memberships.d.ts +3 -36
  113. package/dist/components/groups/memberships.js +105 -480
  114. package/dist/components/groups/memberships.js.map +1 -1
  115. package/dist/components/groups/scoped.d.ts +23 -0
  116. package/dist/components/groups/scoped.js +92 -0
  117. package/dist/components/groups/scoped.js.map +1 -0
  118. package/dist/components/media-folder-editor.d.ts +5 -0
  119. package/dist/components/media-folder-editor.js +418 -230
  120. package/dist/components/media-folder-editor.js.map +1 -1
  121. package/dist/components/social/poll.js +57 -48
  122. package/dist/components/social/poll.js.map +1 -1
  123. package/dist/components/sortable-list.d.ts +5 -1
  124. package/dist/components/sortable-list.js +97 -81
  125. package/dist/components/sortable-list.js.map +1 -1
  126. package/dist/index.d.ts +1 -1
  127. package/dist/index.js +41 -37
  128. package/dist/locales/admin/de.js +78 -45
  129. package/dist/locales/admin/de.js.map +1 -1
  130. package/dist/locales/admin/en.d.ts +74 -41
  131. package/dist/locales/admin/en.js +78 -45
  132. package/dist/locales/admin/en.js.map +1 -1
  133. package/dist/locales/admin/es.js +78 -45
  134. package/dist/locales/admin/es.js.map +1 -1
  135. package/dist/locales/admin/ja.js +78 -45
  136. package/dist/locales/admin/ja.js.map +1 -1
  137. package/dist/locales/admin/ko.js +78 -45
  138. package/dist/locales/admin/ko.js.map +1 -1
  139. package/dist/locales/admin/zh.js +78 -45
  140. package/dist/locales/admin/zh.js.map +1 -1
  141. package/dist/locales/de.js +2 -0
  142. package/dist/locales/de.js.map +1 -1
  143. package/dist/locales/discovery/de.js +25 -1
  144. package/dist/locales/discovery/de.js.map +1 -1
  145. package/dist/locales/discovery/en.d.ts +24 -0
  146. package/dist/locales/discovery/en.js +25 -1
  147. package/dist/locales/discovery/en.js.map +1 -1
  148. package/dist/locales/discovery/es.js +25 -1
  149. package/dist/locales/discovery/es.js.map +1 -1
  150. package/dist/locales/discovery/ja.js +25 -1
  151. package/dist/locales/discovery/ja.js.map +1 -1
  152. package/dist/locales/discovery/ko.js +25 -1
  153. package/dist/locales/discovery/ko.js.map +1 -1
  154. package/dist/locales/discovery/zh.js +25 -1
  155. package/dist/locales/discovery/zh.js.map +1 -1
  156. package/dist/locales/en.d.ts +120 -43
  157. package/dist/locales/en.js +2 -0
  158. package/dist/locales/en.js.map +1 -1
  159. package/dist/locales/es.js +2 -0
  160. package/dist/locales/es.js.map +1 -1
  161. package/dist/locales/groups/de.js +22 -4
  162. package/dist/locales/groups/de.js.map +1 -1
  163. package/dist/locales/groups/en.d.ts +20 -2
  164. package/dist/locales/groups/en.js +22 -4
  165. package/dist/locales/groups/en.js.map +1 -1
  166. package/dist/locales/groups/es.js +22 -4
  167. package/dist/locales/groups/es.js.map +1 -1
  168. package/dist/locales/groups/ja.js +22 -4
  169. package/dist/locales/groups/ja.js.map +1 -1
  170. package/dist/locales/groups/ko.js +22 -4
  171. package/dist/locales/groups/ko.js.map +1 -1
  172. package/dist/locales/groups/zh.js +22 -4
  173. package/dist/locales/groups/zh.js.map +1 -1
  174. package/dist/locales/ja.js +2 -0
  175. package/dist/locales/ja.js.map +1 -1
  176. package/dist/locales/ko.js +2 -0
  177. package/dist/locales/ko.js.map +1 -1
  178. package/dist/locales/zh.js +2 -0
  179. package/dist/locales/zh.js.map +1 -1
  180. package/dist/react/contents.js +1 -1
  181. package/dist/react/contents.js.map +1 -1
  182. package/dist/react/discovery.d.ts +16 -0
  183. package/dist/react/discovery.js +69 -37
  184. package/dist/react/discovery.js.map +1 -1
  185. package/dist/react/groups.d.ts +19 -3
  186. package/dist/react/groups.js +49 -22
  187. package/dist/react/groups.js.map +1 -1
  188. package/dist/react/index.d.ts +6 -4
  189. package/dist/react/page-notices.d.ts +13 -0
  190. package/dist/react/page-notices.js +38 -0
  191. package/dist/react/page-notices.js.map +1 -0
  192. package/dist/react/post-reads.d.ts +23 -0
  193. package/dist/react/post-reads.js +90 -0
  194. package/dist/react/post-reads.js.map +1 -0
  195. package/dist/react/remembered-sort.d.ts +5 -0
  196. package/dist/react/remembered-sort.js +29 -0
  197. package/dist/react/remembered-sort.js.map +1 -0
  198. package/dist/react/resources.js +2 -2
  199. package/dist/react/resources.js.map +1 -1
  200. package/dist/react/stats.d.ts +3 -7
  201. package/dist/react/stats.js +2 -6
  202. package/dist/react/stats.js.map +1 -1
  203. package/dist/react/use-engagement.d.ts +2 -0
  204. package/dist/react/use-engagement.js +40 -34
  205. package/dist/react/use-engagement.js.map +1 -1
  206. package/dist/react.js +29 -27
  207. package/dist/styles.css +1 -1
  208. package/dist/urls/index.d.ts +1 -1
  209. package/dist/urls.js +2 -2
  210. package/dist/urls.js.map +1 -1
  211. package/package.json +1 -1
  212. package/dist/components/admin/page-review.d.ts +0 -13
  213. package/dist/components/admin/page-review.js +0 -225
  214. package/dist/components/admin/page-review.js.map +0 -1
  215. package/dist/react/page-review.d.ts +0 -14
  216. package/dist/react/page-review.js +0 -37
  217. package/dist/react/page-review.js.map +0 -1
@@ -8,39 +8,40 @@ import { ImageCropDialog as s } from "../image-crop-dialog.js";
8
8
  import { Badge as c } from "../../ui/badge.js";
9
9
  import { Skeleton as l } from "../../ui/skeleton.js";
10
10
  import { ErrorLine as u, contentError as d, useAction as f } from "../social/parts.js";
11
- import { GroupAvatar as p, GroupCover as m } from "./parts.js";
12
- import { priceLabel as h } from "../content/paywall.js";
13
- import { useContext as g, useRef as _, useState as v } from "react";
14
- import { jsx as y, jsxs as b } from "react/jsx-runtime";
15
- import { cn as x } from "cn";
11
+ import { GroupAvatar as p, GroupCover as m, GroupLink as h } from "./parts.js";
12
+ import { priceLabel as g } from "../content/paywall.js";
13
+ import { ScopedGroups as _ } from "./scoped.js";
14
+ import { useContext as v, useRef as y, useState as b } from "react";
15
+ import { jsx as x, jsxs as S } from "react/jsx-runtime";
16
+ import { cn as C } from "cn";
16
17
  //#region src/components/groups/banner.tsx
17
- function S({ group: i, onCheckout: a, onSignIn: s, client: l, onError: p, className: m, appearance: _ }) {
18
- let v = e(), { t: S } = v, C = r(l), w = g(n), T = f("membership.join", p), E = i.access;
18
+ function w({ group: i, onCheckout: a, onSignIn: s, client: l, onError: p, className: m, appearance: h }) {
19
+ let _ = e(), { t: y } = _, b = r(l), w = v(n), T = f("membership.join", p), E = i.access;
19
20
  if (!E || E.editor || !E.offer && !E.member && !E.unknown) return null;
20
21
  let D = w?.viewer === null, O = s ?? w?.onSignIn, k = E.offer && Number(E.offer.terms.amount) > 0 ? E.offer : void 0;
21
- return /* @__PURE__ */ b(t, {
22
- appearance: _,
23
- className: x("grid gap-2 rounded-lg border border-border p-3 text-sm", m),
22
+ return /* @__PURE__ */ S(t, {
23
+ appearance: h,
24
+ className: C("grid gap-2 rounded-lg border border-border p-3 text-sm", m),
24
25
  "data-ckui": "membership-box",
25
26
  "data-member": E.member,
26
- children: [E.unknown ? /* @__PURE__ */ y("p", {
27
+ children: [E.unknown ? /* @__PURE__ */ x("p", {
27
28
  className: "text-muted-foreground",
28
- children: S("membership.unknown")
29
- }) : E.member ? /* @__PURE__ */ b("div", {
29
+ children: y("membership.unknown")
30
+ }) : E.member ? /* @__PURE__ */ S("div", {
30
31
  className: "flex flex-wrap items-center gap-2",
31
- children: [/* @__PURE__ */ y(c, { children: S("membership.member") }), !k && /* @__PURE__ */ y(o, {
32
+ children: [/* @__PURE__ */ x(c, { children: y("membership.member") }), !k && /* @__PURE__ */ x(o, {
32
33
  size: "sm",
33
34
  variant: "ghost",
34
35
  disabled: T.pending,
35
- onClick: () => void T.run(() => C.offers.join(i.id, !1)),
36
- children: S("membership.leave")
36
+ onClick: () => void T.run(() => b.offers.join(i.id, !1)),
37
+ children: y("membership.leave")
37
38
  })]
38
- }) : /* @__PURE__ */ b("div", {
39
+ }) : /* @__PURE__ */ S("div", {
39
40
  className: "grid gap-2",
40
- children: [/* @__PURE__ */ y("p", {
41
+ children: [/* @__PURE__ */ x("p", {
41
42
  className: "text-muted-foreground",
42
- children: S(k ? "membership.paidPitch" : "membership.freePitch")
43
- }), /* @__PURE__ */ y(o, {
43
+ children: y(k ? "membership.paidPitch" : "membership.freePitch")
44
+ }), /* @__PURE__ */ x(o, {
44
45
  size: "sm",
45
46
  className: "justify-self-start",
46
47
  disabled: T.pending || !!k && !a,
@@ -50,50 +51,59 @@ function S({ group: i, onCheckout: a, onSignIn: s, client: l, onError: p, classN
50
51
  key: E.key,
51
52
  offer: k
52
53
  });
53
- T.run(() => C.offers.join(i.id));
54
+ T.run(() => b.offers.join(i.id));
54
55
  },
55
- children: k ? S("membership.joinFor", { price: h(v, k.terms) }) : S("membership.join")
56
+ children: k ? y("membership.joinFor", { price: g(_, k.terms) }) : y("membership.join")
56
57
  })]
57
- }), T.error && /* @__PURE__ */ y(u, { children: d(v, T.error) })]
58
+ }), T.error && /* @__PURE__ */ x(u, { children: d(_, T.error) })]
58
59
  });
59
60
  }
60
- function C(n) {
61
+ function T(n) {
61
62
  let r = e(), i = a(n.group ? null : n.id, {
62
63
  language: n.language,
63
64
  client: n.client
64
65
  }), o = n.group ?? i.group;
65
- if (!o) return i.error ? /* @__PURE__ */ y(u, { children: d(r, i.error) }) : /* @__PURE__ */ y(l, { className: "h-48 w-full" });
66
+ if (!o) return i.error ? /* @__PURE__ */ x(u, { children: d(r, i.error) }) : /* @__PURE__ */ x(l, { className: "h-48 w-full" });
66
67
  let s = o.text.find((e) => e.language === (n.language ?? o.name_language)) ?? o.text.find((e) => e.description), c = o.counts.filter((e) => e.language === (n.language ?? "")).reduce((e, t) => e + t.count, 0);
67
- return /* @__PURE__ */ b(t, {
68
+ return /* @__PURE__ */ S(t, {
68
69
  appearance: n.appearance,
69
- className: x("grid gap-3 text-sm", n.className),
70
+ className: C("grid gap-3 text-sm", n.className),
70
71
  "data-ckui": "group-banner",
71
72
  "data-kind": o.kind,
72
73
  children: [
73
- /* @__PURE__ */ y(m, {
74
+ /* @__PURE__ */ x(m, {
74
75
  group: o,
75
76
  className: "aspect-[3/1] w-full"
76
77
  }),
77
- /* @__PURE__ */ b("div", {
78
+ /* @__PURE__ */ S("div", {
78
79
  className: "flex flex-wrap items-end gap-4 px-2 sm:px-4",
79
80
  children: [
80
- /* @__PURE__ */ y(p, {
81
+ /* @__PURE__ */ x(p, {
81
82
  group: o,
82
83
  className: "-mt-12 size-20 border-4 border-background sm:-mt-16 sm:size-28"
83
84
  }),
84
- /* @__PURE__ */ b("div", {
85
+ /* @__PURE__ */ S("div", {
85
86
  className: "grid min-w-0 flex-1 gap-1",
86
- children: [/* @__PURE__ */ y("h1", {
87
- className: "text-xl font-semibold break-words",
88
- children: o.name || o.slug
89
- }), /* @__PURE__ */ y("p", {
90
- className: "text-xs text-muted-foreground tabular-nums",
91
- children: r.plural("groups.items", c)
92
- })]
87
+ children: [
88
+ o.scope && /* @__PURE__ */ x(h, {
89
+ group: o.scope,
90
+ href: n.href,
91
+ className: "text-xs text-muted-foreground hover:underline",
92
+ children: o.scope.name || o.scope.slug
93
+ }),
94
+ /* @__PURE__ */ x("h1", {
95
+ className: "text-xl font-semibold break-words",
96
+ children: o.name || o.slug
97
+ }),
98
+ /* @__PURE__ */ x("p", {
99
+ className: "text-xs text-muted-foreground tabular-nums",
100
+ children: r.plural("groups.items", c)
101
+ })
102
+ ]
93
103
  }),
94
- /* @__PURE__ */ b("div", {
104
+ /* @__PURE__ */ S("div", {
95
105
  className: "flex flex-wrap items-start gap-2",
96
- children: [/* @__PURE__ */ y(S, {
106
+ children: [/* @__PURE__ */ x(w, {
97
107
  group: o,
98
108
  onCheckout: n.onCheckout,
99
109
  client: n.client
@@ -101,60 +111,68 @@ function C(n) {
101
111
  })
102
112
  ]
103
113
  }),
104
- s?.description && /* @__PURE__ */ y("p", {
114
+ s?.description && /* @__PURE__ */ x("p", {
105
115
  className: "px-2 break-words whitespace-pre-line sm:px-4",
106
116
  children: s.description
117
+ }),
118
+ /* @__PURE__ */ x(_, {
119
+ group: o,
120
+ language: n.language,
121
+ labels: n.labels,
122
+ href: n.href,
123
+ client: n.client,
124
+ className: "px-2 sm:px-4"
107
125
  })
108
126
  ]
109
127
  });
110
128
  }
111
- function w({ group: t, slot: n, crop: r = !0, client: c, onError: l, className: d, management: h }) {
112
- let { t: g } = e(), S = a(t.id, {
129
+ function E({ group: t, slot: n, crop: r = !0, client: c, onError: l, className: d, management: h }) {
130
+ let { t: g } = e(), _ = a(t.id, {
113
131
  client: c,
114
132
  management: h
115
- }), C = _(null), w = f("group.save", l), [T, E] = v(null), [D, O] = v(null), k = S.group ?? t, A = n === "cover" ? !!k.cover?.image || !!k.cover?.content_id && !k.cover.auto : !!k.avatar?.image, j = (e, t) => w.run(() => S.uploadImage(n, e, {
133
+ }), v = y(null), w = f("group.save", l), [T, E] = b(null), [D, O] = b(null), k = _.group ?? t, A = n === "cover" ? !!k.cover?.image || !!k.cover?.content_id && !k.cover.auto : !!k.avatar?.image, j = (e, t) => w.run(() => _.uploadImage(n, e, {
116
134
  wait: !1,
117
135
  edit: t
118
136
  })), M = () => {
119
137
  T?.revoke?.(), E(null), O(null);
120
138
  };
121
- return /* @__PURE__ */ b("div", {
122
- className: x("flex flex-wrap items-center gap-3", d),
139
+ return /* @__PURE__ */ S("div", {
140
+ className: C("flex flex-wrap items-center gap-3", d),
123
141
  "data-ckui": "group-image-editor",
124
142
  "data-slot": n,
125
143
  children: [
126
- n === "avatar" ? /* @__PURE__ */ y(p, {
144
+ n === "avatar" ? /* @__PURE__ */ x(p, {
127
145
  group: k,
128
- src: S.previews.avatar,
146
+ src: _.previews.avatar,
129
147
  className: "size-16"
130
- }) : /* @__PURE__ */ y(m, {
148
+ }) : /* @__PURE__ */ x(m, {
131
149
  group: k,
132
- src: S.previews.cover,
150
+ src: _.previews.cover,
133
151
  className: "aspect-[3/1] w-48"
134
152
  }),
135
- /* @__PURE__ */ b("div", {
153
+ /* @__PURE__ */ S("div", {
136
154
  className: "grid gap-2",
137
155
  children: [
138
- /* @__PURE__ */ b("div", {
156
+ /* @__PURE__ */ S("div", {
139
157
  className: "flex flex-wrap gap-2",
140
- children: [/* @__PURE__ */ y(o, {
158
+ children: [/* @__PURE__ */ x(o, {
141
159
  type: "button",
142
160
  size: "sm",
143
161
  variant: "outline",
144
162
  disabled: w.pending,
145
- onClick: () => C.current?.click(),
163
+ onClick: () => v.current?.click(),
146
164
  children: w.pending ? g("groupEditor.uploading") : g(n === "avatar" ? "groupEditor.uploadAvatar" : "groupEditor.uploadCover")
147
- }), A && /* @__PURE__ */ y(o, {
165
+ }), A && /* @__PURE__ */ x(o, {
148
166
  type: "button",
149
167
  size: "sm",
150
168
  variant: "ghost",
151
169
  disabled: w.pending,
152
- onClick: () => void w.run(() => S.update(n === "cover" ? { cover: {} } : { avatar: {} })),
170
+ onClick: () => void w.run(() => _.update(n === "cover" ? { cover: {} } : { avatar: {} })),
153
171
  children: g(n === "avatar" ? "groupEditor.removeAvatar" : "groupEditor.coverUseAuto")
154
172
  })]
155
173
  }),
156
- /* @__PURE__ */ y("input", {
157
- ref: C,
174
+ /* @__PURE__ */ x("input", {
175
+ ref: v,
158
176
  type: "file",
159
177
  accept: "image/*",
160
178
  hidden: !0,
@@ -169,10 +187,10 @@ function w({ group: t, slot: n, crop: r = !0, client: c, onError: l, className:
169
187
  }
170
188
  }
171
189
  }),
172
- /* @__PURE__ */ y(u, { error: w.error })
190
+ /* @__PURE__ */ x(u, { error: w.error })
173
191
  ]
174
192
  }),
175
- /* @__PURE__ */ y(s, {
193
+ /* @__PURE__ */ x(s, {
176
194
  open: !!T,
177
195
  onOpenChange: (e) => !e && M(),
178
196
  source: T,
@@ -192,6 +210,6 @@ function w({ group: t, slot: n, crop: r = !0, client: c, onError: l, className:
192
210
  });
193
211
  }
194
212
  //#endregion
195
- export { C as GroupBanner, w as GroupImageEditor, S as MembershipBox };
213
+ export { T as GroupBanner, E as GroupImageEditor, w as MembershipBox };
196
214
 
197
215
  //# sourceMappingURL=banner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"banner.js","names":[],"sources":["../../../src/components/groups/banner.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useRef, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Edit, GroupAccess, GroupDetail, Sale } from \"../../client/generated/wire.js\";\nimport type { EntityImageSlot } from \"../../client/entities.js\";\nimport { decodeImage, type CropSource } from \"../../client/image.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext, useContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useGroup } from \"../../react/groups.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { ImageCropDialog } from \"../image-crop-dialog.js\";\nimport { contentError, ErrorLine, useAction } from \"../social/parts.js\";\nimport { priceLabel } from \"../content/paywall.js\";\nimport { GroupAvatar, GroupCover, type GroupLike } from \"./parts.js\";\n\nexport interface MembershipBoxProps {\n group: Pick<GroupDetail, \"id\" | \"kind\" | \"name\"> & { access?: GroupAccess };\n /** Buys a sold membership: the host creates the order through billing-ui. */\n onCheckout?: (o: { key: string; offer: Sale }) => void;\n /** Asks a signed-out visitor to sign in; default the provider's. */\n onSignIn?: () => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A group's membership for the reader: member, join free, or join for its price; nothing to join while it sells no membership. */\nexport function MembershipBox({ group, onCheckout, onSignIn, client: own, onError, className, appearance }: MembershipBoxProps) {\n const m = useMessages();\n const { t } = m;\n const client = useContentKitClient(own);\n const ctx = useContext(ContentKitContext);\n const join = useAction(\"membership.join\", onError);\n const a = group.access;\n if (!a || a.editor || (!a.offer && !a.member && !a.unknown)) return null;\n const signedOut = ctx?.viewer === null;\n const signIn = onSignIn ?? ctx?.onSignIn;\n const offer: Sale | undefined = a.offer && Number(a.offer.terms.amount) > 0 ? a.offer : undefined;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-2 rounded-lg border border-border p-3 text-sm\", className)} data-ckui=\"membership-box\" data-member={a.member}>\n {a.unknown ? (\n <p className=\"text-muted-foreground\">{t(\"membership.unknown\")}</p>\n ) : a.member ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge>{t(\"membership.member\")}</Badge>\n {!offer && (\n <Button size=\"sm\" variant=\"ghost\" disabled={join.pending} onClick={() => void join.run(() => client.offers.join(group.id, false))}>\n {t(\"membership.leave\")}\n </Button>\n )}\n </div>\n ) : (\n <div className=\"grid gap-2\">\n <p className=\"text-muted-foreground\">{t(offer ? \"membership.paidPitch\" : \"membership.freePitch\")}</p>\n <Button\n size=\"sm\"\n className=\"justify-self-start\"\n disabled={join.pending || (!!offer && !onCheckout)}\n onClick={() => {\n if (signedOut && signIn) return signIn();\n if (offer) return onCheckout?.({ key: a.key, offer });\n void join.run(() => client.offers.join(group.id));\n }}\n >\n {offer ? t(\"membership.joinFor\", { price: priceLabel(m, offer.terms) }) : t(\"membership.join\")}\n </Button>\n </div>\n )}\n {join.error && <ErrorLine>{contentError(m, join.error)}</ErrorLine>}\n </ContentKitUiRoot>\n );\n}\n\nexport interface GroupBannerProps {\n /** The group the host already read; or give id. */\n group?: GroupDetail;\n id?: string;\n language?: string;\n onCheckout?: MembershipBoxProps[\"onCheckout\"];\n /** Beside the membership box: the host's actions (follow, share, edit). */\n children?: ReactNode;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A group's page head: its cover across, its avatar, name, description, content count and membership. */\nexport function GroupBanner(p: GroupBannerProps) {\n const m = useMessages();\n const read = useGroup(p.group ? null : p.id, { language: p.language, client: p.client });\n const g = p.group ?? read.group;\n if (!g) return read.error ? <ErrorLine>{contentError(m, read.error)}</ErrorLine> : <Skeleton className=\"h-48 w-full\" />;\n const text = g.text.find((x) => x.language === (p.language ?? g.name_language)) ?? g.text.find((x) => x.description);\n const count = g.counts.filter((c) => c.language === (p.language ?? \"\")).reduce((n, c) => n + c.count, 0);\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"group-banner\" data-kind={g.kind}>\n <GroupCover group={g} className=\"aspect-[3/1] w-full\" />\n <div className=\"flex flex-wrap items-end gap-4 px-2 sm:px-4\">\n <GroupAvatar group={g} className=\"-mt-12 size-20 border-4 border-background sm:-mt-16 sm:size-28\" />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <h1 className=\"text-xl font-semibold break-words\">{g.name || g.slug}</h1>\n <p className=\"text-xs text-muted-foreground tabular-nums\">{m.plural(\"groups.items\", count)}</p>\n </div>\n <div className=\"flex flex-wrap items-start gap-2\">\n <MembershipBox group={g} onCheckout={p.onCheckout} client={p.client} />\n {p.children}\n </div>\n </div>\n {text?.description && <p className=\"px-2 break-words whitespace-pre-line sm:px-4\">{text.description}</p>}\n </ContentKitUiRoot>\n );\n}\n\nexport interface GroupImageEditorProps {\n group: GroupLike;\n /** Read the entity through the staff management API, including internal entities. */\n management?: boolean;\n /** The cover, or a user-owned kind's avatar. */\n slot: EntityImageSlot;\n /** Crop before uploading. Default true. */\n crop?: boolean;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n}\n\n/** A group's cover or avatar for its editors: pick (and crop) an image, upload it and select it; or go back to none (a cover: automatic). */\nexport function GroupImageEditor({ group, slot, crop = true, client, onError, className, management }: GroupImageEditorProps) {\n const { t } = useMessages();\n const g = useGroup(group.id, { client, management });\n const input = useRef<HTMLInputElement>(null);\n const save = useAction(\"group.save\", onError);\n const [source, setSource] = useState<CropSource | null>(null);\n const [file, setFile] = useState<File | null>(null);\n const shown = g.group ?? group;\n const set = slot === \"cover\" ? !!shown.cover?.image || (!!shown.cover?.content_id && !shown.cover.auto) : !!shown.avatar?.image;\n const upload = (f: Blob & { name?: string }, edit?: Edit) => save.run(() => g.uploadImage(slot, f, { wait: false, edit }));\n const close = () => {\n source?.revoke?.();\n setSource(null);\n setFile(null);\n };\n return (\n <div className={cn(\"flex flex-wrap items-center gap-3\", className)} data-ckui=\"group-image-editor\" data-slot={slot}>\n {slot === \"avatar\" ? <GroupAvatar group={shown} src={g.previews.avatar} className=\"size-16\" /> : <GroupCover group={shown} src={g.previews.cover} className=\"aspect-[3/1] w-48\" />}\n <div className=\"grid gap-2\">\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={save.pending} onClick={() => input.current?.click()}>\n {save.pending ? t(\"groupEditor.uploading\") : t(slot === \"avatar\" ? \"groupEditor.uploadAvatar\" : \"groupEditor.uploadCover\")}\n </Button>\n {set && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={save.pending} onClick={() => void save.run(() => g.update(slot === \"cover\" ? { cover: {} } : { avatar: {} }))}>\n {t(slot === \"avatar\" ? \"groupEditor.removeAvatar\" : \"groupEditor.coverUseAuto\")}\n </Button>\n )}\n </div>\n <input\n ref={input}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={t(slot === \"avatar\" ? \"groupEditor.uploadAvatar\" : \"groupEditor.uploadCover\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (!f) return;\n if (!crop) return void upload(f);\n decodeImage(f).then(\n (s) => {\n setFile(f);\n setSource(s);\n },\n () => void upload(f),\n );\n }}\n />\n <ErrorLine error={save.error} />\n </div>\n <ImageCropDialog\n open={!!source}\n onOpenChange={(open) => !open && close()}\n source={source}\n aspect={slot === \"avatar\" ? \"1:1\" : \"3:1\"}\n aspects={slot === \"avatar\" ? undefined : [\"3:1\", \"16:9\", \"\"]}\n round={slot === \"avatar\"}\n onConfirm={(edit) => {\n const f = file;\n close();\n if (f) void upload(f, edit ?? undefined);\n }}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"banner.js","names":[],"sources":["../../../src/components/groups/banner.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useRef, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Edit, GroupAccess, GroupDetail, Sale } from \"../../client/generated/wire.js\";\nimport type { EntityImageSlot } from \"../../client/entities.js\";\nimport { decodeImage, type CropSource } from \"../../client/image.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext, useContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useGroup } from \"../../react/groups.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { ImageCropDialog } from \"../image-crop-dialog.js\";\nimport { contentError, ErrorLine, useAction } from \"../social/parts.js\";\nimport { priceLabel } from \"../content/paywall.js\";\nimport { GroupAvatar, GroupCover, GroupLink, type GroupLike, type Labels } from \"./parts.js\";\nimport { ScopedGroups } from \"./scoped.js\";\n\nexport interface MembershipBoxProps {\n group: Pick<GroupDetail, \"id\" | \"kind\" | \"name\"> & { access?: GroupAccess };\n /** Buys a sold membership: the host creates the order through billing-ui. */\n onCheckout?: (o: { key: string; offer: Sale }) => void;\n /** Asks a signed-out visitor to sign in; default the provider's. */\n onSignIn?: () => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A group's membership for the reader: member, join free, or join for its price; nothing to join while it sells no membership. */\nexport function MembershipBox({ group, onCheckout, onSignIn, client: own, onError, className, appearance }: MembershipBoxProps) {\n const m = useMessages();\n const { t } = m;\n const client = useContentKitClient(own);\n const ctx = useContext(ContentKitContext);\n const join = useAction(\"membership.join\", onError);\n const a = group.access;\n if (!a || a.editor || (!a.offer && !a.member && !a.unknown)) return null;\n const signedOut = ctx?.viewer === null;\n const signIn = onSignIn ?? ctx?.onSignIn;\n const offer: Sale | undefined = a.offer && Number(a.offer.terms.amount) > 0 ? a.offer : undefined;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-2 rounded-lg border border-border p-3 text-sm\", className)} data-ckui=\"membership-box\" data-member={a.member}>\n {a.unknown ? (\n <p className=\"text-muted-foreground\">{t(\"membership.unknown\")}</p>\n ) : a.member ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge>{t(\"membership.member\")}</Badge>\n {!offer && (\n <Button size=\"sm\" variant=\"ghost\" disabled={join.pending} onClick={() => void join.run(() => client.offers.join(group.id, false))}>\n {t(\"membership.leave\")}\n </Button>\n )}\n </div>\n ) : (\n <div className=\"grid gap-2\">\n <p className=\"text-muted-foreground\">{t(offer ? \"membership.paidPitch\" : \"membership.freePitch\")}</p>\n <Button\n size=\"sm\"\n className=\"justify-self-start\"\n disabled={join.pending || (!!offer && !onCheckout)}\n onClick={() => {\n if (signedOut && signIn) return signIn();\n if (offer) return onCheckout?.({ key: a.key, offer });\n void join.run(() => client.offers.join(group.id));\n }}\n >\n {offer ? t(\"membership.joinFor\", { price: priceLabel(m, offer.terms) }) : t(\"membership.join\")}\n </Button>\n </div>\n )}\n {join.error && <ErrorLine>{contentError(m, join.error)}</ErrorLine>}\n </ContentKitUiRoot>\n );\n}\n\nexport interface GroupBannerProps {\n /** The group the host already read; or give id. */\n group?: GroupDetail;\n id?: string;\n language?: string;\n onCheckout?: MembershipBoxProps[\"onCheckout\"];\n /** Section titles of scoped entities (e.g. \"characters\"); default humanized. */\n labels?: Labels;\n /** A group's link (scope, scoped entities); default its canonical path. */\n href?: (group: GroupLike) => string | undefined;\n /** Beside the membership box: the host's actions (follow, share, edit). */\n children?: ReactNode;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A group's page head: its cover across, its avatar, scope, name, description, content count and membership, then the entities it scopes. */\nexport function GroupBanner(p: GroupBannerProps) {\n const m = useMessages();\n const read = useGroup(p.group ? null : p.id, { language: p.language, client: p.client });\n const g = p.group ?? read.group;\n if (!g) return read.error ? <ErrorLine>{contentError(m, read.error)}</ErrorLine> : <Skeleton className=\"h-48 w-full\" />;\n const text = g.text.find((x) => x.language === (p.language ?? g.name_language)) ?? g.text.find((x) => x.description);\n const count = g.counts.filter((c) => c.language === (p.language ?? \"\")).reduce((n, c) => n + c.count, 0);\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"group-banner\" data-kind={g.kind}>\n <GroupCover group={g} className=\"aspect-[3/1] w-full\" />\n <div className=\"flex flex-wrap items-end gap-4 px-2 sm:px-4\">\n <GroupAvatar group={g} className=\"-mt-12 size-20 border-4 border-background sm:-mt-16 sm:size-28\" />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n {g.scope && (\n <GroupLink group={g.scope} href={p.href} className=\"text-xs text-muted-foreground hover:underline\">\n {g.scope.name || g.scope.slug}\n </GroupLink>\n )}\n <h1 className=\"text-xl font-semibold break-words\">{g.name || g.slug}</h1>\n <p className=\"text-xs text-muted-foreground tabular-nums\">{m.plural(\"groups.items\", count)}</p>\n </div>\n <div className=\"flex flex-wrap items-start gap-2\">\n <MembershipBox group={g} onCheckout={p.onCheckout} client={p.client} />\n {p.children}\n </div>\n </div>\n {text?.description && <p className=\"px-2 break-words whitespace-pre-line sm:px-4\">{text.description}</p>}\n <ScopedGroups group={g} language={p.language} labels={p.labels} href={p.href} client={p.client} className=\"px-2 sm:px-4\" />\n </ContentKitUiRoot>\n );\n}\n\nexport interface GroupImageEditorProps {\n group: GroupLike;\n /** Read the entity through the staff management API, including internal entities. */\n management?: boolean;\n /** The cover, or a user-owned kind's avatar. */\n slot: EntityImageSlot;\n /** Crop before uploading. Default true. */\n crop?: boolean;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n}\n\n/** A group's cover or avatar for its editors: pick (and crop) an image, upload it and select it; or go back to none (a cover: automatic). */\nexport function GroupImageEditor({ group, slot, crop = true, client, onError, className, management }: GroupImageEditorProps) {\n const { t } = useMessages();\n const g = useGroup(group.id, { client, management });\n const input = useRef<HTMLInputElement>(null);\n const save = useAction(\"group.save\", onError);\n const [source, setSource] = useState<CropSource | null>(null);\n const [file, setFile] = useState<File | null>(null);\n const shown = g.group ?? group;\n const set = slot === \"cover\" ? !!shown.cover?.image || (!!shown.cover?.content_id && !shown.cover.auto) : !!shown.avatar?.image;\n const upload = (f: Blob & { name?: string }, edit?: Edit) => save.run(() => g.uploadImage(slot, f, { wait: false, edit }));\n const close = () => {\n source?.revoke?.();\n setSource(null);\n setFile(null);\n };\n return (\n <div className={cn(\"flex flex-wrap items-center gap-3\", className)} data-ckui=\"group-image-editor\" data-slot={slot}>\n {slot === \"avatar\" ? <GroupAvatar group={shown} src={g.previews.avatar} className=\"size-16\" /> : <GroupCover group={shown} src={g.previews.cover} className=\"aspect-[3/1] w-48\" />}\n <div className=\"grid gap-2\">\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={save.pending} onClick={() => input.current?.click()}>\n {save.pending ? t(\"groupEditor.uploading\") : t(slot === \"avatar\" ? \"groupEditor.uploadAvatar\" : \"groupEditor.uploadCover\")}\n </Button>\n {set && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={save.pending} onClick={() => void save.run(() => g.update(slot === \"cover\" ? { cover: {} } : { avatar: {} }))}>\n {t(slot === \"avatar\" ? \"groupEditor.removeAvatar\" : \"groupEditor.coverUseAuto\")}\n </Button>\n )}\n </div>\n <input\n ref={input}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={t(slot === \"avatar\" ? \"groupEditor.uploadAvatar\" : \"groupEditor.uploadCover\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (!f) return;\n if (!crop) return void upload(f);\n decodeImage(f).then(\n (s) => {\n setFile(f);\n setSource(s);\n },\n () => void upload(f),\n );\n }}\n />\n <ErrorLine error={save.error} />\n </div>\n <ImageCropDialog\n open={!!source}\n onOpenChange={(open) => !open && close()}\n source={source}\n aspect={slot === \"avatar\" ? \"1:1\" : \"3:1\"}\n aspects={slot === \"avatar\" ? undefined : [\"3:1\", \"16:9\", \"\"]}\n round={slot === \"avatar\"}\n onConfirm={(edit) => {\n const f = file;\n close();\n if (f) void upload(f, edit ?? undefined);\n }}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCA,SAAgB,EAAc,EAAE,UAAO,eAAY,aAAU,QAAQ,GAAK,YAAS,cAAW,iBAAkC;CAC9H,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAS,EAAoB,CAAG,GAChC,IAAM,EAAW,CAAiB,GAClC,IAAO,EAAU,mBAAmB,CAAO,GAC3C,IAAI,EAAM;CAChB,IAAI,CAAC,KAAK,EAAE,UAAW,CAAC,EAAE,SAAS,CAAC,EAAE,UAAU,CAAC,EAAE,SAAU,OAAO;CACpE,IAAM,IAAY,GAAK,WAAW,MAC5B,IAAS,KAAY,GAAK,UAC1B,IAA0B,EAAE,SAAS,OAAO,EAAE,MAAM,MAAM,MAAM,IAAI,IAAI,EAAE,QAAQ,KAAA;CACxF,OACE,kBAAC,GAAD;EAA8B;EAAY,WAAW,EAAG,0DAA0D,CAAS;EAAG,aAAU;EAAiB,eAAa,EAAE;EAAxK,UAAA,CACG,EAAE,UACD,kBAAC,KAAD;GAAG,WAAU;GAAyB,UAAA,EAAE,oBAAoB;EAAK,CAAA,IAC/D,EAAE,SACJ,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,mBAAmB,EAAS,CAAA,GACrC,CAAC,KACA,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAQ,UAAU,EAAK;IAAS,eAAe,KAAK,EAAK,UAAU,EAAO,OAAO,KAAK,EAAM,IAAI,EAAK,CAAC;IAC7H,UAAA,EAAE,kBAAkB;GACf,CAAA,CAEP;EAEL,CAAA,IAAA,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,IAAQ,yBAAyB,sBAAsB;GAAK,CAAA,GACpG,kBAAC,GAAD;IACE,MAAK;IACL,WAAU;IACV,UAAU,EAAK,WAAY,CAAC,CAAC,KAAS,CAAC;IACvC,eAAe;KACb,IAAI,KAAa,GAAQ,OAAO,EAAO;KACvC,IAAI,GAAO,OAAO,IAAa;MAAE,KAAK,EAAE;MAAK;KAAM,CAAC;KACpD,EAAU,UAAU,EAAO,OAAO,KAAK,EAAM,EAAE,CAAC;IAClD;IAEC,UAAA,IAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAW,GAAG,EAAM,KAAK,EAAE,CAAC,IAAI,EAAE,iBAAiB;GACvF,CAAA,CACL;EAEN,CAAA,GAAA,EAAK,SAAS,kBAAC,GAAD,EAAA,UAAY,EAAa,GAAG,EAAK,KAAK,EAAa,CAAA,CAClD;;AAEtB;AAoBA,SAAgB,EAAY,GAAqB;CAC/C,IAAM,IAAI,EAAY,GAChB,IAAO,EAAS,EAAE,QAAQ,OAAO,EAAE,IAAI;EAAE,UAAU,EAAE;EAAU,QAAQ,EAAE;CAAO,CAAC,GACjF,IAAI,EAAE,SAAS,EAAK;CAC1B,IAAI,CAAC,GAAG,OAAO,EAAK,QAAQ,kBAAC,GAAD,EAAA,UAAY,EAAa,GAAG,EAAK,KAAK,EAAa,CAAA,IAAI,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA;CACtH,IAAM,IAAO,EAAE,KAAK,MAAM,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,KAAK,EAAE,KAAK,MAAM,MAAM,EAAE,WAAW,GAC7G,IAAQ,EAAE,OAAO,QAAQ,MAAM,EAAE,cAAc,EAAE,YAAY,GAAG,CAAC,CAAC,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC;CACvG,OACE,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EAAe,aAAW,EAAE;EAApI,UAAA;GACE,kBAAC,GAAD;IAAY,OAAO;IAAG,WAAU;GAAuB,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,GAAD;MAAa,OAAO;MAAG,WAAU;KAAkE,CAAA;KACnG,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACG,EAAE,SACD,kBAAC,GAAD;QAAW,OAAO,EAAE;QAAO,MAAM,EAAE;QAAM,WAAU;QAChD,UAAA,EAAE,MAAM,QAAQ,EAAE,MAAM;OAChB,CAAA;OAEb,kBAAC,MAAD;QAAI,WAAU;QAAqC,UAAA,EAAE,QAAQ,EAAE;OAAS,CAAA;OACxE,kBAAC,KAAD;QAAG,WAAU;QAA8C,UAAA,EAAE,OAAO,gBAAgB,CAAK;OAAK,CAAA;MAC3F;;KACL,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OAAe,OAAO;OAAG,YAAY,EAAE;OAAY,QAAQ,EAAE;MAAS,CAAA,GACrE,EAAE,QACA;;IACF;;GACJ,GAAM,eAAe,kBAAC,KAAD;IAAG,WAAU;IAAgD,UAAA,EAAK;GAAe,CAAA;GACvG,kBAAC,GAAD;IAAc,OAAO;IAAG,UAAU,EAAE;IAAU,QAAQ,EAAE;IAAQ,MAAM,EAAE;IAAM,QAAQ,EAAE;IAAQ,WAAU;GAAgB,CAAA;EAC1G;;AAEtB;AAgBA,SAAgB,EAAiB,EAAE,UAAO,SAAM,UAAO,IAAM,WAAQ,YAAS,cAAW,iBAAqC;CAC5H,IAAM,EAAE,SAAM,EAAY,GACpB,IAAI,EAAS,EAAM,IAAI;EAAE;EAAQ;CAAW,CAAC,GAC7C,IAAQ,EAAyB,IAAI,GACrC,IAAO,EAAU,cAAc,CAAO,GACtC,CAAC,GAAQ,KAAa,EAA4B,IAAI,GACtD,CAAC,GAAM,KAAW,EAAsB,IAAI,GAC5C,IAAQ,EAAE,SAAS,GACnB,IAAM,MAAS,UAAU,CAAC,CAAC,EAAM,OAAO,SAAU,CAAC,CAAC,EAAM,OAAO,cAAc,CAAC,EAAM,MAAM,OAAQ,CAAC,CAAC,EAAM,QAAQ,OACpH,KAAU,GAA6B,MAAgB,EAAK,UAAU,EAAE,YAAY,GAAM,GAAG;EAAE,MAAM;EAAO;CAAK,CAAC,CAAC,GACnH,UAAc;EAGlB,AAFA,GAAQ,SAAS,GACjB,EAAU,IAAI,GACd,EAAQ,IAAI;CACd;CACA,OACE,kBAAC,OAAD;EAAK,WAAW,EAAG,qCAAqC,CAAS;EAAG,aAAU;EAAqB,aAAW;EAA9G,UAAA;GACG,MAAS,WAAW,kBAAC,GAAD;IAAa,OAAO;IAAO,KAAK,EAAE,SAAS;IAAQ,WAAU;GAAW,CAAA,IAAI,kBAAC,GAAD;IAAY,OAAO;IAAO,KAAK,EAAE,SAAS;IAAO,WAAU;GAAqB,CAAA;GACjL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OAAQ,MAAK;OAAS,MAAK;OAAK,SAAQ;OAAU,UAAU,EAAK;OAAS,eAAe,EAAM,SAAS,MAAM;OAC3G,UAAA,EAAK,UAAU,EAAE,uBAAuB,IAAI,EAAE,MAAS,WAAW,6BAA6B,yBAAyB;MACnH,CAAA,GACP,KACC,kBAAC,GAAD;OAAQ,MAAK;OAAS,MAAK;OAAK,SAAQ;OAAQ,UAAU,EAAK;OAAS,eAAe,KAAK,EAAK,UAAU,EAAE,OAAO,MAAS,UAAU,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;OACnK,UAAA,EAAE,MAAS,WAAW,6BAA6B,0BAA0B;MACxE,CAAA,CAEP;;KACL,kBAAC,SAAD;MACE,KAAK;MACL,MAAK;MACL,QAAO;MACP,QAAA;MACA,cAAY,EAAE,MAAS,WAAW,6BAA6B,yBAAyB;MACxF,WAAW,MAAM;OACf,IAAM,IAAI,EAAE,OAAO,QAAQ;OAC3B,MAAE,OAAO,QAAQ,IACZ,GACL;YAAI,CAAC,GAAM,OAAO,KAAK,EAAO,CAAC;QAC/B,EAAY,CAAC,CAAC,CAAC,MACZ,MAAM;SAEL,AADA,EAAQ,CAAC,GACT,EAAU,CAAC;QACb,SACM,KAAK,EAAO,CAAC,CACrB;OAP+B;MAQjC;KACD,CAAA;KACD,kBAAC,GAAD,EAAW,OAAO,EAAK,MAAQ,CAAA;IAC5B;;GACL,kBAAC,GAAD;IACE,MAAM,CAAC,CAAC;IACR,eAAe,MAAS,CAAC,KAAQ,EAAM;IAC/B;IACR,QAAQ,MAAS,WAAW,QAAQ;IACpC,SAAS,MAAS,WAAW,KAAA,IAAY;KAAC;KAAO;KAAQ;IAAE;IAC3D,OAAO,MAAS;IAChB,YAAY,MAAS;KACnB,IAAM,IAAI;KAEV,AADA,EAAM,GACF,KAAG,EAAY,GAAG,KAAQ,KAAA,CAAS;IACzC;GACD,CAAA;EACE;;AAET"}
@@ -15,8 +15,11 @@ export interface GroupDirectoryProps {
15
15
  parent?: string;
16
16
  /** Groups scoped by this group (characters of a series). */
17
17
  scope?: string;
18
- /** Default "name". */
18
+ /** Default "name". With sorts, the menu's choice; with onSortChange the host keeps it. */
19
19
  sort?: GroupSort;
20
+ /** The sorts offered as a menu; none by default. */
21
+ sorts?: readonly GroupSort[];
22
+ onSortChange?: (sort: GroupSort) => void;
20
23
  /** The letter bucket shown, a-z or "#", null for every group. With onLetterChange the host keeps it (in its URL). */
21
24
  letter?: string | null;
22
25
  onLetterChange?: (letter: string | null) => void;
@@ -29,9 +32,16 @@ export interface GroupDirectoryProps {
29
32
  /** A group's entry instead of the default one. */
30
33
  renderGroup?: (group: GroupCard) => ReactNode;
31
34
  pageSize?: number;
35
+ /** Numbered pages with a shown range instead of load more. */
36
+ pagination?: "pages";
37
+ /** The numbered page, from 1; with onPageChange the host keeps it. A changed letter or sort starts again at page 1: a host keeping the page drops it with them. */
38
+ page?: number;
39
+ onPageChange?: (page: number) => void;
40
+ /** Each numbered page's link, for crawlers and opening in a new tab. */
41
+ pageHref?: (page: number) => string;
32
42
  client?: ContentKitClient;
33
43
  className?: string;
34
44
  appearance?: ContentKitUiAppearance;
35
45
  }
36
- /** A directory of one kind's groups: A-Z buckets, names with content counts or covers, paged. */
46
+ /** A directory of one kind's groups: A-Z buckets, an optional sort, names with content counts or covers, paged. */
37
47
  export declare function GroupDirectory(p: GroupDirectoryProps): import('react').JSX.Element;