2
0
mirror of https://github.com/iconify/iconify.git synced 2024-12-13 14:13:06 +00:00

Refactoring React: working offline component with tests and demo

This commit is contained in:
Vjacheslav Trushkin 2021-04-23 23:50:17 +03:00
parent a846dbcc8c
commit 9fb87b1e61
33 changed files with 39835 additions and 685 deletions

23
packages/react-demo/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,68 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

38244
packages/react-demo/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
{
"name": "@iconify/react-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "^4.0.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@iconify-icons/mdi-light": "^1.1.0",
"@iconify-icons/uil": "^1.1.1",
"@iconify/react": "^3.0.0-dev"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,132 @@
.App {
font-family: Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: left;
color: #2c3e50;
font-size: 16px;
line-height: 1.5;
}
/* Sections */
section {
border-bottom: 1px dotted #ccc;
padding: 16px;
}
section:last-child {
border-bottom-width: 0;
}
section:after {
content: ' ';
display: table;
clear: both;
}
h1 {
margin: 0 0 16px;
padding: 0;
font-size: 24px;
font-weight: normal;
}
p {
margin: 12px 0 4px;
padding: 0;
}
/* 24px icon */
.icon-24 svg {
font-size: 24px;
line-height: 1;
vertical-align: -0.25em;
}
/* Alert demo */
.alert {
position: relative;
margin: 8px;
padding: 16px;
padding-left: 48px;
background: #ba3329;
color: #fff;
border-radius: 5px;
float: left;
}
.alert + div {
clear: both;
}
.alert svg {
position: absolute;
left: 12px;
top: 50%;
font-size: 24px;
line-height: 1em;
margin: -0.5em 0 0;
}
/* Checkbox component */
.checkbox-container {
margin: 8px 0;
}
.checkbox {
cursor: pointer;
/* color: #1769aa; */
color: #626262;
text-decoration: none;
}
.checkbox:hover {
color: #ba3329;
text-decoration: underline;
}
.checkbox svg {
margin-right: 4px;
color: #afafaf;
font-size: 24px;
line-height: 1em;
vertical-align: -0.25em;
}
.checkbox--checked svg {
color: #327335;
}
.checkbox:hover svg {
color: inherit;
}
.checkbox-container small {
margin-left: 4px;
opacity: 0.7;
}
/* Inline demo */
.inline-demo svg {
color: #06a;
margin: 0 8px;
position: relative;
z-index: 2;
background: #fff;
}
.inline-demo div {
position: relative;
font-size: 16px;
line-height: 1.5;
}
.inline-demo div:before,
.inline-demo div:after {
content: '';
position: absolute;
left: 0;
right: 0;
height: 0;
border-top: 1px dashed #506874;
opacity: 0.5;
z-index: -1;
}
.inline-demo div:before {
bottom: 5px;
}
.inline-demo div:after {
bottom: 7px;
border-top-color: #ba3329;
}

230
packages/react-demo/src/App.js vendored Normal file
View File

@ -0,0 +1,230 @@
import React from 'react';
import {
Icon,
InlineIcon,
addIcon,
addCollection,
} from '@iconify/react/dist/offline';
import accountIcon from '@iconify-icons/mdi-light/account';
import alertIcon from '@iconify-icons/mdi-light/alert';
import homeIcon from '@iconify-icons/mdi-light/home';
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 { Checkbox } from './components/Checkbox';
import { InlineDemo } from './components/Inline';
import './App.css';
// Add 'mdi-light:presentation-play' as 'demo'
addIcon('demo', presentationPlay);
// Add custom icon as 'experiment'
addIcon('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', {
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,
});
// Add few mdi-light: icons
addCollection({
prefix: 'mdi-light',
icons: {
'account-alert': {
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"/>',
},
'link': {
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,
});
// Component
class CheckboxIcon extends React.Component {
constructor(props) {
super();
this.state = {
checked: props.checked,
};
this._check = this._check.bind(this);
}
render() {
const checked = this.state.checked;
const icon = checked ? checkedIcon : uncheckedIcon;
const color = checked ? 'green' : 'red';
return (
<InlineIcon
icon={icon}
onClick={this._check}
style={{ cursor: 'pointer', color }}
/>
);
}
_check(event) {
event.preventDefault();
this.setState({
checked: !this.state.checked,
});
}
}
function App() {
// Debug logging for testing references, which cannot be reliably tested with unit tests
console.log('References that should be logged: valid icon');
console.log('References that might be logged: missing icon');
console.log('References that should not be logged: missing text icon');
return (
<div className="App">
<section className="icon-24">
<h1>Usage</h1>
<div>
Empty icon: <Icon />
</div>
<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="mdi-light:account-alert" />
<Icon icon="mdi-light:link" />
</div>
<div className="alert">
<Icon icon={alertIcon} />
Important notice with alert icon!
</div>
</section>
<section>
<h1>Checkbox</h1>
<div>
<Checkbox
checked={true}
text="Checkbox example"
hint="(click to toggle)"
/>
<Checkbox
checked={false}
text="Another checkbox example"
hint="(click to toggle)"
/>
</div>
</section>
<InlineDemo />
<section>
<h1>Tests</h1>
<p>
<Icon
icon={homeIcon}
style={{
color: '#1769aa',
fontSize: '24px',
lineHeight: '1em',
verticalAlign: '-0.25em',
}}
/>
Home icon! 24px icon in 16px text with 24px line height,
aligned using inline style.
</p>
<p>
Empty icon (span): <Icon />
<br />
Empty icon (emoji): <Icon>😀</Icon>
<br />
Empty icon (em and emoji):
<Icon>
<em> empty</em>😀
</Icon>
</p>
<p>
Clickable icon (testing events and style): <CheckboxIcon />
</p>
<p>
Colored icons (block, inline, block):
<InlineIcon
icon={alertIcon}
style={{
fontSize: '1.5em',
verticalAlign: 0,
marginLeft: '8px',
}}
color="green"
/>
<InlineIcon
icon={alertIcon}
style={{
fontSize: '1.5em',
color: 'red',
marginLeft: '8px',
}}
/>
<Icon
icon={alertIcon}
style={{
fontSize: '1.5em',
color: 'purple',
marginLeft: '8px',
}}
/>
</p>
<p>
Testing reference by adding border to icon:{' '}
<Icon
icon="demo"
ref={(element) => {
console.log('Ref: valid icon');
element.style.border = '1px solid red';
}}
/>
<br />
Reference to missing icon:{' '}
<Icon
ref={(element) => {
console.log('Ref: missing icon');
element.style.border = '1px solid red';
}}
/>
<br />
Reference to missing icon with fallback text:{' '}
<Icon
ref={(element) => {
console.log('Ref: missing text icon');
element.style.border = '1px solid red';
}}
>
😀
</Icon>
</p>
<p>
Testing replacing ids in icons: <Icon icon="noto-robot" />{' '}
<Icon icon="noto-robot" /> (default handler)
<Icon icon="noto-robot" id="test1" />{' '}
<Icon icon="noto-robot" id="test2" /> (string)
</p>
</section>
</div>
);
}
export default App;

View File

@ -0,0 +1,38 @@
import React from 'react';
import { Icon } from '@iconify/react/dist/offline';
import checkedIcon from '@iconify-icons/uil/check-circle';
import uncheckedIcon from '@iconify-icons/uil/circle';
export class Checkbox extends React.Component {
constructor(props) {
super();
this.state = {
checked: props.checked,
};
this._check = this._check.bind(this);
}
render() {
const checked = this.state.checked;
const icon = checked ? checkedIcon : uncheckedIcon;
const className =
'checkbox checkbox--' + (checked ? 'checked' : 'unchecked');
return (
<div className="checkbox-container">
<a href="# " className={className} onClick={this._check}>
<Icon icon={icon} />
{this.props.text}
</a>
<small>{this.props.hint}</small>
</div>
);
}
_check(event) {
event.preventDefault();
this.setState({
checked: !this.state.checked,
});
}
}

View File

@ -0,0 +1,27 @@
import React from 'react';
import { Icon } from '@iconify/react/dist/offline';
export function InlineDemo() {
return (
<section className="inline-demo">
<h1>Inline (components/Inline.jsx)</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

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

12
packages/react-demo/src/index.js vendored Normal file
View File

@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

141
packages/react-demo/src/serviceWorker.js vendored Normal file
View File

@ -0,0 +1,141 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}

View File

@ -0,0 +1,43 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "lib/iconify.d.ts",
"bundledPackages": [
"@iconify/types",
"@iconify/core",
"@cyberalien/redundancy"
],
"compiler": {},
"apiReport": {
"enabled": false
},
"docModel": {
"enabled": false
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/dist/iconify.d.ts"
},
"tsdocMetadata": {
"enabled": false
},
"messages": {
"compilerMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"extractorMessageReporting": {
"default": {
"logLevel": "warning"
},
"ae-missing-release-tag": {
"logLevel": "none"
}
},
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
}
}
}
}

