mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
feat: iconPicker support more icon (#5996)
* refactor: iconpicker support more icon * fix: test * fix: test * fix: test
This commit is contained in:
parent
7abe820950
commit
cbdd5ffe8c
@ -33,9 +33,7 @@ export function registerIcons(components) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(antIcons).forEach((name) => {
|
Object.keys(antIcons).forEach((name) => {
|
||||||
if (name.endsWith('Outlined')) {
|
registerIcon(name, antIcons[name]);
|
||||||
registerIcon(name, antIcons[name]);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IconProps {
|
interface IconProps {
|
||||||
|
@ -1047,5 +1047,8 @@
|
|||||||
"Associate": "关联",
|
"Associate": "关联",
|
||||||
"Please add or select record": "请添加或选择数据",
|
"Please add or select record": "请添加或选择数据",
|
||||||
"No data": "暂无数据",
|
"No data": "暂无数据",
|
||||||
"Fields can only be used correctly if they are defined with an interface.": "只有字段设置了interface字段才能正常使用"
|
"Fields can only be used correctly if they are defined with an interface.": "只有字段设置了interface字段才能正常使用",
|
||||||
}
|
"Outlined": "线框风格",
|
||||||
|
"Filled": "实底风格",
|
||||||
|
"Two tone": "双色风格"
|
||||||
|
}
|
||||||
|
@ -11,8 +11,8 @@ import { CloseOutlined, LoadingOutlined } from '@ant-design/icons';
|
|||||||
import { useFormLayout } from '@formily/antd-v5';
|
import { useFormLayout } from '@formily/antd-v5';
|
||||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
import { isValid } from '@formily/shared';
|
import { isValid } from '@formily/shared';
|
||||||
import { Button, Empty, Input, Space, theme } from 'antd';
|
import { Button, Empty, Input, Space, theme, Radio, Flex } from 'antd';
|
||||||
import { debounce } from 'lodash';
|
import { debounce, groupBy } from 'lodash';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon, hasIcon, icons } from '../../../icon';
|
import { Icon, hasIcon, icons } from '../../../icon';
|
||||||
@ -33,6 +33,14 @@ interface IconPickerReadPrettyProps {
|
|||||||
value?: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupByIconName = (data) => {
|
||||||
|
return groupBy(data, (str) => {
|
||||||
|
if (str.endsWith('outlined')) return 'Outlined';
|
||||||
|
if (str.endsWith('filled')) return 'Filled';
|
||||||
|
if (str.endsWith('twotone')) return 'TwoTone';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function IconField(props: IconPickerProps) {
|
function IconField(props: IconPickerProps) {
|
||||||
const { fontSizeXL } = theme.useToken().token;
|
const { fontSizeXL } = theme.useToken().token;
|
||||||
const availableIcons = [...icons.keys()];
|
const availableIcons = [...icons.keys()];
|
||||||
@ -40,7 +48,9 @@ function IconField(props: IconPickerProps) {
|
|||||||
const { value, onChange, disabled, iconSize = fontSizeXL, searchable = true } = props;
|
const { value, onChange, disabled, iconSize = fontSizeXL, searchable = true } = props;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [filteredIcons, setFilteredIcons] = useState(availableIcons);
|
const [filteredIcons, setFilteredIcons] = useState(availableIcons);
|
||||||
|
const [type, setType] = useState('Outlined');
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const groupIconData = groupByIconName(availableIcons);
|
||||||
|
|
||||||
const style: any = {
|
const style: any = {
|
||||||
width: '26em',
|
width: '26em',
|
||||||
@ -57,6 +67,52 @@ function IconField(props: IconPickerProps) {
|
|||||||
);
|
);
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
|
const IconContent = () => {
|
||||||
|
return (
|
||||||
|
<Flex vertical gap="middle">
|
||||||
|
<Radio.Group
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: t('Outlined'),
|
||||||
|
value: 'Outlined',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Filled'),
|
||||||
|
value: 'Filled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Two tone'),
|
||||||
|
value: 'TwoTone',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={type}
|
||||||
|
optionType="button"
|
||||||
|
onChange={(e) => {
|
||||||
|
setType(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
{groupIconData[type].map((key) => {
|
||||||
|
if (filteredIcons.includes(key)) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={key}
|
||||||
|
title={key.replace(/outlined|filled|twotone$/i, '')}
|
||||||
|
style={{ fontSize: iconSize, marginRight: 10, cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onChange(key);
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type={key} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Space.Compact>
|
<Space.Compact>
|
||||||
@ -71,28 +127,11 @@ function IconField(props: IconPickerProps) {
|
|||||||
}}
|
}}
|
||||||
content={
|
content={
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
{filteredIcons.length === 0 ? (
|
{filteredIcons.length === 0 ? <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> : <IconContent />}
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
|
||||||
) : (
|
|
||||||
filteredIcons.map((key) => (
|
|
||||||
<span
|
|
||||||
key={key}
|
|
||||||
title={key.replace(/outlined|filled|twotone$/i, '')}
|
|
||||||
style={{ fontSize: iconSize, marginRight: 10, cursor: 'pointer' }}
|
|
||||||
onClick={() => {
|
|
||||||
onChange(key);
|
|
||||||
setVisible(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon type={key} />
|
|
||||||
</span>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
title={
|
title={
|
||||||
<div>
|
<div>
|
||||||
<div>{t('Icon')}</div>
|
|
||||||
{searchable && (
|
{searchable && (
|
||||||
<Search
|
<Search
|
||||||
style={{ marginTop: 8 }}
|
style={{ marginTop: 8 }}
|
||||||
|
@ -18,7 +18,6 @@ describe('IconPicker', () => {
|
|||||||
const button = container.querySelector('button') as HTMLButtonElement;
|
const button = container.querySelector('button') as HTMLButtonElement;
|
||||||
await userEvent.click(button);
|
await userEvent.click(button);
|
||||||
|
|
||||||
expect(screen.getByText('Icon')).toHaveTextContent(`Icon`);
|
|
||||||
expect(screen.queryAllByRole('img').length).toBe(422);
|
expect(screen.queryAllByRole('img').length).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export async function iconChecker(options: IconCheckOptions) {
|
|||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expectNoTsError(screen.queryByRole('tooltip')).toBeInTheDocument();
|
expectNoTsError(screen.queryByRole('tooltip')).toBeInTheDocument();
|
||||||
expectNoTsError(screen.getByRole('tooltip').querySelector('.ant-popover-title')).toHaveTextContent('Icon');
|
// expectNoTsError(screen.getByRole('tooltip').querySelector('.ant-popover-title')).toHaveTextContent('Icon');
|
||||||
});
|
});
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('tooltip').querySelector(`span[aria-label="${options.newValue}"]`));
|
await userEvent.click(screen.getByRole('tooltip').querySelector(`span[aria-label="${options.newValue}"]`));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user