Closed 9uenther closed 11 months ago
You cannot load icons dynamically with the UIcon
component, you need to install iconify collections. You can learn more about it in the documentation: https://ui.nuxtlabs.com/getting-started/theming#icons
However, you can use nuxt-icon module to achieve this.
I played around and made a few adjustments so that i can pass the IconifyIcon
object for the icon.vue
aka UIcon
component in the name. I write the svg stuff directly into the style attribute.
<UIcon :name="getIcon(myDynamicIconName)" />
<template>
<span :class="getName" :style="getStyle" />
</template>
<script>
import { computed, defineComponent } from "vue";
import { classNames } from "../../utils";
export default defineComponent({
props: {
name: {
type: [String, Object],
required: true
},
class: {
type: String,
default: null
}
},
setup(props) {
const getName = computed(() => {
if(typeof props.name === 'string') {
return classNames(
props.name,
props.class
);
}
return props.class;
});
const getStyle = computed(() => {
if(typeof props.name === 'object') {
const data = {...props.name};
const svgUrl = `url("data:image/svg+xml,${encodeURIComponent(
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='${data.left} ${data.top} ${data.width} ${data.height}' width='${data.width}' height='${data.height}'>${data.body}</svg>`
)}")`;
return {
'--svg': svgUrl,
'background-color': 'currentColor',
'-webkit-mask-image': `var(--svg), ${svgUrl}`,
'mask-image': 'var(--svg)',
'-webkit-mask-repeat': 'no-repeat',
'mask-repeat': 'no-repeat',
'-webkit-mask-size': '100% 100%',
'mask-size': '100% 100%',
};
}
return null;
});
return {
getName,
getStyle
};
}
});
</script>
My own getIcon component using import { iconExists, getIcon, loadIcon } from '@iconify/vue';
.
That's pretty much what the IconCSS
component of Nuxt Icon does: https://github.com/nuxt-modules/icon#css-icons
Ah ok, i didn't know that yet. I got it from the i-mdi...
CSS class and replaced a few values manually. My component loads the icon as needed something like this:
<UIcon :name="getIcon(myDynamicIconName)" />
// composables/getIcon.js
import { iconExists, getIcon, loadIcon } from '@iconify/vue';
import { computed, ref } from 'vue';
const icons = ref({});
const loader = async (icon) => {
if (!iconExists(icon)) {
await loadIcon(icon)
.then((data) => {
icons.value[icon] = data;
});
} else if (!icons.value[icon]) {
icons.value[icon] = getIcon(icon);
}
return icons.value[icon];
}
export default (icon) => {
const iconLoader = loader(icon);
return icons.value[icon];
}
This is more practical in my app than defining all the icons first. Whereby some of them stem from user input and i would have to load all the icons.
@9uenther Can this issue be closed?
I don't think this should be closed - it's a very common use case, for example, we asynchronously fetch menu items that are stored in the CMS, and we have no idea what icons are going to be used by the editors.
Ideally, the icon component should accept a string, and resolve the icon at runtime.
As mentioned previously, for this case you should use https://github.com/nuxt-modules/icon.
There is also no issue using both, for example on Volta we use UIcon
for all the icons defined within the app which are bundled and IconCSS
from nuxt-icon
for the milestones where the user can pick their own icon.
Thanks for a swift reply Ben! What is the reason for not using Nuxt Icon by default if you don't mind me asking?
Historically nuxt-icon
was the Icon
component we made for this library before making it open-source and Atinux created a module out of it.
However, since this ui library was originally made for Volta and when this package by egoist was released https://github.com/egoist/tailwindcss-icons I made the choice to switch to a system with bundled icons like UnoCSS does: https://unocss.dev/presets/icons.
It's a tiny bit more work as you have to install the iconify packages for the collections you want to use but this makes icons instantly load as they are bundled in your css instead of fetching them from the Iconify API when rendering the page the first time.
In general, the component is fine. However, i would extend it by dynamic use, as i have already indicated above.
I think Iconify also pretty perfect, because i can add my own icons with relatively little effort or make them available via api.
As another work around, I created a dummy component that renders the icons that are "dynamic". This dummy component is not used anywhere but allows the icons to be included. :)
<script lang="ts" setup>
// NOTE: This is just a way to whitelist icons for the app. It's not used anywhere.
const icons = [
"i-heroicons-computer-desktop",
"i-heroicons-moon",
"i-heroicons-sun",
];
</script>
<template>
<UIcon v-for="icon in icons"
:key="icon"
:name="icon"
class="w-8 h-8" />
</template>
@Jak3b0 You can also put those in comments anywhere in your app, they will be picked up by Tailwind.
Hi @benjamincanac , thanks for your response!
Not sure I understand the relation with Tailwind in this case though. I had the icon names defined in an array of objects inside a computed property and the icons were not included.
@benjamincanac No offense intended here but this is a very bad design decision.
It's a tiny bit more work as you have to install the iconify packages for the collections you want to use but this makes icons instantly load as they are bundled in your css instead of fetching them from the Iconify API when rendering the page the first time.
So the problem we're trying to solve here is that "nuxt-icon causes icons to not instantly load", right?
And I would agree that it's a pretty bad problem. The iconify API is not the most reliable, and occasionally icons don't render on the Nuxt UI official website.
This is caused by a known issue in nuxt-icon, https://github.com/nuxt-modules/icon/issues/34 or https://github.com/nuxt-modules/icon/issues/99
Basically, nuxt-icon doesn't load the icon on the server side, and instead opt to call the iconify API from the client at all times.
So the reasonable solution to the problem of "nuxt-icon icons doesn't instantly load" would be "fix nuxt-icon so that icons can be injected and bundled at build time".
Instead of doing that, here we introduced another dependency so that icons can be injected by tailwind as a CSS icon.
What's the problem with that?
egoist/tailwindcss-icons
was introduced in the first place.addIconProvider
. egoist/tailwindcss-icons
expect you to provide them in nuxt.config.ts
. I can use bundle:name
as the name
property in "static" icons but not dynamic icons. That means for any custom icons I have to package them twice, once for static icons as an npm package and once for dynamic icons on my custom iconify server.nuxt-icon
embeds the SVG into the DOM. egoist/tailwindcss-icons
uses CSS icons. monotone icons are image-mask
. Image icons are background-image
. That means if I want to style a subset of the paths in the SVG, I can do that with nuxt-icon
, but not egoist/tailwindcss-icons
.What I'm trying to say here is that by introducing egoist/tailwindcss-icons
you're significantly complicating an otherwise simple problem. Just go into nuxt-icon
, help out @Atinux, and fix nuxt-icon
so that it can preload icons. Call useAsyncData
to fetch the icons on the server side so they're available as soon as the page loads. That way you have one unified method of loading icons, instead of introducing a whole lot of mental overload for a problem as trivial as showing icons.
@benjamincanac how could i use dynamic or nuxt-icon
with DropdownItem
.
currently DropdownItem icon
allows only to be string
.
could that be extended to also allow string | UIcon
so dynamic
could be passed?
@Sazzels This is unfortunately not possible at the moment, the dynamic
prop only works on the Icon
component or globally through the app.config.ts
. You might be able to override the slots to put an <UIcon dynamic />
.
I want to pass the names dynamically. But if they have not been loaded before, the icon does not appear.
Is it possible to use an
IconifyIcon
object like this?Docs: https://iconify.design/docs/icon-components/vue/get-icon.html