View File

@ -1,12 +1,12 @@
{
"name": "@iconify/react",
"version": "2.0.0-rc.9",
"version": "3.0.0-dev",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@iconify/react",
"version": "2.0.0-rc.9",
"version": "3.0.0-dev",
"license": "MIT",
"devDependencies": {
"@babel/preset-env": "^7.13.15",
@ -22,7 +22,6 @@
"react": "^17.0.2",
"react-test-renderer": "^17.0.2",
"rollup": "^2.45.2",
"rollup-plugin-terser": "^7.0.2",
"typescript": "^4.2.4"
}
},
@ -3331,7 +3330,8 @@
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
"dev": true,
"optional": true
},
"node_modules/commondir": {
"version": "1.0.1",
@ -7436,15 +7436,6 @@
"node": ">=0.6"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
@ -7850,21 +7841,6 @@
"fsevents": "~2.3.1"
}
},
"node_modules/rollup-plugin-terser": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"jest-worker": "^26.2.1",
"serialize-javascript": "^4.0.0",
"terser": "^5.0.0"
},
"peerDependencies": {
"rollup": "^2.0.0"
}
},
"node_modules/rsvp": {
"version": "4.8.5",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
@ -8219,15 +8195,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/serialize-javascript": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -8911,32 +8878,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/terser": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz",
"integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==",
"dev": true,
"dependencies": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.19"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@ -12291,7 +12232,8 @@
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
"dev": true,
"optional": true
},
"commondir": {
"version": "1.0.1",
@ -15445,15 +15387,6 @@
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"requires": {
"safe-buffer": "^5.1.0"
}
},
"react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
@ -15770,18 +15703,6 @@
"fsevents": "~2.3.1"
}
},
"rollup-plugin-terser": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
"jest-worker": "^26.2.1",
"serialize-javascript": "^4.0.0",
"terser": "^5.0.0"
}
},
"rsvp": {
"version": "4.8.5",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
@ -16069,15 +15990,6 @@
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"serialize-javascript": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -16635,25 +16547,6 @@
"supports-hyperlinks": "^2.0.0"
}
},
"terser": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz",
"integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.19"
},
"dependencies": {
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
},
"test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",

