Skip to content

Commit ff188fd

Browse files
committed
Replace prism+chakra Code component with shiki+tailwind (#5393)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on refactoring code related to code blocks in the UI by replacing the `CodeBlock` component with a new `CodeClient` component and introducing a `PlainTextCodeBlock` for plain text display. It also updates styles and dependencies. ### Detailed summary - Removed `CodeBlock` component from various files. - Introduced `CodeClient` component for enhanced code rendering. - Added `PlainTextCodeBlock` for displaying plain text. - Updated styles in `globals.css` for dark mode support. - Added new utility functions for code formatting. - Updated dependencies in `package.json` and `pnpm-lock.yaml`. > The following files were skipped due to too many changes: `pnpm-lock.yaml` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 512eeb5 commit ff188fd

File tree

35 files changed

+724
-603
lines changed

35 files changed

+724
-603
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,8 @@
2424
},
2525
"[javascript]": {
2626
"editor.defaultFormatter": "biomejs.biome"
27+
},
28+
"[css]": {
29+
"editor.defaultFormatter": "biomejs.biome"
2730
}
2831
}

apps/dashboard/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@
7878
"papaparse": "^5.4.1",
7979
"pluralize": "^8.0.0",
8080
"posthog-js": "1.67.1",
81-
"prism-react-renderer": "^2.3.1",
82-
"prismjs": "^1.29.0",
8381
"qrcode": "^1.5.3",
8482
"react": "19.0.0-rc-69d4b800-20241021",
8583
"react-children-utilities": "^2.10.0",
@@ -94,6 +92,7 @@
9492
"recharts": "^2.13.3",
9593
"remark-gfm": "^4.0.0",
9694
"server-only": "^0.0.1",
95+
"shiki": "1.22.2",
9796
"sonner": "^1.7.0",
9897
"spdx-correct": "^3.2.0",
9998
"swagger-ui-react": "^5.17.14",

apps/dashboard/src/@/components/ui/CodeBlock.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

