diff --git a/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.stories.tsx b/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.stories.tsx new file mode 100644 index 000000000..5c47addf9 --- /dev/null +++ b/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.stories.tsx @@ -0,0 +1,32 @@ +import { + SandpackProvider, + SandpackThemeProvider, + SandpackCodeEditor, +} from "../../"; + +import { OpenInCodeSandboxButton, UnstyledOpenInCodeSandboxButton } from "."; + +export default { + title: "components/OpenInCodeSandboxButton", + component: OpenInCodeSandboxButton, +}; + +export const Main = (): JSX.Element => ( + + + + + + +); + +export const Unstyled = (): JSX.Element => ( + + + + + Open in CodeSandbox + + + +); diff --git a/sandpack-react/src/common/OpenInCodeSandboxButton.tsx b/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.tsx similarity index 55% rename from sandpack-react/src/common/OpenInCodeSandboxButton.tsx rename to sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.tsx index 448889509..2e0045af0 100644 --- a/sandpack-react/src/common/OpenInCodeSandboxButton.tsx +++ b/sandpack-react/src/common/OpenInCodeSandboxButton/OpenInCodeSandboxButton.tsx @@ -1,16 +1,16 @@ import { useClasser } from "@code-hike/classer"; import * as React from "react"; -import { useCodeSandboxLink } from "../hooks/useCodeSandboxLink"; -import { useSandpackTheme } from "../hooks/useSandpackTheme"; -import { CSBIcon } from "../icons"; -import { isDarkColor } from "../utils/stringUtils"; +import { useSandpackTheme } from "../../hooks/useSandpackTheme"; +import { CSBIcon } from "../../icons"; +import { isDarkColor } from "../../utils/stringUtils"; + +import { UnstyledOpenInCodeSandboxButton } from "./UnstyledOpenInCodeSandboxButton"; /** * @category Components */ export const OpenInCodeSandboxButton: React.FC = () => { - const url = useCodeSandboxLink(); const { theme } = useSandpackTheme(); const c = useClasser("sp"); @@ -19,14 +19,10 @@ export const OpenInCodeSandboxButton: React.FC = () => { : "csb-icon-light"; return ( - - + ); }; diff --git a/sandpack-react/src/common/OpenInCodeSandboxButton/UnstyledOpenInCodeSandboxButton.tsx b/sandpack-react/src/common/OpenInCodeSandboxButton/UnstyledOpenInCodeSandboxButton.tsx new file mode 100644 index 000000000..15262d078 --- /dev/null +++ b/sandpack-react/src/common/OpenInCodeSandboxButton/UnstyledOpenInCodeSandboxButton.tsx @@ -0,0 +1,95 @@ +import type { SandpackBundlerFiles } from "@codesandbox/sandpack-client"; +import { getParameters } from "codesandbox-import-utils/lib/api/define"; +import * as React from "react"; + +import { useSandpack } from "../../hooks/useSandpack"; +import type { SandboxEnvironment } from "../../types"; + +const CSB_URL = "https://codesandbox.io/api/v1/sandboxes/define"; + +const getFileParameters = ( + files: SandpackBundlerFiles, + environment?: SandboxEnvironment +) => { + type NormalizedFiles = Record< + string, + { + content: string; + isBinary: boolean; + } + >; + + const normalizedFiles = Object.keys(files).reduce((prev, next) => { + const fileName = next.replace("/", ""); + const value = { + content: files[next].code, + isBinary: false, + }; + + return { ...prev, [fileName]: value }; + }, {} as NormalizedFiles); + + return getParameters({ + files: normalizedFiles, + ...(environment ? { template: environment } : null), + }); +}; + +export const UnstyledOpenInCodeSandboxButton: React.FC<{ + className?: string; +}> = ({ children, ...props }) => { + const [paramsValues, setParamsValues] = React.useState(""); + const { sandpack } = useSandpack(); + const formRef = React.useRef(null); + + React.useEffect( + function debounce() { + const timer = setTimeout(() => { + const params = getFileParameters(sandpack.files, sandpack.environment); + + setParamsValues(params); + }, 600); + + return () => { + clearTimeout(timer); + }; + }, + [sandpack.environment, sandpack.files] + ); + + // Register the usage of the codesandbox link + React.useEffect(function registerUsage() { + sandpack.openInCSBRegisteredRef.current = true; + }, []); + + /** + * This is a safe limit to avoid too long requests (401), + * as all parameters are attached in the URL + */ + if (paramsValues.length > 1500) { + return ( + + ); + } + + return ( + + {children} + + ); +}; diff --git a/sandpack-react/src/common/OpenInCodeSandboxButton/index.ts b/sandpack-react/src/common/OpenInCodeSandboxButton/index.ts new file mode 100644 index 000000000..03a6bc0f2 --- /dev/null +++ b/sandpack-react/src/common/OpenInCodeSandboxButton/index.ts @@ -0,0 +1,2 @@ +export { OpenInCodeSandboxButton } from "./OpenInCodeSandboxButton"; +export { UnstyledOpenInCodeSandboxButton } from "./UnstyledOpenInCodeSandboxButton"; diff --git a/sandpack-react/src/hooks/index.ts b/sandpack-react/src/hooks/index.ts index 6975cebf5..d080f080f 100644 --- a/sandpack-react/src/hooks/index.ts +++ b/sandpack-react/src/hooks/index.ts @@ -1,5 +1,4 @@ export * from "./useActiveCode"; -export * from "./useCodeSandboxLink"; export * from "./useErrorMessage"; export * from "./useLoadingOverlayState"; export * from "./useSandpack"; diff --git a/sandpack-react/src/hooks/useCodeSandboxLink.ts b/sandpack-react/src/hooks/useCodeSandboxLink.ts deleted file mode 100644 index c8e26988d..000000000 --- a/sandpack-react/src/hooks/useCodeSandboxLink.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { SandpackBundlerFiles } from "@codesandbox/sandpack-client"; -import { getParameters } from "codesandbox-import-utils/lib/api/define"; -import * as React from "react"; - -import type { SandboxEnvironment } from "../types"; - -import { useSandpack } from "./useSandpack"; - -const getFileParameters = ( - files: SandpackBundlerFiles, - environment?: SandboxEnvironment -) => { - const normalized: Record = - Object.keys(files).reduce( - (prev, next) => ({ - ...prev, - [next.replace("/", "")]: { - content: files[next].code, - isBinary: false, - }, - }), - {} - ); - - return getParameters({ - files: normalized, - ...(environment ? { template: environment } : null), - }); -}; - -/** - * @category Hooks - */ -export const useCodeSandboxLink = (): string => { - const { sandpack } = useSandpack(); - const params = getFileParameters(sandpack.files, sandpack.environment); - - // Register the usage of the codesandbox link - React.useEffect(() => { - sandpack.openInCSBRegisteredRef.current = true; - }, []); - - return `https://codesandbox.io/api/v1/sandboxes/define?parameters=${params}&query=file=${sandpack.activePath}%26from-sandpack=true`; -}; diff --git a/sandpack-react/src/presets/CustomSandpack.stories.tsx b/sandpack-react/src/presets/CustomSandpack.stories.tsx index e2706c762..74fc9d6f1 100644 --- a/sandpack-react/src/presets/CustomSandpack.stories.tsx +++ b/sandpack-react/src/presets/CustomSandpack.stories.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; -import { useSandpack } from "../hooks/useSandpack"; import type { ViewportSize } from "../"; import { Sandpack, @@ -13,12 +12,13 @@ import { SandpackCodeViewer, SandpackCodeEditor, SandpackTranspiledCode, - useCodeSandboxLink, useSandpackTheme, useActiveCode, useSandpackNavigation, SandpackStack, } from "../"; +import { UnstyledOpenInCodeSandboxButton } from "../common"; +import { useSandpack } from "../hooks/useSandpack"; export default { title: "presets/Sandpack: custom", @@ -71,12 +71,6 @@ export const UsingVisualElements: React.FC = () => ( ); -const CustomOpenInCSB = () => { - const url = useCodeSandboxLink(); - - return Open in CodeSandbox; -}; - const CustomRefreshButton = () => { const { refresh } = useSandpackNavigation(); @@ -87,6 +81,14 @@ const CustomRefreshButton = () => { ); }; +const CustomOpenInCSB = () => { + return ( + + Open in CodeSandbox + + ); +}; + const CustomCodeEditor = () => { const { code, updateCode } = useActiveCode(); const { theme } = useSandpackTheme(); @@ -103,7 +105,7 @@ const CustomCodeEditor = () => { background: theme.palette.defaultBackground, border: `1px solid ${theme.palette.inactiveText}`, color: theme.palette.activeText, - lineHeight: theme.typography.lineheight, + lineHeight: theme.typography.lineHeight, }} > {code} diff --git a/website/docs/docs/advanced-usage/components.md b/website/docs/docs/advanced-usage/components.md index 9f32c7846..43b1eb543 100644 --- a/website/docs/docs/advanced-usage/components.md +++ b/website/docs/docs/advanced-usage/components.md @@ -173,6 +173,33 @@ For situations when you strictly want to show some code and run it in the browse +## UnstyledOpenInCodeSandboxButton & OpenInCodeSandboxButton + +You can build a custom button that creates a new sandbox from the sandpack files. It will include any edits made in the Sandpack editor, so it is a great way to persist your changes. The created sandbox will open on [CodeSandbox](https://codesandbox.io) in a new tab. + +Let's use the `UnstyledOpenInCodeSandboxButton` as an example: + +```jsx +import { + SandpackProvider, + SandpackLayout, + SandpackCodeEditor, + UnstyledOpenInCodeSandboxButton, +} from "@codesandbox/sandpack-react"; +const CustomSandpack = () => ( + + + + + Open in CodeSandbox + + + +); +``` + +The `UnstyledOpenInCodeSandboxButton` is a basic component that does not carry any styles. If you want a ready-to-use component, use the `OpenInCodeSandboxButton` instead, which has the same functionality but includes the CodeSandbox logo. + ## Other components You can also bring other components in the mix: `SandpackTranspiledCode`, `FileTabs`, `FileExplorer`, `Navigator` and so on. diff --git a/website/docs/docs/advanced-usage/hooks.md b/website/docs/docs/advanced-usage/hooks.md index cc9f98977..77c391a8b 100644 --- a/website/docs/docs/advanced-usage/hooks.md +++ b/website/docs/docs/advanced-usage/hooks.md @@ -129,24 +129,6 @@ const CustomRefreshButton = () => { }; ``` -## useCodeSandboxLink - -Similarly, we can build a custom link that opens the sandpack files in a new tab -on https://codesandbox.io. Let's the use `useCodeSandboxLink` for that: - -```jsx -import { useCodeSandboxLink } from "@codesandbox/sandpack-react"; - -const CustomOpenInCSB = () => { - const url = useCodeSandboxLink(); - return ( - - Open in CodeSandbox - - ); -}; -``` - ## useActiveCode We implemented the `SandpackCodeEditor` on top of