108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
import { Copy, ExternalLink, Globe } from "lucide-react";
|
|
import type { LinkMetadata } from "@/lib/link-metadata";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { Button } from "@/components/ui/button";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface LinkPreviewCardProps {
|
|
metadata: LinkMetadata;
|
|
compact?: boolean;
|
|
}
|
|
|
|
export function LinkPreviewCard({ metadata, compact }: LinkPreviewCardProps) {
|
|
const handleOpen = (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
window.electronLink.openExternal(metadata.url);
|
|
};
|
|
|
|
const handleCopy = (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
navigator.clipboard.writeText(metadata.url);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="max-w-sm overflow-hidden rounded-2xl bg-white/10 backdrop-blur-md"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleOpen(e);
|
|
}}
|
|
>
|
|
{metadata.image && (
|
|
<img
|
|
src={metadata.image}
|
|
alt=""
|
|
className="h-32 w-full object-cover"
|
|
onError={(e) => {
|
|
(e.target as HTMLImageElement).style.display = "none";
|
|
}}
|
|
/>
|
|
)}
|
|
<div className="flex flex-col gap-1 p-3">
|
|
<div className="flex items-center gap-1.5 text-xs text-white/50">
|
|
{metadata.favicon ? (
|
|
<img
|
|
src={metadata.favicon}
|
|
alt=""
|
|
className="size-4 rounded-sm"
|
|
onError={(e) => {
|
|
(e.target as HTMLImageElement).replaceWith(
|
|
document.createElement("span"),
|
|
);
|
|
}}
|
|
/>
|
|
) : (
|
|
<Globe className="size-4" />
|
|
)}
|
|
<span className="truncate">{metadata.domain}</span>
|
|
</div>
|
|
{metadata.title && (
|
|
<p className="truncate text-sm font-semibold leading-snug text-white">
|
|
{metadata.title}
|
|
</p>
|
|
)}
|
|
{metadata.description && (
|
|
<p className="line-clamp-2 text-xs leading-relaxed text-white/70">
|
|
{metadata.description}
|
|
</p>
|
|
)}
|
|
{!compact && (
|
|
<div className="mt-1.5 flex gap-2">
|
|
<Button
|
|
variant="ghost"
|
|
size="xs"
|
|
className="text-white/70 hover:bg-white/10 hover:text-white"
|
|
onClick={handleOpen}
|
|
>
|
|
<ExternalLink data-icon="inline-start" />
|
|
Open
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="xs"
|
|
className="text-white/70 hover:bg-white/10 hover:text-white"
|
|
onClick={handleCopy}
|
|
>
|
|
<Copy data-icon="inline-start" />
|
|
Copy
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function LinkPreviewCardSkeleton() {
|
|
return (
|
|
<div className="max-w-sm overflow-hidden rounded-2xl bg-white/10 backdrop-blur-md">
|
|
<Skeleton className="h-32 w-full rounded-none bg-white/5" />
|
|
<div className="flex flex-col gap-2 p-3">
|
|
<Skeleton className="h-3 w-24 bg-white/10" />
|
|
<Skeleton className="h-4 w-48 bg-white/10" />
|
|
<Skeleton className="h-3 w-full bg-white/10" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|