mirror of
https://github.com/R0m1k3/Menu.git
synced 2026-10-11 17:28:28 +02:00
64 lines
1.7 KiB
TypeScript
64 lines
1.7 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import Link from 'next/link';
|
|
import Image from 'next/image';
|
|
import { ArrowUpRight, Globe } from 'lucide-react';
|
|
import { GlassCard } from './GlassCard';
|
|
import styles from './MenuButton.module.css';
|
|
|
|
interface MenuButtonProps {
|
|
name: string;
|
|
url: string;
|
|
}
|
|
|
|
/**
|
|
* Extract base domain from URL for favicon fetch.
|
|
*/
|
|
function getDomain(url: string): string {
|
|
try {
|
|
const { hostname } = new URL(url);
|
|
return hostname;
|
|
} catch {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* MenuButton component displaying a favicon and app name.
|
|
* Favicons are fetched from Google's favicon CDN.
|
|
*/
|
|
export const MenuButton: React.FC<MenuButtonProps> = ({ name, url }) => {
|
|
const domain = getDomain(url);
|
|
const faviconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=64`;
|
|
const [faviconError, setFaviconError] = useState(false);
|
|
|
|
return (
|
|
<Link href={url} target="_blank" rel="noopener noreferrer" className={styles.menuButton}>
|
|
<GlassCard hoverable className={styles.menuButton}>
|
|
<ArrowUpRight size={16} className={styles.arrowIcon} />
|
|
|
|
<div className={styles.faviconWrapper}>
|
|
{!faviconError ? (
|
|
<Image
|
|
src={faviconUrl}
|
|
alt={`${name} icon`}
|
|
width={40}
|
|
height={40}
|
|
className={styles.favicon}
|
|
onError={() => setFaviconError(true)}
|
|
unoptimized
|
|
/>
|
|
) : (
|
|
<div className={styles.faviconFallback}>
|
|
<Globe size={28} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<span className={styles.label}>{name}</span>
|
|
</GlassCard>
|
|
</Link>
|
|
);
|
|
};
|