Skip to content

Commit 9ef8b39

Browse files
committed
Improve loading state for code block (#5520)
DASH-473
1 parent 2ae404d commit 9ef8b39

File tree

9 files changed

+90
-131
lines changed

9 files changed

+90
-131
lines changed

apps/dashboard/src/@/components/blocks/code-segment.client.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ export const CodeSegment: React.FC<CodeSegmentProps> = ({
109109
<>
110110
<CodeClient
111111
code={code}
112-
loadingClassName="min-h-[450px] rounded-none border-none"
113112
className="rounded-none border-none"
114113
lang={
115114
isInstallCommand
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use client";
2+
3+
import { cn } from "@/lib/utils";
4+
import { useClipboard } from "hooks/useClipboard";
5+
import { CheckIcon, CopyIcon } from "lucide-react";
6+
import { ScrollShadow } from "../ScrollShadow/ScrollShadow";
7+
import { Button } from "../button";
8+
9+
export function CodeBlockContainer(props: {
10+
codeToCopy: string;
11+
children: React.ReactNode;
12+
className?: string;
13+
scrollableClassName?: string;
14+
copyButtonClassName?: string;
15+
}) {
16+
const { hasCopied, onCopy } = useClipboard(props.codeToCopy);
17+
18+
return (
19+
<div
20+
className={cn(
21+
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-background",
22+
props.className,
23+
)}
24+
>
25+
<ScrollShadow
26+
scrollableClassName={cn("p-4", props.scrollableClassName)}
27+
className="text-xs md:text-sm [&_*]:leading-relaxed"
28+
shadowColor="hsl(var(--muted))"
29+
>
30+
{props.children}
31+
</ScrollShadow>
32+
33+
<Button
34+
size="sm"
35+
variant="outline"
36+
onClick={onCopy}
37+
className={cn(
38+
"absolute top-3.5 right-3.5 h-auto bg-background p-2",
39+
props.copyButtonClassName,
40+
)}
41+
>
42+
{hasCopied ? (
43+
<CheckIcon className="size-3 text-green-500" />
44+
) : (
45+
<CopyIcon className="size-3 text-muted-foreground" />
46+
)}
47+
</Button>
48+
</div>
49+
);
50+
}
Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,23 @@
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";
1+
import { CodeBlockContainer } from "./CodeBlockContainer";
72

83
export function RenderCode(props: {
94
code: string;
105
html: string;
116
className?: string;
127
scrollableClassName?: string;
8+
copyButtonClassName?: string;
139
}) {
1410
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-
)}
11+
<CodeBlockContainer
12+
codeToCopy={props.code}
13+
className={props.className}
14+
copyButtonClassName={props.copyButtonClassName}
15+
scrollableClassName={props.scrollableClassName}
2016
>
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"
17+
<div
18+
// biome-ignore lint/security/noDangerouslySetInnerHtml: we know what we're doing here
19+
dangerouslySetInnerHTML={{ __html: props.html }}
3520
/>
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>
21+
</CodeBlockContainer>
6922
);
7023
}
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
11
"use client";
2-
3-
import { cn } from "@/lib/utils";
42
import { keepPreviousData, useQuery } from "@tanstack/react-query";
53
import type { BundledLanguage } from "shiki";
6-
import { Spinner } from "../../ui/Spinner/Spinner";
74
import { RenderCode } from "./RenderCode";
85
import { getCodeHtml } from "./getCodeHtml";
6+
import { PlainTextCodeBlock } from "./plaintext-code";
97

108
export type CodeProps = {
119
code: string;
1210
lang: BundledLanguage;
1311
className?: string;
1412
scrollableClassName?: string;
15-
loadingClassName?: string;
1613
keepPreviousDataOnCodeChange?: boolean;
14+
copyButtonClassName?: string;
1715
};
1816

1917
export const CodeClient: React.FC<CodeProps> = ({
2018
code,
2119
lang,
2220
className,
2321
scrollableClassName,
24-
loadingClassName,
2522
keepPreviousDataOnCodeChange = false,
23+
copyButtonClassName,
2624
}) => {
2725
const codeQuery = useQuery({
2826
queryKey: ["html", code],
@@ -35,14 +33,12 @@ export const CodeClient: React.FC<CodeProps> = ({
3533

3634
if (!codeQuery.data) {
3735
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>
36+
<PlainTextCodeBlock
37+
code={code}
38+
className={className}
39+
scrollableClassName={scrollableClassName}
40+
copyButtonClassName={copyButtonClassName}
41+
/>
4642
);
4743
}
4844

@@ -52,6 +48,7 @@ export const CodeClient: React.FC<CodeProps> = ({
5248
html={codeQuery.data.html}
5349
className={className}
5450
scrollableClassName={scrollableClassName}
51+
copyButtonClassName={copyButtonClassName}
5552
/>
5653
);
5754
};
Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
"use client";
2-
31
import { cn } from "@/lib/utils";
4-
import { useClipboard } from "hooks/useClipboard";
5-
import { CheckIcon, CopyIcon } from "lucide-react";
6-
import { ScrollShadow } from "../ScrollShadow/ScrollShadow";
7-
import { Button } from "../button";
2+
import { CodeBlockContainer } from "./CodeBlockContainer";
83

94
export function PlainTextCodeBlock(props: {
105
code: string;
@@ -13,40 +8,16 @@ export function PlainTextCodeBlock(props: {
138
scrollableClassName?: string;
149
codeClassName?: string;
1510
}) {
16-
const { hasCopied, onCopy } = useClipboard(props.code);
17-
1811
return (
19-
<div
20-
className={cn(
21-
"group relative max-w-full overflow-hidden rounded-lg border border-border bg-background",
22-
props.className,
23-
)}
12+
<CodeBlockContainer
13+
codeToCopy={props.code}
14+
className={props.className}
15+
copyButtonClassName={props.copyButtonClassName}
16+
scrollableClassName={props.scrollableClassName}
2417
>
25-
<ScrollShadow
26-
scrollableClassName={cn("p-4", props.scrollableClassName)}
27-
className="text-xs md:text-sm [&_*]:leading-relaxed"
28-
shadowColor="hsl(var(--muted))"
29-
>
30-
<code className={cn("block whitespace-pre", props.codeClassName)}>
31-
{props.code}
32-
</code>
33-
</ScrollShadow>
34-
35-
<Button
36-
size="sm"
37-
variant="outline"
38-
onClick={onCopy}
39-
className={cn(
40-
"absolute top-3.5 right-3.5 h-auto bg-background p-2",
41-
props.copyButtonClassName,
42-
)}
43-
>
44-
{hasCopied ? (
45-
<CheckIcon className="size-3 text-green-500" />
46-
) : (
47-
<CopyIcon className="size-3 text-muted-foreground" />
48-
)}
49-
</Button>
50-
</div>
18+
<code className={cn("block whitespace-pre", props.codeClassName)}>
19+
{props.code}
20+
</code>
21+
</CodeBlockContainer>
5122
);
5223
}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/embed/embed-setup.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -529,11 +529,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
529529

530530
<Card className="flex w-full flex-col gap-2 md:w-1/2">
531531
<Heading size="title.sm">Embed Code</Heading>
532-
<CodeClient
533-
code={embedCode}
534-
lang="html"
535-
loadingClassName="min-h-[190px]"
536-
/>
532+
<CodeClient code={embedCode} lang="html" />
537533
<Button
538534
className="w-auto gap-2"
539535
variant="outline"

apps/dashboard/src/components/connect/CodePlayground.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,6 @@ const CodePlayground = ({
325325

326326
<QueryClientProvider client={queryClient}>
327327
<CodeClient
328-
loadingClassName="min-h-[490px]"
329328
code={code}
330329
lang={
331330
environment === "react" || environment === "react-native"

apps/dashboard/src/components/contract-components/published-contract/markdown-renderer.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,12 @@ export const MarkdownRenderer: React.FC<{
9090
if (props?.className) {
9191
if (disableCodeHighlight) {
9292
return (
93-
<PlainTextCodeBlock
94-
{...cleanedProps(props)}
95-
code={onlyText(props.children).trim()}
96-
/>
93+
<div className="mb-4">
94+
<PlainTextCodeBlock
95+
{...cleanedProps(props)}
96+
code={onlyText(props.children).trim()}
97+
/>
98+
</div>
9799
);
98100
}
99101
const language = props.className.replace("language-", "");

apps/dashboard/src/components/contract-components/shared/sources-accordion.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,7 @@ export const SourcesAccordion: React.FC<SourcesAccordionProps> = ({
3838
</AccordionButton>
3939
<AccordionPanel>
4040
{isExpanded && (
41-
<CodeClient
42-
code={JSON.stringify(abi, null, 2)}
43-
lang="json"
44-
loadingClassName="min-h-[500px]"
45-
/>
41+
<CodeClient code={JSON.stringify(abi, null, 2)} lang="json" />
4642
)}
4743
</AccordionPanel>
4844
</>
@@ -67,11 +63,7 @@ export const SourcesAccordion: React.FC<SourcesAccordionProps> = ({
6763
</AccordionButton>
6864
<AccordionPanel>
6965
{isExpanded && (
70-
<CodeClient
71-
code={signature.source.trim()}
72-
lang="solidity"
73-
loadingClassName="min-h-[500px]"
74-
/>
66+
<CodeClient code={signature.source.trim()} lang="solidity" />
7567
)}
7668
</AccordionPanel>
7769
</>

0 commit comments

Comments
 (0)