2
0
mirror of https://github.com/iconify/iconify.git synced 2024-11-09 14:50:56 +00:00

vue: add rendering modes

This commit is contained in:
Vjacheslav Trushkin 2022-04-15 09:58:43 +03:00
parent 59af5790aa
commit 5967e3c469
13 changed files with 180 additions and 62 deletions

View File

@ -1,7 +1,11 @@
<template>
<div class="checkbox-container">
<span :class="className">
<Icon :icon="icon" @click="check" />{{ text }}
<Icon
:icon="icon"
:mode="isChecked ? 'inline' : 'style'"
@click="check"
/>{{ text }}
</span>
<small>{{ hint }}</small>
</div>
@ -22,7 +26,7 @@ export default {
checked: Boolean,
},
methods: {
check(event) {
check(event: MouseEvent) {
event.preventDefault();
this.state = !this.isChecked;
},

View File

@ -2,21 +2,43 @@
<section class="inline-demo">
<h1>Inline demo</h1>
<div>
Block icon (behaving like image):
Block icons (behaving like image):
<OfflineIcon id="inline-demo-block-offline" icon="experiment2" />
<OfflineIcon
id="inline-demo-block-offline"
icon="experiment2"
mode="style"
/>
<FullIcon id="inline-demo-block-full" icon="experiment2" />
<FullIcon
id="inline-demo-block-full"
icon="experiment2"
mode="style"
/>
</div>
<div>
Inline icon (behaving line text / icon font):
Inline icons (behaving line text / icon font):
<OfflineIcon
id="inline-demo-inline-offline"
icon="experiment2"
:inline="true"
/>
<OfflineIcon
id="inline-demo-inline-offline"
icon="experiment2"
:inline="true"
mode="style"
/>
<FullIcon
id="inline-demo-inline-full"
icon="experiment2"
:inline="true"
/>
<FullIcon
id="inline-demo-inline-full"
icon="experiment2"
:inline="true"
mode="style"
/>
</div>
</section>

View File

@ -2,8 +2,9 @@
<section class="icon-24">
<h1>Usage (full module)</h1>
<div>
Icon referenced by name:
Icons referenced by name (rendered as SVG, then as SPAN):
<Icon icon="mdi:home" />
<Icon icon="mdi:home" mode="style" />
</div>
<div class="alert">
<Icon icon="mdi-light:alert" />Important notice with alert icon!

View File

@ -2,20 +2,24 @@
<section class="icon-24">
<h1>Usage (full module, offline mode)</h1>
<div>
Icon referenced by name:
<Icon icon="demo" />
Icons referenced by name (rendered as SVG, then as SPAN):
<Icon icon="demo" /><Icon icon="demo" mode="style" />
</div>
<div>
Icon referenced by object:
<Icon v-bind:icon="demoIcon" />
Icons referenced by object:
<Icon v-bind:icon="demoIcon" /><Icon
v-bind:icon="demoIcon"
mode="style"
/>
</div>
<div>
2 icons imported from icon set:
<Icon icon="alert1" />
<Icon icon="link1" />
<Icon icon="link1" mode="style" />
</div>
<div class="alert">
<Icon :icon="alertIcon" />Important notice with alert icon!
<Icon :icon="alertIcon" mode="style" />Important notice with alert
icon!
</div>
</section>
</template>

View File

@ -2,16 +2,19 @@
<section class="icon-24">
<h1>Usage (offline module)</h1>
<div>
Icon referenced by name:
<Icon icon="demo" />
Icons referenced by name (rendered as SVG, then as SPAN):
<Icon icon="demo" /><Icon icon="demo" mode="style" />
</div>
<div>
Icon referenced by object:
<Icon v-bind:icon="demoIcon" />
Icons referenced by object:
<Icon v-bind:icon="demoIcon" /><Icon
v-bind:icon="demoIcon"
mode="style"
/>
</div>
<div>
2 icons imported from icon set:
<Icon icon="offline-mdi-light:account-alert" />
<Icon icon="offline-mdi-light:account-alert" mode="style" />
<Icon icon="offline-mdi-light:link" />
</div>
<div class="alert">

View File

@ -31,7 +31,7 @@ p {
}
/* 24px icon */
.icon-24 svg {
.icon-24 :where(svg, span) {
font-size: 24px;
line-height: 1;
vertical-align: -0.25em;
@ -53,7 +53,7 @@ p {
clear: both;
}
.alert svg {
.alert :where(svg, span) {
position: absolute;
left: 12px;
top: 50%;
@ -78,7 +78,7 @@ p {
text-decoration: underline;
} */
.checkbox svg {
.checkbox :where(svg, span) {
cursor: pointer;
margin-right: 4px;
color: #afafaf;
@ -86,14 +86,14 @@ p {
line-height: 1em;
vertical-align: -0.25em;
}
.checkbox svg:hover {
.checkbox :where(svg, span):hover {
color: #ba3329;
}
.checkbox--checked svg {
.checkbox--checked :where(svg, span) {
color: #327335;
}
.checkbox:hover svg {
.checkbox:hover :where(svg, span) {
color: inherit;
}
@ -103,7 +103,7 @@ p {
}
/* Inline demo */
.inline-demo svg {
.inline-demo :where(svg, span) {
color: #06a;
margin: 0 8px;
position: relative;

View File

@ -25,7 +25,6 @@ const svgDefaults: SVGProps<SVGSVGElement> = {
'xmlnsXlink': 'http://www.w3.org/1999/xlink',
'aria-hidden': true,
'role': 'img',
'style': {}, // Include style if it isn't set to add verticalAlign later
};
/**

View File

@ -3,6 +3,16 @@ import type { IconifyIconCustomisations as RawIconCustomisations } from '@iconif
export { RawIconCustomisations };
/**
* Icon render mode
*
* 'style' = 'bg' or 'mask', depending on icon content
* 'bg' = inline style using `background`
* 'mask' = inline style using `mask`
* 'inline' = inline SVG.
*/
export type IconifyRenderMode = 'style' | 'bg' | 'mask' | 'inline';
// Allow rotation to be string
/**
* Icon customisations
@ -23,6 +33,9 @@ export interface IconifyIconProps extends IconifyIconCustomisations {
// Icon object
icon: IconifyIcon | string;
// Render mode
mode?: IconifyRenderMode;
// Style
color?: string;

View File

@ -13,7 +13,13 @@ import {
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
import { iconToSVG } from '@iconify/utils/lib/svg/build';
import { replaceIDs } from '@iconify/utils/lib/svg/id';
import type { IconifyIconCustomisations, IconProps } from './props';
import { iconToHTML } from '@iconify/utils/lib/svg/html';
import { svgToURL } from '@iconify/utils/lib/svg/url';
import type {
IconifyIconCustomisations,
IconifyRenderMode,
IconProps,
} from './props';
/**
* Default SVG attributes
@ -25,6 +31,39 @@ const svgDefaults: Record<string, unknown> = {
'role': 'img',
};
/**
* Style modes
*/
const commonProps: Record<string, string> = {
display: 'inline-block',
};
const monotoneProps: Record<string, string> = {
backgroundColor: 'currentColor',
};
const coloredProps: Record<string, string> = {
backgroundColor: 'transparent',
};
// Dynamically add common props to variables above
const propsToAdd: Record<string, string> = {
Image: 'var(--svg)',
Repeat: 'no-repeat',
Size: '100% 100%',
};
const propsToAddTo: Record<string, Record<string, string>> = {
webkitMask: monotoneProps,
mask: monotoneProps,
background: coloredProps,
};
for (const prefix in propsToAddTo) {
const list = propsToAddTo[prefix];
for (const prop in propsToAdd) {
list[prefix + prop] = propsToAdd[prop];
}
}
/**
* Aliases for customisations.
* In Vue 'v-' properties are reserved, so v-align and v-flip must be renamed
@ -71,10 +110,15 @@ export const render = (
) as FullIconCustomisations;
const componentProps = { ...svgDefaults };
// Check mode
const mode: IconifyRenderMode = props.mode || 'inline';
// Copy style
let style: VStyle =
typeof props.style === 'object' && !(props.style instanceof Array)
? { ...props.style }
const style: VStyle = {};
const propsStyle = props.style;
const customStyle =
typeof propsStyle === 'object' && !(propsStyle instanceof Array)
? propsStyle
: {};
// Get element properties
@ -88,6 +132,7 @@ export const render = (
case 'icon':
case 'style':
case 'onLoad':
case 'mode':
break;
// Boolean attributes
@ -161,37 +206,64 @@ export const render = (
// Generate icon
const item = iconToSVG(icon, customisations);
const renderAttribs = item.attributes;
// Add icon stuff
for (let key in item.attributes) {
componentProps[key] = item.attributes[key];
}
if (
item.inline &&
style.verticalAlign === void 0 &&
style['vertical-align'] === void 0
) {
// Inline display
if (item.inline) {
style.verticalAlign = '-0.125em';
}
// Counter for ids based on "id" property to render icons consistently on server and client
let localCounter = 0;
let id = props.id;
if (typeof id === 'string') {
// Convert '-' to '_' to avoid errors in animations
id = id.replace(/-/g, '_');
if (mode === 'inline') {
// Add style
componentProps.style = {
...style,
...customStyle,
};
// Add icon stuff
Object.assign(componentProps, renderAttribs);
// Counter for ids based on "id" property to render icons consistently on server and client
let localCounter = 0;
let id = props.id;
if (typeof id === 'string') {
// Convert '-' to '_' to avoid errors in animations
id = id.replace(/-/g, '_');
}
// Add innerHTML and style to props
componentProps['innerHTML'] = replaceIDs(
item.body,
id ? () => id + 'ID' + localCounter++ : 'iconifyVue'
);
// Render icon
return h('svg', componentProps);
}
// Add innerHTML and style to props
componentProps['innerHTML'] = replaceIDs(
item.body,
id ? () => id + 'ID' + localCounter++ : 'iconifyVue'
);
if (Object.keys(style).length > 0) {
componentProps['style'] = style;
}
// Render <span> with style
const { body, width, height } = icon;
const useMask =
mode === 'mask' ||
(mode === 'bg' ? false : body.indexOf('currentColor') !== -1);
// Render icon
return h('svg', componentProps);
// Generate SVG
const html = iconToHTML(body, {
...renderAttribs,
width: width + '',
height: height + '',
});
// Generate style
componentProps.style = {
...style,
'--svg': svgToURL(html),
'width': renderAttribs.width,
'height': renderAttribs.height,
...commonProps,
...(useMask ? monotoneProps : coloredProps),
...customStyle,
};
return h('span', componentProps);
};

View File

@ -121,10 +121,10 @@ describe('Passing attributes', () => {
});
test('color with style', async () => {
// color overrides style
// Style overrides color
const Wrapper = {
components: { Icon },
template: `<Icon :icon="icon" color="purple" style="color: green;" />`,
template: `<Icon :icon="icon" color="green" style="color: purple;" />`,
data() {
return {
icon: iconData,

View File

@ -98,7 +98,7 @@ describe('Inline attribute', () => {
await nextTick();
expect(wrapper.html()).toContain(
'color: red; vertical-align: -0.125em;'
'style="vertical-align: -0.125em; color: red;"'
);
});
@ -121,7 +121,7 @@ describe('Inline attribute', () => {
await nextTick();
expect(wrapper.html()).toContain(
'color: red; vertical-align: -0.125em;'
'style="vertical-align: -0.125em; color: red;"'
);
});

View File

@ -108,10 +108,10 @@ describe('Passing attributes', () => {
});
test('color with style', () => {
// color overrides style
// style overrides color
const Wrapper = {
components: { Icon },
template: `<Icon :icon="icon" color="purple" style="color: green;" />`,
template: `<Icon :icon="icon" color="green" style="color: purple;" />`,
data() {
return {
icon: iconData,

View File

@ -87,7 +87,7 @@ describe('Inline attribute', () => {
const wrapper = mount(Wrapper, {});
expect(wrapper.html()).toContain(
'color: red; vertical-align: -0.125em;'
'style="vertical-align: -0.125em; color: red;"'
);
});
@ -108,7 +108,7 @@ describe('Inline attribute', () => {
const wrapper = mount(Wrapper, {});
expect(wrapper.html()).toContain(
'color: red; vertical-align: -0.125em;'
'style="vertical-align: -0.125em; color: red;"'
);
});