@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
@@ -1,12 +1,23 @@
1
1
  import { ContentQuery, ListSort } from '../../client/discovery.js';
2
2
  import { Translator } from '../../i18n/messages.js';
3
3
  import { CardListProps, LockedCards } from './grid.js';
4
- /** A sort's name: "Newest", "Popular, this week". */
5
- export declare function sortLabel(m: Translator, sort: ListSort): string;
4
+ /** A sort's name: "Newest", "Popular, this week", "Best match". */
5
+ export declare function sortLabel(m: Translator, sort: ListSort | "relevance"): string;
6
+ export interface SortMenuProps<S extends string> {
7
+ value: S;
8
+ /** The sorts offered, in menu order. */
9
+ sorts: readonly S[];
10
+ onChange: (sort: S) => void;
11
+ /** A sort's name; default the list sorts' names. */
12
+ label?: (sort: S) => string;
13
+ className?: string;
14
+ }
15
+ /** A list's sort control; hosts keep the choice (URL, remembered per page type). */
16
+ export declare function SortMenu<S extends string>({ value, sorts, onChange, label, className }: SortMenuProps<S>): import('react').JSX.Element;
6
17
  export interface ContentListingProps extends CardListProps {
7
18
  /** The listed kind: its version traits and filterable attributes become filters, its sort the default. */
8
19
  kind?: string;
9
- /** The sort and filters; with onQueryChange the host keeps them (e.g. in the URL). Other fields (groups, a tag page's) pass through. */
20
+ /** The sort and filters; with onQueryChange the host keeps them (e.g. in the URL). Other fields (groups, a tag page's) pass through. A changed query starts again at page 1: a host keeping the page drops it with the query. */
10
21
  query?: ContentQuery;
11
22
  onQueryChange?: (query: ContentQuery) => void;
12
23
  /** The sorts offered; default newest, popular (week, month, all time), top rated, most favorited, title, oldest. */
@@ -26,26 +26,43 @@ function b(e, t) {
26
26
  period: e.t(`listing.period_${r}`)
27
27
  }) : i;
28
28
  }
