Skip to content

feat(playground): add Sandpack component playground#349

Open
bntvllnt wants to merge 6 commits into
mainfrom
feat/256-sandpack-playground
Open

feat(playground): add Sandpack component playground#349
bntvllnt wants to merge 6 commits into
mainfrom
feat/256-sandpack-playground

Conversation

@bntvllnt
Copy link
Copy Markdown
Collaborator

@bntvllnt bntvllnt commented May 13, 2026

Closes #256

Summary

  • Add a Sandpack-powered Playground tab beside each component Storybook preview.
  • Add a mobile-friendly /components/[slug]/playground route generated for every registry component.
  • Seed sandboxes from the first registry example with a fallback demo, include Tailwind and @vllnt/ui, and track opens with Vercel Analytics.
  • Keep Sandpack client-only through a dynamic import so the editor bundle is isolated to the playground surfaces.
  • Refresh the generated Sandpack Tailwind config so sandboxed examples load @vllnt/ui/tailwind-preset and scan local sandbox files plus @vllnt/ui package dist/src paths for token and component-internal classes.

Dependency tradeoff

  • Adds @codesandbox/sandpack-react to apps/registry only. This brings Sandpack and CodeMirror editor dependencies for the live-editing experience, but it is route/tab scoped and not part of the package library build.

Fixes in this revision (HEAD dbf081d498863c1d330b277ac2f6cb6022adbd8d)

  1. Pinned sandbox deps@vitejs/plugin-react and vite changed from "latest" to "^5.0.0" / "^7.3.2" in the Sandpack-generated package.json.
  2. Controlled Tabs — replaced defaultValue={activeTab} key={activeTab} (full remount on hash change) with controlled value={activeTab} onValueChange={setActiveTab}; extended TabsProps, TabsTriggerProps, and TabsContentProps in @vllnt/ui and the registry copy to accept value, tabIndex, and aria-hidden.
  3. Mobile tab a11y — Playground TabsTrigger (hidden on mobile) now has aria-hidden="true" and tabIndex={-1}; corresponding TabsContent panel also has aria-hidden="true".
  4. Fail-fast package versiongetRegistryPackageVersion now throws instead of silently returning "latest" when version is undefined.

Verification at HEAD dbf081d498863c1d330b277ac2f6cb6022adbd8d

  • eslint components/playground/sandpack-playground.tsx components/playground/preview-playground-tabs.tsx lib/playground.ts — passed
  • tsc --noEmit (registry) — passed (0 errors)
  • pnpm -F @vllnt/ui build — passed (ESM + DTS)
  • pnpm -F @vllnt/ui-registry build — passed
  • vitest run src/components/tabs/tabs.test.tsx — passed (5 tests)
  • pnpm -F @vllnt/ui lint src/components/tabs/ — passed

Merge note

  • Rebased onto main at 67d3640. Lockfile conflict resolved with --theirs + pnpm install --no-frozen-lockfile.
  • This branch touches apps/registry/package.json and pnpm-lock.yaml; rebase may be needed if another registry dependency PR lands first.

Copy link
Copy Markdown
Collaborator Author

@bntvllnt bntvllnt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — 1 finding (blocking, published as COMMENT)

BLOCKING

  • C1 — Sandpack preview omits the VLLNT Tailwind contract
    • Evidence: apps/registry/components/playground/sandpack-playground.tsx:92-100 generates a sandbox Tailwind config with only ./index.html and ./src/**/*.{ts,tsx} content, theme.extend: {}, and no @vllnt/ui/tailwind-preset/equivalent token theme. The sandbox examples import @vllnt/ui/styles.css and @vllnt/ui/themes/default.css, and both /src/main.tsx plus the fallback demo use token utilities such as bg-background, text-foreground, bg-card, and text-card-foreground. Those utilities are defined by the VLLNT Tailwind preset/theme, not Tailwind defaults.
    • Why it matters: the new playground is meant to render real @vllnt/ui examples, but Tailwind will not generate the design-token utilities or scan the package internals. Published components and the default fallback demo can render unstyled or partially styled in the preview even while the docs page itself looks correct.
    • Fix: make the generated sandbox Tailwind config consume the exported @vllnt/ui/tailwind-preset or inline the same token theme/plugin contract, and include the published package/dist/source paths needed for component-internal class scanning.

WARN

  • None.

VERIFIED CLEAN

  • Re-fetched PR #349 immediately before publishing; head is still 8c4961fd7bdb900848908515345c799252a5f6a0 on feat/256-sandpack-playground.
  • Re-checked the previously found blocker against the live diff and package contract: @vllnt/ui exports ./tailwind-preset, while the generated sandbox config still omits it.
  • Existing GitHub checks are green at this head.

VALIDATION

  • Ran: gh pr view 349 --repo vllnt/ui --json ... before review and before publish.
  • Ran: gh pr checks 349 --repo vllnt/ui (all listed checks passing).
  • Inspected: apps/registry/components/playground/sandpack-playground.tsx, apps/registry/lib/playground.ts, packages/ui/package.json, packages/ui/src/tailwind-preset.ts, and registry/package Tailwind configs.
  • Not run: live browser Sandpack smoke; this publication reconciles the already-local blocker against the current live head.

Note: GitHub rejected REQUEST_CHANGES from the current authenticated account because it is the PR author, so this formal COMMENT review carries the blocking review gate instead of a request-changes verdict.