View File

@ -15,13 +15,14 @@
"build": "node build",
"build:lib": "tsc -b",
"build:dist": "rollup -c rollup.config.js",
"build:api": "api-extractor run --local --verbose",
"prebuild:api": "api-extractor run --local --verbose --config api-extractor.offline.json",
"build:api": "api-extractor run --local --verbose --config api-extractor.iconify.json",
"pretest": "npm run build",
"test": "jest"
},
"main": "dist/offline.js",
"module": "dist/offline.esm.js",
"types": "dist/offline.d.ts",
"main": "dist/iconify.js",
"module": "dist/iconify.esm.js",
"types": "dist/iconify.d.ts",
"devDependencies": {
"@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13",
@ -36,7 +37,6 @@
"react": "^17.0.2",
"react-test-renderer": "^17.0.2",
"rollup": "^2.45.2",
"rollup-plugin-terser": "^7.0.2",
"typescript": "^4.2.4"
}
}

View File

@ -1,30 +1,15 @@
import { writeFileSync, mkdirSync } from 'fs';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import buble from '@rollup/plugin-buble';
import { terser } from 'rollup-plugin-terser';
const name = 'offline';
const names = ['offline', 'iconify'];
// Write main file
const text = `'use strict'
const config = [];
if (process.env.NODE_ENV === 'production') {
module.exports = require('./${name}.cjs.production.js')
} else {
module.exports = require('./${name}.cjs.development.js')
}
`;
try {
mkdirSync('dist', 0o755);
} catch (err) {}
writeFileSync(`dist/${name}.js`, text, 'utf8');
// Export configuration
const config = [
// Module
{
// Write all packages
names.forEach((name) => {
// ES module
config.push({
input: `lib/${name}.js`,
output: [
{
@ -34,31 +19,20 @@ const config = [
],
external: ['react'],
plugins: [resolve(), commonjs(), buble()],
},
// Dev build
{
});
// CommonJS module
config.push({
input: `lib/${name}.js`,
output: [
{
file: `dist/${name}.cjs.development.js`,
file: `dist/${name}.js`,
format: 'cjs',
},
],
external: ['react'],
plugins: [resolve(), commonjs(), buble()],
},
// Production
{
input: `lib/${name}.js`,
output: [
{
file: `dist/${name}.cjs.production.js`,
format: 'cjs',
},
],
external: ['react'],
plugins: [resolve(), commonjs(), buble(), terser()],
},
];
});
});
export default config;

View File

@ -0,0 +1,124 @@
import React from 'react';
import type { IconifyIcon, IconifyJSON } from '@iconify/types';
import type {
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 type {
IconifyIconCustomisations,
IconifyIconProps,
IconProps,
IconRef,
} from './props';
import { render } from './render';
/**
* Export stuff from props.ts
*/
export { IconifyIconCustomisations, IconifyIconProps, IconProps };
/**
* Export types that could be used in component
*/
export {
IconifyIcon,
IconifyJSON,
IconifyHorizontalIconAlignment,
IconifyVerticalIconAlignment,
IconifyIconSize,
};
/**
* Storage for icons referred by name
*/
const storage: Record<string, Required<IconifyIcon>> = Object.create(null);
/**
* Generate icon
*/
function component(
props: IconProps,
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;
// 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(icon, props, inline, ref);
}
/**
* Type for exported components
*/
export type Component = (props: IconProps) => JSX.Element;
/**
* Block icon
*
* @param props - Component properties
*/
export const Icon: Component = React.forwardRef(
(props: IconProps, ref?: IconRef) => component(props, false, ref)
);
/**
* Inline icon (has negative verticalAlign that makes it behave like icon font)
*
* @param props - Component properties
*/
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

@ -104,7 +104,7 @@ export function addIcon(name: string, data: IconifyIcon): void {
* 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.
* @param prefix Optional prefix to add to icon names, true (default) if prefix from icon set should be used.
*/
export function addCollection(
data: IconifyJSON,

View File

@ -1,527 +0,0 @@
import React from 'react';
import { Icon, InlineIcon, addIcon, addCollection } from '../';
import renderer from 'react-test-renderer';
const iconData = {
body:
'<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
width: 24,
height: 24,
};
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('Creating component', () => {
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,
});
});
test('empty icon', () => {
const component = renderer.create(<Icon />);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'span',
props: {},
children: null,
});
});
test('empty icon with children', () => {
// Missing 'icon' property, should render children
const component = renderer.create(
<Icon>
<i class="fa fa-home" />
</Icon>
);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'i',
props: {},
children: null,
});
});
test('empty icon with text children', () => {
// Missing 'icon' property, should render children
const component = renderer.create(<Icon>icon</Icon>);
const tree = component.toJSON();
expect(tree).toMatch('icon');
});
});
describe('Using storage', () => {
test('using storage', () => {
addIcon('test-icon', iconData);
const component = renderer.create(<Icon icon="test-icon" />);
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('using storage with icon set', () => {
const iconSet = {
prefix: 'mdi-light',
icons: {
account: {
body:
'<path d="M11.5 14c4.142 0 7.5 1.567 7.5 3.5V20H4v-2.5c0-1.933 3.358-3.5 7.5-3.5zm6.5 3.5c0-1.38-2.91-2.5-6.5-2.5S5 16.12 5 17.5V19h13v-1.5zM11.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-5z" fill="currentColor"/>',
},
home: {
body:
'<path d="M16 8.414l-4.5-4.5L4.414 11H6v8h3v-6h5v6h3v-8h1.586L17 9.414V6h-1v2.414zM2 12l9.5-9.5L15 6V5h3v4l3 3h-3v7.998h-5v-6h-3v6H5V12H2z" fill="currentColor"/>',
},
},
width: 24,
height: 24,
};
addCollection(iconSet);
const component = renderer.create(<Icon icon="mdi-light:account" />);
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: iconSet.icons.account.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconSet.width + ' ' + iconSet.height,
},
children: null,
});
});
test('using storage with icon set with custom prefix', () => {
const iconSet = {
prefix: 'mdi-light',
icons: {
'account-alert': {
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"/>',
},
'link': {
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,
};
addCollection(iconSet, 'custom-');
const component = renderer.create(<Icon icon="custom-link" />);
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: iconSet.icons.link.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconSet.width + ' ' + iconSet.height,
},
children: null,
});
});
test('missing icon from storage', () => {
const component = renderer.create(<Icon icon="missing-icon" />);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'span',
props: {},
children: null,
});
});
});
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);
});
});
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');
});
});
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');
});
});
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');
});
});
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,62 @@
import React from 'react';
import { Icon, InlineIcon } 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('Creating component', () => {
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,64 @@
import React from 'react';
import { Icon, InlineIcon } 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 />);
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/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('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/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('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/offline';
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, InlineIcon } 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('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,130 @@
import React from 'react';
import { Icon, addIcon, addCollection } 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('Using storage', () => {
test('using storage', () => {
addIcon('test-icon', iconData);
const component = renderer.create(<Icon icon="test-icon" />);
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('using storage with icon set', () => {
const iconSet = {
prefix: 'mdi-light',
icons: {
account: {
body:
'<path d="M11.5 14c4.142 0 7.5 1.567 7.5 3.5V20H4v-2.5c0-1.933 3.358-3.5 7.5-3.5zm6.5 3.5c0-1.38-2.91-2.5-6.5-2.5S5 16.12 5 17.5V19h13v-1.5zM11.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-5z" fill="currentColor"/>',
},
home: {
body:
'<path d="M16 8.414l-4.5-4.5L4.414 11H6v8h3v-6h5v6h3v-8h1.586L17 9.414V6h-1v2.414zM2 12l9.5-9.5L15 6V5h3v4l3 3h-3v7.998h-5v-6h-3v6H5V12H2z" fill="currentColor"/>',
},
},
width: 24,
height: 24,
};
addCollection(iconSet);
const component = renderer.create(<Icon icon="mdi-light:account" />);
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: iconSet.icons.account.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconSet.width + ' ' + iconSet.height,
},
children: null,
});
});
test('using storage with icon set with custom prefix', () => {
const iconSet = {
prefix: 'mdi-light',
icons: {
'account-alert': {
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"/>',
},
'link': {
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,
};
addCollection(iconSet, 'custom-');
const component = renderer.create(<Icon icon="custom-link" />);
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: iconSet.icons.link.body,
},
'width': '1em',
'height': '1em',
'preserveAspectRatio': 'xMidYMid meet',
'viewBox': '0 0 ' + iconSet.width + ' ' + iconSet.height,
},
children: null,
});
});
test('missing icon from storage', () => {
const component = renderer.create(<Icon icon="missing-icon" />);
const tree = component.toJSON();
expect(tree).toMatchObject({
type: 'span',
props: {},
children: null,
});
});
});

View File

@ -0,0 +1,135 @@
import React from 'react';
import { Icon, InlineIcon } 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('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');
});
});