{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "post-card",
  "title": "Post Card",
  "author": "mcpcn",
  "description": "Post card with image, title, excerpt, author info and read more button. Supports default, compact, horizontal, and covered variants. Includes post-detail for fullscreen view.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "tooltip",
    "https://mcpcn.dev/r/shared-types.json",
    "https://mcpcn.dev/r/apps-sdk-theme.json"
  ],
  "files": [
    {
      "path": "registry/blogging/post-card.tsx",
      "content": "\"use client\";\n\nimport { createContext, createElement, useContext } from \"react\";\nimport type { ComponentProps, ImgHTMLAttributes } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nimport type { Post } from \"./types\";\n\nexport type { Post } from \"./types\";\n\nconst BlockImage = (props: ImgHTMLAttributes<HTMLImageElement>) =>\n  createElement(\"img\", props);\n\ninterface PostCardContextValue {\n  onReadMore?: (post: Post) => void;\n  post: Post;\n  showAuthor: boolean;\n  showCategory: boolean;\n  showImage: boolean;\n  variant: \"compact\" | \"covered\" | \"default\" | \"horizontal\";\n}\n\nconst PostCardContext = createContext<PostCardContextValue | null>(null);\n\nexport const usePostCard = () => {\n  const context = useContext(PostCardContext);\n  if (!context) {\n    throw new Error(\"PostCard components must be used within PostCard\");\n  }\n  return context;\n};\n\nconst DEFAULT_POST: Post = {\n  author: {\n    avatar: \"https://picsum.photos/seed/sarah/150/150\",\n    name: \"Sarah Chen\",\n  },\n  category: \"Tutorial\",\n  coverImage: \"https://picsum.photos/seed/post-card/800/450\",\n  excerpt:\n    \"Learn how to build conversational interfaces with our comprehensive component library designed for AI-powered applications.\",\n  publishedAt: \"2024-01-15\",\n  readTime: \"5 min read\",\n  tags: [\"Tutorial\", \"Components\"],\n  title: \"Getting Started with Agentic UI Components\",\n};\n\nconst formatDate = (date: string) =>\n  new Date(date).toLocaleDateString(\"en-US\", {\n    day: \"numeric\",\n    month: \"short\",\n    year: \"numeric\",\n  });\n\nexport interface PostCardProps extends ComponentProps<\"article\"> {\n  actions?: {\n    onReadMore?: (post: Post) => void;\n  };\n  appearance?: {\n    showAuthor?: boolean;\n    showCategory?: boolean;\n    showImage?: boolean;\n    variant?: \"default\" | \"compact\" | \"horizontal\" | \"covered\";\n  };\n  data?: {\n    post?: Post;\n  };\n}\n\nexport const PostCardImage = ({\n  className,\n  ...props\n}: ComponentProps<\"div\">) => {\n  const { post, showImage } = usePostCard();\n  if (!showImage) {\n    return null;\n  }\n  return (\n    <div className={cn(\"overflow-hidden bg-muted\", className)} {...props}>\n      {post.coverImage ? (\n        <BlockImage\n          alt={post.title ?? \"\"}\n          className=\"size-full object-cover\"\n          src={post.coverImage}\n        />\n      ) : (\n        <div className=\"size-full bg-muted\" />\n      )}\n    </div>\n  );\n};\n\nexport const PostCardCategory = ({\n  children,\n  className,\n  ...props\n}: ComponentProps<\"p\">) => {\n  const { post, showCategory } = usePostCard();\n  if (!(showCategory && (children || post.category))) {\n    return null;\n  }\n  return (\n    <p\n      className={cn(\n        \"font-medium text-[10px] text-muted-foreground uppercase tracking-wide\",\n        className\n      )}\n      {...props}\n    >\n      {children ?? post.category}\n    </p>\n  );\n};\n\nexport const PostCardTags = ({\n  children,\n  className,\n  ...props\n}: ComponentProps<\"div\">) => {\n  const { post } = usePostCard();\n  const tags = post.tags?.slice(0, 2) ?? [];\n  if (!(children || tags.length > 0)) {\n    return null;\n  }\n  return (\n    <div className={cn(\"flex flex-wrap gap-1\", className)} {...props}>\n      {children ??\n        tags.map((tag) => (\n          <span\n            className=\"rounded-full bg-muted px-2 py-0.5 text-muted-foreground text-xs\"\n            key={tag}\n          >\n            {tag}\n          </span>\n        ))}\n    </div>\n  );\n};\n\nexport const PostCardAuthor = ({\n  children,\n  className,\n  ...props\n}: ComponentProps<\"div\">) => {\n  const { post, showAuthor } = usePostCard();\n  if (!showAuthor) {\n    return null;\n  }\n  return (\n    <div className={cn(\"flex items-center gap-2\", className)} {...props}>\n      {children ?? (\n        <>\n          {post.author?.avatar && (\n            <BlockImage\n              alt={post.author.name ?? \"\"}\n              className=\"size-6 rounded-full object-cover\"\n              src={post.author.avatar}\n            />\n          )}\n          <div className=\"text-xs\">\n            {post.author?.name && (\n              <p className=\"font-medium\">{post.author.name}</p>\n            )}\n            {post.publishedAt && (\n              <p className=\"text-muted-foreground\">\n                {formatDate(post.publishedAt)}\n              </p>\n            )}\n          </div>\n        </>\n      )}\n    </div>\n  );\n};\n\nexport const PostCardAction = ({\n  children,\n  ...props\n}: ComponentProps<typeof Button>) => {\n  const { onReadMore, post } = usePostCard();\n  return (\n    <Button onClick={() => onReadMore?.(post)} size=\"sm\" {...props}>\n      {children ?? \"Read\"}\n    </Button>\n  );\n};\n\nexport const PostCardText = ({\n  children,\n  className,\n  ...props\n}: ComponentProps<\"div\">) => {\n  const { post } = usePostCard();\n  return (\n    <div className={className} {...props}>\n      {children ?? (\n        <>\n          <PostCardCategory />\n          {post.title && (\n            <h3 className=\"line-clamp-2 font-medium text-sm leading-tight\">\n              {post.title}\n            </h3>\n          )}\n          {post.excerpt && (\n            <p className=\"mt-1 line-clamp-2 text-muted-foreground text-xs\">\n              {post.excerpt}\n            </p>\n          )}\n          <PostCardTags className=\"mt-1.5\" />\n        </>\n      )}\n    </div>\n  );\n};\n\nconst CoveredPostCard = () => {\n  const { post } = usePostCard();\n  return (\n    <div className=\"relative overflow-hidden rounded-lg border\">\n      <PostCardImage className=\"min-h-[280px] w-full sm:aspect-[16/9] sm:min-h-0\" />\n      <div className=\"absolute inset-0 bg-black/60\" />\n      <div className=\"absolute inset-0 flex flex-col justify-end p-4 text-white\">\n        <PostCardCategory className=\"text-white/70\" />\n        {post.title && (\n          <h2 className=\"mt-1 font-semibold text-lg leading-tight\">\n            {post.title}\n          </h2>\n        )}\n        {post.excerpt && (\n          <p className=\"mt-1 line-clamp-2 text-sm text-white/80\">\n            {post.excerpt}\n          </p>\n        )}\n        <div className=\"mt-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n          <PostCardAuthor className=\"[&_p:last-child]:text-white/60\" />\n          <PostCardAction className=\"w-full sm:w-auto\" variant=\"secondary\">\n            Read article\n          </PostCardAction>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst HorizontalPostCard = () => (\n  <div className=\"flex flex-col gap-4 rounded-lg border bg-card p-3 sm:flex-row\">\n    <PostCardImage className=\"aspect-video shrink-0 rounded-md sm:aspect-square sm:size-24\" />\n    <div className=\"flex flex-1 flex-col justify-between\">\n      <PostCardText />\n      <div className=\"mt-2 flex items-center justify-between\">\n        <PostCardAuthor />\n        <PostCardAction />\n      </div>\n    </div>\n  </div>\n);\n\nconst CompactPostCard = () => (\n  <div className=\"rounded-lg border bg-card p-3\">\n    <PostCardText />\n    <div className=\"mt-3 flex items-center justify-between\">\n      <PostCardAuthor />\n      <PostCardAction />\n    </div>\n  </div>\n);\n\nconst DefaultPostCard = () => (\n  <div className=\"overflow-hidden rounded-lg border bg-card\">\n    <PostCardImage className=\"aspect-video w-full\" />\n    <div className=\"p-4\">\n      <PostCardText />\n      <div className=\"mt-4 flex items-center justify-between\">\n        <PostCardAuthor />\n        <PostCardAction />\n      </div>\n    </div>\n  </div>\n);\n\nexport const PostCardContent = () => {\n  const { variant } = usePostCard();\n  if (variant === \"covered\") {\n    return <CoveredPostCard />;\n  }\n  if (variant === \"horizontal\") {\n    return <HorizontalPostCard />;\n  }\n  if (variant === \"compact\") {\n    return <CompactPostCard />;\n  }\n  return <DefaultPostCard />;\n};\n\nconst PostCardRoot = ({\n  actions,\n  appearance,\n  children,\n  className,\n  data,\n  ...props\n}: PostCardProps & { children: React.ReactNode }) => {\n  const context: PostCardContextValue = {\n    onReadMore: actions?.onReadMore,\n    post: data?.post ?? DEFAULT_POST,\n    showAuthor: appearance?.showAuthor ?? true,\n    showCategory: appearance?.showCategory ?? true,\n    showImage: appearance?.showImage ?? true,\n    variant: appearance?.variant ?? \"default\",\n  };\n  return (\n    <PostCardContext.Provider value={context}>\n      <article className={className} {...props}>\n        {children}\n      </article>\n    </PostCardContext.Provider>\n  );\n};\n\nexport const PostCard = PostCardRoot;\n",
      "type": "registry:block",
      "target": "components/ui/post-card.tsx"
    },
    {
      "path": "registry/blogging/post-detail.tsx",
      "content": "\"use client\";\n\nimport { Calendar, Clock, ExternalLink, Maximize2 } from \"lucide-react\";\nimport { createContext, createElement, useContext } from \"react\";\nimport type { ComponentProps, ImgHTMLAttributes } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nimport type { Post } from \"./types\";\n\nconst BlockImage = (props: ImgHTMLAttributes<HTMLImageElement>) =>\n  createElement(\"img\", props);\n\ninterface PostDetailContextValue {\n  content?: string;\n  displayMode: \"fullscreen\" | \"inline\" | \"pip\";\n  onBack?: () => void;\n  onReadMore?: () => void;\n  onReadRelated?: (post: Post) => void;\n  post: Post;\n  relatedPosts: Post[];\n  showAuthor: boolean;\n  showCover: boolean;\n}\n\nconst PostDetailContext = createContext<PostDetailContextValue | null>(null);\n\nexport const usePostDetail = () => {\n  const context = useContext(PostDetailContext);\n  if (!context) {\n    throw new Error(\"PostDetail components must be used within PostDetail\");\n  }\n  return context;\n};\n\nconst DEFAULT_POST: Post = {\n  author: {\n    avatar: \"https://picsum.photos/seed/sarah/150/150\",\n    name: \"Sarah Chen\",\n  },\n  category: \"Tutorial\",\n  coverImage: \"https://picsum.photos/seed/post-detail/800/450\",\n  excerpt:\n    \"Learn how to build conversational interfaces for AI-powered applications.\",\n  publishedAt: \"2024-01-15\",\n  readTime: \"5 min read\",\n  tags: [\"Tutorial\", \"Components\", \"AI\", \"React\", \"TypeScript\"],\n  title: \"Getting Started with Agentic UI Components\",\n};\n\nconst DEFAULT_CONTENT = `\n  <p>Building modern AI-powered applications requires a new approach to UI design. Traditional web components don't always translate well to conversational interfaces, where context and flow are paramount.</p>\n  <p>Our Agentic UI component library provides purpose-built components that work seamlessly within chat interfaces.</p>\n  <h2>Key Features</h2>\n  <p>Each component supports inline, fullscreen, and picture-in-picture display modes while adapting to mobile devices and themes.</p>\n`;\n\nconst DEFAULT_RELATED: Post[] = [\n  {\n    excerpt: \"Best practices for intuitive UI components in chat environments.\",\n    readTime: \"8 min read\",\n    title: \"Designing for Conversational Interfaces\",\n    url: \"https://example.com/posts/designing-conversational-interfaces\",\n  },\n  {\n    excerpt: \"Use Model Context Protocol for seamless backend communication.\",\n    readTime: \"12 min read\",\n    title: \"MCP Integration Patterns\",\n    url: \"https://example.com/posts/mcp-integration-patterns\",\n  },\n];\n\nconst formatDate = (date: string) =>\n  new Date(date).toLocaleDateString(\"en-US\", {\n    day: \"numeric\",\n    month: \"long\",\n    year: \"numeric\",\n  });\n\nexport interface PostDetailProps extends ComponentProps<\"article\"> {\n  actions?: {\n    onBack?: () => void;\n    onReadMore?: () => void;\n    onReadRelated?: (post: Post) => void;\n  };\n  appearance?: {\n    displayMode?: \"inline\" | \"pip\" | \"fullscreen\";\n    showAuthor?: boolean;\n    showCover?: boolean;\n  };\n  data?: {\n    content?: string;\n    post?: Post;\n    relatedPosts?: Post[];\n  };\n}\n\nexport const PostDetailCover = ({\n  className,\n  ...props\n}: ComponentProps<\"div\">) => {\n  const { post, showCover } = usePostDetail();\n  if (!showCover) {\n    return null;\n  }\n  return (\n    <div className={cn(\"overflow-hidden bg-muted\", className)} {...props}>\n      {post.coverImage ? (\n        <BlockImage\n          alt={post.title ?? \"Post cover\"}\n          className=\"size-full object-cover\"\n          src={post.coverImage}\n        />\n      ) : (\n        <div className=\"size-full bg-muted\" />\n      )}\n    </div>\n  );\n};\n\nexport const PostDetailAuthor = ({\n  children,\n  className,\n  ...props\n}: ComponentProps<\"div\">) => {\n  const { post, showAuthor } = usePostDetail();\n  if (!showAuthor) {\n    return null;\n  }\n  return (\n    <div className={cn(\"flex items-center gap-3\", className)} {...props}>\n      {children ?? (\n        <>\n          {post.author?.avatar && (\n            <BlockImage\n              alt={post.author.name ?? \"Author\"}\n              className=\"size-9 rounded-full object-cover\"\n              src={post.author.avatar}\n            />\n          )}\n          <div>\n            {post.author?.name && (\n              <p className=\"font-medium text-sm\">{post.author.name}</p>\n            )}\n            <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground text-xs\">\n              {post.publishedAt && (\n                <span className=\"flex items-center gap-1\">\n                  <Calendar className=\"size-3.5\" />\n                  {formatDate(post.publishedAt)}\n                </span>\n              )}\n              {post.readTime && (\n                <span className=\"flex items-center gap-1\">\n                  <Clock className=\"size-3.5\" />\n                  {post.readTime}\n                </span>\n              )}\n            </div>\n          </div>\n        </>\n      )}\n    </div>\n  );\n};\n\nexport const PostDetailHeader = ({\n  children,\n  className,\n  ...props\n}: ComponentProps<\"header\">) => {\n  const { post } = usePostDetail();\n  return (\n    <header className={className} {...props}>\n      {children ?? (\n        <>\n          {post.category && (\n            <p className=\"font-medium text-muted-foreground text-xs uppercase tracking-wide\">\n              {post.category}\n            </p>\n          )}\n          {post.title && (\n            <h1 className=\"mt-2 font-bold text-2xl leading-tight sm:text-4xl\">\n              {post.title}\n            </h1>\n          )}\n          {post.excerpt && (\n            <p className=\"mt-3 text-muted-foreground sm:text-lg\">\n              {post.excerpt}\n            </p>\n          )}\n          <PostDetailAuthor className=\"mt-5\" />\n        </>\n      )}\n    </header>\n  );\n};\n\nexport const PostDetailBody = ({\n  children,\n  className,\n  ...props\n}: ComponentProps<\"div\">) => {\n  const { content } = usePostDetail();\n  if (children) {\n    return (\n      <div className={className} {...props}>\n        {children}\n      </div>\n    );\n  }\n  if (!content) {\n    return null;\n  }\n  return (\n    <div\n      className={cn(\n        \"prose prose-neutral max-w-none dark:prose-invert prose-headings:font-semibold prose-p:text-muted-foreground\",\n        className\n      )}\n      dangerouslySetInnerHTML={{ __html: content }}\n      {...props}\n    />\n  );\n};\n\nexport const PostDetailRelated = ({\n  children,\n  className,\n  ...props\n}: ComponentProps<\"div\">) => {\n  const { onReadRelated, relatedPosts } = usePostDetail();\n  if (!(children || relatedPosts.length > 0)) {\n    return null;\n  }\n  return (\n    <div className={cn(\"border-t pt-6\", className)} {...props}>\n      {children ?? (\n        <>\n          <h2 className=\"mb-4 font-semibold text-lg\">Related posts</h2>\n          <div className=\"space-y-2\">\n            {relatedPosts.map((post, index) => (\n              <button\n                className=\"flex w-full items-center gap-3 rounded-lg border p-3 text-left transition-colors hover:bg-muted\"\n                key={`${post.title ?? \"post\"}-${index}`}\n                onClick={() => onReadRelated?.(post)}\n                type=\"button\"\n              >\n                <div className=\"min-w-0 flex-1\">\n                  {post.title && <p className=\"font-medium\">{post.title}</p>}\n                  {post.excerpt && (\n                    <p className=\"mt-1 line-clamp-1 text-muted-foreground text-sm\">\n                      {post.excerpt}\n                    </p>\n                  )}\n                  {post.readTime && (\n                    <p className=\"mt-1 text-muted-foreground text-xs\">\n                      {post.readTime}\n                    </p>\n                  )}\n                </div>\n                <ExternalLink className=\"size-4 shrink-0 text-muted-foreground\" />\n              </button>\n            ))}\n          </div>\n        </>\n      )}\n    </div>\n  );\n};\n\nconst CompactPostDetail = () => {\n  const { displayMode, onReadMore, post } = usePostDetail();\n  return (\n    <div className=\"overflow-hidden rounded-xl border bg-card\">\n      <PostDetailCover className=\"aspect-video w-full\" />\n      <div className=\"p-4\">\n        {post.category && (\n          <p className=\"font-medium text-muted-foreground text-xs uppercase tracking-wide\">\n            {post.category}\n          </p>\n        )}\n        {post.title && (\n          <h2 className=\"mt-1 line-clamp-2 font-semibold text-xl\">\n            {post.title}\n          </h2>\n        )}\n        {post.excerpt && (\n          <p className=\"mt-2 line-clamp-3 text-muted-foreground text-sm\">\n            {post.excerpt}\n          </p>\n        )}\n        <PostDetailAuthor className=\"mt-4\" />\n        <Button className=\"mt-4 w-full\" onClick={onReadMore} size=\"sm\">\n          {displayMode === \"pip\" ? \"Open article\" : \"Read article\"}\n          <Maximize2 className=\"ml-2 size-4\" />\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nconst FullPostDetail = () => (\n  <div className=\"rounded-xl border bg-card\">\n    <PostDetailCover className=\"aspect-[21/9] w-full rounded-t-xl\" />\n    <div className=\"mx-auto max-w-3xl space-y-8 px-5 py-8 sm:px-8\">\n      <PostDetailHeader />\n      <PostDetailBody />\n      <PostDetailRelated />\n    </div>\n  </div>\n);\n\nexport const PostDetailContent = () => {\n  const { displayMode } = usePostDetail();\n  return displayMode === \"fullscreen\" ? (\n    <FullPostDetail />\n  ) : (\n    <CompactPostDetail />\n  );\n};\n\nconst PostDetailRoot = ({\n  actions,\n  appearance,\n  children,\n  className,\n  data,\n  ...props\n}: PostDetailProps & { children: React.ReactNode }) => {\n  const context: PostDetailContextValue = {\n    content: data?.content ?? DEFAULT_CONTENT,\n    displayMode: appearance?.displayMode ?? \"inline\",\n    onBack: actions?.onBack,\n    onReadMore: actions?.onReadMore,\n    onReadRelated: actions?.onReadRelated,\n    post: data?.post ?? DEFAULT_POST,\n    relatedPosts: data?.relatedPosts ?? DEFAULT_RELATED,\n    showAuthor: appearance?.showAuthor ?? true,\n    showCover: appearance?.showCover ?? true,\n  };\n  return (\n    <PostDetailContext.Provider value={context}>\n      <article className={className} {...props}>\n        {children}\n      </article>\n    </PostDetailContext.Provider>\n  );\n};\n\nexport const PostDetail = PostDetailRoot;\n",
      "type": "registry:block",
      "target": "components/ui/post-detail.tsx"
    }
  ],
  "categories": [
    "blogging"
  ],
  "type": "registry:block"
}