Skip to content

Commit 8613032

Browse files
isArlekinAleksei
andauthored
feat: upload dynamic icon in isolation (#44)
* Rework dynamic icons component * Add icon/[iconSymbol] page to make sure that icons loaded separately * Remove 'use client' in DynamicIcon * Use <GithubSVGIcon /> in <StaticIcon />. Get rid of useEffect * Create UnknownIcon component * Move GithubSvgIcon, Image, SVG, UnknownIcon into Base directory * Fix mono icon displaying --------- Co-authored-by: Aleksei <[email protected]>
1 parent 28899a4 commit 8613032

File tree

18 files changed

+2833
-2323
lines changed

18 files changed

+2833
-2323
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use client";
2+
3+
import { Web3Icon } from "@bgd-labs/react-web3-icons";
4+
import { FC } from "react";
5+
6+
import { IconLoader } from "@/components/IconCard";
7+
8+
type PageProps = {
9+
params: {
10+
iconSymbol: string;
11+
};
12+
};
13+
14+
const Page: FC<PageProps> = ({ params }) => {
15+
const { iconSymbol } = params;
16+
return (
17+
<div>
18+
<Web3Icon
19+
symbol={iconSymbol}
20+
assetTag="a"
21+
mono={false}
22+
loader={<IconLoader />}
23+
className="size-[70px]"
24+
/>
25+
</div>
26+
);
27+
};
28+
29+
export default Page;

apps/docs/src/components/AssetIconCard.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Web3Icon } from "@bgd-labs/react-web3-icons";
44
import { githubIconsPath } from "@bgd-labs/react-web3-icons/dist/constants";
55
import { IconVariant } from "@bgd-labs/react-web3-icons/dist/utils";
6+
import { useRouter } from "next/navigation";
67
import { useState } from "react";
78

