| | |
| | | import * as React from 'react'; |
| | | import Icon, * as AntdIcons from '@ant-design/icons'; |
| | | import { Radio, Input, Empty } from 'antd'; |
| | | import type { RadioChangeEvent } from 'antd/es/radio/interface'; |
| | | import debounce from 'lodash/debounce'; |
| | | import Category from './Category'; |
| | | import IconPicSearcher from './IconPicSearcher'; |
| | | import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons'; |
| | | import type { CategoriesKeys } from './fields'; |
| | | import { categories } from './fields'; |
| | | // import { useIntl } from '@umijs/max'; |
| | | |
| | | export enum ThemeType { |
| | | Filled = 'Filled', |
| | | Outlined = 'Outlined', |
| | | TwoTone = 'TwoTone', |
| | | } |
| | | |
| | | const allIcons: { [key: string]: any } = AntdIcons; |
| | | |
| | | interface IconSelectorProps { |
| | | //intl: any; |
| | | onSelect: any; |
| | | } |
| | | |
| | | interface IconSelectorState { |
| | | theme: ThemeType; |
| | | searchKey: string; |
| | | } |
| | | |
| | | const IconSelector: React.FC<IconSelectorProps> = (props) => { |
| | | // const intl = useIntl(); |
| | | // const { messages } = intl; |
| | | const { onSelect } = props; |
| | | const [displayState, setDisplayState] = React.useState<IconSelectorState>({ |
| | | theme: ThemeType.Outlined, |
| | | searchKey: '', |
| | | }); |
| | | |
| | | const newIconNames: string[] = []; |
| | | |
| | | const handleSearchIcon = React.useCallback( |
| | | debounce((searchKey: string) => { |
| | | setDisplayState(prevState => ({ ...prevState, searchKey })); |
| | | }), |
| | | [], |
| | | ); |
| | | |
| | | const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => { |
| | | setDisplayState(prevState => ({ ...prevState, theme: e.target.value as ThemeType })); |
| | | }, []); |
| | | |
| | | const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => { |
| | | const { searchKey = '', theme } = displayState; |
| | | |
| | | const categoriesResult = Object.keys(categories) |
| | | .map((key: CategoriesKeys) => { |
| | | let iconList = categories[key]; |
| | | if (searchKey) { |
| | | const matchKey = searchKey |
| | | // eslint-disable-next-line prefer-regex-literals |
| | | .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name) |
| | | .replace(/(Filled|Outlined|TwoTone)$/, '') |
| | | .toLowerCase(); |
| | | iconList = iconList.filter((iconName:string) => iconName.toLowerCase().includes(matchKey)); |
| | | } |
| | | |
| | | // CopyrightCircle is same as Copyright, don't show it |
| | | iconList = iconList.filter((icon:string) => icon !== 'CopyrightCircle'); |
| | | |
| | | return { |
| | | category: key, |
| | | icons: iconList.map((iconName:string) => iconName + theme).filter((iconName:string) => allIcons[iconName]), |
| | | }; |
| | | }) |
| | | .filter(({ icons }) => !!icons.length) |
| | | .map(({ category, icons }) => ( |
| | | <Category |
| | | key={category} |
| | | title={category as CategoriesKeys} |
| | | theme={theme} |
| | | icons={icons} |
| | | newIcons={newIconNames} |
| | | onSelect={(type, name) => { |
| | | if (onSelect) { |
| | | onSelect(name, allIcons[name]); |
| | | } |
| | | }} |
| | | /> |
| | | )); |
| | | return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult; |
| | | }, [displayState.searchKey, displayState.theme]); |
| | | return ( |
| | | <> |
| | | <div style={{ display: 'flex', justifyContent: 'space-between' }}> |
| | | <Radio.Group |
| | | value={displayState.theme} |
| | | onChange={handleChangeTheme} |
| | | size="large" |
| | | optionType="button" |
| | | buttonStyle="solid" |
| | | options={[ |
| | | { |
| | | label: <Icon component={OutlinedIcon} />, |
| | | value: ThemeType.Outlined |
| | | }, |
| | | { |
| | | label: <Icon component={FilledIcon} />, |
| | | value: ThemeType.Filled |
| | | }, |
| | | { |
| | | label: <Icon component={TwoToneIcon} />, |
| | | value: ThemeType.TwoTone |
| | | }, |
| | | ]} |
| | | > |
| | | {/* <Radio.Button value={ThemeType.Outlined}> |
| | | <Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']} |
| | | </Radio.Button> |
| | | <Radio.Button value={ThemeType.Filled}> |
| | | <Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']} |
| | | </Radio.Button> |
| | | <Radio.Button value={ThemeType.TwoTone}> |
| | | <Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']} |
| | | </Radio.Button> */} |
| | | </Radio.Group> |
| | | <Input.Search |
| | | // placeholder={messages['app.docs.components.icon.search.placeholder']} |
| | | style={{ margin: '0 10px', flex: 1 }} |
| | | allowClear |
| | | onChange={e => handleSearchIcon(e.currentTarget.value)} |
| | | size="large" |
| | | autoFocus |
| | | suffix={<IconPicSearcher />} |
| | | /> |
| | | </div> |
| | | {renderCategories} |
| | | </> |
| | | ); |
| | | }; |
| | | |
| | | export default IconSelector |
| | | import * as React from 'react';
|
| | | import Icon, * as AntdIcons from '@ant-design/icons';
|
| | | import { Radio, Input, Empty } from 'antd';
|
| | | import type { RadioChangeEvent } from 'antd/es/radio/interface';
|
| | | import debounce from 'lodash/debounce';
|
| | | import Category from './Category';
|
| | | import IconPicSearcher from './IconPicSearcher';
|
| | | import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
|
| | | import type { CategoriesKeys } from './fields';
|
| | | import { categories } from './fields';
|
| | | // import { useIntl } from '@umijs/max';
|
| | |
|
| | | export enum ThemeType {
|
| | | Filled = 'Filled',
|
| | | Outlined = 'Outlined',
|
| | | TwoTone = 'TwoTone',
|
| | | }
|
| | |
|
| | | const allIcons: { [key: string]: any } = AntdIcons;
|
| | |
|
| | | interface IconSelectorProps {
|
| | | //intl: any;
|
| | | onSelect: any;
|
| | | }
|
| | |
|
| | | interface IconSelectorState {
|
| | | theme: ThemeType;
|
| | | searchKey: string;
|
| | | }
|
| | |
|
| | | const IconSelector: React.FC<IconSelectorProps> = (props) => {
|
| | | // const intl = useIntl();
|
| | | // const { messages } = intl;
|
| | | const { onSelect } = props;
|
| | | const [displayState, setDisplayState] = React.useState<IconSelectorState>({
|
| | | theme: ThemeType.Outlined,
|
| | | searchKey: '',
|
| | | });
|
| | |
|
| | | const newIconNames: string[] = [];
|
| | |
|
| | | const handleSearchIcon = React.useCallback(
|
| | | debounce((searchKey: string) => {
|
| | | setDisplayState(prevState => ({ ...prevState, searchKey }));
|
| | | }),
|
| | | [],
|
| | | );
|
| | |
|
| | | const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => {
|
| | | setDisplayState(prevState => ({ ...prevState, theme: e.target.value as ThemeType }));
|
| | | }, []);
|
| | |
|
| | | const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
|
| | | const { searchKey = '', theme } = displayState;
|
| | |
|
| | | const categoriesResult = Object.keys(categories)
|
| | | .map((key: CategoriesKeys) => {
|
| | | let iconList = categories[key];
|
| | | if (searchKey) {
|
| | | const matchKey = searchKey
|
| | | // eslint-disable-next-line prefer-regex-literals
|
| | | .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name)
|
| | | .replace(/(Filled|Outlined|TwoTone)$/, '')
|
| | | .toLowerCase();
|
| | | iconList = iconList.filter((iconName:string) => iconName.toLowerCase().includes(matchKey));
|
| | | }
|
| | |
|
| | | // CopyrightCircle is same as Copyright, don't show it
|
| | | iconList = iconList.filter((icon:string) => icon !== 'CopyrightCircle');
|
| | |
|
| | | return {
|
| | | category: key,
|
| | | icons: iconList.map((iconName:string) => iconName + theme).filter((iconName:string) => allIcons[iconName]),
|
| | | };
|
| | | })
|
| | | .filter(({ icons }) => !!icons.length)
|
| | | .map(({ category, icons }) => (
|
| | | <Category
|
| | | key={category}
|
| | | title={category as CategoriesKeys}
|
| | | theme={theme}
|
| | | icons={icons}
|
| | | newIcons={newIconNames}
|
| | | onSelect={(type, name) => {
|
| | | if (onSelect) {
|
| | | onSelect(name, allIcons[name]);
|
| | | }
|
| | | }}
|
| | | />
|
| | | ));
|
| | | return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
|
| | | }, [displayState.searchKey, displayState.theme]);
|
| | | return (
|
| | | <>
|
| | | <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
| | | <Radio.Group
|
| | | value={displayState.theme}
|
| | | onChange={handleChangeTheme}
|
| | | size="large"
|
| | | optionType="button"
|
| | | buttonStyle="solid"
|
| | | options={[
|
| | | {
|
| | | label: <Icon component={OutlinedIcon} />,
|
| | | value: ThemeType.Outlined
|
| | | },
|
| | | {
|
| | | label: <Icon component={FilledIcon} />,
|
| | | value: ThemeType.Filled
|
| | | },
|
| | | {
|
| | | label: <Icon component={TwoToneIcon} />,
|
| | | value: ThemeType.TwoTone
|
| | | },
|
| | | ]}
|
| | | >
|
| | | {/* <Radio.Button value={ThemeType.Outlined}>
|
| | | <Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
|
| | | </Radio.Button>
|
| | | <Radio.Button value={ThemeType.Filled}>
|
| | | <Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
|
| | | </Radio.Button>
|
| | | <Radio.Button value={ThemeType.TwoTone}>
|
| | | <Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
|
| | | </Radio.Button> */}
|
| | | </Radio.Group>
|
| | | <Input.Search
|
| | | // placeholder={messages['app.docs.components.icon.search.placeholder']}
|
| | | style={{ margin: '0 10px', flex: 1 }}
|
| | | allowClear
|
| | | onChange={e => handleSearchIcon(e.currentTarget.value)}
|
| | | size="large"
|
| | | autoFocus
|
| | | suffix={<IconPicSearcher />}
|
| | | />
|
| | | </div>
|
| | | {renderCategories}
|
| | | </>
|
| | | );
|
| | | };
|
| | |
|
| | | export default IconSelector
|