'init_again'
Change-Id: Ib7ecdb9f5baeab1e4681152a57b936edf7475b35
diff --git a/src/components/IconSelector/Category.tsx b/src/components/IconSelector/Category.tsx
new file mode 100644
index 0000000..2391687
--- /dev/null
+++ b/src/components/IconSelector/Category.tsx
@@ -0,0 +1,63 @@
+import * as React from 'react';
+import CopyableIcon from './CopyableIcon';
+import type { ThemeType } from './index';
+import type { CategoriesKeys } from './fields';
+import { useIntl } from '@umijs/max';
+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(Number(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;
diff --git a/src/components/IconSelector/CopyableIcon.tsx b/src/components/IconSelector/CopyableIcon.tsx
new file mode 100644
index 0000000..a0d9656
--- /dev/null
+++ b/src/components/IconSelector/CopyableIcon.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import { Tooltip } from 'antd';
+import classNames from 'classnames';
+import * as AntdIcons from '@ant-design/icons';
+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;
diff --git a/src/components/IconSelector/IconPicSearcher.tsx b/src/components/IconSelector/IconPicSearcher.tsx
new file mode 100644
index 0000000..3a4cf01
--- /dev/null
+++ b/src/components/IconSelector/IconPicSearcher.tsx
@@ -0,0 +1,233 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { Upload, Tooltip, Popover, Modal, Progress, Spin, Result } from 'antd';
+import * as AntdIcons from '@ant-design/icons';
+import { useIntl } from '@umijs/max';
+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>
+ <Modal
+ 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>
+ </Modal>
+ </div>
+ );
+};
+
+export default PicSearcher;
diff --git a/src/components/IconSelector/fields.ts b/src/components/IconSelector/fields.ts
new file mode 100644
index 0000000..de37e67
--- /dev/null
+++ b/src/components/IconSelector/fields.ts
@@ -0,0 +1,223 @@
+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;
diff --git a/src/components/IconSelector/index.tsx b/src/components/IconSelector/index.tsx
new file mode 100644
index 0000000..78dc931
--- /dev/null
+++ b/src/components/IconSelector/index.tsx
@@ -0,0 +1,142 @@
+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
diff --git a/src/components/IconSelector/style.less b/src/components/IconSelector/style.less
new file mode 100644
index 0000000..0a4353d
--- /dev/null
+++ b/src/components/IconSelector/style.less
@@ -0,0 +1,137 @@
+.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;
+}
diff --git a/src/components/IconSelector/themeIcons.tsx b/src/components/IconSelector/themeIcons.tsx
new file mode 100644
index 0000000..abefe04
--- /dev/null
+++ b/src/components/IconSelector/themeIcons.tsx
@@ -0,0 +1,41 @@
+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>
+ );
+};