feat(www): code blocks package manager (#6075)

* feat(www): code blocks

* fix: code style
This commit is contained in:
shadcn
2024-12-14 19:13:06 +04:00
committed by GitHub
parent 102b0b0c62
commit 2f869a2590
7 changed files with 159 additions and 26 deletions

View File

@@ -0,0 +1,108 @@
"use client"
import * as React from "react"
import { CheckIcon, ClipboardIcon } from "lucide-react"
import { NpmCommands } from "@/types/unist"
import { useConfig } from "@/hooks/use-config"
import { copyToClipboardWithMeta } from "@/components/copy-button"
import { Tabs } from "@/registry/default/ui/tabs"
import { Button } from "@/registry/new-york/ui/button"
import { TabsContent, TabsList, TabsTrigger } from "@/registry/new-york/ui/tabs"
export function CodeBlockCommand({
__npmCommand__,
__yarnCommand__,
__pnpmCommand__,
__bunCommand__,
}: React.ComponentProps<"pre"> & NpmCommands) {
const [config, setConfig] = useConfig()
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
if (hasCopied) {
const timer = setTimeout(() => setHasCopied(false), 2000)
return () => clearTimeout(timer)
}
}, [hasCopied])
const packageManager = config.packageManager || "pnpm"
const tabs = React.useMemo(() => {
return {
pnpm: __pnpmCommand__,
npm: __npmCommand__,
yarn: __yarnCommand__,
bun: __bunCommand__,
}
}, [__npmCommand__, __pnpmCommand__, __yarnCommand__, __bunCommand__])
const copyCommand = React.useCallback(() => {
const command = tabs[packageManager]
if (!command) {
return
}
copyToClipboardWithMeta(command, {
name: "copy_npm_command",
properties: {
command,
pm: packageManager,
},
})
setHasCopied(true)
}, [packageManager, tabs])
return (
<div className="relative mt-6 max-h-[650px] overflow-x-auto rounded-xl bg-zinc-950 dark:bg-zinc-900">
<Tabs
defaultValue={packageManager}
onValueChange={(value) => {
setConfig({
...config,
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
})
}}
>
<div className="flex items-center justify-between border-b border-zinc-800 bg-zinc-900 px-3 pt-2.5">
<TabsList className="h-7 translate-y-[2px] gap-3 bg-transparent p-0 pl-1">
{Object.entries(tabs).map(([key, value]) => {
return (
<TabsTrigger
key={key}
value={key}
className="rounded-none border-b border-transparent bg-transparent p-0 pb-1.5 font-mono text-zinc-400 data-[state=active]:border-b-zinc-50 data-[state=active]:bg-transparent data-[state=active]:text-zinc-50"
>
{key}
</TabsTrigger>
)
})}
</TabsList>
</div>
{Object.entries(tabs).map(([key, value]) => {
return (
<TabsContent key={key} value={key} className="mt-0">
<pre className="px-4 py-5">
<code
className="relative font-mono text-sm leading-none"
data-language="bash"
>
{value}
</code>
</pre>
</TabsContent>
)
})}
</Tabs>
<Button
size="icon"
variant="ghost"
className="absolute right-2.5 top-2 z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:h-3 [&_svg]:w-3"
onClick={copyCommand}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
</div>
)
}

View File

