|
1 | | -import { useRouter } from "next/navigation"; |
2 | | -import { useState } from "react"; |
3 | | -export default function ShowcasesItem({ item }) { |
4 | | - const router = useRouter(); |
5 | | - const [isHovered, setIsHovered] = useState(false); |
| 1 | +function getRepositoryLabel(item) { |
| 2 | + if (!item?.repository) { |
| 3 | + return ''; |
| 4 | + } |
| 5 | + if (item.name) { |
| 6 | + return `Visit ${item.name} on GitHub`; |
| 7 | + } |
| 8 | + try { |
| 9 | + const pathname = new URL(item.repository).pathname.replace(/\/+$/, ''); |
| 10 | + const segments = pathname.split('/').filter(Boolean); |
| 11 | + if (segments.length) { |
| 12 | + return `Visit ${segments[segments.length - 1]} on GitHub`; |
| 13 | + } |
| 14 | + } catch (error) { |
| 15 | + // ignore malformed URLs and fall back below |
| 16 | + } |
| 17 | + return 'Visit repository on GitHub'; |
| 18 | +} |
6 | 19 |
|
| 20 | +export default function ShowcasesItem({ item }) { |
| 21 | + const repositoryLabel = getRepositoryLabel(item); |
7 | 22 | return ( |
8 | | - <div |
9 | | - className="rounded overflow-hidden group relative w-full h-full shadow-lg cursor-pointer" |
10 | | - onClick={() => router.push(item.href)} |
11 | | - > |
12 | | - <div |
13 | | - className="bypass-filter bg-cover bg-no-repeat !aspect-[16/9] w-full h-full group-hover:blur-sm group-hover:scale-105 transition-all duration-200" |
14 | | - style={{ backgroundImage: `url(${item.image})` }} |
| 23 | + <article className="relative group h-full"> |
| 24 | + <a |
| 25 | + href={item.href} |
| 26 | + target="_blank" |
| 27 | + rel="noreferrer" |
| 28 | + aria-label={`Visit ${item.title} portal`} |
| 29 | + className="block h-full overflow-hidden rounded-2xl ring-1 ring-white/15 bg-slate-950/40 transition-shadow duration-300 hover:ring-primary" |
15 | 30 | > |
16 | | - <div className="w-full h-full bg-black opacity-0 group-hover:opacity-50 transition-all duration-200"></div> |
17 | | - </div> |
18 | | - <div> |
19 | | - <div className="opacity-0 group-hover:opacity-100 absolute top-0 bottom-0 right-0 left-0 transition-all duration-200 px-2 flex items-center justify-center"> |
20 | | - <div className="text-center text-primary-dark"> |
21 | | - <span className="text-[17px] xl:text-2xl block font-bold"> |
22 | | - {item.title} |
23 | | - </span> |
24 | | - <span className="text-[15px] xl:text-xl font-semibold"> |
25 | | - {item.subtitle} |
26 | | - </span> |
27 | | - <p className="text-[12.5px] xl:text-base font-medium"> |
28 | | - {item.description} |
29 | | - </p> |
30 | | - <div className="flex justify-center mt-2 gap-2 "> |
31 | | - {item.href && ( |
32 | | - <a |
33 | | - target="_blank" |
34 | | - className={` text-white w-8 h-8 ${ |
35 | | - isHovered ? "w-[105px]" : "" |
36 | | - } p-1 bg-primary rounded-full hover:scale-110 transition duration-300 ease-in-out cursor-pointer z-50 `} |
37 | | - rel="noreferrer" |
38 | | - href={item.href} |
39 | | - area-label={`visit ${item.title} portal`} |
40 | | - onMouseEnter={() => setIsHovered(true)} |
41 | | - onMouseLeave={() => setIsHovered(false)} |
42 | | - > |
43 | | - {!isHovered ? ( |
44 | | - <svg |
45 | | - xmlns="http://www.w3.org/2000/svg" |
46 | | - viewBox="0 0 420 420" |
47 | | - stroke="white" |
48 | | - fill="none" |
49 | | - > |
50 | | - <path strokeWidth="26" d="M209,15a195,195 0 1,0 2,0z" /> |
51 | | - <path |
52 | | - strokeWidth="18" |
53 | | - d="m210,15v390m195-195H15M59,90a260,260 0 0,0 302,0 m0,240 a260,260 0 0,0-302,0M195,20a250,250 0 0,0 0,382 m30,0 a250,250 0 0,0 0-382" |
54 | | - /> |
55 | | - </svg> |
56 | | - ) : ( |
57 | | - <span>Go to portal</span> |
58 | | - )} |
59 | | - </a> |
60 | | - )} |
61 | | - {item.repository && ( |
62 | | - <a |
63 | | - target="_blank" |
64 | | - rel="noreferrer" |
65 | | - className="w-8 h-8 bg-black rounded-full p-1 hover:scale-110 transition cursor-pointer z-50" |
66 | | - href={item.repository} |
67 | | - area-label={`visit ${item.repository} github repositry`} |
68 | | - > |
69 | | - <svg |
70 | | - aria-hidden="true" |
71 | | - viewBox="0 0 16 16" |
72 | | - fill="currentColor" |
73 | | - > |
74 | | - <path d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" /> |
75 | | - </svg> |
76 | | - </a> |
77 | | - )} |
78 | | - </div> |
| 31 | + <div className="relative w-full overflow-hidden aspect-[16/9]"> |
| 32 | + <img |
| 33 | + src={item.image} |
| 34 | + alt={item.title} |
| 35 | + className="absolute inset-0 h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" |
| 36 | + /> |
| 37 | + <div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-transparent" /> |
| 38 | + </div> |
| 39 | + <div className="flex h-full flex-col gap-3 p-4 text-white"> |
| 40 | + <div className="text-xs uppercase tracking-wide text-white/70"> |
| 41 | + {item.subtitle} |
| 42 | + </div> |
| 43 | + <div className="space-y-1"> |
| 44 | + <p className="text-xl font-semibold">{item.title}</p> |
| 45 | + <p className="text-sm text-white/80">{item.description}</p> |
| 46 | + </div> |
| 47 | + <div className="mt-auto inline-flex items-center text-sm font-semibold text-primary"> |
| 48 | + Visit portal |
| 49 | + <svg |
| 50 | + className="ml-2 h-4 w-4" |
| 51 | + viewBox="0 0 24 24" |
| 52 | + fill="none" |
| 53 | + stroke="currentColor" |
| 54 | + strokeWidth="2" |
| 55 | + strokeLinecap="round" |
| 56 | + strokeLinejoin="round" |
| 57 | + > |
| 58 | + <path d="M5 12h14" /> |
| 59 | + <path d="m12 5 7 7-7 7" /> |
| 60 | + </svg> |
79 | 61 | </div> |
80 | 62 | </div> |
81 | | - </div> |
82 | | - </div> |
| 63 | + </a> |
| 64 | + {item.repository && ( |
| 65 | + <a |
| 66 | + target="_blank" |
| 67 | + rel="noreferrer" |
| 68 | + className="absolute top-3 right-3 z-20 inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/80 text-white transition-transform duration-200 hover:scale-110" |
| 69 | + href={item.repository} |
| 70 | + aria-label={repositoryLabel} |
| 71 | + > |
| 72 | + <svg aria-hidden="true" viewBox="0 0 16 16" fill="currentColor"> |
| 73 | + <path d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" /> |
| 74 | + </svg> |
| 75 | + </a> |
| 76 | + )} |
| 77 | + </article> |
83 | 78 | ); |
84 | 79 | } |
0 commit comments