"/tailwind.config.js": `/** @type {import("tailwindcss").Config} */
export default {
darkMode: ["class"],
content: ["./index.html", "./src/**/*.{ts,tsx}"],
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this generated Tailwind config does not load the VLLNT Tailwind preset/theme or scan @vllnt/ui internals. The sandbox imports @vllnt/ui/styles.css / themes/default.css, and the examples use token utilities such as bg-background, text-foreground, bg-card, and text-card-foreground, but those utilities are not Tailwind defaults. Without @vllnt/ui/tailwind-preset (or the equivalent token theme/plugin contract plus package content scanning), the playground can render real components/default examples unstyled or partially styled even though the docs page itself is styled. Please wire the generated sandbox Tailwind config to the exported VLLNT preset and include the package paths needed for component class scanning.

@vercel vercel Bot temporarily deployed to Preview – ui.vllnt.ai May 13, 2026 22:07 Inactive
@vllnt-pilot vllnt-pilot Bot had a problem deploying to Preview · pr-349-storybook May 18, 2026 17:14 Failure
@vllnt-pilot vllnt-pilot Bot had a problem deploying to Preview · pr-349-storybook May 18, 2026 17:19 Failure
@vllnt-pilot vllnt-pilot Bot had a problem deploying to Preview · pr-349-storybook May 18, 2026 17:19 Failure
@vllnt-pilot vllnt-pilot Bot had a problem deploying to Preview · pr-349-storybook May 18, 2026 17:21 Failure
@bntvllnt bntvllnt had a problem deploying to Preview · pr-349-ui-registry May 18, 2026 18:24 Failure
@vllnt-pilot
Copy link
Copy Markdown

vllnt-pilot Bot commented May 18, 2026

Preview ready · Updated 2026-05-20T14:42:40Z

Service Status Preview
ui-registry Ready https://pr-349-ui-registry.preview.vllnt.ai
Inspect
  • Tailnet-only by default (not reachable from public internet)
  • Cert: real Let's Encrypt wildcard
  • Reply with /clean to destroy this preview now

@vllnt vllnt deleted a comment from vllnt-pilot Bot May 18, 2026
@vllnt vllnt deleted a comment from vercel Bot May 18, 2026
bntvllnt and others added 6 commits May 19, 2026 18:29
- Pin vite to ^7.3.2 and @vitejs/plugin-react to ^5.0.0 in sandbox-generated package.json (was "latest")
- Replace defaultValue+key remount pattern with controlled value+onValueChange on Tabs; extend Tabs/TabsTrigger/TabsContent props to support value, tabIndex, aria-hidden
- Add aria-hidden="true" tabIndex={-1} to Playground TabsTrigger and aria-hidden to TabsContent panel (hidden on mobile)
- getRegistryPackageVersion now throws instead of silently returning "latest" when version is undefined
Copy link
Copy Markdown
Collaborator Author

@bntvllnt bntvllnt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — 1 finding (blocking, published as COMMENT)

BLOCKING

  • A11Y1 — Desktop Playground tab is visible but hidden from assistive tech
    • Evidence: apps/registry/components/playground/preview-playground-tabs.tsx:54-58 renders the Playground tab trigger with aria-hidden="true" and tabIndex={-1} while className="hidden md:inline-flex" makes it visible on desktop. The matching panel at :76-80 is also rendered with aria-hidden="true" while md:block makes it visible on desktop.
    • Why it matters: desktop keyboard and screen-reader users can see (or be told visually about) the new tab, but it is removed from the accessibility tree and skipped by tab navigation. If #playground activates the tab, the visible editor panel is also hidden from assistive technologies.
    • Fix: only hide the mobile-only duplicate path from the accessibility tree. The desktop TabsTrigger/TabsContent should not carry unconditional aria-hidden, and the trigger should remain keyboard reachable when it is visible.

WARN

  • None.

VERIFIED CLEAN

  • Re-fetched PR #349 immediately before publishing; current head is dbf081d498863c1d330b277ac2f6cb6022adbd8d on feat/256-sandpack-playground.
  • Reviewed all 11 changed files: component page integration, dedicated playground route, playground shell/barrel, preview/playground tabs, Sandpack setup, playground utilities, registry package manifest, both Tabs implementations, and lockfile Sandpack entries.
  • The previous Tailwind sandbox blocker is resolved in this head: the generated sandbox now imports @vllnt/ui/tailwind-preset and scans local plus @vllnt/ui package paths.
  • Viewed-state marked for all 11 changed files.

VALIDATION

  • Ran: pnpm install --frozen-lockfile — passed.
  • Ran: pnpm -F @vllnt/ui lint — passed.
  • Ran: pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json — passed.
  • Ran: pnpm -F ui-registry exec tsc --noEmit — passed.
  • Ran: pnpm -F @vllnt/ui build — passed.
  • Ran: pnpm -F ui-registry build — passed; Next generated /components/[slug]/playground for the registry components.
  • Ran: touched registry-file ESLint command — passed for changed lintable files, with one ignored generated registry default file warning.
  • Gap: pnpm -F ui-registry lint crashes in eslint-plugin-jsx-a11y on untouched apps/registry/app/report/report-bug-form.tsx (TypeError: (0 , _minimatch.default) is not a function), so the full registry lint gate is not authoritative for this PR head.

<div className="flex items-center justify-between gap-4 border-b">
<TabsList className="border-b-0">
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this tab is only hidden visually on mobile (hidden md:inline-flex), but aria-hidden="true" and tabIndex={-1} apply at every breakpoint. On desktop the Playground tab is visible while being removed from the accessibility tree and keyboard tab order; the matching panel below is also rendered with aria-hidden="true" while visible at md:block. Please keep the desktop tab/panel accessible and only hide the mobile alternate path from assistive tech.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: live playground on component pages (Sandpack)

1 participant