{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "x-post",
  "type": "registry:block",
  "title": "X Post",
  "description": "Render X (Twitter) post previews.",
  "dependencies": [
    "lucide-react",
    "zod"
  ],
  "registryDependencies": [
    "button",
    "tooltip"
  ],
  "files": [
    {
      "path": "components/tool-ui/shared/contract.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/contract.ts",
      "content": "import { z } from \"zod\";\nimport { parseWithSchema, safeParseWithSchema } from \"./parse\";\n\nexport interface ToolUiContract<T> {\n  schema: z.ZodType<T>;\n  parse: (input: unknown) => T;\n  safeParse: (input: unknown) => T | null;\n}\n\nexport function defineToolUiContract<T>(\n  componentName: string,\n  schema: z.ZodType<T>,\n): ToolUiContract<T> {\n  return {\n    schema,\n    parse: (input: unknown) => parseWithSchema(schema, input, componentName),\n    safeParse: (input: unknown) => safeParseWithSchema(schema, input),\n  };\n}\n"
    },
    {
      "path": "components/tool-ui/shared/media/aspect-ratio.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/aspect-ratio.ts",
      "content": "import { z } from \"zod\";\n\nexport const AspectRatioSchema = z\n  .enum([\"auto\", \"1:1\", \"4:3\", \"16:9\", \"9:16\"])\n  .default(\"auto\");\n\nexport type AspectRatio = z.infer<typeof AspectRatioSchema>;\n\nexport const MediaFitSchema = z.enum([\"cover\", \"contain\"]).default(\"cover\");\n\nexport type MediaFit = z.infer<typeof MediaFitSchema>;\n\nexport const RATIO_CLASS_MAP: Record<AspectRatio, string> = {\n  auto: \"\",\n  \"1:1\": \"aspect-square\",\n  \"4:3\": \"aspect-[4/3]\",\n  \"16:9\": \"aspect-video\",\n  \"9:16\": \"aspect-[9/16]\",\n};\n\nexport function getRatioClass(ratio: AspectRatio): string {\n  return RATIO_CLASS_MAP[ratio];\n}\n\nexport function getFitClass(fit: MediaFit): string {\n  return fit === \"cover\" ? \"object-cover\" : \"object-contain\";\n}\n"
    },
    {
      "path": "components/tool-ui/shared/media/format-utils.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/format-utils.ts",
      "content": "/**\n * Format duration in milliseconds to human-readable string.\n * @example formatDuration(128000) => \"2:08\"\n * @example formatDuration(3661000) => \"1:01:01\"\n */\nexport function formatDuration(durationMs: number): string {\n  const totalSeconds = Math.round(durationMs / 1000);\n  const hours = Math.floor(totalSeconds / 3600);\n  const minutes = Math.floor((totalSeconds % 3600) / 60);\n  const seconds = totalSeconds % 60;\n\n  if (hours > 0) {\n    return `${hours}:${minutes.toString().padStart(2, \"0\")}:${seconds\n      .toString()\n      .padStart(2, \"0\")}`;\n  }\n  return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n}\n\n/**\n * Format file size in bytes to human-readable string.\n * @example formatFileSize(1024) => \"1 KB\"\n * @example formatFileSize(1536000) => \"1.5 MB\"\n */\nexport function formatFileSize(bytes: number): string {\n  if (bytes < 1024) return `${bytes} B`;\n  const units = [\"KB\", \"MB\", \"GB\"];\n  let size = bytes / 1024;\n  let unit = 0;\n  while (size >= 1024 && unit < units.length - 1) {\n    size /= 1024;\n    unit += 1;\n  }\n  return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unit]}`;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/media/index.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/index.ts",
      "content": "export {\n  AspectRatioSchema,\n  MediaFitSchema,\n  RATIO_CLASS_MAP,\n  getRatioClass,\n  getFitClass,\n  type AspectRatio,\n  type MediaFit,\n} from \"./aspect-ratio\";\n\nexport { OVERLAY_GRADIENT } from \"./overlay-gradient\";\n\nexport { formatDuration, formatFileSize } from \"./format-utils\";\n\nexport { sanitizeHref } from \"./sanitize-href\";\nexport {\n  resolveSafeNavigationHref,\n  openSafeNavigationHref,\n} from \"./safe-navigation\";\n"
    },
    {
      "path": "components/tool-ui/shared/media/overlay-gradient.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/overlay-gradient.ts",
      "content": "/**\n * Eased gradient for hover overlays on media elements.\n * Creates a smooth fade from opaque black at top to transparent.\n *\n * @see https://larsenwork.com/easing-gradients/\n */\nexport const OVERLAY_GRADIENT = `linear-gradient(\n  to bottom,\n  hsl(0, 0%, 0%) 0%,\n  hsla(0, 0%, 0%, 0.987) 8.3%,\n  hsla(0, 0%, 0%, 0.951) 16.6%,\n  hsla(0, 0%, 0%, 0.896) 24.6%,\n  hsla(0, 0%, 0%, 0.825) 32.5%,\n  hsla(0, 0%, 0%, 0.741) 40.1%,\n  hsla(0, 0%, 0%, 0.648) 47.6%,\n  hsla(0, 0%, 0%, 0.55) 54.8%,\n  hsla(0, 0%, 0%, 0.45) 61.7%,\n  hsla(0, 0%, 0%, 0.352) 68.3%,\n  hsla(0, 0%, 0%, 0.259) 74.5%,\n  hsla(0, 0%, 0%, 0.175) 80.4%,\n  hsla(0, 0%, 0%, 0.104) 86%,\n  hsla(0, 0%, 0%, 0.049) 91.1%,\n  hsla(0, 0%, 0%, 0.013) 95.8%,\n  hsla(0, 0%, 0%, 0) 100%\n)` as const;\n"
    },
    {
      "path": "components/tool-ui/shared/media/safe-navigation.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/safe-navigation.ts",
      "content": "import { sanitizeHref } from \"./sanitize-href\";\n\nexport function resolveSafeNavigationHref(\n  ...candidates: Array<string | null | undefined>\n): string | undefined {\n  for (const candidate of candidates) {\n    const safeHref = sanitizeHref(candidate ?? undefined);\n    if (safeHref) {\n      return safeHref;\n    }\n  }\n\n  return undefined;\n}\n\nexport function openSafeNavigationHref(href: string | undefined): boolean {\n  if (!href || typeof window === \"undefined\") {\n    return false;\n  }\n\n  window.open(href, \"_blank\", \"noopener,noreferrer\");\n  return true;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/media/sanitize-href.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/sanitize-href.ts",
      "content": "/**\n * Sanitize a URL to ensure it's safe for use in href attributes.\n * Allows:\n * - Absolute http(s) URLs\n * - Relative URLs (/path, ./path, ../path, ?query, #hash)\n *\n * @returns The sanitized URL string, or undefined if invalid/unsafe\n */\nexport function sanitizeHref(href?: string): string | undefined {\n  if (!href) return undefined;\n  const candidate = href.trim();\n  if (!candidate) return undefined;\n\n  if (\n    candidate.startsWith(\"/\") ||\n    candidate.startsWith(\"./\") ||\n    candidate.startsWith(\"../\") ||\n    candidate.startsWith(\"?\") ||\n    candidate.startsWith(\"#\")\n  ) {\n    if (candidate.startsWith(\"//\")) return undefined;\n    // eslint-disable-next-line no-control-regex -- intentionally matching control characters\n    if (/[\\u0000-\\u001F\\u007F]/.test(candidate)) return undefined;\n    return candidate;\n  }\n\n  try {\n    const url = new URL(candidate);\n    if (url.protocol === \"http:\" || url.protocol === \"https:\") {\n      return url.toString();\n    }\n  } catch {\n    return undefined;\n  }\n  return undefined;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/parse.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/parse.ts",
      "content": "import { z } from \"zod\";\n\nfunction formatZodPath(path: Array<string | number | symbol>): string {\n  if (path.length === 0) return \"root\";\n  return path\n    .map((segment) =>\n      typeof segment === \"number\" ? `[${segment}]` : String(segment),\n    )\n    .join(\".\");\n}\n\n/**\n * Format Zod errors into a compact `path: message` string.\n */\nexport function formatZodError(error: z.ZodError): string {\n  const parts = error.issues.map((issue) => {\n    const path = formatZodPath(issue.path);\n    return `${path}: ${issue.message}`;\n  });\n\n  return Array.from(new Set(parts)).join(\"; \");\n}\n\n/**\n * Parse unknown input and throw a readable error.\n */\nexport function parseWithSchema<T>(\n  schema: z.ZodType<T>,\n  input: unknown,\n  name: string,\n): T {\n  const res = schema.safeParse(input);\n  if (!res.success) {\n    throw new Error(`Invalid ${name} payload: ${formatZodError(res.error)}`);\n  }\n  return res.data;\n}\n\n/**\n * Parse unknown input, returning `null` instead of throwing on failure.\n *\n * Use this in assistant-ui `render` functions where `args` stream in\n * incrementally and may be incomplete until the tool call finishes.\n */\nexport function safeParseWithSchema<T>(\n  schema: z.ZodType<T>,\n  input: unknown,\n): T | null {\n  const res = schema.safeParse(input);\n  return res.success ? res.data : null;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/utils.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/utils.ts",
      "content": "export function formatRelativeTime(iso: string): string {\n  const seconds = Math.round((Date.now() - new Date(iso).getTime()) / 1000);\n  if (seconds < 60) return `${seconds}s`;\n  if (seconds < 3600) return `${Math.round(seconds / 60)}m`;\n  if (seconds < 86400) return `${Math.round(seconds / 3600)}h`;\n  if (seconds < 604800) return `${Math.round(seconds / 86400)}d`;\n  return `${Math.round(seconds / 604800)}w`;\n}\n\nexport function formatCount(count: number): string {\n  if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;\n  if (count >= 1_000) return `${(count / 1_000).toFixed(1)}K`;\n  return String(count);\n}\n\nexport function getDomain(url: string): string {\n  try {\n    return new URL(url).hostname.replace(/^www\\./, \"\");\n  } catch {\n    return \"\";\n  }\n}\n\nexport function prefersReducedMotion(): boolean {\n  return (\n    typeof window !== \"undefined\" &&\n    window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches\n  );\n}\n"
    },
    {
      "path": "components/tool-ui/x-post/_adapter.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/x-post/_adapter.tsx",
      "content": "/**\n * Adapter: UI and utility re-exports for copy-standalone portability.\n *\n * When copying this component to another project, update these imports\n * to match your project's paths:\n *\n *   cn      → Your Tailwind merge utility (e.g., \"@/lib/utils\", \"~/lib/cn\")\n *   Button  → shadcn/ui Button\n *   Tooltip → shadcn/ui Tooltip\n */\n\nexport { cn } from \"@/lib/utils\";\nexport { Button } from \"@/components/ui/button\";\nexport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n"
    },
    {
      "path": "components/tool-ui/x-post/index.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/x-post/index.ts",
      "content": "export { XPost } from \"./x-post\";\nexport type { XPostProps } from \"./x-post\";\nexport type {\n  XPostData,\n  XPostAuthor,\n  XPostMedia,\n  XPostLinkPreview,\n  XPostStats,\n} from \"./schema\";\n"
    },
    {
      "path": "components/tool-ui/x-post/README.md",
      "type": "registry:file",
      "target": "components/tool-ui/x-post/README.md",
      "content": "# X Post\n\nImplementation for the \"x-post\" Tool UI surface.\n\n## Files\n\n- public exports: components/tool-ui/x-post/index.ts\n- serializable schema + parse helpers: components/tool-ui/x-post/schema.ts\n\n## Companion assets\n\n- Docs page: app/docs/social-post/content.mdx\n- Preset payload: lib/presets/x-post.ts\n\n## Quick check\n\nRun this after edits:\n\npnpm test\n"
    },
    {
      "path": "components/tool-ui/x-post/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/x-post/schema.ts",
      "content": "import { z } from \"zod\";\nimport { defineToolUiContract } from \"../shared/contract\";\n\nexport const XPostAuthorSchema = z.object({\n  name: z.string(),\n  handle: z.string(),\n  avatarUrl: z.url(),\n  verified: z.boolean().optional(),\n});\n\nexport const XPostMediaSchema = z.object({\n  type: z.enum([\"image\", \"video\"]),\n  url: z.url(),\n  alt: z.string(),\n  aspectRatio: z.enum([\"1:1\", \"4:3\", \"16:9\", \"9:16\"]).optional(),\n});\n\nexport const XPostLinkPreviewSchema = z.object({\n  url: z.url(),\n  title: z.string().optional(),\n  description: z.string().optional(),\n  imageUrl: z.url().optional(),\n  domain: z.string().optional(),\n});\n\nexport const XPostStatsSchema = z.object({\n  likes: z.number().optional(),\n  isLiked: z.boolean().optional(),\n  isReposted: z.boolean().optional(),\n  isBookmarked: z.boolean().optional(),\n});\n\nexport interface XPostData {\n  id: string;\n  author: z.infer<typeof XPostAuthorSchema>;\n  text?: string;\n  media?: z.infer<typeof XPostMediaSchema>;\n  linkPreview?: z.infer<typeof XPostLinkPreviewSchema>;\n  quotedPost?: XPostData;\n  stats?: z.infer<typeof XPostStatsSchema>;\n  createdAt?: string;\n}\n\nexport const SerializableXPostSchema: z.ZodType<XPostData> = z.object({\n  id: z.string(),\n  author: XPostAuthorSchema,\n  text: z.string().optional(),\n  media: XPostMediaSchema.optional(),\n  linkPreview: XPostLinkPreviewSchema.optional(),\n  quotedPost: z.lazy(() => SerializableXPostSchema).optional(),\n  stats: XPostStatsSchema.optional(),\n  createdAt: z.string().optional(),\n});\nexport type XPostAuthor = z.infer<typeof XPostAuthorSchema>;\nexport type XPostMedia = z.infer<typeof XPostMediaSchema>;\nexport type XPostLinkPreview = z.infer<typeof XPostLinkPreviewSchema>;\nexport type XPostStats = z.infer<typeof XPostStatsSchema>;\n\nconst SerializableXPostSchemaContract = defineToolUiContract(\n  \"XPost\",\n  SerializableXPostSchema,\n);\n\nexport const parseSerializableXPost: (input: unknown) => XPostData =\n  SerializableXPostSchemaContract.parse;\n\nexport const safeParseSerializableXPost: (input: unknown) => XPostData | null =\n  SerializableXPostSchemaContract.safeParse;\n"
    },
    {
      "path": "components/tool-ui/x-post/x-post.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/x-post/x-post.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Heart, Share } from \"lucide-react\";\nimport {\n  cn,\n  Button,\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"./_adapter\";\nimport { formatCount, formatRelativeTime, getDomain } from \"../shared/utils\";\n\nimport { resolveSafeNavigationHref } from \"../shared/media\";\nimport type { XPostData, XPostMedia, XPostLinkPreview } from \"./schema\";\n\nexport interface XPostProps {\n  post: XPostData;\n  className?: string;\n  onAction?: (action: string, post: XPostData) => void;\n}\n\nfunction Avatar({ src, alt }: { src: string; alt: string }) {\n  return (\n    <img\n      src={src}\n      alt={alt}\n      width={40}\n      height={40}\n      className=\"size-10 shrink-0 rounded-full object-cover\"\n    />\n  );\n}\n\nfunction XLogo({ className }: { className?: string }) {\n  return (\n    <svg\n      viewBox=\"0 0 300 271\"\n      className={className}\n      role=\"img\"\n      aria-label=\"X (formerly Twitter) logo\"\n    >\n      <path\n        fill=\"currentColor\"\n        d=\"m236 0h46l-101 115 118 156h-92.6l-72.5-94.8-83 94.8h-46l107-123-113-148h94.9l65.5 86.6zm-16.1 244h25.5l-165-218h-27.4z\"\n      />\n    </svg>\n  );\n}\n\nfunction VerifiedBadge({ className }: { className?: string }) {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      className={className}\n      role=\"img\"\n      aria-label=\"Verified account\"\n    >\n      <path\n        fill=\"currentColor\"\n        d=\"M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .495.083.965.238 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z\"\n      />\n    </svg>\n  );\n}\n\nfunction AuthorInfo({\n  name,\n  handle,\n  verified,\n  createdAt,\n}: {\n  name: string;\n  handle: string;\n  verified?: boolean;\n  createdAt?: string;\n}) {\n  return (\n    <div className=\"flex min-w-0 items-center gap-1\">\n      <span className=\"truncate font-semibold\">{name}</span>\n      {verified && (\n        <VerifiedBadge className=\"size-[18px] shrink-0 text-blue-500\" />\n      )}\n      <span className=\"text-muted-foreground truncate\">@{handle}</span>\n      {createdAt && (\n        <>\n          <span className=\"text-muted-foreground\">·</span>\n          <span className=\"text-muted-foreground\">\n            {formatRelativeTime(createdAt)}\n          </span>\n        </>\n      )}\n    </div>\n  );\n}\n\nfunction PostBody({ text }: { text?: string }) {\n  if (!text) return null;\n  return (\n    <p className=\"text-[15px] leading-normal text-pretty wrap-break-word whitespace-pre-wrap\">\n      {text}\n    </p>\n  );\n}\n\nfunction PostMedia({\n  media,\n  onOpen,\n}: {\n  media: XPostMedia;\n  onOpen?: () => void;\n}) {\n  const aspectRatio =\n    media.aspectRatio === \"1:1\"\n      ? \"1\"\n      : media.aspectRatio === \"4:3\"\n        ? \"4/3\"\n        : \"16/9\";\n\n  return (\n    <button\n      type=\"button\"\n      className=\"bg-muted mt-2 w-full overflow-hidden rounded-xl\"\n      style={{ aspectRatio }}\n      onClick={() => onOpen?.()}\n    >\n      {media.type === \"image\" ? (\n        <img\n          src={media.url}\n          alt={media.alt}\n          className=\"size-full object-cover\"\n          loading=\"lazy\"\n        />\n      ) : (\n        <video\n          src={media.url}\n          controls\n          playsInline\n          className=\"size-full object-contain\"\n        />\n      )}\n    </button>\n  );\n}\n\nfunction PostLinkPreview({ preview }: { preview: XPostLinkPreview }) {\n  const href = resolveSafeNavigationHref(preview.url);\n  const domain = preview.domain ?? getDomain(preview.url);\n  const content = (\n    <>\n      {preview.imageUrl && (\n        <img\n          src={preview.imageUrl}\n          alt=\"\"\n          className=\"h-48 w-full object-cover\"\n          loading=\"lazy\"\n        />\n      )}\n      <div className=\"p-3\">\n        {domain && (\n          <div className=\"text-muted-foreground text-xs\">{domain}</div>\n        )}\n        {preview.title && (\n          <div className=\"font-medium text-pretty\">{preview.title}</div>\n        )}\n        {preview.description && (\n          <div className=\"text-muted-foreground line-clamp-2 text-sm text-pretty\">\n            {preview.description}\n          </div>\n        )}\n      </div>\n    </>\n  );\n\n  if (!href) {\n    return (\n      <div className=\"mt-2 block overflow-hidden rounded-xl border\">\n        {content}\n      </div>\n    );\n  }\n\n  return (\n    <a\n      href={href}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      className=\"hover:bg-muted/50 mt-2 block overflow-hidden rounded-xl border transition-colors\"\n    >\n      {content}\n    </a>\n  );\n}\n\nfunction QuotedPostCard({ post }: { post: XPostData }) {\n  return (\n    <div className=\"hover:bg-muted/30 mt-2 rounded-xl border p-3 transition-colors\">\n      <div className=\"flex min-w-0 items-center gap-1\">\n        <img\n          src={post.author.avatarUrl}\n          alt={`${post.author.name} avatar`}\n          width={16}\n          height={16}\n          className=\"size-4 rounded-full object-cover\"\n        />\n        <span className=\"truncate font-semibold\">{post.author.name}</span>\n        {post.author.verified && (\n          <VerifiedBadge className=\"size-3.5 shrink-0 text-blue-500\" />\n        )}\n        <span className=\"text-muted-foreground truncate\">\n          @{post.author.handle}\n        </span>\n        {post.createdAt && (\n          <>\n            <span className=\"text-muted-foreground shrink-0\">·</span>\n            <span className=\"text-muted-foreground shrink-0\">\n              {formatRelativeTime(post.createdAt)}\n            </span>\n          </>\n        )}\n      </div>\n      {post.text && <p className=\"mt-1.5\">{post.text}</p>}\n      {post.media && (\n        <img\n          src={post.media.url}\n          alt={post.media.alt}\n          className=\"mt-2 rounded-lg\"\n        />\n      )}\n    </div>\n  );\n}\n\nfunction ActionButton({\n  icon: Icon,\n  label,\n  count,\n  active,\n  hoverColor,\n  activeColor,\n  onClick,\n}: {\n  icon: React.ComponentType<{ className?: string }>;\n  label: string;\n  count?: number;\n  active?: boolean;\n  hoverColor: string;\n  activeColor?: string;\n  onClick: () => void;\n}) {\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          onClick={(e) => {\n            e.stopPropagation();\n            onClick();\n          }}\n          className={cn(\n            \"h-auto gap-1.5 px-2 py-1\",\n            hoverColor,\n            active && activeColor,\n          )}\n          aria-label={label}\n        >\n          <Icon className=\"size-4\" />\n          {count !== undefined && (\n            <span className=\"text-sm\">{formatCount(count)}</span>\n          )}\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>{label}</TooltipContent>\n    </Tooltip>\n  );\n}\n\nfunction PostActions({\n  stats,\n  onAction,\n}: {\n  stats?: XPostData[\"stats\"];\n  onAction: (action: string) => void;\n}) {\n  return (\n    <TooltipProvider delayDuration={300}>\n      <div className=\"mt-3 flex items-center gap-4\">\n        <ActionButton\n          icon={Heart}\n          label=\"Like\"\n          count={stats?.likes}\n          active={stats?.isLiked}\n          hoverColor=\"hover:text-pink-500 hover:bg-pink-500/10\"\n          activeColor=\"text-pink-500 fill-pink-500\"\n          onClick={() => onAction(\"like\")}\n        />\n        <ActionButton\n          icon={Share}\n          label=\"Share\"\n          hoverColor=\"hover:text-blue-500 hover:bg-blue-500/10\"\n          onClick={() => onAction(\"share\")}\n        />\n      </div>\n    </TooltipProvider>\n  );\n}\n\nexport function XPost({ post, className, onAction }: XPostProps) {\n  return (\n    <div\n      className={cn(\"flex max-w-xl flex-col gap-3\", className)}\n      data-tool-ui-id={post.id}\n      data-slot=\"x-post\"\n    >\n      <article className=\"bg-card rounded-xl border p-3 shadow-sm\">\n        <div className=\"flex gap-3\">\n          <Avatar\n            src={post.author.avatarUrl}\n            alt={`${post.author.name} avatar`}\n          />\n          <div className=\"min-w-0 flex-1\">\n            <div className=\"flex items-start justify-between gap-2\">\n              <AuthorInfo\n                name={post.author.name}\n                handle={post.author.handle}\n                verified={post.author.verified}\n                createdAt={post.createdAt}\n              />\n              <XLogo className=\"text-muted-foreground/40 size-4\" />\n            </div>\n            <PostBody text={post.text} />\n            {post.media && <PostMedia media={post.media} />}\n            {post.quotedPost && <QuotedPostCard post={post.quotedPost} />}\n            {post.linkPreview && !post.quotedPost && (\n              <PostLinkPreview preview={post.linkPreview} />\n            )}\n            <PostActions\n              stats={post.stats}\n              onAction={(action) => onAction?.(action, post)}\n            />\n          </div>\n        </div>\n      </article>\n    </div>\n  );\n}\n"
    }
  ]
}
