Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

How to make a custom field for an icon library?

default discord avatar
snailedltlast month
5

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?



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™



Wow, that was a fast demonstration! Thanks a bunch!



I'll give it a try



how did you get lucide.icons added to the window variable btw?



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()],
};


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/ui

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



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;
    };
};


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



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



Folder structure in attached image for reference



I'm not even sure I'm trying to get the value anywhere



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...
  ],
};


@281120856527077378

This is what I have working^



I want to add an icon for each select option. How do I do that?



Oooo, nice! Gonna try it out ASAP



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:



How do I pass in the props though? Is there any documentation on that?



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



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!



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() }),
    },
  },
};


@281120856527077378

^



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:



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 🙂



Awesome!


If it's not too much to ask, could you upload some screenshots of how it looks? 🙂

  • default discord avatar
    notchrlast year
    @290479081865084929

    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

    @290479081865084929

    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



    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.



    @290479081865084929

    When you use the CDN version of the library, it adds lucide as a property to window, like most CDN libraries



    @290479081865084929

    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



    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



    I 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 😄



    Ah that would be cool! Though, does option support non-text?



    Maybe their custom select dos



    does*



    Well im saying, the built in one



    This one looks nice:

    https://react-select.com/home

    hmmm



    And what is the current issue with getting the icon to display?



    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*



    And what value type does it expect?



    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

  • Like this



    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

    renders

    a select field, but the actual value saved is not an enum.



    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.



    what do you mean? What props?



    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



    Sounds great! No problem



    Nice! A little css and they could be right next to each other 😁

  • default discord avatar
    infitcon6 months ago

    Do you have a working gist for this? it would be nice

  • default discord avatar
    tyrola2 months ago

    Because I just found this thread:

    https://gist.github.com/BirknerAlex/dd63a499190ea42c08bd2c682df156f5

    I made one but with Font Awesome Icons, but should be easy adjustable for every other icon pack.

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.