| @@ -1,37 +0,0 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-28 14:18:11 | |||
| * @Description: 自定义 Table 数组类单元格 | |||
| */ | |||
| import { Tooltip } from 'antd'; | |||
| function ArrayTableCell(ellipsis: boolean = false, property?: string) { | |||
| return (value?: any | null) => { | |||
| if ( | |||
| value === undefined || | |||
| value === null || | |||
| Array.isArray(value) === false || | |||
| value.length === 0 | |||
| ) { | |||
| return <span>--</span>; | |||
| } | |||
| let list = value; | |||
| if (property && typeof value[0] === 'object') { | |||
| list = value.map((item) => item[property]); | |||
| } | |||
| const text = list.join(','); | |||
| if (ellipsis) { | |||
| return ( | |||
| <Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}> | |||
| <span>{text}</span>; | |||
| </Tooltip> | |||
| ); | |||
| } else { | |||
| return <span>{text}</span>; | |||
| } | |||
| }; | |||
| } | |||
| export default ArrayTableCell; | |||
| @@ -1,45 +0,0 @@ | |||
| import { GithubOutlined } from '@ant-design/icons'; | |||
| import { DefaultFooter } from '@ant-design/pro-components'; | |||
| import { useIntl } from '@umijs/max'; | |||
| import React from 'react'; | |||
| const Footer: React.FC = () => { | |||
| const intl = useIntl(); | |||
| const defaultMessage = intl.formatMessage({ | |||
| id: 'app.copyright.produced', | |||
| defaultMessage: '蚂蚁集团体验技术部出品', | |||
| }); | |||
| const currentYear = new Date().getFullYear(); | |||
| return ( | |||
| <DefaultFooter | |||
| style={{ | |||
| background: 'none', | |||
| }} | |||
| copyright={`${currentYear} ${defaultMessage}`} | |||
| links={[ | |||
| { | |||
| key: 'Ant Design Pro', | |||
| title: 'Ant Design Pro', | |||
| href: 'https://pro.ant.design', | |||
| blankTarget: true, | |||
| }, | |||
| { | |||
| key: 'github', | |||
| title: <GithubOutlined />, | |||
| href: 'https://github.com/ant-design/ant-design-pro', | |||
| blankTarget: true, | |||
| }, | |||
| { | |||
| key: 'Ant Design', | |||
| title: 'Ant Design', | |||
| href: 'https://ant.design', | |||
| blankTarget: true, | |||
| }, | |||
| ]} | |||
| /> | |||
| ); | |||
| }; | |||
| export default Footer; | |||
| @@ -1,64 +0,0 @@ | |||
| import { useIntl } from '@umijs/max'; | |||
| import * as React from 'react'; | |||
| import CopyableIcon from './CopyableIcon'; | |||
| import type { CategoriesKeys } from './fields'; | |||
| import type { ThemeType } from './index'; | |||
| import styles from './style.less'; | |||
| interface CategoryProps { | |||
| title: CategoriesKeys; | |||
| icons: string[]; | |||
| theme: ThemeType; | |||
| newIcons: string[]; | |||
| onSelect: (type: string, name: string) => any; | |||
| } | |||
| const Category: React.FC<CategoryProps> = (props) => { | |||
| const { icons, title, newIcons, theme } = props; | |||
| const intl = useIntl(); | |||
| const [justCopied, setJustCopied] = React.useState<string | null>(null); | |||
| const copyId = React.useRef<NodeJS.Timeout | null>(null); | |||
| const onSelect = React.useCallback((type: string, text: string) => { | |||
| const { onSelect } = props; | |||
| if (onSelect) { | |||
| onSelect(type, text); | |||
| } | |||
| setJustCopied(type); | |||
| copyId.current = setTimeout(() => { | |||
| setJustCopied(null); | |||
| }, 2000); | |||
| }, []); | |||
| React.useEffect( | |||
| () => () => { | |||
| if (copyId.current) { | |||
| clearTimeout(copyId.current); | |||
| } | |||
| }, | |||
| [], | |||
| ); | |||
| return ( | |||
| <div> | |||
| <h4> | |||
| {intl.formatMessage({ | |||
| id: `app.docs.components.icon.category.${title}`, | |||
| defaultMessage: '信息', | |||
| })} | |||
| </h4> | |||
| <ul className={styles.anticonsList}> | |||
| {icons.map((name) => ( | |||
| <CopyableIcon | |||
| key={name} | |||
| name={name} | |||
| theme={theme} | |||
| isNew={newIcons.includes(name)} | |||
| justCopied={justCopied} | |||
| onSelect={onSelect} | |||
| /> | |||
| ))} | |||
| </ul> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default Category; | |||
| @@ -1,44 +0,0 @@ | |||
| import * as AntdIcons from '@ant-design/icons'; | |||
| import { Tooltip } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import * as React from 'react'; | |||
| import type { ThemeType } from './index'; | |||
| import styles from './style.less'; | |||
| const allIcons: { | |||
| [key: string]: any; | |||
| } = AntdIcons; | |||
| export interface CopyableIconProps { | |||
| name: string; | |||
| isNew: boolean; | |||
| theme: ThemeType; | |||
| justCopied: string | null; | |||
| onSelect: (type: string, text: string) => any; | |||
| } | |||
| const CopyableIcon: React.FC<CopyableIconProps> = ({ name, justCopied, onSelect, theme }) => { | |||
| const className = classNames({ | |||
| copied: justCopied === name, | |||
| [theme]: !!theme, | |||
| }); | |||
| return ( | |||
| <li | |||
| className={className} | |||
| onClick={() => { | |||
| if (onSelect) { | |||
| onSelect(theme, name); | |||
| } | |||
| }} | |||
| > | |||
| <Tooltip title={name}> | |||
| {React.createElement(allIcons[name], { className: styles.anticon })} | |||
| </Tooltip> | |||
| {/* <span className={styles.anticonClass}> | |||
| <Badge dot={isNew}>{name}</Badge> | |||
| </span> */} | |||
| </li> | |||
| ); | |||
| }; | |||
| export default CopyableIcon; | |||
| @@ -1,237 +0,0 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import * as AntdIcons from '@ant-design/icons'; | |||
| import { useIntl } from '@umijs/max'; | |||
| import { Popover, Progress, Result, Spin, Tooltip, Upload } from 'antd'; | |||
| import React, { useCallback, useEffect, useState } from 'react'; | |||
| import './style.less'; | |||
| const allIcons: { [key: string]: any } = AntdIcons; | |||
| const { Dragger } = Upload; | |||
| interface AntdIconClassifier { | |||
| load: () => void; | |||
| predict: (imgEl: HTMLImageElement) => void; | |||
| } | |||
| declare global { | |||
| interface Window { | |||
| antdIconClassifier: AntdIconClassifier; | |||
| } | |||
| } | |||
| interface PicSearcherState { | |||
| loading: boolean; | |||
| modalOpen: boolean; | |||
| popoverVisible: boolean; | |||
| icons: iconObject[]; | |||
| fileList: any[]; | |||
| error: boolean; | |||
| modelLoaded: boolean; | |||
| } | |||
| interface iconObject { | |||
| type: string; | |||
| score: number; | |||
| } | |||
| const PicSearcher: React.FC = () => { | |||
| const intl = useIntl(); | |||
| const { formatMessage } = intl; | |||
| const [state, setState] = useState<PicSearcherState>({ | |||
| loading: false, | |||
| modalOpen: false, | |||
| popoverVisible: false, | |||
| icons: [], | |||
| fileList: [], | |||
| error: false, | |||
| modelLoaded: false, | |||
| }); | |||
| const predict = (imgEl: HTMLImageElement) => { | |||
| try { | |||
| let icons: any[] = window.antdIconClassifier.predict(imgEl); | |||
| if (gtag && icons.length) { | |||
| gtag('event', 'icon', { | |||
| event_category: 'search-by-image', | |||
| event_label: icons[0].className, | |||
| }); | |||
| } | |||
| icons = icons.map((i) => ({ score: i.score, type: i.className.replace(/\s/g, '-') })); | |||
| setState((prev) => ({ ...prev, loading: false, error: false, icons })); | |||
| } catch { | |||
| setState((prev) => ({ ...prev, loading: false, error: true })); | |||
| } | |||
| }; | |||
| // eslint-disable-next-line class-methods-use-this | |||
| const toImage = (url: string) => | |||
| new Promise((resolve) => { | |||
| const img = new Image(); | |||
| img.setAttribute('crossOrigin', 'anonymous'); | |||
| img.src = url; | |||
| img.onload = () => { | |||
| resolve(img); | |||
| }; | |||
| }); | |||
| const uploadFile = useCallback((file: File) => { | |||
| setState((prev) => ({ ...prev, loading: true })); | |||
| const reader = new FileReader(); | |||
| reader.onload = () => { | |||
| toImage(reader.result as string).then(predict); | |||
| setState((prev) => ({ | |||
| ...prev, | |||
| fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }], | |||
| })); | |||
| }; | |||
| reader.readAsDataURL(file); | |||
| }, []); | |||
| const onPaste = useCallback((event: ClipboardEvent) => { | |||
| const items = event.clipboardData && event.clipboardData.items; | |||
| let file = null; | |||
| if (items && items.length) { | |||
| for (let i = 0; i < items.length; i++) { | |||
| if (items[i].type.includes('image')) { | |||
| file = items[i].getAsFile(); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| if (file) { | |||
| uploadFile(file); | |||
| } | |||
| }, []); | |||
| const toggleModal = useCallback(() => { | |||
| setState((prev) => ({ | |||
| ...prev, | |||
| modalOpen: !prev.modalOpen, | |||
| popoverVisible: false, | |||
| fileList: [], | |||
| icons: [], | |||
| })); | |||
| if (!localStorage.getItem('disableIconTip')) { | |||
| localStorage.setItem('disableIconTip', 'true'); | |||
| } | |||
| }, []); | |||
| useEffect(() => { | |||
| const script = document.createElement('script'); | |||
| script.onload = async () => { | |||
| await window.antdIconClassifier.load(); | |||
| setState((prev) => ({ ...prev, modelLoaded: true })); | |||
| document.addEventListener('paste', onPaste); | |||
| }; | |||
| script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js'; | |||
| document.head.appendChild(script); | |||
| setState((prev) => ({ ...prev, popoverVisible: !localStorage.getItem('disableIconTip') })); | |||
| return () => { | |||
| document.removeEventListener('paste', onPaste); | |||
| }; | |||
| }, []); | |||
| return ( | |||
| <div className="iconPicSearcher"> | |||
| <Popover | |||
| content={formatMessage({ id: 'app.docs.components.icon.pic-searcher.intro' })} | |||
| open={state.popoverVisible} | |||
| > | |||
| <AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} /> | |||
| </Popover> | |||
| <KFModal | |||
| title={intl.formatMessage({ | |||
| id: 'app.docs.components.icon.pic-searcher.title', | |||
| defaultMessage: '信息', | |||
| })} | |||
| open={state.modalOpen} | |||
| onCancel={toggleModal} | |||
| footer={null} | |||
| > | |||
| {state.modelLoaded || ( | |||
| <Spin | |||
| spinning={!state.modelLoaded} | |||
| tip={formatMessage({ | |||
| id: 'app.docs.components.icon.pic-searcher.modelloading', | |||
| })} | |||
| > | |||
| <div style={{ height: 100 }} /> | |||
| </Spin> | |||
| )} | |||
| {state.modelLoaded && ( | |||
| <Dragger | |||
| accept="image/jpeg, image/png" | |||
| listType="picture" | |||
| customRequest={(o) => uploadFile(o.file as File)} | |||
| fileList={state.fileList} | |||
| showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} | |||
| > | |||
| <p className="ant-upload-drag-icon"> | |||
| <AntdIcons.InboxOutlined /> | |||
| </p> | |||
| <p className="ant-upload-text"> | |||
| {formatMessage({ id: 'app.docs.components.icon.pic-searcher.upload-text' })} | |||
| </p> | |||
| <p className="ant-upload-hint"> | |||
| {formatMessage({ id: 'app.docs.components.icon.pic-searcher.upload-hint' })} | |||
| </p> | |||
| </Dragger> | |||
| )} | |||
| <Spin | |||
| spinning={state.loading} | |||
| tip={formatMessage({ id: 'app.docs.components.icon.pic-searcher.matching' })} | |||
| > | |||
| <div className="icon-pic-search-result"> | |||
| {state.icons.length > 0 && ( | |||
| <div className="result-tip"> | |||
| {formatMessage({ id: 'app.docs.components.icon.pic-searcher.result-tip' })} | |||
| </div> | |||
| )} | |||
| <table> | |||
| {state.icons.length > 0 && ( | |||
| <thead> | |||
| <tr> | |||
| <th className="col-icon"> | |||
| {formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-icon' })} | |||
| </th> | |||
| <th> | |||
| {formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-score' })} | |||
| </th> | |||
| </tr> | |||
| </thead> | |||
| )} | |||
| <tbody> | |||
| {state.icons.map((icon) => { | |||
| const { type } = icon; | |||
| const iconName = `${type | |||
| .split('-') | |||
| .map((str) => `${str[0].toUpperCase()}${str.slice(1)}`) | |||
| .join('')}Outlined`; | |||
| return ( | |||
| <tr key={iconName}> | |||
| <td className="col-icon"> | |||
| <Tooltip title={icon.type} placement="right"> | |||
| {React.createElement(allIcons[iconName])} | |||
| </Tooltip> | |||
| </td> | |||
| <td> | |||
| <Progress percent={Math.ceil(icon.score * 100)} /> | |||
| </td> | |||
| </tr> | |||
| ); | |||
| })} | |||
| </tbody> | |||
| </table> | |||
| {state.error && ( | |||
| <Result | |||
| status="500" | |||
| title="503" | |||
| subTitle={formatMessage({ | |||
| id: 'app.docs.components.icon.pic-searcher.server-error', | |||
| })} | |||
| /> | |||
| )} | |||
| </div> | |||
| </Spin> | |||
| </KFModal> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default PicSearcher; | |||
| @@ -1,223 +0,0 @@ | |||
| import * as AntdIcons from '@ant-design/icons/lib/icons'; | |||
| const all = Object.keys(AntdIcons) | |||
| .map((n) => n.replace(/(Outlined|Filled|TwoTone)$/, '')) | |||
| .filter((n, i, arr) => arr.indexOf(n) === i); | |||
| const direction = [ | |||
| 'StepBackward', | |||
| 'StepForward', | |||
| 'FastBackward', | |||
| 'FastForward', | |||
| 'Shrink', | |||
| 'ArrowsAlt', | |||
| 'Down', | |||
| 'Up', | |||
| 'Left', | |||
| 'Right', | |||
| 'CaretUp', | |||
| 'CaretDown', | |||
| 'CaretLeft', | |||
| 'CaretRight', | |||
| 'UpCircle', | |||
| 'DownCircle', | |||
| 'LeftCircle', | |||
| 'RightCircle', | |||
| 'DoubleRight', | |||
| 'DoubleLeft', | |||
| 'VerticalLeft', | |||
| 'VerticalRight', | |||
| 'VerticalAlignTop', | |||
| 'VerticalAlignMiddle', | |||
| 'VerticalAlignBottom', | |||
| 'Forward', | |||
| 'Backward', | |||
| 'Rollback', | |||
| 'Enter', | |||
| 'Retweet', | |||
| 'Swap', | |||
| 'SwapLeft', | |||
| 'SwapRight', | |||
| 'ArrowUp', | |||
| 'ArrowDown', | |||
| 'ArrowLeft', | |||
| 'ArrowRight', | |||
| 'PlayCircle', | |||
| 'UpSquare', | |||
| 'DownSquare', | |||
| 'LeftSquare', | |||
| 'RightSquare', | |||
| 'Login', | |||
| 'Logout', | |||
| 'MenuFold', | |||
| 'MenuUnfold', | |||
| 'BorderBottom', | |||
| 'BorderHorizontal', | |||
| 'BorderInner', | |||
| 'BorderOuter', | |||
| 'BorderLeft', | |||
| 'BorderRight', | |||
| 'BorderTop', | |||
| 'BorderVerticle', | |||
| 'PicCenter', | |||
| 'PicLeft', | |||
| 'PicRight', | |||
| 'RadiusBottomleft', | |||
| 'RadiusBottomright', | |||
| 'RadiusUpleft', | |||
| 'RadiusUpright', | |||
| 'Fullscreen', | |||
| 'FullscreenExit', | |||
| ]; | |||
| const suggestion = [ | |||
| 'Question', | |||
| 'QuestionCircle', | |||
| 'Plus', | |||
| 'PlusCircle', | |||
| 'Pause', | |||
| 'PauseCircle', | |||
| 'Minus', | |||
| 'MinusCircle', | |||
| 'PlusSquare', | |||
| 'MinusSquare', | |||
| 'Info', | |||
| 'InfoCircle', | |||
| 'Exclamation', | |||
| 'ExclamationCircle', | |||
| 'Close', | |||
| 'CloseCircle', | |||
| 'CloseSquare', | |||
| 'Check', | |||
| 'CheckCircle', | |||
| 'CheckSquare', | |||
| 'ClockCircle', | |||
| 'Warning', | |||
| 'IssuesClose', | |||
| 'Stop', | |||
| ]; | |||
| const editor = [ | |||
| 'Edit', | |||
| 'Form', | |||
| 'Copy', | |||
| 'Scissor', | |||
| 'Delete', | |||
| 'Snippets', | |||
| 'Diff', | |||
| 'Highlight', | |||
| 'AlignCenter', | |||
| 'AlignLeft', | |||
| 'AlignRight', | |||
| 'BgColors', | |||
| 'Bold', | |||
| 'Italic', | |||
| 'Underline', | |||
| 'Strikethrough', | |||
| 'Redo', | |||
| 'Undo', | |||
| 'ZoomIn', | |||
| 'ZoomOut', | |||
| 'FontColors', | |||
| 'FontSize', | |||
| 'LineHeight', | |||
| 'Dash', | |||
| 'SmallDash', | |||
| 'SortAscending', | |||
| 'SortDescending', | |||
| 'Drag', | |||
| 'OrderedList', | |||
| 'UnorderedList', | |||
| 'RadiusSetting', | |||
| 'ColumnWidth', | |||
| 'ColumnHeight', | |||
| ]; | |||
| const data = [ | |||
| 'AreaChart', | |||
| 'PieChart', | |||
| 'BarChart', | |||
| 'DotChart', | |||
| 'LineChart', | |||
| 'RadarChart', | |||
| 'HeatMap', | |||
| 'Fall', | |||
| 'Rise', | |||
| 'Stock', | |||
| 'BoxPlot', | |||
| 'Fund', | |||
| 'Sliders', | |||
| ]; | |||
| const logo = [ | |||
| 'Android', | |||
| 'Apple', | |||
| 'Windows', | |||
| 'Ie', | |||
| 'Chrome', | |||
| 'Github', | |||
| 'Aliwangwang', | |||
| 'Dingding', | |||
| 'WeiboSquare', | |||
| 'WeiboCircle', | |||
| 'TaobaoCircle', | |||
| 'Html5', | |||
| 'Weibo', | |||
| 'Twitter', | |||
| 'Wechat', | |||
| 'Youtube', | |||
| 'AlipayCircle', | |||
| 'Taobao', | |||
| 'Skype', | |||
| 'Qq', | |||
| 'MediumWorkmark', | |||
| 'Gitlab', | |||
| 'Medium', | |||
| 'Linkedin', | |||
| 'GooglePlus', | |||
| 'Dropbox', | |||
| 'Facebook', | |||
| 'Codepen', | |||
| 'CodeSandbox', | |||
| 'CodeSandboxCircle', | |||
| 'Amazon', | |||
| 'Google', | |||
| 'CodepenCircle', | |||
| 'Alipay', | |||
| 'AntDesign', | |||
| 'AntCloud', | |||
| 'Aliyun', | |||
| 'Zhihu', | |||
| 'Slack', | |||
| 'SlackSquare', | |||
| 'Behance', | |||
| 'BehanceSquare', | |||
| 'Dribbble', | |||
| 'DribbbleSquare', | |||
| 'Instagram', | |||
| 'Yuque', | |||
| 'Alibaba', | |||
| 'Yahoo', | |||
| 'Reddit', | |||
| 'Sketch', | |||
| 'WhatsApp', | |||
| 'Dingtalk', | |||
| ]; | |||
| const datum = [...direction, ...suggestion, ...editor, ...data, ...logo]; | |||
| const other = all.filter((n) => !datum.includes(n)); | |||
| export const categories = { | |||
| direction, | |||
| suggestion, | |||
| editor, | |||
| data, | |||
| logo, | |||
| other, | |||
| }; | |||
| export default categories; | |||
| export type Categories = typeof categories; | |||
| export type CategoriesKeys = keyof Categories; | |||
| @@ -1,146 +0,0 @@ | |||
| import Icon, * as AntdIcons from '@ant-design/icons'; | |||
| import { Empty, Input, Radio } from 'antd'; | |||
| import type { RadioChangeEvent } from 'antd/es/radio/interface'; | |||
| import debounce from 'lodash/debounce'; | |||
| import * as React from 'react'; | |||
| import Category from './Category'; | |||
| import IconPicSearcher from './IconPicSearcher'; | |||
| import type { CategoriesKeys } from './fields'; | |||
| import { categories } from './fields'; | |||
| import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons'; | |||
| // 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; | |||
| @@ -1,137 +0,0 @@ | |||
| .iconPicSearcher { | |||
| display: inline-block; | |||
| margin: 0 8px; | |||
| .icon-pic-btn { | |||
| color: @text-color-secondary; | |||
| cursor: pointer; | |||
| transition: all 0.3s; | |||
| &:hover { | |||
| color: @input-icon-hover-color; | |||
| } | |||
| } | |||
| } | |||
| .icon-pic-preview { | |||
| width: 30px; | |||
| height: 30px; | |||
| margin-top: 10px; | |||
| padding: 8px; | |||
| text-align: center; | |||
| border: 1px solid @border-color-base; | |||
| border-radius: 4px; | |||
| > img { | |||
| max-width: 50px; | |||
| max-height: 50px; | |||
| } | |||
| } | |||
| .icon-pic-search-result { | |||
| min-height: 50px; | |||
| padding: 0 10px; | |||
| > .result-tip { | |||
| padding: 10px 0; | |||
| color: @text-color-secondary; | |||
| } | |||
| > table { | |||
| width: 100%; | |||
| .col-icon { | |||
| width: 80px; | |||
| padding: 10px 0; | |||
| > .anticon { | |||
| font-size: 30px; | |||
| :hover { | |||
| color: @link-hover-color; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| ul.anticonsList { | |||
| margin: 2px 0; | |||
| overflow: hidden; | |||
| direction: ltr; | |||
| list-style: none; | |||
| li { | |||
| position: relative; | |||
| float: left; | |||
| width: 48px; | |||
| height: 48px; | |||
| margin: 3px 0; | |||
| padding: 2px 0 0; | |||
| overflow: hidden; | |||
| color: #555; | |||
| text-align: center; | |||
| list-style: none; | |||
| background-color: inherit; | |||
| border-radius: 4px; | |||
| cursor: pointer; | |||
| transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out; | |||
| .rtl & { | |||
| margin: 3px 0; | |||
| padding: 2px 0 0; | |||
| } | |||
| .anticon { | |||
| margin: 4px 0 2px; | |||
| font-size: 24px; | |||
| transition: transform 0.3s ease-in-out; | |||
| will-change: transform; | |||
| } | |||
| .anticonClass { | |||
| display: block; | |||
| font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; | |||
| white-space: nowrap; | |||
| text-align: center; | |||
| transform: scale(0.83); | |||
| .ant-badge { | |||
| transition: color 0.3s ease-in-out; | |||
| } | |||
| } | |||
| &:hover { | |||
| color: #fff; | |||
| background-color: @primary-color; | |||
| .anticon { | |||
| transform: scale(1.4); | |||
| } | |||
| .ant-badge { | |||
| color: #fff; | |||
| } | |||
| } | |||
| &.TwoTone:hover { | |||
| background-color: #8ecafe; | |||
| } | |||
| &.copied:hover { | |||
| color: rgba(255, 255, 255, 0.2); | |||
| } | |||
| &.copied::after { | |||
| top: -2px; | |||
| opacity: 1; | |||
| } | |||
| } | |||
| } | |||
| .copied-code { | |||
| padding: 2px 4px; | |||
| font-size: 12px; | |||
| background: #f5f5f5; | |||
| border-radius: 2px; | |||
| } | |||
| @@ -1,40 +0,0 @@ | |||
| import * as React from 'react'; | |||
| export const FilledIcon: React.FC = (props) => { | |||
| const path = | |||
| 'M864 64H160C107 64 64 107 64 160v' + | |||
| '704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' + | |||
| '0c0-53-43-96-96-96z'; | |||
| return ( | |||
| <svg {...props} viewBox="0 0 1024 1024"> | |||
| <path d={path} /> | |||
| </svg> | |||
| ); | |||
| }; | |||
| export const OutlinedIcon: React.FC = (props) => { | |||
| const path = | |||
| 'M864 64H160C107 64 64 107 64 160v7' + | |||
| '04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' + | |||
| '0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' + | |||
| '12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' + | |||
| ' 12 12v680c0 6.6-5.4 12-12 12z'; | |||
| return ( | |||
| <svg {...props} viewBox="0 0 1024 1024"> | |||
| <path d={path} /> | |||
| </svg> | |||
| ); | |||
| }; | |||
| export const TwoToneIcon: React.FC = (props) => { | |||
| const path = | |||
| 'M16 512c0 273.932 222.066 496 496 49' + | |||
| '6s496-222.068 496-496S785.932 16 512 16 16 238.' + | |||
| '066 16 512z m496 368V144c203.41 0 368 164.622 3' + | |||
| '68 368 0 203.41-164.622 368-368 368z'; | |||
| return ( | |||
| <svg {...props} viewBox="0 0 1024 1024"> | |||
| <path d={path} /> | |||
| </svg> | |||
| ); | |||
| }; | |||
| @@ -1,7 +0,0 @@ | |||
| .kf-confirm-modal { | |||
| &__content { | |||
| width: 100%; | |||
| font-size: 18px; | |||
| text-align: center; | |||
| } | |||
| } | |||
| @@ -1,37 +0,0 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-10-10 10:54:25 | |||
| * @Description: 自定义 Confirm Modal | |||
| */ | |||
| import classNames from 'classnames'; | |||
| import KFModal, { KFModalProps } from '../KFModal'; | |||
| import './index.less'; | |||
| export interface KFConfirmModalProps extends KFModalProps { | |||
| content: string; | |||
| } | |||
| function KFConfirmModal({ | |||
| title, | |||
| image, | |||
| className = '', | |||
| centered, | |||
| maskClosable, | |||
| content, | |||
| ...rest | |||
| }: KFConfirmModalProps) { | |||
| return ( | |||
| <KFModal | |||
| className={classNames(['kf-confirm-modal', className])} | |||
| {...rest} | |||
| centered={centered ?? true} | |||
| maskClosable={maskClosable ?? false} | |||
| title={title} | |||
| image={image ?? require('@/assets/img/edit-experiment.png')} | |||
| > | |||
| <div className="kf-confirm-modal__content">{content}</div> | |||
| </KFModal> | |||
| ); | |||
| } | |||
| export default KFConfirmModal; | |||