Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/www/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15057,7 +15057,12 @@ export const TweetNotFound = ({
export const TweetHeader = ({ tweet }: { tweet: EnrichedTweet }) => (
<div className="flex flex-row items-start justify-between tracking-normal">
<div className="flex items-center space-x-3">
<a href={tweet.user.url} target="_blank" rel="noreferrer">
<a
href={tweet.user.url}
target="_blank"
rel="noreferrer"
className="shrink-0"
>
<img
title={`Profile picture of ${tweet.user.name}`}
alt={tweet.user.screen_name}
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/tweet-card.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"path": "registry/magicui/tweet-card.tsx",
"content": "/* eslint-disable @next/next/no-img-element */\nimport { 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 href={tweet.user.url} target=\"_blank\" rel=\"noreferrer\">\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 }\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\nexport const MagicTweet = ({\n tweet,\n className,\n ...props\n}: {\n tweet: Tweet\n className?: string\n}) => {\n const enrichedTweet = enrichTweet(tweet)\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",
"content": "/* eslint-disable @next/next/no-img-element */\nimport { 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 }\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\nexport const MagicTweet = ({\n tweet,\n className,\n ...props\n}: {\n tweet: Tweet\n className?: string\n}) => {\n const enrichedTweet = enrichTweet(tweet)\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"
}
]
Expand Down
7 changes: 6 additions & 1 deletion apps/www/registry/magicui/tweet-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ export const TweetNotFound = ({
export const TweetHeader = ({ tweet }: { tweet: EnrichedTweet }) => (
<div className="flex flex-row items-start justify-between tracking-normal">
<div className="flex items-center space-x-3">
<a href={tweet.user.url} target="_blank" rel="noreferrer">
<a
href={tweet.user.url}
target="_blank"
rel="noreferrer"
className="shrink-0"
>
<img
title={`Profile picture of ${tweet.user.name}`}
alt={tweet.user.screen_name}
Expand Down
Loading