@openrails/contentkit-ui 0.93.1 → 0.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -5
- package/dist/client/browser-reader-preferences.d.ts +38 -0
- package/dist/client/browser-reader-preferences.js +172 -0
- package/dist/client/browser-reader-preferences.js.map +1 -0
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +27 -25
- package/dist/client/client.js.map +1 -1
- package/dist/client/discovery.d.ts +14 -1
- package/dist/client/discovery.js +36 -22
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/generated/routes.d.ts +33 -7
- package/dist/client/generated/routes.js +57 -2
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +15 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/http.d.ts +18 -1
- package/dist/client/http.js +67 -26
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +3 -1
- package/dist/client/reader-preferences.d.ts +46 -0
- package/dist/client/reader-preferences.js +147 -0
- package/dist/client/reader-preferences.js.map +1 -0
- package/dist/client.js +17 -15
- package/dist/components/content/card.d.ts +21 -5
- package/dist/components/content/card.js +80 -67
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/daily-releases.d.ts +15 -0
- package/dist/components/content/daily-releases.js +167 -0
- package/dist/components/content/daily-releases.js.map +1 -0
- package/dist/components/content/filters.d.ts +1 -1
- package/dist/components/content/filters.js +1 -5
- package/dist/components/content/filters.js.map +1 -1
- package/dist/components/content/grid.d.ts +14 -1
- package/dist/components/content/grid.js +58 -32
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/index.d.ts +4 -2
- package/dist/components/content/popular.d.ts +11 -0
- package/dist/components/content/popular.js +49 -0
- package/dist/components/content/popular.js.map +1 -0
- package/dist/components/content/reader-settings.d.ts +1 -1
- package/dist/components/content/reader-settings.js +67 -44
- package/dist/components/content/reader-settings.js.map +1 -1
- package/dist/index.js +22 -20
- package/dist/locales/discovery/de.js +6 -0
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +6 -0
- package/dist/locales/discovery/en.js +6 -0
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +6 -0
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +6 -0
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +6 -0
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +6 -0
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +6 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/discovery.d.ts +14 -4
- package/dist/react/discovery.js +196 -149
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/provider.d.ts +4 -1
- package/dist/react/provider.js +38 -31
- package/dist/react/provider.js.map +1 -1
- package/dist/react/reader-preferences.d.ts +13 -0
- package/dist/react/reader-preferences.js +15 -0
- package/dist/react/reader-preferences.js.map +1 -0
- package/dist/react/resources.js +11 -11
- package/dist/react/resources.js.map +1 -1
- package/dist/react.js +10 -9
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid.js","names":[],"sources":["../../../src/components/content/grid.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentQuery, HistoryQuery, RailQuery, SearchQuery } from \"../../client/discovery.js\";\nimport type { Card } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useContents, useExposure, useHistory, useLockedCount, useRecommendations, useSearch, useSimilar, type UseRail } from \"../../react/discovery.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport type { UseList } from \"../../react/use-resource.js\";\nimport { useNearViewport } from \"../../react/viewport.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { contentError, useAction } from \"../social/parts.js\";\nimport { ContentCard, type ContentCardProps } from \"./card.js\";\n\nexport interface CardListProps extends Omit<ContentCardProps, \"card\" | \"onOpen\" | \"children\" | \"className\"> {\n /** Where the list is, for exposures and click attribution: \"home\", \"search\", \"similar\". */\n surface?: string;\n /** Loads the next page as the end nears, instead of a button. */\n infinite?: boolean;\n /** A card's entry instead of ContentCard. */\n renderCard?: (card: Card, position: number) => ReactNode;\n /** Shown when nothing matches. */\n empty?: ReactNode;\n pageSize?: number;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\nconst GRID = \"grid grid-cols-[repeat(auto-fill,minmax(min(100%,10rem),1fr))] gap-x-4 gap-y-6\";\n\n/** A list's cards in a responsive grid, paged, with exposures and click signals. */\nexport function CardGrid({ list, render, ...p }: CardListProps & { list: UseList<Card>; render: string }) {\n const m = useMessages();\n const { t } = m;\n const { kinds } = useKinds({ client: p.client });\n const exposure = useExposure(list.items, { surface: p.surface ?? \"list\", render, client: p.client });\n const [sentinel, near] = useNearViewport({ margin: \"600px 0px\", leaveMargin: \"1200px 0px\" });\n const { hasMore, loadingMore, loadMore } = list;\n useEffect(() => {\n if (p.infinite && near && hasMore && !loadingMore) void loadMore();\n }, [p.infinite, near, hasMore, loadingMore, loadMore]);\n const tiers = p.tiers ?? kinds?.tiers;\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"content-grid\" aria-busy={list.loading}>\n {list.loading && !list.items.length ? (\n <div className={GRID}>\n {Array.from({ length: 8 }, (_, i) => (\n <Skeleton key={i} className={p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\"} />\n ))}\n </div>\n ) : list.error && !list.items.length ? (\n <div role=\"alert\" className=\"grid justify-items-start gap-2\">\n <p className=\"text-destructive\">{contentError(m, list.error)}</p>\n <Button size=\"sm\" variant=\"outline\" onClick={list.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n ) : !list.items.length ? (\n <div className=\"text-muted-foreground\" role=\"status\">\n {p.empty ?? t(\"contents.empty\")}\n </div>\n ) : (\n <ol className={GRID}>\n {list.items.map((card, i) => (\n <li key={card.id} ref={exposure.track(card.id, i + 1)}>\n {p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n href={p.href}\n shape={p.shape}\n groupKinds={p.groupKinds}\n chips={p.chips}\n tiers={tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n onOpen={() => exposure.click(card.id, i + 1)}\n />\n )}\n </li>\n ))}\n </ol>\n )}\n {list.hasMore && (\n <div ref={sentinel} className=\"grid justify-items-center\">\n <Button variant=\"outline\" size=\"sm\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.loadingMore ? t(\"contents.loading\") : t(\"contents.loadMore\")}\n </Button>\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n\n/**\n * What a list does with cards the reader doesn't open: shows them marked\n * (show), leaves them out (hide), or lists them apart, counted, behind a\n * toggle that loads them when opened (collapse).\n */\nexport type LockedCards = \"show\" | \"hide\" | \"collapse\";\n\nconst LOCKED_LEVELS = [\"tier\", \"group\", \"ppv\", \"group_ppv\"] as const;\n\n/** Contents as cards (GET /v1/contents): any sort and filter, paged. */\nexport function ContentGrid({ query, locked = \"show\", ...p }: CardListProps & { query: ContentQuery; locked?: LockedCards }) {\n const q = useMemo<ContentQuery>(\n () => ({ ...query, limit: query.limit ?? p.pageSize, ...(locked === \"show\" ? {} : { mode: \"hide\" }) }),\n [query, p.pageSize, locked],\n );\n const list = useContents(q, { client: p.client });\n return (\n <>\n {locked === \"collapse\" && <LockedSection {...p} query={query} />}\n <CardGrid {...p} list={list} render={JSON.stringify(q)} />\n </>\n );\n}\n\n/** A list's locked cards, counted, loaded and shown on opening. */\nfunction LockedSection({ query, ...p }: CardListProps & { query: ContentQuery }) {\n const m = useMessages();\n const [open, setOpen] = useState(false);\n const { count } = useLockedCount(query, { client: p.client });\n const q = useMemo<ContentQuery | null>(\n () => (open ? { ...query, mode: \"lock\", access: query.access ?? LOCKED_LEVELS, limit: query.limit ?? p.pageSize } : null),\n [open, query, p.pageSize],\n );\n const list = useContents(q, { client: p.client });\n const items = useMemo(() => list.items.filter((c) => !c.access.allowed), [list.items]);\n if (!count) return null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className=\"grid gap-3 text-sm\" data-ckui=\"locked-cards\">\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-start\" aria-expanded={open} onClick={() => setOpen(!open)}>\n {open ? m.t(\"contents.lockedHide\") : m.plural(\"contents.lockedShow\", count)}\n </Button>\n {open && <CardGrid {...p} list={{ ...list, items }} render={JSON.stringify(q)} />}\n </ContentKitUiRoot>\n );\n}\n\n/** Search results as cards; nothing until the query has text. */\nexport function SearchResults({ query, ...p }: CardListProps & { query: SearchQuery }) {\n const { t } = useMessages();\n const q = useMemo(() => ({ ...query, limit: query.limit ?? p.pageSize }), [query, p.pageSize]);\n const list = useSearch(q, { client: p.client });\n if (!query.q.trim()) return null;\n return <CardGrid surface=\"search\" empty={t(\"contents.noResults\", { q: query.q.trim() })} {...p} list={list} render={JSON.stringify(q)} />;\n}\n\n/** The signed-in reader's history as cards, each removable, with a clear-all. */\nexport function ContentHistory({ query = {}, ...p }: CardListProps & { query?: HistoryQuery }) {\n const { t } = useMessages();\n const ctx = useContext(ContentKitContext);\n const q = useMemo(() => ({ ...query, limit: query.limit ?? p.pageSize }), [query, p.pageSize]);\n const list = useHistory(q, { client: p.client });\n const forget = useAction(\"history.forget\");\n if (ctx?.viewer === null) return <p className=\"text-muted-foreground\">{t(\"contents.historySignedOut\")}</p>;\n return (\n <div className=\"grid gap-3\" data-ckui=\"content-history\">\n {list.items.length > 0 && (\n <Button size=\"sm\" variant=\"outline\" className=\"justify-self-end\" disabled={forget.pending} onClick={() => void forget.run(() => list.forget(query.kind ? { kind: query.kind } : {}))}>\n {t(\"contents.clearHistory\")}\n </Button>\n )}\n <CardGrid\n surface=\"history\"\n empty={t(\"contents.historyEmpty\")}\n {...p}\n list={list}\n render={JSON.stringify(q)}\n renderCard={\n p.renderCard ??\n ((card) => (\n <ContentCard\n card={card}\n href={p.href}\n shape={p.shape}\n groupKinds={p.groupKinds}\n chips={p.chips}\n tiers={p.tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n >\n <Button size=\"xs\" variant=\"ghost\" className=\"justify-self-start\" disabled={forget.pending} onClick={() => void forget.run(() => list.forget({ content_id: card.id }))}>\n {t(\"contents.removeFromHistory\")}\n </Button>\n </ContentCard>\n ))\n }\n />\n </div>\n );\n}\n\nexport interface ContentRailProps extends Omit<CardListProps, \"infinite\" | \"empty\"> {\n /** The rail's heading. */\n title: ReactNode;\n /** Contents like this one. */\n similar?: string;\n /** For the signed-in reader. */\n recommendations?: boolean;\n /** The reader's history: { status: \"in_progress\" } is a continue-watching rail. */\n history?: HistoryQuery;\n /** An ordered group's members in its order: a featured rail (its windows apply). */\n group?: string;\n /** Any list: a sort and filters. */\n list?: ContentQuery;\n /** Filters and reader options of a similar or recommended rail. */\n query?: RailQuery;\n /** A link beside the heading, e.g. to the whole list. */\n more?: ReactNode;\n}\n\n/**\n * A horizontal row of contents: similar, recommended, the reader's history,\n * an ordered group's members or any list. It hides itself when it has none or\n * the site runs without what it needs.\n */\nexport function ContentRail({ title, similar, recommendations, history, group, list: listQuery, query = {}, more, ...p }: ContentRailProps) {\n const limit = query.limit ?? p.pageSize ?? 12;\n const q = useMemo(() => ({ ...query, limit }), [query, limit]);\n const listing = useMemo(\n () => (listQuery ? { ...listQuery, limit: listQuery.limit ?? limit } : group ? { groups: [group], sort: \"position\" as const, limit } : null),\n [listQuery, group, limit],\n );\n const like = useSimilar(recommendations || history || listing ? null : similar, q, { client: p.client });\n const mine = useRecommendations(recommendations ? q : null, { client: p.client });\n const seen = useHistory(history ? { ...history, limit: history.limit ?? limit } : null, { client: p.client });\n const listed = useContents(listing, { client: p.client });\n const rail: UseRail = recommendations\n ? mine\n : history || listing\n ? (() => {\n const l = history ? seen : listed;\n return { items: l.items, loading: l.loading, error: l.error, unavailable: l.error?.code === \"not_configured\", reload: l.reload };\n })()\n : like;\n const { kinds } = useKinds({ client: p.client });\n const surface = p.surface ?? (recommendations ? \"recommendations\" : history ? \"history\" : group ? \"featured\" : listing ? \"list\" : \"similar\");\n const exposure = useExposure(rail.items, { surface, render: JSON.stringify([similar, recommendations, history, group, listing, q]), client: p.client });\n const source = similar || recommendations || history || group || listing;\n if (!source || rail.unavailable || (!rail.loading && !rail.items.length)) return null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"content-rail\" data-surface={surface}>\n <div className=\"flex items-baseline justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{title}</h2>\n {more}\n </div>\n <ol className=\"flex snap-x gap-4 overflow-x-auto pb-2\">\n {(rail.loading && !rail.items.length ? Array.from({ length: 6 }, () => null) : rail.items).map((card, i) => (\n <li key={card?.id ?? i} className=\"w-36 shrink-0 snap-start sm:w-44\" ref={card ? exposure.track(card.id, i + 1) : undefined}>\n {!card ? (\n <Skeleton className={p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\"} />\n ) : p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n href={p.href}\n shape={p.shape}\n groupKinds={p.groupKinds}\n chips={p.chips}\n tiers={p.tiers ?? kinds?.tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n onOpen={() => exposure.click(card.id, i + 1)}\n />\n )}\n </li>\n ))}\n </ol>\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAiCA,IAAM,IAAO;AAGb,SAAgB,EAAS,EAAE,SAAM,WAAQ,GAAG,KAA8D;CACxG,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,EAAE,aAAU,EAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GACzC,IAAW,EAAY,EAAK,OAAO;EAAE,SAAS,EAAE,WAAW;EAAQ;EAAQ,QAAQ,EAAE;CAAO,CAAC,GAC7F,CAAC,GAAU,KAAQ,EAAgB;EAAE,QAAQ;EAAa,aAAa;CAAa,CAAC,GACrF,EAAE,YAAS,gBAAa,gBAAa;CAC3C,QAAgB;EACd,AAAI,EAAE,YAAY,KAAQ,KAAW,CAAC,KAAa,EAAc;CACnE,GAAG;EAAC,EAAE;EAAU;EAAM;EAAS;EAAa;CAAQ,CAAC;CACrD,IAAM,IAAQ,EAAE,SAAS,GAAO;CAChC,OACE,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EAAe,aAAW,EAAK;EAAvI,UAAA,CACG,EAAK,WAAW,CAAC,EAAK,MAAM,SAC3B,kBAAC,OAAD;GAAK,WAAW;GACb,UAAA,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAC7B,kBAAC,GAAD,EAAkB,WAAW,EAAE,UAAU,cAAc,wBAAwB,sBAAwB,GAAxF,CAAwF,CACxG;EACE,CAAA,IACH,EAAK,SAAS,CAAC,EAAK,MAAM,SAC5B,kBAAC,OAAD;GAAK,MAAK;GAAQ,WAAU;GAA5B,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;IAAoB,UAAA,EAAa,GAAG,EAAK,KAAK;GAAK,CAAA,GAChE,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,SAAS,EAAK;IAC/C,UAAA,EAAE,cAAc;GACX,CAAA,CACL;EACH,CAAA,IAAC,EAAK,MAAM,SAKd,kBAAC,MAAD;GAAI,WAAW;GACZ,UAAA,EAAK,MAAM,KAAK,GAAM,MACrB,kBAAC,MAAD;IAAkB,KAAK,EAAS,MAAM,EAAK,IAAI,IAAI,CAAC;IACjD,UAAA,EAAE,aACD,EAAE,WAAW,GAAM,IAAI,CAAC,IAExB,kBAAC,GAAD;KACQ;KACN,MAAM,EAAE;KACR,OAAO,EAAE;KACT,YAAY,EAAE;KACd,OAAO,EAAE;KACF;KACP,QAAQ,EAAE;KACV,OAAO,EAAE;KACT,WAAW,EAAE;KACb,UAAU,EAAE;KACZ,cAAc,EAAS,MAAM,EAAK,IAAI,IAAI,CAAC;IAC5C,CAAA;GAED,GAlBK,EAAK,EAkBV,CACL;EACC,CAAA,IA1BJ,kBAAC,OAAD;GAAK,WAAU;GAAwB,MAAK;GACzC,UAAA,EAAE,SAAS,EAAE,gBAAgB;EAC3B,CAAA,GA0BN,EAAK,WACJ,kBAAC,OAAD;GAAK,KAAK;GAAU,WAAU;GAC5B,UAAA,kBAAC,GAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,UAAU,EAAK;IAAa,eAAe,KAAK,EAAK,SAAS;IAC/F,UAAA,EAAK,cAAc,EAAE,kBAAkB,IAAI,EAAE,mBAAmB;GAC3D,CAAA;EACL,CAAA,CAES;;AAEtB;AASA,IAAM,IAAgB;CAAC;CAAQ;CAAS;CAAO;AAAW;AAG1D,SAAgB,EAAY,EAAE,UAAO,YAAS,QAAQ,GAAG,KAAoE;CAC3H,IAAM,IAAI,SACD;EAAE,GAAG;EAAO,OAAO,EAAM,SAAS,EAAE;EAAU,GAAI,MAAW,SAAS,CAAC,IAAI,EAAE,MAAM,OAAO;CAAG,IACpG;EAAC;EAAO,EAAE;EAAU;CAAM,CAC5B,GACM,IAAO,EAAY,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC;CAChD,OACE,kBAAA,GAAA,EAAA,UAAA,CACG,MAAW,cAAc,kBAAC,GAAD;EAAe,GAAI;EAAU;CAAQ,CAAA,GAC/D,kBAAC,GAAD;EAAU,GAAI;EAAS;EAAM,QAAQ,KAAK,UAAU,CAAC;CAAI,CAAA,CACzD,EAAA,CAAA;AAEN;AAGA,SAAS,EAAc,EAAE,UAAO,GAAG,KAA8C;CAC/E,IAAM,IAAI,EAAY,GAChB,CAAC,GAAM,KAAW,EAAS,EAAK,GAChC,EAAE,aAAU,EAAe,GAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,GACtD,IAAI,QACD,IAAO;EAAE,GAAG;EAAO,MAAM;EAAQ,QAAQ,EAAM,UAAU;EAAe,OAAO,EAAM,SAAS,EAAE;CAAS,IAAI,MACpH;EAAC;EAAM;EAAO,EAAE;CAAQ,CAC1B,GACM,IAAO,EAAY,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GAC1C,IAAQ,QAAc,EAAK,MAAM,QAAQ,MAAM,CAAC,EAAE,OAAO,OAAO,GAAG,CAAC,EAAK,KAAK,CAAC;CAErF,OADK,IAEH,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAU;EAAqB,aAAU;EAArF,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,MAAK;GAAK,WAAU;GAAqB,iBAAe;GAAM,eAAe,EAAQ,CAAC,CAAI;GACjH,UAAA,IAAO,EAAE,EAAE,qBAAqB,IAAI,EAAE,OAAO,uBAAuB,CAAK;EACpE,CAAA,GACP,KAAQ,kBAAC,GAAD;GAAU,GAAI;GAAG,MAAM;IAAE,GAAG;IAAM;GAAM;GAAG,QAAQ,KAAK,UAAU,CAAC;EAAI,CAAA,CAChE;MAPD;AASrB;AAGA,SAAgB,EAAc,EAAE,UAAO,GAAG,KAA6C;CACrF,IAAM,EAAE,SAAM,EAAY,GACpB,IAAI,SAAe;EAAE,GAAG;EAAO,OAAO,EAAM,SAAS,EAAE;CAAS,IAAI,CAAC,GAAO,EAAE,QAAQ,CAAC,GACvF,IAAO,EAAU,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC;CAE9C,OADK,EAAM,EAAE,KAAK,IACX,kBAAC,GAAD;EAAU,SAAQ;EAAS,OAAO,EAAE,sBAAsB,EAAE,GAAG,EAAM,EAAE,KAAK,EAAE,CAAC;EAAG,GAAI;EAAS;EAAM,QAAQ,KAAK,UAAU,CAAC;CAAI,CAAA,IAD5G;AAE9B;AAGA,SAAgB,EAAe,EAAE,WAAQ,CAAC,GAAG,GAAG,KAA+C;CAC7F,IAAM,EAAE,SAAM,EAAY,GACpB,IAAM,EAAW,CAAiB,GAClC,IAAI,SAAe;EAAE,GAAG;EAAO,OAAO,EAAM,SAAS,EAAE;CAAS,IAAI,CAAC,GAAO,EAAE,QAAQ,CAAC,GACvF,IAAO,EAAW,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GACzC,IAAS,EAAU,gBAAgB;CAEzC,OADI,GAAK,WAAW,OAAa,kBAAC,KAAD;EAAG,WAAU;EAAyB,UAAA,EAAE,2BAA2B;CAAK,CAAA,IAEvG,kBAAC,OAAD;EAAK,WAAU;EAAa,aAAU;EAAtC,UAAA,CACG,EAAK,MAAM,SAAS,KACnB,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,WAAU;GAAmB,UAAU,EAAO;GAAS,eAAe,KAAK,EAAO,UAAU,EAAK,OAAO,EAAM,OAAO,EAAE,MAAM,EAAM,KAAK,IAAI,CAAC,CAAC,CAAC;GAChL,UAAA,EAAE,uBAAuB;EACpB,CAAA,GAEV,kBAAC,GAAD;GACE,SAAQ;GACR,OAAO,EAAE,uBAAuB;GAChC,GAAI;GACE;GACN,QAAQ,KAAK,UAAU,CAAC;GACxB,YACE,EAAE,gBACA,MACA,kBAAC,GAAD;IACQ;IACN,MAAM,EAAE;IACR,OAAO,EAAE;IACT,YAAY,EAAE;IACd,OAAO,EAAE;IACT,OAAO,EAAE;IACT,QAAQ,EAAE;IACV,OAAO,EAAE;IACT,WAAW,EAAE;IACb,UAAU,EAAE;IAEZ,UAAA,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAQ,WAAU;KAAqB,UAAU,EAAO;KAAS,eAAe,KAAK,EAAO,UAAU,EAAK,OAAO,EAAE,YAAY,EAAK,GAAG,CAAC,CAAC;KACjK,UAAA,EAAE,4BAA4B;IACzB,CAAA;GACG,CAAA;EAGlB,CAAA,CACE;;AAET;AA0BA,SAAgB,EAAY,EAAE,UAAO,YAAS,oBAAiB,YAAS,UAAO,MAAM,GAAW,WAAQ,CAAC,GAAG,SAAM,GAAG,KAAuB;CAC1I,IAAM,IAAQ,EAAM,SAAS,EAAE,YAAY,IACrC,IAAI,SAAe;EAAE,GAAG;EAAO;CAAM,IAAI,CAAC,GAAO,CAAK,CAAC,GACvD,IAAU,QACP,IAAY;EAAE,GAAG;EAAW,OAAO,EAAU,SAAS;CAAM,IAAI,IAAQ;EAAE,QAAQ,CAAC,CAAK;EAAG,MAAM;EAAqB;CAAM,IAAI,MACvI;EAAC;EAAW;EAAO;CAAK,CAC1B,GACM,IAAO,EAAW,KAAmB,KAAW,IAAU,OAAO,GAAS,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GACjG,IAAO,EAAmB,IAAkB,IAAI,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,GAC1E,IAAO,EAAW,IAAU;EAAE,GAAG;EAAS,OAAO,EAAQ,SAAS;CAAM,IAAI,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,GACtG,IAAS,EAAY,GAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GAClD,IAAgB,IAClB,IACA,KAAW,WACF;EACL,IAAM,IAAI,IAAU,IAAO;EAC3B,OAAO;GAAE,OAAO,EAAE;GAAO,SAAS,EAAE;GAAS,OAAO,EAAE;GAAO,aAAa,EAAE,OAAO,SAAS;GAAkB,QAAQ,EAAE;EAAO;CACjI,EAAA,CAAG,IACH,GACA,EAAE,aAAU,EAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GACzC,IAAU,EAAE,YAAY,IAAkB,oBAAoB,IAAU,YAAY,IAAQ,aAAa,IAAU,SAAS,YAC5H,IAAW,EAAY,EAAK,OAAO;EAAE;EAAS,QAAQ,KAAK,UAAU;GAAC;GAAS;GAAiB;GAAS;GAAO;GAAS;EAAC,CAAC;EAAG,QAAQ,EAAE;CAAO,CAAC;CAGtJ,OADI,EADW,KAAW,KAAmB,KAAW,KAAS,MAClD,EAAK,eAAgB,CAAC,EAAK,WAAW,CAAC,EAAK,MAAM,SAAgB,OAE/E,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EAAe,gBAAc;EAArI,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;IAA2B,UAAA;GAAU,CAAA,GAClD,CACE;EACL,CAAA,GAAA,kBAAC,MAAD;GAAI,WAAU;GACV,WAAA,EAAK,WAAW,CAAC,EAAK,MAAM,SAAS,MAAM,KAAK,EAAE,QAAQ,EAAE,SAAS,IAAI,IAAI,EAAK,MAAA,CAAO,KAAK,GAAM,MACpG,kBAAC,MAAD;IAAwB,WAAU;IAAmC,KAAK,IAAO,EAAS,MAAM,EAAK,IAAI,IAAI,CAAC,IAAI,KAAA;IAC/G,UAAC,IAEE,EAAE,aACJ,EAAE,WAAW,GAAM,IAAI,CAAC,IAExB,kBAAC,GAAD;KACQ;KACN,MAAM,EAAE;KACR,OAAO,EAAE;KACT,YAAY,EAAE;KACd,OAAO,EAAE;KACT,OAAO,EAAE,SAAS,GAAO;KACzB,QAAQ,EAAE;KACV,OAAO,EAAE;KACT,WAAW,EAAE;KACb,UAAU,EAAE;KACZ,cAAc,EAAS,MAAM,EAAK,IAAI,IAAI,CAAC;IAC5C,CAAA,IAhBD,kBAAC,GAAD,EAAU,WAAW,EAAE,UAAU,cAAc,wBAAwB,sBAAwB,CAAA;GAkB/F,GApBK,GAAM,MAAM,CAoBjB,CACL;EACC,CAAA,CACY;;AAEtB"}
|
|
1
|
+
{"version":3,"file":"grid.js","names":[],"sources":["../../../src/components/content/grid.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useEffect, useMemo, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentQuery, HistoryQuery, RailQuery, SearchQuery } from \"../../client/discovery.js\";\nimport type { Card } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useContents, useExposure, useHistory, useLockedCount, useRecommendations, useSearch, useSimilar, type UseRail } from \"../../react/discovery.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport type { UseList } from \"../../react/use-resource.js\";\nimport { useNearViewport } from \"../../react/viewport.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { contentError, useAction } from \"../social/parts.js\";\nimport { ContentCard, type ContentCardProps } from \"./card.js\";\n\nexport interface CardListProps extends Omit<ContentCardProps, \"card\" | \"onOpen\" | \"children\" | \"className\"> {\n /** Where the list is, for exposures and click attribution: \"home\", \"search\", \"similar\". */\n surface?: string;\n /** Loads the next page as the end nears, instead of a button. */\n infinite?: boolean;\n /** A card's entry instead of ContentCard. */\n renderCard?: (card: Card, position: number) => ReactNode;\n /** Shown when nothing matches. */\n empty?: ReactNode;\n pageSize?: number;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Opt into a fluid grid with no desktop column ceiling. */\n grid?: CardGridLayout;\n}\n\nconst GRID = \"grid grid-cols-[repeat(auto-fill,minmax(min(100%,10rem),1fr))] gap-x-4 gap-y-6\";\n\nexport interface CardGridLayout {\n /** Minimum desktop card width in CSS pixels. Cards grow to fill available space. */\n minCardWidth: number;\n /** Equal-width columns below the desktop breakpoint. Default 2. */\n mobileColumns?: number;\n}\n\n/** Shared by complete lists, release-day sections and grid-style previews. */\nexport function cardGridLayout(layout?: CardGridLayout): { className: string; style?: CSSProperties } {\n if (!layout) return { className: GRID };\n const width = Number.isFinite(layout.minCardWidth) ? Math.max(1, layout.minCardWidth) : 160;\n const columns = layout.mobileColumns !== undefined && Number.isFinite(layout.mobileColumns) ? Math.max(1, Math.floor(layout.mobileColumns)) : 2;\n return {\n className: \"grid grid-cols-[repeat(var(--ckui-card-mobile-columns),minmax(0,1fr))] gap-x-4 gap-y-6 md:grid-cols-[repeat(auto-fill,minmax(min(100%,var(--ckui-card-min-width)),1fr))]\",\n style: { \"--ckui-card-min-width\": `${width}px`, \"--ckui-card-mobile-columns\": columns } as CSSProperties,\n };\n}\n\n/** A list's cards in a responsive grid, paged, with exposures and click signals. */\nexport function CardGrid({ list, render, ...p }: CardListProps & { list: UseList<Card>; render: string }) {\n const m = useMessages();\n const { t } = m;\n const { kinds } = useKinds({ client: p.client });\n const exposure = useExposure(list.items, { surface: p.surface ?? \"list\", render, client: p.client });\n const [sentinel, near] = useNearViewport({ margin: \"600px 0px\", leaveMargin: \"1200px 0px\" });\n const { hasMore, loadingMore, loadMore } = list;\n useEffect(() => {\n if (p.infinite && near && hasMore && !loadingMore) void loadMore();\n }, [p.infinite, near, hasMore, loadingMore, loadMore]);\n const tiers = p.tiers ?? kinds?.tiers;\n const layout = cardGridLayout(p.grid);\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"content-grid\" aria-busy={list.loading}>\n {list.loading && !list.items.length ? (\n <div className={layout.className} style={layout.style}>\n {Array.from({ length: 8 }, (_, i) => (\n <Skeleton key={i} className={p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\"} />\n ))}\n </div>\n ) : list.error && !list.items.length ? (\n <div role=\"alert\" className=\"grid justify-items-start gap-2\">\n <p className=\"text-destructive\">{contentError(m, list.error)}</p>\n <Button size=\"sm\" variant=\"outline\" onClick={list.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n ) : !list.items.length ? (\n <div className=\"text-muted-foreground\" role=\"status\">\n {p.empty ?? t(\"contents.empty\")}\n </div>\n ) : (\n <ol className={layout.className} style={layout.style}>\n {list.items.map((card, i) => (\n <li key={card.id} ref={exposure.track(card.id, i + 1)}>\n {p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n href={p.href}\n shape={p.shape}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n onOpen={() => exposure.click(card.id, i + 1)}\n />\n )}\n </li>\n ))}\n </ol>\n )}\n {list.hasMore && (\n <div ref={sentinel} className=\"grid justify-items-center\">\n <Button variant=\"outline\" size=\"sm\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.loadingMore ? t(\"contents.loading\") : t(\"contents.loadMore\")}\n </Button>\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n\n/**\n * What a list does with cards the reader doesn't open: shows them marked\n * (show), leaves them out (hide), or lists them apart, counted, behind a\n * toggle that loads them when opened (collapse).\n */\nexport type LockedCards = \"show\" | \"hide\" | \"collapse\";\n\nconst LOCKED_LEVELS = [\"tier\", \"group\", \"ppv\", \"group_ppv\"] as const;\n\n/** Contents as cards (GET /v1/contents): any sort and filter, paged. */\nexport function ContentGrid({ query, locked = \"show\", ...p }: CardListProps & { query: ContentQuery; locked?: LockedCards }) {\n const q = useMemo<ContentQuery>(\n () => ({ ...query, limit: query.limit ?? p.pageSize, ...(locked === \"show\" ? {} : { mode: \"hide\" }) }),\n [query, p.pageSize, locked],\n );\n const list = useContents(q, { client: p.client });\n return (\n <>\n {locked === \"collapse\" && <LockedSection {...p} query={query} />}\n <CardGrid {...p} list={list} render={JSON.stringify(q)} />\n </>\n );\n}\n\n/** A list's locked cards, counted, loaded and shown on opening. */\nfunction LockedSection({ query, ...p }: CardListProps & { query: ContentQuery }) {\n const m = useMessages();\n const [open, setOpen] = useState(false);\n const { count } = useLockedCount(query, { client: p.client });\n const q = useMemo<ContentQuery | null>(\n () => (open ? { ...query, mode: \"lock\", access: query.access ?? LOCKED_LEVELS, limit: query.limit ?? p.pageSize } : null),\n [open, query, p.pageSize],\n );\n const list = useContents(q, { client: p.client });\n const items = useMemo(() => list.items.filter((c) => !c.access.allowed), [list.items]);\n if (!count) return null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className=\"grid gap-3 text-sm\" data-ckui=\"locked-cards\">\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-start\" aria-expanded={open} onClick={() => setOpen(!open)}>\n {open ? m.t(\"contents.lockedHide\") : m.plural(\"contents.lockedShow\", count)}\n </Button>\n {open && <CardGrid {...p} list={{ ...list, items }} render={JSON.stringify(q)} />}\n </ContentKitUiRoot>\n );\n}\n\n/** Search results as cards; nothing until the query has text. */\nexport function SearchResults({ query, ...p }: CardListProps & { query: SearchQuery }) {\n const { t } = useMessages();\n const q = useMemo(() => ({ ...query, limit: query.limit ?? p.pageSize }), [query, p.pageSize]);\n const list = useSearch(q, { client: p.client });\n if (!query.q.trim()) return null;\n return <CardGrid surface=\"search\" empty={t(\"contents.noResults\", { q: query.q.trim() })} {...p} list={list} render={JSON.stringify(q)} />;\n}\n\n/** The signed-in reader's history as cards, each removable, with a clear-all. */\nexport function ContentHistory({ query = {}, ...p }: CardListProps & { query?: HistoryQuery }) {\n const { t } = useMessages();\n const ctx = useContext(ContentKitContext);\n const q = useMemo(() => ({ ...query, limit: query.limit ?? p.pageSize }), [query, p.pageSize]);\n const list = useHistory(q, { client: p.client });\n const forget = useAction(\"history.forget\");\n if (ctx?.viewer === null) return <p className=\"text-muted-foreground\">{t(\"contents.historySignedOut\")}</p>;\n return (\n <div className=\"grid gap-3\" data-ckui=\"content-history\">\n {list.items.length > 0 && (\n <Button size=\"sm\" variant=\"outline\" className=\"justify-self-end\" disabled={forget.pending} onClick={() => void forget.run(() => list.forget(query.kind ? { kind: query.kind } : {}))}>\n {t(\"contents.clearHistory\")}\n </Button>\n )}\n <CardGrid\n surface=\"history\"\n empty={t(\"contents.historyEmpty\")}\n {...p}\n list={list}\n render={JSON.stringify(q)}\n renderCard={\n p.renderCard ??\n ((card) => (\n <ContentCard\n card={card}\n href={p.href}\n shape={p.shape}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={p.tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n >\n <Button size=\"xs\" variant=\"ghost\" className=\"justify-self-start\" disabled={forget.pending} onClick={() => void forget.run(() => list.forget({ content_id: card.id }))}>\n {t(\"contents.removeFromHistory\")}\n </Button>\n </ContentCard>\n ))\n }\n />\n </div>\n );\n}\n\nexport interface ContentRailProps extends Omit<CardListProps, \"infinite\" | \"empty\"> {\n /** The rail's heading. */\n title: ReactNode;\n /** Contents like this one. */\n similar?: string;\n /** For the signed-in reader. */\n recommendations?: boolean;\n /** The reader's history: { status: \"in_progress\" } is a continue-watching rail. */\n history?: HistoryQuery;\n /** An ordered group's members in its order: a featured rail (its windows apply). */\n group?: string;\n /** Any list: a sort and filters. */\n list?: ContentQuery;\n /** Filters and reader options of a similar or recommended rail. */\n query?: RailQuery;\n /** A link beside the heading, e.g. to the whole list. */\n more?: ReactNode;\n}\n\n/**\n * A horizontal row of contents: similar, recommended, the reader's history,\n * an ordered group's members or any list. It hides itself when it has none or\n * the site runs without what it needs.\n */\nexport function ContentRail({ title, similar, recommendations, history, group, list: listQuery, query = {}, more, ...p }: ContentRailProps) {\n const limit = query.limit ?? p.pageSize ?? 12;\n const q = useMemo(() => ({ ...query, limit }), [query, limit]);\n const listing = useMemo(\n () => (listQuery ? { ...listQuery, limit: listQuery.limit ?? limit } : group ? { groups: [group], sort: \"position\" as const, limit } : null),\n [listQuery, group, limit],\n );\n const like = useSimilar(recommendations || history || listing ? null : similar, q, { client: p.client });\n const mine = useRecommendations(recommendations ? q : null, { client: p.client });\n const seen = useHistory(history ? { ...history, limit: history.limit ?? limit } : null, { client: p.client });\n const listed = useContents(listing, { client: p.client });\n const rail: UseRail = recommendations\n ? mine\n : history || listing\n ? (() => {\n const l = history ? seen : listed;\n return { items: l.items, loading: l.loading, error: l.error, unavailable: l.error?.code === \"not_configured\", reload: l.reload };\n })()\n : like;\n const { kinds } = useKinds({ client: p.client });\n const surface = p.surface ?? (recommendations ? \"recommendations\" : history ? \"history\" : group ? \"featured\" : listing ? \"list\" : \"similar\");\n const exposure = useExposure(rail.items, { surface, render: JSON.stringify([similar, recommendations, history, group, listing, q]), client: p.client });\n const source = similar || recommendations || history || group || listing;\n const layout = p.grid ? cardGridLayout(p.grid) : undefined;\n if (!source || rail.unavailable || (!rail.loading && !rail.items.length)) return null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"content-rail\" data-surface={surface}>\n <div className=\"flex items-baseline justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{title}</h2>\n {more}\n </div>\n <ol className={layout?.className ?? \"flex snap-x gap-4 overflow-x-auto pb-2\"} style={layout?.style}>\n {(rail.loading && !rail.items.length ? Array.from({ length: 6 }, () => null) : rail.items).map((card, i) => (\n <li key={card?.id ?? i} className={layout ? undefined : \"w-36 shrink-0 snap-start sm:w-44\"} ref={card ? exposure.track(card.id, i + 1) : undefined}>\n {!card ? (\n <Skeleton className={p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\"} />\n ) : p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n href={p.href}\n shape={p.shape}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={p.tiers ?? kinds?.tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n onOpen={() => exposure.click(card.id, i + 1)}\n />\n )}\n </li>\n ))}\n </ol>\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export { ContentCard, OwnerChip, PostCard, lockLabel, type ContentCardProps, type PostCardProps } from './card.js';
|
|
2
|
-
export { CardGrid, ContentGrid, ContentHistory, ContentRail, SearchResults, type CardListProps, type ContentRailProps, type LockedCards } from './grid.js';
|
|
1
|
+
export { ContentCard, OwnerChip, PostCard, lockLabel, type CardMetadata, type ContentCardProps, type PostCardProps } from './card.js';
|
|
2
|
+
export { CardGrid, ContentGrid, ContentHistory, ContentRail, SearchResults, type CardGridLayout, type CardListProps, type ContentRailProps, type LockedCards } from './grid.js';
|
|
3
|
+
export { DailyReleases, type DailyReleasesProps } from './daily-releases.js';
|
|
4
|
+
export { PopularContentGrid, type PopularContentGridProps } from './popular.js';
|
|
3
5
|
export { SearchBox, type SearchBoxProps } from './search.js';
|
|
4
6
|
export { ReaderSettings, type ReaderSettingsProps } from './reader-settings.js';
|
|
5
7
|
export { ContentFilterSettings, ReleaseDateIndex, type ContentFilterSettingsProps, type ReleaseDateIndexProps } from './filters.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PopularPeriod, PopularQuery } from '../../client/discovery.js';
|
|
2
|
+
import { CardListProps, LockedCards } from './grid.js';
|
|
3
|
+
export interface PopularContentGridProps extends Omit<CardListProps, "infinite"> {
|
|
4
|
+
query?: PopularQuery;
|
|
5
|
+
/** Controlled period; otherwise the selector starts at 7d. */
|
|
6
|
+
period?: PopularPeriod;
|
|
7
|
+
onPeriodChange?: (period: PopularPeriod) => void;
|
|
8
|
+
locked?: LockedCards;
|
|
9
|
+
}
|
|
10
|
+
/** A popularity listing with matched score/age windows and infinite paging. */
|
|
11
|
+
export declare function PopularContentGrid({ query, period, onPeriodChange, locked, ...p }: PopularContentGridProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useMessages as e } from "../../i18n/context.js";
|
|
2
|
+
import { ContentKitUiRoot as t } from "../../scope.js";
|
|
3
|
+
import { Select as n, SelectContent as r, SelectItem as i, SelectTrigger as a, SelectValue as o } from "../../ui/select.js";
|
|
4
|
+
import { popularQuery as s } from "../../client/discovery.js";
|
|
5
|
+
import { ContentGrid as c } from "./grid.js";
|
|
6
|
+
import { useMemo as l, useState as u } from "react";
|
|
7
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
8
|
+
import { cn as p } from "cn";
|
|
9
|
+
//#region src/components/content/popular.tsx
|
|
10
|
+
var m = [
|
|
11
|
+
"7d",
|
|
12
|
+
"90d",
|
|
13
|
+
"365d",
|
|
14
|
+
"all"
|
|
15
|
+
];
|
|
16
|
+
function h({ query: h, period: g, onPeriodChange: _, locked: v, ...y }) {
|
|
17
|
+
let b = e(), [x, S] = u("7d"), C = g ?? x, w = l(() => s(C, h), [C, h]), T = (e) => e === "all" ? b.t("listing.period_all") : b.plural("listing.pastDays", Number(e.slice(0, -1)));
|
|
18
|
+
return /* @__PURE__ */ f(t, {
|
|
19
|
+
appearance: y.appearance,
|
|
20
|
+
className: p("grid gap-4 text-sm", y.className),
|
|
21
|
+
"data-ckui": "popular-grid",
|
|
22
|
+
children: [/* @__PURE__ */ f(n, {
|
|
23
|
+
value: C,
|
|
24
|
+
onValueChange: (e) => {
|
|
25
|
+
if (!e || !m.includes(e)) return;
|
|
26
|
+
let t = e;
|
|
27
|
+
g === void 0 && S(t), _?.(t);
|
|
28
|
+
},
|
|
29
|
+
children: [/* @__PURE__ */ d(a, {
|
|
30
|
+
className: "w-fit min-w-40",
|
|
31
|
+
"aria-label": b.t("listing.popularPeriod"),
|
|
32
|
+
children: /* @__PURE__ */ d(o, { children: T(C) })
|
|
33
|
+
}), /* @__PURE__ */ d(r, { children: m.map((e) => /* @__PURE__ */ d(i, {
|
|
34
|
+
value: e,
|
|
35
|
+
children: T(e)
|
|
36
|
+
}, e)) })]
|
|
37
|
+
}), /* @__PURE__ */ d(c, {
|
|
38
|
+
...y,
|
|
39
|
+
surface: y.surface ?? "popular",
|
|
40
|
+
query: w,
|
|
41
|
+
locked: v,
|
|
42
|
+
infinite: !0
|
|
43
|
+
})]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { h as PopularContentGrid };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=popular.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popular.js","names":[],"sources":["../../../src/components/content/popular.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useMemo, useState } from \"react\";\nimport { popularQuery, type PopularPeriod, type PopularQuery } from \"../../client/discovery.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { ContentGrid, type CardListProps, type LockedCards } from \"./grid.js\";\n\nexport interface PopularContentGridProps extends Omit<CardListProps, \"infinite\"> {\n query?: PopularQuery;\n /** Controlled period; otherwise the selector starts at 7d. */\n period?: PopularPeriod;\n onPeriodChange?: (period: PopularPeriod) => void;\n locked?: LockedCards;\n}\n\nconst PERIODS: readonly PopularPeriod[] = [\"7d\", \"90d\", \"365d\", \"all\"];\n\n/** A popularity listing with matched score/age windows and infinite paging. */\nexport function PopularContentGrid({ query, period, onPeriodChange, locked, ...p }: PopularContentGridProps) {\n const m = useMessages();\n const [ownPeriod, setOwnPeriod] = useState<PopularPeriod>(\"7d\");\n const current = period ?? ownPeriod;\n const list = useMemo(() => popularQuery(current, query), [current, query]);\n const label = (value: PopularPeriod) => value === \"all\" ? m.t(\"listing.period_all\") : m.plural(\"listing.pastDays\", Number(value.slice(0, -1)));\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"popular-grid\">\n <Select value={current} onValueChange={(value) => {\n if (!value || !PERIODS.includes(value as PopularPeriod)) return;\n const next = value as PopularPeriod;\n if (period === undefined) setOwnPeriod(next);\n onPeriodChange?.(next);\n }}>\n <SelectTrigger className=\"w-fit min-w-40\" aria-label={m.t(\"listing.popularPeriod\")}><SelectValue>{label(current)}</SelectValue></SelectTrigger>\n <SelectContent>{PERIODS.map((value) => <SelectItem key={value} value={value}>{label(value)}</SelectItem>)}</SelectContent>\n </Select>\n <ContentGrid {...p} surface={p.surface ?? \"popular\"} query={list} locked={locked} infinite />\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;AAgBA,IAAM,IAAoC;CAAC;CAAM;CAAO;CAAQ;AAAK;AAGrE,SAAgB,EAAmB,EAAE,UAAO,WAAQ,mBAAgB,WAAQ,GAAG,KAA8B;CAC3G,IAAM,IAAI,EAAY,GAChB,CAAC,GAAW,KAAgB,EAAwB,IAAI,GACxD,IAAU,KAAU,GACpB,IAAO,QAAc,EAAa,GAAS,CAAK,GAAG,CAAC,GAAS,CAAK,CAAC,GACnE,KAAS,MAAyB,MAAU,QAAQ,EAAE,EAAE,oBAAoB,IAAI,EAAE,OAAO,oBAAoB,OAAO,EAAM,MAAM,GAAG,EAAE,CAAC,CAAC;CAC7I,OACE,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EAAxG,UAAA,CACE,kBAAC,GAAD;GAAQ,OAAO;GAAS,gBAAgB,MAAU;IAChD,IAAI,CAAC,KAAS,CAAC,EAAQ,SAAS,CAAsB,GAAG;IACzD,IAAM,IAAO;IAEb,AADI,MAAW,KAAA,KAAW,EAAa,CAAI,GAC3C,IAAiB,CAAI;GACvB;GALA,UAAA,CAME,kBAAC,GAAD;IAAe,WAAU;IAAiB,cAAY,EAAE,EAAE,uBAAuB;IAAG,UAAA,kBAAC,GAAD,EAAA,UAAc,EAAM,CAAO,EAAe,CAAA;GAAgB,CAAA,GAC9I,kBAAC,GAAD,EAAA,UAAgB,EAAQ,KAAK,MAAU,kBAAC,GAAD;IAA+B;IAAQ,UAAA,EAAM,CAAK;GAAc,GAA/C,CAA+C,CAAC,EAAiB,CAAA,CACnH;EACR,CAAA,GAAA,kBAAC,GAAD;GAAa,GAAI;GAAG,SAAS,EAAE,WAAW;GAAW,OAAO;GAAc;GAAQ,UAAA;EAAU,CAAA,CAC5E;;AAEtB"}
|
|
@@ -15,5 +15,5 @@ export interface ReaderSettingsProps {
|
|
|
15
15
|
className?: string;
|
|
16
16
|
appearance?: ContentKitUiAppearance;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
18
|
+
/** Language preferences and filters for both guests and signed-in readers. */
|
|
19
19
|
export declare function ReaderSettings(p: ReaderSettingsProps): import('react').JSX.Element;
|
|
@@ -1,95 +1,118 @@
|
|
|
1
1
|
import { useMessages as e } from "../../i18n/context.js";
|
|
2
2
|
import { AppearanceContext as t, ContentKitUiRoot as n } from "../../scope.js";
|
|
3
|
-
import {
|
|
3
|
+
import { useOptionalContentKitClient as r } from "../../react/context.js";
|
|
4
4
|
import { Button as i } from "../../ui/button.js";
|
|
5
5
|
import { Dialog as a, DialogContent as o, DialogDescription as s, DialogHeader as c, DialogTitle as l, DialogTrigger as u } from "../../ui/dialog.js";
|
|
6
6
|
import { Label as d } from "../../ui/label.js";
|
|
7
7
|
import { Select as f, SelectContent as p, SelectItem as m, SelectTrigger as h, SelectValue as g } from "../../ui/select.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { ErrorLine as _, useAction as v } from "../social/parts.js";
|
|
9
|
+
import { useContentFilters as y } from "../../react/discovery.js";
|
|
10
|
+
import { ContentFilterSettings as b } from "./filters.js";
|
|
11
|
+
import { useContext as x, useId as S, useSyncExternalStore as C } from "react";
|
|
12
|
+
import { jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
13
|
+
import { Settings01Icon as E } from "@hugeicons/core-free-icons";
|
|
14
|
+
import { HugeiconsIcon as D } from "@hugeicons/react";
|
|
14
15
|
//#region src/components/content/reader-settings.tsx
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
var O = Object.freeze({
|
|
17
|
+
loading: !1,
|
|
18
|
+
saving: !1,
|
|
19
|
+
pending: !1
|
|
20
|
+
}), k = () => O, A = () => () => {};
|
|
21
|
+
function j(y) {
|
|
22
|
+
let { t: b } = e(), O = S(), j = r(y.client), N = j?.preferences, P = C(N?.subscribe ?? A, N?.snapshot ?? k, k), F = y.language, I = v("preferences.save", y.onError), L = x(t), R = y.appearance ?? L;
|
|
23
|
+
return /* @__PURE__ */ w(t.Provider, {
|
|
24
|
+
value: R,
|
|
25
|
+
children: /* @__PURE__ */ w(n, {
|
|
26
|
+
className: y.className,
|
|
27
|
+
appearance: R,
|
|
22
28
|
"data-ckui": "reader-settings",
|
|
23
|
-
children: /* @__PURE__ */
|
|
24
|
-
render: /* @__PURE__ */
|
|
29
|
+
children: /* @__PURE__ */ T(a, { children: [/* @__PURE__ */ T(u, {
|
|
30
|
+
render: /* @__PURE__ */ w(i, {
|
|
25
31
|
type: "button",
|
|
26
32
|
variant: "ghost",
|
|
27
33
|
size: "sm",
|
|
28
|
-
"aria-label":
|
|
34
|
+
"aria-label": b("readerSettings.title"),
|
|
29
35
|
"data-ckui": "reader-settings-trigger"
|
|
30
36
|
}),
|
|
31
|
-
children: [/* @__PURE__ */
|
|
32
|
-
icon:
|
|
37
|
+
children: [/* @__PURE__ */ w(D, {
|
|
38
|
+
icon: E,
|
|
33
39
|
strokeWidth: 2,
|
|
34
40
|
className: "size-4",
|
|
35
41
|
"aria-hidden": !0
|
|
36
|
-
}), /* @__PURE__ */
|
|
37
|
-
}), /* @__PURE__ */
|
|
42
|
+
}), /* @__PURE__ */ w("span", { children: y.language.toUpperCase() })]
|
|
43
|
+
}), /* @__PURE__ */ T(o, {
|
|
38
44
|
className: "max-h-[calc(100dvh-2rem)] overflow-y-auto",
|
|
39
45
|
children: [
|
|
40
|
-
/* @__PURE__ */
|
|
46
|
+
/* @__PURE__ */ T(c, { children: [/* @__PURE__ */ w(l, { children: b("readerSettings.title") }), /* @__PURE__ */ w(s, {
|
|
41
47
|
className: "sr-only",
|
|
42
|
-
children:
|
|
48
|
+
children: b("readerSettings.description")
|
|
43
49
|
})] }),
|
|
44
|
-
/* @__PURE__ */
|
|
50
|
+
/* @__PURE__ */ T("div", {
|
|
45
51
|
className: "grid gap-3",
|
|
46
|
-
children: [/* @__PURE__ */
|
|
47
|
-
htmlFor: `${
|
|
48
|
-
children:
|
|
49
|
-
}), /* @__PURE__ */
|
|
50
|
-
value:
|
|
52
|
+
children: [/* @__PURE__ */ w(d, {
|
|
53
|
+
htmlFor: `${O}-language`,
|
|
54
|
+
children: b("listing.language")
|
|
55
|
+
}), /* @__PURE__ */ T(f, {
|
|
56
|
+
value: F,
|
|
57
|
+
disabled: I.pending || P.loading,
|
|
51
58
|
onValueChange: (e) => {
|
|
52
|
-
e && e !==
|
|
59
|
+
e && e !== F && I.run(async () => {
|
|
60
|
+
await N?.setLanguage(e), y.onLanguageChange(e);
|
|
61
|
+
});
|
|
53
62
|
},
|
|
54
|
-
children: [/* @__PURE__ */
|
|
55
|
-
id: `${
|
|
63
|
+
children: [/* @__PURE__ */ w(h, {
|
|
64
|
+
id: `${O}-language`,
|
|
56
65
|
className: "w-full",
|
|
57
|
-
children: /* @__PURE__ */
|
|
58
|
-
}), /* @__PURE__ */
|
|
66
|
+
children: /* @__PURE__ */ w(g, { children: y.languages.find((e) => e.value === F)?.label ?? F })
|
|
67
|
+
}), /* @__PURE__ */ w(p, { children: y.languages.map((e) => /* @__PURE__ */ w(m, {
|
|
59
68
|
value: e.value,
|
|
60
69
|
children: e.label
|
|
61
70
|
}, e.value)) })]
|
|
62
71
|
})]
|
|
63
72
|
}),
|
|
64
|
-
|
|
65
|
-
language:
|
|
66
|
-
client:
|
|
67
|
-
onError:
|
|
73
|
+
j && /* @__PURE__ */ w(M, {
|
|
74
|
+
language: y.language,
|
|
75
|
+
client: j,
|
|
76
|
+
onError: y.onError
|
|
77
|
+
}),
|
|
78
|
+
(P.pending || I.pending) && !P.error && /* @__PURE__ */ w("p", {
|
|
79
|
+
className: "text-sm text-muted-foreground",
|
|
80
|
+
role: "status",
|
|
81
|
+
children: b("readerSettings.savingPreferences")
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ w(_, { children: P.error || I.error ? b("readerSettings.preferencesNotSaved") : null }),
|
|
84
|
+
P.error && N && /* @__PURE__ */ w(i, {
|
|
85
|
+
type: "button",
|
|
86
|
+
variant: "outline",
|
|
87
|
+
onClick: () => {
|
|
88
|
+
I.run(() => N.retry());
|
|
89
|
+
},
|
|
90
|
+
children: b("common.retry")
|
|
68
91
|
})
|
|
69
92
|
]
|
|
70
93
|
})] })
|
|
71
94
|
})
|
|
72
95
|
});
|
|
73
96
|
}
|
|
74
|
-
function
|
|
75
|
-
let { t: n } = e(), r =
|
|
97
|
+
function M(t) {
|
|
98
|
+
let { t: n } = e(), r = S(), { filters: i } = y({
|
|
76
99
|
language: t.language,
|
|
77
100
|
client: t.client
|
|
78
101
|
});
|
|
79
|
-
return i?.filters.length === 0 ? null : /* @__PURE__ */
|
|
102
|
+
return i?.filters.length === 0 ? null : /* @__PURE__ */ T("section", {
|
|
80
103
|
"aria-labelledby": r,
|
|
81
104
|
className: "grid gap-3",
|
|
82
|
-
children: [/* @__PURE__ */
|
|
105
|
+
children: [/* @__PURE__ */ w("h3", {
|
|
83
106
|
id: r,
|
|
84
107
|
className: "font-medium",
|
|
85
108
|
children: n("readerSettings.filters")
|
|
86
|
-
}), /* @__PURE__ */
|
|
109
|
+
}), /* @__PURE__ */ w(b, {
|
|
87
110
|
...t,
|
|
88
111
|
includeDeleted: !1
|
|
89
112
|
})]
|
|
90
113
|
});
|
|
91
114
|
}
|
|
92
115
|
//#endregion
|
|
93
|
-
export {
|
|
116
|
+
export { j as ReaderSettings };
|
|
94
117
|
|
|
95
118
|
//# sourceMappingURL=reader-settings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reader-settings.js","names":[],"sources":["../../../src/components/content/reader-settings.tsx"],"sourcesContent":["import { Settings01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { useContext, useId } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"reader-settings.js","names":[],"sources":["../../../src/components/content/reader-settings.tsx"],"sourcesContent":["import { Settings01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { useContext, useId, useSyncExternalStore } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useOptionalContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters } from \"../../react/discovery.js\";\nimport { AppearanceContext, ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from \"#ckui/ui/dialog\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { ContentFilterSettings } from \"./filters.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport type { ReaderPreferenceState } from \"../../client/reader-preferences.js\";\n\nconst EMPTY_PREFERENCES: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\nconst noPreferences = () => EMPTY_PREFERENCES;\nconst noSubscription = () => () => {};\n\nexport interface ReaderSettingsProps {\n languages: readonly { value: string; label: string }[];\n /** The current language, controlled by the host. */\n language: string;\n /** The host applies its own route and preference changes. */\n onLanguageChange: (language: string) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Language preferences and filters for both guests and signed-in readers. */\nexport function ReaderSettings(p: ReaderSettingsProps) {\n const messages = useMessages();\n const { t } = messages;\n const id = useId();\n const client = useOptionalContentKitClient(p.client);\n const preferences = client?.preferences;\n const state = useSyncExternalStore(preferences?.subscribe ?? noSubscription, preferences?.snapshot ?? noPreferences, noPreferences);\n // The selector describes the current view. A saved default must not make a\n // localized deep link look selected in a language it is not displaying.\n const selectedLanguage = p.language;\n const save = useAction(\"preferences.save\", p.onError);\n const inheritedAppearance = useContext(AppearanceContext);\n const appearance = p.appearance ?? inheritedAppearance;\n return (\n <AppearanceContext.Provider value={appearance}>\n <ContentKitUiRoot className={p.className} appearance={appearance} data-ckui=\"reader-settings\">\n <Dialog>\n <DialogTrigger render={<Button type=\"button\" variant=\"ghost\" size=\"sm\" aria-label={t(\"readerSettings.title\")} data-ckui=\"reader-settings-trigger\" />}>\n <HugeiconsIcon icon={Settings01Icon} strokeWidth={2} className=\"size-4\" aria-hidden />\n <span>{p.language.toUpperCase()}</span>\n </DialogTrigger>\n <DialogContent className=\"max-h-[calc(100dvh-2rem)] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t(\"readerSettings.title\")}</DialogTitle>\n <DialogDescription className=\"sr-only\">{t(\"readerSettings.description\")}</DialogDescription>\n </DialogHeader>\n <div className=\"grid gap-3\">\n <Label htmlFor={`${id}-language`}>{t(\"listing.language\")}</Label>\n <Select value={selectedLanguage} disabled={save.pending || state.loading} onValueChange={(value) => {\n if (!value || value === selectedLanguage) return;\n void save.run(async () => { await preferences?.setLanguage(value); p.onLanguageChange(value); });\n }}>\n <SelectTrigger id={`${id}-language`} className=\"w-full\">\n <SelectValue>{p.languages.find((item) => item.value === selectedLanguage)?.label ?? selectedLanguage}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {p.languages.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}\n </SelectContent>\n </Select>\n </div>\n {client && <ReaderFilters language={p.language} client={client} onError={p.onError} />}\n {(state.pending || save.pending) && !state.error && <p className=\"text-sm text-muted-foreground\" role=\"status\">{t(\"readerSettings.savingPreferences\")}</p>}\n <ErrorLine>{state.error || save.error ? t(\"readerSettings.preferencesNotSaved\") : null}</ErrorLine>\n {state.error && preferences && <Button type=\"button\" variant=\"outline\" onClick={() => { void save.run(() => preferences.retry()); }}>{t(\"common.retry\")}</Button>}\n </DialogContent>\n </Dialog>\n </ContentKitUiRoot>\n </AppearanceContext.Provider>\n );\n}\n\nfunction ReaderFilters(p: Pick<ReaderSettingsProps, \"language\" | \"client\" | \"onError\">) {\n const { t } = useMessages();\n const id = useId();\n const { filters } = useContentFilters({ language: p.language, client: p.client });\n if (filters?.filters.length === 0) return null;\n return (\n <section aria-labelledby={id} className=\"grid gap-3\">\n <h3 id={id} className=\"font-medium\">{t(\"readerSettings.filters\")}</h3>\n <ContentFilterSettings {...p} includeDeleted={false} />\n </section>\n );\n}\n"],"mappings":"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"}
|