apps/dashboard/src/@/components/ui/code.tsx

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
2+
import { Button } from "@/components/ui/button";
3+
import { ToolTipLabel } from "@/components/ui/tooltip";
4+
import { cn } from "@/lib/utils";
5+
import { CheckIcon, CopyIcon } from "lucide-react";
6+
import { useState } from "react";
7+
8+
export function RenderCode(props: {
9+
code: string;
10+
html: string;
11+
className?: string;
12+
scrollableClassName?: string;
13+
}) {
14+
return (
15+
<div
16+
className={cn(
17+
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-background",
18+
props.className,
19+
)}
20+
>
21+
<ScrollShadow
22+
scrollableClassName={cn("p-4", props.scrollableClassName)}
23+
className="text-xs md:text-sm [&_*]:leading-relaxed"
24+
shadowColor="hsl(var(--muted))"
25+
>
26+
<div
27+
// biome-ignore lint/security/noDangerouslySetInnerHtml: we know what we're doing here
28+
dangerouslySetInnerHTML={{ __html: props.html }}
29+
/>
30+
</ScrollShadow>
31+
<CopyButton
32+
text={props.code}
33+
iconClassName="size-3"
34+
className="absolute top-4 right-4 z-10 border border-border bg-background p-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
35+
/>
36+
</div>
37+
);
38+
}
39+
40+
function CopyButton(props: {
41+
text: string;
42+
className?: string;
43+
iconClassName?: string;
44+
}) {
45+
const [isCopied, setIsCopied] = useState(false);
46+
return (
47+
<ToolTipLabel label="Copy">
48+
<Button
49+
variant="ghost"
50+
aria-label="Copy"
51+
className={cn("h-auto w-auto p-1", props.className)}
52+
onClick={() => {
53+
navigator.clipboard.writeText(props.text);
54+
setIsCopied(true);
55+
setTimeout(() => setIsCopied(false), 1000);
56+
}}
57+
>
58+
{isCopied ? (
59+
<CheckIcon
60+
className={cn("size-4 text-green-500", props.iconClassName)}
61+
/>
62+
) : (
63+
<CopyIcon
64+
className={cn("size-4 text-muted-foreground", props.iconClassName)}
65+
/>
66+
)}
67+
</Button>
68+
</ToolTipLabel>
69+
);
70+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"use client";
2+
3+
import { cn } from "@/lib/utils";
4+
import { keepPreviousData, useQuery } from "@tanstack/react-query";
5+
import type { BundledLanguage } from "shiki";
6+
import { Spinner } from "../../ui/Spinner/Spinner";
7+
import { RenderCode } from "./RenderCode";
8+
import { getCodeHtml } from "./getCodeHtml";
9+
10+
export type CodeProps = {
11+
code: string;
12+
lang: BundledLanguage;
13+
className?: string;
14+
scrollableClassName?: string;
15+
loadingClassName?: string;
16+
keepPreviousDataOnCodeChange?: boolean;
17+
};
18+
19+
export const CodeClient: React.FC<CodeProps> = ({
20+
code,
21+
lang,
22+
className,
23+
scrollableClassName,
24+
loadingClassName,
25+
keepPreviousDataOnCodeChange = false,
26+
}) => {
27+
const codeQuery = useQuery({
28+
queryKey: ["html", code],
29+
queryFn: () => getCodeHtml(code, lang),
30+
placeholderData: keepPreviousDataOnCodeChange
31+
? keepPreviousData
32+
: undefined,
33+
retry: false,
34+
});
35+
36+
if (!codeQuery.data) {
37+
return (
38+
<div
39+
className={cn(
40+
"flex min-h-[200px] items-center justify-center rounded-lg border border-border",
41+
loadingClassName,
42+
)}
43+
>
44+
<Spinner className="size-8" />
45+
</div>
46+
);
47+
}
48+
49+
return (
50+
<RenderCode
51+
code={codeQuery.data.formattedCode}
52+
html={codeQuery.data.html}
53+
className={className}
54+
scrollableClassName={scrollableClassName}
55+
/>
56+
);
57+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { BundledLanguage } from "shiki";
2+
import { RenderCode } from "./RenderCode";
3+
import { getCodeHtml } from "./getCodeHtml";
4+
5+
export type CodeProps = {
6+
code: string;
7+
lang: BundledLanguage;
8+
className?: string;
9+
};
10+
11+
export const CodeServer: React.FC<CodeProps> = async ({
12+
code,
13+
lang,
14+
className,
15+
}) => {
16+
const { html, formattedCode } = await getCodeHtml(code, lang);
17+
return <RenderCode code={formattedCode} html={html} className={className} />;
18+
};
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { BadgeContainer, mobileViewport } from "stories/utils";
3+
import { CodeClient } from "./code.client";
4+
5+
const meta = {
6+
title: "code/lang",
7+
component: Component,
8+
parameters: {},
9+
} satisfies Meta<typeof Component>;
10+
11+
export default meta;
12+
type Story = StoryObj<typeof meta>;
13+
14+
export const Desktop: Story = {
15+
args: {},
16+
};
17+
18+
export const Mobile: Story = {
19+
args: {},
20+
parameters: {
21+
viewport: mobileViewport("iphone14"),
22+
},
23+
};
24+
25+
const tsCode = `\
26+
type User = {
27+
name: string;
28+
age: number;
29+
}
30+
31+
function logUser(user: User) {
32+
console.log(user)
33+
}
34+
`;
35+
36+
const jsCode = `\
37+
import { getContract } from "thirdweb";
38+
import { sepolia } from "thirdweb/chains";
39+
import { getOwnedNFTs } from "thirdweb/extensions/erc1155";
40+
41+
const contract = getContract({
42+
client,
43+
address: "0x1234...",
44+
chain: sepolia,
45+
});
46+
`;
47+
48+
const jsxCode = `\
49+
import { ThirdwebProvider } from "thirdweb/react";
50+
51+
function Main() {
52+
return (
53+
<ThirdwebProvider>
54+
<App />
55+
</ThirdwebProvider>
56+
);
57+
}`;
58+
59+
const tsxCode = `\
60+
type User = {
61+
name: string;
62+
age: number;
63+
}
64+
65+
function UserInfo(props: { user: User }) {
66+
return <div> {props.user.name} </div>
67+
}
68+
`;
69+
70+
const tsxCodeWithFormError = `\
71+
// This piece of code has invalid syntax and can't be formatted by prettier
72+
// this should not crash the page - and be rendered as is without formatting
73+
// the format error is logged in console
74+
75+
type User = // missing { here
76+
name: string;
77+
age: number;
78+
}
79+
80+
function UserInfo(props: { user: User }) {
81+
return <div> {props.user.name} </div>
82+
}
83+
`;
84+
85+
const bashCode = "pnpm i thirdweb";
86+
87+
function Component() {
88+
return (
89+
<div className="container flex max-w-[600px] flex-col gap-10 py-10">
90+
<BadgeContainer label="ts">
91+
<CodeClient code={tsCode} lang="ts" />
92+
</BadgeContainer>
93+
94+
<BadgeContainer label="js">
95+
<CodeClient code={jsCode} lang="js" />
96+
</BadgeContainer>
97+
98+
<BadgeContainer label="bash">
99+
<CodeClient code={bashCode} lang="bash" />
100+
</BadgeContainer>
101+
102+
<BadgeContainer label="jsx">
103+
<CodeClient code={jsxCode} lang="jsx" />
104+
</BadgeContainer>
105+
106+
<BadgeContainer label="tsx">
107+
<CodeClient code={tsxCode} lang="tsx" />
108+
</BadgeContainer>
109+
110+
<BadgeContainer label="tsx">
111+
<CodeClient code={tsxCodeWithFormError} lang="tsx" />
112+
</BadgeContainer>
113+
</div>
114+
);
115+
}

0 commit comments

Comments
 (0)