Fork of atp.tools as a universal profile for people on the ATmosphere
1import { AppBskyEmbedImages } from "@atcute/client/lexicons";
2import { X } from "lucide-react";
3import { useState } from "preact/hooks";
4
5export const getBlueskyCdnLink = (
6 did: string,
7 cid: string,
8 ext: string,
9 type: string = "feed_fullsize",
10) => {
11 return `https://cdn.bsky.app/img/${type}/plain/${did}/${cid}@${ext}`;
12};
13
14export const AppBskyEmbedImagesLayout = ({
15 did,
16 images,
17}: {
18 did: string;
19 images: AppBskyEmbedImages.Image[];
20}) => {
21 const [selectedImage, setSelectedImage] = useState<number | null>(null);
22 const imageCount = images.length;
23
24 // Different grid layouts based on number of images
25 const gridClassName =
26 {
27 1: "grid-cols-1",
28 2: "grid-cols-2",
29 3: "grid-cols-2",
30 4: "grid-cols-2",
31 }[Math.min(imageCount, 4)] || "grid-cols-2";
32
33 return (
34 <>
35 <div className={`grid ${gridClassName} gap-2 w-full`}>
36 {images.map((image, i) => (
37 <div
38 key={i}
39 className={`relative overflow-hidden rounded-lg cursor-pointer ${
40 imageCount === 3 && i === 0 ? "col-span-2" : ""
41 }`}
42 onClick={() => setSelectedImage(i)}
43 >
44 <img
45 src={getBlueskyCdnLink(did, image.image.ref.$link, "jpeg")}
46 alt=""
47 className={`w-full max-w-96 h-full cursor-pointer object-cover transition-transform duration-300 hover:scale-[101%] ${imageCount > 1 && "max-h-64"}`}
48 style={{
49 aspectRatio: imageCount === 1 ? "" : "1/1",
50 }}
51 loading="lazy"
52 />
53 </div>
54 ))}
55 </div>
56
57 {selectedImage !== null && (
58 <>
59 {/* Image Preview */}
60 <div
61 className="fixed inset-0 bg-black/80 flex flex-col gap-2 items-center justify-center z-50"
62 onClick={() => setSelectedImage(null)}
63 >
64 <img
65 src={getBlueskyCdnLink(
66 did,
67 images[selectedImage].image.ref.$link,
68 "png",
69 )}
70 className="max-h-[90vh] max-w-[90vw] object-contain"
71 />
72 {images[selectedImage].alt && (
73 <div className="text-white">
74 Alt text: {images[selectedImage].alt}
75 </div>
76 )}
77 </div>
78 <div className="fixed top-2 right-2 z-50">
79 <button
80 className="text-blue-100 hover:text-red-400 transition-colors duration-300"
81 onClick={() => setSelectedImage(null)}
82 >
83 <X />
84 </button>
85 </div>
86 </>
87 )}
88 </>
89 );
90};