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
131 changes: 131 additions & 0 deletions apps/dashboard/src/@/components/blocks/code-segment.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"use client";
import { CodeClient } from "@/components/ui/code/code.client";
import type React from "react";
import { type Dispatch, type SetStateAction, useMemo } from "react";
import { TabButtons } from "../ui/tabs";

export type CodeEnvironment =
| "javascript"
| "typescript"
| "react"
| "react-native"
| "unity";

type SupportedEnvironment = {
environment: CodeEnvironment;
title: string;
};

type CodeSnippet = Partial<Record<CodeEnvironment, string>>;

const Environments: SupportedEnvironment[] = [
{
environment: "javascript",
title: "JavaScript",
},
{
environment: "typescript",
title: "TypeScript",
},
{
environment: "react",
title: "React",
},
{
environment: "react-native",
title: "React Native",
},
{
environment: "unity",
title: "Unity",
},
];

interface CodeSegmentProps {
snippet: CodeSnippet;
environment: CodeEnvironment;
setEnvironment:
| Dispatch<SetStateAction<CodeEnvironment>>
| ((language: CodeEnvironment) => void);
isInstallCommand?: boolean;
hideTabs?: boolean;
onlyTabs?: boolean;
}

export const CodeSegment: React.FC<CodeSegmentProps> = ({
snippet,
environment,
setEnvironment,
isInstallCommand,
hideTabs,
onlyTabs,
}) => {
const activeEnvironment: CodeEnvironment = useMemo(() => {
return (
snippet[environment] ? environment : Object.keys(snippet)[0]
) as CodeEnvironment;
}, [environment, snippet]);

const activeSnippet = useMemo(() => {
return snippet[activeEnvironment];
}, [activeEnvironment, snippet]);

const lines = useMemo(
() => (activeSnippet ? activeSnippet.split("\n") : []),
[activeSnippet],
);

const code = lines.join("\n").trim();

const environments = Environments.filter(
(env) =>
Object.keys(snippet).includes(env.environment) &&
snippet[env.environment],
);

return (
<div
className={
"flex flex-col overflow-hidden rounded-lg border border-border"
}
>
{!hideTabs && (
<TabButtons
tabs={environments.map((env) => ({
label: env.title,
onClick: () => setEnvironment(env.environment),
isActive: activeEnvironment === env.environment,
name: env.title,
isEnabled: true,
}))}
tabClassName="text-sm gap-2 !text-sm"
tabIconClassName="size-4"
tabContainerClassName="px-3 pt-1.5 gap-0.5"
hideBottomLine={!!onlyTabs}
/>
)}

{onlyTabs ? null : (
<>
<CodeClient
code={code}
loadingClassName="min-h-[450px] rounded-none border-none"
className="rounded-none border-none"
lang={
isInstallCommand
? activeEnvironment === "react-native"
? "tsx"
: "bash"
: activeEnvironment === "react" ||
activeEnvironment === "react-native"
? "tsx"
: activeEnvironment === "unity"
? "cpp"
: activeEnvironment
}
/>
</>
)}
</div>
);
};
174 changes: 174 additions & 0 deletions apps/dashboard/src/@/components/blocks/code-segment.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
import { type CodeEnvironment, CodeSegment } from "./code-segment.client";

const meta = {
title: "blocks/CodeSegment",
component: Story,
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof Story>;

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

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

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

type Mode = "default" | "no-tabs" | "only-tabs";

function Story() {
const [mode, setMode] = useState<Mode>("default");
const modes: Mode[] = ["default", "no-tabs", "only-tabs"];

return (
<div className="container flex max-w-[700px] flex-col gap-10 py-10">
<Select
value={mode}
onValueChange={(v) => {
setMode(v as Mode);
}}
>
<SelectTrigger className="w-[180px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{modes.map((item) => {
return (
<SelectItem key={item} value={item}>
{item}
</SelectItem>
);
})}
</SelectGroup>
</SelectContent>
</Select>

<Variant envsToShow={["javascript"]} storyLabel="1 env" mode={mode} />

<Variant
envsToShow={["javascript", "typescript"]}
storyLabel="2 envs"
mode={mode}
/>

