Browse Source

upgrade Icon api

pull/11322/head
HeskeyBaozi 6 years ago
parent
commit
7925355ca7
  1. 117
      components/icon/CustomIcon.tsx
  2. 116
      components/icon/IconFont.tsx
  3. 135
      components/icon/__tests__/__snapshots__/index.test.js.snap
  4. 111
      components/icon/__tests__/index.test.js
  5. 12
      components/icon/demo/ant-design-text.svg
  6. 43
      components/icon/demo/assets/ant-design.svg
  7. 9
      components/icon/demo/assets/heart.svg
  8. 98
      components/icon/demo/custom.md
  9. 10
      components/icon/demo/iconfont.md
  10. 124
      components/icon/index.tsx
  11. 9
      components/icon/utils.ts
  12. 1
      package.json
  13. 51
      site/bisheng.config.js

117
components/icon/CustomIcon.tsx

@ -1,117 +0,0 @@
import * as React from 'react';
import classNames from 'classnames';
import { Omit } from '../_util/type';
import { IconProps } from './index';
import { getComputedSvgStyle } from './utils';
export interface CustomIconProps extends Omit<IconProps, 'type'> {
svgType?: string | SpriteSvgIcon;
viewBox?: string;
component?: React.ComponentType<CustomIconComponentProps>;
}
export interface CustomIconComponentProps {
width: string | number;
height: string | number;
fill: string;
viewBox: string;
className?: string;
style?: React.CSSProperties;
}
export interface SpriteSvgIcon {
id: string;
viewBox?: string;
[key: string]: any;
}
// These props make sure that the SVG behaviours like general text.
// Reference: https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
export const svgBaseProps = {
width: '1em',
height: '1em',
fill: 'currentColor',
['aria-hidden']: 'true',
};
const CustomIcon: React.SFC<CustomIconProps> = (props) => {
const {
svgType: spriteSvgIcon,
className = '',
spin,
// ⬇️ Todo, what's the best default value?
// ⬇️ "0 0 24 24" for material-ui or "0 0 1024 1024" for ant-design
viewBox = '0 0 1024 1024',
children,
svgClassName,
rotate,
flip,
style,
svgStyle = {},
tag = 'i',
onClick,
component: Component,
} = props;
const classString = classNames({
anticon: true,
}, className);
const svgClassString = classNames({
svgClassName,
[`anticon-spin`]: !!spin,
});
const innerSvgProps = {
...svgBaseProps,
viewBox,
className: svgClassString,
style: getComputedSvgStyle({ rotate, flip }, svgStyle),
};
let content = (
<svg {...innerSvgProps}>
{children}
</svg>
);
if (spriteSvgIcon) {
if (isSVGSpriteObject(spriteSvgIcon)) {
content = (
<svg {...innerSvgProps} viewBox={spriteSvgIcon.viewBox || viewBox}>
<use xlinkHref={`#${spriteSvgIcon.id}`}/>
</svg>
);
} else { // typeof spriteSvgIcon === 'string'
content = (
<svg {...innerSvgProps} viewBox={viewBox}>
<use xlinkHref={`#${spriteSvgIcon}`}/>
</svg>
);
}
}
if (Component) {
content = (
<Component {...innerSvgProps}>
{children}
</Component>
);
}
return React.createElement(
tag,
{
className: classString,
style,
onClick,
},
content,
);
};
function isSVGSpriteObject(obj: any): obj is SpriteSvgIcon {
return typeof obj.id === 'string';
}
export default CustomIcon;

116
components/icon/IconFont.tsx