@@ -11,6 +11,7 @@ import { Event } from "@/lib/events"
import { cn } from "@/lib/utils"
import { useConfig } from "@/hooks/use-config"
import { Callout } from "@/components/callout"
import { CodeBlockCommand } from "@/components/code-block-command"
import { CodeBlockWrapper } from "@/components/code-block-wrapper"
import { ComponentExample } from "@/components/component-example"
import { ComponentPreview } from "@/components/component-preview"
@@ -192,16 +193,30 @@ const components = {
__src__?: string
__event__?: Event["name"]
} & NpmCommands) => {
const isNpmCommand =
__npmCommand__ && __yarnCommand__ && __pnpmCommand__ && __bunCommand__
if (isNpmCommand) {
return (
<CodeBlockCommand
__npmCommand__={__npmCommand__}
__yarnCommand__={__yarnCommand__}
__pnpmCommand__={__pnpmCommand__}
__bunCommand__={__bunCommand__}
/>
)
}
return (
<StyleWrapper styleName={__style__}>
<pre
className={cn(
"mb-4 mt-6 max-h-[650px] overflow-x-auto rounded-lg border bg-zinc-950 py-4 dark:bg-zinc-900",
"mb-4 mt-6 max-h-[650px] overflow-x-auto rounded-xl bg-zinc-950 py-4 dark:bg-zinc-900",
className
)}
{...props}
/>
{__rawString__ && !__npmCommand__ && (
{__rawString__ && (
<CopyButton
value={__rawString__}
src={__src__}
@@ -209,20 +224,6 @@ const components = {
className={cn("absolute right-4 top-4", __withMeta__ && "top-16")}
/>
)}
{__npmCommand__ &&
__yarnCommand__ &&
__pnpmCommand__ &&
__bunCommand__ && (
<CopyNpmCommandButton
commands={{
__npmCommand__,
__yarnCommand__,
__pnpmCommand__,
__bunCommand__,
}}
className={cn("absolute right-4 top-4", __withMeta__ && "top-16")}
/>
)}
</StyleWrapper>
)
},

View File

@@ -47,7 +47,7 @@ Do you want to use CSS variables for colors? no / yes
### Install Tailwind CSS
```bash
npm add -D tailwindcss@latest autoprefixer@latest
npm install -D tailwindcss@latest autoprefixer@latest
```
Then we create a `postcss.config.js` file:

View File

@@ -19,7 +19,9 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
```bash
npm install -D tailwindcss postcss autoprefixer
```
```bash
npx tailwindcss init -p
```
@@ -93,11 +95,10 @@ Add the following code to the `tsconfig.app.json` file to resolve paths, for you
### Update vite.config.ts
Add the following code to the vite.config.ts so your app can resolve paths without error
Add the following code to the vite.config.ts so your app can resolve paths without error:
```bash
# (so you can import "path" without error)
npm i -D @types/node
npm install -D @types/node
```
```typescript

View File

@@ -8,12 +8,14 @@ type Config = {
style: Style["name"]
theme: BaseColor["name"]
radius: number
packageManager: "npm" | "yarn" | "pnpm" | "bun"
}
const configAtom = atomWithStorage<Config>("config", {
style: "new-york",
theme: "zinc",
radius: 0.5,
packageManager: "pnpm",
})
export function useConfig() {

View File

@@ -37,11 +37,14 @@ export function rehypeComponent() {
} else {
const component = Index[style.name][name]
src = fileName
? component.files.find((file: string) => {
return (
file.endsWith(`${fileName}.tsx`) ||
file.endsWith(`${fileName}.ts`)
)
? component.files.find((file: unknown) => {
if (typeof file === "string") {
return (
file.endsWith(`${fileName}.tsx`) ||
file.endsWith(`${fileName}.ts`)
)
}
return false
}) || component.files[0]?.path
: component.files[0]?.path
}

View File

@@ -26,7 +26,7 @@ export function rehypeNpmCommand() {
)
}
// npx create.
// npx create-.
if (node.properties?.["__rawString__"]?.startsWith("npx create-")) {
const npmCommand = node.properties?.["__rawString__"]
node.properties["__npmCommand__"] = npmCommand
@@ -44,6 +44,24 @@ export function rehypeNpmCommand() {
)
}
// npm create.
if (node.properties?.["__rawString__"]?.startsWith("npm create")) {
const npmCommand = node.properties?.["__rawString__"]
node.properties["__npmCommand__"] = npmCommand
node.properties["__yarnCommand__"] = npmCommand.replace(
"npm create",
"yarn create"
)
node.properties["__pnpmCommand__"] = npmCommand.replace(
"npm create",
"pnpm create"
)
node.properties["__bunCommand__"] = npmCommand.replace(
"npm create",
"bun create"
)
}
// npx.
if (
node.properties?.["__rawString__"]?.startsWith("npx") &&