<Variant
envsToShow={[
"javascript",
"typescript",
"react",
"react-native",
"unity",
]}
storyLabel="5 envs"
mode={mode}
/>
</div>
);
}

function Variant(props: {
envsToShow: CodeEnvironment[];
storyLabel: string;
mode: Mode;
}) {
const [env, setEnv] = useState<CodeEnvironment>(
props.envsToShow[0] || "javascript",
);
return (
<BadgeContainer label={props.storyLabel}>
<CodeSegment
snippet={{
javascript: props.envsToShow.includes("javascript")
? jsCode
: undefined,
react: props.envsToShow.includes("react") ? jsxCode : undefined,
unity: props.envsToShow.includes("unity") ? unityCode : undefined,
"react-native": props.envsToShow.includes("react-native")
? jsxCode
: undefined,
typescript: props.envsToShow.includes("typescript")
? tsCode
: undefined,
}}
environment={env}
setEnvironment={setEnv}
hideTabs={props.mode === "no-tabs"}
onlyTabs={props.mode === "only-tabs"}
/>
</BadgeContainer>
);
}

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 unityCode = `\
using Thirdweb;

// Reference the SDK
var sdk = ThirdwebManager.Instance.SDK;

// Create data
NFTMetadata meta = new NFTMetadata()
{
name = "Unity NFT",
description = "Minted From Unity",
image = "ipfs://QmbpciV7R5SSPb6aT9kEBAxoYoXBUsStJkMpxzymV4ZcVc",
};
string metaJson = Newtonsoft.Json.JsonConvert.SerializeObject(meta);

// Upload raw text or from a file path
var response = await ThirdwebManager.Instance.SDK.storage.UploadText(metaJson);`;

const tsCode = `\
type User = {
name: string;
age: number;
}

function logUser(user: User) {
console.log(user)
}
`;
10 changes: 8 additions & 2 deletions apps/dashboard/src/@/components/ui/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,18 @@ export function TabButtons(props: {
tabContainerClassName?: string;
containerClassName?: string;
shadowColor?: string;
tabIconClassName?: string;
hideBottomLine?: boolean;
}) {
const { containerRef, lineRef, activeTabRef } =
useUnderline<HTMLButtonElement>();

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

<ScrollShadow
scrollableClassName="pb-[8px] relative"
Expand All @@ -120,7 +124,9 @@ export function TabButtons(props: {
)}
onClick={tab.isEnabled ? tab.onClick : undefined}
>
{tab.icon && <tab.icon className="size-6" />}
{tab.icon && (
<tab.icon className={cn("size-6", props.tabIconClassName)} />
)}
{tab.name}
</Button>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"use client";

import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
import {
type CodeEnvironment,
CodeSegment,
} from "@/components/blocks/code-segment.client";
import { Divider, Flex, GridItem, SimpleGrid, Tooltip } from "@chakra-ui/react";
import { CodeSegment } from "components/contract-tabs/code/CodeSegment";
import type { CodeEnvironment } from "components/contract-tabs/code/types";
import { RelevantDataSection } from "components/dashboard/RelevantDataSection";
import { useState } from "react";
import { Card, Heading, Link, Text, TrackedCopyButton } from "tw-components";
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/components/connect/CodePlayground.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { CodeEnvironment } from "@/components/blocks/code-segment.client";
import { CodeClient } from "@/components/ui/code/code.client";
import {
Flex,
Expand All @@ -10,7 +11,6 @@ import {
} from "@chakra-ui/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ChakraNextImage } from "components/Image";
import type { CodeEnvironment } from "components/contract-tabs/code/types";
import { Aurora } from "components/homepage/Aurora";
import { connectPlaygroundData } from "components/product-pages/common/connect/data";
import { useState } from "react";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"use client";

import {
type CodeEnvironment,
CodeSegment,
} from "@/components/blocks/code-segment.client";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
Expand Down Expand Up @@ -49,8 +53,6 @@ import {
COMMANDS,
formatSnippet,
} from "../../contract-ui/tabs/code/components/code-overview";
import { CodeSegment } from "../contract-tabs/code/CodeSegment";
import type { CodeEnvironment } from "../contract-tabs/code/types";
import { InteractiveAbiFunction } from "./interactive-abi-function";

const ContractFunctionComment = lazy(
Expand Down
Loading
Loading