188 lines
5.0 KiB
TypeScript
188 lines
5.0 KiB
TypeScript
import * as IconPark from '@icon-park/react';
|
|
|
|
import { Input, List, Modal, Pagination } from 'antd';
|
|
import { useState } from 'react';
|
|
|
|
const allIcons = Object.keys(IconPark);
|
|
|
|
// 动态构造 iconMap
|
|
const iconMap: Record<string, React.ReactNode> = {};
|
|
|
|
allIcons.forEach((iconName) => {
|
|
// 排除不需要的属性(如默认导出等)
|
|
if (
|
|
iconName !== 'default' &&
|
|
typeof IconPark[iconName as keyof typeof IconPark] === 'function'
|
|
) {
|
|
const IconComponent = IconPark[
|
|
iconName as keyof typeof IconPark
|
|
] as React.ComponentType<any>;
|
|
iconMap[iconName] = <IconComponent theme="outline" size="16" />;
|
|
}
|
|
});
|
|
|
|
// 图标选项列表
|
|
const iconOptions = Object.keys(iconMap).map((key) => ({
|
|
label: (
|
|
<span>
|
|
{iconMap[key]} {key}
|
|
</span>
|
|
),
|
|
value: key,
|
|
}));
|
|
|
|
interface IconSelectorProps {
|
|
value?: string;
|
|
onChange?: (value: string) => void;
|
|
}
|
|
|
|
const IconSelector: React.FC<IconSelectorProps> = ({ value, onChange }) => {
|
|
const [open, setOpen] = useState(false);
|
|
const [searchValue, setSearchValue] = useState('');
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const pageSize = 48;
|
|
|
|
// 过滤图标
|
|
const filteredIcons = iconOptions.filter(
|
|
(option) =>
|
|
option.value.toLowerCase().includes(searchValue.toLowerCase()) ||
|
|
option.value.toLowerCase().includes(searchValue.toLowerCase()),
|
|
);
|
|
|
|
// 分页数据
|
|
const paginatedIcons = filteredIcons.slice(
|
|
(currentPage - 1) * pageSize,
|
|
currentPage * pageSize,
|
|
);
|
|
|
|
const handleSelect = (iconName: string) => {
|
|
onChange?.(iconName);
|
|
setOpen(false);
|
|
setCurrentPage(1);
|
|
setSearchValue('');
|
|
};
|
|
|
|
const handleClear = () => {
|
|
onChange?.('');
|
|
setOpen(false);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
onClick={() => setOpen(true)}
|
|
style={{
|
|
border: '1px solid #d9d9d9',
|
|
borderRadius: 6,
|
|
padding: '4px 11px',
|
|
cursor: 'pointer',
|
|
minHeight: 32,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
{value ? (
|
|
<span>
|
|
{iconMap[value as keyof typeof iconMap]}
|
|
<span style={{ marginLeft: 8 }}>{value}</span>
|
|
</span>
|
|
) : (
|
|
<span style={{ color: '#bfbfbf' }}>请选择图标</span>
|
|
)}
|
|
</div>
|
|
|
|
<Modal
|
|
title="选择图标"
|
|
open={open}
|
|
onCancel={() => {
|
|
setOpen(false);
|
|
setCurrentPage(1);
|
|
setSearchValue('');
|
|
}}
|
|
onOk={() => setOpen(false)}
|
|
width={800}
|
|
styles={{ body: { padding: 0 } }}
|
|
okText="确定"
|
|
cancelText="取消"
|
|
>
|
|
<div style={{ padding: 16 }}>
|
|
<Input.Search
|
|
placeholder="搜索图标..."
|
|
value={searchValue}
|
|
onChange={(e) => {
|
|
setSearchValue(e.target.value);
|
|
setCurrentPage(1);
|
|
}}
|
|
style={{ marginBottom: 16 }}
|
|
/>
|
|
|
|
{filteredIcons.length === 0 ? (
|
|
<IconPark.Empty title="未找到相关图标" />
|
|
) : (
|
|
<>
|
|
<List
|
|
grid={{ gutter: 16, column: 8 }}
|
|
dataSource={paginatedIcons}
|
|
renderItem={(item) => (
|
|
<List.Item
|
|
onClick={() => handleSelect(item.value)}
|
|
style={{
|
|
cursor: 'pointer',
|
|
textAlign: 'center',
|
|
border:
|
|
value === item.value
|
|
? '1px solid #1890ff'
|
|
: '1px solid transparent',
|
|
borderRadius: 6,
|
|
padding: 8,
|
|
}}
|
|
>
|
|
<div>
|
|
<div style={{ fontSize: 20, marginBottom: 4 }}>
|
|
{iconMap[item.value as keyof typeof iconMap]}
|
|
</div>
|
|
<div
|
|
style={{
|
|
fontSize: 12,
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
}}
|
|
>
|
|
{item.value}
|
|
</div>
|
|
</div>
|
|
</List.Item>
|
|
)}
|
|
/>
|
|
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
marginTop: 16,
|
|
}}
|
|
>
|
|
<Pagination
|
|
current={currentPage}
|
|
pageSize={pageSize}
|
|
total={filteredIcons.length}
|
|
onChange={setCurrentPage}
|
|
showSizeChanger={false}
|
|
size="small"
|
|
/>
|
|
<div>
|
|
<a onClick={handleClear} style={{ marginRight: 16 }}>
|
|
清除选择
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</Modal>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default IconSelector;
|