ant-design/components/icon/CustomIcon.tsx
2018-07-23 20:12:59 +08:00

119 lines
3.2 KiB
TypeScript

import * as React from 'react';
import classNames from 'classnames';
import omit from 'omit.js';
import { Omit } from '../_util/type';
import { IconProps } from './index';
export interface CustomIconProps extends Omit<IconProps, 'type'> {
viewBox?: string;
component?: React.ComponentType<CustomIconComponentProps>;
}
export interface CustomIconComponentProps {
width: Readonly<string | number>;
height: Readonly<string | number>;
fill: Readonly<string>;
viewBox: Readonly<string>;
}
// 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
const svgBaseProps = {
width: '1em',
height: '1em',
fill: 'currentColor',
['aria-hidden']: 'true',
};
const CustomIcon: React.SFC<CustomIconProps> = (props) => {
const {
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,
component: Component,
} = props;
const classString = classNames({
anticon: true,
'anticon-spin': !!spin,
}, className);
let content = (
<svg {...omit(props, ['type', 'spin', 'className'])} {...svgBaseProps} viewBox={viewBox}>
{children}
</svg>
);
if (Component) {
content = <Component {...svgBaseProps} viewBox={viewBox}>{children}</Component>;
}
return (
<i className={classString}>
{content}
</i>
);
};
const customCache = new Set<string>();
export interface CustomIconOptions {
namespace?: string;
prefix?: string;
scriptUrl?: string;
extraCommonProps?: { [key: string]: any };
}
export function create(options: CustomIconOptions = {}): React.ComponentClass<IconProps> {
const { namespace, prefix = '', scriptUrl, extraCommonProps = {} } = options;
class Custom extends React.Component<IconProps> {
render() {
const { type, className = '', spin } = this.props;
const classString = classNames({
anticon: true,
'anticon-spin': !!spin || type === 'loading',
}, className);
return (
<i className={classString}>
<svg
{...extraCommonProps}
{...omit(this.props, ['type', 'spin', 'className'])}
{...svgBaseProps}
>
<use xlinkHref={`#${prefix}${type}`}/>
</svg>
</i>
);
}
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);
}
}
}
return Custom;
}
export default CustomIcon;