blob: 78dc931bf5abb0bd95e654013304ace9f0675fe3 [file] [log] [blame]
86133aaa3f5d2025-04-20 21:33:29 +08001import * as React from 'react';
2import Icon, * as AntdIcons from '@ant-design/icons';
3import { Radio, Input, Empty } from 'antd';
4import type { RadioChangeEvent } from 'antd/es/radio/interface';
5import debounce from 'lodash/debounce';
6import Category from './Category';
7import IconPicSearcher from './IconPicSearcher';
8import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
9import type { CategoriesKeys } from './fields';
10import { categories } from './fields';
11// import { useIntl } from '@umijs/max';
12
13export enum ThemeType {
14 Filled = 'Filled',
15 Outlined = 'Outlined',
16 TwoTone = 'TwoTone',
17}
18
19const allIcons: { [key: string]: any } = AntdIcons;
20
21interface IconSelectorProps {
22 //intl: any;
23 onSelect: any;
24}
25
26interface IconSelectorState {
27 theme: ThemeType;
28 searchKey: string;
29}
30
31const IconSelector: React.FC<IconSelectorProps> = (props) => {
32 // const intl = useIntl();
33 // const { messages } = intl;
34 const { onSelect } = props;
35 const [displayState, setDisplayState] = React.useState<IconSelectorState>({
36 theme: ThemeType.Outlined,
37 searchKey: '',
38 });
39
40 const newIconNames: string[] = [];
41
42 const handleSearchIcon = React.useCallback(
43 debounce((searchKey: string) => {
44 setDisplayState(prevState => ({ ...prevState, searchKey }));
45 }),
46 [],
47 );
48
49 const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => {
50 setDisplayState(prevState => ({ ...prevState, theme: e.target.value as ThemeType }));
51 }, []);
52
53 const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
54 const { searchKey = '', theme } = displayState;
55
56 const categoriesResult = Object.keys(categories)
57 .map((key: CategoriesKeys) => {
58 let iconList = categories[key];
59 if (searchKey) {
60 const matchKey = searchKey
61 // eslint-disable-next-line prefer-regex-literals
62 .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name)
63 .replace(/(Filled|Outlined|TwoTone)$/, '')
64 .toLowerCase();
65 iconList = iconList.filter((iconName:string) => iconName.toLowerCase().includes(matchKey));
66 }
67
68 // CopyrightCircle is same as Copyright, don't show it
69 iconList = iconList.filter((icon:string) => icon !== 'CopyrightCircle');
70
71 return {
72 category: key,
73 icons: iconList.map((iconName:string) => iconName + theme).filter((iconName:string) => allIcons[iconName]),
74 };
75 })
76 .filter(({ icons }) => !!icons.length)
77 .map(({ category, icons }) => (
78 <Category
79 key={category}
80 title={category as CategoriesKeys}
81 theme={theme}
82 icons={icons}
83 newIcons={newIconNames}
84 onSelect={(type, name) => {
85 if (onSelect) {
86 onSelect(name, allIcons[name]);
87 }
88 }}
89 />
90 ));
91 return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
92 }, [displayState.searchKey, displayState.theme]);
93 return (
94 <>
95 <div style={{ display: 'flex', justifyContent: 'space-between' }}>
96 <Radio.Group
97 value={displayState.theme}
98 onChange={handleChangeTheme}
99 size="large"
100 optionType="button"
101 buttonStyle="solid"
102 options={[
103 {
104 label: <Icon component={OutlinedIcon} />,
105 value: ThemeType.Outlined
106 },
107 {
108 label: <Icon component={FilledIcon} />,
109 value: ThemeType.Filled
110 },
111 {
112 label: <Icon component={TwoToneIcon} />,
113 value: ThemeType.TwoTone
114 },
115 ]}
116 >
117 {/* <Radio.Button value={ThemeType.Outlined}>
118 <Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
119 </Radio.Button>
120 <Radio.Button value={ThemeType.Filled}>
121 <Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
122 </Radio.Button>
123 <Radio.Button value={ThemeType.TwoTone}>
124 <Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
125 </Radio.Button> */}
126 </Radio.Group>
127 <Input.Search
128 // placeholder={messages['app.docs.components.icon.search.placeholder']}
129 style={{ margin: '0 10px', flex: 1 }}
130 allowClear
131 onChange={e => handleSearchIcon(e.currentTarget.value)}
132 size="large"
133 autoFocus
134 suffix={<IconPicSearcher />}
135 />
136 </div>
137 {renderCategories}
138 </>
139 );
140};
141
142export default IconSelector