mirror of
https://github.com/iconify/iconify.git
synced 2024-12-04 18:23:17 +00:00
React wrapper for web component: working component, tests and demo
This commit is contained in:
parent
08974d4aa8
commit
c03de0fb07
@ -52,16 +52,16 @@ p {
|
||||
color: #afafaf;
|
||||
display: none;
|
||||
}
|
||||
.test-row-icons > .hidden {
|
||||
.test-row-icons > iconify-icon.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.test-row-icons > .visible {
|
||||
.test-row-icons > iconify-icon.visible {
|
||||
display: inline-block;
|
||||
}
|
||||
.test-row-icons > .success {
|
||||
.test-row-icons > iconify-icon.success {
|
||||
color: #327335;
|
||||
}
|
||||
.test-row-icons > .failed {
|
||||
.test-row-icons > iconify-icon.failed {
|
||||
color: #ba3329;
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,21 @@
|
||||
import { addIcon, addCollection, disableCache } from '@iconify-icon/react';
|
||||
import playIcon from '@iconify-icons/mdi-light/play';
|
||||
import chartIcon from '@iconify-icons/mdi-light/chart-histogram';
|
||||
|
||||
import { Checkbox } from './demo-components/Checkbox';
|
||||
import { InlineDemo } from './demo-components/Inline';
|
||||
import { OfflineUsageDemo } from './demo-components/UsageOffline';
|
||||
import { FullUsageDemo } from './demo-components/UsageAPI';
|
||||
|
||||
import { TestIcon } from './test-components/TestIcon';
|
||||
|
||||
import './App.css';
|
||||
|
||||
// Disable cache
|
||||
disableCache('all');
|
||||
|
||||
// Add few custom icons
|
||||
addIcon('test:demo', playIcon);
|
||||
addIcon('test:experiment2', {
|
||||
addIcon('demo', chartIcon);
|
||||
addIcon('experiment2', {
|
||||
width: 16,
|
||||
height: 16,
|
||||
body: '<mask id="coffee-mask" x="0" y="0" width="16" height="16"><g fill="white"><path d="M5-2c0 2-2 2-2 4s2 2 2 4-2 2-2 4 2 2 2 4M8.5-2c0 2-2 2-2 4s2 2 2 4-2 2-2 4 2 2 2 4M12-2c0 2-2 2-2 4s2 2 2 4-2 2-2 4 2 2 2 4" stroke="white" stroke-width="1" fill="none"><animateMotion path="M0 0v-8" calcMode="linear" dur="3s" repeatCount="indefinite" /></path></g><rect y="4" width="16" height="12" fill="black" /><path d="M2 5H13C14.1046 5 15 5.89543 15 7V8C15 9.10457 14.1046 10 13 10H12V14C12 15.1046 11.1046 16 10 16H4C2.89543 16 2 15.1046 2 14V5Z" fill="white" /><path d="M12 6H13C13.5523 6 14 6.44772 14 7V8C14 8.55228 13.5523 9 13 9H12V6Z" fill="black" /></mask><rect mask="url(#coffee-mask)" width="16" height="16" fill="currentColor" />',
|
||||
@ -57,6 +59,8 @@ function App() {
|
||||
</section>
|
||||
|
||||
<InlineDemo />
|
||||
|
||||
<TestIcon />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,18 +7,18 @@ export function InlineDemo() {
|
||||
<h1>Inline demo</h1>
|
||||
<div>
|
||||
Block icon (behaving like image):
|
||||
<Icon icon="test:experiment2" />
|
||||
<Icon icon="experiment2" />
|
||||
<Icon
|
||||
icon="test:experiment2"
|
||||
icon="experiment2"
|
||||
inline={true}
|
||||
style={{ verticalAlign: '0' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
Inline icon (behaving line text / icon font):
|
||||
<Icon icon="test:experiment2" inline={true} />
|
||||
<Icon icon="experiment2" inline={true} />
|
||||
<Icon
|
||||
icon="test:experiment2"
|
||||
icon="experiment2"
|
||||
style={{ verticalAlign: '-0.125em' }}
|
||||
/>
|
||||
</div>
|
||||
|
@ -8,9 +8,8 @@ export function OfflineUsageDemo() {
|
||||
<section className="icon-24">
|
||||
<h1>Usage (offline mode: using preloaded icons)</h1>
|
||||
<div>
|
||||
Icons referenced by name (as SVG, as SPAN):{' '}
|
||||
<Icon icon="test:demo" />
|
||||
<Icon icon="test:demo" mode="style" />
|
||||
Icons referenced by name (as SVG, as SPAN): <Icon icon="demo" />
|
||||
<Icon icon="demo" mode="style" />
|
||||
</div>
|
||||
<div>
|
||||
Icons referenced by object (as SVG, as SPAN):{' '}
|
||||
|
146
iconify-icon-demo/react-demo/src/test-components/TestIcon.tsx
Normal file
146
iconify-icon-demo/react-demo/src/test-components/TestIcon.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@iconify-icon/react';
|
||||
import { Tests, toggleTest } from './Tests';
|
||||
|
||||
export function TestIcon() {
|
||||
return (
|
||||
<section className="tests">
|
||||
<h1>Tests (offline module)</h1>
|
||||
|
||||
<h2>References</h2>
|
||||
|
||||
<div className="test-row">
|
||||
<Tests id="offline-ref1" />
|
||||
Getting reference
|
||||
<Icon
|
||||
icon="demo"
|
||||
inline
|
||||
ref={(element) => {
|
||||
const key = 'offline-ref1';
|
||||
if (element?.tagName.toLowerCase() === 'iconify-icon') {
|
||||
toggleTest(key, 'success');
|
||||
} else {
|
||||
toggleTest(key, 'failed');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2>Style</h2>
|
||||
|
||||
<div className="test-row">
|
||||
<Tests id="offline-style" />
|
||||
Inline style for icon
|
||||
<Icon
|
||||
icon="demo"
|
||||
style={{
|
||||
color: '#1769aa',
|
||||
fontSize: '24px',
|
||||
lineHeight: '1em',
|
||||
verticalAlign: '-0.25em',
|
||||
}}
|
||||
ref={(element) => {
|
||||
const key = 'offline-style';
|
||||
if (element) {
|
||||
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 ? 'failed' : 'success');
|
||||
} else {
|
||||
toggleTest(key, 'failed');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="test-row">
|
||||
<Tests id="offline-color2" />
|
||||
Green color from style:{' '}
|
||||
<Icon
|
||||
icon="demo"
|
||||
style={{
|
||||
color: 'green',
|
||||
}}
|
||||
ref={(element) => {
|
||||
const key = 'offline-color2';
|
||||
if (element) {
|
||||
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 ? 'failed' : 'success');
|
||||
} else {
|
||||
toggleTest(key, 'failed');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2>Properties</h2>
|
||||
|
||||
<div className="test-row">
|
||||
<Tests id="icon-rotate2" />
|
||||
Rotation as number:{' '}
|
||||
<Icon
|
||||
icon="demo"
|
||||
rotate={2}
|
||||
inline
|
||||
ref={(element) => {
|
||||
const key = 'icon-rotate2';
|
||||
if (element) {
|
||||
toggleTest(
|
||||
key,
|
||||
element.getAttribute('rotate') !== '2'
|
||||
? 'failed'
|
||||
: 'success'
|
||||
);
|
||||
} else {
|
||||
toggleTest(key, 'failed');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Icon icon="demo" rotate="180deg" inline />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
64
iconify-icon-demo/react-demo/src/test-components/Tests.tsx
Normal file
64
iconify-icon-demo/react-demo/src/test-components/Tests.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@iconify-icon/react';
|
||||
import successIcon from '@iconify-icons/uil/check-circle';
|
||||
import pendingIcon from '@iconify-icons/uil/question-circle';
|
||||
import failedIcon from '@iconify-icons/uil/times-circle';
|
||||
|
||||
type StatusIcon = 'success' | 'failed' | 'pending';
|
||||
|
||||
interface TestIconsProps {
|
||||
id: string;
|
||||
icon?: StatusIcon;
|
||||
}
|
||||
|
||||
export function Tests(props: TestIconsProps) {
|
||||
const id = 'test-icons-' + props.id;
|
||||
const icon = props.icon || 'pending';
|
||||
|
||||
return (
|
||||
<span className="test-row-icons" id={id}>
|
||||
<Icon
|
||||
icon={successIcon}
|
||||
inline={true}
|
||||
className={
|
||||
'success ' + (icon === 'success' ? 'visible' : 'hidden')
|
||||
}
|
||||
/>
|
||||
<Icon
|
||||
icon={pendingIcon}
|
||||
inline={true}
|
||||
className={
|
||||
'pending ' + (icon === 'pending' ? 'visible' : 'hidden')
|
||||
}
|
||||
/>
|
||||
<Icon
|
||||
icon={failedIcon}
|
||||
inline={true}
|
||||
className={
|
||||
'failed ' + (icon === 'failed' ? 'visible' : 'hidden')
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function toggleTest(id: string, status: StatusIcon) {
|
||||
const node = document.getElementById('test-icons-' + id);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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('.' + status);
|
||||
if (toggle) {
|
||||
toggle.classList.remove('hidden');
|
||||
toggle.classList.add('visible');
|
||||
}
|
||||
}
|
3943
iconify-icon/react/package-lock.json
generated
3943
iconify-icon/react/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@
|
||||
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "tsup",
|
||||
"test": "jest --runInBand"
|
||||
"test": "vitest --config vitest.config.mjs"
|
||||
},
|
||||
"main": "dist/iconify.js",
|
||||
"module": "dist/iconify.mjs",
|
||||
@ -38,16 +38,16 @@
|
||||
"iconify-icon": "^0.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@types/react": "^17.0.41",
|
||||
"babel-jest": "^27.4.6",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"@vitejs/plugin-react": "^1.3.2",
|
||||
"jest": "^28.0.0-alpha.7",
|
||||
"react": "^17.0.2",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"react": "^18.1.0",
|
||||
"react-test-renderer": "^18.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"tsup": "^5.12.7",
|
||||
"typescript": "^4.6.2"
|
||||
"typescript": "^4.6.2",
|
||||
"vitest": "^0.12.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
@ -1,5 +1,9 @@
|
||||
import React from 'react';
|
||||
import type { IconifyIcon, IconifyIconAttributes } from 'iconify-icon';
|
||||
import type {
|
||||
IconifyIcon,
|
||||
IconifyIconAttributes,
|
||||
IconifyIconHTMLElement,
|
||||
} from 'iconify-icon';
|
||||
|
||||
/**
|
||||
* Export types
|
||||
@ -39,7 +43,11 @@ export type { IconifyIconBuildResult } from 'iconify-icon';
|
||||
export type { IconifyBrowserCacheType } from 'iconify-icon';
|
||||
|
||||
// Component types
|
||||
export type { IconifyIconAttributes, IconifyRenderMode } from 'iconify-icon';
|
||||
export type {
|
||||
IconifyIconAttributes,
|
||||
IconifyRenderMode,
|
||||
IconifyIconHTMLElement,
|
||||
} from 'iconify-icon';
|
||||
|
||||
/**
|
||||
* Export functions
|
||||
@ -70,25 +78,37 @@ export interface IconifyIconProps
|
||||
IconifyIconAttributes {
|
||||
icon: string | IconifyIcon;
|
||||
inline?: boolean;
|
||||
rotate?: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* React component
|
||||
*/
|
||||
export function Icon(props: IconifyIconProps) {
|
||||
const newProps = {
|
||||
...props,
|
||||
};
|
||||
export const Icon = React.forwardRef(
|
||||
(
|
||||
props: IconifyIconProps,
|
||||
ref: React.ForwardedRef<IconifyIconHTMLElement>
|
||||
) => {
|
||||
const newProps: Record<string, unknown> = {
|
||||
...props,
|
||||
ref,
|
||||
};
|
||||
|
||||
// Stringify icon
|
||||
if (typeof props.icon === 'object') {
|
||||
newProps.icon = JSON.stringify(props.icon);
|
||||
// Stringify icon
|
||||
if (typeof props.icon === 'object') {
|
||||
newProps.icon = JSON.stringify(props.icon);
|
||||
}
|
||||
|
||||
// Boolean
|
||||
if (!props.inline) {
|
||||
delete newProps.inline;
|
||||
}
|
||||
|
||||
// React cannot handle className for web components
|
||||
if (props.className) {
|
||||
newProps['class'] = props.className;
|
||||
}
|
||||
|
||||
return React.createElement('iconify-icon', newProps);
|
||||
}
|
||||
|
||||
// Boolean
|
||||
if (!props.inline) {
|
||||
delete newProps.inline;
|
||||
}
|
||||
|
||||
return React.createElement('iconify-icon', newProps);
|
||||
}
|
||||
);
|
||||
|
25
iconify-icon/react/tests/creating-component-test.tsx
Normal file
25
iconify-icon/react/tests/creating-component-test.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import React from 'react';
|
||||
import { Icon } from '../dist/iconify';
|
||||
import { create } 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 = create(<Icon icon={iconData} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchObject({
|
||||
type: 'iconify-icon',
|
||||
props: {
|
||||
icon: JSON.stringify(iconData),
|
||||
},
|
||||
children: null,
|
||||
});
|
||||
});
|
||||
});
|
27
iconify-icon/react/tests/reference-test.tsx
Normal file
27
iconify-icon/react/tests/reference-test.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import React from 'react';
|
||||
import { Icon } from '../dist/iconify';
|
||||
import { create } from 'react-test-renderer';
|
||||
|
||||
const iconData = {
|
||||
body: '<path d="M4 19h16v2H4zm5-4h11v2H9zm-5-4h16v2H4zm0-8h16v2H4zm5 4h11v2H9z" fill="currentColor"/>',
|
||||
width: 24,
|
||||
height: 24,
|
||||
};
|
||||
|
||||
describe('Testing references', () => {
|
||||
test('basic icon reference', () => {
|
||||
let gotRef = false;
|
||||
const component = create(
|
||||
<Icon
|
||||
icon={iconData}
|
||||
ref={() => {
|
||||
gotRef = true;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
// Ref should have been called by now
|
||||
expect(gotRef).toEqual(true);
|
||||
});
|
||||
});
|
@ -8,6 +8,7 @@
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
|
14
iconify-icon/react/vitest.config.mjs
Normal file
14
iconify-icon/react/vitest.config.mjs
Normal file
@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
globals: true,
|
||||
watch: false,
|
||||
include: ['**/tests/*-test.{ts,tsx}'],
|
||||
transformMode: {
|
||||
web: [/\.[jt]sx$/],
|
||||
},
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user