Skip to content

Commit 96bdc5a

Browse files
committed
Migrate CodeSegment to shadcn/tailwind + Improved UI
1 parent b30177c commit 96bdc5a

File tree

9 files changed

+326
-187
lines changed

9 files changed

+326
-187
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"use client";
2+
import { CodeClient } from "@/components/ui/code/code.client";
3+
import type React from "react";
4+
import { type Dispatch, type SetStateAction, useMemo } from "react";
5+
import { TabButtons } from "../ui/tabs";
6+
7+
export type CodeEnvironment =
8+
| "javascript"
9+
| "typescript"
10+
| "react"
11+
| "react-native"
12+
| "unity";
13+
14+
type SupportedEnvironment = {
15+
environment: CodeEnvironment;
16+
title: string;
17+
};
18+
19+
type CodeSnippet = Partial<Record<CodeEnvironment, string>>;
20+
21+
const Environments: SupportedEnvironment[] = [
22+
{
23+
environment: "javascript",
24+
title: "JavaScript",
25+
},
26+
{
27+
environment: "typescript",
28+
title: "TypeScript",
29+
},
30+
{
31+
environment: "react",
32+
title: "React",
33+
},
34+
{
35+
environment: "react-native",
36+
title: "React Native",
37+
},
38+
{
39+
environment: "unity",
40+
title: "Unity",
41+
},
42+
];
43+
44+
interface CodeSegmentProps {
45+
snippet: CodeSnippet;
46+
environment: CodeEnvironment;
47+
setEnvironment:
48+
| Dispatch<SetStateAction<CodeEnvironment>>
49+
| ((language: CodeEnvironment) => void);
50+
isInstallCommand?: boolean;
51+
hideTabs?: boolean;
52+
onlyTabs?: boolean;
53+
}
54+
55+
export const CodeSegment: React.FC<CodeSegmentProps> = ({
56+
snippet,
57+
environment,
58+
setEnvironment,
59+
isInstallCommand,
60+
hideTabs,
61+
onlyTabs,
62+
}) => {
63+
const activeEnvironment: CodeEnvironment = useMemo(() => {
64+
return (
65+
snippet[environment] ? environment : Object.keys(snippet)[0]
66+
) as CodeEnvironment;
67+
}, [environment, snippet]);
68+
69+
const activeSnippet = useMemo(() => {
70+
return snippet[activeEnvironment];
71+
}, [activeEnvironment, snippet]);
72+
73+
const lines = useMemo(
74+
() => (activeSnippet ? activeSnippet.split("\n") : []),
75+
[activeSnippet],
76+
);
77+
78+
const code = lines.join("\n").trim();
79+
80+
const environments = Environments.filter(
81+
(env) =>
82+
Object.keys(snippet).includes(env.environment) &&
83+
snippet[env.environment],
84+
);
85+
86+
return (
87+
<div
88+
className={
89+
"flex flex-col overflow-hidden rounded-lg border border-border"
90+
}
91+
>
92+
{!hideTabs && (
93+
<TabButtons
94+
tabs={environments.map((env) => ({
95+
label: env.title,
96+
onClick: () => setEnvironment(env.environment),
97+
isActive: activeEnvironment === env.environment,
98+
name: env.title,
99+
isEnabled: true,
100+
}))}
101+
tabClassName="text-sm gap-2 !text-sm"
102+
tabIconClassName="size-4"
103+
tabContainerClassName="px-3 pt-1.5 gap-0.5"
104+
hideBottomLine={!!onlyTabs}
105+
/>
106+
)}
107+
108+
{onlyTabs ? null : (
109+
<>
110+
<CodeClient
111+
code={code}
112+
loadingClassName="min-h-[450px]"
113+
className="rounded-none border-none"
114+
lang={
115+
isInstallCommand
116+
? activeEnvironment === "react-native"
117+
? "tsx"
118+
: "bash"
119+
: activeEnvironment === "react" ||
120+
activeEnvironment === "react-native"
121+
? "tsx"
122+
: activeEnvironment === "unity"
123+
? "cpp"
124+
: activeEnvironment
125+
}
126+
/>
127+
</>
128+
)}
129+
</div>
130+
);
131+
};
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import {
2+
Select,
3+
SelectContent,
4+
SelectGroup,
5+
SelectItem,
6+
SelectTrigger,
7+
SelectValue,
8+
} from "@/components/ui/select";
9+
import type { Meta, StoryObj } from "@storybook/react";
10+
import { useState } from "react";
11+
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
12+
import { type CodeEnvironment, CodeSegment } from "./code-segment.client";
13+
14+
const meta = {
15+
title: "blocks/CodeSegment",
16+
component: Story,
17+
parameters: {
18+
nextjs: {
19+
appDirectory: true,
20+
},
21+
},
22+
} satisfies Meta<typeof Story>;
23+
24+
export default meta;
25+
type Story = StoryObj<typeof meta>;
26+
27+
export const Desktop: Story = {
28+
args: {},
29+
};
30+
31+
export const Mobile: Story = {
32+
args: {},
33+
parameters: {
34+
viewport: mobileViewport("iphone14"),
35+
},
36+
};
37+
38+
type Mode = "default" | "no-tabs" | "only-tabs";
39+
40+
function Story() {
41+
const [mode, setMode] = useState<Mode>("default");
42+
const modes: Mode[] = ["default", "no-tabs", "only-tabs"];
43+
44+
return (
45+
<div className="container flex max-w-[700px] flex-col gap-10 py-10">
46+
<Select
47+
value={mode}
48+
onValueChange={(v) => {
49+
setMode(v as Mode);
50+
}}
51+
>
52+
<SelectTrigger className="w-[180px]">
53+
<SelectValue />
54+
</SelectTrigger>
55+
<SelectContent>
56+
<SelectGroup>
57+
{modes.map((item) => {
58+
return (
59+
<SelectItem key={item} value={item}>
60+
{item}
61+
</SelectItem>
62+
);
63+
})}
64+
</SelectGroup>
65+
</SelectContent>
66+
</Select>
67+
68+
<Variant envsToShow={["javascript"]} storyLabel="1 env" mode={mode} />
69+
70+
<Variant
71+
envsToShow={["javascript", "typescript"]}
72+
storyLabel="2 envs"
73+
mode={mode}
74+
/>
75+
76+
<Variant
77+
envsToShow={[
78+
"javascript",
79+
"typescript",
80+
"react",
81+
"react-native",
82+
"unity",
83+
]}
84+
storyLabel="5 envs"
85+
mode={mode}
86+
/>
87+
</div>
88+
);
89+
}
90+
91+
function Variant(props: {
92+
envsToShow: CodeEnvironment[];
93+
storyLabel: string;
94+
mode: Mode;
95+
}) {
96+
const [env, setEnv] = useState<CodeEnvironment>(
97+
props.envsToShow[0] || "javascript",
98+
);
99+
return (
100+
<BadgeContainer label={props.storyLabel}>
101+
<CodeSegment
102+
snippet={{
103+
javascript: props.envsToShow.includes("javascript")
104+
? jsCode
105+
: undefined,
106+
react: props.envsToShow.includes("react") ? jsxCode : undefined,
107+
unity: props.envsToShow.includes("unity") ? unityCode : undefined,
108+
"react-native": props.envsToShow.includes("react-native")
109+
? jsxCode
110+
: undefined,
111+
typescript: props.envsToShow.includes("typescript")
112+
? tsCode
113+
: undefined,
114+
}}
115+
environment={env}
116+
setEnvironment={setEnv}
117+
hideTabs={props.mode === "no-tabs"}
118+
onlyTabs={props.mode === "only-tabs"}
119+
/>
120+
</BadgeContainer>
121+
);
122+
}
123+
124+
const jsCode = `\
125+
import { getContract } from "thirdweb";
126+
import { sepolia } from "thirdweb/chains";
127+
import { getOwnedNFTs } from "thirdweb/extensions/erc1155";
128+
129+
const contract = getContract({
130+
client,
131+
address: "0x1234...",
132+
chain: sepolia,
133+
});
134+
`;
135+
136+
const jsxCode = `\
137+
import { ThirdwebProvider } from "thirdweb/react";
138+
139+
function Main() {
140+
return (
141+
<ThirdwebProvider>
142+
<App />
143+
</ThirdwebProvider>
144+
);
145+
}`;
146+
147+
const unityCode = `\
148+
using Thirdweb;
149+
150+
// Reference the SDK
151+
var sdk = ThirdwebManager.Instance.SDK;
152+
153+
// Create data
154+
NFTMetadata meta = new NFTMetadata()
155+
{
156+
name = "Unity NFT",
157+
description = "Minted From Unity",
158+
image = "ipfs://QmbpciV7R5SSPb6aT9kEBAxoYoXBUsStJkMpxzymV4ZcVc",
159+
};
160+
string metaJson = Newtonsoft.Json.JsonConvert.SerializeObject(meta);
161+
162+
// Upload raw text or from a file path
163+
var response = await ThirdwebManager.Instance.SDK.storage.UploadText(metaJson);`;
164+
165+
const tsCode = `\
166+
type User = {
167+
name: string;
168+
age: number;
169+
}
170+
171+
function logUser(user: User) {
172+
console.log(user)
173+
}
174+
`;

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,18 @@ export function TabButtons(props: {
8787
tabContainerClassName?: string;
8888
containerClassName?: string;
8989
shadowColor?: string;
90+
tabIconClassName?: string;
91+
hideBottomLine?: boolean;
9092
}) {
9193
const { containerRef, lineRef, activeTabRef } =
9294
useUnderline<HTMLButtonElement>();
9395

9496
return (
9597
<div className={cn("relative", props.containerClassName)}>
9698
{/* Bottom line */}
97-
<div className="absolute right-0 bottom-0 left-0 h-[1px] bg-border" />
99+
{!props.hideBottomLine && (
100+
<div className="absolute right-0 bottom-0 left-0 h-[1px] bg-border" />
101+
)}
98102

99103
<ScrollShadow
100104
scrollableClassName="pb-[8px] relative"
@@ -120,7 +124,9 @@ export function TabButtons(props: {
120124
)}
121125
onClick={tab.isEnabled ? tab.onClick : undefined}
122126
>
123-
{tab.icon && <tab.icon className="size-6" />}
127+
{tab.icon && (
128+
<tab.icon className={cn("size-6", props.tabIconClassName)} />
129+
)}
124130
{tab.name}
125131
</Button>
126132
);

apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/storage/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"use client";
22

33
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
4+
import {
5+
type CodeEnvironment,
6+
CodeSegment,
7+
} from "@/components/blocks/code-segment.client";
48
import { Divider, Flex, GridItem, SimpleGrid, Tooltip } from "@chakra-ui/react";
5-
import { CodeSegment } from "components/contract-tabs/code/CodeSegment";
6-
import type { CodeEnvironment } from "components/contract-tabs/code/types";
79
import { RelevantDataSection } from "components/dashboard/RelevantDataSection";
810
import { useState } from "react";
911
import { Card, Heading, Link, Text, TrackedCopyButton } from "tw-components";

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { CodeEnvironment } from "@/components/blocks/code-segment.client";
12
import { CodeClient } from "@/components/ui/code/code.client";
23
import {
34
Flex,
@@ -10,7 +11,6 @@ import {
1011
} from "@chakra-ui/react";
1112
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
1213
import { ChakraNextImage } from "components/Image";
13-
import type { CodeEnvironment } from "components/contract-tabs/code/types";
1414
import { Aurora } from "components/homepage/Aurora";
1515
import { connectPlaygroundData } from "components/product-pages/common/connect/data";
1616
import { useState } from "react";

apps/dashboard/src/components/contract-functions/contract-function.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"use client";
22

3+
import {
4+
type CodeEnvironment,
5+
CodeSegment,
6+
} from "@/components/blocks/code-segment.client";
37
import { CopyTextButton } from "@/components/ui/CopyTextButton";
48
import { Badge } from "@/components/ui/badge";
59
import { Input } from "@/components/ui/input";
@@ -49,8 +53,6 @@ import {
4953
COMMANDS,
5054
formatSnippet,
5155
} from "../../contract-ui/tabs/code/components/code-overview";
52-
import { CodeSegment } from "../contract-tabs/code/CodeSegment";
53-
import type { CodeEnvironment } from "../contract-tabs/code/types";
5456
import { InteractiveAbiFunction } from "./interactive-abi-function";
5557

5658
const ContractFunctionComment = lazy(

0 commit comments

Comments
 (0)