Skip to content

Commit de29597

Browse files
committed
Merge branch aidenybai:main into chore/explanation-wording
2 parents 387427d + 140639d commit de29597

File tree

2 files changed

+263
-1
lines changed

2 files changed

+263
-1
lines changed

packages/website/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Link from "next/link";
44
import { useState, useEffect } from "react";
55
import Companies from "@/components/companies";
66
import TodoDemo from "@/components/todo-demo";
7-
import InstallGuide from "@/components/installl-guide";
7+
import InstallGuide from "@/components/install-guide";
88

99
export default function Home() {
1010
const [showDemo, setShowDemo] = useState(false);
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
'use client';
2+
3+
import React, { useState, useEffect } from 'react';
4+
import Image from "next/image";
5+
import hljs from 'highlight.js';
6+
import 'highlight.js/styles/github-dark.css';
7+
8+
const ClipboardIcon = ({ className }: { className: string }) => (
9+
<svg
10+
className={className}
11+
viewBox="0 0 24 24"
12+
fill="none"
13+
stroke="currentColor"
14+
>
15+
<path
16+
strokeLinecap="round"
17+
strokeLinejoin="round"
18+
strokeWidth={2}
19+
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"
20+
/>
21+
</svg>
22+
);
23+
24+
const CheckIcon = ({ className }: { className: string }) => (
25+
<svg
26+
className={className}
27+
viewBox="0 0 24 24"
28+
fill="none"
29+
stroke="currentColor"
30+
>
31+
<path
32+
strokeLinecap="round"
33+
strokeLinejoin="round"
34+
strokeWidth={2}
35+
d="M5 13l4 4L19 7"
36+
/>
37+
</svg>
38+
);
39+
40+
const Tabs = ['script', 'nextjs-app', 'nextjs-pages', 'vite', 'remix'] as const;
41+
type Tab = (typeof Tabs)[number];
42+
43+
export default function InstallGuide() {
44+
const [copied, setCopied] = useState(false);
45+
const [activeTab, setActiveTab] = useState<Tab>('script');
46+
const [height, setHeight] = useState('auto');
47+
const contentRef = React.useRef<HTMLPreElement>(null);
48+
49+
useEffect(() => {
50+
if (contentRef.current) {
51+
const newHeight = contentRef.current.scrollHeight;
52+
setHeight(`${newHeight}px`);
53+
}
54+
}, [activeTab]);
55+
56+
const handleTabChange = (tab: Tab) => {
57+
if (contentRef.current) {
58+
setHeight(`${contentRef.current.scrollHeight}px`);
59+
}
60+
setActiveTab(tab);
61+
};
62+
63+
const copyToClipboard = async () => {
64+
await navigator.clipboard.writeText(getCodeForTab(activeTab));
65+
setCopied(true);
66+
setTimeout(() => setCopied(false), 2000);
67+
};
68+
69+
const getCodeForTab = (tab: Tab) => {
70+
switch (tab) {
71+
case 'script':
72+
return `<!-- paste this BEFORE any scripts -->
73+
<script
74+
crossOrigin="anonymous"
75+
src="//unpkg.com/react-scan/dist/auto.global.js"
76+
></script>
77+
`;
78+
case 'nextjs-app':
79+
return `export default function RootLayout({
80+
children,
81+
}: {
82+
children: React.ReactNode
83+
}) {
84+
return (
85+
<html lang="en">
86+
<head>
87+
<script
88+
crossOrigin="anonymous"
89+
src="//unpkg.com/react-scan/dist/auto.global.js"
90+
/>
91+
{/* rest of your scripts go under */}
92+
</head>
93+
<body>{children}</body>
94+
</html>
95+
)
96+
}`;
97+
case 'nextjs-pages':
98+
return `import { Html, Head, Main, NextScript } from 'next/document';
99+
100+
export default function Document() {
101+
return (
102+
<Html lang="en">
103+
<Head>
104+
<script
105+
crossOrigin="anonymous"
106+
src="//unpkg.com/react-scan/dist/auto.global.js"
107+
/>
108+
{/* rest of your scripts go under */}
109+
</Head>
110+
<body>
111+
<Main />
112+
<NextScript />
113+
</body>
114+
</Html>
115+
);
116+
}`;
117+
case 'vite':
118+
return `<!doctype html>
119+
<html lang="en">
120+
<head>
121+
<script
122+
crossOrigin="anonymous"
123+
src="//unpkg.com/react-scan/dist/auto.global.js"
124+
/>
125+
<!-- rest of your scripts go under -->
126+
</head>
127+
<body>
128+
<!-- ... -->
129+
</body>
130+
</html>`;
131+
case 'remix':
132+
return `import { Links, Meta, Outlet, Scripts } from "@remix-run/react";
133+
134+
export default function App() {
135+
return (
136+
<html>
137+
<head>
138+
<link
139+
rel="icon"
140+
href="data:image/x-icon;base64,AA"
141+
/>
142+
<Meta />
143+
<script
144+
crossOrigin="anonymous"
145+
src="//unpkg.com/react-scan/dist/auto.global.js"
146+
/>
147+
<Links />
148+
</head>
149+
<body>
150+
<Outlet />
151+
<Scripts />
152+
</body>
153+
</html>
154+
);
155+
}
156+
`;
157+
}
158+
};
159+
160+
const highlightedCode = hljs.highlight(getCodeForTab(activeTab), { language: 'javascript' }).value;
161+
162+
return (
163+
<div className="mt-4">
164+
{/* Window frame */}
165+
<div className="overflow-hidden rounded-md border border-[#373737] bg-[#1e1e1e]">
166+
{/* Window title bar */}
167+
<div className="flex items-center justify-between border-b border-[#333] bg-[#1e1e1e] px-4 py-2">
168+
<div className="flex items-center gap-1.5">
169+
<span className="size-2.5 rounded-full bg-[#ff5f57]/60"></span>
170+
<span className="size-2.5 rounded-full bg-[#febc2e]/60"></span>
171+
<span className="size-2.5 rounded-full bg-[#28c840]/60"></span>
172+
</div>
173+
<div className="flex items-center gap-2">
174+
<Image src="/logo.svg" alt="react-scan-logo" width={16} height={16} />
175+
<span className="text-[#858585] text-sm">React Scan</span>
176+
</div>
177+
</div>
178+
179+
<div>
180+
<div className="flex bg-[#252526]">
181+
{Tabs.map((tab) => (
182+
<button
183+
key={tab}
184+
onClick={() => handleTabChange(tab)}
185+
className={`relative px-4 py-2 text-[15px] transition-colors ${activeTab === tab
186+
? 'bg-[#1e1e1e] text-white before:absolute before:left-0 before:top-0 before:h-[1px] before:w-full before:bg-[#7a68e7]'
187+
: 'text-[#969696] hover:text-white'
188+
}`}
189+
>
190+
{tab === 'script' ? 'Script Tag' :
191+
tab === 'nextjs-app' ? 'Next.js (App)' :
192+
tab === 'nextjs-pages' ? 'Next.js (Pages)' :
193+
tab === 'vite' ? 'Vite' :
194+
tab === 'remix' ? 'Remix' :
195+
''}
196+
</button>
197+
))}
198+
</div>
199+
200+
<div className="grid grid-rows-[auto_1fr_auto] bg-[#1e1e1e]">
201+
<div className="flex items-center gap-2 shadow-xl px-3 py-1.5">
202+
<span className="text-xs text-neutral-300/50">
203+
{activeTab === 'script' ? 'index.html' :
204+
activeTab === 'nextjs-app' ? 'app/layout.tsx' :
205+
activeTab === 'nextjs-pages' ? 'pages/_document.tsx' :
206+
activeTab === 'vite' ? 'index.html' :
207+
activeTab === 'remix' ? 'app/root.tsx' :
208+
''}
209+
</span>
210+
</div>
211+
212+
<div
213+
className="overflow-hidden transition-[height] duration-150 ease-in-out"
214+
style={{ height }}
215+
>
216+
<div
217+
className="transform transition-all duration-500"
218+
>
219+
<pre
220+
ref={contentRef}
221+
className="text-neutral-300 group relative whitespace-pre font-mono text-xs py-4 px-3"
222+
>
223+
<code
224+
className="language-javascript relative flex"
225+
dangerouslySetInnerHTML={{
226+
__html: `
227+
<div class="select-none min-w-8 max-w-8 pr-2.5 inline-block text-right text-[#858585] opacity-50">${highlightedCode.split('\n').map((_, i) => i + 1).join('\n')
228+
}</div>
229+
<div class="flex-1">${highlightedCode}</div>
230+
`
231+
}}
232+
/>
233+
<button
234+
onClick={() => {
235+
void copyToClipboard();
236+
}}
237+
className="absolute right-4 top-4 rounded bg-[#333] p-1.5 opacity-0 transition-opacity hover:bg-[#444] group-hover:opacity-100"
238+
>
239+
{copied ? (
240+
<CheckIcon className="size-4 text-green-500" />
241+
) : (
242+
<ClipboardIcon className="size-4 text-white" />
243+
)}
244+
</button>
245+
</pre>
246+
</div>
247+
</div>
248+
249+
<div className="border-t border-[#2d2d2d] bg-[#2d2d2d] px-3 py-1.5 text-xs text-[#8b949e]">
250+
<a
251+
className="hover:text-white"
252+
href="https://github.com/aidenybai/react-scan#readme"
253+
>
254+
→ Full installation guide
255+
</a>
256+
</div>
257+
</div>
258+
</div>
259+
</div>
260+
</div>
261+
);
262+
}

0 commit comments

Comments
 (0)