shadcn 업데이트 && email components

This commit is contained in:
암냥 2026-03-26 21:39:11 +09:00
commit d7475dc0eb
No known key found for this signature in database
60 changed files with 1282 additions and 565 deletions

View file

@ -1,70 +1,157 @@
import { Github, Instagram, MailIcon, Rss } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
"use client";
import { MailIcon, Rss, GitBranch, Copy, Check } from "lucide-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import Image from "next/image";
import Link from "next/link";
import { Dialog, DialogContent, DialogHeader, DialogDescription, DialogFooter, DialogClose, DialogTitle } from "./ui/dialog";
import React from "react";
import { Button } from "./ui/button";
import {
Item,
ItemActions,
ItemContent,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"
type ContactMethod = {
name: string;
icon: React.ReactNode;
url?: string;
onClick?: () => void;
};
type EmailContact = {
label: string;
address: string;
};
const contact = [
{
"name": "Email",
"url": "mailto:me@imnya.ng",
"icon": <MailIcon className="w-5 h-5" />,
},
{
"name": "Blog",
"url": "https://blog.imnya.ng",
"icon": <Rss className="w-5 h-5" />
},
{
"name": "GitHub",
"url": "https://github.com/imnyang",
"icon": <Github className="w-5 h-5" />
},
{
"name": "Instagram",
"url": "https://instagram.com/imnya.ng",
"icon": <Instagram className="w-5 h-5" />
},
{
"name": "𝕏",
"url": "https://x.com/imnya_ng",
"icon": <p className="text-[20px] font-bold">𝕏</p>
},
{
"name": "Discord",
"url": "https://discord.gg/uxxn2ZZHGn",
"icon": <Image src="/icon/discord.svg" alt="Discord" width={20} height={20} className="w-5 h-5 invert-0 dark:invert" />
},
{
"name": "maishift",
"url": "https://mai.sft.sh/imnyang",
"icon": <Image src="/icon/maimai.webp" alt="mai.sft.sh" width={20} height={20} className="w-5 h-5 invert-0 dark:invert" />
}
]
export default function Contact() {
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const [copiedEmail, setCopiedEmail] = React.useState<string | null>(null);
const emailContacts: EmailContact[] = [
{ label: "Personal", address: "me@imnya.ng" },
{ label: "ADOFAI.gg", address: "imnyang@adofai.gg" },
{ label: "Dazzle.st", address: "imnyang@dazzle.st" },
];
const contact: ContactMethod[] = [
{
"name": "Email",
"onClick": () => setIsDialogOpen(true),
"icon": <MailIcon className="w-5 h-5" />,
},
{
"name": "Blog",
"url": "https://blog.imnya.ng",
"icon": <Rss className="w-5 h-5" />
},
{
"name": "GitHub",
"url": "https://github.com/imnyang",
"icon": <Image src="/icon/github.svg" width={24} height={24} alt="GitHub" className="w-6 h-6" />
},
{
"name": "git.mizuki.guru",
"url": "https://git.mizuki.guru/imnyang",
"icon": <GitBranch className="w-5 h-5" />
},
{
"name": "Instagram",
"url": "https://instagram.com/imnya.ng",
"icon": <Image src="/icon/instagram.svg" width={18} height={18} alt="Instagram" className="w-4 h-4" />
},
{
"name": "𝕏",
"url": "https://x.com/imnya_ng",
"icon": <p className="text-[20px] font-bold">𝕏</p>
}
]
const handleClick = (method: ContactMethod) => {
if (method.onClick) {
method.onClick();
} else if (method.url) {
window.open(method.url, "_blank", "noopener,noreferrer");
}
};
const copyEmail = (email: string) => {
void navigator.clipboard.writeText(email);
setCopiedEmail(email);
setTimeout(() => {
setCopiedEmail((currentEmail) => (currentEmail === email ? null : currentEmail));
}, 1200);
};
const openMailTo = (email: string) => {
window.location.href = `mailto:${email}`;
};
return (
<div className="flex flex-row space-x-4">
{contact.map((method) => (
<Tooltip key={method.name} >
<Tooltip key={method.name}>
<TooltipTrigger asChild>
<Link
href={method.url}
className="flex items-center space-x-2 text-foreground/80 hover:text-foreground transition-colors"
target="_blank"
rel="noopener noreferrer"
<button
type="button"
onClick={() => handleClick(method)}
className="flex items-center space-x-2 text-foreground/80 hover:text-foreground transition-colors hover:cursor-pointer"
>
{method.icon}
</Link>
</button>
</TooltipTrigger>
<TooltipContent>
<p>{method.name}</p>
</TooltipContent>
</Tooltip>
))}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Contact Me</DialogTitle>
<DialogDescription asChild>
<div className="flex flex-col gap-4">
{emailContacts.map((emailContact) => (
<div key={emailContact.address}>
<p>{emailContact.label} : </p>
<Item variant="outline" size="sm" asChild>
<button
type="button"
onClick={() => copyEmail(emailContact.address)}
onDoubleClick={() => openMailTo(emailContact.address)}
className="active:scale-95 transition-transform"
>
<ItemMedia>
<MailIcon className="size-5" />
</ItemMedia>
<ItemContent>
<ItemTitle>{emailContact.address}</ItemTitle>
</ItemContent>
<ItemActions>
{copiedEmail === emailContact.address ? (
<Check className="size-4" />
) : (
<Copy className="size-4" />
)}
</ItemActions>
</button>
</Item>
</div>
))}
</div>
</DialogDescription>
</DialogHeader>
<DialogFooter className="sm:justify-end">
<DialogClose asChild>
<Button type="button">Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}