feat: 重设计 UI/UX — 展示/管理分离 + shadcn/ui + Admin 后台
- 引入 shadcn/ui(base-nova 风格,Tailwind v4,14 个组件) - 新增 Admin 后台路由架构:/admin(总览)、/admin/bookmarks(管理) - 重写首页为纯展示书签导航(BookmarkCard + CategoryGrid) - 新增 Admin 侧边栏导航(AdminSidebar + SidebarProvider) - 书签管理页:双栏布局 + Dialog 表单 + DnD 排序 + Toast 通知 - 修复 IconPicker overflow 裁切(改用 Dialog portal) - 修复嵌套 button hydration 错误(base-ui render prop) - 删除旧组件(CategorySection/BookmarkItem/IconPicker)和旧路由 - 所有新依赖归入 root catalog - 更新 AGENTS.md 文档(目录结构、shadcn 模式、render prop 规范)
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import * as icons from 'lucide-react'
|
||||
import { Search } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const ICON_NAMES = [
|
||||
'Globe',
|
||||
'Home',
|
||||
'Code',
|
||||
'Database',
|
||||
'Mail',
|
||||
'MessageSquare',
|
||||
'Music',
|
||||
'Video',
|
||||
'Image',
|
||||
'FileText',
|
||||
'Folder',
|
||||
'Star',
|
||||
'Heart',
|
||||
'Bookmark',
|
||||
'Search',
|
||||
'Settings',
|
||||
'User',
|
||||
'Shield',
|
||||
'Key',
|
||||
'Terminal',
|
||||
'Github',
|
||||
'Chrome',
|
||||
'Cpu',
|
||||
'Server',
|
||||
'Cloud',
|
||||
'Wifi',
|
||||
'Zap',
|
||||
'Coffee',
|
||||
'BookOpen',
|
||||
'Briefcase',
|
||||
'Calendar',
|
||||
'Clock',
|
||||
'Download',
|
||||
'Edit',
|
||||
'ExternalLink',
|
||||
'Eye',
|
||||
'Film',
|
||||
'Gift',
|
||||
'Headphones',
|
||||
'Layout',
|
||||
'Link',
|
||||
'Map',
|
||||
'Monitor',
|
||||
'Package',
|
||||
'Phone',
|
||||
'ShoppingCart',
|
||||
'Smartphone',
|
||||
'Tv',
|
||||
'Upload',
|
||||
'Box',
|
||||
'Compass',
|
||||
'Rss',
|
||||
'Camera',
|
||||
'Printer',
|
||||
'Layers',
|
||||
'Activity',
|
||||
] as const
|
||||
|
||||
const allIcons = icons as unknown as Record<string, React.ComponentType<{ className?: string }>>
|
||||
|
||||
interface IconPickerDialogProps {
|
||||
value: string | null
|
||||
onChange: (iconName: string) => void
|
||||
}
|
||||
|
||||
export const IconPickerDialog = ({ value, onChange }: IconPickerDialogProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [filter, setFilter] = useState('')
|
||||
|
||||
const filteredIcons = ICON_NAMES.filter((name) => name.toLowerCase().includes(filter.toLowerCase()))
|
||||
const CurrentIcon = (value && allIcons[value]) || null
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger>
|
||||
<Button variant="outline" className="w-full justify-start gap-2">
|
||||
{CurrentIcon ? <CurrentIcon className="size-4" /> : <Search className="size-4 text-muted-foreground" />}
|
||||
<span className={cn('truncate', !value && 'text-muted-foreground')}>{value ?? '选择图标'}</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>选择图标</DialogTitle>
|
||||
<DialogDescription>搜索并选择一个 lucide 图标作为书签图标</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Input value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="搜索图标..." />
|
||||
|
||||
<div className="grid max-h-80 grid-cols-8 gap-2 overflow-y-auto rounded-lg border p-2">
|
||||
{filteredIcons.map((name) => {
|
||||
const Icon = allIcons[name]
|
||||
if (!Icon) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={name}
|
||||
type="button"
|
||||
size="icon"
|
||||
variant={value === name ? 'secondary' : 'ghost'}
|
||||
title={name}
|
||||
onClick={() => {
|
||||
onChange(name)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{filteredIcons.length === 0 && <p className="text-center text-sm text-muted-foreground">未找到匹配图标</p>}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user