Skip to content

Commit a8a69f8

Browse files
authored
feat(www): code blocks package manager (shadcn-ui#6075)
* feat(www): code blocks * fix: code style
1 parent 8429a1a commit a8a69f8

File tree

7 files changed

+159
-26
lines changed

7 files changed

+159
-26
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import { CheckIcon, ClipboardIcon } from "lucide-react"
5+
6+
import { NpmCommands } from "@/types/unist"
7+
import { useConfig } from "@/hooks/use-config"
8+
import { copyToClipboardWithMeta } from "@/components/copy-button"
9+
import { Tabs } from "@/registry/default/ui/tabs"
10+
import { Button } from "@/registry/new-york/ui/button"
11+
import { TabsContent, TabsList, TabsTrigger } from "@/registry/new-york/ui/tabs"
12+
13+
export function CodeBlockCommand({
14+
__npmCommand__,
15+
__yarnCommand__,
16+
__pnpmCommand__,
17+
__bunCommand__,
18+
}: React.ComponentProps<"pre"> & NpmCommands) {
19+
const [config, setConfig] = useConfig()
20+
const [hasCopied, setHasCopied] = React.useState(false)
21+
22+
React.useEffect(() => {
23+
if (hasCopied) {
24+
const timer = setTimeout(() => setHasCopied(false), 2000)
25+
return () => clearTimeout(timer)
26+
}
27+
}, [hasCopied])
28+
29+
const packageManager = config.packageManager || "pnpm"
30+
const tabs = React.useMemo(() => {
31+
return {
32+
pnpm: __pnpmCommand__,
33+
npm: __npmCommand__,
34+
yarn: __yarnCommand__,
35+
bun: __bunCommand__,
36+
}
37+
}, [__npmCommand__, __pnpmCommand__, __yarnCommand__, __bunCommand__])
38+
39+
const copyCommand = React.useCallback(() => {
40+
const command = tabs[packageManager]
41+
42+
if (!command) {
43+
return
44+
}
45+
46+
copyToClipboardWithMeta(command, {
47+
name: "copy_npm_command",
48+
properties: {
49+
command,
50+
pm: packageManager,
51+
},
52+
})
53+
setHasCopied(true)
54+
}, [packageManager, tabs])
55+
56+
return (
57+
<div className="relative mt-6 max-h-[650px] overflow-x-auto rounded-xl bg-zinc-950 dark:bg-zinc-900">
58+
<Tabs
59+
defaultValue={packageManager}
60+
onValueChange={(value) => {
61+
setConfig({
62+
...config,
63+
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
64+
})
65+
}}
66+
>
67+
<div className="flex items-center justify-between border-b border-zinc-800 bg-zinc-900 px-3 pt-2.5">
68+
<TabsList className="h-7 translate-y-[2px] gap-3 bg-transparent p-0 pl-1">
69+
{Object.entries(tabs).map(([key, value]) => {
70+
return (
71+
<TabsTrigger
72+
key={key}
73+
value={key}
74+
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"
75+
>
76+
{key}
77+
</TabsTrigger>
78+
)
79+
})}
80+
</TabsList>
81+
</div>
82+
{Object.entries(tabs).map(([key, value]) => {
83+
return (
84+
<TabsContent key={key} value={key} className="mt-0">
85+
<pre className="px-4 py-5">
86+
<code
87+
className="relative font-mono text-sm leading-none"
88+
data-language="bash"
89+
>
90+
{value}
91+
</code>
92+
</pre>
93+
</TabsContent>
94+
)
95+
})}
96+
</Tabs>
97+
<Button
98+
size="icon"
99+
variant="ghost"
100+
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"
101+
onClick={copyCommand}
102+
>
103+
<span className="sr-only">Copy</span>
104+
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
105+
</Button>
106+
</div>
107+
)
108+
}

apps/www/components/mdx-components.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Event } from "@/lib/events"
1111
import { cn } from "@/lib/utils"
1212
import { useConfig } from "@/hooks/use-config"
1313
import { Callout } from "@/components/callout"
14+
import { CodeBlockCommand } from "@/components/code-block-command"
1415
import { CodeBlockWrapper } from "@/components/code-block-wrapper"
1516
import { ComponentExample } from "@/components/component-example"
1617
import { ComponentPreview } from "@/components/component-preview"
@@ -192,37 +193,37 @@ const components = {
192193
__src__?: string
193194
__event__?: Event["name"]
194195
} & NpmCommands) => {
196+
const isNpmCommand =
197+
__npmCommand__ && __yarnCommand__ && __pnpmCommand__ && __bunCommand__
198+
199+
if (isNpmCommand) {
200+
return (
201+
<CodeBlockCommand
202+
__npmCommand__={__npmCommand__}
203+
__yarnCommand__={__yarnCommand__}
204+
__pnpmCommand__={__pnpmCommand__}
205+
__bunCommand__={__bunCommand__}
206+
/>
207+
)
208+
}
209+
195210
return (
196211
<StyleWrapper styleName={__style__}>
197212
<pre
198213
className={cn(
199-
"mb-4 mt-6 max-h-[650px] overflow-x-auto rounded-lg border bg-zinc-950 py-4 dark:bg-zinc-900",
214+
"mb-4 mt-6 max-h-[650px] overflow-x-auto rounded-xl bg-zinc-950 py-4 dark:bg-zinc-900",
200215
className
201216
)}
202217
{...props}
203218
/>
204-
{__rawString__ && !__npmCommand__ && (
219+
{__rawString__ && (
205220
<CopyButton
206221
value={__rawString__}
207222
src={__src__}
208223
event={__event__}
209224
className={cn("absolute right-4 top-4", __withMeta__ && "top-16")}
210225
/>
211226
)}
212-
{__npmCommand__ &&
213-
__yarnCommand__ &&
214-
__pnpmCommand__ &&
215-
__bunCommand__ && (
216-
<CopyNpmCommandButton
217-
commands={{
218-
__npmCommand__,
219-
__yarnCommand__,
220-
__pnpmCommand__,
221-
__bunCommand__,
222-
}}
223-
className={cn("absolute right-4 top-4", __withMeta__ && "top-16")}
224-
/>
225-
)}
226227
</StyleWrapper>
227228
)
228229
},

apps/www/content/docs/installation/remix.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Do you want to use CSS variables for colors? › no / yes
4747
### Install Tailwind CSS
4848

4949
```bash
50-
npm add -D tailwindcss@latest autoprefixer@latest
50+
npm install -D tailwindcss@latest autoprefixer@latest
5151
```
5252

5353
Then we create a `postcss.config.js` file:

apps/www/content/docs/installation/vite.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
1919

2020
```bash
2121
npm install -D tailwindcss postcss autoprefixer
22+
```
2223

24+
```bash
2325
npx tailwindcss init -p
2426
```
2527

@@ -93,11 +95,10 @@ Add the following code to the `tsconfig.app.json` file to resolve paths, for you
9395

9496
### Update vite.config.ts
9597

96-
Add the following code to the vite.config.ts so your app can resolve paths without error
98+
Add the following code to the vite.config.ts so your app can resolve paths without error:
9799

98100
```bash
99-
# (so you can import "path" without error)
100-
npm i -D @types/node
101+
npm install -D @types/node
101102
```
102103

103104
```typescript

apps/www/hooks/use-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ type Config = {
88
style: Style["name"]
99
theme: BaseColor["name"]
1010
radius: number
11+
packageManager: "npm" | "yarn" | "pnpm" | "bun"
1112
}
1213

1314
const configAtom = atomWithStorage<Config>("config", {
1415
style: "new-york",
1516
theme: "zinc",
1617
radius: 0.5,
18+
packageManager: "pnpm",
1719
})
1820

1921
export function useConfig() {

apps/www/lib/rehype-component.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ export function rehypeComponent() {
3737
} else {
3838
const component = Index[style.name][name]
3939
src = fileName
40-
? component.files.find((file: string) => {
41-
return (
42-
file.endsWith(`${fileName}.tsx`) ||
43-
file.endsWith(`${fileName}.ts`)
44-
)
40+
? component.files.find((file: unknown) => {
41+
if (typeof file === "string") {
42+
return (
43+
file.endsWith(`${fileName}.tsx`) ||
44+
file.endsWith(`${fileName}.ts`)
45+
)
46+
}
47+
return false
4548
}) || component.files[0]?.path
4649
: component.files[0]?.path
4750
}

apps/www/lib/rehype-npm-command.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function rehypeNpmCommand() {
2626
)
2727
}
2828

29-
// npx create.
29+
// npx create-.
3030
if (node.properties?.["__rawString__"]?.startsWith("npx create-")) {
3131
const npmCommand = node.properties?.["__rawString__"]
3232
node.properties["__npmCommand__"] = npmCommand
@@ -44,6 +44,24 @@ export function rehypeNpmCommand() {
4444
)
4545
}
4646

47+
// npm create.
48+
if (node.properties?.["__rawString__"]?.startsWith("npm create")) {
49+
const npmCommand = node.properties?.["__rawString__"]
50+
node.properties["__npmCommand__"] = npmCommand
51+
node.properties["__yarnCommand__"] = npmCommand.replace(
52+
"npm create",
53+
"yarn create"
54+
)
55+
node.properties["__pnpmCommand__"] = npmCommand.replace(
56+
"npm create",
57+
"pnpm create"
58+
)
59+
node.properties["__bunCommand__"] = npmCommand.replace(
60+
"npm create",
61+
"bun create"
62+
)
63+
}
64+
4765
// npx.
4866
if (
4967
node.properties?.["__rawString__"]?.startsWith("npx") &&

0 commit comments

Comments
 (0)