mirror of
https://github.com/iconify/iconify.git
synced 2024-09-28 13:09:07 +00:00
chore: rewrite react component
This commit is contained in:
parent
59bf4012b5
commit
e244ddea3d
@ -12,7 +12,11 @@ export function InlineDemo() {
|
|||||||
id="inline-demo-block-offline"
|
id="inline-demo-block-offline"
|
||||||
icon="experiment2"
|
icon="experiment2"
|
||||||
/>
|
/>
|
||||||
<FullIcon id="inline-demo-block-full" icon="experiment2" />
|
<FullIcon
|
||||||
|
id="inline-demo-block-full"
|
||||||
|
icon="experiment2"
|
||||||
|
ssr={true}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Inline icon (behaving line text / icon font):
|
Inline icon (behaving line text / icon font):
|
||||||
@ -27,6 +31,7 @@ export function InlineDemo() {
|
|||||||
icon="experiment2"
|
icon="experiment2"
|
||||||
inline={true}
|
inline={true}
|
||||||
mode="style"
|
mode="style"
|
||||||
|
ssr={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -9,8 +9,6 @@ const commands = [];
|
|||||||
const compile = {
|
const compile = {
|
||||||
// Compile TypeScript src -> lib
|
// Compile TypeScript src -> lib
|
||||||
lib: true,
|
lib: true,
|
||||||
// Fix types for icon components
|
|
||||||
cleanup: true,
|
|
||||||
// Generate bundle from compiled files lib -> dist
|
// Generate bundle from compiled files lib -> dist
|
||||||
dist: true,
|
dist: true,
|
||||||
// Generate TypeScript definitions in dist
|
// Generate TypeScript definitions in dist
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const reactSearch = "import React from 'react';";
|
|
||||||
const reactImport = 'React';
|
|
||||||
|
|
||||||
function fixTypes(filename) {
|
|
||||||
let content = fs.readFileSync(__dirname + filename, 'utf8');
|
|
||||||
if (content.indexOf(reactSearch) === -1) {
|
|
||||||
throw new Error(`Missing React import in ${filename}`);
|
|
||||||
}
|
|
||||||
let replaced = false;
|
|
||||||
|
|
||||||
['Icon', 'InlineIcon'].forEach((name) => {
|
|
||||||
const searchStart = `export declare const ${name}: ${reactImport}.ForwardRefExoticComponent`;
|
|
||||||
const searchEnd = `& ${reactImport}.RefAttributes<IconRef>>;`;
|
|
||||||
const replace = `export declare const ${name}: (props: IconProps) => ${reactImport}.ReactElement<IconProps, string | ${reactImport}.JSXElementConstructor<any>>;`;
|
|
||||||
|
|
||||||
if (content.indexOf(replace) !== -1) {
|
|
||||||
// Already replaced
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const parts = content.split(searchStart);
|
|
||||||
if (parts.length !== 2) {
|
|
||||||
throw new Error(
|
|
||||||
`Error replacing types for component ${name} in ${filename}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const contentStart = parts.shift();
|
|
||||||
const parts2 = parts.shift().split(searchEnd);
|
|
||||||
if (parts2.length < 2) {
|
|
||||||
throw new Error(
|
|
||||||
`Error replacing types for component ${name} in ${filename}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
parts2.shift();
|
|
||||||
|
|
||||||
content = contentStart + replace + parts2.join(searchEnd);
|
|
||||||
replaced = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (replaced) {
|
|
||||||
fs.writeFileSync(__dirname + filename, content, 'utf8');
|
|
||||||
console.log(`Fixed component types in ${filename}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fixTypes('/lib/iconify.d.ts');
|
|
||||||
fixTypes('/lib/offline.d.ts');
|
|
@ -2,7 +2,11 @@
|
|||||||
"name": "@iconify/react",
|
"name": "@iconify/react",
|
||||||
"description": "Iconify icon component for React.",
|
"description": "Iconify icon component for React.",
|
||||||
"author": "Vjacheslav Trushkin",
|
"author": "Vjacheslav Trushkin",
|
||||||
"version": "4.1.1",
|
"version": "5.0.0-beta.1",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"tag": "next"
|
||||||
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": "https://github.com/iconify/iconify/issues",
|
"bugs": "https://github.com/iconify/iconify/issues",
|
||||||
"homepage": "https://iconify.design/",
|
"homepage": "https://iconify.design/",
|
||||||
@ -20,7 +24,6 @@
|
|||||||
"build:dist": "rollup -c rollup.config.mjs",
|
"build:dist": "rollup -c rollup.config.mjs",
|
||||||
"prebuild:api": "api-extractor run --local --verbose --config api-extractor.offline.json",
|
"prebuild:api": "api-extractor run --local --verbose --config api-extractor.offline.json",
|
||||||
"build:api": "api-extractor run --local --verbose --config api-extractor.iconify.json",
|
"build:api": "api-extractor run --local --verbose --config api-extractor.iconify.json",
|
||||||
"build:cleanup": "node cleanup",
|
|
||||||
"test": "jest --runInBand"
|
"test": "jest --runInBand"
|
||||||
},
|
},
|
||||||
"main": "dist/iconify.js",
|
"main": "dist/iconify.js",
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
'use client';
|
import {
|
||||||
|
useEffect,
|
||||||
import React from 'react';
|
useState,
|
||||||
|
forwardRef,
|
||||||
|
createElement,
|
||||||
|
type Ref,
|
||||||
|
} from 'react';
|
||||||
import type { IconifyJSON, IconifyIcon } from '@iconify/types';
|
import type { IconifyJSON, IconifyIcon } from '@iconify/types';
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
import type { IconifyIconName } from '@iconify/utils/lib/icon/name';
|
import type { IconifyIconName } from '@iconify/utils/lib/icon/name';
|
||||||
import { stringToIcon } from '@iconify/utils/lib/icon/name';
|
|
||||||
import type { IconifyIconSize } from '@iconify/utils/lib/customisations/defaults';
|
import type { IconifyIconSize } from '@iconify/utils/lib/customisations/defaults';
|
||||||
import type { IconifyStorageFunctions } from '@iconify/core/lib/storage/functions';
|
import type { IconifyStorageFunctions } from '@iconify/core/lib/storage/functions';
|
||||||
import {
|
import {
|
||||||
@ -73,7 +76,7 @@ import type {
|
|||||||
IconifyIconProps,
|
IconifyIconProps,
|
||||||
IconifyRenderMode,
|
IconifyRenderMode,
|
||||||
IconProps,
|
IconProps,
|
||||||
IconRef,
|
IconElement,
|
||||||
} from './props';
|
} from './props';
|
||||||
|
|
||||||
// Render SVG
|
// Render SVG
|
||||||
@ -218,240 +221,129 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
|
|||||||
* Component
|
* Component
|
||||||
*/
|
*/
|
||||||
interface InternalIconProps extends IconProps {
|
interface InternalIconProps extends IconProps {
|
||||||
_ref?: IconRef;
|
_ref?: Ref<IconElement> | null;
|
||||||
_inline: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IconComponentData {
|
function IconComponent(props: InternalIconProps): JSX.Element {
|
||||||
data: IconifyIcon;
|
interface State {
|
||||||
classes?: string[];
|
// Currently rendered icon
|
||||||
}
|
|
||||||
|
|
||||||
interface IconComponentState {
|
|
||||||
icon: IconComponentData | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ComponentAbortData {
|
|
||||||
name: string;
|
name: string;
|
||||||
abort: IconifyIconLoaderAbort;
|
|
||||||
}
|
|
||||||
|
|
||||||
class IconComponent extends React.Component<
|
// Icon data, null if missing
|
||||||
InternalIconProps,
|
data?: IconifyIcon | null;
|
||||||
IconComponentState
|
|
||||||
> {
|
|
||||||
protected _icon: string;
|
|
||||||
protected _loading: ComponentAbortData | null;
|
|
||||||
|
|
||||||
constructor(props: InternalIconProps) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
// Render placeholder before component is mounted
|
|
||||||
icon: null,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
const [mounted, setMounted] = useState(!!props.ssr);
|
||||||
|
|
||||||
/**
|
interface AbortState {
|
||||||
* Abort loading icon
|
callback?: IconifyIconLoaderAbort;
|
||||||
*/
|
|
||||||
_abortLoading() {
|
|
||||||
if (this._loading) {
|
|
||||||
this._loading.abort();
|
|
||||||
this._loading = null;
|
|
||||||
}
|
}
|
||||||
}
|
const [abort, setAbort] = useState<AbortState>({});
|
||||||
|
const [state, setState] = useState<State>({
|
||||||
/**
|
name: '',
|
||||||
* Update state
|
|
||||||
*/
|
|
||||||
_setData(icon: IconComponentData | null) {
|
|
||||||
if (this.state.icon !== icon) {
|
|
||||||
this.setState({
|
|
||||||
icon,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cancel loading
|
||||||
|
function cleanup() {
|
||||||
|
const callback = abort.callback;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
setAbort({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Update state
|
||||||
* Check if icon should be loaded
|
function updateState() {
|
||||||
*/
|
const name = props.icon;
|
||||||
_checkIcon(changed: boolean) {
|
if (typeof name === 'object') {
|
||||||
const state = this.state;
|
// Icon as object
|
||||||
const icon = this.props.icon;
|
cleanup();
|
||||||
|
setState({
|
||||||
// Icon is an object
|
name: '',
|
||||||
if (
|
data: name,
|
||||||
typeof icon === 'object' &&
|
|
||||||
icon !== null &&
|
|
||||||
typeof icon.body === 'string'
|
|
||||||
) {
|
|
||||||
// Stop loading
|
|
||||||
this._icon = '';
|
|
||||||
this._abortLoading();
|
|
||||||
|
|
||||||
if (changed || state.icon === null) {
|
|
||||||
// Set data if it was changed
|
|
||||||
this._setData({
|
|
||||||
data: icon,
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid icon?
|
// New icon or got icon data
|
||||||
let iconName: IconifyIconName | null;
|
const data = getIconData(name);
|
||||||
if (
|
if (state.name !== name || data !== state.data) {
|
||||||
typeof icon !== 'string' ||
|
cleanup();
|
||||||
(iconName = stringToIcon(icon, false, true)) === null
|
setState({
|
||||||
) {
|
name,
|
||||||
this._abortLoading();
|
|
||||||
this._setData(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load icon
|
|
||||||
const data = getIconData(iconName);
|
|
||||||
if (!data) {
|
|
||||||
// Icon data is not available
|
|
||||||
if (!this._loading || this._loading.name !== icon) {
|
|
||||||
// New icon to load
|
|
||||||
this._abortLoading();
|
|
||||||
this._icon = '';
|
|
||||||
this._setData(null);
|
|
||||||
if (data !== null) {
|
|
||||||
// Icon was not loaded
|
|
||||||
this._loading = {
|
|
||||||
name: icon,
|
|
||||||
abort: loadIcons(
|
|
||||||
[iconName],
|
|
||||||
this._checkIcon.bind(this, false)
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Icon data is available
|
|
||||||
if (this._icon !== icon || state.icon === null) {
|
|
||||||
// New icon or icon has been loaded
|
|
||||||
this._abortLoading();
|
|
||||||
this._icon = icon;
|
|
||||||
|
|
||||||
// Add classes
|
|
||||||
const classes: string[] = ['iconify'];
|
|
||||||
if (iconName.prefix !== '') {
|
|
||||||
classes.push('iconify--' + iconName.prefix);
|
|
||||||
}
|
|
||||||
if (iconName.provider !== '') {
|
|
||||||
classes.push('iconify--' + iconName.provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set data
|
|
||||||
this._setData({
|
|
||||||
data,
|
data,
|
||||||
classes,
|
|
||||||
});
|
});
|
||||||
if (this.props.onLoad) {
|
if (data === undefined) {
|
||||||
this.props.onLoad(icon);
|
// Load icon, update state when done
|
||||||
|
const callback = loadIcons([name], updateState);
|
||||||
|
setAbort({
|
||||||
|
callback,
|
||||||
|
});
|
||||||
|
} else if (data) {
|
||||||
|
// Icon data is available: trigger onLoad callback if present
|
||||||
|
props.onLoad?.(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Mounted state, cleanup for loader
|
||||||
* Component mounted
|
useEffect(() => {
|
||||||
*/
|
setMounted(true);
|
||||||
componentDidMount() {
|
updateState();
|
||||||
this._checkIcon(false);
|
return cleanup;
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
/**
|
// Icon changed
|
||||||
* Component updated
|
useEffect(() => {
|
||||||
*/
|
if (mounted) {
|
||||||
componentDidUpdate(oldProps) {
|
updateState();
|
||||||
if (oldProps.icon !== this.props.icon) {
|
|
||||||
this._checkIcon(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abort loading
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
this._abortLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const props = this.props;
|
|
||||||
const icon = this.state.icon;
|
|
||||||
|
|
||||||
if (icon === null) {
|
|
||||||
// Render placeholder
|
|
||||||
return props.children
|
|
||||||
? (props.children as JSX.Element)
|
|
||||||
: React.createElement('span', {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add classes
|
|
||||||
let newProps = props;
|
|
||||||
if (icon.classes) {
|
|
||||||
newProps = {
|
|
||||||
...props,
|
|
||||||
className:
|
|
||||||
(typeof props.className === 'string'
|
|
||||||
? props.className + ' '
|
|
||||||
: '') + icon.classes.join(' '),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}, [props.icon]);
|
||||||
|
|
||||||
// Render icon
|
// Render icon
|
||||||
|
const { name, data } = state;
|
||||||
|
if (!data) {
|
||||||
|
return props.children
|
||||||
|
? (props.children as JSX.Element)
|
||||||
|
: createElement('span', {});
|
||||||
|
}
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
{
|
{
|
||||||
...defaultIconProps,
|
...defaultIconProps,
|
||||||
...icon.data,
|
...data,
|
||||||
},
|
},
|
||||||
newProps,
|
props,
|
||||||
props._inline,
|
name
|
||||||
props._ref
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Component type
|
||||||
|
type IconComponentType = (props: IconProps) => JSX.Element;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block icon
|
* Block icon
|
||||||
*
|
*
|
||||||
* @param props - Component properties
|
* @param props - Component properties
|
||||||
*/
|
*/
|
||||||
export const Icon = React.forwardRef<IconRef, IconProps>(function Icon(
|
export const Icon = forwardRef<IconElement, IconProps>((props, ref) =>
|
||||||
props,
|
IconComponent({
|
||||||
ref
|
|
||||||
) {
|
|
||||||
const newProps = {
|
|
||||||
...props,
|
...props,
|
||||||
_ref: ref,
|
_ref: ref,
|
||||||
_inline: false,
|
})
|
||||||
} as InternalIconProps;
|
) as IconComponentType;
|
||||||
return React.createElement(IconComponent, newProps);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline icon (has negative verticalAlign that makes it behave like icon font)
|
* Inline icon (has negative verticalAlign that makes it behave like icon font)
|
||||||
*
|
*
|
||||||
* @param props - Component properties
|
* @param props - Component properties
|
||||||
*/
|
*/
|
||||||
export const InlineIcon = React.forwardRef<IconRef, IconProps>(
|
export const InlineIcon = forwardRef<IconElement, IconProps>((props, ref) =>
|
||||||
function InlineIcon(props, ref) {
|
IconComponent({
|
||||||
const newProps = {
|
inline: true,
|
||||||
...props,
|
...props,
|
||||||
_ref: ref,
|
_ref: ref,
|
||||||
_inline: true,
|
})
|
||||||
} as InternalIconProps;
|
) as IconComponentType;
|
||||||
return React.createElement(IconComponent, newProps);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal API
|
* Internal API
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
'use client';
|
import { forwardRef, createElement, type Ref } from 'react';
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import type { IconifyIcon, IconifyJSON } from '@iconify/types';
|
import type { IconifyIcon, IconifyJSON } from '@iconify/types';
|
||||||
import type { IconifyIconSize } from '@iconify/utils/lib/customisations/defaults';
|
import type { IconifyIconSize } from '@iconify/utils/lib/customisations/defaults';
|
||||||
import { defaultIconProps } from '@iconify/utils/lib/icon/defaults';
|
import { defaultIconProps } from '@iconify/utils/lib/icon/defaults';
|
||||||
@ -11,7 +9,7 @@ import type {
|
|||||||
IconifyIconProps,
|
IconifyIconProps,
|
||||||
IconifyRenderMode,
|
IconifyRenderMode,
|
||||||
IconProps,
|
IconProps,
|
||||||
IconRef,
|
IconElement,
|
||||||
} from './props';
|
} from './props';
|
||||||
import { render } from './render';
|
import { render } from './render';
|
||||||
|
|
||||||
@ -31,67 +29,59 @@ export { IconifyIcon, IconifyJSON, IconifyIconSize, IconifyRenderMode };
|
|||||||
const storage: Record<string, IconifyIcon> = Object.create(null);
|
const storage: Record<string, IconifyIcon> = Object.create(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate icon
|
* Component
|
||||||
*/
|
*/
|
||||||
function component(
|
interface InternalIconProps extends IconProps {
|
||||||
props: IconProps,
|
_ref?: Ref<IconElement> | null;
|
||||||
inline: boolean,
|
}
|
||||||
ref?: React.ForwardedRef<IconRef>
|
|
||||||
): JSX.Element {
|
function IconComponent(props: InternalIconProps): JSX.Element {
|
||||||
// Split properties
|
const icon = props.icon;
|
||||||
const propsIcon = props.icon;
|
const data = typeof icon === 'string' ? storage[icon] : icon;
|
||||||
const icon =
|
|
||||||
typeof propsIcon === 'string'
|
if (!data) {
|
||||||
? storage[propsIcon]
|
return props.children
|
||||||
: typeof propsIcon === 'object'
|
? (props.children as JSX.Element)
|
||||||
? propsIcon
|
: createElement('span', {});
|
||||||
: null;
|
|
||||||
|
|
||||||
// Validate icon object
|
|
||||||
if (
|
|
||||||
icon === null ||
|
|
||||||
typeof icon !== 'object' ||
|
|
||||||
typeof icon.body !== 'string'
|
|
||||||
) {
|
|
||||||
return props.children
|
|
||||||
? (props.children as JSX.Element)
|
|
||||||
: React.createElement('span', {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid icon: render it
|
|
||||||
return render(
|
return render(
|
||||||
{
|
{
|
||||||
...defaultIconProps,
|
...defaultIconProps,
|
||||||
...icon,
|
...data,
|
||||||
},
|
},
|
||||||
props,
|
props,
|
||||||
inline,
|
typeof icon === 'string' ? icon : undefined
|
||||||
ref as IconRef
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Component type
|
||||||
|
type IconComponentType = (props: IconProps) => JSX.Element;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block icon
|
* Block icon
|
||||||
*
|
*
|
||||||
* @param props - Component properties
|
* @param props - Component properties
|
||||||
*/
|
*/
|
||||||
export const Icon = React.forwardRef<IconRef, IconProps>(function Icon(
|
export const Icon = forwardRef<IconElement, IconProps>((props, ref) =>
|
||||||
props,
|
IconComponent({
|
||||||
ref
|
...props,
|
||||||
) {
|
_ref: ref,
|
||||||
return component(props, false, ref);
|
})
|
||||||
});
|
) as IconComponentType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline icon (has negative verticalAlign that makes it behave like icon font)
|
* Inline icon (has negative verticalAlign that makes it behave like icon font)
|
||||||
*
|
*
|
||||||
* @param props - Component properties
|
* @param props - Component properties
|
||||||
*/
|
*/
|
||||||
export const InlineIcon = React.forwardRef<IconRef, IconProps>(
|
export const InlineIcon = forwardRef<IconElement, IconProps>((props, ref) =>
|
||||||
function InlineIcon(props, ref) {
|
IconComponent({
|
||||||
return component(props, true, ref);
|
inline: true,
|
||||||
}
|
...props,
|
||||||
);
|
_ref: ref,
|
||||||
|
})
|
||||||
|
) as IconComponentType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add icon to storage, allowing to call it by name
|
* Add icon to storage, allowing to call it by name
|
||||||
|
@ -53,6 +53,9 @@ export interface IconifyIconProps extends IconifyIconCustomisations {
|
|||||||
// Unique id, used as base for ids for shapes. Use it to get consistent ids for server side rendering
|
// Unique id, used as base for ids for shapes. Use it to get consistent ids for server side rendering
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
||||||
|
// If true, icon will be rendered without waiting for component to mount, such as when rendering on server side
|
||||||
|
ssr?: boolean;
|
||||||
|
|
||||||
// Callback to call when icon data has been loaded. Used only for icons loaded from API
|
// Callback to call when icon data has been loaded. Used only for icons loaded from API
|
||||||
onLoad?: IconifyIconOnLoad;
|
onLoad?: IconifyIconOnLoad;
|
||||||
}
|
}
|
||||||
@ -62,13 +65,12 @@ export interface IconifyIconProps extends IconifyIconCustomisations {
|
|||||||
*/
|
*/
|
||||||
type IconifyElementProps = SVGProps<SVGSVGElement>;
|
type IconifyElementProps = SVGProps<SVGSVGElement>;
|
||||||
|
|
||||||
export type IconRef = RefAttributes<SVGSVGElement>;
|
/**
|
||||||
|
* Reference for SVG element
|
||||||
export interface ReactRefProp {
|
*/
|
||||||
ref?: IconRef;
|
export type IconElement = SVGSVGElement | HTMLSpanElement;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mix of icon properties and SVGSVGElement properties
|
* Mix of icon properties and SVGSVGElement properties
|
||||||
*/
|
*/
|
||||||
export type IconProps = IconifyElementProps & IconifyIconProps & ReactRefProp;
|
export type IconProps = IconifyElementProps & IconifyIconProps;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import { createElement } from 'react';
|
||||||
import type { SVGProps } from 'react';
|
import type { SVGProps, CSSProperties } from 'react';
|
||||||
import type { IconifyIcon } from '@iconify/types';
|
import type { IconifyIcon } from '@iconify/types';
|
||||||
import { mergeCustomisations } from '@iconify/utils/lib/customisations/merge';
|
import { mergeCustomisations } from '@iconify/utils/lib/customisations/merge';
|
||||||
import { flipFromString } from '@iconify/utils/lib/customisations/flip';
|
import { flipFromString } from '@iconify/utils/lib/customisations/flip';
|
||||||
@ -13,9 +13,9 @@ import type {
|
|||||||
IconifyIconCustomisations,
|
IconifyIconCustomisations,
|
||||||
IconifyRenderMode,
|
IconifyRenderMode,
|
||||||
IconProps,
|
IconProps,
|
||||||
IconRef,
|
|
||||||
} from './props';
|
} from './props';
|
||||||
import { defaultExtendedIconCustomisations } from './props';
|
import { defaultExtendedIconCustomisations } from './props';
|
||||||
|
import { stringToIcon } from '@iconify/utils/lib/icon/name';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default SVG attributes
|
* Default SVG attributes
|
||||||
@ -85,14 +85,11 @@ export const render = (
|
|||||||
// Partial properties
|
// Partial properties
|
||||||
props: IconProps,
|
props: IconProps,
|
||||||
|
|
||||||
// True if icon should have vertical-align added
|
// Icon name
|
||||||
inline: boolean,
|
name?: string
|
||||||
|
|
||||||
// Optional reference for SVG/SPAN, extracted by React.forwardRef()
|
|
||||||
ref?: IconRef
|
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
// Get default properties
|
// Get default properties
|
||||||
const defaultProps = inline
|
const defaultProps = props.inline
|
||||||
? inlineDefaults
|
? inlineDefaults
|
||||||
: defaultExtendedIconCustomisations;
|
: defaultExtendedIconCustomisations;
|
||||||
|
|
||||||
@ -103,14 +100,29 @@ export const render = (
|
|||||||
const mode: IconifyRenderMode = props.mode || 'svg';
|
const mode: IconifyRenderMode = props.mode || 'svg';
|
||||||
|
|
||||||
// Create style
|
// Create style
|
||||||
const style: React.CSSProperties = {};
|
const style: CSSProperties = {};
|
||||||
const customStyle = props.style || {};
|
const customStyle = props.style || {};
|
||||||
|
|
||||||
// Create SVG component properties
|
// Create SVG component properties
|
||||||
const componentProps = {
|
const componentProps = {
|
||||||
...(mode === 'svg' ? svgDefaults : {}),
|
...(mode === 'svg' ? svgDefaults : {}),
|
||||||
ref,
|
|
||||||
};
|
};
|
||||||
|
if (name) {
|
||||||
|
const iconName = stringToIcon(name, false, true);
|
||||||
|
if (iconName) {
|
||||||
|
const classNames: string[] = ['iconify'];
|
||||||
|
const props: (keyof typeof iconName)[] = [
|
||||||
|
'provider',
|
||||||
|
'prefix',
|
||||||
|
] as const;
|
||||||
|
for (const prop of props) {
|
||||||
|
if (iconName[prop]) {
|
||||||
|
classNames.push('iconify--' + iconName[prop]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentProps.className = classNames.join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get element properties
|
// Get element properties
|
||||||
for (let key in props) {
|
for (let key in props) {
|
||||||
@ -125,8 +137,19 @@ export const render = (
|
|||||||
case 'children':
|
case 'children':
|
||||||
case 'onLoad':
|
case 'onLoad':
|
||||||
case 'mode':
|
case 'mode':
|
||||||
|
case 'ssr':
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Forward ref
|
||||||
case '_ref':
|
case '_ref':
|
||||||
case '_inline':
|
componentProps.ref = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Merge class names
|
||||||
|
case 'className':
|
||||||
|
componentProps[key] =
|
||||||
|
(componentProps[key] ? componentProps[key] + ' ' : '') +
|
||||||
|
value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Boolean attributes
|
// Boolean attributes
|
||||||
@ -210,7 +233,7 @@ export const render = (
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
return React.createElement('svg', componentProps);
|
return createElement('svg', componentProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render <span> with style
|
// Render <span> with style
|
||||||
@ -235,7 +258,7 @@ export const render = (
|
|||||||
...commonProps,
|
...commonProps,
|
||||||
...(useMask ? monotoneProps : coloredProps),
|
...(useMask ? monotoneProps : coloredProps),
|
||||||
...customStyle,
|
...customStyle,
|
||||||
} as React.CSSProperties;
|
} as CSSProperties;
|
||||||
|
|
||||||
return React.createElement('span', componentProps);
|
return createElement('span', componentProps);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user