"use client"; import Link from "next/link"; import { CSSProperties, useEffect, useMemo, useState } from "react"; import iconData from "../data/web-icons.json"; import styles from "./IconLibrary.module.css"; type IconItem = (typeof iconData.icons)[number]; type CodeMode = "character" | "html" | "react" | "css"; type PreviewBackground = "light" | "soft" | "dark"; export default function IconLibraryPage() { const [query, setQuery] = useState(""); const [category, setCategory] = useState("all"); const [selectedId, setSelectedId] = useState("home"); const [favorites, setFavorites] = useState([]); const [view, setView] = useState<"grid" | "list">("grid"); const [size, setSize] = useState(28); const [color, setColor] = useState("#27324a"); const [background, setBackground] = useState("soft"); const [codeMode, setCodeMode] = useState("html"); const [onlyFavorites, setOnlyFavorites] = useState(false); const [toast, setToast] = useState(""); useEffect(() => { try { const saved = window.localStorage.getItem("orbit-icon-favorites"); if (saved) setFavorites(JSON.parse(saved)); } catch { /* use an empty favorite list */ } }, []); useEffect(() => { window.localStorage.setItem("orbit-icon-favorites", JSON.stringify(favorites)); }, [favorites]); const selected = iconData.icons.find((icon) => icon.id === selectedId) ?? iconData.icons[0]; const filteredIcons = useMemo(() => iconData.icons.filter((icon) => { const text = `${icon.name}${icon.cn}${icon.id}${icon.keywords.join("")}`.toLowerCase(); const matchesQuery = text.includes(query.toLowerCase()); const matchesCategory = category === "all" || icon.category === category; const matchesFavorite = !onlyFavorites || favorites.includes(icon.id); return matchesQuery && matchesCategory && matchesFavorite; }), [query, category, onlyFavorites, favorites]); const categoryName = iconData.categories.find((item) => item.id === selected.category)?.name ?? "通用"; const code = getIconCode(selected, codeMode, size, color); const previewStyle = { "--preview-size": `${size}px`, "--preview-color": color } as CSSProperties; const notify = (message: string) => { setToast(message); window.setTimeout(() => setToast(""), 1800); }; const copy = async (value: string, label: string) => { try { await navigator.clipboard.writeText(value); } catch { const input = document.createElement("textarea"); input.value = value; input.style.position = "fixed"; input.style.opacity = "0"; document.body.appendChild(input); input.select(); document.execCommand("copy"); input.remove(); } notify(`${label}已复制`); }; const toggleFavorite = (id: string) => setFavorites((current) => current.includes(id) ? current.filter((item) => item !== id) : [...current, id]); const exportFavorites = () => { const items = iconData.icons.filter((icon) => favorites.includes(icon.id)); const blob = new Blob([JSON.stringify(items, null, 2)], { type: "application/json;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "web-icons-favorites.json"; link.click(); URL.revokeObjectURL(url); notify("收藏图标已导出"); }; return (
GGlyphKitWEB ICON LIBRARY

SYMBOL PICKER

为界面选择一个合适的图标

搜索、预览并复制。全部图标使用系统字体,不需要安装额外资源。
{["⌂","✓","⌕","✎","▦"].map((symbol,index) => {symbol})}
预览大小 setSize(Number(event.target.value))} />{size}px
{onlyFavorites ? "我的收藏" : iconData.categories.find((item) => item.id === category)?.name}{filteredIcons.length} 个结果
{(query || onlyFavorites || category !== "all") && }
{filteredIcons.map((icon) => { const favorite = favorites.includes(icon.id); return
; })} {filteredIcons.length === 0 &&

没有找到匹配图标

尝试搜索“首页”“编辑”“文件”或英文关键词。

}
{toast &&
✓ {toast}
}
); } function categorySymbol(id: string) { return ({ all: "✣", navigation: "⌖", actions: "✎", status: "◉", media: "▶", files: "▤", business: "▦", communication: "✉", identity: "⚥", biology: "⚛", breeding: "♂♀", pets: "🐾︎" } as Record)[id] ?? "◇"; } function unicodeValue(symbol: string) { return Array.from(symbol).map((char) => char.codePointAt(0)?.toString(16).toUpperCase().padStart(4, "0")).join(" "); } function cssEscaped(symbol: string) { return Array.from(symbol).map((char) => `\\${char.codePointAt(0)?.toString(16).toUpperCase()} `).join(""); } function getIconCode(icon: IconItem, mode: CodeMode, size: number, color: string) { if (mode === "character") return icon.symbol; if (mode === "html") return ``; if (mode === "react") return `${icon.symbol}`; return `.web-icon::before {\n content: "${cssEscaped(icon.symbol)}";\n color: ${color};\n font: ${size}px/1 "Segoe UI Symbol", sans-serif;\n}`; }