deploy-ease-platform/frontend/src/components/LucideIconSelect/index.tsx
2025-10-24 22:02:45 +08:00

142 lines
5.6 KiB
TypeScript

import React, { useState, useMemo } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Search } from "lucide-react";
import { searchLucideIcons, ICON_CATEGORIES } from '@/config/lucideIcons';
import DynamicIcon from '@/components/DynamicIcon';
interface LucideIconSelectProps {
/** 当前选中的图标名称 */
value?: string;
/** 图标变化回调 */
onChange?: (iconName: string) => void;
/** 是否显示弹窗 */
open: boolean;
/** 关闭弹窗回调 */
onOpenChange: (open: boolean) => void;
}
/**
* Lucide 图标选择器组件
*
* 支持:
* - 搜索图标
* - 分类浏览
* - 点击选择
*/
const LucideIconSelect: React.FC<LucideIconSelectProps> = ({
value,
onChange,
open,
onOpenChange
}) => {
const [search, setSearch] = useState('');
const [category, setCategory] = useState('all');
// 过滤图标列表
const iconList = useMemo(() => {
return searchLucideIcons(search, category);
}, [search, category]);
// 选择图标
const handleSelect = (iconName: string) => {
onChange?.(iconName);
onOpenChange(false);
setSearch('');
setCategory('all');
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* 搜索框 */}
<div className="relative">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="搜索图标名称..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-10"
/>
</div>
{/* 分类标签 */}
<Tabs value={category} onValueChange={setCategory}>
<TabsList className="w-full flex-wrap h-auto">
<TabsTrigger value="all" className="text-xs"> ({iconList.length})</TabsTrigger>
{Object.keys(ICON_CATEGORIES).map((cat) => (
<TabsTrigger key={cat} value={cat} className="text-xs">
{cat}
</TabsTrigger>
))}
</TabsList>
<TabsContent value={category} className="mt-4">
{/* 图标网格 */}
<div className="grid grid-cols-8 gap-2 max-h-[400px] overflow-y-auto p-2 border rounded-md">
{iconList.length > 0 ? (
iconList.map(({ name, component: Icon }) => (
<Button
key={name}
variant={value === name ? "default" : "outline"}
className="h-20 flex flex-col items-center justify-center gap-2 p-2"
onClick={() => handleSelect(name)}
title={name}
>
<Icon className="h-6 w-6" />
<span className="text-[10px] truncate w-full text-center">
{name}
</span>
</Button>
))
) : (
<div className="col-span-8 text-center py-8 text-muted-foreground">
</div>
)}
</div>
</TabsContent>
</Tabs>
{/* 当前选中 */}
{value && (
<div className="flex items-center gap-2 p-3 border rounded-md bg-muted/50">
<span className="text-sm text-muted-foreground">:</span>
<div className="flex items-center gap-2">
<DynamicIcon name={value} className="h-5 w-5" />
<code className="text-sm">{value}</code>
</div>
<Button
variant="ghost"
size="sm"
className="ml-auto"
onClick={() => {
onChange?.('');
onOpenChange(false);
}}
>
</Button>
</div>
)}
</div>
</DialogContent>
</Dialog>
);
};
export default LucideIconSelect;