From 5c08c7c17d2b74e0affce1d4117bceb7ab0752c6 Mon Sep 17 00:00:00 2001 From: MananTank Date: Wed, 27 Aug 2025 23:18:35 +0000 Subject: [PATCH] packages/ui: Add code components (#7927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR focuses on refactoring code related to the `CodeClient` component, improving its structure and imports, while also updating several components to use a new centralized location for code utilities. It removes deprecated files and enhances code organization across the project. ### Detailed summary - Deleted unused files related to clipboard and code rendering. - Refactored `CodeClient` to use a centralized import from `@workspace/ui/components/code/code.client`. - Updated various components to import `CodeClient` and other utilities from the new centralized location. - Added new `CodeServer` component for server-side rendering of code. - Improved loading states and error handling in code components. - Updated component imports across the project to maintain consistency. - Enhanced `PlainTextCodeBlock` functionality with new props for better customization. - Removed redundant code and improved overall project structure. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/dashboard/knip.json | 3 +- .../src/@/components/ui/code/code.client.tsx | 70 +----------------- .../src/@/components/ui/code/code.server.tsx | 26 ++----- .../@/components/ui/code/plaintext-code.tsx | 30 +------- apps/playground-web/knip.json | 3 +- .../blueprint-playground.client.tsx | 3 +- .../src/app/payments/components/CodeGen.tsx | 23 +++--- .../_components/airdrop-code.tsx | 6 +- .../mint-tokens/_components/mint-code.tsx | 6 +- .../webhooks/_components/webhooks-preview.tsx | 8 +-- .../wallets/sign-in/components/CodeGen.tsx | 18 +++-- .../5792-get-capabilities.tsx | 3 +- .../src/components/code/RenderCode.tsx | 39 ---------- .../src/components/code/code-example.tsx | 4 +- .../src/components/code/code.client.tsx | 58 +-------------- .../src/components/code/code.tsx | 28 +------- .../src/components/code/getCodeHtml.tsx | 38 ---------- .../src/components/in-app-wallet/profiles.tsx | 3 +- .../src/components/ui/markdown-renderer.tsx | 2 +- .../APIEndpointMeta/DynamicRequestExample.tsx | 5 +- .../components/Document/AuthMethodsTabs.tsx | 3 +- .../components/code/CodeBlockContainer.tsx | 59 --------------- .../portal/src/components/code/RenderCode.tsx | 39 ---------- .../src/components/code/code.client.tsx | 61 ++-------------- .../src/components/code/getCodeHtml.tsx | 47 ------------ .../src/components/code/plaintext-code.tsx | 30 +------- .../components/markdown/MarkdownRenderer.tsx | 13 ++-- apps/portal/src/components/ui/CopyButton.tsx | 36 ---------- packages/ui/package.json | 11 ++- packages/ui/src/components/badge.tsx | 3 +- packages/ui/src/components/breadcrumb.tsx | 3 +- packages/ui/src/components/button.stories.tsx | 4 +- packages/ui/src/components/button.tsx | 2 +- .../components}/code/CodeBlockContainer.tsx | 8 +-- .../ui/src/components}/code/RenderCode.tsx | 0 .../ui/src/components/code/code.client.tsx | 71 +++++++++++++++++++ .../ui/src/components/code/code.server.tsx | 22 ++++++ .../ui/src/components}/code/code.stories.tsx | 4 +- .../ui/src/components}/code/getCodeHtml.tsx | 0 .../code/plaintext-code.stories.tsx | 4 +- .../ui/src/components/code/plaintext-code.tsx | 29 ++++++++ packages/ui/src/components/img.stories.tsx | 4 +- packages/ui/src/components/img.tsx | 2 +- packages/ui/src/components/input.tsx | 2 +- packages/ui/src/components/scroll-shadow.tsx | 2 +- packages/ui/src/components/spinner.tsx | 2 +- packages/ui/src/components/table.stories.tsx | 4 +- packages/ui/src/components/table.tsx | 2 +- packages/ui/src/components/tooltip.tsx | 3 +- .../ui}/src/hooks/useClipboard.ts | 0 packages/ui/src/storybook/utils.tsx | 2 +- packages/ui/tsconfig.json | 5 +- pnpm-lock.yaml | 6 ++ 53 files changed, 228 insertions(+), 631 deletions(-) delete mode 100644 apps/playground-web/src/components/code/RenderCode.tsx delete mode 100644 apps/playground-web/src/components/code/getCodeHtml.tsx delete mode 100644 apps/portal/src/components/code/CodeBlockContainer.tsx delete mode 100644 apps/portal/src/components/code/RenderCode.tsx delete mode 100644 apps/portal/src/components/code/getCodeHtml.tsx delete mode 100644 apps/portal/src/components/ui/CopyButton.tsx rename {apps/dashboard/src/@/components/ui => packages/ui/src/components}/code/CodeBlockContainer.tsx (87%) rename {apps/dashboard/src/@/components/ui => packages/ui/src/components}/code/RenderCode.tsx (100%) create mode 100644 packages/ui/src/components/code/code.client.tsx create mode 100644 packages/ui/src/components/code/code.server.tsx rename {apps/dashboard/src/@/components/ui => packages/ui/src/components}/code/code.stories.tsx (96%) rename {apps/dashboard/src/@/components/ui => packages/ui/src/components}/code/getCodeHtml.tsx (100%) rename {apps/dashboard/src/@/components/ui => packages/ui/src/components}/code/plaintext-code.stories.tsx (92%) create mode 100644 packages/ui/src/components/code/plaintext-code.tsx rename {apps/portal => packages/ui}/src/hooks/useClipboard.ts (100%) diff --git a/apps/dashboard/knip.json b/apps/dashboard/knip.json index 9d3a56b9ed3..3260bd4557c 100644 --- a/apps/dashboard/knip.json +++ b/apps/dashboard/knip.json @@ -15,7 +15,8 @@ "fast-xml-parser", "@workspace/ui", "tailwindcss-animate", - "@radix-ui/react-tooltip" + "@radix-ui/react-tooltip", + "shiki" ], "next": true, "project": ["src/**"] diff --git a/apps/dashboard/src/@/components/ui/code/code.client.tsx b/apps/dashboard/src/@/components/ui/code/code.client.tsx index a56b4ba6543..5b837a9e19b 100644 --- a/apps/dashboard/src/@/components/ui/code/code.client.tsx +++ b/apps/dashboard/src/@/components/ui/code/code.client.tsx @@ -1,71 +1,3 @@ "use client"; -import { keepPreviousData, useQuery } from "@tanstack/react-query"; -import type { BundledLanguage } from "shiki"; -import { getCodeHtml } from "./getCodeHtml"; -import { PlainTextCodeBlock } from "./plaintext-code"; -import { RenderCode } from "./RenderCode"; -export type CodeProps = { - code: string; - lang: BundledLanguage; - className?: string; - scrollableClassName?: string; - keepPreviousDataOnCodeChange?: boolean; - copyButtonClassName?: string; - scrollableContainerClassName?: string; - shadowColor?: string; - ignoreFormattingErrors?: boolean; - onCopy?: (code: string) => void; -}; - -export const CodeClient: React.FC = ({ - code, - lang, - className, - scrollableClassName, - keepPreviousDataOnCodeChange = false, - copyButtonClassName, - ignoreFormattingErrors, - scrollableContainerClassName, - shadowColor, - onCopy, -}) => { - const codeQuery = useQuery({ - placeholderData: keepPreviousDataOnCodeChange - ? keepPreviousData - : undefined, - queryFn: () => - getCodeHtml(code, lang, { - ignoreFormattingErrors: ignoreFormattingErrors, - }), - queryKey: ["html", code], - retry: false, - }); - - if (!codeQuery.data) { - return ( - - ); - } - - return ( - - ); -}; +export { CodeClient } from "@workspace/ui/components/code/code.client"; diff --git a/apps/dashboard/src/@/components/ui/code/code.server.tsx b/apps/dashboard/src/@/components/ui/code/code.server.tsx index 82a87a4e8d0..4e4621a7f03 100644 --- a/apps/dashboard/src/@/components/ui/code/code.server.tsx +++ b/apps/dashboard/src/@/components/ui/code/code.server.tsx @@ -1,22 +1,4 @@ -import type { BundledLanguage } from "shiki"; -import { getCodeHtml } from "./getCodeHtml"; -import { RenderCode } from "./RenderCode"; - -export type CodeProps = { - code: string; - lang: BundledLanguage; - className?: string; - ignoreFormattingErrors?: boolean; -}; - -export const CodeServer: React.FC = async ({ - code, - lang, - className, - ignoreFormattingErrors, -}) => { - const { html, formattedCode } = await getCodeHtml(code, lang, { - ignoreFormattingErrors, - }); - return ; -}; +export { + type CodeProps, + CodeServer, +} from "@workspace/ui/components/code/code.server"; diff --git a/apps/dashboard/src/@/components/ui/code/plaintext-code.tsx b/apps/dashboard/src/@/components/ui/code/plaintext-code.tsx index de79cc4d354..12a0e83743b 100644 --- a/apps/dashboard/src/@/components/ui/code/plaintext-code.tsx +++ b/apps/dashboard/src/@/components/ui/code/plaintext-code.tsx @@ -1,29 +1 @@ -import { cn } from "@/lib/utils"; -import { CodeBlockContainer } from "./CodeBlockContainer"; - -export function PlainTextCodeBlock(props: { - code: string; - copyButtonClassName?: string; - className?: string; - scrollableClassName?: string; - codeClassName?: string; - scrollableContainerClassName?: string; - shadowColor?: string; - onCopy?: (code: string) => void; -}) { - return ( - - - {props.code} - - - ); -} +export { PlainTextCodeBlock } from "@workspace/ui/components/code/plaintext-code"; diff --git a/apps/playground-web/knip.json b/apps/playground-web/knip.json index c4187581b15..335586f1233 100644 --- a/apps/playground-web/knip.json +++ b/apps/playground-web/knip.json @@ -5,7 +5,8 @@ "server-only", "@workspace/ui", "tailwindcss-animate", - "@radix-ui/react-tooltip" + "@radix-ui/react-tooltip", + "prettier" ], "next": true, "project": ["src/**"] diff --git a/apps/playground-web/src/app/insight/[blueprint_slug]/blueprint-playground.client.tsx b/apps/playground-web/src/app/insight/[blueprint_slug]/blueprint-playground.client.tsx index ac91fa43506..b86c2d108d1 100644 --- a/apps/playground-web/src/app/insight/[blueprint_slug]/blueprint-playground.client.tsx +++ b/apps/playground-web/src/app/insight/[blueprint_slug]/blueprint-playground.client.tsx @@ -17,7 +17,7 @@ import { useForm, } from "react-hook-form"; import { z } from "zod"; -import { CodeClient, CodeLoading } from "@/components/code/code.client"; +import { CodeClient } from "@/components/code/code.client"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form"; @@ -724,7 +724,6 @@ function ResponseSection(props: { className="rounded-none border-none bg-transparent" code={formattedData || ""} lang="json" - loader={} scrollableClassName="h-full" scrollableContainerClassName="h-full" // shadowColor="hsl(var(--muted)/50%)" diff --git a/apps/playground-web/src/app/payments/components/CodeGen.tsx b/apps/playground-web/src/app/payments/components/CodeGen.tsx index 70f46c55afc..7e7da112df8 100644 --- a/apps/playground-web/src/app/payments/components/CodeGen.tsx +++ b/apps/playground-web/src/app/payments/components/CodeGen.tsx @@ -1,19 +1,26 @@ import { lazy, Suspense } from "react"; -import { CodeLoading } from "../../../components/code/code.client"; +import { LoadingDots } from "../../../components/ui/LoadingDots"; import type { BridgeComponentsPlaygroundOptions } from "./types"; -const CodeClient = lazy(() => import("../../../components/code/code.client")); +const CodeClient = lazy(() => + import("../../../components/code/code.client").then((m) => ({ + default: m.CodeClient, + })), +); + +function CodeLoading() { + return ( +
+ +
+ ); +} export function CodeGen(props: { options: BridgeComponentsPlaygroundOptions }) { return (
}> - } - /> +
); diff --git a/apps/playground-web/src/app/transactions/airdrop-tokens/_components/airdrop-code.tsx b/apps/playground-web/src/app/transactions/airdrop-tokens/_components/airdrop-code.tsx index 5bbb0611d9e..2e11ad3deb2 100644 --- a/apps/playground-web/src/app/transactions/airdrop-tokens/_components/airdrop-code.tsx +++ b/apps/playground-web/src/app/transactions/airdrop-tokens/_components/airdrop-code.tsx @@ -1,4 +1,4 @@ -import { Code } from "../../../../components/code/code"; +import { CodeServer } from "@/components/code/code"; import { airdropExample } from "../constants"; export function AirdropCode() { @@ -15,7 +15,7 @@ export function AirdropCode() {

Send Airdrop Transaction Request

- +
@@ -28,7 +28,7 @@ export function AirdropCode() { of the transaction using the following code.

- + ); } diff --git a/apps/playground-web/src/app/transactions/mint-tokens/_components/mint-code.tsx b/apps/playground-web/src/app/transactions/mint-tokens/_components/mint-code.tsx index fc378086fcd..bbd004b5339 100644 --- a/apps/playground-web/src/app/transactions/mint-tokens/_components/mint-code.tsx +++ b/apps/playground-web/src/app/transactions/mint-tokens/_components/mint-code.tsx @@ -1,4 +1,4 @@ -import { Code } from "../../../../components/code/code"; +import { CodeServer } from "@/components/code/code"; import { mintExample } from "../constants"; export function MintCode() { @@ -15,7 +15,7 @@ export function MintCode() {

Send Transaction Request to Mint Dynamic NFTs

- +
@@ -28,7 +28,7 @@ export function MintCode() { the transaction using the following code.

- + ); } diff --git a/apps/playground-web/src/app/transactions/webhooks/_components/webhooks-preview.tsx b/apps/playground-web/src/app/transactions/webhooks/_components/webhooks-preview.tsx index 4593a33c484..5f4bf2aed64 100644 --- a/apps/playground-web/src/app/transactions/webhooks/_components/webhooks-preview.tsx +++ b/apps/playground-web/src/app/transactions/webhooks/_components/webhooks-preview.tsx @@ -19,8 +19,7 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import CodeClient from "../../../../components/code/code.client"; -import { LoadingDots } from "../../../../components/ui/LoadingDots"; +import { CodeClient } from "../../../../components/code/code.client"; import { Label } from "../../../../components/ui/label"; import { Spinner } from "../../../../components/ui/Spinner"; import { THIRDWEB_CLIENT } from "../../../../lib/client"; @@ -147,11 +146,6 @@ export function EngineWebhooksPreview() { className="grow rounded-none border-none" code={JSON.stringify(engineTxStatusQuery.data, null, 2)} lang="json" - loader={ -
- -
- } scrollableContainerClassName="h-full" /> diff --git a/apps/playground-web/src/app/wallets/sign-in/components/CodeGen.tsx b/apps/playground-web/src/app/wallets/sign-in/components/CodeGen.tsx index 886e46cb7a9..408b14d8ce0 100644 --- a/apps/playground-web/src/app/wallets/sign-in/components/CodeGen.tsx +++ b/apps/playground-web/src/app/wallets/sign-in/components/CodeGen.tsx @@ -1,11 +1,21 @@ import { lazy, Suspense } from "react"; -import { CodeLoading } from "../../../../components/code/code.client"; +import { LoadingDots } from "../../../../components/ui/LoadingDots"; import type { ConnectPlaygroundOptions } from "./types"; -const CodeClient = lazy( - () => import("../../../../components/code/code.client"), +const CodeClient = lazy(() => + import("../../../../components/code/code.client").then((m) => ({ + default: m.CodeClient, + })), ); +function CodeLoading() { + return ( +
+ +
+ ); +} + export function CodeGen(props: { connectOptions: ConnectPlaygroundOptions }) { return (
@@ -14,8 +24,6 @@ export function CodeGen(props: { connectOptions: ConnectPlaygroundOptions }) { className="xl:h-[calc(100vh-100px)]" code={getCode(props.connectOptions)} lang="tsx" - // Need to add max-h in both places - TODO figure out a better way - loader={} scrollableClassName="xl:h-[calc(100vh-100px)]" /> diff --git a/apps/playground-web/src/components/account-abstraction/5792-get-capabilities.tsx b/apps/playground-web/src/components/account-abstraction/5792-get-capabilities.tsx index b8c76bdcdf6..bd8fe60bcd7 100644 --- a/apps/playground-web/src/components/account-abstraction/5792-get-capabilities.tsx +++ b/apps/playground-web/src/components/account-abstraction/5792-get-capabilities.tsx @@ -8,7 +8,7 @@ import { } from "thirdweb/react"; import { createWallet, inAppWallet } from "thirdweb/wallets"; import { THIRDWEB_CLIENT } from "../../lib/client"; -import CodeClient from "../code/code.client"; +import { CodeClient } from "../code/code.client"; export function Eip5792GetCapabilitiesPreview() { const capabilities = useCapabilities(); @@ -48,7 +48,6 @@ export function Eip5792GetCapabilitiesPreview() { className="max-h-[500px] w-[400px] overflow-y-auto" code={JSON.stringify(capabilities.data, null, 2)} lang="json" - loader={
Loading...
} scrollableClassName="h-full" scrollableContainerClassName="h-full" /> diff --git a/apps/playground-web/src/components/code/RenderCode.tsx b/apps/playground-web/src/components/code/RenderCode.tsx deleted file mode 100644 index eb25d85786e..00000000000 --- a/apps/playground-web/src/components/code/RenderCode.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { cn } from "../../lib/utils"; -import { CopyButton } from "../ui/CopyButton"; -import { ScrollShadow } from "../ui/ScrollShadow"; - -export function RenderCode(props: { - code: string; - html: string; - className?: string; - scrollableClassName?: string; - scrollableContainerClassName?: string; -}) { - return ( -
- -
- - -
- ); -} diff --git a/apps/playground-web/src/components/code/code-example.tsx b/apps/playground-web/src/components/code/code-example.tsx index c102de5635d..1a196550de5 100644 --- a/apps/playground-web/src/components/code/code-example.tsx +++ b/apps/playground-web/src/components/code/code-example.tsx @@ -2,7 +2,7 @@ import { Code2Icon, EyeIcon } from "lucide-react"; import type { JSX } from "react"; import type { BundledLanguage } from "shiki"; import { ClientOnly } from "../ClientOnly"; -import { Code } from "./code"; +import { CodeServer } from "./code"; type CodeExampleProps = { preview: JSX.Element; @@ -35,7 +35,7 @@ export const CodeExample: React.FC = ({
- - -
- ); -} - -export const CodeClient: React.FC = ({ - code, - lang, - loader, - className, - scrollableClassName, - scrollableContainerClassName, -}) => { - const codeQuery = useQuery({ - placeholderData: keepPreviousData, - queryFn: () => getCodeHtml(code, lang), - queryKey: ["html", code], - }); - - if (!codeQuery.data) { - return loader; - } - - return ( - - ); -}; - -/** @alias */ -export default CodeClient; +export { CodeClient } from "@workspace/ui/components/code/code.client"; diff --git a/apps/playground-web/src/components/code/code.tsx b/apps/playground-web/src/components/code/code.tsx index 1c87c58ee32..84234ad2a77 100644 --- a/apps/playground-web/src/components/code/code.tsx +++ b/apps/playground-web/src/components/code/code.tsx @@ -1,27 +1 @@ -import type { BundledLanguage } from "shiki"; -import { getCodeHtml } from "./getCodeHtml"; -import { RenderCode } from "./RenderCode"; - -type CodeProps = { - code: string; - lang: BundledLanguage; - className?: string; - scrollableClassName?: string; -}; - -export const Code: React.FC = async ({ - code, - lang, - className, - scrollableClassName, -}) => { - const { html, formattedCode } = await getCodeHtml(code, lang); - return ( - - ); -}; +export { CodeServer } from "@workspace/ui/components/code/code.server"; diff --git a/apps/playground-web/src/components/code/getCodeHtml.tsx b/apps/playground-web/src/components/code/getCodeHtml.tsx deleted file mode 100644 index aa4e06784a5..00000000000 --- a/apps/playground-web/src/components/code/getCodeHtml.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import * as parserBabel from "prettier/plugins/babel"; -import { format } from "prettier/standalone"; -import { type BundledLanguage, codeToHtml } from "shiki"; - -function isPrettierSupportedLang(lang: BundledLanguage) { - return ( - lang === "js" || - lang === "jsx" || - lang === "ts" || - lang === "tsx" || - lang === "javascript" || - lang === "typescript" - ); -} - -export async function getCodeHtml(code: string, lang: BundledLanguage) { - const estreePlugin = await import("prettier/plugins/estree"); - - const formattedCode = isPrettierSupportedLang(lang) - ? await format(code, { - parser: "babel-ts", - plugins: [parserBabel, estreePlugin.default], - printWidth: 80, - }).catch(() => { - return code; - }) - : code; - - const html = await codeToHtml(formattedCode, { - lang: lang, - themes: { - dark: "github-dark-default", - light: "github-light", - }, - }); - - return { formattedCode, html }; -} diff --git a/apps/playground-web/src/components/in-app-wallet/profiles.tsx b/apps/playground-web/src/components/in-app-wallet/profiles.tsx index 329331f8644..74b803a8f19 100644 --- a/apps/playground-web/src/components/in-app-wallet/profiles.tsx +++ b/apps/playground-web/src/components/in-app-wallet/profiles.tsx @@ -3,7 +3,7 @@ import { baseSepolia } from "thirdweb/chains"; import { useActiveAccount, useLinkProfile, useProfiles } from "thirdweb/react"; import { createWallet, type WalletId } from "thirdweb/wallets"; import { THIRDWEB_CLIENT } from "../../lib/client"; -import CodeClient, { CodeLoading } from "../code/code.client"; +import { CodeClient } from "../code/code.client"; import { StyledConnectButton } from "../styled-connect-button"; import { Button } from "../ui/button"; @@ -20,7 +20,6 @@ export function LinkedAccounts() { } />
) : ( diff --git a/apps/playground-web/src/components/ui/markdown-renderer.tsx b/apps/playground-web/src/components/ui/markdown-renderer.tsx index e8f79a0be3f..ad83bbaedfe 100644 --- a/apps/playground-web/src/components/ui/markdown-renderer.tsx +++ b/apps/playground-web/src/components/ui/markdown-renderer.tsx @@ -13,7 +13,7 @@ import { TableRow, } from "@/components/ui/table"; import { cn } from "@/lib/utils"; -import CodeClient from "../code/code.client"; +import { CodeClient } from "../code/code.client"; export const MarkdownRenderer: React.FC<{ markdownText: string; diff --git a/apps/portal/src/components/Document/APIEndpointMeta/DynamicRequestExample.tsx b/apps/portal/src/components/Document/APIEndpointMeta/DynamicRequestExample.tsx index 5ae508c515c..b3f10319bd9 100644 --- a/apps/portal/src/components/Document/APIEndpointMeta/DynamicRequestExample.tsx +++ b/apps/portal/src/components/Document/APIEndpointMeta/DynamicRequestExample.tsx @@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useEffect, useState } from "react"; -import CodeClient, { CodeLoading } from "../../code/code.client"; +import { CodeClient } from "../../code/code.client"; import { Details } from "../Details"; import type { APIParameter } from "./ApiEndpoint"; import { RequestExample } from "./RequestExample"; @@ -59,7 +59,6 @@ function InlineParameterItem({ param }: { param: APIParameter }) { } className="rounded-none border-none" lang="json" - loader={} scrollableContainerClassName="m-0" scrollableClassName="max-h-[200px]" /> @@ -146,7 +145,6 @@ export function DynamicRequestExample(props: DynamicRequestExampleProps) { } scrollableContainerClassName="m-0" lang={example.lang} /> @@ -166,7 +164,6 @@ export function DynamicRequestExample(props: DynamicRequestExampleProps) { } scrollableContainerClassName="m-0" lang={selectedExample.lang} /> diff --git a/apps/portal/src/components/Document/AuthMethodsTabs.tsx b/apps/portal/src/components/Document/AuthMethodsTabs.tsx index c0fd3fec1ab..1aa37d7032e 100644 --- a/apps/portal/src/components/Document/AuthMethodsTabs.tsx +++ b/apps/portal/src/components/Document/AuthMethodsTabs.tsx @@ -15,7 +15,7 @@ import { UnrealEngineIcon, } from "@/icons"; import { cn } from "@/lib/utils"; -import { CodeClient, CodeLoading } from "../code/code.client"; +import { CodeClient } from "../code/code.client"; type AuthMethod = | "email" @@ -927,7 +927,6 @@ function AuthMethodsTabsContent() { ? "csharp" : "typescript" } - loader={} className="text-sm" /> ), diff --git a/apps/portal/src/components/code/CodeBlockContainer.tsx b/apps/portal/src/components/code/CodeBlockContainer.tsx deleted file mode 100644 index 90fec3dae8b..00000000000 --- a/apps/portal/src/components/code/CodeBlockContainer.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client"; - -import { CheckIcon, CopyIcon } from "lucide-react"; -import { ScrollShadow } from "@/components/ui/ScrollShadow"; // Adjusted path for portal -import { useClipboard } from "@/hooks/useClipboard"; // Adjusted path for portal -import { cn } from "@/lib/utils"; -import { Button } from "../ui/button"; // Adjusted path for portal - -export function CodeBlockContainer(props: { - codeToCopy: string; - children: React.ReactNode; - className?: string; - scrollableClassName?: string; - scrollableContainerClassName?: string; - copyButtonClassName?: string; - shadowColor?: string; - onCopy?: (code: string) => void; -}) { - const { hasCopied, onCopy: onClipboardCopy } = useClipboard(props.codeToCopy); // Renamed onCopy to avoid conflict - - return ( -
- - {props.children} - - - -
- ); -} diff --git a/apps/portal/src/components/code/RenderCode.tsx b/apps/portal/src/components/code/RenderCode.tsx deleted file mode 100644 index 5b423ad6288..00000000000 --- a/apps/portal/src/components/code/RenderCode.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { ScrollShadow } from "@/components/ui/ScrollShadow"; -import { cn } from "../../lib/utils"; -import { CopyButton } from "../ui/CopyButton"; - -export function RenderCode(props: { - code: string; - html: string; - className?: string; - scrollableClassName?: string; - scrollableContainerClassName?: string; -}) { - return ( -
- -
- - -
- ); -} diff --git a/apps/portal/src/components/code/code.client.tsx b/apps/portal/src/components/code/code.client.tsx index e0ed9789863..89767cbe17c 100644 --- a/apps/portal/src/components/code/code.client.tsx +++ b/apps/portal/src/components/code/code.client.tsx @@ -1,57 +1,6 @@ -import { keepPreviousData, useQuery } from "@tanstack/react-query"; -import type { BundledLanguage } from "shiki"; -import { LoadingDots } from "../ui/LoadingDots"; -import { getCodeHtml } from "./getCodeHtml"; -import { RenderCode } from "./RenderCode"; +"use client"; -// Use CodeClient where the code changes based user input -// Using RSC in that scenario feels too slow and unnecessary keep hitting the server - -type CodeProps = { - code: string; - lang: BundledLanguage | string | undefined | null; - loader: React.ReactNode; - className?: string; - scrollableClassName?: string; - scrollableContainerClassName?: string; -}; - -export function CodeLoading() { - return ( -
- -
- ); -} - -export const CodeClient: React.FC = ({ - code, - lang, - loader, - className, - scrollableClassName, - scrollableContainerClassName, -}) => { - const codeQuery = useQuery({ - placeholderData: keepPreviousData, - queryFn: () => getCodeHtml(code, lang), - queryKey: ["html", code], - }); - - if (!codeQuery.data) { - return loader; - } - - return ( - - ); -}; - -/** @alias */ -export default CodeClient; +export { + CodeClient, + type CodeProps, +} from "@workspace/ui/components/code/code.client"; diff --git a/apps/portal/src/components/code/getCodeHtml.tsx b/apps/portal/src/components/code/getCodeHtml.tsx deleted file mode 100644 index f083aee0126..00000000000 --- a/apps/portal/src/components/code/getCodeHtml.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as parserBabel from "prettier/plugins/babel"; -import { format } from "prettier/standalone"; -import { type BundledLanguage, codeToHtml } from "shiki"; - -function isPrettierSupportedLang( - lang: BundledLanguage | string | undefined | null, -) { - if (!lang) { - return false; - } - - return ( - lang === "js" || - lang === "jsx" || - lang === "ts" || - lang === "tsx" || - lang === "javascript" || - lang === "typescript" - ); -} - -export async function getCodeHtml( - code: string, - lang: BundledLanguage | string | undefined | null, -) { - const estreePlugin = await import("prettier/plugins/estree"); - - const formattedCode = isPrettierSupportedLang(lang) - ? await format(code, { - parser: "babel-ts", - plugins: [parserBabel, estreePlugin.default], - printWidth: 80, - }).catch(() => { - return code; - }) - : code; - - const html = await codeToHtml(formattedCode, { - lang: (lang || "javascript") as BundledLanguage, - themes: { - dark: "github-dark-default", - light: "github-light", - }, - }); - - return { formattedCode, html }; -} diff --git a/apps/portal/src/components/code/plaintext-code.tsx b/apps/portal/src/components/code/plaintext-code.tsx index f85a5099741..12a0e83743b 100644 --- a/apps/portal/src/components/code/plaintext-code.tsx +++ b/apps/portal/src/components/code/plaintext-code.tsx @@ -1,29 +1 @@ -import { cn } from "@/lib/utils"; // Adjusted path for portal -import { CodeBlockContainer } from "./CodeBlockContainer"; // Path within portal/components/code - -export function PlainTextCodeBlock(props: { - code: string; - copyButtonClassName?: string; - className?: string; - scrollableClassName?: string; - codeClassName?: string; - scrollableContainerClassName?: string; - shadowColor?: string; - onCopy?: (code: string) => void; -}) { - return ( - - - {props.code} - - - ); -} +export { PlainTextCodeBlock } from "@workspace/ui/components/code/plaintext-code"; diff --git a/apps/portal/src/components/markdown/MarkdownRenderer.tsx b/apps/portal/src/components/markdown/MarkdownRenderer.tsx index f97421bd289..a611fc9ef02 100644 --- a/apps/portal/src/components/markdown/MarkdownRenderer.tsx +++ b/apps/portal/src/components/markdown/MarkdownRenderer.tsx @@ -1,8 +1,7 @@ import { onlyText } from "react-children-utilities"; // Assuming this dependency is available import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; // Assuming this dependency is available -import type { BundledLanguage } from "shiki"; // For CodeClient lang prop -import { CodeClient, CodeLoading } from "@/components/code/code.client"; // Adjusted path for portal +import { CodeClient, type CodeProps } from "@/components/code/code.client"; // Adjusted path for portal import { PlainTextCodeBlock } from "@/components/code/plaintext-code"; // Adjusted path for portal import { InlineCode } from "@/components/ui/inline-code"; // Adjusted path for portal import { @@ -85,18 +84,14 @@ export const MarkdownRenderer: React.FC<{
); } - const language = inheritedClassName.replace( - "language-", - "", - ) as BundledLanguage; + const language = inheritedClassName.replace("language-", ""); + return (
} // Basic loader />
); diff --git a/apps/portal/src/components/ui/CopyButton.tsx b/apps/portal/src/components/ui/CopyButton.tsx deleted file mode 100644 index 933ded9f6c9..00000000000 --- a/apps/portal/src/components/ui/CopyButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; - -import { CheckIcon, CopyIcon } from "lucide-react"; -import { cn } from "@/lib/utils"; -import { useClipboard } from "../../hooks/useClipboard"; -import { Button } from "./button"; -import { ToolTipLabel } from "./tooltip"; - -export function CopyButton(props: { - text: string; - className?: string; - iconClassName?: string; - variant?: "ghost" | "primary" | "secondary"; -}) { - const { hasCopied, onCopy } = useClipboard(props.text, 1000); - return ( - - - - ); -} diff --git a/packages/ui/package.json b/packages/ui/package.json index b07a58afcb9..051da1fea0a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -11,7 +11,11 @@ "./src/hooks/*.ts", "./src/hooks/*.tsx" ], - "./components/*": "./src/components/*.tsx" + "./components/*": "./src/components/*.tsx", + "./storybook/*": [ + "./src/storybook/*.tsx", + "./src/storybook/*.ts" + ] }, "dependencies": { "@radix-ui/react-label": "^2.1.7", @@ -23,12 +27,15 @@ "lucide-react": "0.525.0", "next": "15.3.5", "next-themes": "^0.4.6", + "prettier": "3.6.2", + "shiki": "1.27.0", "sonner": "2.0.6", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7" }, "scripts": { - "storybook": "storybook dev -p 6006" + "storybook": "storybook dev -p 6006", + "typecheck": "tsc --noEmit" }, "peerDependencies": { "react": "^19.0.0", diff --git a/packages/ui/src/components/badge.tsx b/packages/ui/src/components/badge.tsx index c1f5874fe11..7825e467076 100644 --- a/packages/ui/src/components/badge.tsx +++ b/packages/ui/src/components/badge.tsx @@ -1,8 +1,7 @@ +import { cn } from "@workspace/ui/lib/utils"; import { cva, type VariantProps } from "class-variance-authority"; import type * as React from "react"; -import { cn } from "@/lib/utils"; - const badgeVariants = cva( "inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 leading-4", { diff --git a/packages/ui/src/components/breadcrumb.tsx b/packages/ui/src/components/breadcrumb.tsx index 60e4b7848a3..49ea7cde5c1 100644 --- a/packages/ui/src/components/breadcrumb.tsx +++ b/packages/ui/src/components/breadcrumb.tsx @@ -1,9 +1,8 @@ import { Slot } from "@radix-ui/react-slot"; +import { cn } from "@workspace/ui/lib/utils"; import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"; import * as React from "react"; -import { cn } from "@/lib/utils"; - const Breadcrumb = React.forwardRef< HTMLElement, React.ComponentPropsWithoutRef<"nav"> & { diff --git a/packages/ui/src/components/button.stories.tsx b/packages/ui/src/components/button.stories.tsx index c5da13a84ec..7b8052ca9ea 100644 --- a/packages/ui/src/components/button.stories.tsx +++ b/packages/ui/src/components/button.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/nextjs"; +import { Button } from "@workspace/ui/components/button"; +import { BadgeContainer } from "@workspace/ui/storybook/utils"; import { StarIcon } from "lucide-react"; -import { Button } from "@/components/button"; -import { BadgeContainer } from "@/storybook/utils"; const meta = { component: Component, diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx index bc6b1f07433..0510f6b09d3 100644 --- a/packages/ui/src/components/button.tsx +++ b/packages/ui/src/components/button.tsx @@ -1,7 +1,7 @@ import { Slot } from "@radix-ui/react-slot"; +import { cn } from "@workspace/ui/lib/utils"; import { cva, type VariantProps } from "class-variance-authority"; import * as React from "react"; -import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", diff --git a/apps/dashboard/src/@/components/ui/code/CodeBlockContainer.tsx b/packages/ui/src/components/code/CodeBlockContainer.tsx similarity index 87% rename from apps/dashboard/src/@/components/ui/code/CodeBlockContainer.tsx rename to packages/ui/src/components/code/CodeBlockContainer.tsx index 9aa752d9f4b..420766225c1 100644 --- a/apps/dashboard/src/@/components/ui/code/CodeBlockContainer.tsx +++ b/packages/ui/src/components/code/CodeBlockContainer.tsx @@ -1,10 +1,10 @@ "use client"; +import { useClipboard } from "@workspace/ui/hooks/useClipboard"; +import { cn } from "@workspace/ui/lib/utils"; import { CheckIcon, CopyIcon } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { ScrollShadow } from "@/components/ui/ScrollShadow"; -import { useClipboard } from "@/hooks/useClipboard"; -import { cn } from "@/lib/utils"; +import { Button } from "../button"; +import { ScrollShadow } from "../scroll-shadow"; export function CodeBlockContainer(props: { codeToCopy: string; diff --git a/apps/dashboard/src/@/components/ui/code/RenderCode.tsx b/packages/ui/src/components/code/RenderCode.tsx similarity index 100% rename from apps/dashboard/src/@/components/ui/code/RenderCode.tsx rename to packages/ui/src/components/code/RenderCode.tsx diff --git a/packages/ui/src/components/code/code.client.tsx b/packages/ui/src/components/code/code.client.tsx new file mode 100644 index 00000000000..a56b4ba6543 --- /dev/null +++ b/packages/ui/src/components/code/code.client.tsx @@ -0,0 +1,71 @@ +"use client"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import type { BundledLanguage } from "shiki"; +import { getCodeHtml } from "./getCodeHtml"; +import { PlainTextCodeBlock } from "./plaintext-code"; +import { RenderCode } from "./RenderCode"; + +export type CodeProps = { + code: string; + lang: BundledLanguage; + className?: string; + scrollableClassName?: string; + keepPreviousDataOnCodeChange?: boolean; + copyButtonClassName?: string; + scrollableContainerClassName?: string; + shadowColor?: string; + ignoreFormattingErrors?: boolean; + onCopy?: (code: string) => void; +}; + +export const CodeClient: React.FC = ({ + code, + lang, + className, + scrollableClassName, + keepPreviousDataOnCodeChange = false, + copyButtonClassName, + ignoreFormattingErrors, + scrollableContainerClassName, + shadowColor, + onCopy, +}) => { + const codeQuery = useQuery({ + placeholderData: keepPreviousDataOnCodeChange + ? keepPreviousData + : undefined, + queryFn: () => + getCodeHtml(code, lang, { + ignoreFormattingErrors: ignoreFormattingErrors, + }), + queryKey: ["html", code], + retry: false, + }); + + if (!codeQuery.data) { + return ( + + ); + } + + return ( + + ); +}; diff --git a/packages/ui/src/components/code/code.server.tsx b/packages/ui/src/components/code/code.server.tsx new file mode 100644 index 00000000000..82a87a4e8d0 --- /dev/null +++ b/packages/ui/src/components/code/code.server.tsx @@ -0,0 +1,22 @@ +import type { BundledLanguage } from "shiki"; +import { getCodeHtml } from "./getCodeHtml"; +import { RenderCode } from "./RenderCode"; + +export type CodeProps = { + code: string; + lang: BundledLanguage; + className?: string; + ignoreFormattingErrors?: boolean; +}; + +export const CodeServer: React.FC = async ({ + code, + lang, + className, + ignoreFormattingErrors, +}) => { + const { html, formattedCode } = await getCodeHtml(code, lang, { + ignoreFormattingErrors, + }); + return ; +}; diff --git a/apps/dashboard/src/@/components/ui/code/code.stories.tsx b/packages/ui/src/components/code/code.stories.tsx similarity index 96% rename from apps/dashboard/src/@/components/ui/code/code.stories.tsx rename to packages/ui/src/components/code/code.stories.tsx index 6c134227fc2..dbcf86e7d00 100644 --- a/apps/dashboard/src/@/components/ui/code/code.stories.tsx +++ b/packages/ui/src/components/code/code.stories.tsx @@ -1,11 +1,11 @@ import type { Meta, StoryObj } from "@storybook/nextjs"; -import { BadgeContainer } from "@/storybook/utils"; +import { BadgeContainer } from "@workspace/ui/storybook/utils"; import { CodeClient } from "./code.client"; const meta = { component: Component, parameters: {}, - title: "code/lang", + title: "ui/code/lang", } satisfies Meta; export default meta; diff --git a/apps/dashboard/src/@/components/ui/code/getCodeHtml.tsx b/packages/ui/src/components/code/getCodeHtml.tsx similarity index 100% rename from apps/dashboard/src/@/components/ui/code/getCodeHtml.tsx rename to packages/ui/src/components/code/getCodeHtml.tsx diff --git a/apps/dashboard/src/@/components/ui/code/plaintext-code.stories.tsx b/packages/ui/src/components/code/plaintext-code.stories.tsx similarity index 92% rename from apps/dashboard/src/@/components/ui/code/plaintext-code.stories.tsx rename to packages/ui/src/components/code/plaintext-code.stories.tsx index 00064cba244..a57249d3345 100644 --- a/apps/dashboard/src/@/components/ui/code/plaintext-code.stories.tsx +++ b/packages/ui/src/components/code/plaintext-code.stories.tsx @@ -1,11 +1,11 @@ import type { Meta, StoryObj } from "@storybook/nextjs"; -import { BadgeContainer } from "@/storybook/utils"; +import { BadgeContainer } from "@workspace/ui/storybook/utils"; import { PlainTextCodeBlock } from "./plaintext-code"; const meta = { component: Component, parameters: {}, - title: "code/plaintext", + title: "ui/code/plaintext", } satisfies Meta; export default meta; diff --git a/packages/ui/src/components/code/plaintext-code.tsx b/packages/ui/src/components/code/plaintext-code.tsx new file mode 100644 index 00000000000..b48eb11820e --- /dev/null +++ b/packages/ui/src/components/code/plaintext-code.tsx @@ -0,0 +1,29 @@ +import { cn } from "@workspace/ui/lib/utils"; +import { CodeBlockContainer } from "./CodeBlockContainer"; + +export function PlainTextCodeBlock(props: { + code: string; + copyButtonClassName?: string; + className?: string; + scrollableClassName?: string; + codeClassName?: string; + scrollableContainerClassName?: string; + shadowColor?: string; + onCopy?: (code: string) => void; +}) { + return ( + + + {props.code} + + + ); +} diff --git a/packages/ui/src/components/img.stories.tsx b/packages/ui/src/components/img.stories.tsx index f22d14f17fe..243172d0306 100644 --- a/packages/ui/src/components/img.stories.tsx +++ b/packages/ui/src/components/img.stories.tsx @@ -1,8 +1,8 @@ import type { Meta, StoryObj } from "@storybook/nextjs"; +import { Button } from "@workspace/ui/components/button"; +import { BadgeContainer } from "@workspace/ui/storybook/utils"; import { ImageIcon, Loader2 } from "lucide-react"; import { useState } from "react"; -import { Button } from "@/components/button"; -import { BadgeContainer } from "@/storybook/utils"; import { Img } from "./img"; const meta = { diff --git a/packages/ui/src/components/img.tsx b/packages/ui/src/components/img.tsx index a7d5ec44fef..4a31e260994 100644 --- a/packages/ui/src/components/img.tsx +++ b/packages/ui/src/components/img.tsx @@ -1,6 +1,6 @@ "use client"; +import { cn } from "@workspace/ui/lib/utils"; import { useLayoutEffect, useRef, useState } from "react"; -import { cn } from "@/lib/utils"; type imgElementProps = React.DetailedHTMLProps< React.ImgHTMLAttributes, diff --git a/packages/ui/src/components/input.tsx b/packages/ui/src/components/input.tsx index 465f5065cc7..73c16bed777 100644 --- a/packages/ui/src/components/input.tsx +++ b/packages/ui/src/components/input.tsx @@ -1,5 +1,5 @@ +import { cn } from "@workspace/ui/lib/utils"; import * as React from "react"; -import { cn } from "@/lib/utils"; export interface InputProps extends React.InputHTMLAttributes {} diff --git a/packages/ui/src/components/scroll-shadow.tsx b/packages/ui/src/components/scroll-shadow.tsx index 4a1b6919637..6a6e2bfae72 100644 --- a/packages/ui/src/components/scroll-shadow.tsx +++ b/packages/ui/src/components/scroll-shadow.tsx @@ -1,7 +1,7 @@ "use client"; +import { cn } from "@workspace/ui/lib/utils"; import { useLayoutEffect, useRef } from "react"; -import { cn } from "@/lib/utils"; export function ScrollShadow(props: { children: React.ReactNode; diff --git a/packages/ui/src/components/spinner.tsx b/packages/ui/src/components/spinner.tsx index 59d26e79e9e..eb51b6c52e1 100644 --- a/packages/ui/src/components/spinner.tsx +++ b/packages/ui/src/components/spinner.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils"; +import { cn } from "@workspace/ui/lib/utils"; export function Spinner(props: { className?: string }) { return ( diff --git a/packages/ui/src/components/table.stories.tsx b/packages/ui/src/components/table.stories.tsx index c6a8acfed29..54c4d43e700 100644 --- a/packages/ui/src/components/table.stories.tsx +++ b/packages/ui/src/components/table.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/nextjs"; +import { cn } from "@workspace/ui/lib/utils"; +import { BadgeContainer } from "@workspace/ui/storybook/utils"; import Link from "next/link"; -import { cn } from "@/lib/utils"; -import { BadgeContainer } from "@/storybook/utils"; import { Badge } from "./badge"; import { Table, diff --git a/packages/ui/src/components/table.tsx b/packages/ui/src/components/table.tsx index cd575926071..61bea7750a6 100644 --- a/packages/ui/src/components/table.tsx +++ b/packages/ui/src/components/table.tsx @@ -1,5 +1,5 @@ +import { cn } from "@workspace/ui/lib/utils"; import * as React from "react"; -import { cn } from "@/lib/utils"; import { ScrollShadow } from "./scroll-shadow"; const Table = React.forwardRef< diff --git a/packages/ui/src/components/tooltip.tsx b/packages/ui/src/components/tooltip.tsx index 687bfe4fac7..b03614e0d75 100644 --- a/packages/ui/src/components/tooltip.tsx +++ b/packages/ui/src/components/tooltip.tsx @@ -1,10 +1,9 @@ "use client"; import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { cn } from "@workspace/ui/lib/utils"; import * as React from "react"; -import { cn } from "@/lib/utils"; - const TooltipProvider = TooltipPrimitive.Provider; const Tooltip = TooltipPrimitive.Root; diff --git a/apps/portal/src/hooks/useClipboard.ts b/packages/ui/src/hooks/useClipboard.ts similarity index 100% rename from apps/portal/src/hooks/useClipboard.ts rename to packages/ui/src/hooks/useClipboard.ts diff --git a/packages/ui/src/storybook/utils.tsx b/packages/ui/src/storybook/utils.tsx index 9915d675562..6b9939579ec 100644 --- a/packages/ui/src/storybook/utils.tsx +++ b/packages/ui/src/storybook/utils.tsx @@ -1,4 +1,4 @@ -import { Badge } from "@/components/badge"; +import { Badge } from "@workspace/ui/components/badge"; function StoryBadge(props: { label: string }) { return ( diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 59179be8a91..6f1963e3014 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -18,10 +18,7 @@ "noUncheckedIndexedAccess": true, "resolveJsonModule": true, "baseUrl": ".", - "jsx": "preserve", - "paths": { - "@/*": ["./src/*"] - } + "jsx": "preserve" }, "include": ["src"], "exclude": ["node_modules"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eea6f78e480..7201bdc4fe2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1504,12 +1504,18 @@ importers: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + prettier: + specifier: 3.6.2 + version: 3.6.2 react: specifier: ^19.0.0 version: 19.1.0 react-dom: specifier: ^19.0.0 version: 19.1.0(react@19.1.0) + shiki: + specifier: 1.27.0 + version: 1.27.0 sonner: specifier: 2.0.6 version: 2.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)