christopherafbjur / sanity-plugin-icon-picker

MIT License
30 stars 25 forks source link

Add usage example to Readme #19

Closed christopherafbjur closed 1 year ago

christopherafbjur commented 3 years ago

Add examples om how to consume the icon data in the front-end.

Here's an example written as an issue response as reference:

https://github.com/christopherafbjur/sanity-plugin-icon-picker/issues/18#issuecomment-900080685

ehowey commented 2 years ago

I would second this as an interesting improvement to the Readme.

I hacked away at this with a Gatsby site and am pretty happy with my final solution, sure it could maybe be better though...in the case of Gatsby you do not want to use the recommended example from the issue referenced above or it will really affect your performance score because of how it affects code splitting.

Here is what I did...if you need the code just shoot me a message.

  1. Implement example from above inside of gatsby-node.js and create a custom node in the GraphQL layer called something like resolvedIcons. You will need to convert what you get from react-icons to a string to pass it through the GraphQL data layer.
  2. Convert that string back to JSON on the frontend
  3. Use the GenIcons function from react-icons to generate the icons on the frontend from the JSON.

This is a bit messy BUT it prevents you from really bloating your bundle size with unneeded icons.

christopherafbjur commented 2 years ago

Thank you @ehowey for sharing. Would you mind sharing an example snippet? I think this could be really useful.

ehowey commented 2 years ago

Okey here goes...this is probably not the best code...so please hack away and adapt as needed. It isn't a super clean solution BUT it works and keeps the bloat out of your frontend and moves it over to the build process.

// gatsby-node.js
const FiIcons = require("react-icons/fi")
const HiIcons = require("react-icons/hi")
const MdIcons = require("react-icons/md")

// Function to get the Icons needed to display
const getIcon = (icon) => {
  if (icon.provider === "fi") {
    const Icon = FiIcons[icon.name]
    const stringIcon = `${Icon}`
    const startIndex = stringIcon.indexOf("GenIcon(") + 8
    const endIndex = stringIcon.lastIndexOf(")(props)")
    const sliced = stringIcon.slice(startIndex, endIndex)
    return sliced
  }
  if (icon.provider === "hi") {
    const Icon = HiIcons[icon.name]
    const stringIcon = `${Icon}`
    const startIndex = stringIcon.indexOf("GenIcon(") + 8
    const endIndex = stringIcon.lastIndexOf(")(props)")
    const sliced = stringIcon.slice(startIndex, endIndex)
    return sliced
  }
  if (icon.provider === "mdi") {
    const Icon = MdIcons[icon.name]
    const stringIcon = `${Icon}`
    const startIndex = stringIcon.indexOf("GenIcon(") + 8
    const endIndex = stringIcon.lastIndexOf(")(props)")
    const sliced = stringIcon.slice(startIndex, endIndex)
    return sliced
  }
}

// Create the schema for GraphQL
exports.createSchemaCustomization = ({ actions, schema }) => {
  const { createTypes } = actions

  const sanitySocialLinksTypeDefs = `
    type SanitySocialLinks implements Node {
      resolvedHeaderSocialLinks: [SocialLinks]
      resolvedFooterSocialLinks: [SocialLinks]
    }
    type SocialLinks {
      iconAsString: String
      link: String
      name: String
    }
  `
  createTypes(sanitySocialLinksTypeDefs)
}

// Resolve the icon data at the end of the GraphQL process after the other nodes are available
exports.createResolvers = ({ createResolvers }) => { 
  const iconResolvers = {
    SanitySocialLinks: {
      resolvedHeaderSocialLinks: {
        resolve: (source, args = {}) => {
          const iconObject = source.headerSocialLinks.map((socialLink) => {
            return {
              name: socialLink.name,
              link: socialLink.link,
              iconAsString: getIcon(socialLink.icon),
            }
          })
          return iconObject
        },
      },
      resolvedFooterSocialLinks: {
        resolve: (source, args = {}) => {
          const iconObject = source.footerSocialLinks.map((socialLink) => {
            return {
              name: socialLink.name,
              link: socialLink.link,
              iconAsString: getIcon(socialLink.icon),
            }
          })
          return iconObject
        },
      },
    },
  }
  createResolvers(iconResolvers)
}
// iconGenerator.js
// This component is responsible for actually resolving the icons based on data passed in from GraphQL
import { GenIcon } from "react-icons"

// icon comes in as a string from GraphQL and then is parsed back into JSON.
// Example of the format coming in
// "{"tag":"svg","attr":{"viewBox":"0 0 20 20","fill":"currentColor"},"child":[{"tag":"path","attr":{"fillRule":"evenodd","d":"M14.243 5.757a6 6 0 10-.986 9.284 1 1 0 111.087 1.678A8 8 0 1118 10a3 3 0 01-4.8 2.401A4 4 0 1114 10a1 1 0 102 0c0-1.537-.586-3.07-1.757-4.243zM12 10a2 2 0 10-4 0 2 2 0 004 0z","clipRule":"evenodd"}}]}"

const IconGenerator = ({ icon }) => {
  // Parse the string back to JSON
  const parsed = JSON.parse(icon)
  // Pass the object into the icon generator
  const Icon = GenIcon(parsed)
  return <Icon />
}

export default IconGenerator
// headerSocialLinks.js
// This just takes the data that is passed to it and loops over the data to present the icons
// The query would look something like this:
// allSanitySocialLinks(
        //   limit: 1
        //   sort: { fields: _updatedAt, order: DESC }
        // ) {
        //   nodes {
        //     resolvedFooterSocialLinks {
        //       name
        //       link
        //       iconAsString
        //     }
        //     resolvedHeaderSocialLinks {
        //       name
        //       link
        //       iconAsString
        //     }
        //   }
        // }
import { Fragment } from "react"
import IconGenerator from "../../shared/iconGenerator"

const HeaderSocialLinks = ({ socialLinks }) => {
  return (
    <Fragment>
      {socialLinks.map((link) => (
        <li key={link.name}>
          <a
            href={link.link}
            sx={{
              py: 2,
              pl: 2,
              pr: 2,
              ":last-of-type": {
                pr: 0,
              },
            }}
          >
            <IconGenerator icon={link.iconAsString} />
          </a>
        </li>
      ))}
    </Fragment>
  )
}

export default HeaderSocialLinks
christopherafbjur commented 2 years ago

Thank you so much @ehowey will look at implementing this into readme asap 👍

ehowey commented 2 years ago

I would maybe leave it here or link it? I don't know if it is read me quality code?

christopherafbjur commented 2 years ago

@ehowey If you leave it here I'll have a look and and tweak it to a bare minimum adapted for the readme :)

Poggen commented 2 years ago

I ended up implementing something similar to what @ehowey shared using a nextJS edge API route and reduced my bundle size with ~2MB. But it still feels like a very ugly way of doing it—it would have been so much more convenient to just get the icon as a string from the groq query directly.

christopherafbjur commented 1 year ago

I've been thinking about this and decided that it's better to keep the usage example as simple as possible and library indepedent. It should be thought of as a PoC example. Will be too time consuming to add different usage examples for different libraries.

How ever, you might find https://github.com/christopherafbjur/sanity-plugin-icon-picker/issues/37#issuecomment-1560828851 interesting as an alternative for these workarounds due to the limitations of react icons.