{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tweet-card",
  "type": "registry:ui",
  "title": "Tweet Card",
  "description": "A card that displays a tweet with the author's name, handle, and profile picture.",
  "dependencies": [
    "react-tweet"
  ],
  "files": [
    {
      "path": "registry/magicui/tweet-card.tsx",
      "content": "import { Suspense } from \"react\"\nimport { enrichTweet, type EnrichedTweet, type TweetProps } from \"react-tweet\"\nimport { getTweet, type Tweet } from \"react-tweet/api\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface TwitterIconProps {\n  className?: string\n  [key: string]: unknown\n}\nconst Twitter = ({ className, ...props }: TwitterIconProps) => (\n  <svg\n    stroke=\"currentColor\"\n    fill=\"currentColor\"\n    strokeWidth=\"0\"\n    viewBox=\"0 0 24 24\"\n    height=\"1em\"\n    width=\"1em\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className={className}\n    {...props}\n  >\n    <g>\n      <path fill=\"none\" d=\"M0 0h24v24H0z\"></path>\n      <path d=\"M22.162 5.656a8.384 8.384 0 0 1-2.402.658A4.196 4.196 0 0 0 21.6 4c-.82.488-1.719.83-2.656 1.015a4.182 4.182 0 0 0-7.126 3.814 11.874 11.874 0 0 1-8.62-4.37 4.168 4.168 0 0 0-.566 2.103c0 1.45.738 2.731 1.86 3.481a4.168 4.168 0 0 1-1.894-.523v.052a4.185 4.185 0 0 0 3.355 4.101 4.21 4.21 0 0 1-1.89.072A4.185 4.185 0 0 0 7.97 16.65a8.394 8.394 0 0 1-6.191 1.732 11.83 11.83 0 0 0 6.41 1.88c7.693 0 11.9-6.373 11.9-11.9 0-.18-.005-.362-.013-.54a8.496 8.496 0 0 0 2.087-2.165z\"></path>\n    </g>\n  </svg>\n)\n\nconst Verified = ({ className, ...props }: TwitterIconProps) => (\n  <svg\n    aria-label=\"Verified Account\"\n    viewBox=\"0 0 24 24\"\n    className={className}\n    {...props}\n  >\n    <g fill=\"currentColor\">\n      <path 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 .494.083.964.237 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    </g>\n  </svg>\n)\n\nexport const truncate = (str: string | null, length: number) => {\n  if (!str || str.length <= length) return str\n  return `${str.slice(0, length - 3)}...`\n}\n\nconst Skeleton = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => {\n  return (\n    <div className={cn(\"bg-primary/10 rounded-md\", className)} {...props} />\n  )\n}\n\nexport const TweetSkeleton = ({\n  className,\n  ...props\n}: {\n  className?: string\n  [key: string]: unknown\n}) => (\n  <div\n    className={cn(\n      \"flex size-full max-h-max min-w-72 flex-col gap-2 rounded-xl border p-4\",\n      className\n    )}\n    {...props}\n  >\n    <div className=\"flex flex-row gap-2\">\n      <Skeleton className=\"size-10 shrink-0 rounded-full\" />\n      <Skeleton className=\"h-10 w-full\" />\n    </div>\n    <Skeleton className=\"h-20 w-full\" />\n  </div>\n)\n\nexport const TweetNotFound = ({\n  className,\n  ...props\n}: {\n  className?: string\n  [key: string]: unknown\n}) => (\n  <div\n    className={cn(\n      \"flex size-full flex-col items-center justify-center gap-2 rounded-lg border p-4\",\n      className\n    )}\n    {...props}\n  >\n    <h3>Tweet not found</h3>\n  </div>\n)\n\nexport const TweetHeader = ({ tweet }: { tweet: EnrichedTweet }) => (\n  <div className=\"flex flex-row items-start justify-between tracking-normal\">\n    <div className=\"flex items-center space-x-3\">\n      <a\n        href={tweet.user.url}\n        target=\"_blank\"\n        rel=\"noreferrer\"\n        className=\"shrink-0\"\n      >\n        <img\n          title={`Profile picture of ${tweet.user.name}`}\n          alt={tweet.user.screen_name}\n          height={48}\n          width={48}\n          src={tweet.user.profile_image_url_https}\n          className=\"border-border/50 overflow-hidden rounded-full border\"\n        />\n      </a>\n      <div className=\"flex flex-col gap-0.5\">\n        <a\n          href={tweet.user.url}\n          target=\"_blank\"\n          rel=\"noreferrer\"\n          className=\"text-foreground flex items-center font-medium whitespace-nowrap transition-opacity hover:opacity-80\"\n        >\n          {truncate(tweet.user.name, 20)}\n          {tweet.user.verified ||\n            (tweet.user.is_blue_verified && (\n              <Verified className=\"ml-1 inline size-4 text-blue-500\" />\n            ))}\n        </a>\n        <div className=\"flex items-center space-x-1\">\n          <a\n            href={tweet.user.url}\n            target=\"_blank\"\n            rel=\"noreferrer\"\n            className=\"text-muted-foreground hover:text-foreground text-sm transition-colors\"\n          >\n            @{truncate(tweet.user.screen_name, 16)}\n          </a>\n        </div>\n      </div>\n    </div>\n    <a href={tweet.url} target=\"_blank\" rel=\"noreferrer\">\n      <span className=\"sr-only\">Link to tweet</span>\n      <Twitter className=\"text-muted-foreground hover:text-foreground size-5 items-start transition-all ease-in-out hover:scale-105\" />\n    </a>\n  </div>\n)\n\nexport const TweetBody = ({ tweet }: { tweet: EnrichedTweet }) => (\n  <div className=\"text-[15px] leading-relaxed tracking-normal wrap-break-word\">\n    {tweet.entities.map((entity, idx) => {\n      switch (entity.type) {\n        case \"url\":\n        case \"symbol\":\n        case \"hashtag\":\n        case \"mention\":\n          return (\n            <a\n              key={idx}\n              href={entity.href}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"text-muted-foreground hover:text-foreground text-[15px] font-normal transition-colors\"\n            >\n              <span>{entity.text}</span>\n            </a>\n          )\n        case \"text\":\n          return (\n            <span\n              key={idx}\n              className=\"text-foreground text-[15px] font-normal\"\n              dangerouslySetInnerHTML={{ __html: entity.text }}\n            />\n          )\n        default:\n          return null\n      }\n    })}\n  </div>\n)\n\nexport const TweetMedia = ({ tweet }: { tweet: EnrichedTweet }) => {\n  if (!tweet.video && !tweet.photos) return null\n  return (\n    <div className=\"flex flex-1 items-center justify-center\">\n      {tweet.video && (\n        <video\n          poster={tweet.video.poster}\n          autoPlay\n          loop\n          muted\n          playsInline\n          className=\"rounded-xl border shadow-sm\"\n        >\n          <source src={tweet.video.variants[0].src} type=\"video/mp4\" />\n          Your browser does not support the video tag.\n        </video>\n      )}\n      {tweet.photos && (\n        <div className=\"relative flex transform-gpu snap-x snap-mandatory gap-4 overflow-x-auto\">\n          <div className=\"shrink-0 snap-center sm:w-2\" />\n          {tweet.photos.map((photo) => (\n            <img\n              key={photo.url}\n              src={photo.url}\n              width={photo.width}\n              height={photo.height}\n              title={\"Photo by \" + tweet.user.name}\n              alt={tweet.text}\n              className=\"h-64 w-5/6 shrink-0 snap-center snap-always rounded-xl border object-cover shadow-sm\"\n            />\n          ))}\n          <div className=\"shrink-0 snap-center sm:w-2\" />\n        </div>\n      )}\n      {!tweet.video &&\n        !tweet.photos &&\n        // @ts-expect-error package doesn't have type definitions\n        tweet?.card?.binding_values?.thumbnail_image_large?.image_value.url && (\n          <img\n            src={\n              // @ts-expect-error package doesn't have type definitions\n              tweet.card.binding_values.thumbnail_image_large.image_value.url\n            }\n            className=\"h-64 rounded-xl border object-cover shadow-sm\"\n            alt={tweet.text}\n          />\n        )}\n    </div>\n  )\n}\n\nconst withSafeEntities = <T extends { entities?: Tweet[\"entities\"] }>(\n  tweet: T\n): T & { entities: Tweet[\"entities\"] } => ({\n  ...tweet,\n  entities: {\n    ...tweet.entities,\n    hashtags: tweet.entities?.hashtags ?? [],\n    urls: tweet.entities?.urls ?? [],\n    symbols: tweet.entities?.symbols ?? [],\n    user_mentions: tweet.entities?.user_mentions ?? [],\n  },\n})\n\nexport const MagicTweet = ({\n  tweet,\n  className,\n  ...props\n}: {\n  tweet: Tweet\n  className?: string\n}) => {\n  const safeTweet: Tweet = {\n    ...withSafeEntities(tweet),\n    quoted_tweet: tweet.quoted_tweet\n      ? withSafeEntities(tweet.quoted_tweet)\n      : undefined,\n  }\n  const enrichedTweet = enrichTweet(safeTweet)\n  return (\n    <div\n      className={cn(\n        \"relative flex h-fit w-full max-w-lg flex-col gap-4 overflow-hidden rounded-xl border p-5\",\n        className\n      )}\n      {...props}\n    >\n      <TweetHeader tweet={enrichedTweet} />\n      <TweetBody tweet={enrichedTweet} />\n      <TweetMedia tweet={enrichedTweet} />\n    </div>\n  )\n}\n\n/**\n * TweetCard (Server Side Only)\n */\nexport const TweetCard = async ({\n  id,\n  components,\n  fallback = <TweetSkeleton />,\n  onError,\n  ...props\n}: TweetProps & {\n  className?: string\n}) => {\n  const tweet = id\n    ? await getTweet(id).catch((err) => {\n        if (onError) {\n          onError(err)\n        } else {\n          console.error(err)\n        }\n      })\n    : undefined\n\n  if (!tweet) {\n    const NotFound = components?.TweetNotFound ?? TweetNotFound\n    return <NotFound {...props} />\n  }\n\n  return (\n    <Suspense fallback={fallback}>\n      <MagicTweet tweet={tweet} {...props} />\n    </Suspense>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}