diff --git a/src/app/api/components/all/route.ts b/src/app/api/components/all/route.ts index c360944..7a81f4a 100644 --- a/src/app/api/components/all/route.ts +++ b/src/app/api/components/all/route.ts @@ -6,130 +6,39 @@ import { componentExamples } from "@/lib/component-registry"; type ComponentData = { name: string; fileName: string; - files: Record; + code: string; exampleCode: string; }; -/** - * Extracts @/components/ui/ imports from a code string - */ -function extractUIImports(code: string): string[] { - const importRegex = /from\s+["']@\/components\/ui\/([^"']+)["']/g; - const matches = [...code.matchAll(importRegex)]; - return matches.map((match) => match[1]); -} - -/** - * Recursively loads component dependencies by parsing imports - */ -async function loadComponentWithDependencies( - componentFileName: string, - componentsDir: string, - loadedComponents = new Set() -): Promise> { - // Prevent circular dependencies and duplicates - if (loadedComponents.has(componentFileName)) { - return {}; - } - - loadedComponents.add(componentFileName); - - const files: Record = {}; - const filePath = join(componentsDir, componentFileName); - - try { - const code = await readFile(filePath, "utf-8"); - - // Extract component name without extension for the key - const componentKey = componentFileName.replace(/\.tsx?$/, ""); - files[componentKey] = code; - - // Parse imports to find dependencies on @/components/ui/ - const dependencies = extractUIImports(code); - - // Load each dependency recursively - for (const dependencyName of dependencies) { - const dependencyFileName = `${dependencyName}.tsx`; - - const dependencyFiles = await loadComponentWithDependencies( - dependencyFileName, - componentsDir, - loadedComponents - ); - - // Merge dependency files - Object.assign(files, dependencyFiles); - } - - return files; - } catch (error) { - console.error(`Error loading component ${componentFileName}:`, error); - return files; - } -} - export async function GET() { try { const componentsDir = join(process.cwd(), "src", "components", "ui"); const files = await readdir(componentsDir); - // Get list of components - const componentsList = files - .filter((file) => file.endsWith(".tsx")) - .map((file) => ({ - fileName: file, - name: file - .replace(".tsx", "") - .split("-") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "), - })) - .sort((a, b) => a.name.localeCompare(b.name)); - - // Load all components with their dependencies const components: ComponentData[] = []; - for (const component of componentsList) { - const loadedComponents = new Set(); - const componentFiles = await loadComponentWithDependencies( - component.fileName, - componentsDir, - loadedComponents - ); + for (const file of files) { + if (!file.endsWith(".tsx")) continue; - const componentKey = component.fileName.replace(".tsx", ""); + const componentKey = file.replace(".tsx", ""); + const filePath = join(componentsDir, file); + const code = await readFile(filePath, "utf-8"); const exampleCode = componentExamples[componentKey] || ""; - // Extract dependencies from the example code - if (exampleCode) { - const exampleDependencies = extractUIImports(exampleCode); - - // Load any dependencies from the example code that weren't already loaded - for (const dependencyName of exampleDependencies) { - const dependencyFileName = `${dependencyName}.tsx`; - - // Only load if not already loaded - if (!loadedComponents.has(dependencyFileName)) { - const dependencyFiles = await loadComponentWithDependencies( - dependencyFileName, - componentsDir, - loadedComponents - ); - - // Merge dependency files - Object.assign(componentFiles, dependencyFiles); - } - } - } - components.push({ - name: component.name, - fileName: component.fileName, - files: componentFiles, + name: file + .replace(".tsx", "") + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "), + fileName: file, + code, exampleCode, }); } + components.sort((a, b) => a.name.localeCompare(b.name)); + return NextResponse.json({ components }); } catch (error) { console.error("Error loading all components:", error); diff --git a/src/components/component-viewer.tsx b/src/components/component-viewer.tsx index 80d0213..5c25f53 100644 --- a/src/components/component-viewer.tsx +++ b/src/components/component-viewer.tsx @@ -14,7 +14,7 @@ import { useSandpackNavigation, } from "@codesandbox/sandpack-react"; import { useTheme } from "next-themes"; -import { useEffect, useMemo } from "react"; +import { useEffect, useLayoutEffect, useMemo, useState } from "react"; import { ALL_DEPENDENCIES } from "@/lib/component-registry"; function transformAbsoluteToRelativeImports(code: string): string { @@ -290,8 +290,7 @@ const getPackageJSON = (dependencies: Record) => type: "module", scripts: { dev: "vite", - build: - "npx tailwindcss -i ./index.css -o ./output.css && tsc && vite build", + build: "tsc && vite build", preview: "vite preview", }, dependencies: { @@ -355,6 +354,9 @@ export function ComponentViewer() { const { activeComponentName } = useActiveComponent(); const { data: allComponents = [] } = useComponents(); const { resolvedTheme } = useTheme(); + const [initialFiles, setInitialFiles] = useState< + Record + >({}); const activeComponent = useMemo(() => { return allComponents.find( @@ -362,27 +364,19 @@ export function ComponentViewer() { ) as ComponentData; }, [allComponents, activeComponentName]); - const files = useMemo(() => { - if (!activeComponent) return {}; - - const { fileName, exampleCode, files: componentFiles } = activeComponent; - const componentName = fileName.replace(".tsx", ""); - const componentCode = componentFiles[componentName] || ""; + // Setup all files once on mount + useEffect(() => { + if (allComponents.length === 0 || !activeComponent) return; - const transformedExampleCode = - transformAbsoluteToRelativeImports(exampleCode); - const transformedComponentCode = - transformAbsoluteToRelativeImports(componentCode); + const transformedExampleCode = transformAbsoluteToRelativeImports( + activeComponent.exampleCode + ); const setupFiles: Record = { "/App.tsx": { code: transformedExampleCode, readOnly: false, }, - [`/${componentName}.tsx`]: { - code: transformedComponentCode, - readOnly: false, - }, "/index.html": { code: getIndexHTML(resolvedTheme === "dark"), readOnly: false, @@ -391,24 +385,29 @@ export function ComponentViewer() { code: getPackageJSON(ALL_DEPENDENCIES), readOnly: true, }, + "/vite.config.ts": { + code: getViteConfigTS(ALL_DEPENDENCIES), + readOnly: false, + }, ...INITIAL_FILES, }; - for (const [fileName, code] of Object.entries(componentFiles)) { - if (fileName === componentName) continue; + for (const component of allComponents) { + const componentName = component.fileName.replace(".tsx", ""); + const transformedCode = transformAbsoluteToRelativeImports( + component.code + ); - const transformedCode = transformAbsoluteToRelativeImports(code); - - setupFiles[`/${fileName}.tsx`] = { + setupFiles[`/${componentName}.tsx`] = { code: transformedCode, readOnly: false, }; } - return setupFiles; - }, [activeComponent]); + setInitialFiles(setupFiles); + }, [allComponents]); - if (Object.keys(files).length === 0) { + if (Object.keys(initialFiles).length === 0) { return (

Loading component...

@@ -419,7 +418,7 @@ export function ComponentViewer() { return (
- + + Preview
- - - +
+ + + + + + +
@@ -465,25 +470,40 @@ export function ComponentViewer() { ); } -function UpdateDarkMode({ - files, -}: { - files: Record; -}) { - const { resolvedTheme } = useTheme(); +function UpdateActiveComponent() { + const { activeComponentName } = useActiveComponent(); + const { data: allComponents = [] } = useComponents(); const { sandpack } = useSandpack(); - const { refresh } = useSandpackNavigation(); useEffect(() => { - const timeout = setTimeout(() => { - refresh(); - }, 300); + console.log("running update active component hook"); + const activeComponent = allComponents.find( + (c) => c.fileName === activeComponentName + ); - return () => clearTimeout(timeout); - }, [files, refresh]); + if (!activeComponent) return; + + const transformedExampleCode = transformAbsoluteToRelativeImports( + activeComponent.exampleCode + ); + + sandpack.updateFile("/App.tsx", transformedExampleCode, true); + }, [activeComponentName, allComponents]); + + return null; +} + +function UpdateDarkMode() { + const { resolvedTheme } = useTheme(); + const { sandpack } = useSandpack(); useEffect(() => { - sandpack.updateFile("/index.html", getIndexHTML(resolvedTheme === "dark")); + console.log("running update dark mode hook"); + sandpack.updateFile( + "/index.html", + getIndexHTML(resolvedTheme === "dark"), + true + ); }, [resolvedTheme]); return null; diff --git a/src/hooks/use-components.ts b/src/hooks/use-components.ts index e1aee26..ebba04d 100644 --- a/src/hooks/use-components.ts +++ b/src/hooks/use-components.ts @@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query"; export type ComponentData = { name: string; fileName: string; - files: Record; + code: string; exampleCode: string; };