I want to integrate Lucide-icons into payloadcms.
To do this I want a autofill or searchable select field which shows the name and a preview of the icon from the lucide-icons icon pack.
It seems like this should be possible, but I'm not sure how to implement it. Are there any ready made examples of icon fields that integrate with icon packs?
@Snailed lt This would be a super cool idea, I love the lucide icons
I'm not a React guy, but since Payload uses React components, this would probably be a good fit
https://lucide.dev/guide/packages/lucide-react
And then built via custom component / field
https://payloadcms.com/docs/admin/components#fields
Looks like a good starting point!
I'm just not sure how to build the custom component so that the select field autopopulates options with the lucide icon names.
I hope someone ends up making a plugin for such a field... If not I might give it a try soon™
@Snailed lt Ah that shoiuld be pretty easy!
for instance, say it was plain javascript
https://jsfiddle.net/notchris/37t4pkz0/5/
const iconSelect = document.createElement('select')
Object.keys(window.lucide.icons).forEach((icon) => {
const opt = document.createElement('option')
opt.value = icon
opt.innerHTML = icon
iconSelect.appendChild(opt)
})
document.body.appendChild(iconSelect)
That creates a select list of every icon name from the Lucide package
(the main package exports
icons
as a keyed list
So now you can create a custom select field in payload
and BOOM it shall work
If you still need help, I can help you write it
But give it a go and show me what you come up with
Wow, that was a fast demonstration! Thanks a bunch!
I'll give it a try
Good luck! And yeah, if you get really stuck, let me know, I would start with.....
Creating a basic Select field type (Before creating a custom field that displays the actual icon, etc)
https://payloadcms.com/docs/fields/select
Note: In the JSFIDDLE example I shared, the
lucide.icons
object is a keyed by the icon names, where each value is the SVG of that icon. I'm simply getting the key names, not the SVG, so youll want to loop over each key/value, maybe with Object.entries.
how did you get lucide.icons added to the window variable btw?
@Snailed lt When you use the CDN version of the library, it adds lucide as a property to window, like most CDN libraries
@Snailed lt The ESM version should have it available under the modules default export
or one of the exports
This page has some information about that
https://lucide.dev/guide/packages/lucide
You will likely install lucide via your package mananger instead
And then import it right into your component
import { createIcons, icons } from 'lucide'
at which point, icons should be the same as
lucide.icons
in my example
I managed to get the select up, now I just need to make the value a combination of a SVG and the name of the icon
Implementation 👇
import { Field } from 'payload/types';
import { Option } from 'payload/dist/fields/config/types';
import deepMerge from '../utilities/deepMerge';
import { createIcons, icons } from 'lucide';
export function generateLucideIconOptions(): Option[] {
const lucideIconOptions: Option[] = [];
Object.keys(icons).forEach((icon) => {
lucideIconOptions.push({
label: icon,
value: icon,
});
});
console.debug('lucideIconOptions', lucideIconOptions);
return lucideIconOptions;
}
export function lucideIcon({ overrides = {} } = {}): Field {
const lucideIcon: Field = {
name: 'lucideIcon',
type: 'select',
options: generateLucideIconOptions(),
};
return deepMerge(lucideIcon, overrides);
}
Usage 👇
import { CollectionConfig } from 'payload/types';
import { lucideIcon } from '../fields/lucideIcon';
export const Icons: CollectionConfig = {
slug: 'icons',
labels: {
singular: 'Icon',
plural: 'Icons',
},
fields: [lucideIcon()],
};
YOO
Nicely done!
😄
Note: Rendering a ton of SVGs could cause render blocking
You may only want to display the icon of the selected value
and add maybe a link to the live icon list on lucide
Good idea!
Thanks for all the help
Do you still have time to help with the ui field implementation?
I haven't messed around with ui fields yet, so not sure where to start
The documentation is a bit scarce:
https://payloadcms.com/docs/fields/uiI can help out in a little bit! I'm working on finishing a task at work but will be avail around Noon to check back 😄
Great, thanks!
So I've figured out that I need to create a Custom Component Field.
My idea is that I can just copy the one used for
type: 'select'
, and allow the addition of this type as an option:
export type OptionObjectWithIcon = {
label: Record<string, string> | string;
value: string;
icon: string; // svg code as a string
};
Then if the type is
OptionObjectWithIcon
I'll just render the svg inside the select's option
Ah that would be cool! Though, does option support non-text?
Maybe their custom select dos
does*
I have no idea how to do it in practice though, so I've been trying to get ChatGPT to do it for me to no avail:
https://chat.openai.com/share/cd94d7b8-b639-432a-9acc-b2e9060f72ca
They have a custom select? 😮
The built in select field is of type
SelectField
:
export type OptionObject = {
label: Record<string, string> | string;
value: string;
};
export type Option = OptionObject | string;
export type SelectField = FieldBase & {
type: 'select';
options: Option[];
hasMany?: boolean;
admin?: Admin & {
isClearable?: boolean;
isSortable?: boolean;
};
};
hmmm
Agreed, but if we can just override the original one so that it accepts my type (
OptionObjectWithIcon
) as an option, and renders the SVG if
option.icon
is not empty I think that would be better since we hopefully keep the same styling instead of having to re-style a new component
And what is the current issue with getting the icon to display?
I get this error:
[15:29:35] ERROR (payload): There were 1 errors validating your Payload config
[15:29:35] ERROR (payload): 1: Collection "products" > Field "categories" > "value" does not match any of the allowed types
[nodemon] app crashed - waiting for file changes before starting...
not sure why though
I can share all the relevant files if you want
Hmm, if we get to the point where I need to check the files then OK, but lets try to debug some more
So the error complains
Collection "products" > Field "categories" > "value"
When are you getting the value of the categories field
setting*
Folder structure in attached image for reference
And what value type does it expect?
I'm not even sure I'm trying to get the value anywhere
It's likely just through general validation
Give me a little bit and I'll be back on after lunch to review this more in depth
Don't feel the urge to respond right away, but I'll just post this while I still remember it 😛
I think it's complaining about the value inside categoriesOptions here:
// collections/Products.ts
import { CollectionConfig } from 'payload/types';
import CustomSelectFieldWithIcon, {
OptionObjectWithIcon,
} from '../fields/selectFieldWithIcon'; // Adjust the path if needed
// Define the options for the Categories field as custom OptionObject[] type
const categoriesOptions: OptionObjectWithIcon[] = [
{
label: {
en: 'Electronics',
fr: 'Électronique', // You can include labels for other languages as well
},
value: 'electronics',
icon: '<svg>...</svg>', // Replace with your SVG icon as a string
},
{
label: {
en: 'Clothing',
fr: 'Vêtements',
},
value: 'clothing',
icon: '<svg>...</svg>',
},
// Add more options as needed
];
export const Products: CollectionConfig = {
slug: 'products',
fields: [
// Your other fields...
{
name: 'categories',
label: 'Categories',
type: 'select',
options: categoriesOptions, // Use the custom OptionObject type for options
admin: {
// Optional admin configuration
components: {
Field: CustomSelectFieldWithIcon, // Use the custom field component
},
},
},
// Your other fields...
],
};
@Jarrod This is what I have working^
I want to add an icon for each select option. How do I do that?
Like this
Oooo, nice! Gonna try it out ASAP
Basically the underlying field is a text field, because the payload config is static and you will not know the icon keys beforehand. So you render a custom react component that
rendersa select field, but the actual value saved is not an enum.
I see. That example was great since it taught me which "components" I need to make to create a custom component and make use of it
I'm wondering how I can pass parameters to the index.tsx file though. So that I can choose the
options
content myself
I'm not really experienced with React btw.
So please let me know if I'm asking questions about React and not Payload 🙂
This is what I get when I run the example code btw:
Yes, that is what you should get. Now you can style it however you want, you will need to generate the options for lucid icons and use those instead of my placeholders.
How do I pass in the props though? Is there any documentation on that?
what do you mean? What props?
like this?
export function CustomSelect({
path,
options = ['icon-1', 'icon-2'],
}): React.FC<any> {
return (
<div>
<Select path={path} name={path} options={options} />
<RenderIcon path={path} />
</div>
);
}
The props to the CustomSelect function
in that component, you need to create the options object to pass into the select component
sounds like you guys talked above about how to curate a list of icon keys
that is what you would pass into the select component
and you can run that code right within this component. You will likely want to create some state with React (setting the value inside a useEffect) and then pass the state value through to the Select
Alright, I think I kinda understand. I'll try for a bit and get back to you if I'm completely stuck.
Thanks a lot for the help!
Sounds great! No problem
Shouldn't this work?
import { Field } from 'payload/types';
import { Option } from 'payload/dist/fields/config/types';
import { createIcons, icons } from 'lucide';
import { CustomSelect } from './CustomSelect';
export function generateLucideIconOptions(): Option[] {
const lucideIconOptions: Option[] = [];
Object.keys(icons).forEach((icon) => {
lucideIconOptions.push({
label: icon,
value: icon,
});
});
return lucideIconOptions;
}
export const IconSelectorField: Field = {
name: 'lucidIcon',
type: 'text',
admin: {
components: {
Field: CustomSelect({ options: generateLucideIconOptions() }),
},
},
};
@Jarrod ^
It happens when my CustomSelect code is like this
If I change it to this I get a different error
I made it!
Not as generic as I'd wanted, but I finally made it work
My implementation:
Thank you both chris and Jarrod for the help!
Result:
Nice! A little css and they could be right next to each other 😁
Do you have a working gist for this? it would be nice
No, I sadly didn't make one. Maybe a image to text service can be used to get the code though?
Feel free to make a gist if you'd like. Consider the code open-sourced 🙂
Star
Discord
online
Get dedicated engineering support directly from the Payload team..