2
0
mirror of https://github.com/iconify/iconify.git synced 2024-12-12 21:57:50 +00:00

Refactoring React: full component but without API support

This commit is contained in:
Vjacheslav Trushkin 2021-04-24 14:49:37 +03:00
parent ac37225e5e
commit 3f73f4f145
25 changed files with 1389 additions and 453 deletions

View File

@ -45,18 +45,22 @@ p {
font-size: 16px;
line-height: 1.5;
}
.test-row > svg {
color: #afafaf;
.test-row-icons {
padding-right: 4px;
}
.test-row > svg.success {
.test-row-icons > svg {
color: #afafaf;
display: none;
}
.test-row-icons > svg.visible {
display: inline-block;
}
.test-row-icons > svg.success {
color: #327335;
}
.test-row > svg.fail {
.test-row-icons > svg.failed {
color: #ba3329;
}
.test-row > span {
padding-left: 4px;
}
/* 24px icon */
.icon-24 svg {

View File

@ -1,44 +1,47 @@
import React from 'react';
import {
Icon,
InlineIcon,
addIcon,
addCollection,
addIcon as addOfflineIcon,
addCollection as addOfflineCollection,
} from '@iconify/react/dist/offline';
import alertIcon from '@iconify-icons/mdi-light/alert';
import {
addIcon as addOnlineIcon,
addCollection as addOnlineCollection,
} from '@iconify/react/dist/iconify';
import presentationPlay from '@iconify-icons/mdi-light/presentation-play';
import checkedIcon from '@iconify-icons/uil/check-circle';
import uncheckedIcon from '@iconify-icons/uil/circle';
import playIcon from '@iconify-icons/mdi-light/play';
import { Checkbox } from './components/Checkbox';
import { InlineDemo } from './components/Inline';
import { OfflineUsageDemo } from './components/UsageOffline';
import { TestsOffline } from './components/TestsOffline';
import { Checkbox } from './demo-components/Checkbox';
import { InlineDemo } from './demo-components/Inline';
import { OfflineUsageDemo } from './demo-components/UsageOffline';
import { FullOfflineUsageDemo } from './demo-components/UsageFullOffline';
import { TestsOffline } from './test-components/TestsOffline';
import { TestsFullOffline } from './test-components/TestsFullOffline';
import './App.css';
// Add 'mdi-light:presentation-play' as 'demo'
addIcon('demo', presentationPlay);
// Add 'mdi-light:presentation-play' as 'demo' for offline module
addOfflineIcon('demo', presentationPlay);
// Add 'mdi-light:play' as 'demo' for full module
addOnlineIcon('demo', playIcon);
// Add custom icon as 'experiment'
addIcon('experiment2', {
addOfflineIcon('experiment2', {
width: 16,
height: 16,
body:
'<circle fill-opacity="0.2" cx="8" cy="8" r="7" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15Z" fill="currentColor"/><path d="M7 9L5 7L3.5 8.5L7 12L13 6L11.5 4.5L7 9Z" fill="currentColor"/>',
});
// Add icon with id: noto:robot
addIcon('noto-robot', {
addOnlineIcon('experiment2', {
width: 16,
height: 16,
body:
'<path d="M12.53 53.05c-4.57.01-8.28 3.72-8.28 8.29v38.38a8.297 8.297 0 0 0 8.28 8.28h5.55V53l-5.55.05z" fill="#c62828"/><path d="M115.72 53.05c4.57.01 8.28 3.72 8.28 8.29v38.38c-.01 4.57-3.71 8.28-8.28 8.29h-5.55v-55l5.55.04z" fill="#c62828"/><path d="M113.17 54.41l-.12-10c-.03-4.3-3.53-7.77-7.83-7.75H67.05V23.25c5.11-1.69 7.89-7.2 6.2-12.31c-1.69-5.11-7.2-7.89-12.31-6.2s-7.89 7.2-6.2 12.31a9.743 9.743 0 0 0 6.2 6.2v13.46H22.78c-4.28.01-7.75 3.47-7.78 7.75v71.78c.03 4.28 3.5 7.74 7.78 7.76h82.44c4.3.01 7.8-3.46 7.83-7.76v-7.37h.12V54.41z" fill="#90a4ae"/><path d="M64 18c-2.21 0-4-1.79-4-4s1.79-4 4-4s4 1.79 4 4s-1.79 4-4 4z" fill="#c62828"/><g><linearGradient id="ssvg-id-robota" gradientUnits="userSpaceOnUse" x1="64.005" y1="22.44" x2="64.005" y2="35.55" gradientTransform="matrix(1 0 0 -1 0 130)"><stop offset=".12" stop-color="#e0e0e0"/><stop offset=".52" stop-color="#fff"/><stop offset="1" stop-color="#eaeaea"/></linearGradient><path d="M44.15 94.45h39.71c3.46 0 6.27 2.81 6.27 6.27v.57c0 3.46-2.81 6.27-6.27 6.27H44.15c-3.46 0-6.27-2.81-6.27-6.27v-.57c0-3.46 2.81-6.27 6.27-6.27z" fill="url(#ssvg-id-robota)"/><linearGradient id="ssvg-id-robotb" gradientUnits="userSpaceOnUse" x1="54.85" y1="22.44" x2="54.85" y2="35.53" gradientTransform="matrix(1 0 0 -1 0 130)"><stop offset="0" stop-color="#333"/><stop offset=".55" stop-color="#666"/><stop offset="1" stop-color="#333"/></linearGradient><path fill="url(#ssvg-id-robotb)" d="M53.67 94.47h2.36v13.09h-2.36z"/><linearGradient id="ssvg-id-robotc" gradientUnits="userSpaceOnUse" x1="64.06" y1="22.44" x2="64.06" y2="35.53" gradientTransform="matrix(1 0 0 -1 0 130)"><stop offset="0" stop-color="#333"/><stop offset=".55" stop-color="#666"/><stop offset="1" stop-color="#333"/></linearGradient><path fill="url(#ssvg-id-robotc)" d="M62.88 94.47h2.36v13.09h-2.36z"/><linearGradient id="ssvg-id-robotd" gradientUnits="userSpaceOnUse" x1="73.15" y1="22.44" x2="73.15" y2="35.53" gradientTransform="matrix(1 0 0 -1 0 130)"><stop offset="0" stop-color="#333"/><stop offset=".55" stop-color="#666"/><stop offset="1" stop-color="#333"/></linearGradient><path fill="url(#ssvg-id-robotd)" d="M71.97 94.47h2.36v13.09h-2.36z"/><linearGradient id="ssvg-id-robote" gradientUnits="userSpaceOnUse" x1="82.8" y1="22.44" x2="82.8" y2="35.53" gradientTransform="matrix(1 0 0 -1 0 130)"><stop offset="0" stop-color="#333"/><stop offset=".55" stop-color="#666"/><stop offset="1" stop-color="#333"/></linearGradient><path fill="url(#ssvg-id-robote)" d="M81.62 94.47h2.36v13.09h-2.36z"/><linearGradient id="ssvg-id-robotf" gradientUnits="userSpaceOnUse" x1="45.2" y1="22.46" x2="45.2" y2="35.55" gradientTransform="matrix(1 0 0 -1 0 130)"><stop offset="0" stop-color="#333"/><stop offset=".55" stop-color="#666"/><stop offset="1" stop-color="#333"/></linearGradient><path fill="url(#ssvg-id-robotf)" d="M44.02 94.45h2.36v13.09h-2.36z"/><g><path d="M64 85.33h-5.33c-.55 0-1-.45-1-1c0-.16.04-.31.11-.45l2.74-5.41l2.59-4.78a.996.996 0 0 1 1.76 0l2.61 5l2.71 5.19c.25.49.06 1.09-.43 1.35c-.14.07-.29.11-.45.11L64 85.33z" fill="#c62828"/></g><g><radialGradient id="ssvg-id-robotg" cx="42.64" cy="63.19" r="11.5" gradientTransform="matrix(1 0 0 -1 0 130)" gradientUnits="userSpaceOnUse"><stop offset=".48" stop-color="#fff"/><stop offset=".77" stop-color="#fdfdfd"/><stop offset=".88" stop-color="#f6f6f6"/><stop offset=".96" stop-color="#ebebeb"/><stop offset="1" stop-color="#e0e0e0"/></radialGradient><circle cx="42.64" cy="66.81" r="11.5" fill="url(#ssvg-id-robotg)"/><linearGradient id="ssvg-id-roboth" gradientUnits="userSpaceOnUse" x1="30.14" y1="63.19" x2="55.14" y2="63.19" gradientTransform="matrix(1 0 0 -1 0 130)"><stop offset="0" stop-color="#333"/><stop offset=".55" stop-color="#666"/><stop offset="1" stop-color="#333"/></linearGradient><circle cx="42.64" cy="66.81" r="11.5" fill="none" stroke="url(#ssvg-id-roboth)" stroke-width="2" stroke-miterlimit="10"/><radialGradient id="ssvg-id-roboti" cx="84.95" cy="63.22" r="11.5" gradientTransform="matrix(1 0 0 -1 0 130)" gradientUnits="userSpaceOnUse"><stop offset=".48" stop-color="#fff"/><stop offset=".77" stop-color="#fdfdfd"/><stop offset=".88" stop-color="#f6f6f6"/><stop offset=".96" stop-color="#ebebeb"/><stop offset="1" stop-color="#e0e0e0"/></radialGradient><path d="M85 55.28c-6.35 0-11.5 5.15-11.5 11.5s5.15 11.5 11.5 11.5s11.5-5.15 11.5-11.5c-.01-6.35-5.15-11.49-11.5-11.5z" fill="url(#ssvg-id-roboti)"/><linearGradient id="ssvg-id-robotj" gradientUnits="userSpaceOnUse" x1="72.45" y1="63.22" x2="97.45" y2="63.22" gradientTransform="matrix(1 0 0 -1 0 130)"><stop offset="0" stop-color="#333"/><stop offset=".55" stop-color="#666"/><stop offset="1" stop-color="#333"/></linearGradient><path d="M85 55.28c-6.35 0-11.5 5.15-11.5 11.5s5.15 11.5 11.5 11.5s11.5-5.15 11.5-11.5h0c-.01-6.35-5.15-11.49-11.5-11.5z" fill="none" stroke="url(#ssvg-id-robotj)" stroke-width="2" stroke-miterlimit="10"/></g></g>',
width: 128,
height: 128,
'<g fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M13 8.9c.1.6.2 1.1.4 1.7l.6 1.7l-.5.7H10c0 .5-.2 1-.6 1.4c-.4.4-.9.6-1.4.6c-.5 0-1.1-.2-1.4-.6c-.4-.4-.6-.9-.6-1.4H2.5l-.5-.7l.6-1.7c.2-.8.4-1.6.4-2.4V6c0-.7.1-1.4.4-2c.3-.7.7-1.2 1.2-1.7s1.1-.8 1.8-1C6.9 1.1 7.5 1 8 1c-.2.3-.4.7-.6 1.1c-.2 0-.4 0-.7.2c-.5.1-1 .4-1.4.8c-.4.3-.8.8-1 1.3c-.2.5-.3 1-.3 1.6v2.2c0 .9-.2 1.8-.4 2.7L3.2 12h9.6l-.4-1.1c-.175-.526-.274-1.13-.363-1.674L12 9c.4 0 .7 0 1-.1zM8 14c.2 0 .5-.1.7-.3c.2-.2.3-.4.3-.7H7c0 .3.1.5.3.7c.2.2.5.3.7.3zm7-10a3 3 0 1 1-6 0a3 3 0 0 1 6 0z"></path></g>',
});
// Add few mdi-light: icons
addCollection({
prefix: 'mdi-light',
addOfflineCollection({
prefix: 'offline-mdi-light',
icons: {
'account-alert': {
body:
@ -52,11 +55,27 @@ addCollection({
width: 24,
height: 24,
});
addOnlineCollection({
prefix: '',
icons: {
alert1: {
body:
'<path d="M10.5 14c4.142 0 7.5 1.567 7.5 3.5V20H3v-2.5c0-1.933 3.358-3.5 7.5-3.5zm6.5 3.5c0-1.38-2.91-2.5-6.5-2.5S4 16.12 4 17.5V19h13v-1.5zM10.5 5a3.5 3.5 0 1 1 0 7a3.5 3.5 0 0 1 0-7zm0 1a2.5 2.5 0 1 0 0 5a2.5 2.5 0 0 0 0-5zM20 16v-1h1v1h-1zm0-3V7h1v6h-1z" fill="currentColor"/>',
},
link1: {
body:
'<path d="M8 13v-1h7v1H8zm7.5-6a5.5 5.5 0 1 1 0 11H13v-1h2.5a4.5 4.5 0 1 0 0-9H13V7h2.5zm-8 11a5.5 5.5 0 1 1 0-11H10v1H7.5a4.5 4.5 0 1 0 0 9H10v1H7.5z" fill="currentColor"/>',
},
},
width: 24,
height: 24,
});
function App() {
return (
<div className="App">
<OfflineUsageDemo />
<FullOfflineUsageDemo />
<section>
<h1>Checkbox</h1>
@ -77,6 +96,7 @@ function App() {
<InlineDemo />
<TestsOffline />
<TestsFullOffline />
</div>
);
}

View File

@ -1,27 +0,0 @@
import React from 'react';
import { Icon } from '@iconify/react/dist/offline';
export function InlineDemo() {
return (
<section className="inline-demo">
<h1>Inline demo</h1>
<div>
Block icon (behaving like image):
<Icon icon="experiment2" />
</div>
<div>
Inline icon (behaving line text / icon font):
<Icon icon="experiment2" inline={true} />
</div>
<div>
Using "vertical-align: 0" to override inline attribute:
<Icon icon="experiment2" style={{ verticalAlign: 0 }} />
<Icon
icon="experiment2"
style={{ verticalAlign: 0 }}
inline={true}
/>
</div>
</section>
);
}

View File

@ -1,29 +0,0 @@
import React from 'react';
import { InlineIcon } from '@iconify/react/dist/offline';
import successIcon from '@iconify-icons/uil/check-circle';
import pendingIcon from '@iconify-icons/uil/question-circle';
import failedIcon from '@iconify-icons/uil/times-circle';
export function TestIcon(props) {
let icon = pendingIcon;
let className = '';
switch (props.status) {
case 'success':
case 'default-success':
case true:
icon = successIcon;
className = 'success';
break;
case 'fail':
case false:
icon = failedIcon;
className = 'fail';
break;
default:
}
return <InlineIcon icon={icon} className={className} />;
}

View File

@ -1,285 +0,0 @@
import React from 'react';
import { InlineIcon } from '@iconify/react/dist/offline';
import { TestIcon } from './TestIcon';
export class TestsOffline extends React.Component {
constructor(props) {
super();
const state = {
ref_missing: 'default-success',
};
this.state = state;
}
render() {
const state = this.state;
const success = this._toggle.bind(this, 'success');
const fail = this._toggle.bind(this, 'fail');
return (
<section className="tests">
<h1>Tests (offline module)</h1>
<h2>References</h2>
<div className="test-row">
<TestIcon status={state.ref} />
<span>
Getting reference
<InlineIcon
icon="demo"
ref={(element) => {
const key = 'ref';
if (element && element.tagName === 'svg') {
success(key);
} else {
fail(key);
}
}}
/>
</span>
</div>
<div className="test-row">
<TestIcon status={state.ref_missing} />
<span>
Getting reference for empty icon
<InlineIcon
ref={(element) => {
// Cannot be called because there is no SVG to render!
fail('ref_missing');
}}
/>
</span>
</div>
<div className="test-row">
<TestIcon status={state.ref_missing} />
<span>
Getting reference for missing icon with fallback text{' '}
<InlineIcon
ref={(element) => {
// Cannot be called because there is no SVG to render!
fail('ref_missing');
}}
>
😀
</InlineIcon>
</span>
</div>
<h2>Style</h2>
<div className="test-row">
<TestIcon status={state.style} />
<span>
Inline style for icon
<InlineIcon
icon="demo"
style={{
color: '#1769aa',
fontSize: '24px',
lineHeight: '1em',
verticalAlign: '-0.25em',
}}
ref={(element) => {
const key = 'style';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(23, 105, 170)':
case '#1769aa':
break;
default:
console.log(
'Invalid color:',
style.color
);
errors = true;
}
if (style.fontSize !== '24px') {
console.log(
'Invalid font-size:',
style.fontSize
);
errors = true;
}
if (style.verticalAlign !== '-0.25em') {
console.log(
'Invalid vertical-align:',
style.verticalAlign
);
errors = true;
}
if (errors) {
fail(key);
} else {
success(key);
}
} else {
fail(key);
}
}}
/>
</span>
</div>
<div className="test-row">
<TestIcon status={state.color1} />
<span>
Green color from attribute:{' '}
<InlineIcon
icon="demo"
color="green"
ref={(element) => {
const key = 'color1';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log(
'Invalid color:',
style.color
);
errors = true;
}
if (errors) {
fail(key);
} else {
success(key);
}
} else {
fail(key);
}
}}
/>
</span>
</div>
<div className="test-row">
<TestIcon status={state.color2} />
<span>
Green color from style:{' '}
<InlineIcon
icon="demo"
style={{
color: 'green',
}}
ref={(element) => {
const key = 'color2';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log(
'Invalid color:',
style.color
);
errors = true;
}
if (errors) {
fail(key);
} else {
success(key);
}
} else {
fail(key);
}
}}
/>
</span>
</div>
<div className="test-row">
<TestIcon status={state.color3} />
<span>
Green color from attribute (overrides style) + red from
style:{' '}
<InlineIcon
icon="demo"
color="green"
style={{
color: 'red',
}}
ref={(element) => {
const key = 'color3';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log(
'Invalid color:',
style.color
);
errors = true;
}
if (errors) {
fail(key);
} else {
success(key);
}
} else {
fail(key);
}
}}
/>
</span>
</div>
</section>
);
}
_toggle(value, key) {
setTimeout(() => {
const oldValue = this.state[key];
if (
oldValue === value ||
oldValue === 'success' ||
oldValue === 'fail'
) {
return;
}
this.setState({
[key]: value,
});
});
}
}

View File

@ -0,0 +1,36 @@
import React from 'react';
import { Icon as OfflineIcon } from '@iconify/react/dist/offline';
import { Icon as FullIcon } from '@iconify/react/dist/iconify';
export function InlineDemo() {
return (
<section className="inline-demo">
<h1>Inline demo</h1>
<div>
Block icon (behaving like image):
<OfflineIcon icon="experiment2" />
<FullIcon icon="experiment2" />
</div>
<div>
Inline icon (behaving line text / icon font):
<OfflineIcon icon="experiment2" inline={true} />
<FullIcon icon="experiment2" inline={true} />
</div>
<div>
Using "vertical-align: 0" to override inline attribute:
<OfflineIcon icon="experiment2" style={{ verticalAlign: 0 }} />
<FullIcon icon="experiment2" style={{ verticalAlign: 0 }} />
<OfflineIcon
icon="experiment2"
style={{ verticalAlign: 0 }}
inline={true}
/>
<FullIcon
icon="experiment2"
style={{ verticalAlign: 0 }}
inline={true}
/>
</div>
</section>
);
}

View File

@ -0,0 +1,26 @@
import React from 'react';
import { Icon } from '@iconify/react/dist/iconify';
import accountIcon from '@iconify-icons/mdi-light/account';
import alertIcon from '@iconify-icons/mdi-light/alert';
export function FullOfflineUsageDemo() {
return (
<section className="icon-24">
<h1>Usage (full module, offline mode)</h1>
<div>
Icon referenced by name: <Icon icon="demo" />
</div>
<div>
Icon referenced by object: <Icon icon={accountIcon} />
</div>
<div>
2 icons imported from icon set: <Icon icon="alert1" />
<Icon icon="link1" />
</div>
<div className="alert">
<Icon icon={alertIcon} />
Important notice with alert icon!
</div>
</section>
);
}

View File

@ -7,9 +7,6 @@ export function OfflineUsageDemo() {
return (
<section className="icon-24">
<h1>Usage (offline module)</h1>
<div>
Empty icon: <Icon />
</div>
<div>
Icon referenced by name: <Icon icon="demo" />
</div>
@ -18,8 +15,8 @@ export function OfflineUsageDemo() {
</div>
<div>
2 icons imported from icon set:{' '}
<Icon icon="mdi-light:account-alert" />
<Icon icon="mdi-light:link" />
<Icon icon="offline-mdi-light:account-alert" />
<Icon icon="offline-mdi-light:link" />
</div>
<div className="alert">
<Icon icon={alertIcon} />

View File

@ -0,0 +1,78 @@
import React from 'react';
import { InlineIcon } from '@iconify/react/dist/offline';
import successIcon from '@iconify-icons/uil/check-circle';
import pendingIcon from '@iconify-icons/uil/question-circle';
import failedIcon from '@iconify-icons/uil/times-circle';
function getStatus(status) {
switch (status) {
case 'success':
case 'default-success':
case true:
return 'success';
case 'failed':
case 'fail':
case false:
return 'failed';
default:
return 'pending';
}
}
export function TestIcons(props) {
if (!props.id) {
return null;
}
const id = 'test-icons-' + props.id;
const icon = getStatus(props.status);
return (
<span className="test-row-icons" id={id}>
<InlineIcon
icon={successIcon}
className={
'success ' + (icon === 'success' ? 'visible' : 'hidden')
}
/>
<InlineIcon
icon={pendingIcon}
className={
'pending ' + (icon === 'pending' ? 'visible' : 'hidden')
}
/>
<InlineIcon
icon={failedIcon}
className={
'failed ' + (icon === 'failed' ? 'visible' : 'hidden')
}
/>
</span>
);
}
export function toggleTest(id, status) {
const node = document.getElementById('test-icons-' + id);
if (!node) {
return;
}
// Get icon to show
const icon = getStatus(status);
// Remove previous status
const visible = node.querySelector('.visible');
if (visible) {
visible.classList.remove('visible');
visible.classList.add('hidden');
}
// Show new icon
const toggle = node.querySelector('.' + icon);
if (toggle) {
toggle.classList.remove('hidden');
toggle.classList.add('visible');
}
}

View File

@ -0,0 +1,212 @@
import React from 'react';
import { InlineIcon } from '@iconify/react/dist/iconify';
import { TestIcons, toggleTest } from './TestIcons';
export function TestsFullOffline() {
return (
<section className="tests">
<h1>Tests (full module, without API)</h1>
<h2>References</h2>
<div className="test-row">
<TestIcons id="full-offline-ref1" />
Getting reference
<InlineIcon
icon="demo"
ref={(element) => {
const key = 'full-offline-ref1';
if (element && element.tagName === 'svg') {
toggleTest(key, 'success');
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-offline-ref-missing" status="success" />
Getting reference for empty icon
<InlineIcon
ref={(element) => {
// Cannot be called because there is no SVG to render!
toggleTest('full-offline-ref-missing', 'failed');
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-offline-ref-missing2" status="success" />
Getting reference for missing icon with fallback text{' '}
<InlineIcon
ref={(element) => {
// Cannot be called because there is no SVG to render!
toggleTest('full-offline-ref-missing2', 'failed');
}}
>
😀
</InlineIcon>
</div>
<h2>Style</h2>
<div className="test-row">
<TestIcons id="full-offline-style" />
Inline style for icon
<InlineIcon
icon="demo"
style={{
color: '#1769aa',
fontSize: '24px',
lineHeight: '1em',
verticalAlign: '-0.25em',
}}
ref={(element) => {
const key = 'full-offline-style';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(23, 105, 170)':
case '#1769aa':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
if (style.fontSize !== '24px') {
console.log(
'Invalid font-size:',
style.fontSize
);
errors = true;
}
if (style.verticalAlign !== '-0.25em') {
console.log(
'Invalid vertical-align:',
style.verticalAlign
);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-offline-color1" />
Green color from attribute:{' '}
<InlineIcon
icon="demo"
color="green"
ref={(element) => {
const key = 'full-offline-color1';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-offline-color2" />
Green color from style:{' '}
<InlineIcon
icon="demo"
style={{
color: 'green',
}}
ref={(element) => {
const key = 'full-offline-color2';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="full-offline-color3" />
Green color from attribute (overrides style) + red from style:{' '}
<InlineIcon
icon="demo"
color="green"
style={{
color: 'red',
}}
ref={(element) => {
const key = 'full-offline-color3';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
</section>
);
}

View File

@ -0,0 +1,212 @@
import React from 'react';
import { InlineIcon } from '@iconify/react/dist/offline';
import { TestIcons, toggleTest } from './TestIcons';
export function TestsOffline() {
return (
<section className="tests">
<h1>Tests (offline module)</h1>
<h2>References</h2>
<div className="test-row">
<TestIcons id="offline-ref1" />
Getting reference
<InlineIcon
icon="demo"
ref={(element) => {
const key = 'offline-ref1';
if (element && element.tagName === 'svg') {
toggleTest(key, 'success');
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="offline-ref-missing" status="success" />
Getting reference for empty icon
<InlineIcon
ref={(element) => {
// Cannot be called because there is no SVG to render!
toggleTest('offline-ref-missing', 'failed');
}}
/>
</div>
<div className="test-row">
<TestIcons id="offline-ref-missing2" status="success" />
Getting reference for missing icon with fallback text{' '}
<InlineIcon
ref={(element) => {
// Cannot be called because there is no SVG to render!
toggleTest('offline-ref-missing2', 'failed');
}}
>
😀
</InlineIcon>
</div>
<h2>Style</h2>
<div className="test-row">
<TestIcons id="offline-style" />
Inline style for icon
<InlineIcon
icon="demo"
style={{
color: '#1769aa',
fontSize: '24px',
lineHeight: '1em',
verticalAlign: '-0.25em',
}}
ref={(element) => {
const key = 'offline-style';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(23, 105, 170)':
case '#1769aa':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
if (style.fontSize !== '24px') {
console.log(
'Invalid font-size:',
style.fontSize
);
errors = true;
}
if (style.verticalAlign !== '-0.25em') {
console.log(
'Invalid vertical-align:',
style.verticalAlign
);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="offline-color1" />
Green color from attribute:{' '}
<InlineIcon
icon="demo"
color="green"
ref={(element) => {
const key = 'offline-color1';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="offline-color2" />
Green color from style:{' '}
<InlineIcon
icon="demo"
style={{
color: 'green',
}}
ref={(element) => {
const key = 'offline-color2';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
<div className="test-row">
<TestIcons id="offline-color3" />
Green color from attribute (overrides style) + red from style:{' '}
<InlineIcon
icon="demo"
color="green"
style={{
color: 'red',
}}
ref={(element) => {
const key = 'offline-color3';
if (element && element.tagName === 'svg') {
let errors = false;
// Get style
const style = element.style;
switch (style.color.toLowerCase()) {
case 'rgb(0, 128, 0)':
case '#008000':
case 'green':
break;
default:
console.log('Invalid color:', style.color);
errors = true;
}
toggleTest(key, !errors);
} else {
toggleTest(key, 'failed');
}
}}
/>
</div>
</section>
);
}

View File

@ -12,6 +12,7 @@
"@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13",
"@iconify/core": "^1.0.0-rc.4",
"@iconify/types": "^1.0.6",
"@microsoft/api-extractor": "^7.13.5",
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-commonjs": "^18.0.0",

View File

@ -27,6 +27,7 @@
"@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13",
"@iconify/core": "^1.0.0-rc.4",
"@iconify/types": "^1.0.6",
"@microsoft/api-extractor": "^7.13.5",
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-commonjs": "^18.0.0",

View File

@ -1,40 +1,293 @@
import React from 'react';
import type { IconifyIcon, IconifyJSON } from '@iconify/types';
import type { IconifyJSON } from '@iconify/types';
// Core
import { stringToIcon } from '@iconify/core/lib/icon/name';
import type { IconifyIconName } from '@iconify/core/lib/icon/name';
import type {
IconifyIconSize,
IconifyHorizontalIconAlignment,
IconifyVerticalIconAlignment,
IconifyIconSize,
} from '@iconify/core/lib/customisations';
import { fullIcon } from '@iconify/core/lib/icon';
import { parseIconSet } from '@iconify/core/lib/icon/icon-set';
import {
IconifyStorageFunctions,
storageFunctions,
getIconData,
allowSimpleNames,
} from '@iconify/core/lib/storage/functions';
import {
IconifyBuilderFunctions,
builderFunctions,
} from '@iconify/core/lib/builder/functions';
import { fullIcon, IconifyIcon } from '@iconify/core/lib/icon';
// Modules
import { coreModules } from '@iconify/core/lib/modules';
// API
import { API, IconifyAPIInternalStorage } from '@iconify/core/lib/api/';
import {
IconifyAPIFunctions,
IconifyAPIInternalFunctions,
APIFunctions,
APIInternalFunctions,
} from '@iconify/core/lib/api/functions';
import {
setAPIModule,
IconifyAPIModule,
IconifyAPISendQuery,
IconifyAPIPrepareQuery,
GetIconifyAPIModule,
} from '@iconify/core/lib/api/modules';
import { getAPIModule as getJSONPAPIModule } from '@iconify/core/lib/api/modules/jsonp';
import {
getAPIModule as getFetchAPIModule,
setFetch,
} from '@iconify/core/lib/api/modules/fetch';
import {
setAPIConfig,
PartialIconifyAPIConfig,
IconifyAPIConfig,
getAPIConfig,
GetAPIConfig,
} from '@iconify/core/lib/api/config';
import type {
IconifyIconLoaderCallback,
IconifyIconLoaderAbort,
} from '@iconify/core/lib/interfaces/loader';
// Cache
import { storeCache, loadCache } from '@iconify/core/lib/browser-storage';
import { toggleBrowserCache } from '@iconify/core/lib/browser-storage/functions';
import type {
IconifyBrowserCacheType,
IconifyBrowserCacheFunctions,
} from '@iconify/core/lib/browser-storage/functions';
// Properties
import type {
IconifyIconCustomisations,
IconifyIconProps,
IconProps,
IconRef,
} from './props';
// Render SVG
import { render } from './render';
/**
* Export stuff from props.ts
*/
export { IconifyIconCustomisations, IconifyIconProps, IconProps };
/**
* Export types that could be used in component
* Export required types
*/
// Function sets
export {
IconifyIcon,
IconifyJSON,
IconifyHorizontalIconAlignment,
IconifyVerticalIconAlignment,
IconifyIconSize,
IconifyStorageFunctions,
IconifyBuilderFunctions,
IconifyBrowserCacheFunctions,
IconifyAPIFunctions,
IconifyAPIInternalFunctions,
};
// JSON stuff
export { IconifyIcon, IconifyJSON, IconifyIconName };
// Customisations
export {
IconifyIconCustomisations,
IconifyIconSize,
IconifyHorizontalIconAlignment,
IconifyVerticalIconAlignment,
IconifyIconProps,
IconProps,
};
// API
export {
IconifyAPIConfig,
IconifyIconLoaderCallback,
IconifyIconLoaderAbort,
IconifyAPIInternalStorage,
IconifyAPIModule,
GetAPIConfig,
IconifyAPIPrepareQuery,
IconifyAPISendQuery,
};
/* Browser cache */
export { IconifyBrowserCacheType };
/**
* Storage for icons referred by name
* Enable and disable browser cache
*/
export const enableCache = (storage: IconifyBrowserCacheType) =>
toggleBrowserCache(storage, true);
export const disableCache = (storage: IconifyBrowserCacheType) =>
toggleBrowserCache(storage, false);
/* Storage functions */
/**
* Check if icon exists
*/
export const iconExists = storageFunctions.iconExists;
/**
* Get icon data
*/
export const getIcon = storageFunctions.getIcon;
/**
* List available icons
*/
export const listIcons = storageFunctions.listIcons;
/**
* Add one icon
*/
export const addIcon = storageFunctions.addIcon;
/**
* Add icon set
*/
export const addCollection = storageFunctions.addCollection;
/* Builder functions */
/**
* Calculate icon size
*/
export const calculateSize = builderFunctions.calculateSize;
/**
* Replace unique ids in content
*/
export const replaceIDs = builderFunctions.replaceIDs;
/* API functions */
/**
* Load icons
*/
export const loadIcons = APIFunctions.loadIcons;
/**
* Add API provider
*/
export const addAPIProvider = APIFunctions.addAPIProvider;
/**
* Export internal functions that can be used by third party implementations
*/
export const _api = APIInternalFunctions;
/**
* Initialise stuff
*/
// Enable short names
allowSimpleNames(true);
// Set API
coreModules.api = API;
let getAPIModule: GetIconifyAPIModule;
try {
getAPIModule =
typeof fetch === 'function' && typeof Promise === 'function'
? getFetchAPIModule
: getJSONPAPIModule;
} catch (err) {
getAPIModule = getJSONPAPIModule;
}
setAPIModule('', getAPIModule(getAPIConfig));
/**
* Enable node-fetch for getting icons on server side
*/
export function setNodeFetch(nodeFetch: typeof fetch) {
setFetch(nodeFetch);
if (getAPIModule !== getFetchAPIModule) {
getAPIModule = getFetchAPIModule;
setAPIModule('', getAPIModule(getAPIConfig));
}
}
/**
* Browser stuff
*/
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
// Set cache and load existing cache
coreModules.cache = storeCache;
loadCache();
const _window = window;
// Load icons from global "IconifyPreload"
interface WindowWithIconifyPreload {
IconifyPreload: IconifyJSON[] | IconifyJSON;
}
if (
((_window as unknown) as WindowWithIconifyPreload).IconifyPreload !==
void 0
) {
const preload = ((_window as unknown) as WindowWithIconifyPreload)
.IconifyPreload;
const err = 'Invalid IconifyPreload syntax.';
if (typeof preload === 'object' && preload !== null) {
(preload instanceof Array ? preload : [preload]).forEach((item) => {
try {
if (
// Check if item is an object and not null/array
typeof item !== 'object' ||
item === null ||
item instanceof Array ||
// Check for 'icons' and 'prefix'
typeof item.icons !== 'object' ||
typeof item.prefix !== 'string' ||
// Add icon set
!addCollection(item)
) {
console.error(err);
}
} catch (e) {
console.error(err);
}
});
}
}
// Set API from global "IconifyProviders"
interface WindowWithIconifyProviders {
IconifyProviders: Record<string, PartialIconifyAPIConfig>;
}
if (
((_window as unknown) as WindowWithIconifyProviders)
.IconifyProviders !== void 0
) {
const providers = ((_window as unknown) as WindowWithIconifyProviders)
.IconifyProviders;
if (typeof providers === 'object' && providers !== null) {
for (let key in providers) {
const err = 'IconifyProviders[' + key + '] is invalid.';
try {
const value = providers[key];
if (
typeof value !== 'object' ||
!value ||
value.resources === void 0
) {
continue;
}
if (!setAPIConfig(key, value)) {
console.error(err);
}
} catch (e) {
console.error(err);
}
}
}
}
}
/**
* Component
*/
const storage: Record<string, Required<IconifyIcon>> = Object.create(null);
/**
* Generate icon
@ -44,27 +297,32 @@ function component(
inline: boolean,
ref?: IconRef
): JSX.Element {
// Split properties
const icon =
typeof props.icon === 'string'
? storage[props.icon]
: typeof props.icon === 'object'
? fullIcon(props.icon)
: null;
const icon = props.icon;
// Validate icon object
if (
icon === null ||
typeof icon !== 'object' ||
typeof icon.body !== 'string'
) {
return props.children
? (props.children as JSX.Element)
: React.createElement('span', {});
// Check if icon is an object
if (typeof icon === 'object' && typeof icon.body === 'string') {
return render(fullIcon(icon), props, inline, ref);
}
// Valid icon: render it
return render(icon, props, inline, ref);
// Check if icon is a string
if (typeof icon === 'string') {
const iconName = stringToIcon(icon, true, true);
if (iconName) {
// Valid icon name
const iconData = getIconData(iconName);
if (iconData) {
// Icon is available
return render(iconData, props, inline, ref);
}
// TODO: icon is missing
}
}
// Error
return props.children
? (props.children as JSX.Element)
: React.createElement('span', {});
}
/**
@ -89,36 +347,3 @@ export const Icon: Component = React.forwardRef(
export const InlineIcon: Component = React.forwardRef(
(props: IconProps, ref?: IconRef) => component(props, true, ref)
);
/**
* Add icon to storage, allowing to call it by name
*
* @param name
* @param data
*/
export function addIcon(name: string, data: IconifyIcon): void {
storage[name] = fullIcon(data);
}
/**
* Add collection to storage, allowing to call icons by name
*
* @param data Icon set
* @param prefix Optional prefix to add to icon names, true if prefix from icon set should be used.
*/
export function addCollection(
data: IconifyJSON,
prefix?: string | boolean
): void {
const iconPrefix: string =
typeof prefix === 'string'
? prefix
: prefix !== false && typeof data.prefix === 'string'
? data.prefix + ':'
: '';
parseIconSet(data, (name, icon) => {
if (icon !== null) {
storage[iconPrefix + name] = icon;
}
});
}

View File

@ -0,0 +1,62 @@
import React from 'react';
import { Icon, InlineIcon } from '../../dist/iconify';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Creating component using object', () => {
test('basic icon', () => {
const component = renderer.create(<Icon icon={iconData} />);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {},
'dangerouslySetInnerHTML': {
__html: iconData.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconData.width + ' ' + iconData.height,
},
children: null,
});
});
test('inline icon', () => {
const component = renderer.create(<InlineIcon icon={iconData} />);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'svg',
props: {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {
verticalAlign: '-0.125em',
},
'dangerouslySetInnerHTML': {
__html: iconData.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconData.width + ' ' + iconData.height,
},
children: null,
});
});
});

View File

@ -0,0 +1,57 @@
import React from 'react';
import { Icon } from '../../dist/iconify';
import renderer from 'react-test-renderer';
describe('Empty icon', () => {
test('basic test', () => {
const component = renderer.create(<Icon />);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'span',
props: {},
children: null,
});
});
test('with child node', () => {
const component = renderer.create(
<Icon>
<i class="fa fa-home" />
</Icon>
);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'i',
props: {},
children: null,
});
});
test('with text child node', () => {
const component = renderer.create(<Icon>icon</Icon>);
const tree = component.toJSON();
expect(tree).toMatch('icon');
});
test('with multiple childen', () => {
const component = renderer.create(
<Icon>
<i class="fa fa-home" />
Home
</Icon>
);
const tree = component.toJSON();
expect(tree).toMatchObject([
{
type: 'i',
props: {},
children: null,
},
'Home',
]);
});
});

View File

@ -0,0 +1,88 @@
import React from 'react';
import { Icon, InlineIcon } from '../../dist/iconify';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Passing attributes', () => {
test('title', () => {
const component = renderer.create(
<Icon icon={iconData} title="Icon!" />
);
const tree = component.toJSON();
expect(tree.props.title).toStrictEqual('Icon!');
});
test('aria-hidden', () => {
const component = renderer.create(
<InlineIcon icon={iconData} aria-hidden="false" />
);
const tree = component.toJSON();
expect(tree.props['aria-hidden']).toStrictEqual(void 0);
});
test('ariaHidden', () => {
const component = renderer.create(
<InlineIcon icon={iconData} ariaHidden="false" />
);
const tree = component.toJSON();
expect(tree.props['aria-hidden']).toStrictEqual(void 0);
});
test('style', () => {
const component = renderer.create(
<InlineIcon
icon={iconData}
style={{ verticalAlign: '0', color: 'red' }}
/>
);
const tree = component.toJSON();
expect(tree.props.style).toMatchObject({
verticalAlign: '0',
color: 'red',
});
});
test('color', () => {
const component = renderer.create(<Icon icon={iconData} color="red" />);
const tree = component.toJSON();
expect(tree.props.style).toMatchObject({
color: 'red',
});
});
test('color with style', () => {
const component = renderer.create(
<Icon icon={iconData} color="red" style={{ color: 'green' }} />
);
const tree = component.toJSON();
expect(tree.props.style).toMatchObject({
color: 'red',
});
});
test('attributes that cannot change', () => {
const component = renderer.create(
<InlineIcon
icon={iconData}
viewBox="0 0 0 0"
preserveAspectRatio="none"
/>
);
const tree = component.toJSON();
expect(tree.props.viewBox).toStrictEqual('0 0 24 24');
expect(tree.props.preserveAspectRatio).toStrictEqual('xMidYMid meet');
});
});

View File

@ -0,0 +1,42 @@
import React from 'react';
import { InlineIcon } from '../../dist/iconify';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Dimensions', () => {
test('height', () => {
const component = renderer.create(
<InlineIcon icon={iconData} height="48" />
);
const tree = component.toJSON();
expect(tree.props.height).toStrictEqual('48');
expect(tree.props.width).toStrictEqual('48');
});
test('width and height', () => {
const component = renderer.create(
<InlineIcon icon={iconData} width={32} height="48" />
);
const tree = component.toJSON();
expect(tree.props.height).toStrictEqual('48');
expect(tree.props.width).toStrictEqual('32');
});
test('auto', () => {
const component = renderer.create(
<InlineIcon icon={iconData} height="auto" />
);
const tree = component.toJSON();
expect(tree.props.height).toStrictEqual('24');
expect(tree.props.width).toStrictEqual('24');
});
});

View File

@ -0,0 +1,57 @@
import React from 'react';
import { Icon } from '../../dist/iconify';
import renderer from 'react-test-renderer';
const iconDataWithID = {
body:
'<defs><path id="ssvg-id-1st-place-medala" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medald" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medalf" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medalh" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medalj" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medalm" d="M.93.01h120.55v58.36H.93z"/><path d="M52.849 78.373v-3.908c3.681-.359 6.25-.958 7.703-1.798c1.454-.84 2.54-2.828 3.257-5.962h4.021v40.385h-5.437V78.373h-9.544z" id="ssvg-id-1st-place-medalp"/><linearGradient x1="49.998%" y1="-13.249%" x2="49.998%" y2="90.002%" id="ssvg-id-1st-place-medalb"><stop stop-color="#1E88E5" offset="13.55%"/><stop stop-color="#1565C0" offset="93.8%"/></linearGradient><linearGradient x1="26.648%" y1="2.735%" x2="77.654%" y2="105.978%" id="ssvg-id-1st-place-medalk"><stop stop-color="#64B5F6" offset="13.55%"/><stop stop-color="#2196F3" offset="94.62%"/></linearGradient><radialGradient cx="22.368%" cy="12.5%" fx="22.368%" fy="12.5%" r="95.496%" id="ssvg-id-1st-place-medalo"><stop stop-color="#FFEB3B" offset="29.72%"/><stop stop-color="#FBC02D" offset="95.44%"/></radialGradient></defs><g fill="none" fill-rule="evenodd"><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medalc" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medala"/></mask><path fill="url(#ssvg-id-1st-place-medalb)" fill-rule="nonzero" mask="url(#ssvg-id-1st-place-medalc)" d="M45.44 42.18h31.43l30-48.43H75.44z"/></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medale" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medald"/></mask><g opacity=".2" mask="url(#ssvg-id-1st-place-medale)" fill="#424242" fill-rule="nonzero"><path d="M101.23-3L75.2 39H50.85L77.11-3h24.12zm5.64-3H75.44l-30 48h31.42l30.01-48z"/></g></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medalg" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medalf"/></mask><path d="M79 30H43c-4.42 0-8 3.58-8 8v16.04c0 2.17 1.8 3.95 4.02 3.96h.01c2.23-.01 4.97-1.75 4.97-3.96V44c0-1.1.9-2 2-2h30c1.1 0 2 .9 2 2v9.93c0 1.98 2.35 3.68 4.22 4.04c.26.05.52.08.78.08c2.21 0 4-1.79 4-4V38c0-4.42-3.58-8-8-8z" fill="#FDD835" fill-rule="nonzero" mask="url(#ssvg-id-1st-place-medalg)"/></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medali" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medalh"/></mask><g opacity=".2" mask="url(#ssvg-id-1st-place-medali)" fill="#424242" fill-rule="nonzero"><path d="M79 32c3.31 0 6 2.69 6 6v16.04A2.006 2.006 0 0 1 82.59 56c-1.18-.23-2.59-1.35-2.59-2.07V44c0-2.21-1.79-4-4-4H46c-2.21 0-4 1.79-4 4v10.04c0 .88-1.64 1.96-2.97 1.96c-1.12-.01-2.03-.89-2.03-1.96V38c0-3.31 2.69-6 6-6h36zm0-2H43c-4.42 0-8 3.58-8 8v16.04c0 2.17 1.8 3.95 4.02 3.96h.01c2.23-.01 4.97-1.75 4.97-3.96V44c0-1.1.9-2 2-2h30c1.1 0 2 .9 2 2v9.93c0 1.98 2.35 3.68 4.22 4.04c.26.05.52.08.78.08c2.21 0 4-1.79 4-4V38c0-4.42-3.58-8-8-8z"/></g></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medall" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medalj"/></mask><path fill="url(#ssvg-id-1st-place-medalk)" fill-rule="nonzero" mask="url(#ssvg-id-1st-place-medall)" d="M76.87 42.18H45.44l-30-48.43h31.43z"/></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medaln" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medalm"/></mask><g opacity=".2" mask="url(#ssvg-id-1st-place-medaln)" fill="#424242" fill-rule="nonzero"><path d="M45.1-3l26.35 42H47.1L20.86-3H45.1zm1.77-3H15.44l30 48h31.42L46.87-6z"/></g></g><circle fill="url(#ssvg-id-1st-place-medalo)" fill-rule="nonzero" cx="64" cy="86" r="38"/><path d="M64 51c19.3 0 35 15.7 35 35s-15.7 35-35 35s-35-15.7-35-35s15.7-35 35-35zm0-3c-20.99 0-38 17.01-38 38s17.01 38 38 38s38-17.01 38-38s-17.01-38-38-38z" opacity=".2" fill="#424242" fill-rule="nonzero"/><path d="M47.3 63.59h33.4v44.4H47.3z"/><use fill="#000" xlink:href="#ssvg-id-1st-place-medalp"/><use fill="#FFA000" xlink:href="#ssvg-id-1st-place-medalp"/></g>',
width: 128,
height: 128,
};
describe('Replacing IDs', () => {
test('default behavior', () => {
const component = renderer.create(<Icon icon={iconDataWithID} />);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconDataWithID.body);
});
test('custom generator', () => {
const component = renderer.create(
<Icon icon={iconDataWithID} id="test" />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
// Generate expected body
let expected = iconDataWithID.body;
const replacements = {
'ssvg-id-1st-place-medala': 'test-0',
'ssvg-id-1st-place-medald': 'test-1',
'ssvg-id-1st-place-medalf': 'test-2',
'ssvg-id-1st-place-medalh': 'test-3',
'ssvg-id-1st-place-medalj': 'test-4',
'ssvg-id-1st-place-medalm': 'test-5',
'ssvg-id-1st-place-medalp': 'test-6',
'ssvg-id-1st-place-medalb': 'test-7',
'ssvg-id-1st-place-medalk': 'test-8',
'ssvg-id-1st-place-medalo': 'test-9',
'ssvg-id-1st-place-medalc': 'test-10',
'ssvg-id-1st-place-medale': 'test-11',
'ssvg-id-1st-place-medalg': 'test-12',
'ssvg-id-1st-place-medali': 'test-13',
'ssvg-id-1st-place-medall': 'test-14',
'ssvg-id-1st-place-medaln': 'test-15',
};
Object.keys(replacements).forEach((search) => {
expected = expected.replace(
new RegExp(search, 'g'),
replacements[search]
);
});
expect(body).toStrictEqual(expected);
});
});

View File

@ -0,0 +1,31 @@
import React from 'react';
import { Icon } from '../../dist/iconify';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Inline attribute', () => {
test('string', () => {
const component = renderer.create(
<Icon icon={iconData} inline="true" />
);
const tree = component.toJSON();
expect(tree.props.style.verticalAlign).toStrictEqual('-0.125em');
});
test('false string', () => {
// "false" = true
const component = renderer.create(
<Icon icon={iconData} inline="false" />
);
const tree = component.toJSON();
expect(tree.props.style.verticalAlign).toStrictEqual('-0.125em');
});
});

View File

@ -0,0 +1,135 @@
import React from 'react';
import { InlineIcon } from '../../dist/iconify';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Rotation', () => {
test('number', () => {
const component = renderer.create(
<InlineIcon icon={iconData} rotate={1} />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconData.body);
expect(body).toMatch('rotate(90 ');
});
test('string', () => {
const component = renderer.create(
<InlineIcon icon={iconData} rotate="180deg" />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconData.body);
expect(body).toMatch('rotate(180 ');
});
});
describe('Flip', () => {
test('boolean', () => {
const component = renderer.create(
<InlineIcon icon={iconData} hFlip={true} />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconData.body);
expect(body).toMatch('scale(-1 1)');
});
test('string', () => {
const component = renderer.create(
<InlineIcon icon={iconData} flip="vertical" />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconData.body);
expect(body).toMatch('scale(1 -1)');
});
test('string and boolean', () => {
const component = renderer.create(
<InlineIcon icon={iconData} flip="horizontal" vFlip={true} />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconData.body);
// horizontal + vertical = 180deg rotation
expect(body).toMatch('rotate(180 ');
});
test('string for boolean attribute', () => {
const component = renderer.create(
<InlineIcon icon={iconData} hFlip="true" />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconData.body);
expect(body).toMatch('scale(-1 1)');
});
test('shorthand and boolean', () => {
// 'flip' is processed after 'hFlip', overwriting value
const component = renderer.create(
<InlineIcon icon={iconData} flip="horizontal" hFlip={false} />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconData.body);
expect(body).toMatch('scale(-1 1)');
});
test('shorthand and boolean as string', () => {
const component = renderer.create(
<InlineIcon icon={iconData} flip="vertical" hFlip="true" />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toStrictEqual(iconData.body);
// horizontal + vertical = 180deg rotation
expect(body).toMatch('rotate(180 ');
});
test('wrong case', () => {
const component = renderer.create(
<InlineIcon icon={iconData} vflip={true} />
);
const tree = component.toJSON();
const body = tree.props.dangerouslySetInnerHTML.__html;
expect(body).not.toMatch('scale(');
});
});
describe('Alignment and slice', () => {
test('vAlign and slice', () => {
const component = renderer.create(
<InlineIcon icon={iconData} vAlign="top" slice={true} />
);
const tree = component.toJSON();
expect(tree.props.preserveAspectRatio).toStrictEqual('xMidYMin slice');
});
test('string', () => {
const component = renderer.create(
<InlineIcon icon={iconData} align="left bottom" />
);
const tree = component.toJSON();
expect(tree.props.preserveAspectRatio).toStrictEqual('xMinYMax meet');
});
});

View File

@ -1,14 +1,7 @@
import React from 'react';
import { Icon, InlineIcon } from '../../dist/offline';
import { Icon } from '../../dist/offline';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
describe('Empty icon', () => {
test('basic test', () => {
const component = renderer.create(<Icon />);

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Icon, InlineIcon } from '../../dist/offline';
import { Icon } from '../../dist/offline';
import renderer from 'react-test-renderer';
const iconData = {

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Icon, InlineIcon } from '../../dist/offline';
import { InlineIcon } from '../../dist/offline';
import renderer from 'react-test-renderer';
const iconData = {