@econnect/webcomponents-library
Documentation
Styleguide
Documentation
Styleguide
  • Getting Started

    • Installation
    • Patch Notes
    • Styleguide
  • Styling

    • e-connect colors
  • UI Components

    • Form

      • Search Field
      • Form Field
      • Forms
    • Filter

      • Filter
      • Search Filter
    • Grid

      • Grid
      • Grid Row
    • Inputs

      • File Input
      • Input Checkbox
      • Input Dropdown
      • Input Date
      • Input Date Time
      • Input Date Time Local
      • Input File
      • Input Label
      • Input Multi Select
      • Input Number
      • Input Radio
      • Input Range
      • Input Select
      • Input Switch
      • Input Slider
      • Input Text
      • Input TextArea
      • Input Time
      • Input Time Range
    • Controls

      • Booleans
      • Button
      • Checkboxes
      • Date Displays
      • Dropdown Lists
      • Text Displays
    • Headers

      • e-header
    • Cards

      • e-card
    • Images

      • Icon
      • Images
      • Flag Image
      • Hamburger
    • Iframe
    • Pager
    • Settings
    • Document Validation
    • Navigation

      • Navigation
      • Navigation Menu
      • Navigation Footer
      • Navigation Button
      • Navigation Button Item
      • Navigation Search Item

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
tags :
styles :
icons displayed: 143
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>
Last Updated:: 10/10/24, 2:56 PM
Contributors: Antony Elfferich, AzureAD\MarcelLommers, Marcel Lommers
Next
Images