29
- function x({ kind: x, query: S, onQueryChange: C, sorts: w = v, languages: T, filters: E, ...D }) {
30
- let O = t(), { t: k } = O, A = e(), { kinds: j } = r({ client: D.client }), [M, N] = m({}), P = S ?? M, F = f((e) => {
31
- S === void 0 && N(e), C?.(e);
32
- }, [S, C]), I = x ? j?.kinds[x] : void 0, L = P.sort ?? I?.sort ?? w[0], R = (e) => !E || E.includes(e), z = (I?.versions.traits ?? []).filter(R), B = Object.entries(I?.attributes ?? {}).filter(([e, t]) => t.filterable && (t.values?.length || t.type === "bool") && R(e)), V = (T ?? (j?.languages.length ?? 0) > 1) && !!j?.languages.length, H = (e) => P.attribute?.find((t) => t.startsWith(`${e}:`))?.slice(e.length + 1), U = (e, t) => F({
33
- ...P,
34
- attribute: [...(P.attribute ?? []).filter((t) => !t.startsWith(`${e}:`)), ...t === void 0 ? [] : [`${e}:${t}`]]
35
- }), W = (e) => {
36
- let t = P.traits?.includes(e);
37
- F({
38
- ...P,
39
- traits: t ? P.traits.filter((t) => t !== e) : [...P.traits ?? [], e]
29
+ function x({ value: e, sorts: n, onChange: r, label: i, className: u }) {
30
+ let d = t(), f = i ?? ((e) => b(d, e));
31
+ return /* @__PURE__ */ g(a, {
32
+ value: e,
33
+ onValueChange: (e) => r(e),
34
+ children: [/* @__PURE__ */ h(c, {
35
+ size: "sm",
36
+ "aria-label": d.t("listing.sort"),
37
+ className: u,
38
+ "data-ckui": "listing-sort",
39
+ children: /* @__PURE__ */ h(l, { children: (e) => f(e) })
40
+ }), /* @__PURE__ */ h(o, { children: n.map((e) => /* @__PURE__ */ h(s, {
41
+ value: e,
42
+ children: f(e)
43
+ }, e)) })]
44
+ });
45
+ }
46
+ function S({ kind: b, query: S, onQueryChange: C, sorts: w = v, languages: T, filters: E, ...D }) {
47
+ let { t: O } = t(), k = e(), { kinds: A } = r({ client: D.client }), [j, M] = m({}), N = S ?? j, P = f((e) => {
48
+ S === void 0 && M(e), C?.(e);
49
+ }, [S, C]), F = b ? A?.kinds[b] : void 0, I = N.sort ?? F?.sort ?? w[0] ?? "newest", L = (e) => !E || E.includes(e), R = (F?.versions.traits ?? []).filter(L), z = Object.entries(F?.attributes ?? {}).filter(([e, t]) => t.filterable && (t.values?.length || t.type === "bool") && L(e)), B = (T ?? (A?.languages.length ?? 0) > 1) && !!A?.languages.length, V = (e) => N.attribute?.find((t) => t.startsWith(`${e}:`))?.slice(e.length + 1), H = (e, t) => P({
50
+ ...N,
51
+ attribute: [...(N.attribute ?? []).filter((t) => !t.startsWith(`${e}:`)), ...t === void 0 ? [] : [`${e}:${t}`]]
52
+ }), U = (e) => {
53
+ let t = N.traits?.includes(e);
54
+ P({
55
+ ...N,
56
+ traits: t ? N.traits.filter((t) => t !== e) : [...N.traits ?? [], e]
40
57
  });
41
- }, G = !!(P.language || P.traits?.length || P.attribute?.length), K = p(() => ({
42
- ...P,
43
- kind: x ?? P.kind,
44
- sort: L
58
+ }, W = !!(N.language || N.traits?.length || N.attribute?.length), G = p(() => ({
59
+ ...N,
60
+ kind: b ?? N.kind,
61
+ sort: I
45
62
  }), [
46
- P,
47
- x,
48
- L
63
+ N,
64
+ b,
65
+ I
49
66
  ]);
50
67
  return /* @__PURE__ */ g(n, {
51
68
  appearance: D.appearance,
@@ -53,55 +70,47 @@ function x({ kind: x, query: S, onQueryChange: C, sorts: w = v, languages: T, fi
53
70
  "data-ckui": "content-listing",
54
71
  children: [/* @__PURE__ */ g("div", {
55
72
  role: "toolbar",
56
- "aria-label": k("listing.filters"),
73
+ "aria-label": O("listing.filters"),
57
74
  className: "flex flex-wrap items-center gap-2",
58
75
  children: [
59
- /* @__PURE__ */ g(a, {
60
- value: L,
61
- onValueChange: (e) => F({
62
- ...P,
76
+ /* @__PURE__ */ h(x, {
77
+ value: I,
78
+ sorts: w,
79
+ onChange: (e) => P({
80
+ ...N,
63
81
  sort: e
64
- }),
65
- children: [/* @__PURE__ */ h(c, {
66
- size: "sm",
67
- "aria-label": k("listing.sort"),
68
- "data-ckui": "listing-sort",
69
- children: /* @__PURE__ */ h(l, { children: (e) => b(O, e) })
70
- }), /* @__PURE__ */ h(o, { children: w.map((e) => /* @__PURE__ */ h(s, {
71
- value: e,
72
- children: b(O, e)
73
- }, e)) })]
82
+ })
74
83
  }),
75
- V && /* @__PURE__ */ g(a, {
76
- value: P.language ?? y,
77
- onValueChange: (e) => F({
78
- ...P,
84
+ B && /* @__PURE__ */ g(a, {
85
+ value: N.language ?? y,
86
+ onValueChange: (e) => P({
87
+ ...N,
79
88
  language: e === y ? void 0 : e
80
89
  }),
81
90
  children: [/* @__PURE__ */ h(c, {
82
91
  size: "sm",
83
- "aria-label": k("listing.language"),
92
+ "aria-label": O("listing.language"),
84
93
  "data-ckui": "listing-language",
85
- children: /* @__PURE__ */ h(l, { children: (e) => e === y ? k("listing.anyLanguage") : A(e) })
94
+ children: /* @__PURE__ */ h(l, { children: (e) => e === y ? O("listing.anyLanguage") : k(e) })
86
95
  }), /* @__PURE__ */ g(o, { children: [/* @__PURE__ */ h(s, {
87
96
  value: y,
88
- children: k("listing.anyLanguage")
89
- }), j.languages.map((e) => /* @__PURE__ */ h(s, {
97
+ children: O("listing.anyLanguage")
98
+ }), A.languages.map((e) => /* @__PURE__ */ h(s, {
90
99
  value: e,
91
- children: A(e)
100
+ children: k(e)
92
101
  }, e))] })]
93
102
  }),
94
- B.map(([e, t]) => {
95
- let n = H(e);
103
+ z.map(([e, t]) => {
104
+ let n = V(e);
96
105
  return t.type === "bool" ? /* @__PURE__ */ h(i, {
97
106
  size: "sm",
98
107
  variant: n === "true" ? "default" : "outline",
99
108
  "aria-pressed": n === "true",
100
- onClick: () => U(e, n === "true" ? void 0 : "true"),
109
+ onClick: () => H(e, n === "true" ? void 0 : "true"),
101
110
  children: u(D.labels, e)
102
111
  }, e) : /* @__PURE__ */ g(a, {
103
112
  value: n ?? y,
104
- onValueChange: (t) => U(e, t === y ? void 0 : t),
113
+ onValueChange: (t) => H(e, t === y ? void 0 : t),
105
114
  children: [/* @__PURE__ */ h(c, {
106
115
  size: "sm",
107
116
  "aria-label": u(D.labels, e),
@@ -110,41 +119,41 @@ function x({ kind: x, query: S, onQueryChange: C, sorts: w = v, languages: T, fi
110
119
  children: /* @__PURE__ */ h(l, { children: (t) => t === y ? u(D.labels, e) : u(D.labels, t) })
111
120
  }), /* @__PURE__ */ g(o, { children: [/* @__PURE__ */ h(s, {
112
121
  value: y,
113
- children: k("listing.any")
122
+ children: O("listing.any")
114
123
  }), t.values.map((e) => /* @__PURE__ */ h(s, {
115
124
  value: e,
116
125
  children: u(D.labels, e)
117
126
  }, e))] })]
118
127
  }, e);
119
128
  }),
120
- z.map((e) => {
121
- let t = !!P.traits?.includes(e);
129
+ R.map((e) => {
130
+ let t = !!N.traits?.includes(e);
122
131
  return /* @__PURE__ */ h(i, {
123
132
  size: "sm",
124
133
  variant: t ? "default" : "outline",
125
134
  "aria-pressed": t,
126
- onClick: () => W(e),
135
+ onClick: () => U(e),
127
136
  "data-ckui": "listing-trait",
128
137
  children: u(D.labels, e)
129
138
  }, e);
130
139
  }),
131
- G && /* @__PURE__ */ h(i, {
140
+ W && /* @__PURE__ */ h(i, {
132
141
  size: "sm",
133
142
  variant: "ghost",
134
143
  onClick: () => {
135
- let { language: e, traits: t, attribute: n, ...r } = P;
136
- F(r);
144
+ let { language: e, traits: t, attribute: n, ...r } = N;
145
+ P(r);
137
146
  },
138
- children: k("listing.clear")
147
+ children: O("listing.clear")
139
148
  })
140
149
  ]
141
150
  }), /* @__PURE__ */ h(d, {
142
151
  ...D,
143
- query: K
152
+ query: G
144
153
  })]
145
154
  });
146
155
  }
147
156
  //#endregion
148
- export { x as ContentListing, b as sortLabel };
157
+ export { S as ContentListing, x as SortMenu, b as sortLabel };
149
158
 
150
159
  //# sourceMappingURL=listing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"listing.js","names":[],"sources":["../../../src/components/content/listing.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { ContentQuery, ListSort } from \"../../client/discovery.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { MessageKey, Translator } from \"../../i18n/messages.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { labelOf } from \"../groups/parts.js\";\nimport { ContentGrid, type CardListProps, type LockedCards } from \"./grid.js\";\n\nconst SORTS: readonly ListSort[] = [\"newest\", \"popular:7d\", \"popular:30d\", \"popular\", \"top_rated\", \"most_favorited\", \"title\", \"oldest\"];\nconst ANY = \"*\";\n\n/** A sort's name: \"Newest\", \"Popular, this week\". */\nexport function sortLabel(m: Translator, sort: ListSort): string {\n const [base, period] = sort.split(\":\");\n const name = m.t(`listing.sort_${base}` as MessageKey);\n return period ? m.t(\"listing.sortPeriod\", { sort: name, period: m.t(`listing.period_${period}` as MessageKey) }) : name;\n}\n\nexport interface ContentListingProps extends CardListProps {\n /** The listed kind: its version traits and filterable attributes become filters, its sort the default. */\n kind?: string;\n /** The sort and filters; with onQueryChange the host keeps them (e.g. in the URL). Other fields (groups, a tag page's) pass through. */\n query?: ContentQuery;\n onQueryChange?: (query: ContentQuery) => void;\n /** The sorts offered; default newest, popular (week, month, all time), top rated, most favorited, title, oldest. */\n sorts?: readonly ListSort[];\n /** Cards the reader doesn't open: show (default), hide or collapse. */\n locked?: LockedCards;\n /** Offer a language filter; default when the site has several languages. */\n languages?: boolean;\n /** The traits and attributes offered; default every trait and filterable attribute of the kind. */\n filters?: readonly string[];\n}\n\n/** A listing page: sort, language, trait and attribute filters over a paged grid of a kind's contents. */\nexport function ContentListing({ kind, query: controlled, onQueryChange, sorts = SORTS, languages, filters, ...p }: ContentListingProps) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const { kinds: model } = useKinds({ client: p.client });\n const [own, setOwn] = useState<ContentQuery>({});\n const query = controlled ?? own;\n const set = useCallback(\n (next: ContentQuery) => {\n if (controlled === undefined) setOwn(next);\n onQueryChange?.(next);\n },\n [controlled, onQueryChange],\n );\n const spec = kind ? model?.kinds[kind] : undefined;\n const sort = query.sort ?? (spec?.sort as ListSort | undefined) ?? sorts[0];\n const offered = (name: string) => !filters || filters.includes(name);\n const traits = (spec?.versions.traits ?? []).filter(offered);\n const attributes = Object.entries(spec?.attributes ?? {}).filter(([n, a]) => a.filterable && (a.values?.length || a.type === \"bool\") && offered(n));\n const languageFilter = (languages ?? (model?.languages.length ?? 0) > 1) && !!model?.languages.length;\n const attributeValue = (name: string) => query.attribute?.find((a) => a.startsWith(`${name}:`))?.slice(name.length + 1);\n const setAttribute = (name: string, value: string | undefined) =>\n set({ ...query, attribute: [...(query.attribute ?? []).filter((a) => !a.startsWith(`${name}:`)), ...(value === undefined ? [] : [`${name}:${value}`])] });\n const toggleTrait = (x: string) => {\n const on = query.traits?.includes(x);\n set({ ...query, traits: on ? query.traits!.filter((y) => y !== x) : [...(query.traits ?? []), x] });\n };\n const filtered = !!(query.language || query.traits?.length || query.attribute?.length);\n const list = useMemo(() => ({ ...query, kind: kind ?? query.kind, sort }), [query, kind, sort]);\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"content-listing\">\n <div role=\"toolbar\" aria-label={t(\"listing.filters\")} className=\"flex flex-wrap items-center gap-2\">\n <Select value={sort} onValueChange={(v) => set({ ...query, sort: v as ListSort })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"listing.sort\")} data-ckui=\"listing-sort\">\n <SelectValue>{(v: string) => sortLabel(m, v as ListSort)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {sorts.map((s) => (\n <SelectItem key={s} value={s}>\n {sortLabel(m, s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {languageFilter && (\n <Select value={query.language ?? ANY} onValueChange={(v) => set({ ...query, language: v === ANY ? undefined : (v as string) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"listing.language\")} data-ckui=\"listing-language\">\n <SelectValue>{(v: string) => (v === ANY ? t(\"listing.anyLanguage\") : languageName(v))}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ANY}>{t(\"listing.anyLanguage\")}</SelectItem>\n {model!.languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n {attributes.map(([name, a]) => {\n const value = attributeValue(name);\n if (a.type === \"bool\")\n return (\n <Button key={name} size=\"sm\" variant={value === \"true\" ? \"default\" : \"outline\"} aria-pressed={value === \"true\"} onClick={() => setAttribute(name, value === \"true\" ? undefined : \"true\")}>\n {labelOf(p.labels, name)}\n </Button>\n );\n return (\n <Select key={name} value={value ?? ANY} onValueChange={(v) => setAttribute(name, v === ANY ? undefined : (v as string))}>\n <SelectTrigger size=\"sm\" aria-label={labelOf(p.labels, name)} data-ckui=\"listing-attribute\" data-name={name}>\n <SelectValue>{(v: string) => (v === ANY ? labelOf(p.labels, name) : labelOf(p.labels, v))}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ANY}>{t(\"listing.any\")}</SelectItem>\n {a.values!.map((v) => (\n <SelectItem key={v} value={v}>\n {labelOf(p.labels, v)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n })}\n {traits.map((x) => {\n const on = !!query.traits?.includes(x);\n return (\n <Button key={x} size=\"sm\" variant={on ? \"default\" : \"outline\"} aria-pressed={on} onClick={() => toggleTrait(x)} data-ckui=\"listing-trait\">\n {labelOf(p.labels, x)}\n </Button>\n );\n })}\n {filtered && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n const { language: _l, traits: _t, attribute: _a, ...rest } = query;\n set(rest);\n }}\n >\n {t(\"listing.clear\")}\n </Button>\n )}\n </div>\n <ContentGrid {...p} query={list} />\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"listing.js","names":[],"sources":["../../../src/components/content/listing.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { ContentQuery, ListSort } from \"../../client/discovery.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { MessageKey, Translator } from \"../../i18n/messages.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { labelOf } from \"../groups/parts.js\";\nimport { ContentGrid, type CardListProps, type LockedCards } from \"./grid.js\";\n\nconst SORTS: readonly ListSort[] = [\"newest\", \"popular:7d\", \"popular:30d\", \"popular\", \"top_rated\", \"most_favorited\", \"title\", \"oldest\"];\nconst ANY = \"*\";\n\n/** A sort's name: \"Newest\", \"Popular, this week\", \"Best match\". */\nexport function sortLabel(m: Translator, sort: ListSort | \"relevance\"): string {\n const [base, period] = sort.split(\":\");\n const name = m.t(`listing.sort_${base}` as MessageKey);\n return period ? m.t(\"listing.sortPeriod\", { sort: name, period: m.t(`listing.period_${period}` as MessageKey) }) : name;\n}\n\nexport interface SortMenuProps<S extends string> {\n value: S;\n /** The sorts offered, in menu order. */\n sorts: readonly S[];\n onChange: (sort: S) => void;\n /** A sort's name; default the list sorts' names. */\n label?: (sort: S) => string;\n className?: string;\n}\n\n/** A list's sort control; hosts keep the choice (URL, remembered per page type). */\nexport function SortMenu<S extends string>({ value, sorts, onChange, label, className }: SortMenuProps<S>) {\n const m = useMessages();\n const name = label ?? ((s: S) => sortLabel(m, s as ListSort));\n return (\n <Select value={value} onValueChange={(v) => onChange(v as S)}>\n <SelectTrigger size=\"sm\" aria-label={m.t(\"listing.sort\")} className={className} data-ckui=\"listing-sort\">\n <SelectValue>{(v: string) => name(v as S)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {sorts.map((s) => (\n <SelectItem key={s} value={s}>\n {name(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n\nexport interface ContentListingProps extends CardListProps {\n /** The listed kind: its version traits and filterable attributes become filters, its sort the default. */\n kind?: string;\n /** The sort and filters; with onQueryChange the host keeps them (e.g. in the URL). Other fields (groups, a tag page's) pass through. A changed query starts again at page 1: a host keeping the page drops it with the query. */\n query?: ContentQuery;\n onQueryChange?: (query: ContentQuery) => void;\n /** The sorts offered; default newest, popular (week, month, all time), top rated, most favorited, title, oldest. */\n sorts?: readonly ListSort[];\n /** Cards the reader doesn't open: show (default), hide or collapse. */\n locked?: LockedCards;\n /** Offer a language filter; default when the site has several languages. */\n languages?: boolean;\n /** The traits and attributes offered; default every trait and filterable attribute of the kind. */\n filters?: readonly string[];\n}\n\n/** A listing page: sort, language, trait and attribute filters over a paged grid of a kind's contents. */\nexport function ContentListing({ kind, query: controlled, onQueryChange, sorts = SORTS, languages, filters, ...p }: ContentListingProps) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const { kinds: model } = useKinds({ client: p.client });\n const [own, setOwn] = useState<ContentQuery>({});\n const query = controlled ?? own;\n const set = useCallback(\n (next: ContentQuery) => {\n if (controlled === undefined) setOwn(next);\n onQueryChange?.(next);\n },\n [controlled, onQueryChange],\n );\n const spec = kind ? model?.kinds[kind] : undefined;\n const sort: ListSort = query.sort ?? (spec?.sort as ListSort | undefined) ?? sorts[0] ?? \"newest\";\n const offered = (name: string) => !filters || filters.includes(name);\n const traits = (spec?.versions.traits ?? []).filter(offered);\n const attributes = Object.entries(spec?.attributes ?? {}).filter(([n, a]) => a.filterable && (a.values?.length || a.type === \"bool\") && offered(n));\n const languageFilter = (languages ?? (model?.languages.length ?? 0) > 1) && !!model?.languages.length;\n const attributeValue = (name: string) => query.attribute?.find((a) => a.startsWith(`${name}:`))?.slice(name.length + 1);\n const setAttribute = (name: string, value: string | undefined) =>\n set({ ...query, attribute: [...(query.attribute ?? []).filter((a) => !a.startsWith(`${name}:`)), ...(value === undefined ? [] : [`${name}:${value}`])] });\n const toggleTrait = (x: string) => {\n const on = query.traits?.includes(x);\n set({ ...query, traits: on ? query.traits!.filter((y) => y !== x) : [...(query.traits ?? []), x] });\n };\n const filtered = !!(query.language || query.traits?.length || query.attribute?.length);\n const list = useMemo(() => ({ ...query, kind: kind ?? query.kind, sort }), [query, kind, sort]);\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"content-listing\">\n <div role=\"toolbar\" aria-label={t(\"listing.filters\")} className=\"flex flex-wrap items-center gap-2\">\n <SortMenu<ListSort> value={sort} sorts={sorts} onChange={(s) => set({ ...query, sort: s })} />\n {languageFilter && (\n <Select value={query.language ?? ANY} onValueChange={(v) => set({ ...query, language: v === ANY ? undefined : (v as string) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"listing.language\")} data-ckui=\"listing-language\">\n <SelectValue>{(v: string) => (v === ANY ? t(\"listing.anyLanguage\") : languageName(v))}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ANY}>{t(\"listing.anyLanguage\")}</SelectItem>\n {model!.languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n {attributes.map(([name, a]) => {\n const value = attributeValue(name);\n if (a.type === \"bool\")\n return (\n <Button key={name} size=\"sm\" variant={value === \"true\" ? \"default\" : \"outline\"} aria-pressed={value === \"true\"} onClick={() => setAttribute(name, value === \"true\" ? undefined : \"true\")}>\n {labelOf(p.labels, name)}\n </Button>\n );\n return (\n <Select key={name} value={value ?? ANY} onValueChange={(v) => setAttribute(name, v === ANY ? undefined : (v as string))}>\n <SelectTrigger size=\"sm\" aria-label={labelOf(p.labels, name)} data-ckui=\"listing-attribute\" data-name={name}>\n <SelectValue>{(v: string) => (v === ANY ? labelOf(p.labels, name) : labelOf(p.labels, v))}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ANY}>{t(\"listing.any\")}</SelectItem>\n {a.values!.map((v) => (\n <SelectItem key={v} value={v}>\n {labelOf(p.labels, v)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n })}\n {traits.map((x) => {\n const on = !!query.traits?.includes(x);\n return (\n <Button key={x} size=\"sm\" variant={on ? \"default\" : \"outline\"} aria-pressed={on} onClick={() => toggleTrait(x)} data-ckui=\"listing-trait\">\n {labelOf(p.labels, x)}\n </Button>\n );\n })}\n {filtered && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n const { language: _l, traits: _t, attribute: _a, ...rest } = query;\n set(rest);\n }}\n >\n {t(\"listing.clear\")}\n </Button>\n )}\n </div>\n <ContentGrid {...p} query={list} />\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;;;AAYA,IAAM,IAA6B;CAAC;CAAU;CAAc;CAAe;CAAW;CAAa;CAAkB;CAAS;AAAQ,GAChI,IAAM;AAGZ,SAAgB,EAAU,GAAe,GAAsC;CAC7E,IAAM,CAAC,GAAM,KAAU,EAAK,MAAM,GAAG,GAC/B,IAAO,EAAE,EAAE,gBAAgB,GAAoB;CACrD,OAAO,IAAS,EAAE,EAAE,sBAAsB;EAAE,MAAM;EAAM,QAAQ,EAAE,EAAE,kBAAkB,GAAsB;CAAE,CAAC,IAAI;AACrH;AAaA,SAAgB,EAA2B,EAAE,UAAO,UAAO,aAAU,UAAO,gBAA+B;CACzG,IAAM,IAAI,EAAY,GAChB,IAAO,OAAW,MAAS,EAAU,GAAG,CAAa;CAC3D,OACE,kBAAC,GAAD;EAAe;EAAO,gBAAgB,MAAM,EAAS,CAAM;EAA3D,UAAA,CACE,kBAAC,GAAD;GAAe,MAAK;GAAK,cAAY,EAAE,EAAE,cAAc;GAAc;GAAW,aAAU;GACxF,UAAA,kBAAC,GAAD,EAAA,WAAe,MAAc,EAAK,CAAM,EAAe,CAAA;EAC1C,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAM,KAAK,MACV,kBAAC,GAAD;GAAoB,OAAO;GACxB,UAAA,EAAK,CAAC;EACG,GAFK,CAEL,CACb,EACY,CAAA,CACT;;AAEZ;AAmBA,SAAgB,EAAe,EAAE,SAAM,OAAO,GAAY,kBAAe,WAAQ,GAAO,cAAW,YAAS,GAAG,KAA0B;CAEvI,IAAM,EAAE,SADE,EACI,GACR,IAAe,EAAgB,GAC/B,EAAE,OAAO,MAAU,EAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GAChD,CAAC,GAAK,KAAU,EAAuB,CAAC,CAAC,GACzC,IAAQ,KAAc,GACtB,IAAM,GACT,MAAuB;EAEtB,AADI,MAAe,KAAA,KAAW,EAAO,CAAI,GACzC,IAAgB,CAAI;CACtB,GACA,CAAC,GAAY,CAAa,CAC5B,GACM,IAAO,IAAO,GAAO,MAAM,KAAQ,KAAA,GACnC,IAAiB,EAAM,QAAS,GAAM,QAAiC,EAAM,MAAM,UACnF,KAAW,MAAiB,CAAC,KAAW,EAAQ,SAAS,CAAI,GAC7D,KAAU,GAAM,SAAS,UAAU,CAAC,EAAA,CAAG,OAAO,CAAO,GACrD,IAAa,OAAO,QAAQ,GAAM,cAAc,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,EAAE,eAAe,EAAE,QAAQ,UAAU,EAAE,SAAS,WAAW,EAAQ,CAAC,CAAC,GAC5I,KAAkB,MAAc,GAAO,UAAU,UAAU,KAAK,MAAM,CAAC,CAAC,GAAO,UAAU,QACzF,KAAkB,MAAiB,EAAM,WAAW,MAAM,MAAM,EAAE,WAAW,GAAG,EAAK,EAAE,CAAC,CAAC,EAAE,MAAM,EAAK,SAAS,CAAC,GAChH,KAAgB,GAAc,MAClC,EAAI;EAAE,GAAG;EAAO,WAAW,CAAC,IAAI,EAAM,aAAa,CAAC,EAAA,CAAG,QAAQ,MAAM,CAAC,EAAE,WAAW,GAAG,EAAK,EAAE,CAAC,GAAG,GAAI,MAAU,KAAA,IAAY,CAAC,IAAI,CAAC,GAAG,EAAK,GAAG,GAAO,CAAE;CAAE,CAAC,GACpJ,KAAe,MAAc;EACjC,IAAM,IAAK,EAAM,QAAQ,SAAS,CAAC;EACnC,EAAI;GAAE,GAAG;GAAO,QAAQ,IAAK,EAAM,OAAQ,QAAQ,MAAM,MAAM,CAAC,IAAI,CAAC,GAAI,EAAM,UAAU,CAAC,GAAI,CAAC;EAAE,CAAC;CACpG,GACM,IAAW,CAAC,EAAE,EAAM,YAAY,EAAM,QAAQ,UAAU,EAAM,WAAW,SACzE,IAAO,SAAe;EAAE,GAAG;EAAO,MAAM,KAAQ,EAAM;EAAM;CAAK,IAAI;EAAC;EAAO;EAAM;CAAI,CAAC;CAC9F,OACE,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EAAxG,UAAA,CACE,kBAAC,OAAD;GAAK,MAAK;GAAU,cAAY,EAAE,iBAAiB;GAAG,WAAU;GAAhE,UAAA;IACE,kBAAC,GAAD;KAAoB,OAAO;KAAa;KAAO,WAAW,MAAM,EAAI;MAAE,GAAG;MAAO,MAAM;KAAE,CAAC;IAAI,CAAA;IAC5F,KACC,kBAAC,GAAD;KAAQ,OAAO,EAAM,YAAY;KAAK,gBAAgB,MAAM,EAAI;MAAE,GAAG;MAAO,UAAU,MAAM,IAAM,KAAA,IAAa;KAAa,CAAC;KAA7H,UAAA,CACE,kBAAC,GAAD;MAAe,MAAK;MAAK,cAAY,EAAE,kBAAkB;MAAG,aAAU;MACpE,UAAA,kBAAC,GAAD,EAAA,WAAe,MAAe,MAAM,IAAM,EAAE,qBAAqB,IAAI,EAAa,CAAC,EAAgB,CAAA;KACtF,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAY,OAAO;MAAM,UAAA,EAAE,qBAAqB;KAAc,CAAA,GAC7D,EAAO,UAAU,KAAK,MACrB,kBAAC,GAAD;MAAoB,OAAO;MACxB,UAAA,EAAa,CAAC;KACL,GAFK,CAEL,CACb,CACY,EAAA,CAAA,CACT;;IAET,EAAW,KAAK,CAAC,GAAM,OAAO;KAC7B,IAAM,IAAQ,EAAe,CAAI;KAOjC,OANI,EAAE,SAAS,SAEX,kBAAC,GAAD;MAAmB,MAAK;MAAK,SAAS,MAAU,SAAS,YAAY;MAAW,gBAAc,MAAU;MAAQ,eAAe,EAAa,GAAM,MAAU,SAAS,KAAA,IAAY,MAAM;MACpL,UAAA,EAAQ,EAAE,QAAQ,CAAI;KACjB,GAFK,CAEL,IAGV,kBAAC,GAAD;MAAmB,OAAO,KAAS;MAAK,gBAAgB,MAAM,EAAa,GAAM,MAAM,IAAM,KAAA,IAAa,CAAY;MAAtH,UAAA,CACE,kBAAC,GAAD;OAAe,MAAK;OAAK,cAAY,EAAQ,EAAE,QAAQ,CAAI;OAAG,aAAU;OAAoB,aAAW;OACrG,UAAA,kBAAC,GAAD,EAAA,WAAe,MAAe,MAAM,IAAM,EAAQ,EAAE,QAAQ,CAAI,IAAI,EAAQ,EAAE,QAAQ,CAAC,EAAgB,CAAA;MAC1F,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAY,OAAO;OAAM,UAAA,EAAE,aAAa;MAAc,CAAA,GACrD,EAAE,OAAQ,KAAK,MACd,kBAAC,GAAD;OAAoB,OAAO;OACxB,UAAA,EAAQ,EAAE,QAAQ,CAAC;MACV,GAFK,CAEL,CACb,CACY,EAAA,CAAA,CACT;KAZK,GAAA,CAYL;IAEZ,CAAC;IACA,EAAO,KAAK,MAAM;KACjB,IAAM,IAAK,CAAC,CAAC,EAAM,QAAQ,SAAS,CAAC;KACrC,OACE,kBAAC,GAAD;MAAgB,MAAK;MAAK,SAAS,IAAK,YAAY;MAAW,gBAAc;MAAI,eAAe,EAAY,CAAC;MAAG,aAAU;MACvH,UAAA,EAAQ,EAAE,QAAQ,CAAC;KACd,GAFK,CAEL;IAEZ,CAAC;IACA,KACC,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAe;MACb,IAAM,EAAE,UAAU,GAAI,QAAQ,GAAI,WAAW,GAAI,GAAG,MAAS;MAC7D,EAAI,CAAI;KACV;KAEC,UAAA,EAAE,eAAe;IACZ,CAAA;GAEP;EACL,CAAA,GAAA,kBAAC,GAAD;GAAa,GAAI;GAAG,OAAO;EAAO,CAAA,CAClB;;AAEtB"}
@@ -4,14 +4,14 @@ import { Badge as n } from "../../ui/badge.js";
4
4
  import { Input as r } from "../../ui/input.js";
5
5
  import { Label as i } from "../../ui/label.js";
6
6
  import { Select as a, SelectContent as o, SelectItem as s, SelectTrigger as c, SelectValue as l } from "../../ui/select.js";
7
- import { useDraft as u } from "../../react/use-draft.js";
8
- import { useEditorSaveTask as d } from "./editor-save.js";
9
- import { priceLabel as f } from "./paywall.js";
7
+ import { priceLabel as u } from "./paywall.js";
8
+ import { useDraft as d } from "../../react/use-draft.js";
9
+ import { useEditorSaveTask as f } from "./editor-save.js";
10
10
  import { useId as p, useMemo as m } from "react";
11
11
  import { jsx as h, jsxs as g } from "react/jsx-runtime";
12
12
  //#region src/components/content/offer-fields.tsx
13
13
  function _({ offer: _, onSell: v, onWithdraw: y, pending: b }) {
14
- let x = e(), { t: S } = x, C = p(), w = _ && (_.state ?? "active") === "active" ? _ : void 0, T = u(m(() => ({
14
+ let x = e(), { t: S } = x, C = p(), w = _ && (_.state ?? "active") === "active" ? _ : void 0, T = d(m(() => ({
15
15
  amount: w?.terms.amount ?? "",
16
16
  currency: w?.terms.currency ?? "USD",
17
17
  mode: w?.terms.renew_hours ? "renew" : w?.terms.access_hours ? "rent" : "once",
@@ -33,7 +33,7 @@ function _({ offer: _, onSell: v, onWithdraw: y, pending: b }) {
33
33
  days: k
34
34
  }) : !1;
35
35
  };
36
- return d(`offer:${C}`, {
36
+ return f(`offer:${C}`, {
37
37
  label: S("contentEditor.price"),
38
38
  dirty: T.dirty.length > 0,
39
39
  pending: b,
@@ -51,7 +51,7 @@ function _({ offer: _, onSell: v, onWithdraw: y, pending: b }) {
51
51
  variant: "secondary",
52
52
  children: S("contentEditor.onSale")
53
53
  }),
54
- /* @__PURE__ */ h("span", { children: f(x, w.terms) }),
54
+ /* @__PURE__ */ h("span", { children: u(x, w.terms) }),
55
55
  w.synced === !1 && /* @__PURE__ */ h("span", {
56
56
  className: "text-xs text-muted-foreground",
57
57
  children: S("contentEditor.syncing")
@@ -0,0 +1,10 @@
1
+ import { ContentQuery, SearchQuery } from '../../client/discovery.js';
2
+ import { CardListProps } from './grid.js';
3
+ /** ContentGrid's numbered pages: each page with the selection total. */
4
+ export declare function PagedContentGrid({ query, locked, ...p }: CardListProps & {
5
+ query: ContentQuery;
6
+ }): import('react').JSX.Element;
7
+ /** SearchResults' numbered pages; nothing until the query has text. */
8
+ export declare function PagedSearchResults({ query, ...p }: CardListProps & {
9
+ query: SearchQuery;
10
+ }): import('react').JSX.Element | null;
@@ -0,0 +1,116 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { ContentKitUiRoot as t } from "../../scope.js";
3
+ import { useContentsPage as n, useSearchPage as r } from "../../react/discovery.js";
4
+ import { Pagination as i } from "./pagination.js";
5
+ import { CardGrid as a, cardListMode as o } from "./grid.js";
6
+ import { useMemo as s, useRef as c, useState as l } from "react";
7
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
8
+ import { cn as f } from "cn";
9
+ //#region src/components/content/paged-grid.tsx
10
+ var p = 24;
11
+ function m(e, t) {
12
+ let n = JSON.stringify(t), [r, i] = l({
13
+ key: n,
14
+ page: 1
15
+ }), a = r.key === n ? r.page : 1;
16
+ return [Math.max(1, Math.floor(e.page ?? a)), (t) => {
17
+ e.page === void 0 && i({
18
+ key: n,
19
+ page: t
20
+ }), e.onPageChange?.(t);
21
+ }];
22
+ }
23
+ function h({ result: e, page: n, setPage: r, render: o, filterKinds: l, filterLanguage: m, ...h }) {
24
+ let g = c(null), _ = h.pageSize ?? p, v = e.summary?.total ?? 0, y = s(() => ({
25
+ ...e,
26
+ summary: e.summary,
27
+ loadingMore: !1,
28
+ hasMore: !1,
29
+ loadMore: async () => {}
30
+ }), [e]), b = (e, t) => {
31
+ r(e), t && g.current?.scrollIntoView({
32
+ block: "start",
33
+ behavior: "smooth"
34
+ });
35
+ }, x = (t) => !e.loading || v > 0 ? /* @__PURE__ */ u(i, {
36
+ page: n,
37
+ total: v,
38
+ atLeast: e.summary?.truncated,
39
+ pageSize: _,
40
+ range: !t,
41
+ pageHref: h.pageHref,
42
+ onPageChange: (e) => b(e, t)
43
+ }) : null;
44
+ return /* @__PURE__ */ d(t, {
45
+ appearance: h.appearance,
46
+ className: f("grid gap-4 text-sm", h.className),
47
+ "data-ckui": "numbered-grid",
48
+ children: [
49
+ /* @__PURE__ */ u("div", {
50
+ ref: g,
51
+ className: "scroll-mt-20",
52
+ children: x(!1)
53
+ }),
54
+ /* @__PURE__ */ u(a, {
55
+ ...h,
56
+ className: void 0,
57
+ list: y,
58
+ render: o,
59
+ filterKinds: l,
60
+ filterLanguage: m
61
+ }),
62
+ y.items.length > 0 && x(!0)
63
+ ]
64
+ });
65
+ }
66
+ function g({ query: e, locked: t = "show", ...r }) {
67
+ let { page: i, cursor: a, ...c } = e, l = s(() => ({
68
+ ...c,
69
+ summary: !0,
70
+ limit: c.limit ?? r.pageSize ?? p,
71
+ mode: o(t, c.mode)
72
+ }), [
73
+ JSON.stringify(c),
74
+ r.pageSize,
75
+ t
76
+ ]), [d, f] = m(r, l), g = n(l, d, { client: r.client });
77
+ return /* @__PURE__ */ u(h, {
78
+ ...r,
79
+ pageSize: l.limit,
80
+ locked: t,
81
+ result: g,
82
+ page: d,
83
+ setPage: f,
84
+ render: JSON.stringify([l, d]),
85
+ filterKinds: e.kind ? [e.kind] : void 0,
86
+ filterLanguage: e.display_language ?? e.language
87
+ });
88
+ }
89
+ function _({ query: t, ...n }) {
90
+ let { t: i } = e(), { page: a, cursor: c, ...l } = t, d = s(() => ({
91
+ ...l,
92
+ summary: !0,
93
+ limit: l.limit ?? n.pageSize ?? p,
94
+ mode: o(n.locked, l.mode)
95
+ }), [
96
+ JSON.stringify(l),
97
+ n.pageSize,
98
+ n.locked
99
+ ]), [f, g] = m(n, d), _ = r(d, f, { client: n.client });
100
+ return t.q.trim() ? /* @__PURE__ */ u(h, {
101
+ surface: "search",
102
+ empty: i("contents.noResults", { q: t.q.trim() }),
103
+ ...n,
104
+ pageSize: d.limit,
105
+ result: _,
106
+ page: f,
107
+ setPage: g,
108
+ render: JSON.stringify([d, f]),
109
+ filterKinds: t.kind ? [t.kind] : void 0,
110
+ filterLanguage: t.display_language ?? t.language
111
+ }) : null;
112
+ }
113
+ //#endregion
114
+ export { g as PagedContentGrid, _ as PagedSearchResults };
115
+
116
+ //# sourceMappingURL=paged-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paged-grid.js","names":[],"sources":["../../../src/components/content/paged-grid.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useMemo, useRef, useState } from \"react\";\nimport type { ContentQuery, SearchQuery } from \"../../client/discovery.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useContentsPage, useSearchPage, type UseContentsPage } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { CardGrid, cardListMode, type CardListProps } from \"./grid.js\";\nimport { Pagination } from \"./pagination.js\";\n\nconst NUMBERED_PAGE_SIZE = 24;\n\n/** The page a list shows: the host's, else its own, which starts again when the query changes. */\nfunction usePage(p: CardListProps, query: unknown): [number, (page: number) => void] {\n const key = JSON.stringify(query);\n const [own, setOwn] = useState({ key, page: 1 });\n const ownPage = own.key === key ? own.page : 1;\n const page = Math.max(1, Math.floor(p.page ?? ownPage));\n return [\n page,\n (next) => {\n if (p.page === undefined) setOwn({ key, page: next });\n p.onPageChange?.(next);\n },\n ];\n}\n\nfunction NumberedCards({\n result,\n page,\n setPage,\n render,\n filterKinds,\n filterLanguage,\n ...p\n}: CardListProps & { result: UseContentsPage; page: number; setPage: (page: number) => void; render: string; filterKinds?: readonly string[]; filterLanguage?: string }) {\n const top = useRef<HTMLDivElement>(null);\n const size = p.pageSize ?? NUMBERED_PAGE_SIZE;\n const total = result.summary?.total ?? 0;\n const list = useMemo(\n () => ({ ...result, summary: result.summary, loadingMore: false, hasMore: false, loadMore: async () => {} }),\n [result],\n );\n const go = (next: number, scroll: boolean) => {\n setPage(next);\n if (scroll) top.current?.scrollIntoView({ block: \"start\", behavior: \"smooth\" });\n };\n const controls = (bottom: boolean) =>\n !result.loading || total > 0 ? (\n <Pagination page={page} total={total} atLeast={result.summary?.truncated} pageSize={size} range={!bottom} pageHref={p.pageHref} onPageChange={(n) => go(n, bottom)} />\n ) : null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"numbered-grid\">\n <div ref={top} className=\"scroll-mt-20\">\n {controls(false)}\n </div>\n <CardGrid {...p} className={undefined} list={list} render={render} filterKinds={filterKinds} filterLanguage={filterLanguage} />\n {list.items.length > 0 && controls(true)}\n </ContentKitUiRoot>\n );\n}\n\n/** ContentGrid's numbered pages: each page with the selection total. */\nexport function PagedContentGrid({ query, locked = \"show\", ...p }: CardListProps & { query: ContentQuery }) {\n const { page: _page, cursor: _cursor, ...base } = query;\n const q = useMemo<ContentQuery>(\n () => ({ ...base, summary: true, limit: base.limit ?? p.pageSize ?? NUMBERED_PAGE_SIZE, mode: cardListMode(locked, base.mode) }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(base), p.pageSize, locked],\n );\n const [page, setPage] = usePage(p, q);\n const result = useContentsPage(q, page, { client: p.client });\n return (\n <NumberedCards\n {...p}\n pageSize={q.limit}\n locked={locked}\n result={result}\n page={page}\n setPage={setPage}\n render={JSON.stringify([q, page])}\n filterKinds={query.kind ? [query.kind] : undefined}\n filterLanguage={query.display_language ?? query.language}\n />\n );\n}\n\n/** SearchResults' numbered pages; nothing until the query has text. */\nexport function PagedSearchResults({ query, ...p }: CardListProps & { query: SearchQuery }) {\n const { t } = useMessages();\n const { page: _page, cursor: _cursor, ...base } = query;\n const q = useMemo<SearchQuery>(\n () => ({ ...base, summary: true, limit: base.limit ?? p.pageSize ?? NUMBERED_PAGE_SIZE, mode: cardListMode(p.locked, base.mode) }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(base), p.pageSize, p.locked],\n );\n const [page, setPage] = usePage(p, q);\n const result = useSearchPage(q, page, { client: p.client });\n if (!query.q.trim()) return null;\n return (\n <NumberedCards\n surface=\"search\"\n empty={t(\"contents.noResults\", { q: query.q.trim() })}\n {...p}\n pageSize={q.limit}\n result={result}\n page={page}\n setPage={setPage}\n render={JSON.stringify([q, page])}\n filterKinds={query.kind ? [query.kind] : undefined}\n filterLanguage={query.display_language ?? query.language}\n />\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface PaginationProps {
2
+ /** The current page, from 1. */
3
+ page: number;
4
+ /** Every item the pages hold; a lower bound with atLeast. */
5
+ total: number;
6
+ pageSize: number;
7
+ /** total is a lower bound: more pages may follow the last known one. */
8
+ atLeast?: boolean;
9
+ onPageChange: (page: number) => void;
10
+ /** Each page's link, for crawlers and opening in a new tab; clicks still call onPageChange. */
11
+ pageHref?: (page: number) => string;
12
+ /** Show "Showing X–Y of Z"; default true. */
13
+ range?: boolean;
14
+ className?: string;
15
+ }
16
+ /** The page numbers shown: first, last and two either side of the current one, with gaps. */
17
+ export declare function pageWindow(page: number, last: number): (number | "gap")[];
18
+ /** Numbered page controls with the shown range, for lists whose page lives in the host's URL. */
19
+ export declare function Pagination(p: PaginationProps): import('react').JSX.Element | null;
@@ -0,0 +1,97 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { buttonVariants as t } from "../../ui/button.js";
3
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
+ import { cn as i } from "cn";
5
+ import { ArrowLeft01Icon as a, ArrowRight01Icon as o } from "@hugeicons/core-free-icons";
6
+ import { HugeiconsIcon as s } from "@hugeicons/react";
7
+ //#region src/components/content/pagination.tsx
8
+ function c(e, t) {
9
+ let n = new Set([
10
+ 1,
11
+ t,
12
+ e - 2,
13
+ e - 1,
14
+ e,
15
+ e + 1,
16
+ e + 2
17
+ ].filter((e) => e >= 1 && e <= t)), r = [], i = 0;
18
+ for (let e of [...n].sort((e, t) => e - t)) e - i === 2 ? r.push(e - 1) : e - i > 2 && r.push("gap"), r.push(e), i = e;
19
+ return r;
20
+ }
21
+ function l(l) {
22
+ let u = e(), { t: d } = u, f = Math.max(1, l.pageSize), p = Math.max(1, Math.ceil(l.total / f)), m = Math.max(1, l.page), h = Math.max(p, m), g = (e) => new Intl.NumberFormat(u.language).format(e);
23
+ if (l.total === 0 && m === 1) return null;
24
+ let _ = Math.min(l.total, (m - 1) * f + 1), v = Math.min(l.total, m * f), y = (e, r, a = {}) => {
25
+ let o = i(t({
26
+ variant: a.current ? "default" : "ghost",
27
+ size: "sm"
28
+ }), "min-w-8 tabular-nums", a.disabled && "pointer-events-none opacity-50"), s = (t) => {
29
+ t.defaultPrevented || t.metaKey || t.ctrlKey || t.shiftKey || t.altKey || t.button !== 0 || (t.preventDefault(), !a.disabled && !a.current && l.onPageChange(e));
30
+ };
31
+ return l.pageHref && !a.disabled ? /* @__PURE__ */ n("a", {
32
+ href: l.pageHref(e),
33
+ className: o,
34
+ "aria-current": a.current ? "page" : void 0,
35
+ "aria-label": a.aria,
36
+ onClick: s,
37
+ children: r
38
+ }) : /* @__PURE__ */ n("button", {
39
+ type: "button",
40
+ className: o,
41
+ "aria-current": a.current ? "page" : void 0,
42
+ "aria-label": a.aria,
43
+ disabled: a.disabled,
44
+ onClick: s,
45
+ children: r
46
+ });
47
+ };
48
+ return /* @__PURE__ */ r("nav", {
49
+ "aria-label": d("listing.pages"),
50
+ className: i("flex flex-wrap items-center justify-between gap-2 text-sm", l.className),
51
+ "data-ckui": "pagination",
52
+ children: [l.range !== !1 && l.total > 0 && /* @__PURE__ */ n("p", {
53
+ className: "text-muted-foreground tabular-nums",
54
+ role: "status",
55
+ children: d(l.atLeast ? "listing.showingAtLeast" : "listing.showing", {
56
+ from: g(_),
57
+ to: g(v),
58
+ total: g(l.total)
59
+ })
60
+ }), (h > 1 || l.atLeast) && /* @__PURE__ */ r("div", {
61
+ className: "ms-auto flex flex-wrap items-center gap-1",
62
+ children: [
63
+ y(m - 1, /* @__PURE__ */ n(s, {
64
+ icon: a,
65
+ strokeWidth: 2
66
+ }), {
67
+ disabled: m <= 1,
68
+ aria: d("listing.previous")
69
+ }),
70
+ c(m, h).map((e, t) => e === "gap" ? /* @__PURE__ */ n("span", {
71
+ "aria-hidden": !0,
72
+ className: "px-1 text-muted-foreground",
73
+ children: "…"
74
+ }, `gap-${t}`) : /* @__PURE__ */ n("span", { children: y(e, g(e), {
75
+ current: e === m,
76
+ aria: d("listing.goToPage", { page: g(e) })
77
+ }) }, e)),
78
+ l.atLeast && /* @__PURE__ */ n("span", {
79
+ "aria-hidden": !0,
80
+ className: "px-1 text-muted-foreground",
81
+ children: "…"
82
+ }),
83
+ y(m + 1, /* @__PURE__ */ n(s, {
84
+ icon: o,
85
+ strokeWidth: 2
86
+ }), {
87
+ disabled: m >= h && !l.atLeast,
88
+ aria: d("listing.next")
89
+ })
90
+ ]
91
+ })]
92
+ });
93
+ }
94
+ //#endregion
95
+ export { l as Pagination, c as pageWindow };
96
+
97
+ //# sourceMappingURL=pagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.js","names":[],"sources":["../../../src/components/content/pagination.tsx"],"sourcesContent":["import { ArrowLeft01Icon, ArrowRight01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport type { MouseEvent, ReactNode } from \"react\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { buttonVariants } from \"#ckui/ui/button\";\n\nexport interface PaginationProps {\n /** The current page, from 1. */\n page: number;\n /** Every item the pages hold; a lower bound with atLeast. */\n total: number;\n pageSize: number;\n /** total is a lower bound: more pages may follow the last known one. */\n atLeast?: boolean;\n onPageChange: (page: number) => void;\n /** Each page's link, for crawlers and opening in a new tab; clicks still call onPageChange. */\n pageHref?: (page: number) => string;\n /** Show \"Showing X–Y of Z\"; default true. */\n range?: boolean;\n className?: string;\n}\n\n/** The page numbers shown: first, last and two either side of the current one, with gaps. */\nexport function pageWindow(page: number, last: number): (number | \"gap\")[] {\n const shown = new Set([1, last, page - 2, page - 1, page, page + 1, page + 2].filter((n) => n >= 1 && n <= last));\n const out: (number | \"gap\")[] = [];\n let previous = 0;\n for (const n of [...shown].sort((a, b) => a - b)) {\n if (n - previous === 2) out.push(n - 1);\n else if (n - previous > 2) out.push(\"gap\");\n out.push(n);\n previous = n;\n }\n return out;\n}\n\n/** Numbered page controls with the shown range, for lists whose page lives in the host's URL. */\nexport function Pagination(p: PaginationProps) {\n const m = useMessages();\n const { t } = m;\n const size = Math.max(1, p.pageSize);\n const known = Math.max(1, Math.ceil(p.total / size));\n const page = Math.max(1, p.page);\n const last = Math.max(known, page);\n const format = (n: number) => new Intl.NumberFormat(m.language).format(n);\n if (p.total === 0 && page === 1) return null;\n const from = Math.min(p.total, (page - 1) * size + 1);\n const to = Math.min(p.total, page * size);\n const control = (target: number, label: ReactNode, o: { current?: boolean; disabled?: boolean; aria?: string } = {}) => {\n const className = cn(buttonVariants({ variant: o.current ? \"default\" : \"ghost\", size: \"sm\" }), \"min-w-8 tabular-nums\", o.disabled && \"pointer-events-none opacity-50\");\n const go = (event: MouseEvent) => {\n if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return;\n event.preventDefault();\n if (!o.disabled && !o.current) p.onPageChange(target);\n };\n return p.pageHref && !o.disabled ? (\n <a href={p.pageHref(target)} className={className} aria-current={o.current ? \"page\" : undefined} aria-label={o.aria} onClick={go}>\n {label}\n </a>\n ) : (\n <button type=\"button\" className={className} aria-current={o.current ? \"page\" : undefined} aria-label={o.aria} disabled={o.disabled} onClick={go}>\n {label}\n </button>\n );\n };\n return (\n <nav aria-label={t(\"listing.pages\")} className={cn(\"flex flex-wrap items-center justify-between gap-2 text-sm\", p.className)} data-ckui=\"pagination\">\n {p.range !== false && p.total > 0 && (\n <p className=\"text-muted-foreground tabular-nums\" role=\"status\">\n {t(p.atLeast ? \"listing.showingAtLeast\" : \"listing.showing\", { from: format(from), to: format(to), total: format(p.total) })}\n </p>\n )}\n {(last > 1 || p.atLeast) && (\n <div className=\"ms-auto flex flex-wrap items-center gap-1\">\n {control(page - 1, <HugeiconsIcon icon={ArrowLeft01Icon} strokeWidth={2} />, { disabled: page <= 1, aria: t(\"listing.previous\") })}\n {pageWindow(page, last).map((n, i) =>\n n === \"gap\" ? (\n <span key={`gap-${i}`} aria-hidden className=\"px-1 text-muted-foreground\">\n …\n </span>\n ) : (\n <span key={n}>{control(n, format(n), { current: n === page, aria: t(\"listing.goToPage\", { page: format(n) }) })}</span>\n ),\n )}\n {p.atLeast && <span aria-hidden className=\"px-1 text-muted-foreground\">…</span>}\n {control(page + 1, <HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} />, { disabled: page >= last && !p.atLeast, aria: t(\"listing.next\") })}\n </div>\n )}\n </nav>\n );\n}\n"],"mappings":";;;;;;;AAwBA,SAAgB,EAAW,GAAc,GAAkC;CACzE,IAAM,IAAQ,IAAI,IAAI;EAAC;EAAG;EAAM,IAAO;EAAG,IAAO;EAAG;EAAM,IAAO;EAAG,IAAO;CAAC,CAAC,CAAC,QAAQ,MAAM,KAAK,KAAK,KAAK,CAAI,CAAC,GAC1G,IAA0B,CAAC,GAC7B,IAAW;CACf,KAAK,IAAM,KAAK,CAAC,GAAG,CAAK,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,GAI7C,AAHI,IAAI,MAAa,IAAG,EAAI,KAAK,IAAI,CAAC,IAC7B,IAAI,IAAW,KAAG,EAAI,KAAK,KAAK,GACzC,EAAI,KAAK,CAAC,GACV,IAAW;CAEb,OAAO;AACT;AAGA,SAAgB,EAAW,GAAoB;CAC7C,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAO,KAAK,IAAI,GAAG,EAAE,QAAQ,GAC7B,IAAQ,KAAK,IAAI,GAAG,KAAK,KAAK,EAAE,QAAQ,CAAI,CAAC,GAC7C,IAAO,KAAK,IAAI,GAAG,EAAE,IAAI,GACzB,IAAO,KAAK,IAAI,GAAO,CAAI,GAC3B,KAAU,MAAc,IAAI,KAAK,aAAa,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC;CACxE,IAAI,EAAE,UAAU,KAAK,MAAS,GAAG,OAAO;CACxC,IAAM,IAAO,KAAK,IAAI,EAAE,QAAQ,IAAO,KAAK,IAAO,CAAC,GAC9C,IAAK,KAAK,IAAI,EAAE,OAAO,IAAO,CAAI,GAClC,KAAW,GAAgB,GAAkB,IAA8D,CAAC,MAAM;EACtH,IAAM,IAAY,EAAG,EAAe;GAAE,SAAS,EAAE,UAAU,YAAY;GAAS,MAAM;EAAK,CAAC,GAAG,wBAAwB,EAAE,YAAY,gCAAgC,GAC/J,KAAM,MAAsB;GAC5B,EAAM,oBAAoB,EAAM,WAAW,EAAM,WAAW,EAAM,YAAY,EAAM,UAAU,EAAM,WAAW,MACnH,EAAM,eAAe,GACjB,CAAC,EAAE,YAAY,CAAC,EAAE,WAAS,EAAE,aAAa,CAAM;EACtD;EACA,OAAO,EAAE,YAAY,CAAC,EAAE,WACtB,kBAAC,KAAD;GAAG,MAAM,EAAE,SAAS,CAAM;GAAc;GAAW,gBAAc,EAAE,UAAU,SAAS,KAAA;GAAW,cAAY,EAAE;GAAM,SAAS;GAC3H,UAAA;EACA,CAAA,IAEH,kBAAC,UAAD;GAAQ,MAAK;GAAoB;GAAW,gBAAc,EAAE,UAAU,SAAS,KAAA;GAAW,cAAY,EAAE;GAAM,UAAU,EAAE;GAAU,SAAS;GAC1I,UAAA;EACK,CAAA;CAEZ;CACA,OACE,kBAAC,OAAD;EAAK,cAAY,EAAE,eAAe;EAAG,WAAW,EAAG,6DAA6D,EAAE,SAAS;EAAG,aAAU;EAAxI,UAAA,CACG,EAAE,UAAU,MAAS,EAAE,QAAQ,KAC9B,kBAAC,KAAD;GAAG,WAAU;GAAqC,MAAK;GACpD,UAAA,EAAE,EAAE,UAAU,2BAA2B,mBAAmB;IAAE,MAAM,EAAO,CAAI;IAAG,IAAI,EAAO,CAAE;IAAG,OAAO,EAAO,EAAE,KAAK;GAAE,CAAC;EAC1H,CAAA,IAEH,IAAO,KAAK,EAAE,YACd,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACG,EAAQ,IAAO,GAAG,kBAAC,GAAD;KAAe,MAAM;KAAiB,aAAa;IAAI,CAAA,GAAG;KAAE,UAAU,KAAQ;KAAG,MAAM,EAAE,kBAAkB;IAAE,CAAC;IAChI,EAAW,GAAM,CAAI,CAAC,CAAC,KAAK,GAAG,MAC9B,MAAM,QACJ,kBAAC,QAAD;KAAuB,eAAA;KAAY,WAAU;KAA6B,UAAA;IAEpE,GAFK,OAAO,GAEZ,IAEN,kBAAC,QAAD,EAAA,UAAe,EAAQ,GAAG,EAAO,CAAC,GAAG;KAAE,SAAS,MAAM;KAAM,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAO,CAAC,EAAE,CAAC;IAAE,CAAC,EAAQ,GAA3G,CAA2G,CAE1H;IACC,EAAE,WAAW,kBAAC,QAAD;KAAM,eAAA;KAAY,WAAU;KAA6B,UAAA;IAAO,CAAA;IAC7E,EAAQ,IAAO,GAAG,kBAAC,GAAD;KAAe,MAAM;KAAkB,aAAa;IAAI,CAAA,GAAG;KAAE,UAAU,KAAQ,KAAQ,CAAC,EAAE;KAAS,MAAM,EAAE,cAAc;IAAE,CAAC;GAC5I;EAEJ,CAAA,CAAA;;AAET"}
@@ -48,6 +48,15 @@ export interface VersionPickerProps {
48
48
  className?: string;
49
49
  appearance?: ContentKitUiAppearance;
50
50
  }
51
+ export interface LanguageFallbackNoticeProps extends Omit<VersionPickerProps, "labels"> {
52
+ /** The page's language: the notice shows when the shown version doesn't serve it. */
53
+ language: string;
54
+ }
55
+ /**
56
+ * A quiet note that the content isn't available in the page's language, which
57
+ * version it shows instead, and each language it does serve to switch to.
58
+ */
59
+ export declare function LanguageFallbackNotice({ versions, value, onChange, language, className, appearance }: LanguageFallbackNoticeProps): import('react').JSX.Element | null;
51
60
  /**
52
61
  * Chooses among a content's versions: a language first when they differ in
53
62
  * language, then its variants (traits, labels). The choice is the page's ?v=