nextui/apps/docs/components/sandpack/use-sandpack.ts
Junior Garcia b8e7b94586
🚀 (#4570)
* docs: optimize route higtlight (#4520)

* docs: optimize home display (#4519)

* docs: optimize home display and route highlight

* docs: optimize home display

* fix(alert): propagate className (#4535)

* fix(alert): propagate className

* chore(alert): remove className from alert theme

* fix(avatar): title type in Avatar (#4529)

* fix(avatar): title type in Avatar

* fix(alert): apply isEmpty check on title

* fix(alert): alert interface props type

* refactor: remove unnecessary props types (#4530)

* refactor(docs): remove string type as it is included in ReactNode

* refactor: remove unnecessary types

* feat(changeset): add changeset

* chore: remove changeset

* refactor: remove null since ReactNode unions it already

* fix(input): use onPress for wrapper click focus (#4483)

* fix(input): use onPress for wrapper click focus

* test(input): wrapper click focus test

* chore(changeset): input onPress for wrapper click focus

* chore(changeset): minor wording

* Refactor/rebrand (#4532)

* chore: rebrand in progress

* chore: update docs to use heroui

* chore: components renbranded

* chore: figma moved to the docs files

* fix: posthog config

* fix(docs): extra classname in form example (#4465)

* chore: clean git

* chore: make heroui private

* chore: new logo

* chore: node env var renamed

* chore: public robots txt deleted

* chore: wrangler installed

* chore: wrangler renamed

* chore: cloudlfare workers removed

* chore: force vercel deploy

* refactor: first migration and provider

* refactor: rename nextui plugin

* refactor: rename github site

* refactor: rename CONTRIBUTING

* refactor: rename package name

* refactor: nextjs image hostname

* refactor: mdx repo nextui-org rename frontio-ai

* refactor: nextui.org rename heroui.com

* refactor: add heroui to missing places

* fix: heroui plugin name

* fix: update docs

* docs: nextui to heroui add npmrc pnpm migratation

* chore: rename all packages with new org name

* chore: replace frontio-ai by frontioai

* chore: revert previous changes

* chore: small adjustment

* chore: doc updated

* feat: blog

* chore: avatar updated

* fix: url

* chore: add new ogimage

* fix: ogimage command

* fix: heroui name and storybook welcome page

* fix: og image url

* feat: favicon and icon changed

---------

Co-authored-by: աӄա <wingkwong.code@gmail.com>
Co-authored-by: winches <329487092@qq.com>

* fix: postbuild script

* chore: core package updates

* ci(changesets): version packages (#4569)

Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>

* feat: contributors added to the blog

---------

Co-authored-by: winches <329487092@qq.com>
Co-authored-by: աӄա <wingkwong.code@gmail.com>
Co-authored-by: Peterl561 <76144929+Peterl561@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-16 16:24:32 -03:00

219 lines
5.7 KiB
TypeScript

import {useMemo} from "react";
import {SandpackFiles, SandpackPredefinedTemplate} from "@codesandbox/sandpack-react";
import {useTheme} from "next-themes";
import {useLocalStorage} from "usehooks-ts";
import {HighlightedLines} from "./types";
import {getHighlightedLines, getFileName} from "./utils";
import {stylesConfig, postcssConfig, tailwindConfig, getHtmlFile, rootFile} from "./entries";
export interface UseSandpackProps {
files?: SandpackFiles;
typescriptStrict?: boolean;
template?: SandpackPredefinedTemplate;
highlightedLines?: HighlightedLines;
}
const importReact = 'import React from "react";';
export const useSandpack = ({
files = {},
typescriptStrict = false,
template = "vite-react",
highlightedLines,
}: UseSandpackProps) => {
// once the user select a template we store it in local storage
const [currentTemplate, setCurrentTemplate] = useLocalStorage<SandpackPredefinedTemplate>(
"currentTemplate",
template,
);
const hasTypescript = Object.keys(files).some(
(file) => file.includes(".ts") || file.includes(".tsx"),
);
const {theme} = useTheme();
const decorators = getHighlightedLines(highlightedLines, currentTemplate);
const sandpackTemplate = useMemo<SandpackPredefinedTemplate>(
() => (currentTemplate === "vite-react-ts" && hasTypescript ? currentTemplate : "vite-react"),
[currentTemplate, hasTypescript],
);
// map current template to its mime type
const mimeType = useMemo(
() => (sandpackTemplate === "vite-react-ts" ? ".tsx" : ".jsx"),
[sandpackTemplate],
);
// get entry file by current template
const entryFile = useMemo(
() => (sandpackTemplate === "vite-react-ts" ? "index.tsx" : "index.jsx"),
[sandpackTemplate],
);
// filter files by current template
const filteredFiles = Object.keys(files).reduce((acc, key) => {
if (key.includes("App") && !key.includes(mimeType)) {
return acc;
}
if (typescriptStrict && currentTemplate === "vite-react-ts" && key.includes(".js")) {
return acc;
}
if (currentTemplate === "vite-react" && key.includes(".ts")) {
return acc;
}
// @ts-ignore
acc[key] = files[key];
return acc;
}, {});
let dependencies = {
"framer-motion": "11.9.0",
"@heroui/react": "latest",
};
// sort files by dependency
const sortedFiles = Object.keys(filteredFiles)
.sort((a: string, b: string) => {
const aFile = files[a] as string;
const bFile = files[b] as string;
const aName = getFileName(a);
const bName = getFileName(b);
// if bName includes "App" should be first
if (bName.includes("App")) {
return 1;
}
if (aFile?.includes(bName)) {
return -1;
}
if (bFile.includes(aName)) {
return 1;
}
return 0;
})
.reduce((acc, key) => {
let fileContent = files[key] as string;
// Check if the file content includes 'React' import statements, if not, add it
if (
fileContent.includes("React.") &&
!fileContent.includes("from 'react'") &&
!fileContent.includes('from "react"')
) {
fileContent = `${importReact}\n${fileContent}\n`;
}
// Check if file content includes any other dependencies, if yes, add it to dependencies
const importRegex = /import .* from ["'](.*)["']/g;
let match: RegExpExecArray | null;
while ((match = importRegex.exec(fileContent)) !== null) {
const dependencyName = match[1];
if (!dependencies.hasOwnProperty(dependencyName) && !dependencyName.includes("./")) {
// add the dependency to the dependencies object with version 'latest'
// @ts-ignore
dependencies[dependencyName] = "latest";
}
}
return {
...acc,
[key]: fileContent,
};
}, {});
/**
* Uncomment this logic when specific imports are needed
*/
// const heroUIComponents = useMemo(
// () =>
// Object.values(getHeroUIComponents(sortedFiles) || {}).flatMap((e) =>
// e.split(",").map((name) => name.replace(/"/g, "")),
// ),
// [sortedFiles],
// );
// const hasComponents = !isEmpty(heroUIComponents);
// const dependencies = useMemo(() => {
// let deps = {
// "framer-motion": "11.9.0",
// };
// if (hasComponents) {
// let deps = {
// "@heroui/theme": "canary",
// "@heroui/system": "canary",
// };
// heroUIComponents.forEach((component) => {
// deps = {
// ...deps,
// [`@heroui/${component}`]: "canary",
// };
// });
// return deps;
// }
// return {
// ...deps,
// "@heroui/react": "canary",
// };
// }, [hasComponents, heroUIComponents, component]);
// const tailwindConfigFile = useMemo(
// () => (hasComponents ? updateTailwindConfig(tailwindConfig, heroUIComponents) : tailwindConfig),
// [tailwindConfig, heroUIComponents],
// );
const customSetup = {
dependencies,
entry: entryFile,
devDependencies: {
autoprefixer: "^10.4.14",
postcss: "^8.4.21",
tailwindcss: "^3.2.7",
},
};
return {
customSetup,
files: {
...sortedFiles,
[entryFile]: {
code: rootFile,
hidden: true,
},
"index.html": {
code: getHtmlFile(theme ?? "light", entryFile),
hidden: true,
},
"tailwind.config.js": {
code: tailwindConfig,
hidden: true,
},
"postcss.config.js": {
code: postcssConfig,
hidden: true,
},
"styles.css": {
code: stylesConfig,
hidden: true,
},
},
hasTypescript,
entryFile,
sortedFiles,
decorators,
sandpackTemplate,
setCurrentTemplate,
};
};