{ "name": "blog-card-one", "type": "registry:ui", "registryDependencies": [], "dependencies": [], "devDependencies": [], "tailwind": {}, "cssVars": { "light": {}, "dark": {} }, "files": [ { "path": "blog-card-one.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport Link from 'next/link';\nimport { Bookmark, MoreVertical } from 'lucide-react';\nimport { cn } from '@/lib/utils';\n\nexport type BlogCardOneAuthor = {\n name: string;\n avatar?: string; // URL\n href?: string;\n};\n\nexport type BlogCardOneProps = {\n /** Main link target for the whole card */\n href?: string;\n /** Category tag (clickable) */\n category?: { label: string; href?: string };\n /** Title (required for a11y, used as aria-label on the card) */\n title: string;\n /** Short summary */\n excerpt?: string;\n /** Image (decorative if alt is empty) */\n image?: { src: string; alt?: string };\n /** Author & date */\n author?: BlogCardOneAuthor;\n dateISO?: string; // e.g. \"2024-08-15\"\n dateLabel?: string; // e.g. \"Aug 15, 2024\"\n\n /** Buttons (bookmark/menu) visibility */\n showActions?: boolean;\n /** Called when bookmark clicked */\n onBookmark?: () => void;\n /** Called when menu clicked */\n onMenu?: (e: React.MouseEvent) => void;\n\n /** Style variant */\n variant?: 'image-left' | 'image-top';\n\n /** Slot overrides */\n className?: string; // outer card\n imageClassName?: string;\n bodyClassName?: string;\n titleClassName?: string;\n excerptClassName?: string;\n tagClassName?: string;\n};\n\nexport function BlogCardOne({\n href,\n category,\n title,\n excerpt,\n image,\n author,\n dateISO,\n dateLabel,\n showActions = true,\n onBookmark,\n onMenu,\n variant = 'image-left',\n className,\n imageClassName,\n bodyClassName,\n titleClassName,\n excerptClassName,\n tagClassName,\n}: BlogCardOneProps) {\n const Wrapper: React.ElementType = href ? Link : 'article';\n const wrapperProps = href\n ? { href, 'aria-label': title }\n : { role: 'article' };\n\n const Figure = (\n \n {image?.src ? (\n // eslint-disable-next-line @next/next/no-img-element\n \n ) : (\n
\n No image\n
\n )}\n \n );\n\n return (\n \n \n {/* image */}\n {Figure}\n\n {/* content */}\n
\n {/* top row: tag + actions */}\n
\n {category?.label && (\n \n {category.label}\n \n )}\n {showActions && (\n
\n {\n e.preventDefault();\n onBookmark?.();\n }}\n className='rounded-md p-1.5 text-zinc-500 hover:bg-zinc-100 hover:text-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100'\n >\n \n \n {\n e.preventDefault();\n onMenu?.(e);\n }}\n className='rounded-md p-1.5 text-zinc-500 hover:bg-zinc-100 hover:text-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100'\n >\n \n \n
\n )}\n
\n\n {/* title */}\n \n {title}\n \n\n {/* excerpt */}\n {excerpt && (\n \n {excerpt}\n

\n )}\n\n {/* meta */}\n {(author?.name || dateLabel) && (\n
\n {author?.name && (\n \n {author.avatar && (\n // eslint-disable-next-line @next/next/no-img-element\n \n )}\n {author.href ? (\n e.stopPropagation()}\n className='font-medium hover:underline'\n >\n {author.name}\n \n ) : (\n {author.name}\n )}\n \n )}\n {dateLabel && (\n <>\n |\n \n \n )}\n
\n )}\n
\n \n \n );\n}\n\n/* ---------- helpers / subcomponents ---------- */\n\nfunction CategoryTag({\n href,\n className,\n children,\n}: React.PropsWithChildren<{ href?: string; className?: string }>) {\n const TagComp: any = href ? Link : 'span';\n const props = href ? { href } : { role: 'text' };\n return (\n \n {children}\n \n );\n}\n\n/* ---------- Variant export with preset ---------- */\n\n/** A preset that mirrors the screenshot: image on the left. */\nexport function ModernBlogCard(props: Omit) {\n return ;\n}\n\n/** Compact variant with the image stacked on top (great for grids). */\nexport function CompactBlogCard(props: Omit) {\n return ;\n}\n", "type": "registry:ui" } ] }