quasarframework / quasar

Quasar Framework - Build high-performance VueJS user interfaces in record time
https://quasar.dev
MIT License
25.5k stars 3.45k forks source link

Custom icon name mapping #5461

Closed gkraser closed 4 years ago

gkraser commented 4 years ago

The name of the icon depends on the icon library used.

For example, the 'copy' icon in 'FontAwesome' is used as "fas fa-copy", and in 'MaterialIcons' is used as "file_copy".

I want to be able to use icons without a hard link to the icon library.

I suggest using the icon name conversion function in the icon implementation:

// QIcon.js
...
computed: {
    type () {
      let cls
      // current:
      // let icon = this.name

      // I suggest:
      let icon = this.$q.iconMapping(this.name)
...

By default (for compatibility) this function does nothing, it simply returns the passed argument. You can register your conversion implementation.

Now I have to use the conversion like this (It is not comfortable):

<q-btn :icon="$myapp.icon('copy')"/>
lucasfernog commented 4 years ago

You can extend the iconSet object that quasar uses internally. (for example, quasar defines an iconSet called select.arrow, and it is mapped to different icon names, depending on the selected iconSet).

However, it won't be more confortable than your solution.

gkraser commented 4 years ago

My suggestion allows you to write instead:

<q-btn :icon="$myapp.icon('copy')"/>

write it:

<q-btn icon="app:copy"/>

nor does it break the way to use icon libraries directly:

<q-btn icon="fas fa-copy"/>
rstoenescu commented 4 years ago

Hi,

Will be available in "quasar" v1.3.1. Extract from unreleased docs:

Custom mapping

Should you want, you can customize the mapping of icon names. This is especially useful when you are using a custom icon library (that doesn't come with Quasar and its @quasar/extras package).

This can be done by overriding $q.iconMapFn. The recommended place to do it is in the created() hook of your /src/App.vue component.

created () {
  // Example of adding support for
  // <q-icon name="app:...." />
  // This includes support for all "icon" props
  // of Quasar components

  this.$q.iconMapFn = (iconName) => {
    // iconName is the content of QIcon "name" prop

    // your custom approach, the following
    // is just an example:
    if (iconName.startsWith('app:') === true) {
      // we strip the "app:" part
      const name = iconName.substring(4)

      return {
        cls: 'my-app-icon ' + name
      }
    }

    // when we don't return anything from our
    // iconMapFn, the default Quasar icon mapping
    // takes over
  }
}

The syntax for $q.iconMapFn is as follows:

/* Syntax */
iconMapFn (String: iconName) => Object / void 0 (undefined)

/*
 the returned Object (if any) must be of the following form:
 {
   cls: String // class name(s)
   content: String // optional, in case you are using a ligature font
                   // and you need it as content of the QIcon
  }
*/

Notice in the examples above that we are returning a my-app-icon class that gets applied to QIcon if our icon starts with app: prefix. We can use it to define how QIcon should react to it, from a CSS point of view.

Let's assume we have our own webfont called "My App Icon".

/*
  For this example, we are creating:
  /src/css/my-app-icon.css
*/

.my-app-icon {
  font-family: 'My App Icon';
  font-weight: 400;
}

@font-face {
  font-family: 'My App Icon';
  font-style: normal; /* whatever is required for your */
  font-weight: 400;   /* webfont.... */
  src: url("./my-app-icon.woff2") format("woff2"), url("./my-app-icon.woff") format("woff");
}

We should then edit our quasar.conf.js (if using Quasar CLI) to add the newly created CSS file into our app:

css: [
  // ....
  'my-app-icon.css'
]

And also add "my-app-icon.woff2" and "my-app-icon.woff" files into the same folder as "my-app-icon.css" (or somewhere else, but edit the relative paths (see "src:" above) to the woff/woff2 files).

gkraser commented 4 years ago

Please do a check on 'img:' after calling this.$q.iconMapFn! And let iconMapFn return just a string. For example, like this:

// QIcon.js
...
if (this.$q.iconMapFn !== void 0) {
    const res = this.$q.iconMapFn(icon)
    if (res !== void 0) {
        if (typeof res === 'string') {   // check, if string returned
            icon = res                   // now this is the name of the icon
        } else {
            return {
                cls: res.cls + ' ' + commonCls,
                content: res.content !== void 0
                    ? res.content
                    : ' '
            }
        }
    }
}

if (icon.startsWith('img:') === true) {
    return {
        img: true,
        cls: commonCls,
        src: icon.substring(4)
    }
}
...

For example, for such a use case:

let registredIcons = {
    'app:icon1': 'img:/path/to/icon1.svg',
    'app:icon2': 'img:/path/to/icon2.svg',
    'app:copy': 'fas fa-copy',
}
this.$q.iconMapFn = (iconName) => {
    return registredIcons[iconName]
}
gkraser commented 4 years ago

As an option, functrion iconMapFn can always return an object, but you need to provide that the function simply does the conversion of the name of the icon, including the img:.

For example, like this:

// QIcon.js
...
if (this.$q.iconMapFn !== void 0) {
    const res = this.$q.iconMapFn(icon)
    if (res !== void 0) {
        if (res.iconName !== void 0) {
            icon = res.iconName
        } else {
            return {
                cls: res.cls + ' ' + commonCls,
                content: res.content !== void 0
                    ? res.content
                    : ' '
            }
        }
    }
}

// check 'img:' here, after call iconMapFn!
...

Example iconMapFn:

let registredIcons = {
    'app:icon1': 'img:/path/to/icon1.svg',
    'app:icon2': 'img:/path/to/icon2.svg',
    'app:copy': 'fas fa-copy',
}
this.$q.iconMapFn = (iconName) => {
    let regIcon = registredIcons[iconName]
    if (regIcon) {
        return {
            iconName: regIcon
        }
    }
}
rstoenescu commented 4 years ago

Good points! Updated. The new docs look like this now:

Custom mapping

Should you want, you can customize the mapping of icon names.

This can be done by overriding $q.iconMapFn. The recommended place to do it is in the created() hook of your /src/App.vue component.

The syntax for $q.iconMapFn is as follows:

/* Syntax */
iconMapFn (String: iconName) => Object / void 0 (undefined)

/*
 The returned Object (if any) must be one of the following forms:

 1. Defines how to interpret icon
 {
   cls: String // class name(s)
   content: String // optional, in case you are using a ligature font
                   // and you need it as content of the QIcon
  }

  2. Acts essentially as a map to another icon
  {
    icon: String // the mapped icon String, which will be handled
                 // by Quasar as if the original QIcon name was this value
  }
*/

Let's take both cases now.

1. Support for custom icon library

This is especially useful when you are using a custom icon library (that doesn't come with Quasar and its @quasar/extras package).

created () {
  // Example of adding support for
  // <q-icon name="app:...." />
  // This includes support for all "icon" props
  // of Quasar components

  this.$q.iconMapFn = (iconName) => {
    // iconName is the content of QIcon "name" prop

    // your custom approach, the following
    // is just an example:
    if (iconName.startsWith('app:') === true) {
      // we strip the "app:" part
      const name = iconName.substring(4)

      return {
        cls: 'my-app-icon ' + name
      }
    }

    // when we don't return anything from our
    // iconMapFn, the default Quasar icon mapping
    // takes over
  }
}

Notice in the examples above that we are returning a my-app-icon class that gets applied to QIcon if our icon starts with app: prefix. We can use it to define how QIcon should react to it, from a CSS point of view.

Let's assume we have our own webfont called "My App Icon".

/*
  For this example, we are creating:
  /src/css/my-app-icon.css
*/

.my-app-icon {
  font-family: 'My App Icon';
  font-weight: 400;
}

@font-face {
  font-family: 'My App Icon';
  font-style: normal; /* whatever is required for your */
  font-weight: 400;   /* webfont.... */
  src: url("./my-app-icon.woff2") format("woff2"), url("./my-app-icon.woff") format("woff");
}

We should then edit our quasar.conf.js (if using Quasar CLI) to add the newly created CSS file into our app:

css: [
  // ....
  'my-app-icon.css'
]

And also add "my-app-icon.woff2" and "my-app-icon.woff" files into the same folder as "my-app-icon.css" (or somewhere else, but edit the relative paths (see "src:" above) to the woff/woff2 files).

2. Simply mapping a few icons

const myIcons = {
  'app:icon1': 'img:/path/to/icon1.svg',
  'app:icon2': 'img:/path/to/icon2.svg',
  'app:copy': 'fas fa-copy',
}

// ...
created () {
  this.$q.iconMapFn = (iconName) => {
    const icon = myIcons[iconName]
    if (icon !== void 0) {
      return { icon: icon }
    }
  }
}

Now we can use <q-icon name="app:copy" /> or <q-icon name="app:icon1" /> and QIcon will treat "app:copy" and "app:icon1" as if they were written as "fas fa-copy" and "img:/path/to/icon1.svg".

gkraser commented 4 years ago

Thank you very much! Checked, everything works as it should!

rstoenescu commented 4 years ago

Thanks for collaborating on this!

snowyu commented 4 years ago

@rstoenescu Could you put this into boot files?

Derasm commented 1 year ago

How would this look with type declarations for Typescript? As i am looking through it, best i can tell is the iconName: string is typed, but the cls and content isn't typed.