@ -1,8 +1,5 @@
import { IconProps } from './index';
import Icon, { IconProps } from './index';
import * as React from 'react';
import classNames from 'classnames';
import { svgBaseProps } from './CustomIcon';
import { getComputedSvgStyle } from './utils';
const customCache = new Set<string>();
@ -13,85 +10,50 @@ export interface CustomIconOptions {
extraCommonProps?: { [key: string]: any };
}
export interface IconFontProps extends IconProps {
viewBox?: string;
}
export default function create(options: CustomIconOptions = {}): React.ComponentClass<IconFontProps> {
export default function create(options: CustomIconOptions = {}): React.SFC<IconProps> {
const { namespace, prefix = '', scriptUrl, extraCommonProps = {} } = options;
class Custom extends React.Component<IconFontProps> {
render() {
const {
type,
className = '',
spin,
flip,
svgClassName,
tag = 'i',
onClick,
style,
rotate = 0,
svgStyle = {},
viewBox = '0 0 1024 1024',
} = this.props;
const classString = classNames(
{
anticon: true,
},
className,
);
const svgClassString = classNames({
svgClassName,
[`anticon-spin`]: !!spin,
});
/**
* DOM API required.
* Make sure in browser environment.
* The Custom Icon will create a <script/>
* that loads SVG symbols and insert the SVG Element into the document body.
*/
if (typeof document !== 'undefined' && typeof window !== 'undefined'
&& typeof document.createElement === 'function'
&& typeof scriptUrl === 'string' && scriptUrl.length
&& typeof namespace === 'string' && namespace.length
&& !customCache.has(namespace)
) {
const script = document.createElement('script');
script.setAttribute('src', scriptUrl);
script.setAttribute('data-namespace', namespace);
customCache.add(namespace);
document.body.appendChild(script);
}
const innerSvgProps = {
...svgBaseProps,
viewBox,
className: svgClassString,
style: getComputedSvgStyle({ rotate, flip }, svgStyle),
};
const Custom: React.SFC<IconProps> = (props) => {
const { type } = props;
return React.createElement(
tag,
{
className: classString,
style,
onClick,
},
<svg
{...extraCommonProps}
{...innerSvgProps}
>
<use xlinkHref={`#${prefix}${type}`}/>
</svg>,
);
// component > children > type
let content = null;
if (props.type) {
content = <use xlinkHref={`#${prefix}${type}`} />;
}
componentDidMount() {
/**
* DOM API required.
* Make sure in browser environment.
* The Custom Icon will create a <script/>
* that loads SVG symbols and insert the SVG Element into the document body.
*/
if (typeof document !== 'undefined' && typeof window !== 'undefined'
&& typeof document.createElement === 'function'
&& typeof scriptUrl === 'string' && scriptUrl.length
&& typeof namespace === 'string' && namespace.length
&& !customCache.has(namespace)
) {
const script = document.createElement('script');
script.setAttribute('src', scriptUrl);
script.setAttribute('data-namespace', namespace);
customCache.add(namespace);
document.body.appendChild(script);
}
if (props.children) {
content = props.children;
}
}
return (
<Icon
{...props}
{...extraCommonProps}
>
{content}
</Icon>
);
};
Custom.displayName = 'CreatedIcon';
return Custom;
}

135
components/icon/__tests__/__snapshots__/index.test.js.snap

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustomIcon should render custom icon correctly 1`] = `
exports[`Icon \`component\` prop can access to svg defs if has children 1`] = `
<i
class="anticon my-home-icon"
>
@ -9,29 +9,7 @@ exports[`CustomIcon should render custom icon correctly 1`] = `
class=""
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
>
<title>
Cool Home
</title>
<path
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
/>
</svg>
</i>
`;
exports[`CustomIcon should support component prop 1`] = `
<i
class="anticon my-home-icon"
>
<svg
aria-hidden="true"
class=""
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
viewBox="0 0 1024 1024"
width="1em"
>
<defs>
@ -52,14 +30,20 @@ exports[`CustomIcon should support component prop 1`] = `
Cool Home
</title>
<path
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
d="'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'"
fill="url(#gradient)"
/>
</svg>
</i>
`;
exports[`Icon should render correctly with rotate, flip, viewBox props. 1`] = `
exports[`Icon should give warning and render <i>{null}</i> 1`] = `
<i
class="anticon"
/>
`;
exports[`Icon should render correctly with rotate, flip, viewBox props 1`] = `
<i
class="anticon anticon-setting"
>
@ -129,3 +113,102 @@ exports[`Icon should render to a <i class="xxx"><svg>...</svg></i> 1`] = `
</svg>
</i>
`;
exports[`Icon should support pass svg paths as children 1`] = `
<i
class="anticon"
>
<svg
aria-hidden="true"
class=""
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
>
<title>
Cool Home
</title>
<path
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
/>
</svg>
</i>
`;
exports[`Icon should support svg react component 1`] = `
<i
class="anticon my-home-icon"
>
<svg
aria-hidden="true"
class=""
fill="currentColor"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<title>
Cool Home
</title>
<path
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
/>
</svg>
</i>
`;
exports[`Icon.create() should support iconfont.cn 1`] = `
<div
class="icons-list"
>
<i
class="anticon anticon-tuichu"
>
<svg
aria-hidden="true"
class=""
fill="currentColor"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<use
href="#icon-tuichu"
/>
</svg>
</i>
<i
class="anticon anticon-facebook"
>
<svg
aria-hidden="true"
class=""
fill="currentColor"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<use
href="#icon-facebook"
/>
</svg>
</i>
<i
class="anticon anticon-twitter"
>
<svg
aria-hidden="true"
class=""
fill="currentColor"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<use
href="#icon-twitter"
/>
</svg>
</i>
</div>
`;

111
components/icon/__tests__/index.test.js

@ -10,57 +10,98 @@ describe('Icon', () => {
expect(wrapper).toMatchSnapshot();
});
it('should render correctly with rotate, flip, viewBox props.', () => {
it('should render correctly with rotate, flip, viewBox props', () => {
const wrapper = render(
<Icon type="setting" rotate={127} flip="both" viewBox="0 0 24 24" />
);
expect(wrapper).toMatchSnapshot();
});
});
describe('CustomIcon', () => {
const path = 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z';
function HomeIcon(props) {
return (
<Icon.CustomIcon {...props} viewBox="0 0 24 24">
it('should support pass svg paths as children', () => {
const wrapper = render(
<Icon viewBox="0 0 24 24">
<title>Cool Home</title>
<path d={path} />
</Icon.CustomIcon>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</Icon>
);
}
expect(wrapper).toMatchSnapshot();
});
it('should render custom icon correctly', () => {
it('should give warning and render <i>{null}</i>', () => {
const wrapper = render(
<HomeIcon className="my-home-icon" />
<Icon viewBox="0 0 24 24" />
);
expect(wrapper).toMatchSnapshot();
});
it('should support component prop', () => {
describe('`component` prop', () => {
it('can access to svg defs if has children', () => {
const wrapper = render(
<Icon
className="my-home-icon"
component={svgProps => (
<svg {...svgProps}>
<defs>
<linearGradient id="gradient">
<stop offset="20%" stopColor="#39F" />
<stop offset="90%" stopColor="#F3F" />
</linearGradient>
</defs>
{
React.Children.map(
svgProps.children,
child => React.cloneElement(
child,
child.type === 'path' ? { fill: 'url(#gradient)' } : {}
)
)
}
</svg>
)}
>
<title>Cool Home</title>
<path d="'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'" />
</Icon>
);
expect(wrapper).toMatchSnapshot();
});
});
it('should support svg react component', () => {
const SvgComponent = props => (
<svg viewBox="0 0 24 24" {...props}>
<title>Cool Home</title>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</svg>
);
const wrapper = render(
<HomeIcon
<Icon
className="my-home-icon"
component={svgProps => (
<svg {...svgProps}>
<defs>
<linearGradient id="gradient">
<stop offset="20%" stopColor="#39F" />
<stop offset="90%" stopColor="#F3F" />
</linearGradient>
</defs>
{
React.Children.map(
svgProps.children,
child => React.cloneElement(
child,
child.type === 'path' ? { fill: 'url(#gradient)' } : {}
)
)
}
</svg>
)}
/>
component={SvgComponent}
>
<title>Cool Home</title>
<path d="'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'" />
</Icon>
);
expect(wrapper).toMatchSnapshot();
});
});
describe('Icon.create()', () => {
const IconFont = Icon.create({
namespace: 'iconfont-foo',
scriptUrl: 'https://at.alicdn.com/t/font_8d5l8fzk5b87iudi.js',
prefix: 'icon-',
});
it('should support iconfont.cn', () => {
const wrapper = render(
<div className="icons-list">
<IconFont type="tuichu" />
<IconFont type="facebook" />
<IconFont type="twitter" />
</div>
);
expect(wrapper).toMatchSnapshot();
});

12
components/icon/demo/ant-design-text.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.7 KiB

43
components/icon/demo/assets/ant-design.svg

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
<title>Group 28 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="62.1023273%" y1="0%" x2="108.19718%" y2="37.8635764%" id="linearGradient-1">
<stop stop-color="#4285EB" offset="0%"></stop>
<stop stop-color="#2EC7FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="69.644116%" y1="0%" x2="54.0428975%" y2="108.456714%" id="linearGradient-2">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#148EFF" offset="37.8600687%"></stop>
<stop stop-color="#0A60FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="69.6908165%" y1="-12.9743587%" x2="16.7228981%" y2="117.391248%" id="linearGradient-3">
<stop stop-color="#FA816E" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="41.472606%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
<linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-4">
<stop stop-color="#FA8E7D" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="51.2635191%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo" transform="translate(-20.000000, -20.000000)">
<g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)">
<g id="Group-27-Copy-3">
<g id="Group-25" fill-rule="nonzero">
<g id="2">
<path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-1)"></path>
<path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-2)"></path>
</g>
<path d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z" id="Shape" fill="url(#linearGradient-3)"></path>
</g>
<ellipse id="Combined-Shape" fill="url(#linearGradient-4)" cx="100.519339" cy="100.436681" rx="23.6001926" ry="23.580786"></ellipse>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

9
components/icon/demo/assets/heart.svg

@ -0,0 +1,9 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1534313581801" class="icon" style="" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="5833"
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<defs>
<style type="text/css"></style>
</defs>
<path d="M923 283.6c-13.4-31.1-32.6-58.9-56.9-82.8-24.3-23.8-52.5-42.4-84-55.5-32.5-13.5-66.9-20.3-102.4-20.3-49.3 0-97.4 13.5-139.2 39-10 6.1-19.5 12.8-28.5 20.1-9-7.3-18.5-14-28.5-20.1-41.8-25.5-89.9-39-139.2-39-35.5 0-69.9 6.8-102.4 20.3-31.4 13-59.7 31.7-84 55.5-24.4 23.9-43.5 51.7-56.9 82.8-13.9 32.3-21 66.6-21 101.9 0 33.3 6.8 68 20.3 103.3 11.3 29.5 27.5 60.1 48.2 91 32.8 48.9 77.9 99.9 133.9 151.6 92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3 56-51.7 101.1-102.7 133.9-151.6 20.7-30.9 37-61.5 48.2-91 13.5-35.3 20.3-70 20.3-103.3 0.1-35.3-7-69.6-20.9-101.9z" p-id="5834"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

98
components/icon/demo/custom.md

@ -7,7 +7,7 @@ title:
## zh-CN
利用 `Icon` 提供的 `<CustomIcon />` 组件封装一个可复用的自定义图标。可以进一步通过 `component` 属性传入一个组件来修饰 `<svg/>` 标签,以满足特定的需求。
利用 `Icon` 组件封装一个可复用的自定义图标。可以将 `svg` 图标的路径信息作为 `children` 传入至组件,也可以进一步通过 `component` 属性传入一个组件来渲染最终的图标,以满足特定的需求。
## en-US
@ -15,52 +15,84 @@ Todo, Please replace me.
````jsx
import { Icon } from 'antd';
const CustomIcon = Icon.CustomIcon;
import HeartSvg from './assets/heart.svg';
import AntDesignSvg from './assets/ant-design.svg';
// use webpack loader `@svgr/webpack`,
// which convert `*.svg` file into react component.
const StarIcon = (props) => {
const path = 'M908 353l-254-37L541 86c-3-6-8-11-15'
+ '-14-16-8-35-2-43 14L370 316l-254 37a32 32 0 0 0'
+ '-18 55l184 179-44 253c-1 7 0 14 4 20 8 16 27 22'
+ ' 43 13l227-119 227 119c6 4 14 5 20 4 18-3 29-20'
+ ' 26-37l-43-253 184-179a32 32 0 0 0-18-55z';
return (
<CustomIcon {...props} viewBox="0 0 1024 1024">
<title>Cool Star</title>
<path d={path} />
</CustomIcon>
);
};
const HeartIcon = props => (
<Icon component={HeartSvg} viewBox="0 0 1024 1024" {...props} />
);
ReactDOM.render(
<div className="icons-list">
<StarIcon />
<StarIcon spin />
<StarIcon
component={svgProps => (
<svg {...svgProps}>
<defs>
<linearGradient id="gradient">
<stop offset="20%" stopColor="#39F" />
<stop offset="90%" stopColor="#F3F" />
</linearGradient>
</defs>
{React.Children.map(
const AntDesignIcon = props => (
<Icon component={AntDesignSvg} viewBox="0 0 200 200" {...props} />
);
const SvgDefinitions = () => (
<svg className="svg-common-definitions">
<defs>
<linearGradient id="gradient-demo">
<stop offset="20%" stopColor="#39F" />
<stop offset="90%" stopColor="#F3F" />
</linearGradient>
</defs>
</svg>
);
const MaterialHomeIcon = props => (
<Icon viewBox="0 0 24 24" {...props}>
{/* you should pass SVG paths */}
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</Icon>
);
const MaterialColorfulHomeIcon = props => (
<MaterialHomeIcon
{...props}
component={svgProps => (
<svg {...svgProps}>
<defs>
<linearGradient id="gradient">
<stop offset="30%" stopColor="#8360c3" />
<stop offset="70%" stopColor="#2ebf91" />
</linearGradient>
</defs>
{React.Children.map(
svgProps.children,
child => React.cloneElement(
child,
child.type === 'path' ? { fill: 'url(#gradient)' } : {}
)
)}
</svg>
)}
)}
</svg>
)}
/>
);
ReactDOM.render(
<div className="custom-icons-list">
<HeartIcon style={{ color: 'hotpink' }} />
<SvgDefinitions />
<HeartIcon
style={{ fontSize: '18px' }}
svgStyle={{ fill: 'url(#gradient-demo)' }}
/>
<MaterialHomeIcon style={{ color: '#8360c3' }} />
<MaterialColorfulHomeIcon style={{ fontSize: '18px' }} />
<AntDesignIcon style={{ fontSize: '32px' }} />
</div>,
mountNode
);
````
```css
.icons-list > .anticon {
.custom-icons-list > .anticon {
margin-right: 6px;
}
.custom-icons-list > .svg-common-definitions {
position: absolute;
width: 0;
height: 0;
}
```

10
components/icon/demo/iconfont.md

@ -16,17 +16,17 @@ Todo, please replace me!
````jsx
import { Icon } from 'antd';
const FooIcon = Icon.create({
namespace: 'foo',
const IconFont = Icon.create({
namespace: 'iconfont-foo',
scriptUrl: 'https://at.alicdn.com/t/font_8d5l8fzk5b87iudi.js',
prefix: 'icon-',
});
ReactDOM.render(
<div className="icons-list">
<FooIcon type="tuichu" />
<FooIcon type="facebook" />
<FooIcon type="twitter" />
<IconFont type="tuichu" />
<IconFont type="facebook" />
<IconFont type="twitter" />
</div>,
mountNode
);

124
components/icon/index.tsx

@ -2,17 +2,28 @@ import * as React from 'react';
import classNames from 'classnames';
import { antDesignIcons } from '@ant-design/icons';
import ReactIcon from '@ant-design/icons-react';
import CustomIcon from './CustomIcon';
import create from './IconFont';
import { getComputedSvgStyle } from './utils';
import { getComputedSvgStyle, svgBaseProps } from './utils';
import warning from '../_util/warning';
ReactIcon.add(...antDesignIcons);
export interface CustomIconComponentProps {
width: string | number;
height: string | number;
fill: string;
viewBox: string;
className?: string;
style?: React.CSSProperties;
}
export interface IconProps {
type: string;
type?: string;
className?: string;
title?: string;
onClick?: React.MouseEventHandler<any>;
onClick?: React.MouseEventHandler<HTMLElement>;
component?: React.ComponentType<CustomIconComponentProps>;
viewBox?: string;
spin?: boolean;
style?: React.CSSProperties;
svgStyle?: React.CSSProperties;
@ -23,24 +34,43 @@ export interface IconProps {
prefixCls?: string;
}
const Icon: React.SFC<IconProps> = (props: IconProps) => {
const Icon: React.SFC<IconProps> = (props) => {
const {
type,
// affect outter <i>...</i>
tag = 'i',
className = '',
onClick,
style,
// affect inner <svg>...</svg>
type,
component,
viewBox = '0 0 1024 1024',
spin,
flip,
svgClassName,
tag = 'i',
onClick,
style,
rotate = 0,
svgStyle = {},
// children
children,
} = props;
warning(
Boolean(type || component || children),
'Icon should have `type` prop or `component` prop or `children`.',
);
if (component || children) {
warning(
Boolean(viewBox),
'Make sure that you provide correct `viewBox`' +
' prop (default `0 0 1024 1024`) to Icon.',
);
}
const classString = classNames(
{
[`anticon`]: true,
[`anticon-${type}`]: true,
},
{ [`anticon`]: true, [`anticon-${type}`]: Boolean(type) },
className,
);
@ -49,27 +79,71 @@ const Icon: React.SFC<IconProps> = (props: IconProps) => {
[`anticon-spin`]: !!spin || type === 'loading',
});
const computedSvgStyle = getComputedSvgStyle(
{ rotate, flip },
svgStyle,
);
// component > children > type
if (component) {
const innerSvgProps = {
...svgBaseProps,
viewBox,
className: svgClassString,
style: computedSvgStyle,
};
return React.createElement(
tag,
{ className: classString, style, onClick },
React.createElement(
component,
innerSvgProps,
children,
),
);
}
if (children) {
const innerSvgProps = {
...svgBaseProps,
viewBox,
className: svgClassString,
style: computedSvgStyle,
};
return React.createElement(
tag,
{ className: classString, style, onClick },
React.createElement(
'svg',
innerSvgProps,
children,
),
);
}
if (type) {
return React.createElement(
tag,
{ className: classString, style, onClick },
<ReactIcon
className={svgClassString}
type={type}
style={computedSvgStyle}
/>,
);
}
return React.createElement(
tag,
{
className: classString,
style,
onClick,
},
<ReactIcon
className={svgClassString}
type={type}
style={getComputedSvgStyle({ rotate, flip }, svgStyle)}
/>,
{ className: classString, style, onClick },
);
};
export type IconType = React.SFC<IconProps> & {
CustomIcon: typeof CustomIcon;
create: typeof create;
};
(Icon as IconType).CustomIcon = CustomIcon;
Icon.displayName = 'Icon';
(Icon as IconType).create = create;
export default Icon as IconType;

9
components/icon/utils.ts

@ -19,3 +19,12 @@ export function getComputedSvgStyle(
...svgStyle,
};
}
// These props make sure that the SVG behaviours like general text.
// Reference: https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
export const svgBaseProps = {
width: '1em',
height: '1em',
fill: 'currentColor',
['aria-hidden']: 'true',
};

1
package.json

@ -92,6 +92,7 @@
},
"devDependencies": {
"@babel/types": "7.0.0-beta.44",
"@svgr/webpack": "^2.2.0",
"@types/classnames": "^2.2.6",
"@types/prop-types": "^15.5.4",
"@types/react": "^16.0.0",

51
site/bisheng.config.js

@ -24,6 +24,23 @@ function alertBabelConfig(rules) {
});
}
function getBabelConfig(rules) {
let config = null;
rules.forEach((rule) => {
if (config) {
return;
}
if (rule.loader && rule.loader === 'babel-loader') {
config = rule.options;
return;
}
if (rule.use) {
alertBabelConfig(rule.use);
}
});
return config;
}
function usePrettyWebpackBar(config) {
// remove old progress plugin.
config.plugins = config.plugins
@ -130,6 +147,40 @@ module.exports = {
alertBabelConfig(config.module.rules);
usePrettyWebpackBar(config);
const babelConfig = getBabelConfig(config.module.rules);
if (babelConfig) {
config.module.rules = config.module.rules.filter((rule) => {
return rule.test.toString() !== /\.svg(\?v=\d+\.\d+\.\d+)?$/.toString();
});
config.module.rules.push(
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
issuer: {
test: /\.md$/,
},
use: [
{
loader: 'babel-loader',
options: babelConfig,
},
{
loader: '@svgr/webpack',
options: {
babel: false,
ref: true,
},
},
],
},
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
issuer: {
test: /\.css$/,
},
loader: 'url-loader?limit=10000&mimetype=image/svg+xml',
}
);
}
config.plugins.push(
new CSSSplitWebpackPlugin({ size: 4000 }),
new OfflinePlugin({

Loading…
Cancel
Save