89
import { IconCard, IconLoader } from "@/components/IconCard";
@@ -24,6 +25,8 @@ export const AssetIconCard = ({
2425
chainName?: string;
2526
assetTag?: "a" | "stata" | "stk";
2627
}) => {
28+
const router = useRouter();
29+
2730
const [variant, setVariant] = useState(IconVariant.Full);
2831
const iconPath = assetTag
2932
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -53,6 +56,7 @@ export const AssetIconCard = ({
5356
fileName={`${(assetTag ? assetTag : "").toLowerCase()}${symbol.toUpperCase()}${variant === IconVariant.Full ? "" : variant}`}
5457
setActiveType={setVariant}
5558
activeType={variant}
59+
onTitleClick={() => router.push(`/icon/${symbol}`)}
5660
>
5761
<Web3Icon
5862
className="size-[70px]"

apps/docs/src/components/IconCard.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const IconCard = ({
3030
fileName,
3131
setActiveType,
3232
activeType,
33+
onTitleClick,
3334
}: {
3435
children: ReactNode;
3536
name: string;
@@ -39,6 +40,7 @@ export const IconCard = ({
3940
fileName: string;
4041
setActiveType: (type: IconVariant) => void;
4142
activeType: IconVariant;
43+
onTitleClick?: () => void;
4244
}) => {
4345
const handleSetIconVariant = () => {
4446
if (activeType === IconVariant.Full) {
@@ -52,7 +54,12 @@ export const IconCard = ({
5254
<Box>
5355
<div className="relative flex min-h-[285px] w-[200px] flex-col justify-center overflow-hidden rounded-lg pb-4">
5456
<div className="relative flex-1 p-2">
55-
<div className="text-sm font-semibold text-gray-800">{name}</div>
57+
<div
58+
onClick={onTitleClick}
59+
className="cursor-pointer text-sm font-semibold text-gray-800"
60+
>
61+
{name}
62+
</div>
5663
{subName && (
5764
<div className="font-mono text-xs uppercase text-gray-400">
5865
{subName}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { FC } from "react";
2+
import InlineSVG from "react-inlinesvg";
3+
4+
import { IconComponentBaseProps } from "../../utils";
5+
import { generateUniqueHash } from "../../utils/generateUniqueHash";
6+
7+
type GithubSVGIconProps = {
8+
githubSrc: string;
9+
} & IconComponentBaseProps;
10+
11+
const GithubSvgIcon: FC<GithubSVGIconProps> = ({
12+
githubSrc,
13+
loader,
14+
...props
15+
}) => {
16+
return (
17+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
18+
// @ts-expect-error
19+
<InlineSVG
20+
{...props}
21+
src={githubSrc}
22+
uniqueHash={generateUniqueHash()}
23+
uniquifyIDs
24+
loader={loader}
25+
/>
26+
);
27+
};
28+
29+
export default GithubSvgIcon;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React, { ComponentProps } from "react";
2+
3+
export const Image = ({
4+
svgCode,
5+
...props
6+
}: { svgCode: string } & ComponentProps<"img">) => {
7+
return (
8+
<img
9+
{...props}
10+
draggable={false}
11+
onDragStart={(e) => e.preventDefault()}
12+
src={`data:image/svg+xml;base64,${btoa(svgCode)}`}
13+
style={{ outline: "none !important", pointerEvents: "none" }}
14+
alt={props.alt}
15+
/>
16+
);
17+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"use client";
2+
3+
import React, { ComponentProps } from "react";
4+
5+
import { Image } from "./Image";
6+
import { UnknownIcon } from "./UnknownIcon";
7+
8+
type SVGProps = {
9+
svgCode?: string;
10+
} & ComponentProps<"img">;
11+
12+
/**
13+
* Wrapper for get svg image from svg code
14+
*/
15+
export const SVG = ({ svgCode, ...props }: SVGProps) => {
16+
if (!svgCode) {
17+
return <UnknownIcon />;
18+
}
19+
return <Image svgCode={svgCode} {...props} />;
20+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React, { ComponentProps } from "react";
2+
3+
import { iconUnknown } from "../../icons/full/build/icon-unknown.icon";
4+
import { Image } from "./Image";
5+
6+
type UnknownIconProps = ComponentProps<"img">;
7+
8+
export const UnknownIcon = (props: UnknownIconProps) => {
9+
return <Image svgCode={iconUnknown.data} {...props} />;
10+
};

packages/react-web3-icons/src/components/DynamicIcon.tsx

Lines changed: 0 additions & 83 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { FC } from "react";
2+
3+
import { Web3IconType } from "../../icons/full";
4+
import { IconComponentBaseProps } from "../../utils";
5+
import GithubSVGIcon from "../Base/GithubSVGIcon";
6+
import { LoadableIcon } from "./LoadableIcon";
7+
8+
export type DynamicIconProps = IconComponentBaseProps & {
9+
iconKey: Web3IconType | string;
10+
githubSrc?: string;
11+
};
12+
13+
/**
14+
* Wrapper for get icons dynamically
15+
*/
16+
export const DynamicIcon: FC<DynamicIconProps> = ({
17+
githubSrc,
18+
loader,
19+
...props
20+
}) => {
21+
return (
22+
<LoadableIcon
23+
{...props}
24+
fallback={loader}
25+
fallbackComponent={
26+
githubSrc ? (
27+
<GithubSVGIcon githubSrc={githubSrc} loader={loader} />
28+
) : undefined
29+
}
30+
/>
31+
);
32+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import loadable from "@loadable/component";
2+
import camelCase from "lodash.camelcase";
3+
import React, { JSX } from "react";
4+
5+
import { Web3IconType } from "../../icons/full";
6+
import {
7+
capitalize,
8+
formatMonoSvgCode,
9+
IconComponentBaseProps,
10+
} from "../../utils";
11+
import { SVG } from "../Base/SVG";
12+
13+
export type LoadableIconProps = IconComponentBaseProps & {
14+
iconKey: Web3IconType | string;
15+
fallbackComponent?: JSX.Element;
16+
};
17+
18+
export const LoadableIcon = loadable(
19+
async ({ iconKey, mono, fallbackComponent, ...props }: LoadableIconProps) => {
20+
try {
21+
const lowerCasedIconKey = iconKey.toLowerCase();
22+
const iconFileName = `icon-${lowerCasedIconKey}`;
23+
const folder = mono ? "mono" : "full";
24+
const icon = await import(
25+
`../../icons/${folder}/build/${iconFileName}.icon.ts`
26+
);
27+
const iconData = icon[`icon${capitalize(camelCase(lowerCasedIconKey))}`];
28+
29+
if (!iconData && fallbackComponent) {
30+
return {
31+
default: () => <>{fallbackComponent}</>,
32+
};
33+
}
34+
35+
return {
36+
default: () => (
37+
<SVG
38+
svgCode={formatMonoSvgCode({
39+
mono,
40+
svgCode: iconData?.data,
41+
...props,
42+
})}
43+
{...props}
44+
/>
45+
),
46+
};
47+
} catch (e) {
48+
return {
49+
default: () => <SVG svgCode={undefined} {...props} />,
50+
};
51+
}
52+
},
53+
{
54+
ssr: true,
55+
cacheKey: ({ iconKey, mono }) => `${iconKey}-${mono}`,
56+
},
57+
);

0 commit comments

Comments
 (0)