From 766395a03e9100c3b2263880ca7e9bc41428339f Mon Sep 17 00:00:00 2001 From: bntvllnt Date: Wed, 13 May 2026 05:06:08 +0200 Subject: [PATCH 1/3] feat(playground): add Sandpack component playground --- apps/registry/app/components/[slug]/page.tsx | 37 +- .../app/components/[slug]/playground/page.tsx | 141 +++++++ .../playground/component-playground-shell.tsx | 43 ++ apps/registry/components/playground/index.ts | 2 + .../playground/preview-playground-tabs.tsx | 64 +++ .../playground/sandpack-playground.tsx | 166 ++++++++ apps/registry/lib/playground.ts | 54 +++ apps/registry/package.json | 1 + pnpm-lock.yaml | 382 ++++++++++++++++++ 9 files changed, 873 insertions(+), 17 deletions(-) create mode 100644 apps/registry/app/components/[slug]/playground/page.tsx create mode 100644 apps/registry/components/playground/component-playground-shell.tsx create mode 100644 apps/registry/components/playground/index.ts create mode 100644 apps/registry/components/playground/preview-playground-tabs.tsx create mode 100644 apps/registry/components/playground/sandpack-playground.tsx create mode 100644 apps/registry/lib/playground.ts diff --git a/apps/registry/app/components/[slug]/page.tsx b/apps/registry/app/components/[slug]/page.tsx index 827ecfa4..d3ebfa66 100644 --- a/apps/registry/app/components/[slug]/page.tsx +++ b/apps/registry/app/components/[slug]/page.tsx @@ -7,15 +7,15 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; +import { PreviewPlaygroundTabs } from "@/components/playground"; import { QuickAdd } from "@/components/quick-add"; -import { StorybookEmbed } from "@/components/storybook-embed"; import componentMetadata from "@/lib/component-metadata.json"; -import { - breadcrumbLd, - jsonLdScript, - softwareSourceCodeLd, -} from "@/lib/jsonld"; +import { breadcrumbLd, jsonLdScript, softwareSourceCodeLd } from "@/lib/jsonld"; import { generateOGMetadata, generateTwitterMetadata } from "@/lib/og"; +import { + getPlaygroundExample, + getRegistryPackageVersion, +} from "@/lib/playground"; import { canonical } from "@/lib/seo"; import { getCategoryForComponent, @@ -104,6 +104,8 @@ export default async function ComponentPage(props: Props) { const meta = metadata_map[slug]; const displayTitle = meta?.title ?? component.title ?? component.name; const displayDescription = meta?.description ?? component.description ?? ""; + const playgroundExample = getPlaygroundExample(component); + const registryPackageVersion = getRegistryPackageVersion(registry.version); // Read component source for code display let componentCode = ""; @@ -159,6 +161,10 @@ export default async function ComponentPage(props: Props) { const sections = [ { id: "installation", title: "Installation" }, + ...(meta?.defaultStoryId ? [{ id: "preview", title: "Preview" }] : []), + ...(meta?.defaultStoryId + ? [{ id: "playground", title: "Playground" }] + : []), ...(meta?.defaultStoryId ? [{ id: "storybook", title: "Storybook" }] : []), ...(componentCode ? [{ id: "code", title: "Code" }] : []), ...(component.dependencies && component.dependencies.length > 0 @@ -166,8 +172,7 @@ export default async function ComponentPage(props: Props) { : []), ] as { id: string; title: string }[]; - const SITE_URL = - process.env.NEXT_PUBLIC_SITE_URL ?? "https://ui.vllnt.ai"; + const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://ui.vllnt.ai"; return ( <> @@ -221,16 +226,14 @@ export default async function ComponentPage(props: Props) { - {/* Preview — Storybook Embed */} + {/* Preview + Playground */} {meta?.defaultStoryId ? ( -
-
- -
-
+ ) : null} {/* Installation */} diff --git a/apps/registry/app/components/[slug]/playground/page.tsx b/apps/registry/app/components/[slug]/playground/page.tsx new file mode 100644 index 00000000..0186e573 --- /dev/null +++ b/apps/registry/app/components/[slug]/playground/page.tsx @@ -0,0 +1,141 @@ +import { Breadcrumb, Sidebar } from "@vllnt/ui"; +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound } from "next/navigation"; + +import { ComponentPlaygroundShell } from "@/components/playground"; +import componentMetadata from "@/lib/component-metadata.json"; +import { generateOGMetadata, generateTwitterMetadata } from "@/lib/og"; +import { + getPlaygroundExample, + getRegistryPackageVersion, +} from "@/lib/playground"; +import { canonical } from "@/lib/seo"; +import { + getCategoryForComponent, + getSidebarSections, +} from "@/lib/sidebar-sections"; +import registryData from "@/registry.json"; +import type { Registry, RegistryComponent } from "@/types/registry"; + +type Props = { + params: Promise<{ slug: string }>; +}; + +const registry = registryData as Registry; +const metadata_map = componentMetadata as Record< + string, + { + category: string; + defaultStoryId: string; + description: string; + name: string; + stories: { id: string; name: string }[]; + title: string; + } +>; + +function findComponent(slug: string): RegistryComponent | undefined { + return registry.items.find( + (item): item is RegistryComponent => + item.name === slug && item.type === "registry:component", + ); +} + +export async function generateStaticParams() { + return registry.items + .filter( + (item): item is RegistryComponent => item.type === "registry:component", + ) + .map((item) => ({ + slug: item.name, + })); +} + +export async function generateMetadata(props: Props): Promise { + const { slug } = await props.params; + const component = findComponent(slug); + + if (!component) { + return {}; + } + + const meta = metadata_map[slug]; + const title = meta?.title ?? component.title; + const description = + meta?.description ?? + component.description ?? + "Edit a live VLLNT UI component sandbox."; + + const ogParameters = { + category: getCategoryForComponent(slug), + description, + title, + type: "component" as const, + }; + + return { + alternates: { canonical: canonical(`/components/${slug}/playground`) }, + description, + openGraph: generateOGMetadata(ogParameters), + title: `${title} Playground - VLLNT UI`, + twitter: generateTwitterMetadata(ogParameters), + }; +} + +export default async function ComponentPlaygroundPage(props: Props) { + const { slug } = await props.params; + const component = findComponent(slug); + + if (!component) { + notFound(); + } + + const meta = metadata_map[slug]; + const displayTitle = meta?.title ?? component.title ?? component.name; + const displayDescription = + meta?.description ?? component.description ?? "Edit this component live."; + const playgroundExample = getPlaygroundExample(component); + const registryPackageVersion = getRegistryPackageVersion(registry.version); + + return ( + <> + +
+
+ +
+
+

+ {displayTitle} Playground +

+

+ {displayDescription} +

+
+ + Back to component + +
+ +
+
+ + ); +} diff --git a/apps/registry/components/playground/component-playground-shell.tsx b/apps/registry/components/playground/component-playground-shell.tsx new file mode 100644 index 00000000..d4e69a85 --- /dev/null +++ b/apps/registry/components/playground/component-playground-shell.tsx @@ -0,0 +1,43 @@ +"use client"; + +import * as React from "react"; + +import dynamic from "next/dynamic"; + +import type { PlaygroundExample } from "@/lib/playground"; + +type ComponentPlaygroundShellProps = { + componentName: string; + example: PlaygroundExample; + packageVersion: string; + surface: "inline" | "route"; +}; + +const SandpackPlayground = dynamic( + () => + import("./sandpack-playground").then((module) => module.SandpackPlayground), + { + loading: () => ( +
+

Loading playground...

+
+ ), + ssr: false, + }, +); + +export function ComponentPlaygroundShell({ + componentName, + example, + packageVersion, + surface, +}: ComponentPlaygroundShellProps): React.ReactElement { + return ( + + ); +} diff --git a/apps/registry/components/playground/index.ts b/apps/registry/components/playground/index.ts new file mode 100644 index 00000000..0966d2a2 --- /dev/null +++ b/apps/registry/components/playground/index.ts @@ -0,0 +1,2 @@ +export { ComponentPlaygroundShell } from "./component-playground-shell"; +export { PreviewPlaygroundTabs } from "./preview-playground-tabs"; diff --git a/apps/registry/components/playground/preview-playground-tabs.tsx b/apps/registry/components/playground/preview-playground-tabs.tsx new file mode 100644 index 00000000..7c8effd0 --- /dev/null +++ b/apps/registry/components/playground/preview-playground-tabs.tsx @@ -0,0 +1,64 @@ +"use client"; + +import * as React from "react"; + +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@vllnt/ui"; +import { ExternalLink } from "lucide-react"; +import Link from "next/link"; + +import type { PlaygroundExample } from "@/lib/playground"; + +import { StorybookEmbed } from "../storybook-embed"; + +import { ComponentPlaygroundShell } from "./component-playground-shell"; + +type PreviewPlaygroundTabsProps = { + componentName: string; + example: PlaygroundExample; + packageVersion: string; + storyId: string; +}; + +export function PreviewPlaygroundTabs({ + componentName, + example, + packageVersion, + storyId, +}: PreviewPlaygroundTabsProps): React.ReactElement { + return ( +
+ +
+ + Preview + + Playground + + + + Open in playground + + +
+ +
+ +
+
+ +
+ +
+
+
+
+ ); +} diff --git a/apps/registry/components/playground/sandpack-playground.tsx b/apps/registry/components/playground/sandpack-playground.tsx new file mode 100644 index 00000000..516c6357 --- /dev/null +++ b/apps/registry/components/playground/sandpack-playground.tsx @@ -0,0 +1,166 @@ +"use client"; + +import * as React from "react"; + +import { + SandpackCodeEditor, + SandpackLayout, + SandpackPreview, + SandpackProvider, +} from "@codesandbox/sandpack-react"; +import { track } from "@vercel/analytics"; + +import type { PlaygroundExample } from "@/lib/playground"; + +type SandpackPlaygroundProps = { + componentName: string; + example: PlaygroundExample; + packageVersion: string; + surface: "inline" | "route"; +}; + +function buildDependencies(packageVersion: string): Record { + return { + "@vitejs/plugin-react": "latest", + "@vllnt/ui": packageVersion, + autoprefixer: "^10.4.20", + "class-variance-authority": "^0.7.1", + clsx: "^2.1.1", + "lucide-react": "^0.468.0", + postcss: "^8.5.0", + react: "^19.2.0", + "react-dom": "^19.2.0", + "tailwind-merge": "^3.3.1", + tailwindcss: "^3.4.17", + vite: "latest", + }; +} + +function buildFiles( + exampleCode: string, + packageVersion: string, +): Record { + return { + "/package.json": JSON.stringify( + { + dependencies: buildDependencies(packageVersion), + devDependencies: {}, + scripts: { + dev: "vite", + }, + }, + undefined, + 2, + ), + "/postcss.config.js": `export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; +`, + "/src/Demo.tsx": exampleCode, + "/src/main.tsx": `import * as React from "react"; +import { createRoot } from "react-dom/client"; + +import "@vllnt/ui/styles.css"; +import "@vllnt/ui/themes/default.css"; +import "./styles.css"; +import { Demo } from "./Demo"; + +createRoot(document.getElementById("root")!).render( + +
+ +
+
, +); +`, + "/src/styles.css": `@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + color-scheme: light; +} + +body { + margin: 0; + min-width: 320px; +} +`, + "/tailwind.config.js": `/** @type {import("tailwindcss").Config} */ +export default { + darkMode: ["class"], + content: ["./index.html", "./src/**/*.{ts,tsx}"], + theme: { + extend: {}, + }, + plugins: [], +}; +`, + }; +} + +export function SandpackPlayground({ + componentName, + example, + packageVersion, + surface, +}: SandpackPlaygroundProps): React.ReactElement { + React.useEffect(() => { + track("playground_open", { + component: componentName, + packageVersion, + surface, + }); + }, [componentName, packageVersion, surface]); + + const dependencies = React.useMemo( + () => buildDependencies(packageVersion), + [packageVersion], + ); + const files = React.useMemo( + () => buildFiles(example.code, packageVersion), + [example.code, packageVersion], + ); + + return ( +
+
+

{example.title}

+

{example.description}

+
+ + + + + + +
+ ); +} diff --git a/apps/registry/lib/playground.ts b/apps/registry/lib/playground.ts new file mode 100644 index 00000000..83243bbf --- /dev/null +++ b/apps/registry/lib/playground.ts @@ -0,0 +1,54 @@ +import type { RegistryComponent, UsageExample } from "@/types/registry"; + +export type PlaygroundExample = { + code: string; + description: string; + title: string; +}; + +const FALLBACK_CODE = `import { Button } from "@vllnt/ui"; + +export function Demo() { + return ( +
+

Start from the registry

+

+ Edit this sandbox with any VLLNT UI component import, then install the + component from its registry JSON when you are ready to move it into your app. +

+ +
+ ); +} +`; + +function normalizeExample(example: UsageExample): PlaygroundExample { + return { + code: example.code, + description: + example.description ?? + "Edit the component example and inspect the rendered result.", + title: example.title, + }; +} + +export function getPlaygroundExample( + component: RegistryComponent, +): PlaygroundExample { + const [firstExample] = component.examples ?? []; + + if (firstExample?.code) { + return normalizeExample(firstExample); + } + + return { + code: FALLBACK_CODE, + description: + "This component does not have a curated inline example yet. Start from the default VLLNT UI sandbox and swap in the component you want to test.", + title: "Default sandbox", + }; +} + +export function getRegistryPackageVersion(version: string | undefined): string { + return version ? `^${version}` : "latest"; +} diff --git a/apps/registry/package.json b/apps/registry/package.json index e00e1b04..6af3ca58 100644 --- a/apps/registry/package.json +++ b/apps/registry/package.json @@ -15,6 +15,7 @@ "sync-storybook": "tsx scripts/generate-component-metadata.ts" }, "dependencies": { + "@codesandbox/sandpack-react": "^2.20.0", "@mdx-js/mdx": "^3.1.1", "@next/mdx": "^16.0.7", "@vercel/analytics": "^1.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40e57535..61f65494 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: apps/registry: dependencies: + '@codesandbox/sandpack-react': + specifier: ^2.20.0 + version: 2.20.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@mdx-js/mdx': specifier: ^3.1.1 version: 3.1.1 @@ -616,6 +619,45 @@ packages: peerDependencies: storybook: ^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 + '@codemirror/autocomplete@6.20.2': + resolution: {integrity: sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==} + + '@codemirror/commands@6.10.3': + resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==} + + '@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.5': + resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==} + + '@codemirror/language@6.12.3': + resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==} + + '@codemirror/lint@6.9.6': + resolution: {integrity: sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A==} + + '@codemirror/state@6.6.0': + resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==} + + '@codemirror/view@6.42.1': + resolution: {integrity: sha512-ToN3oFc0nsxNUYVF5P0ztLgbC4UPPjPtA9aKYhkOKQaZASpOUo6ISXyQLP66ctVwlDc+j6Jv0uK5IFALkiXztg==} + + '@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 + '@convex-dev/eslint-plugin@1.1.1': resolution: {integrity: sha512-4NsTWNJJLPbti10LZsV1/7UkbaMPFxNz5Ekd3yW5bDkaoU1I0b4TJxk0V+ShbNFTJ2fSqTxm+iGy9XSNCmAoVA==} peerDependencies: @@ -1541,6 +1583,24 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@lezer/common@1.5.2': + resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==} + + '@lezer/css@1.3.3': + resolution: {integrity: sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/html@1.3.13': + resolution: {integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==} + + '@lezer/javascript@1.5.4': + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + + '@lezer/lr@1.4.10': + resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} + '@lit-labs/react@2.1.3': resolution: {integrity: sha512-OD9h2JynerBQUMNzb563jiVpxfvPF0HjQkKY2mx0lpVYvD7F+rtJpOGz6ek+6ufMidV3i+MPT9SX62OKWHFrQg==} @@ -1549,6 +1609,9 @@ packages: peerDependencies: '@types/react': 17 || 18 || 19 + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} @@ -2297,6 +2360,16 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@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' + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -2465,6 +2538,9 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@stitches/core@1.2.8': + resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==} + '@storybook/addon-a11y@10.2.17': resolution: {integrity: sha512-J0ogEc4/XFC+Ytz+X1we6TOKreEk/shgUs/mtxdsLa0xJ6bp2n2OQPSjNtQHH/nK4SRBSfHWPm8ztfcXTzeG9w==} peerDependencies: @@ -3261,6 +3337,9 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + anser@2.3.5: + resolution: {integrity: sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -3441,6 +3520,9 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.9.19: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true @@ -3475,6 +3557,9 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + builtin-modules@5.0.0: resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} engines: {node: '>=18.20'} @@ -3615,6 +3700,9 @@ packages: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} + clean-set@1.1.2: + resolution: {integrity: sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -3777,6 +3865,9 @@ packages: typescript: optional: true + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3838,6 +3929,10 @@ packages: resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} engines: {node: '>=12'} + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -4127,9 +4222,20 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + 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==} @@ -4155,6 +4261,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==} @@ -4314,6 +4423,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'} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4367,6 +4480,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + eventsource-parser@3.0.6: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} @@ -4417,6 +4533,9 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -4845,6 +4964,9 @@ packages: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4897,6 +5019,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + 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. + ip-address@10.0.1: resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} @@ -5786,6 +5912,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.1.6: resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} engines: {node: '>=20.9.0'} @@ -5920,6 +6049,9 @@ packages: resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} engines: {node: '>=0.10.0'} + outvariant@1.4.0: + resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} + outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -6217,6 +6349,9 @@ packages: peerDependencies: react: '>=16.8.0' + react-devtools-inline@4.4.0: + resolution: {integrity: sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==} + react-docgen-typescript@2.4.0: resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} peerDependencies: @@ -6622,6 +6757,9 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + static-browser-server@1.0.3: + resolution: {integrity: sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -6646,6 +6784,9 @@ packages: prettier: optional: true + strict-event-emitter@0.4.6: + resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==} + strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -6742,6 +6883,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -7013,6 +7157,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==} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -7144,6 +7291,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-to-istanbul@9.3.0: @@ -7288,6 +7436,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'} @@ -7827,6 +7978,114 @@ snapshots: - '@chromatic-com/cypress' - '@chromatic-com/playwright' + '@codemirror/autocomplete@6.20.2': + dependencies: + '@codemirror/language': 6.12.3 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.42.1 + '@lezer/common': 1.5.2 + + '@codemirror/commands@6.10.3': + dependencies: + '@codemirror/language': 6.12.3 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.42.1 + '@lezer/common': 1.5.2 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.20.2 + '@codemirror/language': 6.12.3 + '@codemirror/state': 6.6.0 + '@lezer/common': 1.5.2 + '@lezer/css': 1.3.3 + + '@codemirror/lang-html@6.4.11': + dependencies: + '@codemirror/autocomplete': 6.20.2 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/language': 6.12.3 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.42.1 + '@lezer/common': 1.5.2 + '@lezer/css': 1.3.3 + '@lezer/html': 1.3.13 + + '@codemirror/lang-javascript@6.2.5': + dependencies: + '@codemirror/autocomplete': 6.20.2 + '@codemirror/language': 6.12.3 + '@codemirror/lint': 6.9.6 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.42.1 + '@lezer/common': 1.5.2 + '@lezer/javascript': 1.5.4 + + '@codemirror/language@6.12.3': + dependencies: + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.42.1 + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.6': + dependencies: + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.42.1 + crelt: 1.0.6 + + '@codemirror/state@6.6.0': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.42.1': + dependencies: + '@codemirror/state': 6.6.0 + 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.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@codemirror/autocomplete': 6.20.2 + '@codemirror/commands': 6.10.3 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/language': 6.12.3 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.42.1 + '@codesandbox/sandpack-client': 2.19.8 + '@lezer/highlight': 1.2.3 + '@react-hook/intersection-observer': 3.1.2(react@19.2.4) + '@stitches/core': 1.2.8 + anser: 2.3.5 + clean-set: 1.1.2 + dequal: 2.0.3 + escape-carriage: 1.3.1 + lz-string: 1.5.0 + react: 19.2.4 + react-devtools-inline: 4.4.0 + react-dom: 19.2.4(react@19.2.4) + react-is: 17.0.2 + '@convex-dev/eslint-plugin@1.1.1(convex@1.31.7(react@19.2.4))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) @@ -8583,6 +8842,34 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@lezer/common@1.5.2': {} + + '@lezer/css@1.3.3': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.5.2 + + '@lezer/html@1.3.13': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/lr@1.4.10': + dependencies: + '@lezer/common': 1.5.2 + '@lit-labs/react@2.1.3(@types/react@19.2.13)': dependencies: '@lit/react': 1.0.8(@types/react@19.2.13) @@ -8593,6 +8880,8 @@ snapshots: dependencies: '@types/react': 19.2.13 + '@marijn/find-cluster-break@1.0.2': {} + '@mdx-js/mdx@3.1.1': dependencies: '@types/estree': 1.0.8 @@ -9408,6 +9697,16 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@react-hook/intersection-observer@3.1.2(react@19.2.4)': + dependencies: + '@react-hook/passive-layout-effect': 1.2.1(react@19.2.4) + intersection-observer: 0.10.0 + react: 19.2.4 + + '@react-hook/passive-layout-effect@1.2.1(react@19.2.4)': + dependencies: + react: 19.2.4 + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/pluginutils@5.3.0(rollup@4.59.0)': @@ -9519,6 +9818,8 @@ snapshots: '@standard-schema/utils@0.3.0': {} + '@stitches/core@1.2.8': {} + '@storybook/addon-a11y@10.2.17(storybook@10.2.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: '@storybook/global': 5.0.0 @@ -10411,6 +10712,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + anser@2.3.5: {} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -10628,6 +10931,8 @@ snapshots: balanced-match@4.0.4: {} + base64-js@1.5.1: {} + baseline-browser-mapping@2.9.19: {} binary-extensions@2.3.0: {} @@ -10673,6 +10978,11 @@ snapshots: buffer-from@1.1.2: {} + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + builtin-modules@5.0.0: {} bundle-name@4.1.0: @@ -10793,6 +11103,8 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 + clean-set@1.1.2: {} + clean-stack@2.2.0: {} cli-cursor@5.0.0: @@ -10916,6 +11228,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 + crelt@1.0.6: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -10974,6 +11288,11 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + damerau-levenshtein@1.0.8: {} data-uri-to-buffer@4.0.1: {} @@ -11279,8 +11598,26 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + 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-error@4.1.1: {} + 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 @@ -11384,6 +11721,8 @@ snapshots: escalade@3.2.0: {} + escape-carriage@1.3.1: {} + escape-html@1.0.3: {} escape-string-regexp@1.0.5: {} @@ -11611,6 +11950,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 + espree@10.4.0: dependencies: acorn: 8.15.0 @@ -11668,6 +12014,11 @@ snapshots: etag@1.8.1: {} + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + eventsource-parser@3.0.6: {} eventsource@3.0.7: @@ -11760,6 +12111,10 @@ snapshots: transitivePeerDependencies: - supports-color + ext@1.7.0: + dependencies: + type: 2.7.3 + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 @@ -12240,6 +12595,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -12282,6 +12639,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + intersection-observer@0.10.0: {} + ip-address@10.0.1: {} ipaddr.js@1.9.1: {} @@ -13622,6 +13981,8 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + next-tick@1.1.0: {} + next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@next/env': 16.1.6 @@ -13797,6 +14158,8 @@ snapshots: os-homedir@1.0.2: {} + outvariant@1.4.0: {} + outvariant@1.4.3: {} own-keys@1.0.1: @@ -14062,6 +14425,10 @@ snapshots: date-fns-jalali: 4.1.0-0 react: 19.2.4 + react-devtools-inline@4.4.0: + dependencies: + es6-symbol: 3.1.4 + react-docgen-typescript@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -14657,6 +15024,13 @@ snapshots: stackback@0.0.2: {} + static-browser-server@1.0.3: + dependencies: + '@open-draft/deferred-promise': 2.2.0 + dotenv: 16.0.3 + mime-db: 1.54.0 + outvariant: 1.4.3 + statuses@2.0.2: {} std-env@3.10.0: {} @@ -14691,6 +15065,8 @@ snapshots: - react-dom - utf-8-validate + strict-event-emitter@0.4.6: {} + strict-event-emitter@0.5.1: {} string-length@4.0.2: @@ -14811,6 +15187,8 @@ snapshots: strip-json-comments@3.1.1: {} + style-mod@4.1.3: {} + style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -15086,6 +15464,8 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + type@2.7.3: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -15358,6 +15738,8 @@ snapshots: - tsx - yaml + w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 From 8c4961fd7bdb900848908515345c799252a5f6a0 Mon Sep 17 00:00:00 2001 From: bntvllnt Date: Wed, 13 May 2026 05:08:23 +0200 Subject: [PATCH 2/3] fix(playground): support hash navigation --- .../playground/preview-playground-tabs.tsx | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/registry/components/playground/preview-playground-tabs.tsx b/apps/registry/components/playground/preview-playground-tabs.tsx index 7c8effd0..3f725291 100644 --- a/apps/registry/components/playground/preview-playground-tabs.tsx +++ b/apps/registry/components/playground/preview-playground-tabs.tsx @@ -25,9 +25,34 @@ export function PreviewPlaygroundTabs({ packageVersion, storyId, }: PreviewPlaygroundTabsProps): React.ReactElement { + const [activeTab, setActiveTab] = React.useState("preview"); + + React.useEffect(() => { + function selectHashTab(): void { + if (window.location.hash === "#playground") { + setActiveTab("playground"); + } else if (window.location.hash === "#preview") { + setActiveTab("preview"); + } + } + + selectHashTab(); + window.addEventListener("hashchange", selectHashTab); + + return () => { + window.removeEventListener("hashchange", selectHashTab); + }; + }, []); + return (
- +