diff --git a/app/links/page.tsx b/app/links/page.tsx new file mode 100644 index 0000000..746ca09 --- /dev/null +++ b/app/links/page.tsx @@ -0,0 +1,5 @@ +import { LinksContent } from "@/components/links-content"; + +export default function LinksPage() { + return ; +} diff --git a/components/links-content.tsx b/components/links-content.tsx new file mode 100644 index 0000000..edd17e8 --- /dev/null +++ b/components/links-content.tsx @@ -0,0 +1,169 @@ +"use client"; + +import { Icon } from "@iconify/react"; +import Link from "next/link"; +import { useMemo, useState } from "react"; +import { + Badge, + Button, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Input, + Typography, +} from "poyraz-ui/atoms"; +import { Tabs, TabsList, TabsTrigger } from "poyraz-ui/molecules"; +import { + LINK_DIRECTORY, + LINK_DIRECTORY_CATEGORIES, + type LinkDirectoryCategory, +} from "@/lib/links"; + +type CategoryFilter = "all" | LinkDirectoryCategory; + +const FILTER_ITEMS: ReadonlyArray<{ id: CategoryFilter; label: string }> = [ + { id: "all", label: "Tümü" }, + ...LINK_DIRECTORY_CATEGORIES, +]; + +function normalize(value: string) { + return value + .toLocaleLowerCase("tr-TR") + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, ""); +} + +function getCategoryLabel(category: LinkDirectoryCategory) { + return LINK_DIRECTORY_CATEGORIES.find((item) => item.id === category)?.label ?? "Kategori"; +} + +export function LinksContent() { + const [activeCategory, setActiveCategory] = useState("all"); + const [query, setQuery] = useState(""); + const [copiedId, setCopiedId] = useState(null); + + const filteredLinks = useMemo(() => { + const normalizedQuery = normalize(query.trim()); + const queryTokens = normalizedQuery.split(/\s+/).filter(Boolean); + + return LINK_DIRECTORY.filter((item) => { + const categoryMatch = activeCategory === "all" || item.category === activeCategory; + if (!categoryMatch) return false; + if (queryTokens.length === 0) return true; + + const haystack = normalize([ + item.label, + item.href, + ...item.keywords, + getCategoryLabel(item.category), + ].join(" ")); + + return queryTokens.every((token) => haystack.includes(token)); + }); + }, [activeCategory, query]); + + const handleCopy = async (id: string, href: string) => { + try { + await navigator.clipboard.writeText(href); + setCopiedId(id); + setTimeout(() => { + setCopiedId((current) => (current === id ? null : current)); + }, 1400); + } catch { + setCopiedId(null); + } + }; + + return ( + + + + Link Merkezi + + Tüm sosyal medya, sayfa ve kaynak linklerini tek yerden ara, filtrele ve yönet. + + + + + + setQuery(event.target.value)} + placeholder="Linklerde ara... (örn: github, medium, /blog)" + aria-label="Linklerde arama" + /> + + {filteredLinks.length} / {LINK_DIRECTORY.length} sonuç + + + + setActiveCategory(value as CategoryFilter)} + > + + {FILTER_ITEMS.map((item) => ( + + {item.label} + + ))} + + + + + + {filteredLinks.length === 0 ? ( + + + Arama kriterine uygun link bulunamadı. + + + ) : ( + + {filteredLinks.map((item) => ( + + + + + + + + {item.label} + {item.href} + + + {getCategoryLabel(item.category)} + + + + + + Aç + + + + handleCopy(item.id, item.href)} + > + {copiedId === item.id ? "Kopyalandı" : "Kopyala"} + + + + ))} + + )} + + ); +} diff --git a/components/site-navbar.tsx b/components/site-navbar.tsx index 12cb136..f00c9c8 100644 --- a/components/site-navbar.tsx +++ b/components/site-navbar.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import { Icon } from "@iconify/react"; import Link from "next/link"; @@ -20,38 +20,7 @@ import { } from "poyraz-ui/molecules"; import { SearchCommand } from "@/components/search-command"; import { useKeyboardShortcutLabel } from "@/lib/use-keyboard-shortcut-label"; -import { NAV_LINKS, SOCIAL_LINKS } from "@/lib/links"; - -const TOP_ICON_LINKS = [ - { - id: "ui-kit", - label: "UI Kit", - href: "https://ui.poyrazavsever.com", - icon: "mdi:palette-swatch-outline", - external: true, - }, - { - id: "52-weeks-js", - label: "52 Weeks of JS", - href: "https://js.poyrazavsever.com", - icon: "mdi:code-json", - external: true, - }, - { - id: "rss", - label: "RSS", - href: "/rss.xml", - icon: "mdi:rss", - external: false, - }, - { - id: "cv", - label: "Özgeçmiş", - href: "/resume.pdf", - icon: "mdi:file-account-outline", - external: false, - }, -] as const; +import { NAV_LINKS, SOCIAL_LINKS, TOP_ICON_LINKS } from "@/lib/links"; function getNavLinkClass(isActive: boolean) { return [ @@ -121,7 +90,7 @@ export function SiteNavbar() { type="button" onClick={() => setSearchOpen(true)} className="inline-flex h-8 w-44 cursor-pointer items-center justify-between rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground sm:w-52" - aria-label="Komut paletini aç" + aria-label="Komut paletini aç" > @@ -136,7 +105,7 @@ export function SiteNavbar() { Sosyal - Sosyal Bağlantılar + Sosyal BaÄŸlantılar {SOCIAL_LINKS.map((item) => ( @@ -159,17 +128,17 @@ export function SiteNavbar() { - Menü + Menü - Mobil Menü + Mobil Menü setSearchOpen(true)} className="inline-flex h-9 w-full cursor-pointer items-center justify-between rounded-sm border border-border px-3 text-sm text-muted-foreground transition-colors hover:text-foreground" - aria-label="Komut paletini aç" + aria-label="Komut paletini aç" > @@ -224,3 +193,4 @@ export function SiteNavbar() { ); } + diff --git a/lib/links.ts b/lib/links.ts index fe1c9f4..fbd0afe 100644 --- a/lib/links.ts +++ b/lib/links.ts @@ -68,3 +68,85 @@ export const SOCIAL_LINKS = [ icon: "mdi:coffee", }, ] as const; + +export const TOP_ICON_LINKS = [ + { + id: "ui-kit", + label: "UI Kit", + href: "https://ui.poyrazavsever.com", + icon: "mdi:palette-swatch-outline", + external: true, + }, + { + id: "52-weeks-js", + label: "52 Weeks of JS", + href: "https://js.poyrazavsever.com", + icon: "mdi:code-json", + external: true, + }, + { + id: "rss", + label: "RSS", + href: "/rss.xml", + icon: "mdi:rss", + external: false, + }, + { + id: "cv", + label: "Özgeçmiş", + href: "/resume.pdf", + icon: "mdi:file-account-outline", + external: false, + }, +] as const; + +export type LinkDirectoryCategory = "navigation" | "social" | "resources"; + +export type LinkDirectoryItem = { + id: string; + label: string; + href: string; + icon: string; + external: boolean; + category: LinkDirectoryCategory; + keywords: string[]; +}; + +export const LINK_DIRECTORY_CATEGORIES: ReadonlyArray<{ + id: LinkDirectoryCategory; + label: string; +}> = [ + { id: "navigation", label: "Sayfalar" }, + { id: "social", label: "Sosyal" }, + { id: "resources", label: "Kaynaklar" }, +]; + +export const LINK_DIRECTORY: LinkDirectoryItem[] = [ + ...NAV_LINKS.map((item) => ({ + id: item.id, + label: item.label, + href: item.href, + icon: "mdi:compass-outline", + external: false, + category: "navigation" as const, + keywords: [item.label, item.href, "sayfa", "navigasyon", "internal"], + })), + ...SOCIAL_LINKS.map((item) => ({ + id: item.id, + label: item.label, + href: item.href, + icon: item.icon, + external: true, + category: "social" as const, + keywords: [item.label, item.href, "sosyal", "profile", "platform"], + })), + ...TOP_ICON_LINKS.map((item) => ({ + id: item.id, + label: item.label, + href: item.href, + icon: item.icon, + external: item.external, + category: "resources" as const, + keywords: [item.label, item.href, "kaynak", "resource", "quick"], + })), +];