Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
3 changes: 1 addition & 2 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@
"papaparse": "^5.4.1",
"pluralize": "^8.0.0",
"posthog-js": "1.67.1",
"prism-react-renderer": "^2.3.1",
"prismjs": "^1.29.0",
"qrcode": "^1.5.3",
"react": "19.0.0-rc-69d4b800-20241021",
"react-children-utilities": "^2.10.0",
Expand All @@ -94,6 +92,7 @@
"recharts": "^2.13.3",
"remark-gfm": "^4.0.0",
"server-only": "^0.0.1",
"shiki": "1.22.2",
"sonner": "^1.7.0",
"spdx-correct": "^3.2.0",
"swagger-ui-react": "^5.17.14",
Expand Down
46 changes: 0 additions & 46 deletions apps/dashboard/src/@/components/ui/CodeBlock.tsx

This file was deleted.

36 changes: 0 additions & 36 deletions apps/dashboard/src/@/components/ui/code.tsx

This file was deleted.

70 changes: 70 additions & 0 deletions apps/dashboard/src/@/components/ui/code/RenderCode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
import { Button } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { CheckIcon, CopyIcon } from "lucide-react";
import { useState } from "react";

export function RenderCode(props: {
code: string;
html: string;
className?: string;
scrollableClassName?: string;
}) {
return (
<div
className={cn(
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-background",
props.className,
)}
>
<ScrollShadow
scrollableClassName={cn("p-4", props.scrollableClassName)}
className="text-xs md:text-sm [&_*]:leading-relaxed"
shadowColor="hsl(var(--muted))"
>
<div
// biome-ignore lint/security/noDangerouslySetInnerHtml: we know what we're doing here
dangerouslySetInnerHTML={{ __html: props.html }}
/>
</ScrollShadow>
<CopyButton
text={props.code}
iconClassName="size-3"
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"
/>
</div>
);
}

function CopyButton(props: {
text: string;
className?: string;
iconClassName?: string;
}) {
const [isCopied, setIsCopied] = useState(false);
return (
<ToolTipLabel label="Copy">
<Button
variant="ghost"
aria-label="Copy"
className={cn("h-auto w-auto p-1", props.className)}
onClick={() => {
navigator.clipboard.writeText(props.text);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1000);
}}
>
{isCopied ? (
<CheckIcon
className={cn("size-4 text-green-500", props.iconClassName)}
/>
) : (
<CopyIcon
className={cn("size-4 text-muted-foreground", props.iconClassName)}
/>
)}
</Button>
</ToolTipLabel>
);
}
57 changes: 57 additions & 0 deletions apps/dashboard/src/@/components/ui/code/code.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import { cn } from "@/lib/utils";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import type { BundledLanguage } from "shiki";
import { Spinner } from "../../ui/Spinner/Spinner";
import { RenderCode } from "./RenderCode";
import { getCodeHtml } from "./getCodeHtml";

export type CodeProps = {
code: string;
lang: BundledLanguage;
className?: string;
scrollableClassName?: string;
loadingClassName?: string;
keepPreviousDataOnCodeChange?: boolean;
};

export const CodeClient: React.FC<CodeProps> = ({
code,
lang,
className,
scrollableClassName,
loadingClassName,
keepPreviousDataOnCodeChange = false,
}) => {
const codeQuery = useQuery({
queryKey: ["html", code],
queryFn: () => getCodeHtml(code, lang),
placeholderData: keepPreviousDataOnCodeChange
? keepPreviousData
: undefined,
retry: false,
});

if (!codeQuery.data) {
return (
<div
className={cn(
"flex min-h-[200px] items-center justify-center rounded-lg border border-border",
loadingClassName,
)}
>
<Spinner className="size-8" />
</div>
);
}

return (
<RenderCode
code={codeQuery.data.formattedCode}
html={codeQuery.data.html}
className={className}
scrollableClassName={scrollableClassName}
/>
);
};
18 changes: 18 additions & 0 deletions apps/dashboard/src/@/components/ui/code/code.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { BundledLanguage } from "shiki";
import { RenderCode } from "./RenderCode";
import { getCodeHtml } from "./getCodeHtml";

export type CodeProps = {
code: string;
lang: BundledLanguage;
className?: string;
};

export const CodeServer: React.FC<CodeProps> = async ({
code,
lang,
className,
}) => {
const { html, formattedCode } = await getCodeHtml(code, lang);
return <RenderCode code={formattedCode} html={html} className={className} />;
};
115 changes: 115 additions & 0 deletions apps/dashboard/src/@/components/ui/code/code.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import type { Meta, StoryObj } from "@storybook/react";
import { BadgeContainer, mobileViewport } from "stories/utils";
import { CodeClient } from "./code.client";

const meta = {
title: "code/lang",
component: Component,
parameters: {},
} satisfies Meta<typeof Component>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Desktop: Story = {
args: {},
};

export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};

const tsCode = `\
type User = {
name: string;
age: number;
}
function logUser(user: User) {
console.log(user)
}
`;

const jsCode = `\
import { getContract } from "thirdweb";
import { sepolia } from "thirdweb/chains";
import { getOwnedNFTs } from "thirdweb/extensions/erc1155";
const contract = getContract({
client,
address: "0x1234...",
chain: sepolia,
});
`;

const jsxCode = `\
import { ThirdwebProvider } from "thirdweb/react";
function Main() {
return (
<ThirdwebProvider>
<App />
</ThirdwebProvider>
);
}`;

const tsxCode = `\
type User = {
name: string;
age: number;
}
function UserInfo(props: { user: User }) {
return <div> {props.user.name} </div>
}
`;

const tsxCodeWithFormError = `\
// This piece of code has invalid syntax and can't be formatted by prettier
// this should not crash the page - and be rendered as is without formatting
// the format error is logged in console
type User = // missing { here
name: string;
age: number;
}
function UserInfo(props: { user: User }) {
return <div> {props.user.name} </div>
}
`;

const bashCode = "pnpm i thirdweb";

function Component() {
return (
<div className="container flex max-w-[600px] flex-col gap-10 py-10">
<BadgeContainer label="ts">
<CodeClient code={tsCode} lang="ts" />
</BadgeContainer>

<BadgeContainer label="js">
<CodeClient code={jsCode} lang="js" />
</BadgeContainer>

<BadgeContainer label="bash">
<CodeClient code={bashCode} lang="bash" />
</BadgeContainer>

<BadgeContainer label="jsx">
<CodeClient code={jsxCode} lang="jsx" />
</BadgeContainer>

<BadgeContainer label="tsx">
<CodeClient code={tsxCode} lang="tsx" />
</BadgeContainer>

<BadgeContainer label="tsx">
<CodeClient code={tsxCodeWithFormError} lang="tsx" />
</BadgeContainer>
</div>
);
}
Loading
Loading