Giter Club home page Giter Club logo

Comments (7)

jcv8000 avatar jcv8000 commented on June 18, 2024

You can render an svg element pointing to tabler-sprite-nostroke.svg#ICON_NAME whether its in node_modules or wherever

type Props = {
    icon: string;
    color?: string;
    size?: number;
};

export function Icon({ icon, color = "inherit", size = 18 }: Props) {
    return (
        <svg
            style={{
                stroke: "currentColor",
                strokeWidth: 1.75,
                strokeLinecap: "round",
                strokeLinejoin: "round",
                fill: "none",
                color: color,
                width: `${size}px`,
                height: `${size}px`
            }}
        >
            <use href={"path/to/node_modules/@tabler/icons/tabler-sprite-nostroke.svg#tabler-" + icon} />
        </svg>
    );
}

This is how I was doing it at first but I had issues with Electron loading in the entire sprite sheet for each icon in production mode (worked fine in dev mode), so I also have the Tabler Icons Webfont package and use this instead:

import "@tabler/icons-webfont/tabler-icons.min.css";

type Props = {
    icon: string;
    color?: string;
    size?: number;
};

export function Icon({ icon, color = "inherit", size = 18 }: Props) {
    return (
        <i
            className={`ti ti-${icon}`}
            style={{
                fontSize: `${size}px`,
                color: color
            }}
        />
    );
}

from tabler-icons.

tsnery avatar tsnery commented on June 18, 2024

Have you tried rendering a list of all these icons?

from tabler-icons.

nzyoni avatar nzyoni commented on June 18, 2024

I support the addition of this capability 👍

I'm currently working on a component that requires the ability to accept an icon name as a prop and dynamically render it inside. Having this feature would greatly enhance the flexibility of the component.

Additionally, it would be beneficial to have an enum or type that provides a comprehensive list of all available icons. This way, developers can easily reference and choose from a standardized set of icons when using the component.

from tabler-icons.

timheerwagen avatar timheerwagen commented on June 18, 2024

Here is my attempt:

(Optional: get a list of all available icons)
Then Display the icon by its name.

import { icons } from "@tabler/icons-react";
import { useState } from "react";
import dynamic from "next/dynamic";

export default function Home() {
  const [icon, setIcon] = useState<string>("IconAbc");

  return (
    <main className={`flex min-h-screen flex-col items-center p-24 gap-4`}>
      <DynamicIcon icon={icon} />
      {icon}
      <div className="grid grid-cols-12 h-96 overflow-scroll">
        {Object.entries(icons).map(([key, Value]) => (
          <button
            className="p-1 hover:bg-red-500"
            key={key}
            onClick={() => setIcon(key)}
            title={key}
          >
            <Value />
          </button>
        ))}
      </div>
    </main>
  );
}

const DynamicIcon = ({ icon }: { icon?: string }) => {
  if (!icon) return null;

  const Icon = dynamic(
    () => import(`@tabler/icons-react/dist/esm/icons/${icon}.mjs`),
    {
      loading: () => <p>Loading...</p>,
    }
  );

  return <Icon />;
};

from tabler-icons.

timheerwagen avatar timheerwagen commented on June 18, 2024

Update 1

The problem with my previously mentioned solution is that Webpack generates an entry map with all kinds of icons when it is created, which is added when the page is first loaded and is more than 50kb.

Looks like this:

"./IconGalaxy.mjs":[183111,183111],"./IconGardenCart.mjs":[643864,643864],"./IconGardenCartOff.mjs":[49835,49835],"./IconGasStation.mjs":[913266,913266],"./IconGasStationOff.mjs":[695510,695510],"./IconGauge.mjs":[890866,890866],"./IconGaugeFilled.mjs":[161194,161194],"./IconGaugeOff.mjs":[622710,622710],"./IconGavel.mjs":[103125,103125],"./IconGenderAgender.mjs":[467566,467566],"./IconGenderAndrogyne.mjs":[951129,951129],"./IconGenderBigender.mjs":[489974,489974],"./IconGenderDemiboy.mjs":[971931,971931],"./IconGenderDemigirl.mjs":[158563,158563],"./IconGenderEpicene.mjs":[750659,750659],"./IconGenderFemale.mjs":[883878,883878],"./IconGenderFemme.mjs":[527029,527029],"./IconGenderGenderfluid.mjs":[226052,226052],"./IconGenderGenderless.mjs":[640555,640555],"./IconGenderGenderqueer.mjs":[618596,618596],"

image

The icons are bundled in individual chunks, which I think is correct behavior.
image

Does anyone know how to optimize this to avoid the 50kb first-load, or does anyone know a better implementation?

Update 2

I found out that if you create an object that contains all icons -> dynamic import and then dynamically import it, the 50kb chunk is split on first load.

dynamicImports.ts

import { iconsList } from "@tabler/icons-react";
import set from "lodash/set";
import type { FC } from "react";

import { iconToPascalCase } from "./utils";

export const dynamicIconImports: Record<
  (typeof iconsList)["default"][number],
  () => Promise<FC>
> = iconsList.default.reduce((prev, icon) => {
  const pascalIconName = iconToPascalCase(icon);

  set(
    prev,
    pascalIconName,
    () => import(`@tabler/icons-react/dist/esm/icons/Icon${pascalIconName}.mjs`)
  );
  return prev;
}, {});

Then import into the dynamicIcon component.

dynamicIcon.tsx

import { IconFileUnknown, IconLoader } from "@tabler/icons-react";
import dynamic from "next/dynamic";

