Jiarenxiang | 38dcb05 | 2025-03-13 16:40:09 +0800 | [diff] [blame] | 1 | import * as React from 'react'; |
| 2 | import Icon, * as AntdIcons from '@ant-design/icons'; |
| 3 | import { Radio, Input, Empty } from 'antd'; |
| 4 | import type { RadioChangeEvent } from 'antd/es/radio/interface'; |
| 5 | import debounce from 'lodash/debounce'; |
| 6 | import Category from './Category'; |
| 7 | import IconPicSearcher from './IconPicSearcher'; |
| 8 | import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons'; |
| 9 | import type { CategoriesKeys } from './fields'; |
| 10 | import { categories } from './fields'; |
| 11 | // import { useIntl } from '@umijs/max'; |
| 12 | |
| 13 | export enum ThemeType { |
| 14 | Filled = 'Filled', |
| 15 | Outlined = 'Outlined', |
| 16 | TwoTone = 'TwoTone', |
| 17 | } |
| 18 | |
| 19 | const allIcons: { [key: string]: any } = AntdIcons; |
| 20 | |
| 21 | interface IconSelectorProps { |
| 22 | //intl: any; |
| 23 | onSelect: any; |
| 24 | } |
| 25 | |
| 26 | interface IconSelectorState { |
| 27 | theme: ThemeType; |
| 28 | searchKey: string; |
| 29 | } |
| 30 | |
| 31 | const 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 | |
| 142 | export default IconSelector |