Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8e55ea3

Browse files
authoredFeb 5, 2025
feat: new command copy component (magicuidesign#535)
1 parent acd92cd commit 8e55ea3

File tree

8 files changed

+181
-31
lines changed

8 files changed

+181
-31
lines changed
 

‎app/layout.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { fontMono, fontSans } from "@/lib/fonts";
2-
import { absoluteUrl, cn, constructMetadata } from "@/lib/utils";
3-
import { Toaster } from "@/components/ui/sonner";
4-
import { TooltipProvider } from "@/components/ui/tooltip";
51
import { Analytics } from "@/components/analytics";
62
import { PHProvider } from "@/components/posthog-provider";
73
import { ThemeProvider } from "@/components/theme-provider";
4+
import { Toaster } from "@/components/ui/sonner";
5+
import { TooltipProvider } from "@/components/ui/tooltip";
6+
import { fontMono, fontSans } from "@/lib/fonts";
7+
import { absoluteUrl, cn, constructMetadata } from "@/lib/utils";
8+
import { Provider as JotaiProvider } from "jotai";
89

910
import "@/styles/globals.css";
1011
import "@/styles/mdx.css";
@@ -42,15 +43,17 @@ export default function RootLayout({
4243
fontMono.variable,
4344
)}
4445
>
45-
<PHProvider>
46-
<ThemeProvider attribute="class" defaultTheme="light">
47-
<TooltipProvider>
48-
{children}
49-
<Toaster />
50-
<Analytics />
51-
</TooltipProvider>
52-
</ThemeProvider>
53-
</PHProvider>
46+
<JotaiProvider>
47+
<PHProvider>
48+
<ThemeProvider attribute="class" defaultTheme="light">
49+
<TooltipProvider>
50+
{children}
51+
<Toaster />
52+
<Analytics />
53+
</TooltipProvider>
54+
</ThemeProvider>
55+
</PHProvider>
56+
</JotaiProvider>
5457
</body>
5558
</html>
5659
);

‎components/code-block-command.tsx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"use client";
2+
3+
import { copyToClipboardWithMeta } from "@/components/copy-button";
4+
import { Button } from "@/components/ui/button";
5+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
6+
import { useConfig } from "@/hooks/use-config";
7+
import { useMounted } from "@/hooks/use-mounted";
8+
import { NpmCommands } from "@/types/unist";
9+
import { CheckIcon, ClipboardIcon } from "lucide-react";
10+
import * as React from "react";
11+
12+
export function CodeBlockCommand({
13+
__npmCommand__,
14+
__yarnCommand__,
15+
__pnpmCommand__,
16+
__bunCommand__,
17+
}: React.ComponentProps<"pre"> & NpmCommands) {
18+
const [config, setConfig] = useConfig();
19+
const [hasCopied, setHasCopied] = React.useState(false);
20+
const mounted = useMounted();
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+
if (!mounted) {
57+
return null;
58+
}
59+
60+
return (
61+
<div className="relative mt-6 max-h-[650px] overflow-x-auto rounded-xl bg-zinc-950 dark:bg-zinc-900">
62+
<Tabs
63+
defaultValue={packageManager}
64+
onValueChange={(value) => {
65+
console.log("value", value, packageManager);
66+
setConfig({
67+
...config,
68+
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
69+
});
70+
}}
71+
>
72+
<div className="flex items-center justify-between border-b border-zinc-800 bg-zinc-900 px-3 pt-2.5">
73+
<TabsList className="h-7 translate-y-[2px] gap-3 bg-transparent p-0 pl-1">
74+
{Object.entries(tabs).map(([key, value]) => {
75+
return (
76+
<TabsTrigger
77+
key={key}
78+
value={key}
79+
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"
80+
>
81+
{key}
82+
</TabsTrigger>
83+
);
84+
})}
85+
</TabsList>
86+
</div>
87+
{Object.entries(tabs).map(([key, value]) => {
88+
return (
89+
<TabsContent key={key} value={key} className="mt-0">
90+
<pre className="px-4 py-5">
91+
<code
92+
className="relative font-mono text-sm leading-none"
93+
data-language="bash"
94+
>
95+
{value}
96+
</code>
97+
</pre>
98+
</TabsContent>
99+
);
100+
})}
101+
</Tabs>
102+
<Button
103+
size="icon"
104+
variant="ghost"
105+
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"
106+
onClick={copyCommand}
107+
>
108+
<span className="sr-only">Copy</span>
109+
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
110+
</Button>
111+
</div>
112+
);
113+
}

