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:
parent
59af5790aa
commit
5967e3c469
@ -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;
|
||||
},
|
||||
|
@ -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>
|
||||
|
@ -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!
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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;"'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;"'
|
||||
);
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user