mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
fix(docs): open in chat improvements (#5035)
* fix(docs): parse dependency match imported components against heroui * fix(docs): center open in chat components
This commit is contained in:
parent
0a3773c2a1
commit
26080cbf0e
@ -1,25 +1,23 @@
|
||||
"use server";
|
||||
|
||||
import {SandpackFiles} from "@codesandbox/sandpack-react/types";
|
||||
|
||||
import {parseDependencies} from "@/components/docs/components/code-demo/parse-dependencies";
|
||||
import {toKebabCase, toPascalCase} from "@/components/docs/components/code-demo/utils";
|
||||
|
||||
const importReact = 'import React from "react";';
|
||||
|
||||
export const openInChat = async ({title, files}: {title?: string; files: SandpackFiles}) => {
|
||||
export const openInChat = async ({
|
||||
component,
|
||||
title,
|
||||
content,
|
||||
dependencies,
|
||||
useWrapper,
|
||||
}: {
|
||||
component: string;
|
||||
title?: string;
|
||||
content: string;
|
||||
dependencies: {name: string; version: string}[];
|
||||
useWrapper: boolean;
|
||||
}) => {
|
||||
try {
|
||||
// assumes one file for now
|
||||
let content = files["/App.jsx"];
|
||||
|
||||
if (!content || typeof content !== "string") {
|
||||
return {
|
||||
error: "Content is not a string",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
const dependencies = parseDependencies(content);
|
||||
|
||||
// Check if the file content includes 'React' import statements, if not, add it
|
||||
if (
|
||||
content.includes("React.") &&
|
||||
@ -29,6 +27,16 @@ export const openInChat = async ({title, files}: {title?: string; files: Sandpac
|
||||
content = `${importReact}\n${content}\n`;
|
||||
}
|
||||
|
||||
let files: Record<string, string> = {
|
||||
"src/App.tsx": content,
|
||||
};
|
||||
|
||||
const fullName = `${component.charAt(0).toUpperCase() + component.slice(1)} - ${title}`;
|
||||
|
||||
if (useWrapper) {
|
||||
files = getFilesWithWrapper(fullName, content);
|
||||
}
|
||||
|
||||
const response = await fetch(`${process.env.CHAT_API_URL}/import`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@ -36,8 +44,8 @@ export const openInChat = async ({title, files}: {title?: string; files: Sandpac
|
||||
Authorization: `Bearer ${process.env.IMPORT_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
content,
|
||||
title: `${component.charAt(0).toUpperCase() + component.slice(1)} - ${title}`,
|
||||
files,
|
||||
dependencies,
|
||||
}),
|
||||
});
|
||||
@ -63,3 +71,30 @@ export const openInChat = async ({title, files}: {title?: string; files: Sandpac
|
||||
return {error: error, data: null};
|
||||
}
|
||||
};
|
||||
|
||||
const getFilesWithWrapper = (name: string, content: string) => {
|
||||
const pascalName = toPascalCase(name);
|
||||
const kebabName = toKebabCase(name);
|
||||
|
||||
// Replace the export default function name
|
||||
const updatedContent = content.replace(
|
||||
"export default function App()",
|
||||
`export default function ${pascalName}()`,
|
||||
);
|
||||
|
||||
const wrapperContent = `import ${pascalName} from "./components/${kebabName}";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-6">
|
||||
<${pascalName} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
return {
|
||||
[`src/components/${kebabName}.tsx`]: updatedContent,
|
||||
[`src/App.tsx`]: wrapperContent,
|
||||
};
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@ import {usePathname} from "next/navigation";
|
||||
|
||||
import {useCodeDemo, UseCodeDemoProps} from "./use-code-demo";
|
||||
import WindowResizer, {WindowResizerProps} from "./window-resizer";
|
||||
import {parseDependencies} from "./parse-dependencies";
|
||||
|
||||
import {GradientBoxProps} from "@/components/gradient-box";
|
||||
import {SmallLogo} from "@/components/heroui-logo";
|
||||
@ -180,15 +181,34 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
const handleOpenInChat = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
// assume doc demo files are all App.jsx
|
||||
const content = files["/App.jsx"];
|
||||
|
||||
if (!content || typeof content !== "string") {
|
||||
addToast({
|
||||
title: "Error",
|
||||
description: "Invalid demo content",
|
||||
color: "danger",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const component = pathname.split("/components/")[1];
|
||||
const dependencies = parseDependencies(content);
|
||||
|
||||
posthog.capture("CodeDemo - Open in Chat", {
|
||||
component,
|
||||
demo: title,
|
||||
});
|
||||
|
||||
const capitalizedPath = component.charAt(0).toUpperCase() + component.slice(1);
|
||||
const {data, error} = await openInChat({title: `${capitalizedPath} - ${title}`, files});
|
||||
const {data, error} = await openInChat({
|
||||
component,
|
||||
title,
|
||||
content,
|
||||
dependencies,
|
||||
useWrapper: !asIframe,
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
|
||||
@ -1,15 +1,39 @@
|
||||
const packageRegex = /(?:from|import)\s+(?:.*\s+from\s+)?['"]([^'"]+)['"]/g;
|
||||
import React from "react";
|
||||
import * as HeroUI from "@heroui/react";
|
||||
|
||||
const importRegex = /^(import\s+(?!type\s+\{)[\s\S]*?;)/gm;
|
||||
|
||||
export const parseDependencies = (content: string) => {
|
||||
const dependencies: {name: string; version: string}[] = [];
|
||||
|
||||
content.match(packageRegex)?.forEach((match) => {
|
||||
if (match.includes("@heroui")) {
|
||||
return;
|
||||
// by default, react and heroui packages are installed already
|
||||
const installedPackages = {
|
||||
React,
|
||||
...HeroUI,
|
||||
} as Record<string, unknown>;
|
||||
|
||||
// create a map of installed packages
|
||||
const imports = Object.keys(installedPackages).reduce(
|
||||
(acc, key) => {
|
||||
acc[key] = `${key}`;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{React: "React"} as Record<string, string>,
|
||||
);
|
||||
|
||||
// match all imports from the file content
|
||||
content.match(importRegex)?.forEach((match) => {
|
||||
// check if imported component is in default installed packages
|
||||
const componentName = match.match(/\w+/g)?.[1] || "";
|
||||
const matchingImport = imports[componentName];
|
||||
|
||||
if (matchingImport) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (match.includes("./") || match.includes("../")) {
|
||||
return;
|
||||
return "";
|
||||
}
|
||||
|
||||
const packageName = match.match(/['"]([^'"]+)['"]/)?.[1];
|
||||
|
||||
@ -55,3 +55,20 @@ export const joinCode = (filesCode: FileCode[]) => {
|
||||
export const getFileName = (filePath: string) => {
|
||||
return filePath?.split(".")?.[0]?.replace(/\W/g, "");
|
||||
};
|
||||
|
||||
export const toPascalCase = (str: string) => {
|
||||
const cleanStr = str.replace(/[^a-zA-Z0-9\s]/g, "");
|
||||
|
||||
return cleanStr
|
||||
.split(/\s+/)
|
||||
.map((word) => {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
||||
})
|
||||
.join("");
|
||||
};
|
||||
|
||||
export const toKebabCase = (str: string) => {
|
||||
const cleanStr = str.replace(/[^a-zA-Z0-9\s]/g, "");
|
||||
|
||||
return cleanStr.toLowerCase().split(/\s+/).join("-");
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user