‎components/mdx-components.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Callout } from "@/components/callout";
2+
import { CodeBlockCommand } from "@/components/code-block-command";
23
import RepoDownload from "@/components/repo-download";
34
import TechStack from "@/components/tech-stack";
45
import TemplatePreview from "@/components/template-preview";
@@ -17,7 +18,7 @@ import Image from "next/image";
1718
import Link from "next/link";
1819
import { ComponentPreview } from "./component-preview";
1920
import { ComponentSource } from "./component-source";
20-
import { CopyButton, CopyNpmCommandButton } from "./copy-button";
21+
import { CopyButton } from "./copy-button";
2122

2223
const CustomLink = (props: any) => {
2324
const href = props.href;
@@ -242,11 +243,9 @@ const components = {
242243
__withMeta__,
243244
__src__,
244245
__event__,
245-
// __style__,
246246
__name__,
247247
...props
248248
}: React.HTMLAttributes<HTMLPreElement> & {
249-
// __style__?: Style["name"]
250249
__rawString__?: string;
251250
__npmCommand__?: string;
252251
__pnpmCommand__?: string;
@@ -257,6 +256,20 @@ const components = {
257256
__event__?: Event["name"];
258257
__name__?: string;
259258
}) => {
259+
const isNpmCommand =
260+
__npmCommand__ && __yarnCommand__ && __pnpmCommand__ && __bunCommand__;
261+
262+
if (isNpmCommand) {
263+
return (
264+
<CodeBlockCommand
265+
__npmCommand__={__npmCommand__}
266+
__yarnCommand__={__yarnCommand__}
267+
__pnpmCommand__={__pnpmCommand__}
268+
__bunCommand__={__bunCommand__}
269+
/>
270+
);
271+
}
272+
260273
return (
261274
<>
262275
<pre
@@ -274,20 +287,6 @@ const components = {
274287
className={cn("absolute right-4 top-4", __withMeta__ && "top-16")}
275288
/>
276289
)}
277-
{__npmCommand__ &&
278-
__yarnCommand__ &&
279-
__pnpmCommand__ &&
280-
__bunCommand__ && (
281-
<CopyNpmCommandButton
282-
commands={{
283-
__npmCommand__,
284-
__pnpmCommand__,
285-
__yarnCommand__,
286-
__bunCommand__,
287-
}}
288-
className={cn("absolute right-4 top-4", __withMeta__ && "top-16")}
289-
/>
290-
)}
291290
</>
292291
);
293292
},

‎components/toc.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import { useEffect, useMemo, useState } from "react";
44

5+
import { useMounted } from "@/hooks/use-mounted";
56
import type { TableOfContents } from "@/lib/toc";
6-
import { useMounted } from "@/lib/use-mounted";
77
import { cn } from "@/lib/utils";
88

99
interface TocProps {

‎hooks/use-config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useAtom } from "jotai";
2+
import { atomWithStorage } from "jotai/utils";
3+
4+
type Config = {
5+
packageManager: "npm" | "yarn" | "pnpm" | "bun";
6+
};
7+
8+
const configAtom = atomWithStorage<Config>("config", {
9+
packageManager: "pnpm",
10+
});
11+
12+
export function useConfig() {
13+
return useAtom(configAtom);
14+
}
File renamed without changes.

‎package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"date-fns": "^2.30.0",
5757
"geist": "^1.2.2",
5858
"gray-matter": "^4.0.3",
59+
"jotai": "^2.11.3",
5960
"lodash.template": "^4.5.0",
6061
"lucide-react": "^0.401.0",
6162
"motion": "^11.15.0",

‎pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.