{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "article-template",
  "type": "registry:block",
  "title": "Article Template",
  "description": "Full-page surface for long-form articles. Composes the header (title + AuthorByline), the body (Prose-wrapped MDX/HTML), the related-posts grid, prev/next navigation, and share buttons — each in its own `<SectionBoundary>` so the page streams section-by-…",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "blog",
    "template"
  ],
  "dependencies": [],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/author-byline.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/container.json",
    "https://ds.interlace.tools/r/prev-next-post.json",
    "https://ds.interlace.tools/r/prose.json",
    "https://ds.interlace.tools/r/related-posts.json",
    "https://ds.interlace.tools/r/section-boundary.json",
    "https://ds.interlace.tools/r/share-buttons.json",
    "https://ds.interlace.tools/r/skeleton.json",
    "https://ds.interlace.tools/r/stack.json",
    "https://ds.interlace.tools/r/typography.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/templates/article-template.tsx",
      "target": "components/ui/templates/article-template.tsx",
      "type": "registry:block",
      "content": "import * as React from 'react';\n\n// @interlace/article-template v1.1.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/article-template\n// What changed since: https://ds.interlace.tools/c/article-template#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — ArticleTemplate\n *\n * Full-page surface for long-form articles. Composes the header\n * (title + AuthorByline), the body (Prose-wrapped MDX/HTML),\n * the related-posts grid, prev/next navigation, and share buttons —\n * each in its own `<SectionBoundary>` so the page streams section-by-\n * section instead of blocking on the slowest data source.\n *\n * This is the canonical example of the 5-layer architecture in action:\n *\n *   • PRIMITIVES — Container, Prose, Typography (the chrome)\n *   • PATTERNS   — AuthorByline, RelatedPosts, PrevNextPost, ShareButtons\n *   • TEMPLATE   — composes the patterns inside SectionBoundaries\n *\n * A consumer drops:\n *\n *   <ArticleTemplate\n *     header={await getArticleHeader(slug)}        ← async RSC suspends\n *     body={<MDXRenderer source={await getBody(slug)} />}\n *     related={await getRelatedArticles(slug)}      ← async, independent\n *     prevNext={await getPrevNext(slug)}            ← async, independent\n *     shareUrl={absoluteUrl(slug)}\n *   />\n *\n * Each async prop suspends INSIDE its own SectionBoundary, so the page\n * paints incrementally — header first (small payload), then body, then\n * related/prev-next (the slowest queries).\n *\n * ## Anatomy\n *\n *   <article data-slot=\"article-template\" data-min-viewport=\"320\">\n *     <Container size=\"prose\">\n *       <SectionBoundary name=\"article-header\">\n *         <Typography variant=\"h1\">{title}</Typography>\n *         <AuthorByline {...byline} />\n *       </SectionBoundary>\n *       <SectionBoundary name=\"article-body\">\n *         <Prose>{body}</Prose>\n *       </SectionBoundary>\n *       <SectionBoundary name=\"article-share\">\n *         <ShareButtons {...share} />\n *       </SectionBoundary>\n *       <SectionBoundary name=\"article-prev-next\">\n *         <PrevNextPost prev={...} next={...} />\n *       </SectionBoundary>\n *       <SectionBoundary name=\"article-related\">\n *         <RelatedPosts posts={...} />\n *       </SectionBoundary>\n *     </Container>\n *   </article>\n *\n * ## MIN_VIEWPORT — 320\n *\n * Every composed primitive/pattern declares 320 (Container.prose,\n * Prose, AuthorByline, etc.); the template inherits that floor.\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends native el                | `React.ComponentProps<'article'>` + template props          |\n * | R6   | data-slot on root                | `data-slot=\"article-template\"`                              |\n * | R7   | className merged + ...rest       | `cn(className)` + `{...props}` on <article>                 |\n * | R10  | Composition seam (slots)         | `header` / `body` / `related` / `prevNext` / `shareUrl` props |\n * | R14  | Declares min viewport            | `data-min-viewport={String(MIN_VIEWPORT)}`                  |\n * | R18  | Tailwind only                    | Zero inline `style`; layout via Container + cn()            |\n * | R19  | Tokens only                      | (delegated to composed primitives — they own the tokens)    |\n * | R20  | AA contrast                      | (delegated)                                                  |\n * | R25  | Server component                 | Pure composition — no hooks                                 |\n * | R26  | A11y                             | `<article>` landmark + per-section `<SectionBoundary>` regions |\n */\n\nimport { cn } from '@/lib/utils';\nimport { SectionBoundary } from '@/components/ui/section-boundary';\nimport { Container } from '@/components/ui/container';\nimport { Prose } from '@/components/ui/prose';\nimport { Typography } from '@/components/ui/typography';\nimport { AuthorByline } from '@/components/ui/patterns/author-byline';\nimport { PrevNextPost } from '@/components/ui/patterns/prev-next-post';\nimport { RelatedPosts } from '@/components/ui/patterns/related-posts';\nimport { ShareButtons } from '@/components/ui/patterns/share-buttons';\nimport { Skeleton } from '@/components/ui/skeleton';\nimport { Stack } from '@/components/ui/stack';\n\nexport const MIN_VIEWPORT = 320 as const;\n\ninterface ArticleTemplateProps\n  extends Omit<React.ComponentProps<'article'>, 'title'> {\n  /** Article title — rendered as the h1. Required (it's the landmark). */\n  title: React.ReactNode;\n  /**\n   * Author + publish date + reading-time row, rendered under the title.\n   * Consumer can either pass the full ReactNode (max flexibility — e.g.\n   * to add their own meta chip) or use `byline` for the standard\n   * AuthorByline pattern.\n   */\n  header?: React.ReactNode;\n  /** Shortcut: AuthorByline props. Ignored when `header` is provided. */\n  byline?: React.ComponentProps<typeof AuthorByline>;\n  /**\n   * The article body. Wrapped in <Prose> for the canonical type contract.\n   * Pass either rendered MDX/HTML (`<div dangerouslySetInnerHTML={...} />`),\n   * a stream of MDX components, or any ReactNode.\n   */\n  body: React.ReactNode;\n  /** Prev/next links (footer of the article). Both are optional. */\n  prevNext?: React.ComponentProps<typeof PrevNextPost>;\n  /** Related-articles grid (after prev/next). */\n  related?: React.ComponentProps<typeof RelatedPosts>;\n  /**\n   * Share-buttons row. When provided, renders between body and prev/next.\n   * Set to `null` to suppress entirely (some publications hide social\n   * surfaces on niche posts).\n   */\n  share?: React.ComponentProps<typeof ShareButtons> | null;\n}\n\nfunction ArticleTemplate({\n  title,\n  header,\n  byline,\n  body,\n  prevNext,\n  related,\n  share,\n  className,\n  ...props\n}: ArticleTemplateProps) {\n  return (\n    <article\n      data-slot=\"article-template\"\n      data-min-viewport={String(MIN_VIEWPORT)}\n      className={cn('py-xl', className)}\n      {...props}\n    >\n      <Container size=\"prose\">\n        <SectionBoundary name=\"article-header\" skeletonVariant=\"page-header\">\n          <header className=\"flex flex-col gap-md\">\n            <Typography variant=\"h1\" as=\"h1\" className=\"text-balance\">\n              {title}\n            </Typography>\n            {header ?? (byline ? <AuthorByline {...byline} /> : null)}\n          </header>\n        </SectionBoundary>\n\n        <SectionBoundary name=\"article-body\" skeletonVariant=\"prose\">\n          <div className=\"mt-xl\">\n            <Prose>{body}</Prose>\n          </div>\n        </SectionBoundary>\n\n        {share !== null && share !== undefined ? (\n          <SectionBoundary\n            name=\"article-share\"\n            skeletonVariant=\"card\"\n            className=\"mt-xl\"\n          >\n            <ShareButtons {...share} />\n          </SectionBoundary>\n        ) : null}\n\n        {prevNext ? (\n          <SectionBoundary\n            name=\"article-prev-next\"\n            skeletonVariant=\"card\"\n            className=\"mt-xl\"\n          >\n            <PrevNextPost {...prevNext} />\n          </SectionBoundary>\n        ) : null}\n\n        {related ? (\n          <SectionBoundary\n            name=\"article-related\"\n            skeletonVariant=\"article-card\"\n            className=\"mt-xl\"\n          >\n            <RelatedPosts {...related} />\n          </SectionBoundary>\n        ) : null}\n      </Container>\n    </article>\n  );\n}\nArticleTemplate.displayName = 'ArticleTemplate';\n\n/**\n * Page-level loading state — the full-page silhouette of\n * `<ArticleTemplate>`, not a single rect. Rendered by the consumer while\n * the whole route's data is in flight (`loading.tsx` in Next.js), or as\n * a `<SectionBoundary skeleton={…}>` override.\n *\n * Shapes mirror the real layout so the swap is CLS-neutral (R23). One\n * `role=\"status\"` region for the page; inner skeletons pass\n * `label={null}` so screen readers hear one announcement, not ten.\n */\nfunction ArticleTemplateSkeleton({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"article-template-skeleton\"\n      data-min-viewport={String(MIN_VIEWPORT)}\n      role=\"status\"\n      aria-busy=\"true\"\n      aria-label=\"Loading article…\"\n      className={cn('py-xl', className)}\n      {...props}\n    >\n      <Container size=\"prose\">\n        <Stack gap=\"xl\">\n          <Skeleton variant=\"page-header\" label={null} />\n          <Skeleton variant=\"prose\" count={8} label={null} />\n          <Skeleton variant=\"article-card\" label={null} />\n        </Stack>\n      </Container>\n    </div>\n  );\n}\nArticleTemplateSkeleton.displayName = 'ArticleTemplateSkeleton';\nArticleTemplate.Skeleton = ArticleTemplateSkeleton;\n\nexport { ArticleTemplate };\nexport type { ArticleTemplateProps };\n"
    }
  ],
  "meta": {
    "tier": "template",
    "client": false,
    "minViewport": 320,
    "loading": false,
    "version": "1.1.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/article-template\n\nInstalled to `components/ui/templates/article-template.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/templates/article-template';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/article-template\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
