This commit is contained in:
eric
2025-11-13 05:05:53 +08:00
parent cbdc65f56e
commit 891af61146
691 changed files with 83952 additions and 0 deletions

23
app/components/Footer.tsx Normal file
View File

@@ -0,0 +1,23 @@
export default function Footer() {
return (
<footer className="bg-white border-t">
<div className="container mx-auto px-4 py-6 text-center text-gray-600">
<p>
&copy; 2017-{new Date().getFullYear()}{' '}
<a href="/about" className="font-bold hover:text-gray-900">
dh.leti.ltd
</a>
{' '}design by{' '}
<a
href="https://www.viggoz.com"
target="_blank"
rel="noopener noreferrer"
className="font-bold hover:text-gray-900"
>
Viggo
</a>
</p>
</div>
</footer>
)
}

18
app/components/Header.tsx Normal file
View File

@@ -0,0 +1,18 @@
export default function Header() {
return (
<header className="bg-white shadow-sm">
<div className="container mx-auto px-4 py-3 flex justify-between items-center">
<div className="flex items-center">
<div className="hidden md:flex items-center space-x-4">
<a href="https://github.com/tianyaxiang/CoderNavigation"
target="_blank"
rel="noopener noreferrer"
className="text-gray-600 hover:text-gray-900">
<i className="fa-github"></i> GitHub
</a>
</div>
</div>
</div>
</header>
)
}

17
app/components/Layout.tsx Normal file
View File

@@ -0,0 +1,17 @@
import { ReactNode } from 'react'
import Header from './Header'
import Footer from './Footer'
interface LayoutProps {
children: ReactNode
}
export default function Layout({ children }: LayoutProps) {
return (
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow">{children}</main>
<Footer />
</div>
)
}

View File

@@ -0,0 +1,32 @@
import Image from 'next/image'
import Link from 'next/link'
import { ResourceItem } from '../types/navigation'
interface ResourceCardProps extends ResourceItem {}
export default function ResourceCard({ title, description, icon, url }: ResourceCardProps) {
return (
<Link
href={url}
target="_blank"
rel="noopener noreferrer"
className="card hover:shadow-lg transition-shadow"
>
<div className="card-content">
<div className="card-icon">
<Image
src={icon}
alt={title}
width={48}
height={48}
className="rounded-lg"
/>
</div>
<div className="card-text">
<h3 className="card-title">{title}</h3>
<p className="card-description">{description}</p>
</div>
</div>
</Link>
)
}

View File

@@ -0,0 +1,41 @@
'use client'
import { useEffect, useState } from 'react'
export function ScrollToTop() {
const [isVisible, setIsVisible] = useState(false)
// 检查滚动位置
const toggleVisibility = () => {
if (window.pageYOffset > 300) {
setIsVisible(true)
} else {
setIsVisible(false)
}
}
// 滚动到顶部
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
useEffect(() => {
window.addEventListener('scroll', toggleVisibility)
return () => {
window.removeEventListener('scroll', toggleVisibility)
}
}, [])
return (
<button
className={`scroll-to-top ${isVisible ? 'show' : ''}`}
onClick={scrollToTop}
aria-label="Scroll to top"
>
<i className="fas fa-arrow-up"></i>
</button>
)
}

View File

@@ -0,0 +1,31 @@
'use client'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
export function ThemeToggle() {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className="theme-toggle"
aria-label="Toggle theme"
>
{theme === 'dark' ? (
<i className="fas fa-sun text-xl animate-spin-slow" />
) : (
<i className="fas fa-moon text-xl animate-pulse" />
)}
</button>
)
}

View File

@@ -0,0 +1,8 @@
'use client'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { type ThemeProviderProps } from 'next-themes/dist/types'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

View File

@@ -0,0 +1,76 @@
'use client'
import { useState } from 'react'
import { Button } from "@/registry/new-york/ui/button"
import { Input } from "@/registry/new-york/ui/input"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
import { cn } from "@/lib/utils"
const ICONS = [
{ name: 'linecons-star', label: '星星' },
{ name: 'linecons-desktop', label: '桌面' },
{ name: 'linecons-database', label: '数据库' },
{ name: 'linecons-inbox', label: '收件箱' },
{ name: 'linecons-globe', label: '地球' },
{ name: 'linecons-cloud', label: '云' },
{ name: 'linecons-paper-plane', label: '纸飞机' },
{ name: 'linecons-search', label: '搜索' },
// 添加更多图标...
]
interface IconPickerProps {
value: string
onChange: (value: string) => void
}
export function IconPicker({ value, onChange }: IconPickerProps) {
const [open, setOpen] = useState(false)
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
{value ? (
<>
<i className={cn("mr-2 h-4 w-4", value)} />
{ICONS.find(icon => icon.name === value)?.label || value}
</>
) : (
"选择图标..."
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<div className="grid grid-cols-3 gap-2 p-4">
{ICONS.map(icon => (
<Button
key={icon.name}
variant="ghost"
size="sm"
className={cn(
"justify-start",
value === icon.name && "bg-muted"
)}
onClick={() => {
onChange(icon.name)
setOpen(false)
}}
>
<i className={cn("mr-2 h-4 w-4", icon.name)} />
<span className="text-xs">{icon.label}</span>
</Button>
))}
</div>
</PopoverContent>
</Popover>
)
}