Icon v1.0
The <e-icon> component is a component that can be used for .
Usage
An e-icon has attributes that must be provided,
<e-icon
:icon="[ 'required' ]"
/>
Attributes
icon | Type: Array | required
Icons
icon : selected icon
icons displayed: 143styles :
account
active
add
album
all
analytics
archive
arrow-right
arrow-right-thin
assignment
barchart
bolt
book
book-page
building
cake
calendar
camera
cart
cellphone
chart
chat
checked
checked-circle
chevron-double-left
chevron-double-right
chevron-down
chevron-left
chevron-right
circle-error
close
code
commit
compass
contract
cookie
copy
dark-mode
dashboard
delete
developer-guide
devices
discount
document
download
dropdown-arrow
edit
end
energy
equaliser
error
euro
event
expand
feed
file
filter
fingerprint
flow
folder
food
gift
github
government
group
headset
heart
help
home
hour-glass
id
inbox
inbox-in
inbox-out
infinite
info
instagram
invisibility
invoice
invoice-incoming
invoice-outgoing
kvk
language
light-mode
lightbulb
line-chart
link-chain
link-chain-broken
link-chain-off
linked-in
list
lo_econnect_black_rgb__orange
location
log-in
log-out
mail
monitor
more-horizontal
more-vertical
network
open-in-new
order-active
order-approve
order-inactive
pause
pdf
peppol
person
phone
range
receipt
receipt-long
refresh-reload
refresh-setting-solid
reload
reload-redo
remove
save
sdk
search
settings
shieldCheckOutline
sort
sort-asc
sort-desc
sort-large
star
support
tag
tasks
theme
thumbsUp
tiktok
twitter
unarchive
upload
verified-document
verified-user
visibility
webhook
wifi
x
youtube
Examples
Below a few interactive examples of e-icon can be found.
:icon='["energy"]'
<e-icon
:icon='["energy"]'
:color="'yellow'"
style='max-width: 24px'
/>
:icon='["heart"]'
<e-icon
:icon='["heart"]'
color="red"
style='max-width: 24px'
/>
Source Code
e-icon.vue
<script setup lang="ts">
import { computed, reactive, ref, unref, toRaw } from 'vue';
import type { PropType } from 'vue';
import { toLower, cloneDeep, join, includes } from 'lodash-es';
// iconSet
import icons from './icons.json';
// default style
import { IconStyles } from '../../data/iconStyles/iconStyles';
// Typescript models
import {
ParseIconJson,
IconDictionary,
IconModel,
IconStyle,
IconPreset,
IconViewBox
} from 'types';
const props = defineProps({
showTitle: {
type: Boolean,
default: false
},
color: {
type: String,
required: false
},
focusable: {
type: Boolean,
default: false
},
modColor: {
type: String,
required: false
},
rotate: {
type: [Number, String],
default: 0
},
icon: {
type: Array as PropType<string[]>,
default: function () {
return ['error.svg'];
}
},
styleKey: {
type: String,
required: false
},
viewKey: {
type: String,
default: 'base'
},
modKey: {
type: String,
required: false
},
modStyle: {
type: String,
required: false
},
modViewKey: {
type: String,
required: false
},
modKeys: {
type: Object as PropType<Array<string>>,
default: function () {
return [];
}
},
modStyles: {
type: Object as PropType<Array<string>>,
default: function () {
return [];
}
},
modViewKeys: {
type: Object as PropType<Array<string>>,
default: function () {
return [];
}
},
customIcon: {
type: Object as PropType<IconModel>,
required: false
},
customStyle: {
type: Object as PropType<IconStyle>,
required: false
},
customStyles: {
type: Object as PropType<Array<IconStyle>>,
default: function () {
return [];
}
},
customIcons: {
type: Array as PropType<Array<IconModel>>,
default: function () {
return [] as IconModel[];
}
},
customModStyle: {
default: function () {
return null;
},
type: Object
},
customModStyles: {
default: function () {
return [];
},
type: Array
},
customModView: {
type: Object as PropType<IconViewBox>,
default: function () {
return null;
}
},
// Array of preset ModViews
customModViews: {
type: Array as PropType<Array<IconViewBox>>,
default: function () {
return [] as IconViewBox[];
}
},
iconState: {
default: null,
type: String
},
deepState: {
default: true,
type: Boolean
},
debug: {
default: false,
type: Boolean
},
modState: {
default: null,
type: String
},
modStates: {
default: function () {
return [];
},
type: Array
},
customStates: {
default: function () {
return [];
},
type: Array
}
});
const emit = defineEmits<{
(e: 'click', event: MouseEvent): void;
}>();
const data = reactive({
isHovered: false
});
const iconsList = ref(ParseIconJson(icons as unknown as IconDictionary));
const defaultIconStyles = ref(IconStyles);
const iconClasses = computed(() => {
const result = [currentIconState.value, targetIcon.value.key];
if (props.focusable) {
result.push('focusable');
}
return result;
});
const computedIconList = computed(() => {
let result: IconModel[] = [];
const icons = unref(iconsList);
if (props.customIcon) {
result = result.concat(props.customIcon);
}
if (props.customIcons.length > 0) {
result = result.concat(props.customIcons as IconModel[]);
}
const iconList = Object.values(icons) as IconModel[];
return result.concat(iconList);
});
const targetIcon = computed(() => {
const targetKey = props.customIcon ? props.customIcon.key : props.icon[0];
const key = targetKey.includes('.') ? targetKey.slice(0, -4) : targetKey;
const caseInsensitiveKey = toLower(key);
return computedIconList.value.find(
icon => toLower(icon['key']) === caseInsensitiveKey
) as IconModel;
});
const defaultStyleData = computed(() => {
return targetIcon.value.styles['default'];
});
const targetIconBaseStyles = computed(() => {
return targetIcon.value?.styles || {};
});
const baseStyleCount = computed(() => {
return Object.keys(targetIconBaseStyles.value).length;
});
const sharedStyleCount = computed(() => {
return Object.keys(defaultIconStyles.value).length;
});
const customStyleList = computed(() => {
return unref(props.customStyles);
});
const customStyleListCount = computed(() => {
return customStyleList.value?.length;
});
const computedStyleList = computed(() => {
let result: IconStyle[] = [];
if (props.customStyle) {
result = result.concat(props.customStyle);
}
if (baseStyleCount.value > 0) {
result = result.concat(Object.values(targetIconBaseStyles.value));
}
if (sharedStyleCount.value > 0) {
defaultIconStyles.value.forEach(defaultStyle => {
const targetIndex = result.findIndex(f => f.key === defaultStyle.key);
if (targetIndex === -1) {
result.push(defaultStyle);
} else {
result[targetIndex] = cloneDeep(defaultStyle);
}
});
}
if (customStyleListCount.value > 0) {
customStyleList.value.forEach(customStyle => {
const targetIndex = result.findIndex(f => f.key === customStyle.key);
if (targetIndex === -1) {
result.push(customStyle);
} else {
result[targetIndex] = unref(customStyle);
}
});
}
return result as IconStyle[];
});
const transformStyle = computed(() => {
if (props.rotate === 0) return {};
return {
transform: `rotate(${props.rotate}deg)`
};
});
const activeModIcons = computed(() => {
let result: IconModel[] = [];
const iconList = unref(computedIconList);
if (targetIcon.value) {
if (props.modKey) {
const iconForKey = iconList.find(icon => icon['key'] === props.modKey);
if (iconForKey) {
result.push(iconForKey);
}
}
props.modKeys.forEach(mod => {
const iconForKey = iconList.find(icon => icon['key'] === mod);
if (iconForKey) {
result.push(iconForKey);
}
});
}
return result;
});
const iconTitle = computed(() => {
let result = '';
if (targetIcon.value) {
result = targetIcon.value.title;
}
if (activeModIcons.value.length > 0) {
result += ': ';
}
let modCount = activeModIcons.value.length;
activeModIcons.value.forEach((modIcon, index) => {
result += modIcon.title;
if (index + 1 < modCount) {
result += ', ';
}
});
return result;
});
const targetPresetList = computed(() => {
let result: { [key: string]: IconPreset } = {};
if (targetIcon.value) {
result = targetIcon.value.presets;
}
return result;
});
const targetPreset = computed(() => {
let result: IconPreset | null = null;
if (targetIcon.value && props.modKey) {
result = targetPresetList.value[props.modKey];
}
return result;
});
const targetStyleKey = computed(() => {
let targetKey = 'default';
if (props.styleKey) {
targetKey = props.styleKey;
} else if (props.customStyle && props.customStyle.key) {
targetKey = props.customStyle.key;
} else if (props.modKey) {
targetKey = props.modKey;
} else if (targetPreset.value) {
targetKey = targetPreset.value.parentStyle;
}
return targetKey;
});
const currentIconState = computed(() => {
const states: string[] = [];
const result = props.iconState || '';
if (includes(result, 'disabled')) {
return 'disabled';
}
if (!includes(result, 'hovered') && data.isHovered) {
states.push('hovered');
}
if (!result.length && !states.length) {
return 'default';
} else {
if (result) return join([result, ...states], '-');
return join([...states], '-');
}
});
const targetIconStyleData = computed(() => {
const iconData = cloneDeep(toRaw(targetIcon.value)) as IconModel;
// Ensure modified / custom styles are included
computedStyleList.value.forEach(style => {
iconData.styles[style.key] = cloneDeep(toRaw(style));
});
const styles = Object.keys(iconData.styles);
styles.forEach(function (styleKey) {
const style = iconData.styles[styleKey];
const styleStates = Object.keys(style.state);
styleStates.forEach(function (stateKey) {
correctStyling(style, stateKey);
});
});
return iconData;
});
const targetIconStyles = computed(() => {
return targetIconStyleData.value.styles;
});
const targetIconState = computed(() => {
const targetIconStyle = targetIconStyles.value[targetStyleKey.value];
if (targetIconStyle && targetIconStyle.state[currentIconState.value]) {
return targetIconStyle.state[currentIconState.value];
} else {
return defaultStyleData.value.state['default'];
}
});
const computedViewboxList = computed(() => {
let result: IconViewBox[] = [];
if (props.customModView) {
result = result.concat(props.customModView);
}
if (props.customModViews.length > 0) {
result = result.concat(props.customModViews);
}
if (targetIcon.value && Object.keys(targetIcon.value.viewBoxes).length > 0) {
result = result.concat(Object.values(targetIcon.value.viewBoxes));
}
return result;
});
const targetViewBox = computed(() => {
let result: IconViewBox = {
key: 'default',
viewBox: '0 0 20 20',
frame: {
x: 0,
y: 0,
width: 20,
height: 20
}
};
if (targetIcon.value) {
result = computedViewboxList.value.find(
viewBox => viewBox['key'] === props.viewKey
) as IconViewBox;
}
return result;
});
const computedModList = computed(() => {
let result = [];
let singleModStyleKey =
(props.modStyle && Object.keys(props.modStyles).length > 0) || props.modStyles.length > 1;
let singleModViewKey =
(props.modViewKey && Object.keys(props.modViewKeys).length > 0) || props.modViewKeys.length > 1;
let defaultModStyle = props.modStyle ? props.modStyle : props.modStyles[0];
let defaultModView = props.modViewKey ? props.modViewKey : props.modViewKeys[0];
if (props.modKey) {
result = result.concat({
modKey: props.modKey,
modStyle: defaultModStyle,
modViewKey: defaultModView
});
}
defaultModStyle = singleModStyleKey ? props.modStyle : props.modStyles[0];
defaultModView = singleModViewKey ? props.modViewKey : props.modViewKeys[0];
result = result.concat(
result,
props.modKeys.map((targetModKey, index) => {
let targetStyle = props.modStyles[index];
let targetModViewKey = props.modViewKeys[index];
return {
modKey: targetModKey,
modStyle: targetStyle ? targetStyle : defaultModStyle,
modViewKey: targetModViewKey ? targetModViewKey : defaultModView
};
})
);
return result;
});
const computedModifierList = computed(() => {
let result = [];
if (Object.keys(computedModList.value).length > 0) {
computedModList.value.forEach((targetMod, targetIndex) => {
let hasPreset = false;
let modKey = targetMod.modKey;
let modStyle = targetMod.modStyle;
let modViewKey = targetMod.modViewKey;
let presetData = getTargetPreset(targetMod.modKey);
if (presetData) {
hasPreset = true;
presetData.modIcons.forEach((modIconKey, modIndex) => {
modKey = targetMod.modKey ? targetMod.modKey : presetData.modIcons[modIndex];
modStyle = targetMod.modStyle ? targetMod.modStyle : presetData.modStyles[modIndex];
modViewKey = targetMod.modViewKey ? targetMod.modViewKey : presetData.modViews[modIndex];
let modIcon = getIcon(modKey);
let modViewBox = computedViewboxList.value.find(viewBox => viewBox['key'] === modViewKey);
if (modViewBox) {
result.push({
key: 'mod-' + targetIndex,
hasPreset: hasPreset,
iconKey: modKey,
icon: modIcon,
styleKey: modStyle,
style: getIconStyle(modStyle),
iconState: getModState(targetIndex),
frame: modViewBox.frame
});
}
});
} else {
let modIcon = getIcon(modKey);
let modViewBox = computedViewboxList.value.find(viewBox => viewBox['key'] === modViewKey);
if (modViewBox) {
result.push({
key: 'mod-' + targetIndex,
hasPreset: hasPreset,
iconKey: modKey,
icon: modIcon,
styleKey: modStyle,
style: getIconStyle(modStyle),
iconState: getModState(targetIndex),
frame: modViewBox.frame
});
}
}
});
}
return result;
});
const computedCustomModStyleList = computed(() => {
let result = [];
if (props.customModStyle) {
result = result.concat(props.customModStyle);
}
if (Object.keys(props.customModStyles).length > 0) {
result = result.concat(props.customModStyles);
}
return result;
});
const computedModStateList = computed(() => {
let result = [];
if (props.modState) {
result.push(props.modState);
}
if (Object.keys(props.modStates).length > 0) {
result = result.concat(props.modStates);
}
if (props.deepState) {
result = result.map(value => {
return props.deepState ? props.iconState : value;
});
}
return result;
});
function getIcon(iconKey: string) {
return computedIconList.value.find(icon => icon['key'] === iconKey) as IconModel;
}
function getIconStyle(styleKey: string) {
// CustomStyle has priority
return computedStyleList.value.find(style => style['key'] === styleKey) as IconStyle;
}
function getModState(modIndex: number) {
let result = null;
if (props.deepState) {
result = currentIconState.value;
} else if (computedModStateList.value[modIndex]) {
result = computedModStateList.value[modIndex];
}
return result;
}
function getTargetPreset(targetKey: string) {
let result = null;
if (targetIcon.value) {
result = targetPresetList.value[targetKey];
}
return result;
}
function correctStyling(styleData: IconStyle, stateKey?: string) {
const targetState = stateKey && stateKey.length > 0 ? stateKey : currentIconState.value;
const stateStyleData = styleData.state[targetState];
targetIcon.value?.content.forEach((content, index) => {
// Ensure array is sized properly
if (!stateStyleData.fillColors[index]) {
stateStyleData.fillColors.push(null);
}
if (!stateStyleData.strokeColors[index]) {
stateStyleData.strokeColors.push(null);
}
if (content.fill) {
if (props.color) {
stateStyleData.fillColors[index] = props.color;
} else if (!stateStyleData.fillColors[index]) {
if (stateStyleData.colors?.length) {
stateStyleData.fillColors[index] = stateStyleData.colors[0];
} else if (stateStyleData.fillColors[0]) {
stateStyleData.fillColors[index] = stateStyleData.fillColors[0];
} else {
stateStyleData.fillColors[index] = content.fill;
}
}
}
if (content.stroke) {
if (props.color) {
stateStyleData.strokeColors[index] = props.color;
} else if (!stateStyleData.strokeColors[index]) {
if (stateStyleData.colors?.length) {
stateStyleData.strokeColors[index] = stateStyleData.colors[0];
} else if (stateStyleData.strokeColors[0]) {
stateStyleData.strokeColors[index] = stateStyleData.strokeColors[0];
} else {
stateStyleData.strokeColors[index] = content.stroke;
}
}
}
});
}
function getModStyles(iconKey: string) {
let result = [];
let targetIcon = getIcon(iconKey);
if (targetIcon) {
result = Object.values(targetIcon.styles);
if (Object.keys(computedCustomModStyleList.value).length > 0) {
result = result.concat(computedCustomModStyleList.value);
}
}
return result;
}
function setHovered(event: FocusEvent | MouseEvent, newValue: boolean) {
data.isHovered = newValue;
if (!newValue) {
// ensure focus is removed
(event.currentTarget as HTMLElement).blur();
}
}
</script>
<template>
<svg
v-if="targetIcon"
class="e-icon"
xmlns="http://www.w3.org/2000/svg"
:alt="iconTitle"
:viewBox="targetViewBox.viewBox"
:style="transformStyle"
:class="iconClasses"
:focusable="focusable ? 'true' : 'false'"
:tabindex="focusable ? 0 : -1"
@click="emit('click', $event)"
@focusin="setHovered($event, true)"
@focusout="setHovered($event, false)"
@mouseenter="setHovered($event, true)"
@mouseleave="setHovered($event, false)"
>
<title
v-if="showTitle"
v-text="iconTitle"
/>
<component
:is="segment.type"
v-for="(segment, segmentIndex) in targetIcon.content"
:key="segmentIndex"
:class="segment.brush"
:fill="targetIconState.fillColors[segmentIndex]"
:stroke="targetIconState.strokeColors[segmentIndex]"
:stroke-dasharray="segment.strokeDashArray"
:cx="segment.cx"
:cy="segment.cy"
:d="segment.value"
:x="segment.x"
:y="segment.y"
:width="segment.width"
:height="segment.height"
:r="segment.r"
:rx="segment.rx"
:x1="segment.x1"
:x2="segment.x2"
:y1="segment.y1"
:y2="segment.y2"
:clip-rule="segment.clipRule"
:fill-rule="segment.fillRule"
/>
<e-icon
v-for="mod in computedModifierList"
:id="mod.key"
:key="mod.key"
class="mod-icon"
:color="modColor ? modColor : color"
:icon-state="mod.iconState"
:icon="mod.iconKey"
:custom-icon="mod.icon"
:style-key="mod.styleKey"
:custom-styles="getModStyles(mod.iconKey)"
:x="mod.frame.x"
:y="mod.frame.y"
:height="mod.frame.height"
:width="mod.frame.width"
/>
</svg>
</template>
<style scoped lang="scss">
svg {
stroke-width: 1px;
&:not(.focusable) {
outline: none !important;
}
.cls-1 {
stroke: none;
}
.cls-2,
.cls-3,
.cls-4,
.cls-5 {
fill: none;
}
.cls-2 {
stroke-miterlimit: 10;
}
.cls-3 {
stroke-linejoin: round;
}
.cls-4 {
stroke-miterlimit: 10;
stroke-linecap: round;
stroke-width: 0.5px;
}
.cls-5 {
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-6 {
opacity: 0.7;
}
.cls-7 {
opacity: 0.4;
}
}
</style>