'use client';
import { useState, useEffect, useRef, useCallback } from 'react';
import hljs from 'highlight.js/lib/core';
import xml from 'highlight.js/lib/languages/xml';
import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript';
import bash from 'highlight.js/lib/languages/bash';
import 'highlight.js/styles/github-dark.css';
hljs.registerLanguage('xml', xml);
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('typescript', typescript);
hljs.registerLanguage('bash', bash);
const COPY_FEEDBACK_DURATION_MS = 2000;
const InlineCode = ({ children }: { children: React.ReactNode }) => (
{children}
);
interface InstallTab {
id: string;
label: string;
description: React.ReactNode;
lang: string;
code: string;
}
const INSTALL_TABS: InstallTab[] = [
{
id: 'cli',
label: 'CLI',
description: '',
lang: 'bash',
code: `npx -y react-scan@latest init`,
},
{
id: 'script',
label: 'Script Tag',
description: <>Paste this before any scripts in your index.html>,
lang: 'xml',
code: `
`,
},
{
id: 'nextjs-app',
label: 'Next.js (App)',
description: <>Add this inside of your app/layout.tsx>,
lang: 'typescript',
code: `import Script from "next/script";
export default function RootLayout({ children }) {
return (
{children}
);
}`,
},
{
id: 'nextjs-pages',
label: 'Next.js (Pages)',
description: <>Add this into your pages/_document.tsx>,
lang: 'typescript',
code: `import { Html, Head, Main, NextScript } from "next/document";
import Script from "next/script";
export default function Document() {
return (
);
}`,
},
{
id: 'vite',
label: 'Vite',
description: <>Example index.html with React Scan enabled>,
lang: 'xml',
code: `
`,
},
{
id: 'remix',
label: 'Remix',
description: <>Add this inside your app/root.tsx>,
lang: 'typescript',
code: `import { Links, Meta, Outlet, Scripts } from "@remix-run/react";
export default function App() {
return (
);
}`,
},
];
const CopyIcon = () => (
);
const CheckIcon = () => (
);
export default function InstallGuide() {
const [activeTabId, setActiveTabId] = useState(INSTALL_TABS[0].id);
const [didCopy, setDidCopy] = useState(false);
const [height, setHeight] = useState('auto');
const contentRef = useRef(null);
const activeTab =
INSTALL_TABS.find((tab) => tab.id === activeTabId) ?? INSTALL_TABS[0];
const highlightedCode = hljs.highlight(activeTab.code, {
language: activeTab.lang,
}).value;
const syncHeight = useCallback(() => {
if (contentRef.current) {
setHeight(`${contentRef.current.scrollHeight}px`);
}
}, []);
useEffect(syncHeight, [activeTabId, syncHeight]);
const handleTabChange = (tabId: string) => {
syncHeight();
setActiveTabId(tabId);
};
const handleCopy = () => {
navigator.clipboard
.writeText(activeTab.code)
.then(() => {
setDidCopy(true);
setTimeout(() => setDidCopy(false), COPY_FEEDBACK_DURATION_MS);
})
.catch(() => {});
};
const headingText =
activeTabId === 'cli'
? 'Run this command to get started:'
: 'It takes 1 script tag to get started:';
return (
{headingText}
{activeTabId === 'cli' && (
)}
{INSTALL_TABS.map((tab) => {
const isActive = tab.id === activeTab.id;
return (
);
})}
{activeTabId === 'cli' ? (
) : (
)}
{activeTab.id !== 'cli' && activeTab.description && (
{activeTab.description}
)}
);
}