{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "release-row",
  "title": "Release Row",
  "author": "AniBT <https://anibt.net>",
  "description": "AniBT release row: a die-cut cover, the full title over the publisher, episode and three coloured tags, then size, swarm and age columns and always-visible icon squares; wide, stacked and compact forms by container width.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@anibt/theme",
    "@anibt/badge",
    "@anibt/button",
    "@anibt/tooltip"
  ],
  "files": [
    {
      "path": "registry/anibt/blocks/release-row/release-row.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowDownIcon, ArrowUpIcon, CheckIcon, DownloadIcon, MagnetIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Badge } from \"@/registry/anibt/ui/badge\"\nimport { Button } from \"@/registry/anibt/ui/button\"\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/registry/anibt/ui/tooltip\"\n\n/**\n * ReleaseRow — one release as one row (AniBT 「贴纸手账」, after anibt.net's\n * 最新资源 list).\n *\n * **Wide form** — when its `ReleaseRowList` is at least 760px wide (a\n * container query, so a row in a narrow column keeps the phone form): a 40×56\n * cover with the die-cut rim that tips under the pointer, the title over the\n * facts, then size, swarm and age as three columns a reader scans down, then\n * the 32px icon squares. The title wraps in full and is never cut — the\n * release name is what a reader picks a version by.\n *\n * **The facts line** opens with the publisher in pink, then 第 N 集 and at most\n * three coloured tags — resolution (blue), the languages folded into one pink\n * tag, the subtitle type (neutral) — then the remaining specs as plain muted\n * text from 768px up.\n *\n * **Phone form** (narrower lists, and `form=\"stacked\"` at every width): the\n * cover beside the title and facts, then one line of figures with 40px squares\n * (44px targets) at its end. **Compact** (`form=\"compact\"`): no cover, no\n * publisher, the title on one line from 1024, size over age in a 92px column.\n *\n * The title link (`href`, or `link={<Link href=\"…\" />}`) is stretched over the\n * row, so the whole row opens the release; the publisher link and the action\n * squares sit above it. Everything is a prop — the row fetches and formats\n * nothing: pass `fileSize=\"1.21 GB\"`, `time=\"今天 18:08\"` with its ISO\n * `dateTime`, and the swarm figures (`null` is \"not measured\" and prints a\n * dash, never a zero).\n */\n\ntype ReleaseRowLinkElement = React.ReactElement<{ className?: string; children?: React.ReactNode }>\n\ntype ReleaseRowPublisher = {\n  name: string\n  href?: string\n  link?: ReleaseRowLinkElement\n}\n\ntype ReleaseRowCover = {\n  src: string\n  srcSet?: string\n  alt?: string\n}\n\ntype ReleaseRowProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  title: React.ReactNode\n  /** The full title for the browser tooltip; defaults to `title` when it is a string. */\n  titleTooltip?: string\n  /** `lang` of the title (`ja` for a Japanese release name). */\n  titleLang?: string\n  /** Opens the release; stretched over the whole row. */\n  href?: string\n  /** A router link element instead of `href`. */\n  link?: ReleaseRowLinkElement\n  /** The anime's cover (shown at 40×56; give a ≥120w source for 3× screens). */\n  cover?: string | ReleaseRowCover | null\n  /** Above the fold: load the cover eagerly. */\n  priority?: boolean\n  publisher?: string | ReleaseRowPublisher | null\n  /** A category tag in front of the episode (合集, 剧场版…). */\n  category?: React.ReactNode\n  /** 第 12 集, in the round face. */\n  episode?: React.ReactNode\n  /** The blue tag: 1080p, 2160p. */\n  resolution?: string\n  /** Folded into one pink tag: 简中·繁中. */\n  languages?: string[]\n  /** The neutral tag: 内封字幕, 内嵌字幕. */\n  subtitleType?: string\n  /** Everything else, printed as plain muted text from 768px: AVC · WEB-DL · AAC · MKV. */\n  specs?: string[]\n  /** Pre-formatted size: 1.21 GB. */\n  fileSize?: string\n  /** Seeders; `null` / leaving it out prints a dash. */\n  seeders?: number | null\n  leechers?: number | null\n  /** Pre-formatted age: 今天 18:08, 3 小时前. */\n  time?: React.ReactNode\n  /** ISO date of `time`, for `<time dateTime>`. */\n  dateTime?: string\n  /** The magnet address: draws the copy square. `null` keeps a disabled square (the slot exists, this release has none). */\n  magnet?: string | null\n  /** The .torrent URL: draws the download square. `null` keeps a disabled square. */\n  torrentHref?: string | null\n  /** Called after the magnet is copied (show a toast here). */\n  onMagnetCopied?: () => void\n  /** Your own squares (`ReleaseRowAction`s); replaces the magnet / torrent pair. */\n  actions?: React.ReactNode\n  /** `wide` follows the list's width; `stacked` holds the phone form; `compact` is the dense panel row. */\n  form?: \"wide\" | \"stacked\" | \"compact\"\n}\n\n/** The list: a panel with the rows stitched together. `plain` drops the panel. */\nfunction ReleaseRowList({\n  className,\n  plain = false,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & { plain?: boolean }) {\n  return (\n    <TooltipProvider>\n      <div\n        data-slot=\"release-row-list\"\n        className={cn(\"@container/rr-list w-full min-w-0\", plain ? null : \"anibt-sheet px-1\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </TooltipProvider>\n  )\n}\n\n/** A date inside a list: a small blue date sticker on a stitched rule, with an optional sticker at the end. */\nfunction ReleaseRowDateRule({\n  label,\n  trailing,\n  className,\n  ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & { label: React.ReactNode; trailing?: React.ReactNode }) {\n  return (\n    <div\n      data-slot=\"release-row-date\"\n      className={cn(\"flex items-center gap-3 px-3.5 pt-4 pb-1 md:px-3\", className)}\n      {...props}\n    >\n      <Badge variant=\"sticker\" className=\"shrink-0 text-anibt-time-ink\" tilt={-2}>\n        {label}\n      </Badge>\n      <span aria-hidden=\"true\" className=\"anibt-stitch min-w-0 flex-1\" />\n      {trailing}\n    </div>\n  )\n}\n\nconst STRETCHED =\n  \"outline-hidden after:absolute after:inset-0 after:content-[''] focus-visible:after:rounded-[10px] focus-visible:after:outline-2 focus-visible:after:outline-ring focus-visible:after:outline-solid\"\n\nfunction ReleaseRow({\n  title,\n  titleTooltip,\n  titleLang,\n  href,\n  link,\n  cover,\n  priority = false,\n  publisher,\n  category,\n  episode,\n  resolution,\n  languages,\n  subtitleType,\n  specs,\n  fileSize,\n  seeders,\n  leechers,\n  time,\n  dateTime,\n  magnet,\n  torrentHref,\n  onMagnetCopied,\n  actions,\n  form = \"wide\",\n  className,\n  ...props\n}: ReleaseRowProps) {\n  const coverInfo = typeof cover === \"string\" ? { src: cover } : cover\n  const showCover = Boolean(coverInfo) && form !== \"compact\"\n  const pub = typeof publisher === \"string\" ? { name: publisher } : publisher\n  const tooltip = titleTooltip ?? (typeof title === \"string\" ? title : undefined)\n\n  const titleNode = link\n    ? React.cloneElement(link, { className: cn(STRETCHED, link.props.className), children: title })\n    : href\n      ? (\n          <a href={href} className={STRETCHED}>\n            {title}\n          </a>\n        )\n      : title\n\n  const squares =\n    actions ??\n    (magnet !== undefined || torrentHref !== undefined ? (\n      <>\n        {magnet !== undefined ? <ReleaseRowCopyAction value={magnet} onCopied={onMagnetCopied} /> : null}\n        {torrentHref !== undefined ? (\n          <ReleaseRowAction label=\"下载种子\" href={torrentHref ?? undefined} disabled={torrentHref === null} download>\n            <DownloadIcon />\n          </ReleaseRowAction>\n        ) : null}\n      </>\n    ) : null)\n\n  return (\n    <div\n      data-slot=\"release-row\"\n      data-form={form}\n      data-cover={showCover ? \"\" : undefined}\n      className={cn(\n        \"group/rr relative grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-2 px-3.5 py-3 [grid-template-areas:'m_m'_'s_a'] md:px-3\",\n        \"data-[cover]:grid-cols-[40px_minmax(0,1fr)_auto] data-[cover]:[grid-template-areas:'t_m_m'_'s_s_a']\",\n        // Rows of one list are stitched together, inset by the row's padding.\n        \"[[data-slot=release-row]+&]:before:pointer-events-none [[data-slot=release-row]+&]:before:absolute [[data-slot=release-row]+&]:before:inset-x-3.5 [[data-slot=release-row]+&]:before:top-0 [[data-slot=release-row]+&]:before:h-px [[data-slot=release-row]+&]:before:bg-(image:--anibt-stitch) [[data-slot=release-row]+&]:before:bg-size-[8px_1px] [[data-slot=release-row]+&]:before:bg-repeat-x [[data-slot=release-row]+&]:before:content-[''] md:[[data-slot=release-row]+&]:before:inset-x-3\",\n        // Wide: six tracks once the list is 760px wide.\n        \"@min-[760px]/rr-list:data-[form=wide]:grid-cols-[minmax(0,1fr)_80px_96px_76px_auto] @min-[760px]/rr-list:data-[form=wide]:gap-x-4 @min-[760px]/rr-list:data-[form=wide]:[grid-template-areas:none] @min-[760px]/rr-list:data-[form=wide]:data-[cover]:grid-cols-[40px_minmax(0,1fr)_80px_96px_76px_auto]\",\n        // Compact: one flex line.\n        \"data-[form=compact]:flex data-[form=compact]:min-h-[50px] data-[form=compact]:gap-3 data-[form=compact]:py-1.5 lg:data-[form=compact]:h-[50px]\",\n        className\n      )}\n      {...props}\n    >\n      {showCover && coverInfo ? (\n        <div\n          data-slot=\"release-row-thumb\"\n          className=\"anibt-rim relative h-14 w-10 self-start overflow-hidden rounded-[6px] bg-muted transition-transform duration-300 ease-anibt-spring [grid-area:t] motion-safe:group-hover/rr:scale-[1.04] motion-safe:group-hover/rr:-rotate-[4deg] @min-[760px]/rr-list:group-data-[form=wide]/rr:self-center @min-[760px]/rr-list:group-data-[form=wide]/rr:[grid-area:auto]\"\n        >\n          {/* eslint-disable-next-line @next/next/no-img-element -- a registry component: no framework image */}\n          <img\n            src={coverInfo.src}\n            srcSet={coverInfo.srcSet}\n            sizes={coverInfo.srcSet ? \"40px\" : undefined}\n            alt={coverInfo.alt ?? \"\"}\n            width={40}\n            height={56}\n            loading={priority ? \"eager\" : \"lazy\"}\n            fetchPriority={priority ? \"high\" : undefined}\n            decoding=\"async\"\n            className=\"absolute inset-0 size-full object-cover\"\n          />\n        </div>\n      ) : null}\n\n      <div\n        data-slot=\"release-row-main\"\n        className=\"grid min-w-0 gap-1.5 self-start [grid-area:m] group-data-[form=compact]/rr:flex-1 group-data-[form=compact]/rr:gap-0.5 group-data-[form=compact]/rr:self-center @min-[760px]/rr-list:group-data-[form=wide]/rr:self-center @min-[760px]/rr-list:group-data-[form=wide]/rr:[grid-area:auto]\"\n      >\n        <h3\n          data-slot=\"release-row-title\"\n          title={tooltip}\n          lang={titleLang}\n          className=\"m-0 min-w-0 text-[14px] leading-5 font-semibold [overflow-wrap:anywhere] text-foreground transition-colors duration-150 group-hover/rr:text-accent-foreground group-data-[form=compact]/rr:text-[13px] group-data-[form=compact]/rr:leading-[18px] lg:group-data-[form=compact]/rr:anibt-ell\"\n        >\n          {titleNode}\n        </h3>\n        <ReleaseRowFacts\n          publisher={form === \"compact\" ? null : pub}\n          category={category}\n          episode={episode}\n          resolution={resolution}\n          languages={languages}\n          subtitleType={subtitleType}\n          specs={specs}\n        />\n      </div>\n\n      <div\n        data-slot=\"release-row-stats\"\n        className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 whitespace-nowrap [grid-area:s] [&>:empty]:hidden group-data-[form=compact]/rr:w-[92px] group-data-[form=compact]/rr:flex-none group-data-[form=compact]/rr:flex-col group-data-[form=compact]/rr:items-end group-data-[form=compact]/rr:gap-0.5 @min-[760px]/rr-list:group-data-[form=wide]/rr:contents @min-[760px]/rr-list:group-data-[form=wide]/rr:[&>:empty]:block\"\n      >\n        <span className=\"anibt-num text-[13px] text-foreground\">{fileSize ?? \"\"}</span>\n        {form === \"compact\" ? (\n          <span />\n        ) : (\n          <span>{seeders !== undefined || leechers !== undefined ? <ReleaseRowPeers seeders={seeders} leechers={leechers} /> : null}</span>\n        )}\n        {time ? (\n          <time dateTime={dateTime} suppressHydrationWarning className=\"text-[12px] text-muted-foreground\">\n            {time}\n          </time>\n        ) : (\n          <span />\n        )}\n      </div>\n\n      <div\n        data-slot=\"release-row-actions\"\n        className=\"relative z-10 flex shrink-0 items-center justify-end gap-2 justify-self-end [grid-area:a] @min-[760px]/rr-list:group-data-[form=wide]/rr:[grid-area:auto]\"\n      >\n        {squares}\n      </div>\n    </div>\n  )\n}\n\nfunction ReleaseRowFacts({\n  publisher,\n  category,\n  episode,\n  resolution,\n  languages,\n  subtitleType,\n  specs,\n}: {\n  publisher: ReleaseRowPublisher | null | undefined\n  category?: React.ReactNode\n  episode?: React.ReactNode\n  resolution?: string\n  languages?: string[]\n  subtitleType?: string\n  specs?: string[]\n}) {\n  const langs = (languages ?? []).filter((value) => value.trim())\n  const rest = (specs ?? []).filter((value) => value.trim())\n  const hasFacts = Boolean(category || episode || resolution || langs.length || subtitleType || rest.length)\n  if (!publisher && !hasFacts) return null\n\n  const publisherClass =\n    \"anibt-ell max-w-64 font-semibold text-accent-foreground group-data-[form=compact]/rr:hidden\"\n  const publisherNode = publisher ? (\n    publisher.link ? (\n      React.cloneElement(publisher.link, {\n        className: cn(publisherClass, \"relative z-10 hover:underline hover:underline-offset-2\", publisher.link.props.className),\n        children: publisher.name,\n      })\n    ) : publisher.href ? (\n      <a href={publisher.href} title={publisher.name} className={cn(publisherClass, \"relative z-10 hover:underline hover:underline-offset-2\")}>\n        {publisher.name}\n      </a>\n    ) : (\n      <span title={publisher.name} className={publisherClass}>\n        {publisher.name}\n      </span>\n    )\n  ) : null\n\n  return (\n    <div\n      data-slot=\"release-row-facts\"\n      className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-[12px] leading-5 text-anibt-ink2 lg:group-data-[form=compact]/rr:flex-nowrap lg:group-data-[form=compact]/rr:overflow-hidden\"\n    >\n      {publisherNode}\n      {publisher && hasFacts ? <i aria-hidden=\"true\" className=\"size-[3px] flex-none rounded-full bg-muted-foreground\" /> : null}\n      {category ? <Badge face=\"text\">{category}</Badge> : null}\n      {episode ? <span className=\"anibt-num whitespace-nowrap\">{episode}</span> : null}\n      {resolution ? <Badge variant=\"res\">{resolution}</Badge> : null}\n      {langs.length ? (\n        <Badge variant=\"lang\" face=\"text\" title={langs.join(\" / \")}>\n          {langs.join(\"·\")}\n        </Badge>\n      ) : null}\n      {subtitleType ? <Badge face=\"text\">{subtitleType}</Badge> : null}\n      {rest.length ? (\n        <span data-slot=\"release-row-specs\" className=\"hidden min-w-0 text-muted-foreground md:inline\">\n          {rest.map((value, index) => (\n            <React.Fragment key={`${value}-${index}`}>\n              {index > 0 ? <span aria-hidden=\"true\"> · </span> : null}\n              <span className=\"anibt-num\">{value}</span>\n            </React.Fragment>\n          ))}\n        </span>\n      ) : null}\n    </div>\n  )\n}\n\n/**\n * The swarm: ↑ seeders (mint once a live swarm is measured), ↓ leechers. An\n * unmeasured figure is a dash, never a zero the page has not read. Each figure\n * carries its word as its accessible name.\n */\nfunction ReleaseRowPeers({\n  seeders,\n  leechers,\n  seedersLabel = \"做种\",\n  leechersLabel = \"下载中\",\n  className,\n}: {\n  seeders?: number | null\n  leechers?: number | null\n  seedersLabel?: string\n  leechersLabel?: string\n  className?: string\n}) {\n  const show = (value: number | null | undefined) =>\n    value === null || value === undefined || !Number.isFinite(value) ? \"–\" : String(Math.max(0, Math.trunc(value)))\n  const live = typeof seeders === \"number\" && seeders > 0\n  return (\n    <span\n      data-slot=\"release-row-peers\"\n      className={cn(\"inline-flex items-center gap-2 font-anibt-display text-[13px] font-medium text-muted-foreground\", className)}\n    >\n      <span\n        title={`${seedersLabel} ${show(seeders)}`}\n        aria-label={`${seedersLabel} ${show(seeders)}`}\n        className={cn(\"inline-flex items-center gap-0.5 tabular-nums\", live && \"text-anibt-ok\")}\n      >\n        <ArrowUpIcon className=\"size-3.5\" aria-hidden=\"true\" />\n        {show(seeders)}\n      </span>\n      <span\n        title={`${leechersLabel} ${show(leechers)}`}\n        aria-label={`${leechersLabel} ${show(leechers)}`}\n        className=\"inline-flex items-center gap-0.5 tabular-nums\"\n      >\n        <ArrowDownIcon className=\"size-3.5\" aria-hidden=\"true\" />\n        {show(leechers)}\n      </span>\n    </span>\n  )\n}\n\nconst SQUARE =\n  \"size-10 rounded-[12px] md:size-8 md:rounded-[10px] group-data-[form=stacked]/rr:md:size-10 group-data-[form=stacked]/rr:md:rounded-[12px] group-data-[form=compact]/rr:size-8 group-data-[form=compact]/rr:rounded-[10px]\"\n\n/**\n * One icon square on a row, with its name as a tooltip and as its accessible\n * name. `href` makes it a link (`download` for a file); otherwise a button.\n * Always visible — a row never hides its actions until hover.\n */\nfunction ReleaseRowAction({\n  label,\n  href,\n  download,\n  disabled = false,\n  className,\n  children,\n  ...props\n}: Omit<React.ComponentProps<\"button\">, \"children\"> & {\n  label: string\n  href?: string\n  download?: boolean | string\n  children: React.ReactNode\n}) {\n  const square = cn(SQUARE, className)\n  const control =\n    href && !disabled ? (\n      <Button variant=\"secondary\" size=\"icon\" className={square} {...{ asChild: true }}>\n        <a href={href} download={download === true ? \"\" : download || undefined} aria-label={label}>\n          {children}\n        </a>\n      </Button>\n    ) : (\n      <Button variant=\"secondary\" size=\"icon\" className={square} aria-label={label} disabled={disabled} {...props}>\n        {children}\n      </Button>\n    )\n\n  if (disabled) return control\n  return (\n    <Tooltip>\n      <TooltipTrigger {...{ asChild: true }}>{control}</TooltipTrigger>\n      <TooltipContent>{label}</TooltipContent>\n    </Tooltip>\n  )\n}\n\n/**\n * The magnet square: copies the address and answers with a mint tick and\n * 「已复制」 for a moment. `value={null}` is the disabled square of a release\n * without a magnet.\n */\nfunction ReleaseRowCopyAction({\n  value,\n  label = \"复制磁力链接\",\n  copiedLabel = \"已复制\",\n  onCopied,\n  className,\n}: {\n  value: string | null\n  label?: string\n  copiedLabel?: string\n  onCopied?: () => void\n  className?: string\n}) {\n  const [copied, setCopied] = React.useState(false)\n  const [open, setOpen] = React.useState(false)\n  const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n  React.useEffect(() => () => clearTimeout(timer.current), [])\n\n  const copy = async () => {\n    if (!value) return\n    try {\n      await navigator.clipboard.writeText(value)\n    } catch {\n      return\n    }\n    setCopied(true)\n    onCopied?.()\n    clearTimeout(timer.current)\n    timer.current = setTimeout(() => setCopied(false), 1600)\n  }\n\n  if (value === null) {\n    return (\n      <ReleaseRowAction label={label} disabled className={className}>\n        <MagnetIcon />\n      </ReleaseRowAction>\n    )\n  }\n\n  return (\n    // While 「已复制」 shows, the tip stays open even though the press closed it.\n    <Tooltip open={open || copied} onOpenChange={setOpen}>\n      <TooltipTrigger {...{ asChild: true }}>\n        <Button\n          variant=\"secondary\"\n          size=\"icon\"\n          aria-label={label}\n          onClick={copy}\n          className={cn(SQUARE, copied && \"text-anibt-ok\", className)}\n        >\n          {copied ? <CheckIcon /> : <MagnetIcon />}\n          <span role=\"status\" className=\"sr-only\">\n            {copied ? copiedLabel : \"\"}\n          </span>\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>{copied ? copiedLabel : label}</TooltipContent>\n    </Tooltip>\n  )\n}\n\n/** A row-shaped placeholder for a loading list. */\nfunction ReleaseRowSkeleton({ cover = true, className }: { cover?: boolean; className?: string }) {\n  const block = \"animate-pulse rounded-[6px] bg-anibt-deep2 motion-reduce:animate-none\"\n  return (\n    <div\n      data-slot=\"release-row\"\n      data-form=\"wide\"\n      aria-hidden=\"true\"\n      className={cn(\n        \"relative grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-2 px-3.5 py-3 md:px-3\",\n        cover && \"grid-cols-[40px_minmax(0,1fr)_auto]\",\n        \"[[data-slot=release-row]+&]:before:pointer-events-none [[data-slot=release-row]+&]:before:absolute [[data-slot=release-row]+&]:before:inset-x-3.5 [[data-slot=release-row]+&]:before:top-0 [[data-slot=release-row]+&]:before:h-px [[data-slot=release-row]+&]:before:bg-(image:--anibt-stitch) [[data-slot=release-row]+&]:before:bg-size-[8px_1px] [[data-slot=release-row]+&]:before:bg-repeat-x [[data-slot=release-row]+&]:before:content-[''] md:[[data-slot=release-row]+&]:before:inset-x-3\",\n        className\n      )}\n    >\n      {cover ? <div className={cn(block, \"h-14 w-10\")} /> : null}\n      <div className=\"grid gap-2\">\n        <div className={cn(block, \"h-3.5 w-11/12\")} />\n        <div className={cn(block, \"h-3 w-1/2\")} />\n      </div>\n      <div className=\"flex gap-2\">\n        <div className={cn(block, \"size-8 rounded-[10px]\")} />\n        <div className={cn(block, \"size-8 rounded-[10px]\")} />\n      </div>\n    </div>\n  )\n}\n\nexport {\n  ReleaseRow,\n  ReleaseRowAction,\n  ReleaseRowCopyAction,\n  ReleaseRowDateRule,\n  ReleaseRowList,\n  ReleaseRowPeers,\n  ReleaseRowSkeleton,\n}\nexport type { ReleaseRowProps, ReleaseRowPublisher, ReleaseRowCover }\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "blocks",
    "display"
  ],
  "type": "registry:block"
}