const LoadingIcon = () => (
  <IconLoader className="animate-pulse" aria-label="Icon wird geladen..." />
);
export const DynamicIcon = (icon: string)=> {
  const Icon = dynamic(
    async () => {
      const importMap = (await import(`./dynamicImports`)).dynamicIconImports;

      return importMap[icon]?.().catch(() => ({
        default: () => <IconFileUnknown className="opacity-50" />,
      }));
    },
    {
      loading: LoadingIcon,
    }
  );

  return Icon;
};

Now it's a separate chunk. Not ideal, but still better than having 50kb in the first load.

image

Suggestion

If this object containing the dynamic imports were provided by @tabler/icons-react, webpack could statically analyze it and store potential network requests and data.

This also eliminates the need to convert the iconsList to pascalCase on the end user's side.

const dynamicIconImports = {
  "a-b-2": () => import(`@tabler/icons-react/dist/esm/icons/IconAB2.mjs`),
  "a-b-off": () => import(`@tabler/icons-react/dist/esm/icons/IconABOff.mjs`),
  ...
}

I will try to make a PR later.

from tabler-icons.

tsnery avatar tsnery commented on June 18, 2024

Update 1

The problem with my previously mentioned solution is that Webpack generates an entry map with all kinds of icons when it is created, which is added when the page is first loaded and is more than 50kb.

Looks like this:

"./IconGalaxy.mjs":[183111,183111],"./IconGardenCart.mjs":[643864,643864],"./IconGardenCartOff.mjs":[49835,49835],"./IconGasStation.mjs":[913266,913266],"./IconGasStationOff.mjs":[695510,695510],"./IconGauge.mjs":[890866,890866],"./IconGaugeFilled.mjs":[161194,161194],"./IconGaugeOff.mjs":[622710,622710],"./IconGavel.mjs":[103125,103125],"./IconGenderAgender.mjs":[467566,467566],"./IconGenderAndrogyne.mjs":[951129,951129],"./IconGenderBigender.mjs":[489974,489974],"./IconGenderDemiboy.mjs":[971931,971931],"./IconGenderDemigirl.mjs":[158563,158563],"./IconGenderEpicene.mjs":[750659,750659],"./IconGenderFemale.mjs":[883878,883878],"./IconGenderFemme.mjs":[527029,527029],"./IconGenderGenderfluid.mjs":[226052,226052],"./IconGenderGenderless.mjs":[640555,640555],"./IconGenderGenderqueer.mjs":[618596,618596],"

image

The icons are bundled in individual chunks, which I think is correct behavior. image

Does anyone know how to optimize this to avoid the 50kb first-load, or does anyone know a better implementation?

Update 2

I found out that if you create an object that contains all icons -> dynamic import and then dynamically import it, the 50kb chunk is split on first load.

dynamicImports.ts

import { iconsList } from "@tabler/icons-react";
import set from "lodash/set";
import type { FC } from "react";

import { iconToPascalCase } from "./utils";

export const dynamicIconImports: Record<
  (typeof iconsList)["default"][number],
  () => Promise<FC>
> = iconsList.default.reduce((prev, icon) => {
  const pascalIconName = iconToPascalCase(icon);

  set(
    prev,
    pascalIconName,
    () => import(`@tabler/icons-react/dist/esm/icons/Icon${pascalIconName}.mjs`)
  );
  return prev;
}, {});

Then import into the dynamicIcon component.

dynamicIcon.tsx

import { IconFileUnknown, IconLoader } from "@tabler/icons-react";
import dynamic from "next/dynamic";

const LoadingIcon = () => (
  <IconLoader className="animate-pulse" aria-label="Icon wird geladen..." />
);
export const DynamicIcon = (icon: string)=> {
  const Icon = dynamic(
    async () => {
      const importMap = (await import(`./dynamicImports`)).dynamicIconImports;

      return importMap[icon]?.().catch(() => ({
        default: () => <IconFileUnknown className="opacity-50" />,
      }));
    },
    {
      loading: LoadingIcon,
    }
  );

  return Icon;
};

Now it's a separate chunk. Not ideal, but still better than having 50kb in the first load.

image

Suggestion

If this object containing the dynamic imports were provided by @tabler/icons-react, webpack could statically analyze it and store potential network requests and data.

This also eliminates the need to convert the iconsList to pascalCase on the end user's side.

const dynamicIconImports = {
  "a-b-2": () => import(`@tabler/icons-react/dist/esm/icons/IconAB2.mjs`),
  "a-b-off": () => import(`@tabler/icons-react/dist/esm/icons/IconABOff.mjs`),
  ...
}

I will try to make a PR later.

I tested your solution from 3 weeks ago, but I felt the application significantly weighed down the page loading. So, I refactored it in a simpler and more direct way to achieve what I wanted, and I noticed an improvement in performance. I didn't have time to deeply analyze the reason for the improvement, but that's what I was able to do in the short time I had:

import { getIconInPascalCase } from '@/utils/icons/getIconInPascalCase'
import * as icons from '@tabler/icons-react'
import { memo } from 'react'

type Props = icons.TablerIconsProps & {
  icon: string
}

type IconProps = (props: Props) => JSX.Element | null

export const DynamicIcon = memo<IconProps>(function DynamicIcon({
  icon,
  name,
  ...props
}) {
  if (!icon) return null
  const iconName = getIconInPascalCase(icon)
  
  const Icon = icons[iconName] as unknown as IconProps

  return <Icon icon={iconName} name={name} {...props} />
})

from tabler-icons.

timheerwagen avatar timheerwagen commented on June 18, 2024

@tsnery This is because you are importing the entire namespace. Your app may feel faster, but you download every icon in the first-load 400kb gzipped. You can test that with bundle-analyzer.

from tabler-icons.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.