@openrails/contentkit-ui 0.103.2 → 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 (120) 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.js +30 -32
  31. package/dist/components/admin/content-manager.js.map +1 -1
  32. package/dist/components/admin/quick-search.d.ts +14 -0
  33. package/dist/components/admin/quick-search.js +60 -0
  34. package/dist/components/admin/quick-search.js.map +1 -0
  35. package/dist/components/admin/reader-content-editor.js +452 -428
  36. package/dist/components/admin/reader-content-editor.js.map +1 -1
  37. package/dist/components/admin/stats-dashboard.d.ts +4 -4
  38. package/dist/components/admin/stats-dashboard.js +132 -134
  39. package/dist/components/admin/stats-dashboard.js.map +1 -1
  40. package/dist/components/content/daily-releases.js +143 -191
  41. package/dist/components/content/daily-releases.js.map +1 -1
  42. package/dist/components/content/favorites.d.ts +6 -0
  43. package/dist/components/content/favorites.js +33 -0
  44. package/dist/components/content/favorites.js.map +1 -0
  45. package/dist/components/content/filter-dialog.js +19 -15
  46. package/dist/components/content/filter-dialog.js.map +1 -1
  47. package/dist/components/content/filters.d.ts +2 -0
  48. package/dist/components/content/filters.js +5 -1
  49. package/dist/components/content/filters.js.map +1 -1
  50. package/dist/components/content/grid.d.ts +34 -13
  51. package/dist/components/content/grid.js +300 -271
  52. package/dist/components/content/grid.js.map +1 -1
  53. package/dist/components/content/history.d.ts +6 -0
  54. package/dist/components/content/history.js +91 -0
  55. package/dist/components/content/history.js.map +1 -0
  56. package/dist/components/content/index.d.ts +1 -0
  57. package/dist/components/content/search.d.ts +11 -4
  58. package/dist/components/content/search.js +22 -11
  59. package/dist/components/content/search.js.map +1 -1
  60. package/dist/components/content/summary-tile.d.ts +14 -0
  61. package/dist/components/content/summary-tile.js +63 -0
  62. package/dist/components/content/summary-tile.js.map +1 -0
  63. package/dist/components/media-folder-editor.js +1 -1
  64. package/dist/components/media-folder-editor.js.map +1 -1
  65. package/dist/components/video-mini-player.d.ts +4 -1
  66. package/dist/components/video-mini-player.js +77 -71
  67. package/dist/components/video-mini-player.js.map +1 -1
  68. package/dist/components/video-player.d.ts +4 -1
  69. package/dist/components/video-player.js +280 -276
  70. package/dist/components/video-player.js.map +1 -1
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.js +17 -14
  73. package/dist/locales/admin/de.js +8 -8
  74. package/dist/locales/admin/de.js.map +1 -1
  75. package/dist/locales/admin/en.d.ts +4 -4
  76. package/dist/locales/admin/en.js +8 -8
  77. package/dist/locales/admin/en.js.map +1 -1
  78. package/dist/locales/admin/es.js +8 -8
  79. package/dist/locales/admin/es.js.map +1 -1
  80. package/dist/locales/admin/ja.js +8 -8
  81. package/dist/locales/admin/ja.js.map +1 -1
  82. package/dist/locales/admin/ko.js +8 -8
  83. package/dist/locales/admin/ko.js.map +1 -1
  84. package/dist/locales/admin/zh.js +8 -8
  85. package/dist/locales/admin/zh.js.map +1 -1
  86. package/dist/locales/discovery/de.js +0 -1
  87. package/dist/locales/discovery/de.js.map +1 -1
  88. package/dist/locales/discovery/en.d.ts +0 -1
  89. package/dist/locales/discovery/en.js +0 -1
  90. package/dist/locales/discovery/en.js.map +1 -1
  91. package/dist/locales/discovery/es.js +0 -1
  92. package/dist/locales/discovery/es.js.map +1 -1
  93. package/dist/locales/discovery/ja.js +0 -1
  94. package/dist/locales/discovery/ja.js.map +1 -1
  95. package/dist/locales/discovery/ko.js +0 -1
  96. package/dist/locales/discovery/ko.js.map +1 -1
  97. package/dist/locales/discovery/zh.js +0 -1
  98. package/dist/locales/discovery/zh.js.map +1 -1
  99. package/dist/locales/en.d.ts +4 -5
  100. package/dist/react/discovery.d.ts +20 -17
  101. package/dist/react/discovery.js +273 -221
  102. package/dist/react/discovery.js.map +1 -1
  103. package/dist/react/index.d.ts +2 -1
  104. package/dist/react/resources.d.ts +3 -1
  105. package/dist/react/resources.js +4 -0
  106. package/dist/react/resources.js.map +1 -1
  107. package/dist/react/stats.d.ts +2 -2
  108. package/dist/react/stats.js.map +1 -1
  109. package/dist/react/use-engagement.d.ts +26 -0
  110. package/dist/react/use-engagement.js +221 -0
  111. package/dist/react/use-engagement.js.map +1 -0
  112. package/dist/react/use-resource.d.ts +4 -3
  113. package/dist/react/use-resource.js +2 -1
  114. package/dist/react/use-resource.js.map +1 -1
  115. package/dist/react.js +12 -11
  116. package/dist/styles.css +1 -1
  117. package/dist/ui/dialog.d.ts +2 -1
  118. package/dist/ui/dialog.js +23 -20
  119. package/dist/ui/dialog.js.map +1 -1
  120. 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 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, optionally with a 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: CSS pixels or a CSS length such as clamp(...). Cards fill the available space. */\n minCardWidth: number | string;\n /** Maximum desktop columns. Omit to use all available width without a ceiling. */\n maxColumns?: 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 = typeof layout.minCardWidth === \"string\" ? layout.minCardWidth.trim() || \"160px\" : `${Number.isFinite(layout.minCardWidth) ? Math.max(1, layout.minCardWidth) : 160}px`;\n const columns = layout.mobileColumns !== undefined && Number.isFinite(layout.mobileColumns) ? Math.max(1, Math.floor(layout.mobileColumns)) : 2;\n const maximum = layout.maxColumns !== undefined && Number.isFinite(layout.maxColumns) ? Math.max(1, Math.floor(layout.maxColumns)) : undefined;\n const trackWidth = maximum === undefined ? width : `max(${width}, calc((100% - ${maximum - 1}rem) / ${maximum}))`;\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\": trackWidth, \"--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, error } = list;\n useEffect(() => {\n if (p.infinite && near && hasMore && !loadingMore && !error) void loadMore();\n }, [p.infinite, near, hasMore, loadingMore, loadMore, error]);\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.aspectRatio === undefined ? p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\" : \"w-full\"} style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }} />\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 client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\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 gap-2\">\n {list.error && list.items.length > 0 && <p role=\"alert\" className=\"text-destructive\">{contentError(m, list.error)}</p>}\n <Button variant=\"outline\" size=\"sm\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.loadingMore ? t(\"contents.loading\") : list.error ? t(\"common.retry\") : 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 client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\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 ?? \"relative 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.aspectRatio === undefined ? p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\" : \"w-full\"} style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }} />\n ) : p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\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
