From e4f79371399349b2ed3965f1adab057a4651b031 Mon Sep 17 00:00:00 2001 From: moumensoliman Date: Mon, 24 Nov 2025 15:08:06 +0200 Subject: [PATCH 1/7] feat: add LiveEdit wrapper to enable live editing tab for a component --- .../src/app/playground/(demos)/layout.tsx | 11 +- .../codesandbox/demo-tabs-wrapper.tsx | 46 +++ .../codesandbox/files/components.ts | 279 ++++++++++++++++++ .../src/components/codesandbox/files/files.ts | 30 ++ .../src/components/codesandbox/files/index.ts | 20 ++ .../docs/src/components/codesandbox/index.tsx | 201 +++++++++++++ .../src/components/codesandbox/live-tab.tsx | 23 ++ packages/docs/src/lib/get-demo-code.ts | 128 ++++++++ 8 files changed, 737 insertions(+), 1 deletion(-) create mode 100644 packages/docs/src/components/codesandbox/demo-tabs-wrapper.tsx create mode 100644 packages/docs/src/components/codesandbox/files/components.ts create mode 100644 packages/docs/src/components/codesandbox/files/files.ts create mode 100644 packages/docs/src/components/codesandbox/files/index.ts create mode 100644 packages/docs/src/components/codesandbox/index.tsx create mode 100644 packages/docs/src/components/codesandbox/live-tab.tsx create mode 100644 packages/docs/src/lib/get-demo-code.ts diff --git a/packages/docs/src/app/playground/(demos)/layout.tsx b/packages/docs/src/app/playground/(demos)/layout.tsx index 93fedd12e..d7172497b 100644 --- a/packages/docs/src/app/playground/(demos)/layout.tsx +++ b/packages/docs/src/app/playground/(demos)/layout.tsx @@ -1,7 +1,16 @@ import { QuerySpy } from '@/src/components/query-spy' import { QuerystringSkeleton } from '@/src/components/querystring' +import { DemoTabsWrapper } from '@/src/components/codesandbox/demo-tabs-wrapper' import React, { Suspense } from 'react' +const DEMO_PATHS: Record = { + 'basic-counter': 'basic-counter/client.tsx', + 'batching': 'batching/client.tsx', + 'hex-colors': 'hex-colors/client.tsx', + // 'pagination': 'pagination/pagination-controls.client.tsx', // Not included because it's server component + 'tic-tac-toe': 'tic-tac-toe/client.tsx', +} + export default function PlaygroundDemoLayout({ children }: { @@ -12,7 +21,7 @@ export default function PlaygroundDemoLayout({  }> - {children} + {children} ) } diff --git a/packages/docs/src/components/codesandbox/demo-tabs-wrapper.tsx b/packages/docs/src/components/codesandbox/demo-tabs-wrapper.tsx new file mode 100644 index 000000000..e6e50aaaf --- /dev/null +++ b/packages/docs/src/components/codesandbox/demo-tabs-wrapper.tsx @@ -0,0 +1,46 @@ +'use client' + +import { Suspense, type ReactNode } from 'react' +import { usePathname } from 'next/navigation' +import { Tab, Tabs } from 'fumadocs-ui/components/tabs' +import { LiveTab } from './live-tab' + +export interface DemoPaths { + [demoName: string]: string +} + +export interface DemoTabsWrapperProps { + children: ReactNode + demoPaths: DemoPaths + demoTabLabel?: string + liveTabLabel?: string + getDemoName?: (pathname: string) => string +} + +export function DemoTabsWrapper({ + children, + demoPaths, + demoTabLabel = 'Demo', + liveTabLabel = 'Live', + getDemoName = (pathname) => pathname?.split('/').pop() ?? '', +}: DemoTabsWrapperProps) { + const pathname = usePathname() + const demoName = getDemoName(pathname ?? '') + const clientPath = demoPaths[demoName] + + if (!clientPath) return <>{children} + + return ( + + + {children} + + + Loading...}> + + + + + ) +} + diff --git a/packages/docs/src/components/codesandbox/files/components.ts b/packages/docs/src/components/codesandbox/files/components.ts new file mode 100644 index 000000000..94b1715a2 --- /dev/null +++ b/packages/docs/src/components/codesandbox/files/components.ts @@ -0,0 +1,279 @@ +/** + * Component source code for Sandpack demos + * These components are injected into the Sandpack environment + */ + +/** + * Default demo code showing basic nuqs usage + * A simple example with a single query parameter + */ +export const INITIAL_CODE = `import React from 'react'; +import { useQueryState } from "nuqs"; + +export default function Demo() { + const [hello, setHello] = useQueryState("hello", { defaultValue: "" }); + + return ( + <> + setHello(e.target.value || null)} + /> +

Hello, {hello || "anonymous visitor"}!

+ + ); +} +` + +/** + * Compact querystring display component + * Displays URL search parameters in a formatted, readable way + */ +export const QUERYSTRING_COMPONENT = `import React, { Fragment, useMemo } from 'react'; + +export type QuerystringProps = React.ComponentProps<'pre'> & { + value: string | URLSearchParams + keepKeys?: string[] +} + +function filterKeys(query: string | URLSearchParams, keys?: string[]) { + const src = new URLSearchParams(query) + if (!keys?.length) return src + + const dest = new URLSearchParams() + for (const [k, v] of src.entries()) { + if (keys.includes(k)) dest.append(k, v) + } + return dest +} + +export function Querystring({ value, keepKeys, className, ...props }: QuerystringProps) { + const search = useMemo(() => filterKeys(value, keepKeys), [value, keepKeys]) + + return ( +
+      {Array.from(search.entries()).map(([k, v], i) => (
+        
+          
+            {i === 0 ? '?' : <>&}
+          
+          {k}=
+          {v}
+        
+      ))}
+      {search.size === 0 && (
+        
+          {''}
+        
+      )}
+    
+ ) +} +` + +/** + * Query spy with iframe-parent URL sync + * Handles bidirectional communication between Sandpack iframe and parent window + */ +export const QUERY_SPY_COMPONENT = `import React, { useEffect, useRef, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { Querystring, QuerystringProps } from './querystring'; + +export function QuerySpy(props: Omit) { + const [searchParams] = useSearchParams(); + const [current, setCurrent] = useState(() => new URLSearchParams(window.location.search)); + const isUpdating = useRef(false); + const lastSearch = useRef(''); + + const sendToParent = (search: string) => { + if (window.parent !== window) { + window.parent.postMessage({ type: 'nuqs-url-update', search }, '*'); + } + }; + + // Sync React Router params to parent + useEffect(() => { + const params = searchParams.toString(); + if (params !== lastSearch.current && !isUpdating.current) { + lastSearch.current = params; + sendToParent(params); + } + isUpdating.current = false; + }, [searchParams]); + + // Poll for immediate URL changes + useEffect(() => { + let lastUrl = window.location.search; + + const check = () => { + const curr = window.location.search; + if (curr !== lastUrl) { + lastUrl = curr; + const params = new URLSearchParams(curr); + setCurrent(params); + const str = params.toString(); + if (str !== lastSearch.current && !isUpdating.current) { + lastSearch.current = str; + sendToParent(str); + } + } + }; + + let rafId: number; + const poll = () => { + check(); + rafId = requestAnimationFrame(poll); + }; + rafId = requestAnimationFrame(poll); + + return () => cancelAnimationFrame(rafId); + }, []); + + // Listen for parent URL and theme updates + useEffect(() => { + const handleMessage = (e: MessageEvent) => { + if (e.data?.type === 'nuqs-parent-url-update') { + const newSearch = e.data.search; + if (new URLSearchParams(window.location.search).toString() !== newSearch) { + isUpdating.current = true; + window.history.replaceState({}, '', newSearch ? \`?\${newSearch}\` : window.location.pathname); + setCurrent(new URLSearchParams(newSearch)); + window.dispatchEvent(new PopStateEvent('popstate')); + lastSearch.current = newSearch; + } + } else if (e.data?.type === 'nuqs-theme-update') { + const theme = e.data.theme; + if (theme === 'dark') { + document.documentElement.classList.add('dark'); + document.body.style.backgroundColor = 'hsl(var(--background))'; + document.body.style.color = 'hsl(var(--foreground))'; + } else { + document.documentElement.classList.remove('dark'); + document.body.style.backgroundColor = 'hsl(var(--background))'; + document.body.style.color = 'hsl(var(--foreground))'; + } + } + }; + + window.addEventListener('message', handleMessage); + if (window.parent !== window) { + window.parent.postMessage({ type: 'nuqs-child-ready' }, '*'); + } + + return () => window.removeEventListener('message', handleMessage); + }, []); + + return ; +} +` + +/** + * App wrapper component + * Main application layout for demos + */ +export const APP_COMPONENT = `import React from 'react'; +import Demo from './Demo'; +import { QuerySpy } from './QuerySpy'; +import "/globals.css"; + +function App() { + return ( +
+ +
+
+ Query String +
+ +
+
+ ); +} + +export default App; +` + +/** + * Entry point with router and nuqs setup + * Configures React Router, NuqsAdapter, and Tailwind + */ +export const INDEX_COMPONENT = `import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'; +import App from './App'; + +// Configure Tailwind CDN with custom colors +if (typeof window !== 'undefined' && !document.getElementById('tailwind-config')) { + const script = document.createElement('script'); + script.id = 'tailwind-config'; + script.innerHTML = \` + tailwind.config = { + theme: { + extend: { + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))', + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + }, + }, + }; + \`; + document.head.insertBefore(script, document.head.firstChild); +} + +const root = ReactDOM.createRoot(document.getElementById('root')!); + +root.render( + + + + + + + +); +` \ No newline at end of file diff --git a/packages/docs/src/components/codesandbox/files/files.ts b/packages/docs/src/components/codesandbox/files/files.ts new file mode 100644 index 000000000..99956e5b5 --- /dev/null +++ b/packages/docs/src/components/codesandbox/files/files.ts @@ -0,0 +1,30 @@ +import type { SandpackFiles } from '@codesandbox/sandpack-react' +import { + APP_COMPONENT, + INDEX_COMPONENT, + QUERY_SPY_COMPONENT, + QUERYSTRING_COMPONENT, +} from './components' + +/** + * Base Sandpack files required for all demos + * These files provide the runtime environment and URL sync functionality + */ +export const SANDPACK_FILES: SandpackFiles = { + '/App.tsx': { + code: APP_COMPONENT, + hidden: true, + }, + '/QuerySpy.tsx': { + code: QUERY_SPY_COMPONENT, + hidden: true, + }, + '/querystring.tsx': { + code: QUERYSTRING_COMPONENT, + hidden: true, + }, + '/index.tsx': { + code: INDEX_COMPONENT, + hidden: true, + }, +} diff --git a/packages/docs/src/components/codesandbox/files/index.ts b/packages/docs/src/components/codesandbox/files/index.ts new file mode 100644 index 000000000..487b3091a --- /dev/null +++ b/packages/docs/src/components/codesandbox/files/index.ts @@ -0,0 +1,20 @@ +/** + * Sandpack/CodeSandbox integration for nuqs demos + * + * This module provides: + * - Component source code for the Sandpack environment + * - Demo templates showing nuqs usage + * - File configurations for Sandpack + */ + +// Main exports - these are the primary public API +export { SANDPACK_FILES } from './files' + +// Component exports - available if needed for customization +export { + INITIAL_CODE, + APP_COMPONENT, + INDEX_COMPONENT, + QUERY_SPY_COMPONENT, + QUERYSTRING_COMPONENT, +} from './components' diff --git a/packages/docs/src/components/codesandbox/index.tsx b/packages/docs/src/components/codesandbox/index.tsx new file mode 100644 index 000000000..ab0736f4f --- /dev/null +++ b/packages/docs/src/components/codesandbox/index.tsx @@ -0,0 +1,201 @@ +'use client' + +import { useState, useMemo, useEffect, useRef, useCallback } from 'react' +import { Copy, Check, RotateCcw } from 'lucide-react' +import { Button } from '../ui/button' +import { SandpackProvider, SandpackLayout, SandpackCodeEditor, SandpackPreview, useSandpack } from '@codesandbox/sandpack-react' +import { INITIAL_CODE, SANDPACK_FILES } from '@/src/components/codesandbox/files' + +export type CodeSandboxDependencies = Record + +interface CodeSandboxProps { + code?: string + syncTrigger?: number + dependencies?: CodeSandboxDependencies +} + +function CodeSync({ onCodeChange }: { onCodeChange: (code: string) => void }) { + const { sandpack } = useSandpack() + const lastCode = useRef('') + + // Sync code changes from Sandpack editor to parent component state + useEffect(() => { + const file = sandpack.files['/Demo.tsx'] + if (file?.code && file.code !== lastCode.current) { + lastCode.current = file.code + onCodeChange(file.code) + } + }, [sandpack.files, onCodeChange]) + + return null +} + +export function CodeSandbox({ code: initialCode = INITIAL_CODE, syncTrigger, dependencies }: CodeSandboxProps) { + const [code, setCode] = useState(initialCode) + const [copied, setCopied] = useState(false) + const containerRef = useRef(null) + const getSearch = useCallback(() => new URLSearchParams(window.location.search).toString(), []) + const getTheme = useCallback(() => document.documentElement.classList.contains('dark') ? 'dark' : 'light', []) + + // Reset code to initial value when initialCode prop changes + useEffect(() => setCode(initialCode), [initialCode]) + + const handleCopy = useCallback(async () => { + await navigator.clipboard.writeText(code) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }, [code]) + + const handleReset = useCallback(() => setCode(initialCode), [initialCode]) + + const postToIframes = useCallback((type: string, data: any) => { + document.querySelectorAll('iframe').forEach(iframe => + iframe.contentWindow?.postMessage({ type, ...data }, '*') + ) + }, []) + + const files = useMemo(() => ({ + ...(dependencies || {}), + ...SANDPACK_FILES, + '/Demo.tsx': { code, active: true }, + }), [code, dependencies]) + + // Listen for iframe messages and sync URL state bidirectionally between parent and iframe + useEffect(() => { + const handleMessage = (e: MessageEvent) => { + if (e.data?.type === 'nuqs-url-update') { + const current = new URL(window.location.href) + if (current.searchParams.toString() !== e.data.search) { + window.history.replaceState({}, '', `${current.pathname}${e.data.search ? '?' + e.data.search : ''}`) + } + } else if (e.data?.type === 'nuqs-child-ready') { + postToIframes('nuqs-parent-url-update', { search: getSearch() }) + postToIframes('nuqs-theme-update', { theme: getTheme() }) + } + } + + const handlePopState = () => postToIframes('nuqs-parent-url-update', { search: getSearch() }) + + window.addEventListener('message', handleMessage) + window.addEventListener('popstate', handlePopState) + window.addEventListener('hashchange', handlePopState) + + const timer = setTimeout(() => postToIframes('nuqs-parent-url-update', { search: getSearch() }), 1000) + + return () => { + clearTimeout(timer) + window.removeEventListener('message', handleMessage) + window.removeEventListener('popstate', handlePopState) + window.removeEventListener('hashchange', handlePopState) + } + }, [postToIframes, getSearch, getTheme]) + + // Force URL sync when syncTrigger prop changes + useEffect(() => { + if (syncTrigger !== undefined) { + postToIframes('nuqs-parent-url-update', { search: getSearch() }) + } + }, [syncTrigger, postToIframes, getSearch]) + + // Watch for theme changes on document element and sync to iframes + useEffect(() => { + postToIframes('nuqs-theme-update', { theme: getTheme() }) + + const observer = new MutationObserver(() => postToIframes('nuqs-theme-update', { theme: getTheme() })) + observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }) + + return () => observer.disconnect() + }, [postToIframes, getTheme]) + + // Sync URL when component becomes visible in viewport + useEffect(() => { + const observer = new IntersectionObserver( + entries => entries[0].isIntersecting && postToIframes('nuqs-parent-url-update', { search: getSearch() }), + { threshold: 0 } + ) + + if (containerRef.current) observer.observe(containerRef.current) + return () => observer.disconnect() + }, [postToIframes, getSearch]) + + const hasChanges = code !== initialCode + const stats = { lines: code.split('\n').length, chars: code.length } + + return ( +
+
+
+ Live + {stats.lines} lines • {stats.chars} chars +
+
+ + +
+
+ +
+ + + + + + + +
+
+ ) +} \ No newline at end of file diff --git a/packages/docs/src/components/codesandbox/live-tab.tsx b/packages/docs/src/components/codesandbox/live-tab.tsx new file mode 100644 index 000000000..9ff267423 --- /dev/null +++ b/packages/docs/src/components/codesandbox/live-tab.tsx @@ -0,0 +1,23 @@ +'use client' + +import { useEffect, useState } from 'react' +import { CodeSandbox, type CodeSandboxDependencies } from '@/src/components/codesandbox' +import { getDemoCode, loadCodeSandboxFiles } from '@/src/lib/get-demo-code' + +interface LiveTabProps { + demoPath: string +} + +export function LiveTab({ demoPath }: LiveTabProps) { + const [code, setCode] = useState('') + const [deps, setDeps] = useState(null) + + useEffect(() => { + Promise.all([getDemoCode(demoPath), loadCodeSandboxFiles()]).then(([demoCode, dependencies]) => { + setCode(demoCode) + setDeps(dependencies) + }) + }, [demoPath]) + + return +} \ No newline at end of file diff --git a/packages/docs/src/lib/get-demo-code.ts b/packages/docs/src/lib/get-demo-code.ts new file mode 100644 index 000000000..204f03f61 --- /dev/null +++ b/packages/docs/src/lib/get-demo-code.ts @@ -0,0 +1,128 @@ +'use server' + +import fs from 'node:fs/promises' + +/** + * Apply a series of string replacements to the input code + */ +function applyReplacements( + code: string, + replacements: Array<[string | RegExp, string]> +): string { + return replacements.reduce( + (result, [pattern, replacement]) => result.replace(pattern, replacement), + code + ) +} + +export async function getDemoCode( + filePath: string, + isDemoFile: boolean = true +): Promise { + const fullPath = isDemoFile + ? process.cwd() + '/src/app/playground/(demos)/' + filePath + : process.cwd() + '/src' + filePath + + const code = await fs.readFile(fullPath, 'utf8') + + // Skip transformations for CSS files + if (filePath.endsWith('.css')) { + return code + } + + // Check if the file is in components/ui/ directory + const isInComponentsUI = filePath.includes('/components/ui/') + + // Base transformations for JS/TS files + const baseReplacements: Array<[RegExp | string, string]> = [ + [/^'use client'\s*\n?/m, ''], + // For files in components/ui/, use './' for same-directory imports + // For other files, use './components/ui/' + isInComponentsUI + ? [/from '@\/src\/components\/ui\//g, "from './"] + : [/from '@\/src\/components\/ui\//g, "from './components/ui/"], + [/from '@\/src\/lib\/utils'/g, "from '/utils'"], + [/from "@\/src\/lib\/utils"/g, 'from "/utils"'], + [/from '@\/src\/lib\//g, "from './lib/"], + [/from '@\/src\/components\//g, "from './components/"], + ] + + let transformedCode = applyReplacements(code, baseReplacements) + + // Apply demo-specific transformations only for demo files + if (isDemoFile) { + const demoReplacements: Array<[RegExp, string]> = [ + [/export default function \w+/g, 'export default function Demo'], + [/export default function BuilderPatternDemoPage/g, 'export default function Demo'], + [/export default function HexColorsDemo/g, 'export default function Demo'], + [/export default function Client/g, 'export default function Demo'], + ] + + transformedCode = applyReplacements(transformedCode, demoReplacements) + } + + return transformedCode +} + +export async function loadCodeSandboxDependencies() { + const [button, input, card, utils] = await Promise.all([ + getDemoCode('/components/ui/button.tsx', false), + getDemoCode('/components/ui/input.tsx', false), + getDemoCode('/components/ui/card.tsx', false), + getDemoCode('/lib/utils.ts', false), + ]) + + return { + "/components/ui/button.tsx": button, + "/components/ui/input.tsx": input, + "/components/ui/card.tsx": card, + "/utils.ts": utils, + } +} + +/** + * Process global.css for CodeSandbox by removing imports and external dependencies + * Returns CSS content suitable for injection into CodeSandbox preview + */ +export async function getCodeSandboxGlobalCSS(): Promise { + const cssPath = process.cwd() + '/src/app/globals.css' + const cssContent = await fs.readFile(cssPath, 'utf8') + + const cssReplacements: Array<[RegExp, string]> = [ + // Remove @import statements + [/@import\s+['"]tailwindcss['"];?\s*/g, ''], + [/@import\s+['"]fumadocs-ui\/css\/black\.css['"];?\s*/g, ''], + [/@import\s+['"]fumadocs-ui\/css\/preset\.css['"];?\s*/g, ''], + [/@import\s+['"]\.\/styles\/tweaks\.css['"];?\s*/g, ''], + // Clean up multiple consecutive newlines + [/\n{3,}/g, '\n\n'], + ] + + const processedCSS = applyReplacements(cssContent, cssReplacements) + + return processedCSS.trim() +} + +/** + * Create a data URI for the processed CSS + * Can be used directly in CodeSandbox's externalResources + */ +export async function getCodeSandboxGlobalCSSDataURI(): Promise { + const css = await getCodeSandboxGlobalCSS() + return `data:text/css;charset=utf-8,${encodeURIComponent(css)}` +} + +/** + * Get all CodeSandbox files including processed global CSS + */ +export async function loadCodeSandboxFiles() { + const [dependencies, globalCSS] = await Promise.all([ + loadCodeSandboxDependencies(), + getCodeSandboxGlobalCSS() + ]) + + return { + ...dependencies, + "/globals.css": globalCSS, + } +} \ No newline at end of file From 1efe98c92790612eb9593ecd824a1ff0c54866c1 Mon Sep 17 00:00:00 2001 From: Moumen Soliman Date: Mon, 24 Nov 2025 15:24:10 +0200 Subject: [PATCH 2/7] Add @codesandbox/sandpack-react dependency --- packages/docs/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/docs/package.json b/packages/docs/package.json index e8bbfbac4..616f91351 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -24,6 +24,7 @@ "test": "tsc" }, "dependencies": { + "@codesandbox/sandpack-react": "^2.20.0", "@faker-js/faker": "^10.1.0", "@headlessui/react": "2.2.9", "@headlessui/tailwindcss": "^0.2.2", From 12332715352e43113d69d16e03f614165fdb2cf7 Mon Sep 17 00:00:00 2001 From: moumensoliman Date: Mon, 24 Nov 2025 21:18:37 +0200 Subject: [PATCH 3/7] chore: migrate files --- .../src/components/codesandbox/files/files.ts | 30 ---------------- .../src/components/codesandbox/files/index.ts | 34 ++++++++++++++----- 2 files changed, 25 insertions(+), 39 deletions(-) delete mode 100644 packages/docs/src/components/codesandbox/files/files.ts diff --git a/packages/docs/src/components/codesandbox/files/files.ts b/packages/docs/src/components/codesandbox/files/files.ts deleted file mode 100644 index 99956e5b5..000000000 --- a/packages/docs/src/components/codesandbox/files/files.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { SandpackFiles } from '@codesandbox/sandpack-react' -import { - APP_COMPONENT, - INDEX_COMPONENT, - QUERY_SPY_COMPONENT, - QUERYSTRING_COMPONENT, -} from './components' - -/** - * Base Sandpack files required for all demos - * These files provide the runtime environment and URL sync functionality - */ -export const SANDPACK_FILES: SandpackFiles = { - '/App.tsx': { - code: APP_COMPONENT, - hidden: true, - }, - '/QuerySpy.tsx': { - code: QUERY_SPY_COMPONENT, - hidden: true, - }, - '/querystring.tsx': { - code: QUERYSTRING_COMPONENT, - hidden: true, - }, - '/index.tsx': { - code: INDEX_COMPONENT, - hidden: true, - }, -} diff --git a/packages/docs/src/components/codesandbox/files/index.ts b/packages/docs/src/components/codesandbox/files/index.ts index 487b3091a..8ab727fe8 100644 --- a/packages/docs/src/components/codesandbox/files/index.ts +++ b/packages/docs/src/components/codesandbox/files/index.ts @@ -1,14 +1,30 @@ -/** - * Sandpack/CodeSandbox integration for nuqs demos - * - * This module provides: - * - Component source code for the Sandpack environment - * - Demo templates showing nuqs usage - * - File configurations for Sandpack - */ +import type { SandpackFiles } from '@codesandbox/sandpack-react' +import { + APP_COMPONENT, + INDEX_COMPONENT, + QUERY_SPY_COMPONENT, + QUERYSTRING_COMPONENT, +} from './components' // Main exports - these are the primary public API -export { SANDPACK_FILES } from './files' +export const SANDPACK_FILES: SandpackFiles = { + '/App.tsx': { + code: APP_COMPONENT, + hidden: true, + }, + '/QuerySpy.tsx': { + code: QUERY_SPY_COMPONENT, + hidden: true, + }, + '/querystring.tsx': { + code: QUERYSTRING_COMPONENT, + hidden: true, + }, + '/index.tsx': { + code: INDEX_COMPONENT, + hidden: true, + }, +} // Component exports - available if needed for customization export { From 373c01ddffca929053e943b0096e718478af252d Mon Sep 17 00:00:00 2001 From: moumensoliman Date: Tue, 25 Nov 2025 00:53:05 +0200 Subject: [PATCH 4/7] fix: update lockfile --- pnpm-lock.yaml | 371 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42ff5c966..44898bda9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,6 +60,9 @@ importers: packages/docs: dependencies: + '@codesandbox/sandpack-react': + specifier: ^2.20.0 + version: 2.20.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@faker-js/faker': specifier: ^10.1.0 version: 10.1.0 @@ -1078,6 +1081,45 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@codemirror/autocomplete@6.20.0': + resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} + + '@codemirror/commands@6.10.0': + resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-html@6.4.11': + resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} + + '@codemirror/lang-javascript@6.2.4': + resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==} + + '@codemirror/language@6.11.3': + resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + + '@codemirror/lint@6.9.2': + resolution: {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/view@6.38.8': + resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} + + '@codesandbox/nodebox@0.1.8': + resolution: {integrity: sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==} + + '@codesandbox/sandpack-client@2.19.8': + resolution: {integrity: sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==} + + '@codesandbox/sandpack-react@2.20.0': + resolution: {integrity: sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-dom: ^16.8.0 || ^17 || ^18 || ^19 + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -2064,10 +2106,31 @@ packages: '@jspm/core@2.1.0': resolution: {integrity: sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==} + '@lezer/common@1.3.0': + resolution: {integrity: sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==} + + '@lezer/css@1.3.0': + resolution: {integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/html@1.3.12': + resolution: {integrity: sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==} + + '@lezer/javascript@1.5.4': + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + + '@lezer/lr@1.4.3': + resolution: {integrity: sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==} + '@mailpace/mailpace.js@0.1.3': resolution: {integrity: sha512-egh8at5vGozWdofvmZMzKdFKDEILWqi4HTGq9fb6zzfgEzvd4IU+mB+2vFbmgSzRudyDHrD3rXhEKOhgsVx5ww==} engines: {node: '>=10'} + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@mdx-js/mdx@2.3.0': resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} @@ -2977,6 +3040,16 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@react-hook/intersection-observer@3.1.2': + resolution: {integrity: sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==} + peerDependencies: + react: '>=16.8' + + '@react-hook/passive-layout-effect@1.2.1': + resolution: {integrity: sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==} + peerDependencies: + react: '>=16.8' + '@react-router/dev@7.8.1': resolution: {integrity: sha512-ESFe7DbMvCvl7e8N7L9NmI64VJGNCc60/VX1DUZYw/jFfzA5098/6D1aUojcxyVYBbMbVTfw0xmEvD4CsJzy1Q==} engines: {node: '>=20.0.0'} @@ -3707,6 +3780,9 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@stitches/core@1.2.8': + resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -4374,6 +4450,9 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + anser@2.3.3: + resolution: {integrity: sha512-QGY1oxYE7/kkeNmbtY/2ZjQ07BCG3zYdz+k/+sf69kMzEIxb93guHkPnIXITQ+BYi61oQwG74twMOX1tD4aesg==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -4603,6 +4682,9 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bytes-iec@3.1.1: resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} engines: {node: '>= 0.8'} @@ -4724,6 +4806,9 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clean-set@1.1.2: + resolution: {integrity: sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -4975,6 +5060,9 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-env@10.0.0: resolution: {integrity: sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==} engines: {node: '>=20'} @@ -5066,6 +5154,10 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + dargs@8.1.0: resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} engines: {node: '>=12'} @@ -5352,6 +5444,17 @@ packages: es-toolkit@1.40.0: resolution: {integrity: sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q==} + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -5388,6 +5491,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-carriage@1.3.1: + resolution: {integrity: sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -5406,6 +5512,10 @@ packages: esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -5480,6 +5590,9 @@ packages: resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} engines: {node: '>= 0.8'} + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + event-stream@3.3.4: resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} @@ -5550,6 +5663,9 @@ packages: exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -6174,6 +6290,10 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + intersection-observer@0.10.0: + resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==} + deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. + into-stream@7.0.0: resolution: {integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==} engines: {node: '>=12'} @@ -7226,6 +7346,9 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + next@16.0.1: resolution: {integrity: sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==} engines: {node: '>=20.9.0'} @@ -7480,6 +7603,9 @@ packages: outdent@0.8.0: resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} + outvariant@1.4.0: + resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} + outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -7987,6 +8113,9 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-devtools-inline@4.4.0: + resolution: {integrity: sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==} + react-dom@19.1.1: resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} peerDependencies: @@ -8665,6 +8794,9 @@ packages: engines: {node: '>=16'} hasBin: true + static-browser-server@1.0.3: + resolution: {integrity: sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -8692,6 +8824,9 @@ packages: stream-slice@0.1.2: resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} + strict-event-emitter@0.4.6: + resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==} + strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -8755,6 +8890,9 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + style-to-js@1.1.17: resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} @@ -9128,6 +9266,9 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} @@ -9514,6 +9655,9 @@ packages: jsdom: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -10094,6 +10238,114 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@codemirror/autocomplete@6.20.0': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.3.0 + + '@codemirror/commands@6.10.0': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.3.0 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.3.0 + '@lezer/css': 1.3.0 + + '@codemirror/lang-html@6.4.11': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.3.0 + '@lezer/css': 1.3.0 + '@lezer/html': 1.3.12 + + '@codemirror/lang-javascript@6.2.4': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.9.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.3.0 + '@lezer/javascript': 1.5.4 + + '@codemirror/language@6.11.3': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@lezer/common': 1.3.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.3 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.2': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.38.8': + dependencies: + '@codemirror/state': 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + + '@codesandbox/nodebox@0.1.8': + dependencies: + outvariant: 1.4.3 + strict-event-emitter: 0.4.6 + + '@codesandbox/sandpack-client@2.19.8': + dependencies: + '@codesandbox/nodebox': 0.1.8 + buffer: 6.0.3 + dequal: 2.0.3 + mime-db: 1.54.0 + outvariant: 1.4.0 + static-browser-server: 1.0.3 + + '@codesandbox/sandpack-react@2.20.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/commands': 6.10.0 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.8 + '@codesandbox/sandpack-client': 2.19.8 + '@lezer/highlight': 1.2.3 + '@react-hook/intersection-observer': 3.1.2(react@19.2.0) + '@stitches/core': 1.2.8 + anser: 2.3.3 + clean-set: 1.1.2 + dequal: 2.0.3 + escape-carriage: 1.3.1 + lz-string: 1.5.0 + react: 19.2.0 + react-devtools-inline: 4.4.0 + react-dom: 19.2.0(react@19.2.0) + react-is: 17.0.2 + '@colors/colors@1.5.0': optional: true @@ -10821,6 +11073,34 @@ snapshots: '@jspm/core@2.1.0': {} + '@lezer/common@1.3.0': {} + + '@lezer/css@1.3.0': + dependencies: + '@lezer/common': 1.3.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.3 + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.3.0 + + '@lezer/html@1.3.12': + dependencies: + '@lezer/common': 1.3.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.3 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.3.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.3 + + '@lezer/lr@1.4.3': + dependencies: + '@lezer/common': 1.3.0 + '@mailpace/mailpace.js@0.1.3(typescript@5.9.2)': dependencies: '@types/node': 14.18.63 @@ -10830,6 +11110,8 @@ snapshots: - debug - typescript + '@marijn/find-cluster-break@1.0.2': {} + '@mdx-js/mdx@2.3.0': dependencies: '@types/estree-jsx': 1.0.5 @@ -11891,6 +12173,16 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) + '@react-hook/intersection-observer@3.1.2(react@19.2.0)': + dependencies: + '@react-hook/passive-layout-effect': 1.2.1(react@19.2.0) + intersection-observer: 0.10.0 + react: 19.2.0 + + '@react-hook/passive-layout-effect@1.2.1(react@19.2.0)': + dependencies: + react: 19.2.0 + '@react-router/dev@7.8.1(@react-router/serve@7.8.1(react-router@7.8.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.2))(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react-router@7.8.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(terser@5.43.1)(tsx@4.20.4)(typescript@5.9.2)(vite@7.1.12(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1))(yaml@2.8.1)': dependencies: '@babel/core': 7.28.3 @@ -12772,6 +13064,8 @@ snapshots: '@standard-schema/utils@0.3.0': {} + '@stitches/core@1.2.8': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -13597,6 +13891,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + anser@2.3.3: {} + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -13836,6 +14132,11 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + bytes-iec@3.1.1: {} bytes@3.1.2: {} @@ -13950,6 +14251,8 @@ snapshots: dependencies: clsx: 2.1.1 + clean-set@1.1.2: {} + clean-stack@2.2.0: {} clean-stack@5.2.0: @@ -14184,6 +14487,8 @@ snapshots: create-require@1.1.1: {} + crelt@1.0.6: {} + cross-env@10.0.0: dependencies: '@epic-web/invariant': 1.0.0 @@ -14312,6 +14617,11 @@ snapshots: d3-timer@3.0.1: {} + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + dargs@8.1.0: {} dashdash@1.14.1: @@ -14533,6 +14843,24 @@ snapshots: es-toolkit@1.40.0: {} + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -14665,6 +14993,8 @@ snapshots: escalade@3.2.0: {} + escape-carriage@1.3.1: {} + escape-html@1.0.3: {} escape-string-regexp@1.0.5: {} @@ -14678,6 +15008,13 @@ snapshots: esm-env@1.2.2: {} + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + esprima@4.0.1: {} esrecurse@4.3.0: @@ -14763,6 +15100,11 @@ snapshots: '@types/node': 24.3.0 require-like: 0.1.2 + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-stream@3.3.4: dependencies: duplexer: 0.1.2 @@ -14920,6 +15262,10 @@ snapshots: exsolve@1.0.7: {} + ext@1.7.0: + dependencies: + type: 2.7.3 + extend@3.0.2: {} extract-zip@2.0.1(supports-color@8.1.1): @@ -15632,6 +15978,8 @@ snapshots: internmap@2.0.3: {} + intersection-observer@0.10.0: {} + into-stream@7.0.0: dependencies: from2: 2.3.0 @@ -17046,6 +17394,8 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) + next-tick@1.1.0: {} + next@16.0.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 16.0.1 @@ -17260,6 +17610,8 @@ snapshots: outdent@0.8.0: {} + outvariant@1.4.0: {} + outvariant@1.4.3: {} p-each-series@3.0.0: {} @@ -17686,6 +18038,10 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-devtools-inline@4.4.0: + dependencies: + es6-symbol: 3.1.4 + react-dom@19.1.1(react@19.1.1): dependencies: react: 19.1.1 @@ -18707,6 +19063,13 @@ snapshots: transitivePeerDependencies: - supports-color + static-browser-server@1.0.3: + dependencies: + '@open-draft/deferred-promise': 2.2.0 + dotenv: 16.6.1 + mime-db: 1.54.0 + outvariant: 1.4.3 + statuses@2.0.1: {} statuses@2.0.2: {} @@ -18728,6 +19091,8 @@ snapshots: stream-slice@0.1.2: {} + strict-event-emitter@0.4.6: {} + strict-event-emitter@0.5.1: {} string-hash@1.1.3: {} @@ -18791,6 +19156,8 @@ snapshots: strip-json-comments@2.0.1: {} + style-mod@4.1.3: {} + style-to-js@1.1.17: dependencies: style-to-object: 1.0.9 @@ -19143,6 +19510,8 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.1 + type@2.7.3: {} + typescript@5.9.2: {} ufo@1.6.1: {} @@ -19561,6 +19930,8 @@ snapshots: - tsx - yaml + w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 From 5d95e1496e3ba56d3501e37096fee14072610971 Mon Sep 17 00:00:00 2001 From: moumensoliman Date: Wed, 26 Nov 2025 14:04:14 +0200 Subject: [PATCH 5/7] fix: Use Object.fromEntries for more saftey import for global.css --- packages/docs/src/components/codesandbox/index.tsx | 4 ++-- packages/docs/src/lib/get-demo-code.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/docs/src/components/codesandbox/index.tsx b/packages/docs/src/components/codesandbox/index.tsx index ab0736f4f..17414b8cf 100644 --- a/packages/docs/src/components/codesandbox/index.tsx +++ b/packages/docs/src/components/codesandbox/index.tsx @@ -6,7 +6,7 @@ import { Button } from '../ui/button' import { SandpackProvider, SandpackLayout, SandpackCodeEditor, SandpackPreview, useSandpack } from '@codesandbox/sandpack-react' import { INITIAL_CODE, SANDPACK_FILES } from '@/src/components/codesandbox/files' -export type CodeSandboxDependencies = Record +export type CodeSandboxDependencies = Record interface CodeSandboxProps { code?: string @@ -55,7 +55,7 @@ export function CodeSandbox({ code: initialCode = INITIAL_CODE, syncTrigger, dep }, []) const files = useMemo(() => ({ - ...(dependencies || {}), + ...dependencies, ...SANDPACK_FILES, '/Demo.tsx': { code, active: true }, }), [code, dependencies]) diff --git a/packages/docs/src/lib/get-demo-code.ts b/packages/docs/src/lib/get-demo-code.ts index 204f03f61..b6e5623cd 100644 --- a/packages/docs/src/lib/get-demo-code.ts +++ b/packages/docs/src/lib/get-demo-code.ts @@ -122,7 +122,9 @@ export async function loadCodeSandboxFiles() { ]) return { - ...dependencies, - "/globals.css": globalCSS, + ...Object.fromEntries( + Object.entries(dependencies).map(([path, code]) => [path, { code }]) + ), + "/globals.css": { code: globalCSS }, } } \ No newline at end of file From ab6ee8bcb36f717fccf3cff1116fdc4ecc4fbf52 Mon Sep 17 00:00:00 2001 From: moumensoliman Date: Wed, 26 Nov 2025 17:38:24 +0200 Subject: [PATCH 6/7] fix: use resolve to handle ISR --- packages/docs/src/lib/get-demo-code.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/docs/src/lib/get-demo-code.ts b/packages/docs/src/lib/get-demo-code.ts index b6e5623cd..744a4a2c8 100644 --- a/packages/docs/src/lib/get-demo-code.ts +++ b/packages/docs/src/lib/get-demo-code.ts @@ -1,6 +1,7 @@ 'use server' -import fs from 'node:fs/promises' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' /** * Apply a series of string replacements to the input code @@ -19,11 +20,12 @@ export async function getDemoCode( filePath: string, isDemoFile: boolean = true ): Promise { + // Use resolve with process.cwd() for ISR compatibility const fullPath = isDemoFile - ? process.cwd() + '/src/app/playground/(demos)/' + filePath - : process.cwd() + '/src' + filePath + ? resolve(process.cwd(), 'src', 'app', 'playground', '(demos)', filePath) + : resolve(process.cwd(), 'src', filePath) - const code = await fs.readFile(fullPath, 'utf8') + const code = await readFile(fullPath, 'utf8') // Skip transformations for CSS files if (filePath.endsWith('.css')) { @@ -66,10 +68,10 @@ export async function getDemoCode( export async function loadCodeSandboxDependencies() { const [button, input, card, utils] = await Promise.all([ - getDemoCode('/components/ui/button.tsx', false), - getDemoCode('/components/ui/input.tsx', false), - getDemoCode('/components/ui/card.tsx', false), - getDemoCode('/lib/utils.ts', false), + getDemoCode('components/ui/button.tsx', false), + getDemoCode('components/ui/input.tsx', false), + getDemoCode('components/ui/card.tsx', false), + getDemoCode('lib/utils.ts', false), ]) return { @@ -85,8 +87,8 @@ export async function loadCodeSandboxDependencies() { * Returns CSS content suitable for injection into CodeSandbox preview */ export async function getCodeSandboxGlobalCSS(): Promise { - const cssPath = process.cwd() + '/src/app/globals.css' - const cssContent = await fs.readFile(cssPath, 'utf8') + const cssPath = resolve(process.cwd(), 'src', 'app', 'globals.css') + const cssContent = await readFile(cssPath, 'utf8') const cssReplacements: Array<[RegExp, string]> = [ // Remove @import statements From b25e1dd48b8d59b6a843e8aef6140fe5c8378af8 Mon Sep 17 00:00:00 2001 From: moumensoliman Date: Wed, 26 Nov 2025 19:09:12 +0200 Subject: [PATCH 7/7] fix: updated the effect to depend on currentCode (the string) instead of sandpack.files (the object) This fix for Hex code picker case, To prevent re-rendering --- .../docs/src/components/codesandbox/index.tsx | 60 ++++++++++++++++--- .../src/components/codesandbox/live-tab.tsx | 9 ++- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/packages/docs/src/components/codesandbox/index.tsx b/packages/docs/src/components/codesandbox/index.tsx index 17414b8cf..fabe3d054 100644 --- a/packages/docs/src/components/codesandbox/index.tsx +++ b/packages/docs/src/components/codesandbox/index.tsx @@ -17,15 +17,49 @@ interface CodeSandboxProps { function CodeSync({ onCodeChange }: { onCodeChange: (code: string) => void }) { const { sandpack } = useSandpack() const lastCode = useRef('') + const debounceTimer = useRef(null) - // Sync code changes from Sandpack editor to parent component state + // Debounced sync: only update parent state after user stops typing for 500ms + // This prevents the files object from being recreated on every keystroke useEffect(() => { const file = sandpack.files['/Demo.tsx'] - if (file?.code && file.code !== lastCode.current) { - lastCode.current = file.code - onCodeChange(file.code) + const currentCode = file?.code || '' + + if (currentCode && currentCode !== lastCode.current) { + lastCode.current = currentCode + + // Clear previous timer + if (debounceTimer.current) { + clearTimeout(debounceTimer.current) + } + + // Update parent state after 500ms of no changes + debounceTimer.current = setTimeout(() => { + onCodeChange(currentCode) + }, 500) + } + + return () => { + if (debounceTimer.current) { + clearTimeout(debounceTimer.current) + } + } + }, [sandpack.files['/Demo.tsx']?.code]) // eslint-disable-line react-hooks/exhaustive-deps + + return null +} + +function ResetHandler({ resetTrigger, initialCode }: { resetTrigger: number; initialCode: string }) { + const { sandpack } = useSandpack() + const lastTrigger = useRef(0) + + useEffect(() => { + if (resetTrigger > lastTrigger.current && resetTrigger > 0) { + lastTrigger.current = resetTrigger + // Use Sandpack's updateFile to change code without remounting + sandpack.updateFile('/Demo.tsx', initialCode) } - }, [sandpack.files, onCodeChange]) + }, [resetTrigger, initialCode, sandpack]) return null } @@ -33,6 +67,7 @@ function CodeSync({ onCodeChange }: { onCodeChange: (code: string) => void }) { export function CodeSandbox({ code: initialCode = INITIAL_CODE, syncTrigger, dependencies }: CodeSandboxProps) { const [code, setCode] = useState(initialCode) const [copied, setCopied] = useState(false) + const [resetTrigger, setResetTrigger] = useState(0) const containerRef = useRef(null) const getSearch = useCallback(() => new URLSearchParams(window.location.search).toString(), []) const getTheme = useCallback(() => document.documentElement.classList.contains('dark') ? 'dark' : 'light', []) @@ -46,7 +81,10 @@ export function CodeSandbox({ code: initialCode = INITIAL_CODE, syncTrigger, dep setTimeout(() => setCopied(false), 2000) }, [code]) - const handleReset = useCallback(() => setCode(initialCode), [initialCode]) + const handleReset = useCallback(() => { + setCode(initialCode) + setResetTrigger(prev => prev + 1) + }, [initialCode]) const postToIframes = useCallback((type: string, data: any) => { document.querySelectorAll('iframe').forEach(iframe => @@ -54,11 +92,14 @@ export function CodeSandbox({ code: initialCode = INITIAL_CODE, syncTrigger, dep ) }, []) + // Files object should only be created once with initialCode + // After that, Sandpack manages the code internally + // We don't include 'code' in dependencies to prevent recreation on every edit const files = useMemo(() => ({ ...dependencies, ...SANDPACK_FILES, - '/Demo.tsx': { code, active: true }, - }), [code, dependencies]) + '/Demo.tsx': { code: initialCode, active: true }, + }), [initialCode, dependencies]) // Listen for iframe messages and sync URL state bidirectionally between parent and iframe useEffect(() => { @@ -176,10 +217,11 @@ export function CodeSandbox({ code: initialCode = INITIAL_CODE, syncTrigger, dep autorun: true, autoReload: true, recompileMode: 'immediate', - recompileDelay: 300, + recompileDelay: 700, }} > + ('') const [deps, setDeps] = useState(null) + const [isLoading, setIsLoading] = useState(true) useEffect(() => { + setIsLoading(true) Promise.all([getDemoCode(demoPath), loadCodeSandboxFiles()]).then(([demoCode, dependencies]) => { setCode(demoCode) setDeps(dependencies) + setIsLoading(false) }) }, [demoPath]) - return + if (isLoading || !deps) { + return
Loading...
+ } + + return } \ No newline at end of file