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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +105 -43
  2. package/dist/client/beacons.d.ts +3 -13
  3. package/dist/client/beacons.js +4 -45
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/client.d.ts +3 -0
  6. package/dist/client/client.js +45 -43
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/content/favorites.d.ts +2 -0
  9. package/dist/client/content/favorites.js.map +1 -1
  10. package/dist/client/content/types.d.ts +3 -0
  11. package/dist/client/content/types.js.map +1 -1
  12. package/dist/client/discovery.d.ts +28 -3
  13. package/dist/client/discovery.js +22 -1
  14. package/dist/client/discovery.js.map +1 -1
  15. package/dist/client/engagement.d.ts +70 -0
  16. package/dist/client/engagement.js +191 -0
  17. package/dist/client/engagement.js.map +1 -0
  18. package/dist/client/generated/routes.d.ts +35 -9
  19. package/dist/client/generated/routes.js +59 -2
  20. package/dist/client/generated/routes.js.map +1 -1
  21. package/dist/client/generated/wire.d.ts +53 -12
  22. package/dist/client/generated/wire.js.map +1 -1
  23. package/dist/client/guest-reading-history.d.ts +40 -0
  24. package/dist/client/guest-reading-history.js +144 -0
  25. package/dist/client/guest-reading-history.js.map +1 -0
  26. package/dist/client/index.d.ts +4 -2
  27. package/dist/client/stats.d.ts +5 -5
  28. package/dist/client/stats.js.map +1 -1
  29. package/dist/client.js +24 -22
  30. package/dist/components/admin/content-manager.d.ts +1 -1
  31. package/dist/components/admin/content-manager.js +342 -302
  32. package/dist/components/admin/content-manager.js.map +1 -1
  33. package/dist/components/admin/page-review.js +106 -97
  34. package/dist/components/admin/page-review.js.map +1 -1
  35. package/dist/components/admin/quick-search.d.ts +14 -0
  36. package/dist/components/admin/quick-search.js +60 -0
  37. package/dist/components/admin/quick-search.js.map +1 -0
  38. package/dist/components/admin/reader-content-editor.d.ts +6 -0
  39. package/dist/components/admin/reader-content-editor.js +733 -0
  40. package/dist/components/admin/reader-content-editor.js.map +1 -0
  41. package/dist/components/admin/reader-manifest.js +120 -105
  42. package/dist/components/admin/reader-manifest.js.map +1 -1
  43. package/dist/components/admin/stats-dashboard.d.ts +4 -4
  44. package/dist/components/admin/stats-dashboard.js +132 -134
  45. package/dist/components/admin/stats-dashboard.js.map +1 -1
  46. package/dist/components/content/daily-releases.js +143 -191
  47. package/dist/components/content/daily-releases.js.map +1 -1
  48. package/dist/components/content/editor-save.d.ts +32 -0
  49. package/dist/components/content/editor-save.js +51 -0
  50. package/dist/components/content/editor-save.js.map +1 -0
  51. package/dist/components/content/editors.d.ts +17 -2
  52. package/dist/components/content/editors.js +359 -328
  53. package/dist/components/content/editors.js.map +1 -1
  54. package/dist/components/content/favorites.d.ts +6 -0
  55. package/dist/components/content/favorites.js +33 -0
  56. package/dist/components/content/favorites.js.map +1 -0
  57. package/dist/components/content/filter-dialog.js +19 -15
  58. package/dist/components/content/filter-dialog.js.map +1 -1
  59. package/dist/components/content/filters.d.ts +2 -0
  60. package/dist/components/content/filters.js +5 -1
  61. package/dist/components/content/filters.js.map +1 -1
  62. package/dist/components/content/grid.d.ts +34 -13
  63. package/dist/components/content/grid.js +300 -271
  64. package/dist/components/content/grid.js.map +1 -1
  65. package/dist/components/content/history.d.ts +6 -0
  66. package/dist/components/content/history.js +91 -0
  67. package/dist/components/content/history.js.map +1 -0
  68. package/dist/components/content/index.d.ts +1 -0
  69. package/dist/components/content/offer-fields.d.ts +1 -1
  70. package/dist/components/content/offer-fields.js +86 -64
  71. package/dist/components/content/offer-fields.js.map +1 -1
  72. package/dist/components/content/search.d.ts +11 -4
  73. package/dist/components/content/search.js +22 -11
  74. package/dist/components/content/search.js.map +1 -1
  75. package/dist/components/content/summary-tile.d.ts +14 -0
  76. package/dist/components/content/summary-tile.js +63 -0
  77. package/dist/components/content/summary-tile.js.map +1 -0
  78. package/dist/components/groups/memberships.d.ts +11 -1
  79. package/dist/components/groups/memberships.js +1 -1
  80. package/dist/components/groups/memberships.js.map +1 -1
  81. package/dist/components/media-folder-editor.d.ts +4 -0
  82. package/dist/components/media-folder-editor.js +178 -168
  83. package/dist/components/media-folder-editor.js.map +1 -1
  84. package/dist/components/video-mini-player.d.ts +4 -1
  85. package/dist/components/video-mini-player.js +77 -71
  86. package/dist/components/video-mini-player.js.map +1 -1
  87. package/dist/components/video-player.d.ts +4 -1
  88. package/dist/components/video-player.js +280 -276
  89. package/dist/components/video-player.js.map +1 -1
  90. package/dist/index.d.ts +1 -0
  91. package/dist/index.js +17 -14
  92. package/dist/locales/admin/de.js +33 -8
  93. package/dist/locales/admin/de.js.map +1 -1
  94. package/dist/locales/admin/en.d.ts +29 -4
  95. package/dist/locales/admin/en.js +33 -8
  96. package/dist/locales/admin/en.js.map +1 -1
  97. package/dist/locales/admin/es.js +33 -8
  98. package/dist/locales/admin/es.js.map +1 -1
  99. package/dist/locales/admin/ja.js +33 -8
  100. package/dist/locales/admin/ja.js.map +1 -1
  101. package/dist/locales/admin/ko.js +33 -8
  102. package/dist/locales/admin/ko.js.map +1 -1
  103. package/dist/locales/admin/zh.js +33 -8
  104. package/dist/locales/admin/zh.js.map +1 -1
  105. package/dist/locales/discovery/de.js +0 -1
  106. package/dist/locales/discovery/de.js.map +1 -1
  107. package/dist/locales/discovery/en.d.ts +0 -1
  108. package/dist/locales/discovery/en.js +0 -1
  109. package/dist/locales/discovery/en.js.map +1 -1
  110. package/dist/locales/discovery/es.js +0 -1
  111. package/dist/locales/discovery/es.js.map +1 -1
  112. package/dist/locales/discovery/ja.js +0 -1
  113. package/dist/locales/discovery/ja.js.map +1 -1
  114. package/dist/locales/discovery/ko.js +0 -1
  115. package/dist/locales/discovery/ko.js.map +1 -1
  116. package/dist/locales/discovery/zh.js +0 -1
  117. package/dist/locales/discovery/zh.js.map +1 -1
  118. package/dist/locales/en.d.ts +29 -5
  119. package/dist/react/discovery.d.ts +20 -17
  120. package/dist/react/discovery.js +273 -221
  121. package/dist/react/discovery.js.map +1 -1
  122. package/dist/react/index.d.ts +2 -1
  123. package/dist/react/page-review.d.ts +1 -1
  124. package/dist/react/page-review.js +6 -5
  125. package/dist/react/page-review.js.map +1 -1
  126. package/dist/react/resources.d.ts +3 -1
  127. package/dist/react/resources.js +4 -0
  128. package/dist/react/resources.js.map +1 -1
  129. package/dist/react/stats.d.ts +2 -2
  130. package/dist/react/stats.js.map +1 -1
  131. package/dist/react/use-draft.d.ts +8 -0
  132. package/dist/react/use-draft.js +35 -0
  133. package/dist/react/use-draft.js.map +1 -0
  134. package/dist/react/use-engagement.d.ts +26 -0
  135. package/dist/react/use-engagement.js +221 -0
  136. package/dist/react/use-engagement.js.map +1 -0
  137. package/dist/react/use-resource.d.ts +4 -3
  138. package/dist/react/use-resource.js +2 -1
  139. package/dist/react/use-resource.js.map +1 -1
  140. package/dist/react.js +12 -11
  141. package/dist/styles.css +1 -1
  142. package/dist/ui/dialog.d.ts +2 -1
  143. package/dist/ui/dialog.js +23 -20
  144. package/dist/ui/dialog.js.map +1 -1
  145. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"stats-dashboard.js","names":[],"sources":["../../../src/components/admin/stats-dashboard.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentKitError } from \"../../client/errors.js\";\nimport type { Period } from \"../../client/discovery.js\";\nimport type { RankedContent } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useActivity, useTopContents } from \"../../react/stats.js\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\ntype Metric = \"views\" | \"comments\";\n\nexport interface StatsDashboardProps extends AdminFlowProps {\n /** Content kinds offered: codes, or codes with labels; the first shows first. */\n kinds: readonly Choice[];\n /** A ranked item's link; default its path. */\n contentHref?: (content: RankedContent) => string | undefined;\n /** Rows in the ranking. Default 10. */\n limit?: number;\n}\n\nconst choices = (list: readonly Choice[]) => list.map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst DAYS = [7, 30, 90] as const;\nconst METRICS: readonly Metric[] = [\"views\", \"comments\"];\nconst PERIODS: readonly Period[] = [\"7d\", \"30d\", \"90d\", \"365d\", \"all\"];\nconst refused = (e?: ContentKitError) => e?.code === \"forbidden\" || e?.code === \"unauthorized\";\n\n/**\n * Content statistics for staff (root:content:view-restricted): a kind's views\n * per day (view sessions, distinct and signed-in viewers) and its content\n * ranked by views or comments over a period. Views need the signal plane;\n * without it the dashboard says so and still ranks by comments.\n */\nexport function StatsDashboard({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: StatsDashboardProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"stats-dashboard\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Dashboard {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<StatsDashboardProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Dashboard({ kinds, contentHref, limit = 10, client }: Inner) {\n const { t } = useMessages();\n const options = choices(kinds);\n const [kind, setKind] = useState(options[0]?.value ?? \"\");\n const [days, setDays] = useState<number>(7);\n const [metric, setMetric] = useState<Metric>(\"views\");\n const [period, setPeriod] = useState<Period>(\"30d\");\n const activity = useActivity(kind ? { kind, days } : null, { client });\n const top = useTopContents(kind ? { kind, metric, period, limit } : null, { client });\n if (refused(activity.error) || refused(top.error)) return <NoAccess>{t(\"stats.noAccess\")}</NoAccess>;\n const rows = activity.data?.days ?? [];\n const most = Math.max(1, ...rows.map((d) => d.views));\n const total = rows.reduce((n, d) => n + d.views, 0);\n return (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"stats.title\")}</h2>\n {options.length > 1 && (\n <Pick label={t(\"stats.kind\")} value={kind} onChange={setKind} options={options} />\n )}\n </div>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-activity\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.activity\")}</h3>\n <Pick label={t(\"stats.days\")} value={String(days)} onChange={(v) => setDays(Number(v))} options={DAYS.map((d) => ({ value: String(d), label: t(\"stats.lastDays\", { count: d }) }))} />\n </div>\n {activity.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : activity.error ? (\n <LoadFailed error={activity.error} message={t(\"stats.loadFailed\")} retry={activity.reload} />\n ) : activity.loading && !activity.data ? (\n <Rows count={2} />\n ) : !rows.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noActivity\")}</p>\n ) : (\n <table className=\"w-full text-left tabular-nums\" aria-busy={activity.loading}>\n <thead className=\"text-xs text-muted-foreground\">\n <tr>\n <th className=\"py-1 font-medium\">{t(\"stats.date\")}</th>\n <th className=\"py-1 font-medium\">{t(\"stats.views\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.viewers\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.users\")}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((d) => (\n <tr key={d.date} className=\"border-t border-border\" data-ckui=\"stats-day\">\n <td className=\"py-1.5 pe-3 whitespace-nowrap\">{d.date}</td>\n <td className=\"w-1/2 py-1.5 pe-3\">\n <span className=\"flex items-center gap-2\">\n <span aria-hidden className=\"h-2 rounded-full bg-primary\" style={{ width: `${Math.max(2, (d.views / most) * 100)}%` }} />\n {d.views}\n </span>\n </td>\n <td className=\"py-1.5 text-end\">{d.viewers}</td>\n <td className=\"py-1.5 text-end\">{d.users}</td>\n </tr>\n ))}\n </tbody>\n <tfoot>\n <tr className=\"border-t border-border font-medium\">\n <td className=\"py-1.5\">{t(\"stats.total\")}</td>\n <td className=\"py-1.5\" data-ckui=\"stats-total\">\n {total}\n </td>\n <td />\n <td />\n </tr>\n </tfoot>\n </table>\n )}\n </section>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-top\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.top\")}</h3>\n <div className=\"flex flex-wrap gap-2\">\n <Pick label={t(\"stats.metric\")} value={metric} onChange={(v) => setMetric(v as Metric)} options={METRICS.map((x) => ({ value: x, label: t(`stats.metric_${x}`) }))} />\n <Pick label={t(\"stats.period\")} value={period} onChange={(v) => setPeriod(v as Period)} options={PERIODS.map((x) => ({ value: x, label: t(`stats.period_${x}`) }))} />\n </div>\n </div>\n {top.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : top.error ? (\n <LoadFailed error={top.error} message={t(\"stats.loadFailed\")} retry={top.reload} />\n ) : top.loading && !top.data ? (\n <Rows count={3} />\n ) : !top.data?.contents.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noTop\")}</p>\n ) : (\n <ol className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={top.loading}>\n {top.data.contents.map((c, i) => {\n const href = contentHref ? contentHref(c) : c.path;\n return (\n <li key={c.id} className=\"flex items-center gap-3 p-2.5\" data-ckui=\"stats-ranked\">\n <span className=\"w-6 text-end text-muted-foreground tabular-nums\">{i + 1}</span>\n {href ? (\n <a className=\"min-w-0 flex-1 break-words hover:underline\" href={href}>\n {c.title}\n </a>\n ) : (\n <span className=\"min-w-0 flex-1 break-words\">{c.title}</span>\n )}\n <span className=\"tabular-nums\" data-ckui=\"stats-value\">\n {c.value}\n </span>\n </li>\n );\n })}\n </ol>\n )}\n </section>\n </>\n );\n}\n\nfunction Pick({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: { value: string; label: string }[] }) {\n return (\n <Select value={value} onValueChange={(v) => onChange(String(v))}>\n <SelectTrigger size=\"sm\" aria-label={label}>\n <SelectValue>{(v: string) => options.find((o) => o.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stats-dashboard.js","names":[],"sources":["../../../src/components/admin/stats-dashboard.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentKitError } from \"../../client/errors.js\";\nimport type { Period } from \"../../client/discovery.js\";\nimport { formatDuration } from \"../../client/gallery.js\";\nimport type { TopQuery } from \"../../client/stats.js\";\nimport type { RankedContent } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useActivity, useTopContents } from \"../../react/stats.js\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\ntype Metric = NonNullable<TopQuery[\"metric\"]>;\n\nexport interface StatsDashboardProps extends AdminFlowProps {\n /** Content kinds offered: codes, or codes with labels; the first shows first. */\n kinds: readonly Choice[];\n /** A ranked item's link; default its path. */\n contentHref?: (content: RankedContent) => string | undefined;\n /** Rows in the ranking. Default 10. */\n limit?: number;\n}\n\nconst choices = (list: readonly Choice[]) => list.map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst DAYS = [7, 30, 90] as const;\nconst METRICS: readonly Metric[] = [\"readers\", \"comments\"];\nconst PERIODS: readonly Period[] = [\"7d\", \"30d\", \"90d\", \"365d\", \"all\"];\nconst refused = (e?: ContentKitError) => e?.code === \"forbidden\" || e?.code === \"unauthorized\";\n\n/**\n * Staff statistics from measured attention: daily distinct people and focused\n * active time, plus content ranked by distinct engaged people or comments.\n * Daily people are never summed into a misleading period-unique total.\n * Comments remain available without the signal plane.\n */\nexport function StatsDashboard({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: StatsDashboardProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"stats-dashboard\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Dashboard {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<StatsDashboardProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Dashboard({ kinds, contentHref, limit = 10, client }: Inner) {\n const { t } = useMessages();\n const options = choices(kinds);\n const [kind, setKind] = useState(options[0]?.value ?? \"\");\n const [days, setDays] = useState<number>(7);\n const [metric, setMetric] = useState<Metric>(\"readers\");\n const [period, setPeriod] = useState<Period>(\"30d\");\n const activity = useActivity(kind ? { kind, days } : null, { client });\n const top = useTopContents(kind ? { kind, metric, period, limit } : null, { client });\n if (refused(activity.error) || refused(top.error)) return <NoAccess>{t(\"stats.noAccess\")}</NoAccess>;\n const rows = activity.data?.days ?? [];\n const most = Math.max(1, ...rows.map((d) => d.readers));\n const totalActiveMS = rows.reduce((n, d) => n + d.active_ms, 0);\n return (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"stats.title\")}</h2>\n {options.length > 1 && <Pick label={t(\"stats.kind\")} value={kind} onChange={setKind} options={options} />}\n </div>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-activity\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.activity\")}</h3>\n <Pick\n label={t(\"stats.days\")}\n value={String(days)}\n onChange={(v) => setDays(Number(v))}\n options={DAYS.map((d) => ({ value: String(d), label: t(\"stats.lastDays\", { count: d }) }))}\n />\n </div>\n {activity.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : activity.error ? (\n <LoadFailed error={activity.error} message={t(\"stats.loadFailed\")} retry={activity.reload} />\n ) : activity.loading && !activity.data ? (\n <Rows count={2} />\n ) : !rows.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noActivity\")}</p>\n ) : (\n <table className=\"w-full text-left tabular-nums\" aria-busy={activity.loading}>\n <thead className=\"text-xs text-muted-foreground\">\n <tr>\n <th className=\"py-1 font-medium\">{t(\"stats.date\")}</th>\n <th className=\"py-1 font-medium\">{t(\"stats.readers\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.users\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.activeTime\")}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((d) => (\n <tr key={d.date} className=\"border-t border-border\" data-ckui=\"stats-day\">\n <td className=\"py-1.5 pe-3 whitespace-nowrap\">{d.date}</td>\n <td className=\"w-1/2 py-1.5 pe-3\">\n <span className=\"flex items-center gap-2\">\n <span aria-hidden className=\"h-2 rounded-full bg-primary\" style={{ width: `${Math.max(2, (d.readers / most) * 100)}%` }} />\n {d.readers}\n </span>\n </td>\n <td className=\"py-1.5 text-end\">{d.users}</td>\n <td className=\"py-1.5 text-end\">{formatDuration(Math.floor(d.active_ms / 1000))}</td>\n </tr>\n ))}\n </tbody>\n <tfoot>\n <tr className=\"border-t border-border font-medium\">\n <th scope=\"row\" colSpan={3} className=\"py-1.5 font-medium\">\n {t(\"stats.totalActiveTime\")}\n </th>\n <td className=\"py-1.5 text-end\" data-ckui=\"stats-active-total\">\n {formatDuration(Math.floor(totalActiveMS / 1000))}\n </td>\n </tr>\n </tfoot>\n </table>\n )}\n </section>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-top\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.top\")}</h3>\n <div className=\"flex flex-wrap gap-2\">\n <Pick label={t(\"stats.metric\")} value={metric} onChange={(v) => setMetric(v as Metric)} options={METRICS.map((x) => ({ value: x, label: t(`stats.metric_${x}`) }))} />\n <Pick label={t(\"stats.period\")} value={period} onChange={(v) => setPeriod(v as Period)} options={PERIODS.map((x) => ({ value: x, label: t(`stats.period_${x}`) }))} />\n </div>\n </div>\n {top.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : top.error ? (\n <LoadFailed error={top.error} message={t(\"stats.loadFailed\")} retry={top.reload} />\n ) : top.loading && !top.data ? (\n <Rows count={3} />\n ) : !top.data?.contents.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noTop\")}</p>\n ) : (\n <ol className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={top.loading}>\n {top.data.contents.map((c, i) => {\n const href = contentHref ? contentHref(c) : c.path;\n return (\n <li key={c.id} className=\"flex items-center gap-3 p-2.5\" data-ckui=\"stats-ranked\">\n <span className=\"w-6 text-end text-muted-foreground tabular-nums\">{i + 1}</span>\n {href ? (\n <a className=\"min-w-0 flex-1 break-words hover:underline\" href={href}>\n {c.title}\n </a>\n ) : (\n <span className=\"min-w-0 flex-1 break-words\">{c.title}</span>\n )}\n <span className=\"tabular-nums\" data-ckui=\"stats-value\">\n {c.value}\n </span>\n </li>\n );\n })}\n </ol>\n )}\n </section>\n </>\n );\n}\n\nfunction Pick({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: { value: string; label: string }[] }) {\n return (\n <Select value={value} onValueChange={(v) => onChange(String(v))}>\n <SelectTrigger size=\"sm\" aria-label={label}>\n <SelectValue>{(v: string) => options.find((o) => o.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":"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"}
@@ -1,119 +1,117 @@
1
- import { RenditionImg as e } from "../rendition-img.js";
2
- import { ratio as t } from "../../client/aspect.js";
3
- import { useMessages as n } from "../../i18n/context.js";
4
- import { ContentKitUiRoot as r } from "../../scope.js";
5
- import { ContentKitContext as i } from "../../react/context.js";
6
- import { useContentScope as a } from "../../react/use-resource.js";
7
- import { usePresets as o } from "../../react/public.js";
8
- import { Button as s } from "../../ui/button.js";
9
- import { useNearViewport as c } from "../../react/viewport.js";
10
- import { Skeleton as l } from "../../ui/skeleton.js";
11
- import { contentError as u } from "../social/parts.js";
12
- import { useDailyReleases as d, useExposure as f } from "../../react/discovery.js";
13
- import { ContentCard as p } from "./card.js";
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { ContentKitUiRoot as t } from "../../scope.js";
3
+ import { ContentKitContext as n } from "../../react/context.js";
4
+ import { useContentScope as r } from "../../react/use-resource.js";
5
+ import { Button as i } from "../../ui/button.js";
6
+ import { useNearViewport as a } from "../../react/viewport.js";
7
+ import { Skeleton as o } from "../../ui/skeleton.js";
8
+ import { contentError as s } from "../social/parts.js";
9
+ import { useDailyReleases as c, useExposure as l } from "../../react/discovery.js";
10
+ import { ContentCard as u } from "./card.js";
11
+ import { ContentFilterDialog as d } from "./filter-dialog.js";
12
+ import { useReleaseReveal as f } from "./release-reveal.js";
13
+ import { ContentSummaryTile as p } from "./summary-tile.js";
14
14
  import { cardGridLayout as m } from "./grid.js";
15
- import { ContentFilterDialog as h } from "./filter-dialog.js";
16
- import { useReleaseReveal as g } from "./release-reveal.js";
17
- import { useCallback as _, useContext as v, useId as y, useMemo as b, useRef as x, useState as S } from "react";
18
- import { jsx as C, jsxs as w } from "react/jsx-runtime";
19
- import { cn as T } from "cn";
20
- import { ArrowDown01Icon as E, LockIcon as D, Settings01Icon as O } from "@hugeicons/core-free-icons";
21
- import { HugeiconsIcon as k } from "@hugeicons/react";
15
+ import { useCallback as h, useContext as g, useId as _, useMemo as v, useRef as y, useState as b } from "react";
16
+ import { jsx as x, jsxs as S } from "react/jsx-runtime";
17
+ import { cn as C } from "cn";
18
+ import { ArrowDown01Icon as w } from "@hugeicons/core-free-icons";
19
+ import { HugeiconsIcon as T } from "@hugeicons/react";
22
20
  //#region src/components/content/daily-releases.tsx
23
- var A = (e, t) => Number.isFinite(e) ? Math.max(0, Math.floor(e)) : t;
24
- function j({ query: e, initialVisible: t = 30, revealStep: o = 30, collapsePremium: c = !1, summaryLabels: f, ...p }) {
25
- let h = n(), { client: g, viewer: _ } = a(p.client), y = v(i), x = b(() => ({
21
+ var E = (e, t) => Number.isFinite(e) ? Math.max(0, Math.floor(e)) : t;
22
+ function D({ query: a, initialVisible: l = 30, revealStep: u = 30, collapsePremium: d = !1, summaryLabels: f, ...p }) {
23
+ let h = e(), { client: _, viewer: y } = r(p.client), w = g(n), T = v(() => ({
26
24
  days: 10,
27
25
  time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,
28
- ...e
29
- }), [e]), E = d(x, { client: p.client }), D = m(p.grid), O = JSON.stringify(x), k = b(() => ({
30
- client: g,
31
- viewer: _,
32
- language: y?.language,
33
- accessRevision: y?.accessRevision,
34
- requestKey: O
26
+ ...a
27
+ }), [a]), D = c(T, { client: p.client }), O = m(p.grid), A = JSON.stringify(T), j = v(() => ({
28
+ client: _,
29
+ viewer: y,
30
+ language: w?.language,
31
+ accessRevision: w?.accessRevision,
32
+ requestKey: A
35
33
  }), [
36
- g,
37
34
  _,
38
- y?.language,
39
- y?.accessRevision,
40
- O
41
- ]), [j, M] = S({
42
- identity: k,
43
- data: E.data,
35
+ y,
36
+ w?.language,
37
+ w?.accessRevision,
38
+ A
39
+ ]), [M, N] = b({
40
+ identity: j,
41
+ data: D.data,
44
42
  revision: 0
45
43
  });
46
- (j.identity !== k || E.data && j.data !== E.data) && M({
47
- identity: k,
48
- data: E.data,
49
- revision: j.revision + (j.identity === k ? 0 : 1)
44
+ (M.identity !== j || D.data && M.data !== D.data) && N({
45
+ identity: j,
46
+ data: D.data,
47
+ revision: M.revision + (M.identity === j ? 0 : 1)
50
48
  });
51
- let P = E.data ?? (j.identity === k ? j.data : void 0), F = !E.data && !!P, I = b(() => P?.days.filter((e) => e.contents.length || e.filtered_count > 0).sort((e, t) => t.date.localeCompare(e.date)) ?? [], [P]), L = A(t, 30);
52
- return /* @__PURE__ */ w(r, {
49
+ let P = D.data ?? (M.identity === j ? M.data : void 0), F = !D.data && !!P, I = v(() => P?.days.filter((e) => e.contents.length || e.filtered_count > 0).sort((e, t) => t.date.localeCompare(e.date)) ?? [], [P]), L = E(l, 30);
50
+ return /* @__PURE__ */ S(t, {
53
51
  appearance: p.appearance,
54
- className: T("grid gap-6 text-sm", p.className),
52
+ className: C("grid gap-6 text-sm", p.className),
55
53
  "data-ckui": "daily-releases",
56
- "aria-busy": E.loading,
57
- children: [P && E.error && /* @__PURE__ */ w("div", {
54
+ "aria-busy": D.loading,
55
+ children: [P && D.error && /* @__PURE__ */ S("div", {
58
56
  role: "alert",
59
57
  className: "grid justify-items-start gap-2",
60
- children: [/* @__PURE__ */ C("p", {
58
+ children: [/* @__PURE__ */ x("p", {
61
59
  className: "text-destructive",
62
- children: u(h, E.error)
63
- }), /* @__PURE__ */ C(s, {
60
+ children: s(h, D.error)
61
+ }), /* @__PURE__ */ x(i, {
64
62
  type: "button",
65
63
  variant: "outline",
66
- onClick: E.reload,
64
+ onClick: D.reload,
67
65
  children: h.t("common.retry")
68
66
  })]
69
- }), E.loading && !P ? /* @__PURE__ */ C("div", {
70
- className: D.className,
71
- style: D.style,
72
- children: Array.from({ length: 6 }, (e, t) => /* @__PURE__ */ C(l, {
67
+ }), D.loading && !P ? /* @__PURE__ */ x("div", {
68
+ className: O.className,
69
+ style: O.style,
70
+ children: Array.from({ length: 6 }, (e, t) => /* @__PURE__ */ x(o, {
73
71
  className: p.aspectRatio === void 0 ? p.shape === "landscape" ? "aspect-video" : "aspect-[3/4]" : void 0,
74
72
  style: p.aspectRatio === void 0 ? void 0 : { aspectRatio: p.aspectRatio }
75
73
  }, t))
76
- }) : E.error && !P ? /* @__PURE__ */ w("div", {
74
+ }) : D.error && !P ? /* @__PURE__ */ S("div", {
77
75
  role: "alert",
78
76
  className: "grid justify-items-start gap-2",
79
- children: [/* @__PURE__ */ C("p", {
77
+ children: [/* @__PURE__ */ x("p", {
80
78
  className: "text-destructive",
81
- children: u(h, E.error)
82
- }), /* @__PURE__ */ C(s, {
79
+ children: s(h, D.error)
80
+ }), /* @__PURE__ */ x(i, {
83
81
  size: "sm",
84
82
  variant: "outline",
85
- onClick: E.reload,
83
+ onClick: D.reload,
86
84
  children: h.t("common.retry")
87
85
  })]
88
86
  }) : I.length ? I.map((e) => {
89
87
  let t = Math.min(L, e.contents.length);
90
- return L -= t, /* @__PURE__ */ C(N, {
88
+ return L -= t, /* @__PURE__ */ x(k, {
91
89
  day: e,
92
90
  pending: F,
93
- collapsePremium: c,
91
+ collapsePremium: d,
94
92
  summaryLabels: f,
95
- language: x.display_language ?? x.language,
96
- kinds: x.kind ? [x.kind] : void 0,
93
+ language: T.display_language ?? T.language,
94
+ kinds: T.kind ? [T.kind] : void 0,
97
95
  initial: t,
98
- step: Math.max(1, A(o, 30)),
96
+ step: Math.max(1, E(u, 30)),
99
97
  cardProps: p
100
98
  }, JSON.stringify([
101
- O,
99
+ A,
102
100
  P?.from_date,
103
101
  e.date
104
102
  ]));
105
- }) : /* @__PURE__ */ C("p", {
103
+ }) : /* @__PURE__ */ x("p", {
106
104
  role: "status",
107
105
  className: "text-muted-foreground",
108
106
  children: p.empty ?? h.t("contents.empty")
109
107
  })]
110
- }, j.revision);
108
+ }, M.revision);
111
109
  }
112
- var M = (e) => !e.access.allowed && e.access.level === "tier";
113
- function N({ day: e, initial: t, step: r, pending: i, cardProps: a, collapsePremium: o, summaryLabels: c, language: u, kinds: d }) {
114
- let p = n(), _ = y(), [v, D] = S(t > 0 || !e.contents.length && e.filtered_count > 0), [O, A] = S(t), [j, N] = S(!1), [I, L] = S(!1), R = x(null), z = x(null), B = b(() => e.contents.slice(0, O), [e.contents, O]), V = b(() => e.contents.filter(M).length, [e.contents]), H = d?.[0], U = b(() => {
110
+ var O = (e) => !e.access.allowed && e.access.level === "tier";
111
+ function k({ day: t, initial: n, step: r, pending: a, cardProps: s, collapsePremium: c, summaryLabels: u, language: h, kinds: g }) {
112
+ let E = e(), D = _(), [k, j] = b(n > 0 || !t.contents.length && t.filtered_count > 0), [M, N] = b(n), [P, F] = b(!1), [I, L] = b(!1), R = y(null), z = y(null), B = v(() => t.contents.slice(0, M), [t.contents, M]), V = v(() => t.contents.filter(O).length, [t.contents]), H = g?.[0], U = v(() => {
115
113
  let e = !1;
116
- return B.flatMap((t, n) => !o || j || !M(t) ? [{
114
+ return B.flatMap((t, n) => !c || P || !O(t) ? [{
117
115
  key: t.id,
118
116
  card: t,
119
117
  summary: !1,
@@ -126,187 +124,141 @@ function N({ day: e, initial: t, step: r, pending: i, cardProps: a, collapsePrem
126
124
  }]));
127
125
  }, [
128
126
  B,
129
- o,
130
- j
131
- ]), W = b(() => v && !i ? U.filter((e) => !e.summary).map((e) => e.card) : [], [
132
- v,
133
- i,
127
+ c,
128
+ P
129
+ ]), W = v(() => k && !a ? U.filter((e) => !e.summary).map((e) => ({
130
+ ...e.card,
131
+ position: e.position
132
+ })) : [], [
133
+ k,
134
+ a,
134
135
  U
135
- ]), G = f(W, {
136
- surface: a.surface ?? "releases",
137
- render: e.date,
138
- client: a.client
139
- }), K = m(a.grid), q = g(`${O}|${U.map((e) => e.key).join("\0")}`), J = new Intl.DateTimeFormat(p.language, {
136
+ ]), G = l(W, {
137
+ surface: s.surface ?? "releases",
138
+ render: t.date,
139
+ client: s.client
140
+ }), K = m(s.grid), q = f(`${M}|${U.map((e) => e.key).join("\0")}`), J = new Intl.DateTimeFormat(E.language, {
140
141
  dateStyle: "full",
141
142
  timeZone: "UTC"
142
- }).format(/* @__PURE__ */ new Date(`${e.date}T12:00:00Z`)), Y = () => {
143
- !v && !O && A(Math.min(r, e.contents.length)), D(!v);
143
+ }).format(/* @__PURE__ */ new Date(`${t.date}T12:00:00Z`)), Y = () => {
144
+ !k && !M && N(Math.min(r, t.contents.length)), j(!k);
144
145
  }, X = (e) => {
145
- q.prepare(e.detail === 0), N(!0);
146
+ q.prepare(e.detail === 0), F(!0);
146
147
  };
147
- return /* @__PURE__ */ w("section", {
148
+ return /* @__PURE__ */ S("section", {
148
149
  "data-ckui": "release-day",
149
- "data-date": e.date,
150
+ "data-date": t.date,
150
151
  children: [
151
- /* @__PURE__ */ C("h2", { children: /* @__PURE__ */ w(s, {
152
+ /* @__PURE__ */ x("h2", { children: /* @__PURE__ */ S(i, {
152
153
  ref: z,
153
- id: `${_}-heading`,
154
+ id: `${D}-heading`,
154
155
  type: "button",
155
156
  variant: "ghost",
156
157
  className: "h-auto w-full justify-start gap-3 px-0 py-3 text-start text-lg font-semibold",
157
- "aria-expanded": v,
158
- "aria-controls": `${_}-contents`,
158
+ "aria-expanded": k,
159
+ "aria-controls": `${D}-contents`,
159
160
  onClick: Y,
160
- children: [/* @__PURE__ */ C(k, {
161
- icon: E,
161
+ children: [/* @__PURE__ */ x(T, {
162
+ icon: w,
162
163
  strokeWidth: 2,
163
- className: T("size-5 shrink-0 transition-transform duration-200 motion-reduce:transition-none", !v && "-rotate-90"),
164
+ className: C("size-5 shrink-0 transition-transform duration-200 motion-reduce:transition-none", !k && "-rotate-90"),
164
165
  "aria-hidden": !0
165
- }), /* @__PURE__ */ C("time", {
166
- dateTime: e.date,
166
+ }), /* @__PURE__ */ x("time", {
167
+ dateTime: t.date,
167
168
  children: J
168
169
  })]
169
170
  }) }),
170
- v && i && /* @__PURE__ */ C(l, { className: "h-24 w-full" }),
171
- /* @__PURE__ */ C("div", {
172
- hidden: i,
173
- style: i ? { display: "none" } : void 0,
174
- id: `${_}-contents`,
171
+ k && a && /* @__PURE__ */ x(o, { className: "h-24 w-full" }),
172
+ /* @__PURE__ */ x("div", {
173
+ hidden: a,
174
+ style: a ? { display: "none" } : void 0,
175
+ id: `${D}-contents`,
175
176
  role: "region",
176
- "aria-labelledby": `${_}-heading`,
177
- "aria-hidden": !v || i,
178
- inert: !v || i,
179
- className: T("grid transition-[grid-template-rows,opacity] duration-250 ease-out motion-reduce:transition-none", v ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"),
180
- children: /* @__PURE__ */ w("div", {
177
+ "aria-labelledby": `${D}-heading`,
178
+ "aria-hidden": !k || a,
179
+ inert: !k || a,
180
+ className: C("grid transition-[grid-template-rows,opacity] duration-250 ease-out motion-reduce:transition-none", k ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"),
181
+ children: /* @__PURE__ */ S("div", {
181
182
  className: "min-h-0 overflow-hidden",
182
- children: [/* @__PURE__ */ w("ol", {
183
+ children: [/* @__PURE__ */ S("ol", {
183
184
  ref: q.grid,
184
- className: T(K.className, "pt-2"),
185
+ className: C(K.className, "pt-2"),
185
186
  style: K.style,
186
- children: [U.map((e) => e.summary ? /* @__PURE__ */ C(P, {
187
+ children: [U.map((e) => e.summary ? /* @__PURE__ */ x(p, {
187
188
  tileKey: e.key,
188
- contentKind: d?.[0] ?? e.card.kind,
189
- open: v && !i,
189
+ contentKind: g?.[0] ?? e.card.kind,
190
+ open: k && !a,
190
191
  preview: e.card,
191
- cardProps: a,
192
+ cardProps: s,
192
193
  kind: "premium",
193
- label: c?.premium?.(V) ?? p.plural("releases.premiumMore", V),
194
+ label: u?.premium?.(V) ?? E.plural("releases.premiumMore", V),
194
195
  onClick: X
195
- }, e.key) : /* @__PURE__ */ C(F, {
196
+ }, e.key) : /* @__PURE__ */ x(A, {
196
197
  card: e.card,
197
- open: v && !i,
198
+ open: k && !a,
198
199
  position: e.position,
199
200
  exposure: G,
200
- cardProps: a
201
- }, e.key)), e.filtered_count > 0 && /* @__PURE__ */ C(P, {
201
+ cardProps: s
202
+ }, e.key)), t.filtered_count > 0 && /* @__PURE__ */ x(p, {
202
203
  tileKey: "filtered-summary",
203
204
  contentKind: H,
204
- open: v && !i,
205
- cardProps: a,
205
+ open: k && !a,
206
+ cardProps: s,
206
207
  kind: "filtered",
207
- label: c?.filtered?.(e.filtered_count) ?? p.plural("releases.filteredMore", e.filtered_count),
208
+ label: u?.filtered?.(t.filtered_count) ?? E.plural("releases.filteredMore", t.filtered_count),
208
209
  onClick: (e) => {
209
210
  R.current = e.currentTarget, L(!0);
210
211
  }
211
212
  })]
212
- }), O < e.contents.length && /* @__PURE__ */ C("div", {
213
+ }), M < t.contents.length && /* @__PURE__ */ x("div", {
213
214
  className: "flex justify-center pt-5 pb-1",
214
- children: /* @__PURE__ */ C(s, {
215
+ children: /* @__PURE__ */ x(i, {
215
216
  type: "button",
216
217
  variant: "outline",
217
- onClick: (t) => {
218
- q.prepare(t.detail === 0), A((t) => Math.min(t + r, e.contents.length));
218
+ onClick: (e) => {
219
+ q.prepare(e.detail === 0), N((e) => Math.min(e + r, t.contents.length));
219
220
  },
220
- children: p.t("contents.showMore")
221
+ children: E.t("contents.showMore")
221
222
  })
222
223
  })]
223
224
  })
224
225
  }),
225
- /* @__PURE__ */ C(h, {
226
- returnFocus: () => !i && R.current?.isConnected ? R.current : z.current,
226
+ /* @__PURE__ */ x(d, {
227
+ returnFocus: () => !a && R.current?.isConnected ? R.current : z.current,
227
228
  open: I,
228
229
  onOpenChange: L,
229
- client: a.client,
230
- kinds: d,
231
- language: u,
232
- counts: e.filtered_entities ?? [],
233
- description: p.t("releases.filterContext", { date: J }),
234
- appearance: a.appearance
230
+ client: s.client,
231
+ kinds: g,
232
+ language: h,
233
+ counts: t.filtered_entities ?? [],
234
+ description: E.t("releases.filterContext", { date: J }),
235
+ appearance: s.appearance
235
236
  })
236
237
  ]
237
238
  });
238
239
  }
239
- function P({ tileKey: r, kind: i, contentKind: a, preview: l, open: u, label: d, onClick: f, cardProps: p }) {
240
- let { t: m } = n(), [h, g] = c({
241
- margin: "0px",
242
- leaveMargin: "1200px 0px"
243
- }), { presets: _ } = o({ client: p.client }), v = p.aspectRatio ?? t(_.find((e) => e.kind === a && e.name === "cover")?.aspect) ?? (p.shape === "landscape" ? "16 / 9" : "3 / 4");
244
- return /* @__PURE__ */ w("li", {
245
- ref: h,
246
- "data-release-key": r,
247
- children: [/* @__PURE__ */ w(s, {
248
- type: "button",
249
- variant: "outline",
250
- "data-ckui": `${i}-release-tile`,
251
- "aria-label": d,
252
- "aria-expanded": i !== "premium" && void 0,
253
- "aria-haspopup": i === "filtered" ? "dialog" : void 0,
254
- className: "relative flex h-auto w-full flex-col justify-center overflow-hidden rounded-lg p-0 text-center whitespace-normal",
255
- style: { aspectRatio: v },
256
- onClick: f,
257
- children: [l && u && g && /* @__PURE__ */ C(e, {
258
- outputs: l.cover,
259
- alt: "",
260
- fit: "cover",
261
- loading: "lazy",
262
- className: "absolute inset-0 size-full opacity-20"
263
- }), /* @__PURE__ */ w("span", {
264
- className: "relative grid justify-items-center gap-3 p-4",
265
- children: [
266
- /* @__PURE__ */ C(k, {
267
- icon: i === "premium" ? D : O,
268
- strokeWidth: 2,
269
- className: "size-8",
270
- "aria-hidden": !0
271
- }),
272
- /* @__PURE__ */ C("span", {
273
- className: "text-xl font-semibold",
274
- children: d
275
- }),
276
- /* @__PURE__ */ C("span", {
277
- className: "text-sm text-muted-foreground",
278
- children: m(i === "premium" ? "releases.premiumHint" : "releases.manageFilters")
279
- })
280
- ]
281
- })]
282
- }), l && /* @__PURE__ */ C("p", {
283
- className: "mt-2 line-clamp-2 text-lg leading-snug text-muted-foreground",
284
- children: l.title
285
- })]
286
- });
287
- }
288
- function F({ card: e, open: t, position: n, exposure: r, cardProps: i }) {
289
- let [a, o] = c({
240
+ function A({ card: e, open: t, position: n, exposure: r, cardProps: i }) {
241
+ let [o, s] = a({
290
242
  margin: "0px",
291
243
  leaveMargin: "1200px 0px"
292
- }), s = b(() => r.track(e.id, n), [
244
+ }), c = v(() => r.track(e.id, n), [
293
245
  r,
294
246
  e.id,
295
247
  n
296
- ]), l = _((e) => {
297
- a(e), s(e);
298
- }, [a, s]);
299
- return /* @__PURE__ */ C("li", {
248
+ ]), l = h((e) => {
249
+ o(e), c(e);
250
+ }, [o, c]);
251
+ return /* @__PURE__ */ x("li", {
300
252
  ref: l,
301
253
  "data-release-key": e.id,
302
- children: /* @__PURE__ */ C(p, {
254
+ children: /* @__PURE__ */ x(u, {
303
255
  card: e,
304
256
  client: i.client,
305
257
  href: i.href,
306
258
  shape: i.shape,
307
259
  aspectRatio: i.aspectRatio,
308
260
  titlePosition: i.titlePosition,
309
- loadMedia: t && o,
261
+ loadMedia: t && s,
310
262
  groupKinds: i.groupKinds,
311
263
  chips: i.chips,
312
264
  filterEntity: i.filterEntity,
@@ -324,6 +276,6 @@ function F({ card: e, open: t, position: n, exposure: r, cardProps: i }) {
324
276
  });
325
277
  }
326
278
  //#endregion
327
- export { j as DailyReleases };
279
+ export { D as DailyReleases };
328
280
 
329
281
  //# sourceMappingURL=daily-releases.js.map