vue3-context-menu icon indicating copy to clipboard operation
vue3-context-menu copied to clipboard

How to use svg paths from @mdi/js

Open crystalfp opened this issue 1 year ago • 2 comments

Thanks for the component! I'm using the function call to build the menu on right button click.

<div @contextmenu="openContextMenu($event, item)">{{ item.title }}</div>

With the menu builder:

const openContextMenu = (event: MouseEvent, item: DocumentReferenceWithTitle): void => {
    event.preventDefault();

    const menuItems: {label: string; onClick: () => void}[] = [
        {
          label: "Show text",
          onClick: showDocumentText
        },
        {
          label: "Remove",
          onClick: removeDocument
        },
    ];

    ContextMenu.showContextMenu({
        x: event.x,
        y: event.y,
        items: menuItems
    });
};

This works perfectly for text only menu, but I don't know how to use one of the icons from the @mdi/js packages to decorate the entry. From a previous attempt with a custom solution I created the SVG string with:

 const svgIcon = (mdi: string): string => {
	  return `<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor"
stroke-width="0.2" style="margin-right: 7px; position:  relative; top: 0" fill="currentColor"
stroke-linecap="round" stroke-linejoin="round"><path d="${mdi}"></path></svg>`;
};

Using it, for example:

import {mdiTrashCanOutline} from "@mdi/js";
const svg = svgIcon(mdiTrashCanOutline);

How could use this string instead of the symbol mechanism that I'm not been able to make it work. Thanks! mario

crystalfp avatar Oct 05 '23 17:10 crystalfp

Here wat I have done, if anyone interested, but I continue hoping for a better solution.

  1. In the template of the main component of the application add (you should also silence eslint that does not like a template inside a template):
<template id="svg-template">
  <svg stroke="currentColor" stroke-width="0.2" style="display: none"
  fill="currentColor" stroke-linecap="round" stroke-linejoin="round">
    <defs />
  </svg>
</template>
  1. In the component using the context menu add:
let defs: SVGDefsElement | null;
onMounted(() => {

    defs = null;
    const body = document.querySelector("body");
    if(!body) return;

    const template = document.querySelector<HTMLTemplateElement>("#svg-template");
    if(!template) return;
    defs = template.querySelector<SVGDefsElement>("defs");
});
  1. Last, in the routine that builds the menu, add (here I have a menu with two entries):
import {mdiTextBoxOutline, mdiTrashCanOutline} from "@mdi/js";

    const html = `<symbol viewBox="0 0 24 24" id="text-box"><path d="${mdiTextBoxOutline}"></path></symbol>
                  <symbol viewBox="0 0 24 24" id="trash-can"><path d="${mdiTrashCanOutline}"></path></symbol>`;
    if(defs) defs.innerHTML = html.replaceAll(/ {2,}/g, " ");
...
    const menuItems: {label: string; svgIcon: string; svgProps: Record<string, string>; onClick?: () => void}[] = [
        {
            label: "Show text",
            svgIcon: "#text-box",
            svgProps: {fill: fillColor},
            onClick: showDocumentText
        },
...

Hope it helps mario

crystalfp avatar Oct 06 '23 12:10 crystalfp

Simplified and made more elegant. I created a component that define the needed SVG symbols

<script setup lang="ts">
const props = defineProps<{

    /** SVG path and id for icons to be called in the context menu */
    icons: {path: string; id: string}[];
}>();
</script>

<template>
<svg stroke="currentColor" stroke-width="0.2"
     fill="currentColor" stroke-linecap="round" stroke-linejoin="round">
  <defs>
    <symbol v-for="co of props.icons" :id="co.id" :key="co.id" viewBox="0 0 24 24">
      <path :d="co.path" />
    </symbol>
  </defs>
</svg>
</template>

Hope it helps. mario

crystalfp avatar Oct 06 '23 16:10 crystalfp