+ {"version":3,"file":"grid.js","names":[],"sources":["../../../src/components/content/grid.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { AccessMode, ContentQuery, HistoryQuery, RailQuery, SearchQuery } from \"../../client/discovery.js\";\nimport type { Card, ListingSummary } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useContents, useExposure, useHistory, useRecommendations, useSearch, useSimilar, type UseRail } from \"../../react/discovery.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport type { UseList } from \"../../react/use-resource.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useContentScope } 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 } from \"../social/parts.js\";\nimport { ContentFilterDialog } from \"./filter-dialog.js\";\nimport { useReleaseReveal } from \"./release-reveal.js\";\nimport { ContentSummaryTile } from \"./summary-tile.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 /** Collapse paid/locked cards into a counted tile, or hide them; default show. */\n locked?: LockedCards;\n /** Optional-filter summary tiles are on by default; hosts may suppress them in previews. */\n showFilteredSummary?: boolean;\n /** Show the collapsed locked tile only at this measured grid width. Default 0. */\n lockedSummaryMinColumns?: number;\n /** Limit rendered preview tiles without changing the matching selection or summary counts. */\n cardLimit?: number;\n /** Limit cards in mobile previews without mounting their images. */\n mobileCardLimit?: number;\n /** Host-specific nouns for summary controls. */\n summaryLabels?: { premium?: (count: number) => string; filtered?: (count: number) => string };\n /** Opt into a fluid grid, optionally with a 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: CSS pixels or a CSS length such as clamp(...). Cards fill the available space. */\n minCardWidth: number | string;\n /** Maximum desktop columns. Omit to use all available width without a ceiling. */\n maxColumns?: 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 =\n typeof layout.minCardWidth === \"string\" ? layout.minCardWidth.trim() || \"160px\" : `${Number.isFinite(layout.minCardWidth) ? Math.max(1, layout.minCardWidth) : 160}px`;\n const columns = layout.mobileColumns !== undefined && Number.isFinite(layout.mobileColumns) ? Math.max(1, Math.floor(layout.mobileColumns)) : 2;\n const maximum = layout.maxColumns !== undefined && Number.isFinite(layout.maxColumns) ? Math.max(1, Math.floor(layout.maxColumns)) : undefined;\n const trackWidth = maximum === undefined ? width : `max(${width}, calc((100% - ${maximum - 1}rem) / ${maximum}))`;\n return {\n className:\n \"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\": trackWidth, \"--ckui-card-mobile-columns\": columns } as CSSProperties,\n };\n}\n\n/** A list carries exact selection-wide counts when the API was asked for them. */\ntype SummaryList = UseList<Card> & { summary?: ListingSummary };\ntype GridProps = CardListProps & { list: SummaryList; render: string; filterKinds?: readonly string[]; filterLanguage?: string; horizontal?: boolean };\n\n/** A list's cards and summary controls share one responsive, animated grid. */\nexport function CardGrid(p: GridProps) {\n const { client, viewer, language } = useContentScope(p.client);\n const context = useContext(ContentKitContext);\n const [boundary, setBoundary] = useState({ client, viewer, language, access: context?.accessRevision, render: p.render, key: 0 });\n let session = boundary;\n if (boundary.client !== client || boundary.viewer !== viewer || boundary.language !== language || boundary.access !== context?.accessRevision || boundary.render !== p.render) {\n session = { client, viewer, language, access: context?.accessRevision, render: p.render, key: boundary.key + 1 };\n setBoundary(session);\n }\n return <CardGridBody key={session.key} {...p} />;\n}\n\nfunction CardGridBody({ list, render, filterKinds, filterLanguage, horizontal, ...p }: GridProps) {\n const m = useMessages();\n const { t } = m;\n const { kinds } = useKinds({ client: p.client });\n const [expanded, setExpanded] = useState(false);\n const [filtersOpen, setFiltersOpen] = useState(false);\n const filterTrigger = useRef<HTMLElement | null>(null);\n const [element, setElement] = useState<HTMLOListElement | null>(null);\n const [columns, setColumns] = useState(0);\n const [mobile, setMobile] = useState(() => globalThis.matchMedia?.(\"(max-width: 767px)\").matches ?? false);\n useLayoutEffect(() => {\n if (!element) return;\n const measure = () => {\n const tracks = getComputedStyle(element).gridTemplateColumns.trim();\n setColumns(tracks && tracks !== \"none\" ? tracks.split(/\\s+/).length : 0);\n setMobile(globalThis.matchMedia?.(\"(max-width: 767px)\").matches ?? false);\n };\n measure();\n const observer = typeof ResizeObserver === \"function\" ? new ResizeObserver(measure) : undefined;\n observer?.observe(element);\n addEventListener(\"resize\", measure);\n return () => {\n observer?.disconnect();\n removeEventListener(\"resize\", measure);\n };\n }, [element]);\n const lockedCount = list.summary?.locked_count ?? 0;\n const filteredCount = p.showFilteredSummary === false ? 0 : (list.summary?.filtered_count ?? 0);\n const collapse = p.locked === \"collapse\" && !expanded && lockedCount > 0;\n const lockedTile = collapse && lockedCount > 0 && columns >= Math.max(0, p.lockedSummaryMinColumns ?? 0);\n const entries = useMemo(() => {\n const result: ({ key: string; card: Card; position: number } | { key: string; premium: true; card?: Card })[] = [];\n let inserted = false;\n for (const [i, card] of list.items.entries()) {\n if (isLockedCard(card) && (collapse || p.locked === \"hide\")) {\n if (lockedTile && !inserted) {\n result.push({ key: \"premium-summary\", premium: true, card });\n inserted = true;\n }\n continue;\n }\n result.push({ key: card.id, card, position: i + 1 });\n }\n if (lockedTile && !inserted) result.push({ key: \"premium-summary\", premium: true });\n const limit = mobile && p.mobileCardLimit !== undefined ? p.mobileCardLimit : p.cardLimit;\n return limit !== undefined && Number.isFinite(limit) ? result.slice(0, Math.max(0, Math.floor(limit))) : result;\n }, [list.items, collapse, p.locked, lockedTile, mobile, p.mobileCardLimit, p.cardLimit]);\n const shown = useMemo(() => entries.flatMap((entry) => (\"premium\" in entry ? [] : [{ ...entry.card, position: entry.position }])), [entries]);\n const exposure = useExposure(shown, { surface: p.surface ?? \"list\", render, client: p.client });\n const [sentinel, near] = useNearViewport({ margin: \"600px 0px\", leaveMargin: \"1200px 0px\" });\n const { hasMore, loadingMore, loadMore, error } = list;\n useEffect(() => {\n if (p.infinite && near && hasMore && !loadingMore && !error) void loadMore();\n }, [p.infinite, near, hasMore, loadingMore, loadMore, error]);\n const { grid: animationGrid, prepare } = useReleaseReveal(entries.map((entry) => entry.key).join(\"\\0\"));\n const grid = useCallback(\n (node: HTMLOListElement | null) => {\n animationGrid.current = node;\n setElement(node);\n },\n [animationGrid],\n );\n const layout: ReturnType<typeof cardGridLayout> = horizontal\n ? { className: \"relative flex snap-x gap-4 overflow-x-auto pb-2 [&>li]:w-36 [&>li]:shrink-0 [&>li]:snap-start sm:[&>li]:w-44\" }\n : cardGridLayout(p.grid);\n const lowerBound = list.summary?.truncated ? \"≥ \" : \"\";\n const premiumLabel = lowerBound + (p.summaryLabels?.premium?.(lockedCount) ?? m.plural(\"contents.lockedShow\", lockedCount));\n const filteredLabel = lowerBound + (p.summaryLabels?.filtered?.(filteredCount) ?? m.plural(\"releases.filteredMore\", filteredCount));\n const showGrid = entries.length > 0 || filteredCount > 0 || (collapse && lockedCount > 0);\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 <ol aria-hidden className={layout.className} style={layout.style}>\n {Array.from({ length: 8 }, (_, i) => (\n <li key={i}>\n <Skeleton\n className={p.aspectRatio === undefined ? (p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\") : \"w-full\"}\n style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }}\n />\n </li>\n ))}\n </ol>\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 ) : !showGrid ? (\n <div className=\"text-muted-foreground\" role=\"status\">\n {p.empty ?? t(\"contents.empty\")}\n </div>\n ) : (\n <ol ref={grid} tabIndex={-1} className={layout.className} style={layout.style}>\n {entries.map((entry) =>\n \"premium\" in entry ? (\n <ContentSummaryTile\n key={entry.key}\n tileKey={entry.key}\n kind=\"premium\"\n contentKind={filterKinds?.[0] ?? entry.card?.kind}\n preview={entry.card}\n open\n cardProps={p}\n label={premiumLabel}\n onClick={(event) => {\n prepare(event.detail === 0);\n setExpanded(true);\n }}\n />\n ) : (\n <li key={entry.key} data-release-key={entry.key} ref={exposure.track(entry.card.id, entry.position)}>\n {p.renderCard ? (\n p.renderCard(entry.card, entry.position)\n ) : (\n <ContentCard\n card={entry.card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\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(entry.card.id, entry.position)}\n />\n )}\n </li>\n ),\n )}\n {filteredCount > 0 && (\n <ContentSummaryTile\n tileKey=\"filtered-summary\"\n kind=\"filtered\"\n contentKind={filterKinds?.[0] ?? list.items[0]?.kind}\n open\n cardProps={p}\n label={filteredLabel}\n onClick={(event) => {\n filterTrigger.current = event.currentTarget;\n setFiltersOpen(true);\n }}\n />\n )}\n </ol>\n )}\n {list.hasMore && (\n <div ref={sentinel} className=\"grid justify-items-center gap-2\">\n {list.error && list.items.length > 0 && (\n <p role=\"alert\" className=\"text-destructive\">\n {contentError(m, list.error)}\n </p>\n )}\n <Button variant=\"outline\" size=\"sm\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.loadingMore ? t(\"contents.loading\") : list.error ? t(\"common.retry\") : t(\"contents.loadMore\")}\n </Button>\n </div>\n )}\n <ContentFilterDialog\n open={filtersOpen}\n onOpenChange={setFiltersOpen}\n returnFocus={() => (filterTrigger.current?.isConnected ? filterTrigger.current : element)}\n client={p.client}\n kinds={filterKinds}\n language={filterLanguage}\n counts={list.summary?.filtered_entities}\n countsLowerBound={list.summary?.truncated}\n appearance={p.appearance}\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 replaces them with a counted tile\n * that reveals the cached previews in place (collapse).\n */\nexport type LockedCards = \"show\" | \"hide\" | \"collapse\";\n\n/** Settle locked-card admission before pagination, retaining explicit reader modes otherwise. */\nexport function cardListMode(locked: LockedCards | undefined, mode?: AccessMode): AccessMode | undefined {\n return locked === \"hide\" ? \"hide\" : locked === \"collapse\" ? \"lock\" : mode;\n}\n\nconst LOCKED_LEVELS: readonly string[] = [\"tier\", \"group\", \"ppv\", \"group_ppv\"];\nconst isLockedCard = (card: Card) => !card.access.allowed && !card.access.unknown && LOCKED_LEVELS.includes(card.access.level);\n\n/** Contents as cards (GET /v1/contents): any sort and filter, paged. */\nexport function ContentGrid({ query, locked = \"show\", ...p }: CardListProps & { query: ContentQuery }) {\n const q = useMemo<ContentQuery>(\n () => ({\n ...query,\n summary: query.summary ?? true,\n limit: query.limit ?? p.pageSize,\n mode: cardListMode(locked, query.mode),\n }),\n [query, p.pageSize, locked],\n );\n const list = useContents(q, { client: p.client });\n return (\n <CardGrid\n {...p}\n locked={locked}\n list={list}\n render={JSON.stringify(q)}\n filterKinds={query.kind ? [query.kind] : undefined}\n filterLanguage={query.display_language ?? query.language}\n />\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(\n () => ({ ...query, mode: cardListMode(p.locked, query.mode), summary: query.summary ?? true, limit: query.limit ?? p.pageSize }),\n [query, p.pageSize, p.locked],\n );\n const list = useSearch(q, { client: p.client });\n if (!query.q.trim()) return null;\n return (\n <CardGrid\n surface=\"search\"\n empty={t(\"contents.noResults\", { q: query.q.trim() })}\n {...p}\n list={list}\n render={JSON.stringify(q)}\n filterKinds={query.kind ? [query.kind] : undefined}\n filterLanguage={query.display_language ?? query.language}\n />\n );\n}\n\nexport { ContentHistory } from \"./history.js\";\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, mode: cardListMode(p.locked, query.mode), summary: query.summary ?? true, limit }), [query, limit, p.locked]);\n const listing = useMemo(\n () =>\n listQuery\n ? { ...listQuery, mode: cardListMode(p.locked, listQuery.mode), summary: listQuery.summary ?? true, limit: listQuery.limit ?? limit }\n : group\n ? { groups: [group], sort: \"position\" as const, mode: cardListMode(p.locked), summary: true, limit }\n : null,\n [listQuery, group, limit, p.locked],\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, mode: cardListMode(p.locked, history.mode), summary: history.summary ?? true, limit: history.limit ?? limit } : null, {\n client: p.client,\n });\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, summary: l.summary, loading: l.loading, error: l.error, unavailable: l.error?.code === \"not_configured\", reload: l.reload };\n })()\n : like;\n const surface = p.surface ?? (recommendations ? \"recommendations\" : history ? \"history\" : group ? \"featured\" : listing ? \"list\" : \"similar\");\n const source = similar || recommendations || history || group || listing;\n if (!source || rail.unavailable || (!rail.loading && !rail.items.length && !rail.summary?.filtered_count && !rail.summary?.locked_count)) return null;\n const list: SummaryList = { ...rail, loadingMore: false, hasMore: false, loadMore: async () => {} };\n const kind = listQuery?.kind ?? history?.kind ?? q.kind;\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 <CardGrid\n {...p}\n className={undefined}\n surface={surface}\n horizontal={!p.grid}\n list={list}\n render={JSON.stringify([similar, recommendations, history, group, listing, q])}\n filterKinds={kind ? [kind] : undefined}\n filterLanguage={listQuery?.display_language ?? history?.display_language ?? q.display_language ?? listQuery?.language ?? history?.language ?? q.language}\n />\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { HistoryQuery } from '../../client/discovery.js';
2
+ import { CardListProps } from './grid.js';
3
+ /** Account history or this browser's guest history, each removable, with a clear-all. */
4
+ export declare function ContentHistory({ query, ...p }: CardListProps & {
5
+ query?: HistoryQuery;
6
+ }): import('react').JSX.Element;
@@ -0,0 +1,91 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { Button as t } from "../../ui/button.js";
3
+ import { useAction as n } from "../social/parts.js";
4
+ import { useHistory as r } from "../../react/discovery.js";
5
+ import { ContentCard as i } from "./card.js";
6
+ import { ContentFilterDialog as a } from "./filter-dialog.js";
7
+ import { CardGrid as o } from "./grid.js";
8
+ import { useMemo as s, useRef as c, useState as l } from "react";
9
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
10
+ //#region src/components/content/history.tsx
11
+ function f({ query: f = {}, ...p }) {
12
+ let { t: m } = e(), h = s(() => ({
13
+ ...f,
14
+ summary: !0,
15
+ mode: p.locked === "collapse" ? "lock" : p.locked === "hide" ? "hide" : f.mode,
16
+ limit: f.limit ?? p.pageSize
17
+ }), [
18
+ f,
19
+ p.pageSize,
20
+ p.locked
21
+ ]), g = r(h, { client: p.client }), _ = n("history.forget"), [v, y] = l(!1), b = c(null);
22
+ return /* @__PURE__ */ d("div", {
23
+ className: "grid gap-3",
24
+ "data-ckui": "content-history",
25
+ children: [
26
+ g.items.length > 0 && /* @__PURE__ */ u(t, {
27
+ size: "sm",
28
+ variant: "outline",
29
+ className: "justify-self-end",
30
+ disabled: _.pending,
31
+ onClick: () => void _.run(() => g.forget(f.kind ? { kind: f.kind } : {})),
32
+ children: m("contents.clearHistory")
33
+ }),
34
+ g.filterSummaryUnknown && p.showFilteredSummary !== !1 && /* @__PURE__ */ u(t, {
35
+ ref: b,
36
+ variant: "outline",
37
+ className: "justify-self-start",
38
+ onClick: () => y(!0),
39
+ children: m("releases.manageFilters")
40
+ }),
41
+ /* @__PURE__ */ u(a, {
42
+ client: p.client,
43
+ language: h.language,
44
+ open: v,
45
+ onOpenChange: y,
46
+ returnFocus: () => b.current
47
+ }),
48
+ /* @__PURE__ */ u(o, {
49
+ surface: "history",
50
+ empty: m("contents.historyEmpty"),
51
+ ...p,
52
+ list: g,
53
+ render: JSON.stringify(h),
54
+ filterLanguage: h.language,
55
+ renderCard: p.renderCard ?? ((e) => /* @__PURE__ */ u(i, {
56
+ card: e,
57
+ client: p.client,
58
+ href: p.href,
59
+ shape: p.shape,
60
+ aspectRatio: p.aspectRatio,
61
+ titlePosition: p.titlePosition,
62
+ loadMedia: p.loadMedia,
63
+ groupKinds: p.groupKinds,
64
+ chips: p.chips,
65
+ filterEntity: p.filterEntity,
66
+ entityStyle: p.entityStyle,
67
+ entityLayout: p.entityLayout,
68
+ metadata: p.metadata,
69
+ showCounts: p.showCounts,
70
+ tiers: p.tiers,
71
+ labels: p.labels,
72
+ owner: p.owner,
73
+ ownerHref: p.ownerHref,
74
+ decorate: p.decorate,
75
+ children: /* @__PURE__ */ u(t, {
76
+ size: "xs",
77
+ variant: "ghost",
78
+ className: "justify-self-start",
79
+ disabled: _.pending,
80
+ onClick: () => void _.run(() => g.forget({ content_id: e.id })),
81
+ children: m("contents.removeFromHistory")
82
+ })
83
+ }))
84
+ })
85
+ ]
86
+ });
87
+ }
88
+ //#endregion
89
+ export { f as ContentHistory };
90
+
91
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","names":[],"sources":["../../../src/components/content/history.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from \"react\";\nimport type { HistoryQuery } from \"../../client/discovery.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useHistory } from \"../../react/discovery.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { useAction } from \"../social/parts.js\";\nimport { ContentFilterDialog } from \"./filter-dialog.js\";\nimport { ContentCard } from \"./card.js\";\nimport { CardGrid, type CardListProps } from \"./grid.js\";\n\n/** Account history or this browser's guest history, each removable, with a clear-all. */\nexport function ContentHistory({ query = {}, ...p }: CardListProps & { query?: HistoryQuery }) {\n const { t } = useMessages();\n const q = useMemo(() => ({ ...query, summary: true, mode: p.locked === \"collapse\" ? \"lock\" as const : p.locked === \"hide\" ? \"hide\" as const : query.mode, limit: query.limit ?? p.pageSize }), [query, p.pageSize, p.locked]);\n const list = useHistory(q, { client: p.client });\n const forget = useAction(\"history.forget\");\n const [filtersOpen, setFiltersOpen] = useState(false);\n const filterTrigger = useRef<HTMLButtonElement | null>(null);\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 {list.filterSummaryUnknown && p.showFilteredSummary !== false && (\n <Button ref={filterTrigger} variant=\"outline\" className=\"justify-self-start\" onClick={() => setFiltersOpen(true)}>{t(\"releases.manageFilters\")}</Button>\n )}\n <ContentFilterDialog client={p.client} language={q.language} open={filtersOpen} onOpenChange={setFiltersOpen} returnFocus={() => filterTrigger.current} />\n <CardGrid\n surface=\"history\"\n empty={t(\"contents.historyEmpty\")}\n {...p}\n list={list}\n render={JSON.stringify(q)}\n filterLanguage={q.language}\n renderCard={\n p.renderCard ??\n ((card) => (\n <ContentCard\n card={card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\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"],"mappings":";;;;;;;;;;AAWA,SAAgB,EAAe,EAAE,WAAQ,CAAC,GAAG,GAAG,KAA+C;CAC7F,IAAM,EAAE,SAAM,EAAY,GACpB,IAAI,SAAe;EAAE,GAAG;EAAO,SAAS;EAAM,MAAM,EAAE,WAAW,aAAa,SAAkB,EAAE,WAAW,SAAS,SAAkB,EAAM;EAAM,OAAO,EAAM,SAAS,EAAE;CAAS,IAAI;EAAC;EAAO,EAAE;EAAU,EAAE;CAAM,CAAC,GACtN,IAAO,EAAW,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GACzC,IAAS,EAAU,gBAAgB,GACnC,CAAC,GAAa,KAAkB,EAAS,EAAK,GAC9C,IAAgB,EAAiC,IAAI;CAC3D,OACE,kBAAC,OAAD;EAAK,WAAU;EAAa,aAAU;EAAtC,UAAA;GACG,EAAK,MAAM,SAAS,KACnB,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,WAAU;IAAmB,UAAU,EAAO;IAAS,eAAe,KAAK,EAAO,UAAU,EAAK,OAAO,EAAM,OAAO,EAAE,MAAM,EAAM,KAAK,IAAI,CAAC,CAAC,CAAC;IAChL,UAAA,EAAE,uBAAuB;GACpB,CAAA;GAET,EAAK,wBAAwB,EAAE,wBAAwB,MACtD,kBAAC,GAAD;IAAQ,KAAK;IAAe,SAAQ;IAAU,WAAU;IAAqB,eAAe,EAAe,EAAI;IAAI,UAAA,EAAE,wBAAwB;GAAU,CAAA;GAEzJ,kBAAC,GAAD;IAAqB,QAAQ,EAAE;IAAQ,UAAU,EAAE;IAAU,MAAM;IAAa,cAAc;IAAgB,mBAAmB,EAAc;GAAU,CAAA;GACzJ,kBAAC,GAAD;IACE,SAAQ;IACR,OAAO,EAAE,uBAAuB;IAChC,GAAI;IACE;IACN,QAAQ,KAAK,UAAU,CAAC;IACxB,gBAAgB,EAAE;IAClB,YACE,EAAE,gBACA,MACA,kBAAC,GAAD;KACQ;KACN,QAAQ,EAAE;KACV,MAAM,EAAE;KACR,OAAO,EAAE;KACT,aAAa,EAAE;KACf,eAAe,EAAE;KACjB,WAAW,EAAE;KACb,YAAY,EAAE;KACd,OAAO,EAAE;KACT,cAAc,EAAE;KAChB,aAAa,EAAE;KACf,cAAc,EAAE;KAChB,UAAU,EAAE;KACZ,YAAY,EAAE;KACd,OAAO,EAAE;KACT,QAAQ,EAAE;KACV,OAAO,EAAE;KACT,WAAW,EAAE;KACb,UAAU,EAAE;KAEZ,UAAA,kBAAC,GAAD;MAAQ,MAAK;MAAK,SAAQ;MAAQ,WAAU;MAAqB,UAAU,EAAO;MAAS,eAAe,KAAK,EAAO,UAAU,EAAK,OAAO,EAAE,YAAY,EAAK,GAAG,CAAC,CAAC;MACjK,UAAA,EAAE,4BAA4B;KACzB,CAAA;IACG,CAAA;GAGlB,CAAA;EACE;;AAET"}
@@ -13,3 +13,4 @@ export { AccessEditor, ContentDetailsEditor, ContentSections, SegmentsEditor, Ve
13
13
  export { OfferFields } from './offer-fields.js';
14
14
  export { ReaderStats, type ReaderStatsProps } from './reader-stats.js';
15
15
  export { VersionDownloadButton, type VersionDownloadButtonProps } from './download-button.js';
16
+ export { ContentFavorites } from './favorites.js';
@@ -1,6 +1,7 @@
1
1
  import { ContentKitUiAppearance } from '../../appearance.js';
2
2
  import { ContentKitClient } from '../../client/client.js';
3
- import { Card, GroupCard } from '../../client/generated/wire.js';
3
+ import { TypeaheadQuery } from '../../client/discovery.js';
4
+ import { Card, GroupCard, Typeahead } from '../../client/generated/wire.js';
4
5
  import { Labels } from '../groups/parts.js';
5
6
  export interface SearchBoxProps {
6
7
  /** Submits the text: the host shows its search page. */
@@ -14,8 +15,13 @@ export interface SearchBoxProps {
14
15
  /** A suggestion's link; default its canonical path. */
15
16
  href?: (item: Card | GroupCard) => string | undefined;
16
17
  labels?: Labels;
17
- /** Recent searches first, prefix-filtered while typing. Default true. */
18
+ /** Matching recent searches first, with prefixes before interior matches. Default true. */
18
19
  recent?: boolean;
20
+ /** An authenticated or host-specific suggestion source; public typeahead by default. */
21
+ requestSuggestions?: (query: TypeaheadQuery, signal: AbortSignal) => Promise<Typeahead>;
22
+ /** Enter opens an explicitly selected suggestion; free text never submits a results search. */
23
+ selectionOnly?: boolean;
24
+ disabled?: boolean;
19
25
  /** A portal dropdown anchored to the header field; default stays inline. */
20
26
  presentation?: "inline" | "header";
21
27
  client?: ContentKitClient;
@@ -23,7 +29,8 @@ export interface SearchBoxProps {
23
29
  appearance?: ContentKitUiAppearance;
24
30
  }
25
31
  /**
26
- * Recent query prefixes first, followed by entity and title prefixes. Enter
27
- * searches, or opens the chosen suggestion; full search can match more deeply.
32
+ * Matching recent queries first, followed by entity/title substrings. Prefixes
33
+ * are preferred. Enter searches or opens the chosen suggestion; full search can
34
+ * match contextual fields more deeply.
28
35
  */
29
36
  export declare function SearchBox(p: SearchBoxProps): import('react').JSX.Element;
@@ -12,7 +12,7 @@ import { Cancel01Icon as g, Clock01Icon as _, File01Icon as v, Folder01Icon as y
12
12
  import { HugeiconsIcon as w } from "@hugeicons/react";
13
13
  import { createPortal as re } from "react-dom";
14
14
  //#region src/components/content/search.tsx
15
- var T = (e) => e.type === "content" ? `c:${e.card.id}` : e.type === "group" ? `g:${e.group.id}` : `r:${e.query}`, E = (e) => e.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").normalize("NFC").toLowerCase().trim().replace(/\s+/g, " "), D = {
15
+ var T = (e) => e.type === "content" ? `c:${e.card.id}` : e.type === "group" ? `g:${e.group.id}` : `r:${e.query}`, E = (e) => e.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").normalize("NFC").toLowerCase().replace(/(^|[^\p{L}\p{N}\p{M}_])(?:the|a|an)(?=$|[^\p{L}\p{N}\p{M}_])/gu, "$1").trim().replace(/\s+/g, " "), D = {
16
16
  artist: {
17
17
  icon: C,
18
18
  color: "text-blue-500 dark:text-blue-400"
@@ -46,7 +46,7 @@ var T = (e) => e.type === "content" ? `c:${e.card.id}` : e.type === "group" ? `g
46
46
  color: "text-orange-500 dark:text-orange-400"
47
47
  }
48
48
  };
49
- function O(e, t, n, r) {
49
+ function ie(e, t, n, r) {
50
50
  let [i, a] = d({
51
51
  top: 0,
52
52
  left: 0,
@@ -76,7 +76,7 @@ function O(e, t, n, r) {
76
76
  r
77
77
  ]), i;
78
78
  }
79
- function ie({ kind: e }) {
79
+ function O({ kind: e }) {
80
80
  let t = D[e];
81
81
  return /* @__PURE__ */ p(w, {
82
82
  icon: t?.icon ?? x,
@@ -87,14 +87,19 @@ function ie({ kind: e }) {
87
87
  });
88
88
  }
89
89
  function k(l) {
90
- let { t: v, error: y } = t(), b = te(), S = c(r)?.navigate, [C, D] = d(l.defaultValue ?? ""), [k, A] = d(!1), [j, M] = d(), N = u(null), P = u(null), F = u(null), I = l.presentation === "header", L = O(k, I, N, F), { top: R } = L, z = !C.trim(), B = o(C, {
90
+ let { t: v, error: y } = t(), b = te(), S = c(r)?.navigate, [C, D] = d(l.defaultValue ?? ""), [k, A] = d(!1), [j, M] = d(), N = u(null), P = u(null), F = u(null), I = l.presentation === "header", L = ie(k, I, N, F), { top: R } = L, z = !C.trim(), B = o(C, {
91
91
  kind: l.kind,
92
92
  entity_kinds: l.groupKinds,
93
- client: l.client
93
+ client: l.client,
94
+ request: l.requestSuggestions,
95
+ minLength: l.disabled ? Infinity : 1
94
96
  }), V = a({
95
- enabled: l.recent !== !1 && k,
97
+ enabled: l.recent !== !1 && k && !l.disabled,
96
98
  client: l.client
97
- }), H = E(C), U = B.query === C.trim(), W = ne(() => [...l.recent === !1 ? [] : V.searches.filter((e) => E(e.query).startsWith(H)).map((e) => ({
99
+ }), H = E(C), U = B.query === C.trim(), W = ne(() => [...l.recent === !1 ? [] : V.searches.map((e) => ({
100
+ ...e,
101
+ normalized: E(e.query)
102
+ })).filter((e) => z || H && e.normalized.includes(H)).sort((e, t) => Number(t.normalized.startsWith(H)) - Number(e.normalized.startsWith(H))).map((e) => ({
98
103
  type: "recent",
99
104
  query: e.query
100
105
  })), ...!z && U ? [...B.entities.map((e) => ({
@@ -123,7 +128,7 @@ function k(l) {
123
128
  return document.addEventListener("pointerdown", e), () => document.removeEventListener("pointerdown", e);
124
129
  }, [k]);
125
130
  let Y = () => {
126
- A(!0);
131
+ l.disabled || A(!0);
127
132
  }, X = (e) => {
128
133
  A(!1), e && l.onSearch(e);
129
134
  }, Z = (e) => {
@@ -162,6 +167,7 @@ function k(l) {
162
167
  children: W.map((t, n) => /* @__PURE__ */ p("li", {
163
168
  id: `${b}-${n}`,
164
169
  role: "option",
170
+ "aria-label": I && t.type !== "recent" ? `${s(l.labels, t.type === "content" ? t.card.kind : t.group.kind)} ${t.type === "content" ? t.card.title || t.card.code : t.group.name || t.group.slug}` : void 0,
165
171
  "aria-selected": n === G,
166
172
  "data-ckui": "suggestion",
167
173
  "data-type": t.type,
@@ -171,7 +177,7 @@ function k(l) {
171
177
  e.preventDefault(), Z(t);
172
178
  },
173
179
  children: I && t.type !== "recent" ? /* @__PURE__ */ m(f, { children: [
174
- /* @__PURE__ */ p(ie, { kind: t.type === "content" ? t.card.kind : t.group.kind }),
180
+ /* @__PURE__ */ p(O, { kind: t.type === "content" ? t.card.kind : t.group.kind }),
175
181
  /* @__PURE__ */ p("span", {
176
182
  className: "sr-only",
177
183
  children: s(l.labels, t.type === "content" ? t.card.kind : t.group.kind)
@@ -263,12 +269,15 @@ function k(l) {
263
269
  role: "search",
264
270
  className: "relative",
265
271
  onSubmit: (e) => {
266
- if (e.preventDefault(), k && K) return Z(K);
267
- X(C.trim());
272
+ if (e.preventDefault(), !l.disabled) {
273
+ if (k && K) return Z(K);
274
+ l.selectionOnly || X(C.trim());
275
+ }
268
276
  },
269
277
  children: [
270
278
  I ? /* @__PURE__ */ p("button", {
271
279
  type: "submit",
280
+ disabled: l.disabled,
272
281
  className: "absolute top-1/2 left-2.5 -translate-y-1/2 rounded text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring",
273
282
  "aria-label": v("contents.search"),
274
283
  children: /* @__PURE__ */ p(w, {
@@ -293,6 +302,7 @@ function k(l) {
293
302
  "aria-activedescendant": k && K ? `${b}-${G}` : void 0,
294
303
  "aria-autocomplete": "list",
295
304
  autoComplete: "off",
305
+ disabled: l.disabled,
296
306
  className: h("h-9 ps-8", I && "rounded-full border-border/50 bg-foreground/10 pe-9 text-base shadow-none md:text-base"),
297
307
  value: C,
298
308
  placeholder: l.placeholder ?? v("contents.searchPlaceholder"),
@@ -311,6 +321,7 @@ function k(l) {
311
321
  }),
312
322
  I && (C || k) && /* @__PURE__ */ p("button", {
313
323
  type: "button",
324
+ disabled: l.disabled,
314
325
  className: "absolute top-1/2 right-2.5 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring",
315
326
  "aria-label": v(C ? "contents.clearSearch" : "common.close"),
316
327
  onMouseDown: (e) => e.preventDefault(),
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":[],"sources":["../../../src/components/content/search.tsx"],"sourcesContent":["import { Cancel01Icon, Clock01Icon, File01Icon, Folder01Icon, Image01Icon, Search01Icon, TagsIcon, UserIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type RefObject, type KeyboardEvent } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Card, GroupCard } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useRecentSearches, useTypeahead } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Input } from \"#ckui/ui/input\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\n\nexport interface SearchBoxProps {\n /** Submits the text: the host shows its search page. */\n onSearch: (q: string) => void;\n /** Suggest contents of this kind only. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n groupKinds?: readonly string[];\n defaultValue?: string;\n placeholder?: string;\n /** A suggestion's link; default its canonical path. */\n href?: (item: Card | GroupCard) => string | undefined;\n labels?: Labels;\n /** Recent searches first, prefix-filtered while typing. Default true. */\n recent?: boolean;\n /** A portal dropdown anchored to the header field; default stays inline. */\n presentation?: \"inline\" | \"header\";\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ntype Suggestion = { type: \"content\"; card: Card } | { type: \"group\"; group: GroupCard } | { type: \"recent\"; query: string };\nconst suggestionKey = (s: Suggestion) => s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`;\n\n// Match the keyword index's case, Latin accent and whitespace normalization.\nconst prefixText = (text: string) => text.normalize(\"NFKD\").replace(/[\\u0300-\\u036f]/g, \"\").normalize(\"NFC\").toLowerCase().trim().replace(/\\s+/g, \" \");\n\nconst TYPE_ICONS: Record<string, { icon: IconSvgElement; color: string }> = {\n artist: { icon: UserIcon, color: \"text-blue-500 dark:text-blue-400\" },\n series: { icon: Folder01Icon, color: \"text-yellow-600 dark:text-yellow-400\" },\n gallery: { icon: Image01Icon, color: \"text-green-600 dark:text-green-400\" },\n post: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n blog: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n poll: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n character: { icon: UserIcon, color: \"text-purple-500 dark:text-purple-400\" },\n tag: { icon: TagsIcon, color: \"text-orange-500 dark:text-orange-400\" },\n};\n\nfunction useHeaderAnchor(open: boolean, enabled: boolean, root: RefObject<HTMLDivElement | null>, input: RefObject<HTMLInputElement | null>) {\n const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });\n useLayoutEffect(() => {\n if (!open || !enabled || !root.current) return;\n const anchor = input.current ?? root.current;\n const view = root.current.ownerDocument.defaultView;\n if (!view) return;\n const measure = () => {\n const rect = anchor.getBoundingClientRect();\n setPosition({ top: Math.max(0, Math.min(view.innerHeight, rect.bottom + 4)), left: rect.left, width: rect.width });\n };\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(measure);\n observer?.observe(anchor);\n view.addEventListener(\"resize\", measure);\n view.addEventListener(\"scroll\", measure, true);\n view.visualViewport?.addEventListener(\"resize\", measure);\n view.visualViewport?.addEventListener(\"scroll\", measure);\n return () => {\n observer?.disconnect();\n view.removeEventListener(\"resize\", measure);\n view.removeEventListener(\"scroll\", measure, true);\n view.visualViewport?.removeEventListener(\"resize\", measure);\n view.visualViewport?.removeEventListener(\"scroll\", measure);\n };\n }, [open, enabled, root, input]);\n return position;\n}\n\nfunction SearchTypeIcon({ kind }: { kind: string }) {\n const type = TYPE_ICONS[kind];\n return <HugeiconsIcon icon={type?.icon ?? Search01Icon} strokeWidth={2} className={cn(\"size-4 shrink-0\", type?.color ?? \"text-muted-foreground\")} data-search-kind={kind} aria-hidden />;\n}\n\n/**\n * Recent query prefixes first, followed by entity and title prefixes. Enter\n * searches, or opens the chosen suggestion; full search can match more deeply.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t, error } = useMessages();\n const id = useId();\n const navigate = useContext(ContentKitContext)?.navigate;\n const [text, setText] = useState(p.defaultValue ?? \"\");\n const [open, setOpen] = useState(false);\n // null requests the first result when opening an as-yet-unloaded panel.\n const [active, setActive] = useState<string | null>();\n const root = useRef<HTMLDivElement>(null);\n const panel = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const header = p.presentation === \"header\";\n const anchor = useHeaderAnchor(open, header, root, input);\n const { top } = anchor;\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, entity_kinds: p.groupKinds, client: p.client });\n const recent = useRecentSearches({ enabled: p.recent !== false && open, client: p.client });\n const prefix = prefixText(text);\n const current = found.query === text.trim();\n const visible = useMemo<Suggestion[]>(\n () => [\n ...(p.recent === false ? [] : recent.searches.filter((r) => prefixText(r.query).startsWith(prefix)).map((r) => ({ type: \"recent\" as const, query: r.query }))),\n ...(!empty && current ? [\n ...found.entities.map((group) => ({ type: \"group\" as const, group })),\n ...found.contents.map((card) => ({ type: \"content\" as const, card })),\n ] : []),\n ],\n [p.recent, prefix, empty, current, recent.searches, found.contents, found.entities],\n );\n // History and live suggestions can arrive independently. Track identity so\n // prepending history never changes what Enter will open.\n const activeIndex = active === null ? Math.min(0, visible.length - 1) : visible.findIndex((s) => suggestionKey(s) === active);\n const selected = visible[activeIndex];\n const activate = (index: number) => {\n const suggestion = visible[index];\n setActive(suggestion ? suggestionKey(suggestion) : index >= 0 ? null : undefined);\n };\n const loading = recent.loading || (!empty && (!current || found.loading));\n const showRecent = empty && p.recent !== false;\n const emptyMessage = empty ? !header && showRecent && !recent.error ? \"contents.noRecentSearches\" : \"contents.searchHint\" : \"contents.noResults\";\n useEffect(() => {\n if (!open) return;\n const outside = (event: PointerEvent) => {\n if (!root.current?.contains(event.target as Node) && !panel.current?.contains(event.target as Node)) setOpen(false);\n };\n document.addEventListener(\"pointerdown\", outside);\n return () => document.removeEventListener(\"pointerdown\", outside);\n }, [open]);\n const reveal = () => {\n setOpen(true);\n };\n const search = (q: string) => {\n setOpen(false);\n if (q) p.onSearch(q);\n };\n const go = (s: Suggestion) => {\n if (s.type === \"recent\") {\n setText(s.query);\n return search(s.query);\n }\n const url = p.href ? p.href(s.type === \"content\" ? s.card : s.group) : s.type === \"content\" ? s.card.path : s.group.path;\n setOpen(false);\n if (!url) return;\n if (navigate) navigate(url);\n else location.assign(url);\n };\n const panelKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n input.current?.focus();\n setOpen(false);\n setActive(undefined);\n };\n const results = (<>\n {showRecent && (\n <div className={cn(\"flex items-center justify-between gap-2 text-muted-foreground\", header ? \"mb-4 text-base font-semibold\" : \"px-2 py-1 text-xs\")}>\n <span id={`${id}-recent`} className=\"flex items-center gap-2\">{header && <HugeiconsIcon icon={Clock01Icon} strokeWidth={2} className=\"size-5\" aria-hidden />}{t(\"contents.recentSearches\")}</span>\n {visible.length > 0 && <button\n type=\"button\"\n className=\"rounded px-1 hover:text-foreground\"\n onMouseDown={(e) => {\n e.preventDefault();\n }}\n onClick={() => void recent.clear()}\n >\n {t(header ? \"contents.clearAllRecent\" : \"contents.clearRecent\")}\n </button>}\n </div>\n )}\n <ul id={`${id}-list`} role=\"listbox\" aria-label={empty ? t(\"contents.recentSearches\") : t(\"contents.suggestions\")} className={cn(\"grid\", header && \"gap-2\")}>\n {visible.map((s, i) => (\n <li\n key={suggestionKey(s)}\n id={`${id}-${i}`}\n role=\"option\"\n aria-selected={i === activeIndex}\n data-ckui=\"suggestion\"\n data-type={s.type}\n className={cn(\"flex cursor-pointer items-center\", header ? s.type === \"recent\" ? \"gap-2 rounded-md p-3 text-base hover:bg-foreground/10\" : \"gap-3 rounded-md px-4 py-1 text-base font-medium hover:bg-foreground/10\" : \"gap-2 px-2 py-1.5\", i === activeIndex && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(suggestionKey(s))}\n onMouseDown={(e) => {\n e.preventDefault();\n go(s);\n }}\n >\n {header && s.type !== \"recent\" ? (\n <>\n <SearchTypeIcon kind={s.type === \"content\" ? s.card.kind : s.group.kind} />\n <span className=\"sr-only\">{labelOf(p.labels, s.type === \"content\" ? s.card.kind : s.group.kind)}</span>\n <span className=\"min-w-0 flex-1 truncate\">{s.type === \"content\" ? s.card.title || s.card.code : s.group.name || s.group.slug}</span>\n </>\n ) : s.type === \"content\" ? (\n <>\n <span className=\"h-10 w-8 shrink-0 overflow-hidden rounded bg-muted\">\n <RenditionImg outputs={s.card.cover} alt=\"\" fit=\"cover\" className=\"size-full\" />\n </span>\n <span className=\"min-w-0 truncate\">{s.card.title || s.card.code}</span>\n </>\n ) : s.type === \"group\" ? (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{s.group.name || s.group.slug}</span>\n <span className=\"text-xs text-muted-foreground\">{labelOf(p.labels, s.group.kind)}</span>\n </>\n ) : (\n <>\n <HugeiconsIcon icon={header ? Search01Icon : Clock01Icon} strokeWidth={2} className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 truncate\">{s.query}</span>\n <button\n type=\"button\"\n className=\"rounded p-0.5 text-muted-foreground hover:text-foreground\"\n aria-label={t(\"contents.removeRecent\", { q: s.query })}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n onClick={() => void recent.clear(s.query)}\n >\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n </button>\n </>\n )}\n </li>\n ))}\n </ul>\n {loading ? <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex justify-center py-4\" : \"px-3 py-2\")}>{header ? <><span className=\"size-6 animate-spin rounded-full border-2 border-muted border-t-foreground motion-reduce:animate-none\" aria-hidden /><span className=\"sr-only\">{t(\"contents.loading\")}</span></> : t(\"contents.loading\")}</div>\n : !empty && found.error ? <p role=\"alert\" className={cn(\"px-3 py-2 text-destructive\", header && \"text-center\")}>{error(found.error)}</p>\n : visible.length === 0 && <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex flex-col items-center gap-2 py-8 text-center\" : \"px-3 py-2\")}>{header && <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-8\" aria-hidden />}<p>{t(emptyMessage, { q: text.trim() })}</p></div>}\n </>);\n return (\n <ContentKitUiRoot ref={root} appearance={p.appearance} className={cn(\"relative text-sm\", p.className)} data-ckui=\"search-box\" onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget) && !panel.current?.contains(event.relatedTarget)) setOpen(false); }}>\n <form\n role=\"search\"\n className=\"relative\"\n onSubmit={(e) => {\n e.preventDefault();\n if (open && selected) return go(selected);\n search(text.trim());\n }}\n >\n {header ? <button type=\"submit\" className=\"absolute top-1/2 left-2.5 -translate-y-1/2 rounded text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\" aria-label={t(\"contents.search\")}><HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n : <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\" aria-hidden />}\n <Input\n ref={input}\n type={header ? \"text\" : \"search\"}\n role=\"combobox\"\n aria-label={t(\"contents.search\")}\n aria-expanded={open}\n aria-controls={`${id}-list`}\n aria-activedescendant={open && selected ? `${id}-${activeIndex}` : undefined}\n aria-autocomplete=\"list\"\n autoComplete=\"off\"\n className={cn(\"h-9 ps-8\", header && \"rounded-full border-border/50 bg-foreground/10 pe-9 text-base shadow-none md:text-base\")}\n value={text}\n placeholder={p.placeholder ?? t(\"contents.searchPlaceholder\")}\n onChange={(e) => {\n setText(e.target.value);\n setOpen(true);\n setActive(undefined);\n }}\n onFocus={reveal}\n onClick={reveal}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n setActive(undefined);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n reveal();\n activate(open ? Math.min(visible.length - 1, activeIndex + 1) : 0);\n } else if (e.key === \"ArrowUp\" && open) {\n e.preventDefault();\n activate(Math.max(-1, activeIndex - 1));\n }\n }}\n />\n {header && (text || open) && (\n <button\n type=\"button\"\n className=\"absolute top-1/2 right-2.5 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\"\n aria-label={text ? t(\"contents.clearSearch\") : t(\"common.close\")}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n input.current?.focus();\n setActive(undefined);\n if (text) { setText(\"\"); setOpen(true); }\n else setOpen(false);\n }}\n ><HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n )}\n </form>\n {open && (header ? createPortal(\n <ContentKitUiRoot appearance={p.appearance} className=\"fixed inset-x-0 bottom-0 z-[70] text-sm\" style={{ top }} data-ckui=\"search-layer\">\n <div className=\"absolute inset-0 bg-black/30\" data-ckui=\"search-backdrop\" aria-hidden />\n <div ref={panel} style={{ width: anchor.width, marginLeft: anchor.left }} className=\"relative origin-top animate-[search-expand_300ms_ease-out] rounded-lg border border-border bg-background text-foreground shadow-lg transition-none motion-reduce:animate-none\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>\n <div className=\"w-full px-3 pt-3\">\n <div className=\"[--search-max-height:70vh] overflow-y-auto pb-7 md:[--search-max-height:60vh]\" style={{ maxHeight: `min(var(--search-max-height), calc(100dvh - ${top}px - 1.875rem))` }}>\n <div>{results}</div>\n </div>\n </div>\n </div>\n </ContentKitUiRoot>, document.body,\n ) : <div ref={panel} className=\"absolute top-full z-20 mt-1 grid max-h-96 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>{results}</div>)}\n\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../../src/components/content/search.tsx"],"sourcesContent":["import { Cancel01Icon, Clock01Icon, File01Icon, Folder01Icon, Image01Icon, Search01Icon, TagsIcon, UserIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type RefObject, type KeyboardEvent } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { TypeaheadQuery } from \"../../client/discovery.js\";\nimport type { Card, GroupCard, Typeahead } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useRecentSearches, useTypeahead } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Input } from \"#ckui/ui/input\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\n\nexport interface SearchBoxProps {\n /** Submits the text: the host shows its search page. */\n onSearch: (q: string) => void;\n /** Suggest contents of this kind only. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n groupKinds?: readonly string[];\n defaultValue?: string;\n placeholder?: string;\n /** A suggestion's link; default its canonical path. */\n href?: (item: Card | GroupCard) => string | undefined;\n labels?: Labels;\n /** Matching recent searches first, with prefixes before interior matches. Default true. */\n recent?: boolean;\n /** An authenticated or host-specific suggestion source; public typeahead by default. */\n requestSuggestions?: (query: TypeaheadQuery, signal: AbortSignal) => Promise<Typeahead>;\n /** Enter opens an explicitly selected suggestion; free text never submits a results search. */\n selectionOnly?: boolean;\n disabled?: boolean;\n /** A portal dropdown anchored to the header field; default stays inline. */\n presentation?: \"inline\" | \"header\";\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ntype Suggestion = { type: \"content\"; card: Card } | { type: \"group\"; group: GroupCard } | { type: \"recent\"; query: string };\nconst suggestionKey = (s: Suggestion) => s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`;\n\n// Mirror search-only normalization. Identity/slug normalization stays separate;\n// complete English filler words never change Japanese/CJK or interior letters.\nconst searchText = (text: string) => text.normalize(\"NFKD\").replace(/[\\u0300-\\u036f]/g, \"\").normalize(\"NFC\").toLowerCase()\n .replace(/(^|[^\\p{L}\\p{N}\\p{M}_])(?:the|a|an)(?=$|[^\\p{L}\\p{N}\\p{M}_])/gu, \"$1\").trim().replace(/\\s+/g, \" \");\n\nconst TYPE_ICONS: Record<string, { icon: IconSvgElement; color: string }> = {\n artist: { icon: UserIcon, color: \"text-blue-500 dark:text-blue-400\" },\n series: { icon: Folder01Icon, color: \"text-yellow-600 dark:text-yellow-400\" },\n gallery: { icon: Image01Icon, color: \"text-green-600 dark:text-green-400\" },\n post: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n blog: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n poll: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n character: { icon: UserIcon, color: \"text-purple-500 dark:text-purple-400\" },\n tag: { icon: TagsIcon, color: \"text-orange-500 dark:text-orange-400\" },\n};\n\nfunction useHeaderAnchor(open: boolean, enabled: boolean, root: RefObject<HTMLDivElement | null>, input: RefObject<HTMLInputElement | null>) {\n const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });\n useLayoutEffect(() => {\n if (!open || !enabled || !root.current) return;\n const anchor = input.current ?? root.current;\n const view = root.current.ownerDocument.defaultView;\n if (!view) return;\n const measure = () => {\n const rect = anchor.getBoundingClientRect();\n setPosition({ top: Math.max(0, Math.min(view.innerHeight, rect.bottom + 4)), left: rect.left, width: rect.width });\n };\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(measure);\n observer?.observe(anchor);\n view.addEventListener(\"resize\", measure);\n view.addEventListener(\"scroll\", measure, true);\n view.visualViewport?.addEventListener(\"resize\", measure);\n view.visualViewport?.addEventListener(\"scroll\", measure);\n return () => {\n observer?.disconnect();\n view.removeEventListener(\"resize\", measure);\n view.removeEventListener(\"scroll\", measure, true);\n view.visualViewport?.removeEventListener(\"resize\", measure);\n view.visualViewport?.removeEventListener(\"scroll\", measure);\n };\n }, [open, enabled, root, input]);\n return position;\n}\n\nfunction SearchTypeIcon({ kind }: { kind: string }) {\n const type = TYPE_ICONS[kind];\n return <HugeiconsIcon icon={type?.icon ?? Search01Icon} strokeWidth={2} className={cn(\"size-4 shrink-0\", type?.color ?? \"text-muted-foreground\")} data-search-kind={kind} aria-hidden />;\n}\n\n/**\n * Matching recent queries first, followed by entity/title substrings. Prefixes\n * are preferred. Enter searches or opens the chosen suggestion; full search can\n * match contextual fields more deeply.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t, error } = useMessages();\n const id = useId();\n const navigate = useContext(ContentKitContext)?.navigate;\n const [text, setText] = useState(p.defaultValue ?? \"\");\n const [open, setOpen] = useState(false);\n // null requests the first result when opening an as-yet-unloaded panel.\n const [active, setActive] = useState<string | null>();\n const root = useRef<HTMLDivElement>(null);\n const panel = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const header = p.presentation === \"header\";\n const anchor = useHeaderAnchor(open, header, root, input);\n const { top } = anchor;\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, entity_kinds: p.groupKinds, client: p.client, request: p.requestSuggestions, minLength: p.disabled ? Infinity : 1 });\n const recent = useRecentSearches({ enabled: p.recent !== false && open && !p.disabled, client: p.client });\n const normalized = searchText(text);\n const current = found.query === text.trim();\n const visible = useMemo<Suggestion[]>(\n () => [\n ...(p.recent === false ? [] : recent.searches\n .map((r) => ({ ...r, normalized: searchText(r.query) }))\n .filter((r) => empty || (normalized && r.normalized.includes(normalized)))\n .sort((a, b) => Number(b.normalized.startsWith(normalized)) - Number(a.normalized.startsWith(normalized)))\n .map((r) => ({ type: \"recent\" as const, query: r.query }))),\n ...(!empty && current ? [\n ...found.entities.map((group) => ({ type: \"group\" as const, group })),\n ...found.contents.map((card) => ({ type: \"content\" as const, card })),\n ] : []),\n ],\n [p.recent, normalized, empty, current, recent.searches, found.contents, found.entities],\n );\n // History and live suggestions can arrive independently. Track identity so\n // prepending history never changes what Enter will open.\n const activeIndex = active === null ? Math.min(0, visible.length - 1) : visible.findIndex((s) => suggestionKey(s) === active);\n const selected = visible[activeIndex];\n const activate = (index: number) => {\n const suggestion = visible[index];\n setActive(suggestion ? suggestionKey(suggestion) : index >= 0 ? null : undefined);\n };\n const loading = recent.loading || (!empty && (!current || found.loading));\n const showRecent = empty && p.recent !== false;\n const emptyMessage = empty ? !header && showRecent && !recent.error ? \"contents.noRecentSearches\" : \"contents.searchHint\" : \"contents.noResults\";\n useEffect(() => {\n if (!open) return;\n const outside = (event: PointerEvent) => {\n if (!root.current?.contains(event.target as Node) && !panel.current?.contains(event.target as Node)) setOpen(false);\n };\n document.addEventListener(\"pointerdown\", outside);\n return () => document.removeEventListener(\"pointerdown\", outside);\n }, [open]);\n const reveal = () => {\n if (!p.disabled) setOpen(true);\n };\n const search = (q: string) => {\n setOpen(false);\n if (q) p.onSearch(q);\n };\n const go = (s: Suggestion) => {\n if (s.type === \"recent\") {\n setText(s.query);\n return search(s.query);\n }\n const url = p.href ? p.href(s.type === \"content\" ? s.card : s.group) : s.type === \"content\" ? s.card.path : s.group.path;\n setOpen(false);\n if (!url) return;\n if (navigate) navigate(url);\n else location.assign(url);\n };\n const panelKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n input.current?.focus();\n setOpen(false);\n setActive(undefined);\n };\n const results = (<>\n {showRecent && (\n <div className={cn(\"flex items-center justify-between gap-2 text-muted-foreground\", header ? \"mb-4 text-base font-semibold\" : \"px-2 py-1 text-xs\")}>\n <span id={`${id}-recent`} className=\"flex items-center gap-2\">{header && <HugeiconsIcon icon={Clock01Icon} strokeWidth={2} className=\"size-5\" aria-hidden />}{t(\"contents.recentSearches\")}</span>\n {visible.length > 0 && <button\n type=\"button\"\n className=\"rounded px-1 hover:text-foreground\"\n onMouseDown={(e) => {\n e.preventDefault();\n }}\n onClick={() => void recent.clear()}\n >\n {t(header ? \"contents.clearAllRecent\" : \"contents.clearRecent\")}\n </button>}\n </div>\n )}\n <ul id={`${id}-list`} role=\"listbox\" aria-label={empty ? t(\"contents.recentSearches\") : t(\"contents.suggestions\")} className={cn(\"grid\", header && \"gap-2\")}>\n {visible.map((s, i) => (\n <li\n key={suggestionKey(s)}\n id={`${id}-${i}`}\n role=\"option\"\n aria-label={header && s.type !== \"recent\" ? `${labelOf(p.labels, s.type === \"content\" ? s.card.kind : s.group.kind)} ${s.type === \"content\" ? s.card.title || s.card.code : s.group.name || s.group.slug}` : undefined}\n aria-selected={i === activeIndex}\n data-ckui=\"suggestion\"\n data-type={s.type}\n className={cn(\"flex cursor-pointer items-center\", header ? s.type === \"recent\" ? \"gap-2 rounded-md p-3 text-base hover:bg-foreground/10\" : \"gap-3 rounded-md px-4 py-1 text-base font-medium hover:bg-foreground/10\" : \"gap-2 px-2 py-1.5\", i === activeIndex && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(suggestionKey(s))}\n onMouseDown={(e) => {\n e.preventDefault();\n go(s);\n }}\n >\n {header && s.type !== \"recent\" ? (\n <>\n <SearchTypeIcon kind={s.type === \"content\" ? s.card.kind : s.group.kind} />\n <span className=\"sr-only\">{labelOf(p.labels, s.type === \"content\" ? s.card.kind : s.group.kind)}</span>\n <span className=\"min-w-0 flex-1 truncate\">{s.type === \"content\" ? s.card.title || s.card.code : s.group.name || s.group.slug}</span>\n </>\n ) : s.type === \"content\" ? (\n <>\n <span className=\"h-10 w-8 shrink-0 overflow-hidden rounded bg-muted\">\n <RenditionImg outputs={s.card.cover} alt=\"\" fit=\"cover\" className=\"size-full\" />\n </span>\n <span className=\"min-w-0 truncate\">{s.card.title || s.card.code}</span>\n </>\n ) : s.type === \"group\" ? (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{s.group.name || s.group.slug}</span>\n <span className=\"text-xs text-muted-foreground\">{labelOf(p.labels, s.group.kind)}</span>\n </>\n ) : (\n <>\n <HugeiconsIcon icon={header ? Search01Icon : Clock01Icon} strokeWidth={2} className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 truncate\">{s.query}</span>\n <button\n type=\"button\"\n className=\"rounded p-0.5 text-muted-foreground hover:text-foreground\"\n aria-label={t(\"contents.removeRecent\", { q: s.query })}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n onClick={() => void recent.clear(s.query)}\n >\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n </button>\n </>\n )}\n </li>\n ))}\n </ul>\n {loading ? <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex justify-center py-4\" : \"px-3 py-2\")}>{header ? <><span className=\"size-6 animate-spin rounded-full border-2 border-muted border-t-foreground motion-reduce:animate-none\" aria-hidden /><span className=\"sr-only\">{t(\"contents.loading\")}</span></> : t(\"contents.loading\")}</div>\n : !empty && found.error ? <p role=\"alert\" className={cn(\"px-3 py-2 text-destructive\", header && \"text-center\")}>{error(found.error)}</p>\n : visible.length === 0 && <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex flex-col items-center gap-2 py-8 text-center\" : \"px-3 py-2\")}>{header && <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-8\" aria-hidden />}<p>{t(emptyMessage, { q: text.trim() })}</p></div>}\n </>);\n return (\n <ContentKitUiRoot ref={root} appearance={p.appearance} className={cn(\"relative text-sm\", p.className)} data-ckui=\"search-box\" onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget) && !panel.current?.contains(event.relatedTarget)) setOpen(false); }}>\n <form\n role=\"search\"\n className=\"relative\"\n onSubmit={(e) => {\n e.preventDefault();\n if (p.disabled) return;\n if (open && selected) return go(selected);\n if (p.selectionOnly) return;\n search(text.trim());\n }}\n >\n {header ? <button type=\"submit\" disabled={p.disabled} className=\"absolute top-1/2 left-2.5 -translate-y-1/2 rounded text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\" aria-label={t(\"contents.search\")}><HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n : <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\" aria-hidden />}\n <Input\n ref={input}\n type={header ? \"text\" : \"search\"}\n role=\"combobox\"\n aria-label={t(\"contents.search\")}\n aria-expanded={open}\n aria-controls={`${id}-list`}\n aria-activedescendant={open && selected ? `${id}-${activeIndex}` : undefined}\n aria-autocomplete=\"list\"\n autoComplete=\"off\"\n disabled={p.disabled}\n className={cn(\"h-9 ps-8\", header && \"rounded-full border-border/50 bg-foreground/10 pe-9 text-base shadow-none md:text-base\")}\n value={text}\n placeholder={p.placeholder ?? t(\"contents.searchPlaceholder\")}\n onChange={(e) => {\n setText(e.target.value);\n setOpen(true);\n setActive(undefined);\n }}\n onFocus={reveal}\n onClick={reveal}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n setActive(undefined);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n reveal();\n activate(open ? Math.min(visible.length - 1, activeIndex + 1) : 0);\n } else if (e.key === \"ArrowUp\" && open) {\n e.preventDefault();\n activate(Math.max(-1, activeIndex - 1));\n }\n }}\n />\n {header && (text || open) && (\n <button\n type=\"button\"\n disabled={p.disabled}\n className=\"absolute top-1/2 right-2.5 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\"\n aria-label={text ? t(\"contents.clearSearch\") : t(\"common.close\")}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n input.current?.focus();\n setActive(undefined);\n if (text) { setText(\"\"); setOpen(true); }\n else setOpen(false);\n }}\n ><HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n )}\n </form>\n {open && (header ? createPortal(\n <ContentKitUiRoot appearance={p.appearance} className=\"fixed inset-x-0 bottom-0 z-[70] text-sm\" style={{ top }} data-ckui=\"search-layer\">\n <div className=\"absolute inset-0 bg-black/30\" data-ckui=\"search-backdrop\" aria-hidden />\n <div ref={panel} style={{ width: anchor.width, marginLeft: anchor.left }} className=\"relative origin-top animate-[search-expand_300ms_ease-out] rounded-lg border border-border bg-background text-foreground shadow-lg transition-none motion-reduce:animate-none\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>\n <div className=\"w-full px-3 pt-3\">\n <div className=\"[--search-max-height:70vh] overflow-y-auto pb-7 md:[--search-max-height:60vh]\" style={{ maxHeight: `min(var(--search-max-height), calc(100dvh - ${top}px - 1.875rem))` }}>\n <div>{results}</div>\n </div>\n </div>\n </div>\n </ContentKitUiRoot>, document.body,\n ) : <div ref={panel} className=\"absolute top-full z-20 mt-1 grid max-h-96 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>{results}</div>)}\n\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AA4CA,IAAM,KAAiB,MAAkB,EAAE,SAAS,YAAY,KAAK,EAAE,KAAK,OAAO,EAAE,SAAS,UAAU,KAAK,EAAE,MAAM,OAAO,KAAK,EAAE,SAI7H,KAAc,MAAiB,EAAK,UAAU,MAAM,CAAC,CAAC,QAAQ,oBAAoB,EAAE,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,YAAY,CAAC,CACvH,QAAQ,kEAAkE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,QAAQ,GAAG,GAEvG,IAAsE;CAC1E,QAAQ;EAAE,MAAM;EAAU,OAAO;CAAmC;CACpE,QAAQ;EAAE,MAAM;EAAc,OAAO;CAAuC;CAC5E,SAAS;EAAE,MAAM;EAAa,OAAO;CAAqC;CAC1E,MAAM;EAAE,MAAM;EAAY,OAAO;CAAuC;CACxE,MAAM;EAAE,MAAM;EAAY,OAAO;CAAuC;CACxE,MAAM;EAAE,MAAM;EAAY,OAAO;CAAuC;CACxE,WAAW;EAAE,MAAM;EAAU,OAAO;CAAuC;CAC3E,KAAK;EAAE,MAAM;EAAU,OAAO;CAAuC;AACvE;AAEA,SAAS,GAAgB,GAAe,GAAkB,GAAwC,GAA2C;CAC3I,IAAM,CAAC,GAAU,KAAe,EAAS;EAAE,KAAK;EAAG,MAAM;EAAG,OAAO;CAAE,CAAC;CAyBtE,OAxBA,QAAsB;EACpB,IAAI,CAAC,KAAQ,CAAC,KAAW,CAAC,EAAK,SAAS;EACxC,IAAM,IAAS,EAAM,WAAW,EAAK,SAC/B,IAAO,EAAK,QAAQ,cAAc;EACxC,IAAI,CAAC,GAAM;EACX,IAAM,UAAgB;GACpB,IAAM,IAAO,EAAO,sBAAsB;GAC1C,EAAY;IAAE,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,EAAK,aAAa,EAAK,SAAS,CAAC,CAAC;IAAG,MAAM,EAAK;IAAM,OAAO,EAAK;GAAM,CAAC;EACnH;EACA,EAAQ;EACR,IAAM,IAAW,OAAO,iBAAmB,MAAc,KAAA,IAAY,IAAI,eAAe,CAAO;EAM/F,OALA,GAAU,QAAQ,CAAM,GACxB,EAAK,iBAAiB,UAAU,CAAO,GACvC,EAAK,iBAAiB,UAAU,GAAS,EAAI,GAC7C,EAAK,gBAAgB,iBAAiB,UAAU,CAAO,GACvD,EAAK,gBAAgB,iBAAiB,UAAU,CAAO,SAC1C;GAKX,AAJA,GAAU,WAAW,GACrB,EAAK,oBAAoB,UAAU,CAAO,GAC1C,EAAK,oBAAoB,UAAU,GAAS,EAAI,GAChD,EAAK,gBAAgB,oBAAoB,UAAU,CAAO,GAC1D,EAAK,gBAAgB,oBAAoB,UAAU,CAAO;EAC5D;CACF,GAAG;EAAC;EAAM;EAAS;EAAM;CAAK,CAAC,GACxB;AACT;AAEA,SAAS,EAAe,EAAE,WAA0B;CAClD,IAAM,IAAO,EAAW;CACxB,OAAO,kBAAC,GAAD;EAAe,MAAM,GAAM,QAAQ;EAAc,aAAa;EAAG,WAAW,EAAG,mBAAmB,GAAM,SAAS,uBAAuB;EAAG,oBAAkB;EAAM,eAAA;CAAa,CAAA;AACzL;AAOA,SAAgB,EAAU,GAAmB;CAC3C,IAAM,EAAE,MAAG,aAAU,EAAY,GAC3B,IAAK,GAAM,GACX,IAAW,EAAW,CAAiB,CAAC,EAAE,UAC1C,CAAC,GAAM,KAAW,EAAS,EAAE,gBAAgB,EAAE,GAC/C,CAAC,GAAM,KAAW,EAAS,EAAK,GAEhC,CAAC,GAAQ,KAAa,EAAwB,GAC9C,IAAO,EAAuB,IAAI,GAClC,IAAQ,EAAuB,IAAI,GACnC,IAAQ,EAAyB,IAAI,GACrC,IAAS,EAAE,iBAAiB,UAC5B,IAAS,GAAgB,GAAM,GAAQ,GAAM,CAAK,GAClD,EAAE,WAAQ,GACV,IAAQ,CAAC,EAAK,KAAK,GACnB,IAAQ,EAAa,GAAM;EAAE,MAAM,EAAE;EAAM,cAAc,EAAE;EAAY,QAAQ,EAAE;EAAQ,SAAS,EAAE;EAAoB,WAAW,EAAE,WAAW,WAAW;CAAE,CAAC,GAC9J,IAAS,EAAkB;EAAE,SAAS,EAAE,WAAW,MAAS,KAAQ,CAAC,EAAE;EAAU,QAAQ,EAAE;CAAO,CAAC,GACnG,IAAa,EAAW,CAAI,GAC5B,IAAU,EAAM,UAAU,EAAK,KAAK,GACpC,IAAU,SACR,CACJ,GAAI,EAAE,WAAW,KAAQ,CAAC,IAAI,EAAO,SAClC,KAAK,OAAO;EAAE,GAAG;EAAG,YAAY,EAAW,EAAE,KAAK;CAAE,EAAE,CAAC,CACvD,QAAQ,MAAM,KAAU,KAAc,EAAE,WAAW,SAAS,CAAU,CAAE,CAAC,CACzE,MAAM,GAAG,MAAM,OAAO,EAAE,WAAW,WAAW,CAAU,CAAC,IAAI,OAAO,EAAE,WAAW,WAAW,CAAU,CAAC,CAAC,CAAC,CACzG,KAAK,OAAO;EAAE,MAAM;EAAmB,OAAO,EAAE;CAAM,EAAE,GAC3D,GAAI,CAAC,KAAS,IAAU,CACtB,GAAG,EAAM,SAAS,KAAK,OAAW;EAAE,MAAM;EAAkB;CAAM,EAAE,GACpE,GAAG,EAAM,SAAS,KAAK,OAAU;EAAE,MAAM;EAAoB;CAAK,EAAE,CACtE,IAAI,CAAC,CACP,GACA;EAAC,EAAE;EAAQ;EAAY;EAAO;EAAS,EAAO;EAAU,EAAM;EAAU,EAAM;CAAQ,CACxF,GAGM,IAAc,MAAW,OAAO,KAAK,IAAI,GAAG,EAAQ,SAAS,CAAC,IAAI,EAAQ,WAAW,MAAM,EAAc,CAAC,MAAM,CAAM,GACtH,IAAW,EAAQ,IACnB,KAAY,MAAkB;EAClC,IAAM,IAAa,EAAQ;EAC3B,EAAU,IAAa,EAAc,CAAU,IAAI,KAAS,IAAI,OAAO,KAAA,CAAS;CAClF,GACM,KAAU,EAAO,WAAY,CAAC,MAAU,CAAC,KAAW,EAAM,UAC1D,IAAa,KAAS,EAAE,WAAW,IACnC,KAAe,IAAQ,CAAC,KAAU,KAAc,CAAC,EAAO,QAAQ,8BAA8B,wBAAwB;CAC5H,SAAgB;EACd,IAAI,CAAC,GAAM;EACX,IAAM,KAAW,MAAwB;GACvC,AAAI,CAAC,EAAK,SAAS,SAAS,EAAM,MAAc,KAAK,CAAC,EAAM,SAAS,SAAS,EAAM,MAAc,KAAG,EAAQ,EAAK;EACpH;EAEA,OADA,SAAS,iBAAiB,eAAe,CAAO,SACnC,SAAS,oBAAoB,eAAe,CAAO;CAClE,GAAG,CAAC,CAAI,CAAC;CACT,IAAM,UAAe;EACnB,AAAK,EAAE,YAAU,EAAQ,EAAI;CAC/B,GACM,KAAU,MAAc;EAE5B,AADA,EAAQ,EAAK,GACT,KAAG,EAAE,SAAS,CAAC;CACrB,GACM,KAAM,MAAkB;EAC5B,IAAI,EAAE,SAAS,UAEb,OADA,EAAQ,EAAE,KAAK,GACR,EAAO,EAAE,KAAK;EAEvB,IAAM,IAAM,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,YAAY,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM;EACpH,EAAQ,EAAK,GACR,MACD,IAAU,EAAS,CAAG,IACrB,SAAS,OAAO,CAAG;CAC1B,GACM,KAAgB,MAAyB;EACzC,EAAM,QAAQ,aAClB,EAAM,eAAe,GACrB,EAAM,SAAS,MAAM,GACrB,EAAQ,EAAK,GACb,EAAU,KAAA,CAAS;CACrB,GACM,IAAW,kBAAA,GAAA,EAAA,UAAA;EACd,KACC,kBAAC,OAAD;GAAK,WAAW,EAAG,iEAAiE,IAAS,iCAAiC,mBAAmB;GAAjJ,UAAA,CACE,kBAAC,QAAD;IAAM,IAAI,GAAG,EAAG;IAAU,WAAU;IAApC,UAAA,CAA+D,KAAU,kBAAC,GAAD;KAAe,MAAM;KAAa,aAAa;KAAG,WAAU;KAAS,eAAA;IAAa,CAAA,GAAG,EAAE,yBAAyB,CAAQ;GAChM,CAAA,GAAA,EAAQ,SAAS,KAAK,kBAAC,UAAD;IACrB,MAAK;IACL,WAAU;IACV,cAAc,MAAM;KAClB,EAAE,eAAe;IACnB;IACA,eAAe,KAAK,EAAO,MAAM;IAEhC,UAAA,EAAE,IAAS,4BAA4B,sBAAsB;GACxD,CAAA,CACL;;EAEP,kBAAC,MAAD;GAAI,IAAI,GAAG,EAAG;GAAQ,MAAK;GAAU,cAAoB,EAAR,IAAU,4BAA+B,sBAAsB;GAAG,WAAW,EAAG,QAAQ,KAAU,OAAO;GACvJ,UAAA,EAAQ,KAAK,GAAG,MACf,kBAAC,MAAD;IAEE,IAAI,GAAG,EAAG,GAAG;IACb,MAAK;IACL,cAAY,KAAU,EAAE,SAAS,WAAW,GAAG,EAAQ,EAAE,QAAQ,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,IAAI,EAAE,GAAG,EAAE,SAAS,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,SAAS,KAAA;IAC7M,iBAAe,MAAM;IACrB,aAAU;IACV,aAAW,EAAE;IACb,WAAW,EAAG,oCAAoC,IAAS,EAAE,SAAS,WAAW,0DAA0D,4EAA4E,qBAAqB,MAAM,KAAe,kCAAkC;IACnS,oBAAoB,EAAU,EAAc,CAAC,CAAC;IAC9C,cAAc,MAAM;KAElB,AADA,EAAE,eAAe,GACjB,EAAG,CAAC;IACN;IAEC,UAAA,KAAU,EAAE,SAAS,WACpB,kBAAA,GAAA,EAAA,UAAA;KACE,kBAAC,GAAD,EAAgB,MAAM,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,KAAO,CAAA;KAC1E,kBAAC,QAAD;MAAM,WAAU;MAAW,UAAA,EAAQ,EAAE,QAAQ,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,IAAI;KAAQ,CAAA;KACtG,kBAAC,QAAD;MAAM,WAAU;MAA2B,UAAA,EAAE,SAAS,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM;KAAW,CAAA;IACnI,EAAA,CAAA,IACA,EAAE,SAAS,YACb,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;KACd,UAAA,kBAAC,GAAD;MAAc,SAAS,EAAE,KAAK;MAAO,KAAI;MAAG,KAAI;MAAQ,WAAU;KAAa,CAAA;IAC3E,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;KAAoB,UAAA,EAAE,KAAK,SAAS,EAAE,KAAK;IAAW,CAAA,CACtE,EAAA,CAAA,IACA,EAAE,SAAS,UACb,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;KAA2B,UAAA,EAAE,MAAM,QAAQ,EAAE,MAAM;IAAW,CAAA,GAC9E,kBAAC,QAAD;KAAM,WAAU;KAAiC,UAAA,EAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI;IAAQ,CAAA,CACvF,EAAA,CAAA,IAEF,kBAAA,GAAA,EAAA,UAAA;KACE,kBAAC,GAAD;MAAe,MAAM,IAAS,IAAe;MAAa,aAAa;MAAG,WAAU;MAAwC,eAAA;KAAa,CAAA;KACzI,kBAAC,QAAD;MAAM,WAAU;MAA2B,UAAA,EAAE;KAAY,CAAA;KACzD,kBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,cAAY,EAAE,yBAAyB,EAAE,GAAG,EAAE,MAAM,CAAC;MACrD,cAAc,MAAM;OAElB,AADA,EAAE,eAAe,GACjB,EAAE,gBAAgB;MACpB;MACA,eAAe,KAAK,EAAO,MAAM,EAAE,KAAK;MAExC,UAAA,kBAAC,GAAD;OAAe,MAAM;OAAc,aAAa;OAAG,WAAU;OAAW,eAAA;MAAa,CAAA;KAC/E,CAAA;IACR,EAAA,CAAA;GAEF,GAlDG,EAAc,CAAC,CAkDlB,CACL;EACC,CAAA;EACH,KAAU,kBAAC,OAAD;GAAK,MAAK;GAAS,WAAW,EAAG,yBAAyB,IAAS,6BAA6B,WAAW;GAAI,UAAA,IAAS,kBAAA,GAAA,EAAA,UAAA,CAAE,kBAAC,QAAD;IAAM,WAAU;IAAwG,eAAA;GAAa,CAAA,GAAC,kBAAC,QAAD;IAAM,WAAU;IAAW,UAAA,EAAE,kBAAkB;GAAQ,CAAA,CAAG,EAAA,CAAA,IAAI,EAAE,kBAAkB;EAAO,CAAA,IAChW,CAAC,KAAS,EAAM,QAAQ,kBAAC,KAAD;GAAG,MAAK;GAAQ,WAAW,EAAG,8BAA8B,KAAU,aAAa;GAAI,UAAA,EAAM,EAAM,KAAK;EAAK,CAAA,IACrI,EAAQ,WAAW,KAAK,kBAAC,OAAD;GAAK,MAAK;GAAS,WAAW,EAAG,yBAAyB,IAAS,sDAAsD,WAAW;GAApI,UAAA,CAAwI,KAAU,kBAAC,GAAD;IAAe,MAAM;IAAc,aAAa;IAAG,WAAU;IAAS,eAAA;GAAa,CAAA,GAAE,kBAAC,KAAD,EAAA,UAAI,EAAE,IAAc,EAAE,GAAG,EAAK,KAAK,EAAE,CAAC,EAAK,CAAA,CAAM;;CACpT,EAAA,CAAA;CACF,OACE,kBAAC,GAAD;EAAkB,KAAK;EAAM,YAAY,EAAE;EAAY,WAAW,EAAG,oBAAoB,EAAE,SAAS;EAAG,aAAU;EAAa,SAAS,MAAU;GAAE,AAAI,CAAC,EAAM,cAAc,SAAS,EAAM,aAAa,KAAK,CAAC,EAAM,SAAS,SAAS,EAAM,aAAa,KAAG,EAAQ,EAAK;EAAG;EAA5Q,UAAA,CACE,kBAAC,QAAD;GACE,MAAK;GACL,WAAU;GACV,WAAW,MAAM;IACf,MAAE,eAAe,GACb,GAAE,UACN;SAAI,KAAQ,GAAU,OAAO,EAAG,CAAQ;KACpC,EAAE,iBACN,EAAO,EAAK,KAAK,CAAC;IAFsB;GAG1C;GATF,UAAA;IAWG,IAAS,kBAAC,UAAD;KAAQ,MAAK;KAAS,UAAU,EAAE;KAAU,WAAU;KAAoJ,cAAY,EAAE,iBAAiB;KAAG,UAAA,kBAAC,GAAD;MAAe,MAAM;MAAc,aAAa;MAAG,WAAU;MAAS,eAAA;KAAa,CAAA;IAAS,CAAA,IAC9U,kBAAC,GAAD;KAAe,MAAM;KAAc,aAAa;KAAG,WAAU;KAA8F,eAAA;IAAa,CAAA;IAC5K,kBAAC,GAAD;KACE,KAAK;KACL,MAAM,IAAS,SAAS;KACxB,MAAK;KACL,cAAY,EAAE,iBAAiB;KAC/B,iBAAe;KACf,iBAAe,GAAG,EAAG;KACrB,yBAAuB,KAAQ,IAAW,GAAG,EAAG,GAAG,MAAgB,KAAA;KACnE,qBAAkB;KAClB,cAAa;KACb,UAAU,EAAE;KACZ,WAAW,EAAG,YAAY,KAAU,wFAAwF;KAC5H,OAAO;KACP,aAAa,EAAE,eAAe,EAAE,4BAA4B;KAC5D,WAAW,MAAM;MAGf,AAFA,EAAQ,EAAE,OAAO,KAAK,GACtB,EAAQ,EAAI,GACZ,EAAU,KAAA,CAAS;KACrB;KACA,SAAS;KACT,SAAS;KACT,YAAY,MAAM;MAChB,IAAI,EAAE,QAAQ,UAAU;OAGtB,AAFA,EAAE,eAAe,GACjB,EAAQ,EAAK,GACb,EAAU,KAAA,CAAS;OACnB;MACF;MACA,AAAI,EAAE,QAAQ,eACZ,EAAE,eAAe,GACjB,EAAO,GACP,EAAS,IAAO,KAAK,IAAI,EAAQ,SAAS,GAAG,IAAc,CAAC,IAAI,CAAC,KACxD,EAAE,QAAQ,aAAa,MAChC,EAAE,eAAe,GACjB,EAAS,KAAK,IAAI,IAAI,IAAc,CAAC,CAAC;KAE1C;IACD,CAAA;IACA,MAAW,KAAQ,MAClB,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAE;KACZ,WAAU;KACV,cAAmB,EAAP,IAAS,yBAA4B,cAAc;KAC/D,cAAc,MAAU,EAAM,eAAe;KAC7C,eAAe;MAGb,AAFA,EAAM,SAAS,MAAM,GACrB,EAAU,KAAA,CAAS,GACf,KAAQ,EAAQ,EAAE,GAAG,EAAQ,EAAI,KAChC,EAAQ,EAAK;KACpB;KACD,UAAA,kBAAC,GAAD;MAAe,MAAM;MAAc,aAAa;MAAG,WAAU;MAAS,eAAA;KAAa,CAAA;IAAS,CAAA;GAE3F;EACL,CAAA,GAAA,MAAS,IAAS,GACjB,kBAAC,GAAD;GAAkB,YAAY,EAAE;GAAY,WAAU;GAA0C,OAAO,EAAE,OAAI;GAAG,aAAU;GAA1H,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;IAA+B,aAAU;IAAkB,eAAA;GAAa,CAAA,GACvF,kBAAC,OAAD;IAAK,KAAK;IAAO,OAAO;KAAE,OAAO,EAAO;KAAO,YAAY,EAAO;IAAK;IAAG,WAAU;IAAgL,aAAU;IAAe,WAAW;IACtS,UAAA,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,OAAD;MAAK,WAAU;MAAgF,OAAO,EAAE,WAAW,+CAA+C,EAAI,iBAAiB;MACrL,UAAA,kBAAC,OAAD,EAAA,UAAM,EAAa,CAAA;KAChB,CAAA;IACF,CAAA;GACF,CAAA,CACW;EAAG,CAAA,GAAA,SAAS,IAChC,IAAI,kBAAC,OAAD;GAAK,KAAK;GAAO,WAAU;GAAmJ,aAAU;GAAe,WAAW;GAAe,UAAA;EAAa,CAAA,EAElO;;AAEtB"}
@@ -0,0 +1,14 @@
1
+ import { MouseEvent } from 'react';
2
+ import { Card } from '../../client/generated/wire.js';
3
+ import { CardListProps } from './grid.js';
4
+ /** One cover-sized control shared by complete lists and daily release sections. */
5
+ export declare function ContentSummaryTile({ tileKey, kind, contentKind, preview, open, label, onClick, cardProps: p, }: {
6
+ tileKey: string;
7
+ kind: "premium" | "filtered";
8
+ contentKind?: string;
9
+ preview?: Card;
10
+ open: boolean;
11
+ label: string;
12
+ onClick: (event: MouseEvent<HTMLButtonElement>) => void;
13
+ cardProps: CardListProps;
14
+ }): import('react').JSX.Element;
@@ -0,0 +1,63 @@
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 { usePresets as r } from "../../react/public.js";
5
+ import { Button as i } from "../../ui/button.js";
6
+ import { useNearViewport as a } from "../../react/viewport.js";
7
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
8
+ import { LockIcon as c, Settings01Icon as l } from "@hugeicons/core-free-icons";
9
+ import { HugeiconsIcon as u } from "@hugeicons/react";
10
+ //#region src/components/content/summary-tile.tsx
11
+ function d({ tileKey: d, kind: f, contentKind: p, preview: m, open: h, label: g, onClick: _, cardProps: v }) {
12
+ let { t: y } = n(), [b, x] = a({
13
+ margin: "0px",
14
+ leaveMargin: "1200px 0px"
15
+ }), { presets: S } = r({ client: v.client }), C = v.aspectRatio ?? t(S.find((e) => e.kind === p && e.name === "cover")?.aspect) ?? (v.shape === "landscape" ? "16 / 9" : "3 / 4");
16
+ return /* @__PURE__ */ s("li", {
17
+ ref: b,
18
+ "data-release-key": d,
19
+ children: [/* @__PURE__ */ s(i, {
20
+ type: "button",
21
+ variant: "outline",
22
+ "data-ckui": `${f}-release-tile`,
23
+ "aria-label": g,
24
+ "aria-expanded": f !== "premium" && void 0,
25
+ "aria-haspopup": f === "filtered" ? "dialog" : void 0,
26
+ className: "relative flex h-auto w-full flex-col justify-center overflow-hidden rounded-lg p-0 text-center whitespace-normal",
27
+ style: { aspectRatio: C },
28
+ onClick: _,
29
+ children: [m && h && x && /* @__PURE__ */ o(e, {
30
+ outputs: m.cover,
31
+ alt: "",
32
+ fit: "cover",
33
+ loading: "lazy",
34
+ className: "absolute inset-0 size-full opacity-20"
35
+ }), /* @__PURE__ */ s("span", {
36
+ className: "relative grid justify-items-center gap-3 p-4",
37
+ children: [
38
+ /* @__PURE__ */ o(u, {
39
+ icon: f === "premium" ? c : l,
40
+ strokeWidth: 2,
41
+ className: "size-8",
42
+ "aria-hidden": !0
43
+ }),
44
+ /* @__PURE__ */ o("span", {
45
+ className: "text-xl font-semibold",
46
+ children: g
47
+ }),
48
+ /* @__PURE__ */ o("span", {
49
+ className: "text-sm text-muted-foreground",
50
+ children: y(f === "premium" ? "releases.premiumHint" : "releases.manageFilters")
51
+ })
52
+ ]
53
+ })]
54
+ }), m && /* @__PURE__ */ o("p", {
55
+ className: "mt-2 line-clamp-2 text-lg leading-snug text-muted-foreground",
56
+ children: m.title
57
+ })]
58
+ });
59
+ }
60
+ //#endregion
61
+ export { d as ContentSummaryTile };
62
+
63
+ //# sourceMappingURL=summary-tile.js.map