Skip to content

feat(storybook): add @fluentui/react-storybook-addon-playground with in-browser TSX playground - #36728

Draft
Dmytro Kirpa (dmytrokirpa) wants to merge 19 commits into
microsoft:experimental/storybook-playgroundfrom
dmytrokirpa:cursor/storybook-playground-addon-9007
Draft

feat(storybook): add @fluentui/react-storybook-addon-playground with in-browser TSX playground#36728
Dmytro Kirpa (dmytrokirpa) wants to merge 19 commits into
microsoft:experimental/storybook-playgroundfrom
dmytrokirpa:cursor/storybook-playground-addon-9007

Conversation

@dmytrokirpa

@dmytrokirpa Dmytro Kirpa (dmytrokirpa) commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Previous Behavior

Storybook Docs pages only offer "Show code" and the export-to-sandbox buttons (StackBlitz / CodeSandbox). There is no way to edit and run a story's TSX directly in the browser against Fluent UI React v9.

New Behavior

New private workspace package @fluentui/react-storybook-addon-playground (packages/react-components/react-storybook-addon-playground):

  • Storybook addon preset (preset.js)
    • previewAnnotations: registers the withOpenInPlaygroundButton decorator, which adds an Open in Playground button next to "Show code" in Docs mode. It takes the story source from parameters.fullSource (injected by @fluentui/babel-preset-storybook-full-source via the export-to-sandbox addon) and opens ./playground.html#code=<lz-string> in a new tab.
    • staticDirs: serves the pre-built playground app (dist/playground) from the Storybook root, so playground.html is part of the Storybook static output (works in both storybook dev and storybook build).
  • Playground app (src/playground, separate webpack bundle built by the build-playground nx target, which build depends on)
    • Monaco editor with TSX; TSX -> CommonJS transpilation runs off the main thread in Monaco's TypeScript web worker (jsx: react-jsx, importsNotUsedAsValues: preserve so importing a non-allowlisted module is reported right away).
    • TypeScript IntelliSense (auto-completion, quick info, type errors) for the allowlisted dependencies: tools/collect-typings.js walks the transitive .d.ts closure of react, react-dom, @fluentui/react-components(/unstable) and @fluentui/react-icons from the built workspace packages (handles types/typings, sub path packages and typesVersions for Monaco's TS 4.5); a webpack loader emits it as one lazily fetched JSON asset (~300 files, ~530 KB gzipped) that is registered via addExtraLib under virtual file:///node_modules/** paths. Semantic validation is switched on once the types are loaded.
    • Prettier formatting: prettier/standalone + TypeScript parser (lazy chunk) registered as Monaco's document formatting provider, using the repository Prettier settings. Available via the Format toolbar button and Monaco's "Format Document" shortcut (Shift+Alt+F, Ctrl+Shift+I on Linux; the tooltip shows the platform specific one). Syntax errors surface as a toast.
    • Runner extracts require() specifiers, validates them against the allowlist (lazily loaded as separate chunks) and evaluates the module. Component pick order: default -> Default -> first component-like export, so story files work as-is.
    • Preview rendered in a separate React root inside FluentProvider + error boundary; compile / import / runtime / export errors are shown in an error section below the preview.
    • UI: Fluent components on a small flat shell system (one neutral canvas, bordered surfaces, 13px / 12px type scale, color only for the ready / error / warning states). Slim header with Run (Ctrl+Enter / Cmd+Enter, also a Monaco command palette action), Format, Reset, Copy link and a theme Dropdown (Web/Teams light/dark); icon-only toolbar on narrow screens. Pane headers show the IntelliSense and run state. Resizable editor/preview split (role="separator", pointer + keyboard, double-click reset) that stacks below 800px. Editor theme is derived from the active Fluent theme with GitHub-inspired syntax palettes. Code is synced to the URL hash so links are shareable and survive reloads.
  • Jest specs for URL helpers, runner (allowlist, export picking, error kinds), the docs decorator (jsdom), the typings collector (fixture based), the formatter, keyboard shortcut labels and the split-pane hook.
    Repo wiring:
  • Root .storybook/main.js loads the addon via loadWorkspaceAddon; .storybook/preview.js imports its styles.
  • public-docsite-v9 gets the dev dependency, build-storybook dependency and story-glob exclusion.
  • tsconfig.base(.all).json paths, CODEOWNERS.
  • react-storybook-addon-export-to-sandbox: the "Show code" lookup now also ignores .with-open-in-playground-button (change file included).
    Verification: yarn nx run-many -t lint,type-check,test -p react-storybook-addon-playground,react-storybook-addon-export-to-sandbox passes (44 + 50 tests); yarn nx run react-storybook-addon-playground:build succeeds; react-button Storybook in --docs mode injects the button on each story and opens the story in the playground; storybook build --docs output contains playground.html and playground/*.
storybook_playground_feature_demo.mp4

Related Issue(s)

  • n/a

Dmytro Kirpa (dmytrokirpa) and others added 19 commits April 4, 2026 20:41
…or allowlisted dependencies and Prettier formatting
- branded header with primary Run action, compact icon-only toolbar on narrow screens
- pane headers with file name, IntelliSense and run status badges
- resizable editor/preview split (pointer + keyboard), stacked layout on narrow viewports
- Monaco editor theme derived from the active Fluent theme
- preview loading / error placeholders, error bar hint about the last successful render
- Ctrl/Cmd+Enter runs the code from the editor
…playground

- gradient canvas with floating card panes, glass header, gradient brand mark and Run button
- custom status pills (pulsing while compiling), file tab, dotted design-canvas preview with fade-in per run
- custom error panel and empty state
- curated GitHub-inspired syntax palettes for the light and dark editor themes, editor typography tweaks
…sign

- flat neutral canvas, bordered surfaces with a single 8px radius, no gradients / glass / shadows / animations
- standard Fluent primary button for Run, plain toolbar and dividers
- dot + label status indicators (color only for ready / error / warning), spinner while busy
- plain preview surface, flat error panel with a top border

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Same-origin execution of URL-supplied code is unsafe, and shared Storybook targets can omit the required playground assets.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a private Storybook addon providing an in-browser Monaco-based TSX playground for Fluent UI v9 stories.

Changes:

  • Implements editing, compilation, formatting, IntelliSense, preview, themes, and shareable links.
  • Adds the Docs-mode launcher and Storybook static-asset integration.
  • Wires the addon into the public docsite and updates sandbox-addon interoperability.

Merge confidence: 40/100. URL-supplied code currently executes unsandboxed on the Storybook origin, and clean Storybook builds do not consistently build the playground assets.

File summaries
File Description
yarn.lock Registers the workspace package and dependencies.
tsconfig.base.json Adds the package path alias.
tsconfig.base.all.json Adds the complete-workspace alias.
webpack.playground.config.js Bundles the standalone playground.
tsconfig.spec.json Configures test type-checking.
tsconfig.playground.json Configures playground sources.
tsconfig.lib.json Configures addon library output.
tsconfig.json Defines TypeScript project references.
tools/typings-loader.js Emits collected typings as JSON.
tools/playground-typings.json Provides the loader stub asset.
tools/collect-typings.spec.js Tests declaration collection.
tools/collect-typings.js Collects transitive declarations.
src/url.ts Implements shareable code hashes.
src/url.spec.ts Tests URL helpers.
src/types.ts Extends Storybook context types.
src/styles.css Styles the Docs launcher button.
src/public-types.ts Defines addon parameters.
src/preset/preview.ts Registers the decorator.
src/playground/useSplitPane.ts Implements pane resizing.
src/playground/useSplitPane.spec.ts Tests keyboard resizing.
src/playground/useMediaQuery.ts Tracks responsive queries.
src/playground/typings.ts Registers Monaco declaration files.
src/playground/themes.ts Defines selectable Fluent themes.
src/playground/shortcuts.ts Produces platform shortcut labels.
src/playground/shortcuts.spec.ts Tests shortcut labels.
src/playground/runner.ts Evaluates compiled playground code.
src/playground/runner.spec.ts Tests evaluation and allowlisting.
src/playground/Preview.tsx Renders the component preview.
src/playground/Preview.styles.ts Styles the preview surface.
src/playground/Playground.tsx Implements the application shell.
src/playground/Playground.styles.ts Defines shell and responsive styles.
src/playground/playground.html Provides the application document.
src/playground/playground-typings.d.ts Types the generated typings asset.
src/playground/monaco.ts Configures Monaco and workers.
src/playground/modules.ts Defines runtime module loaders.
src/playground/main.tsx Bootstraps the React application.
src/playground/formatter.ts Integrates Prettier with Monaco.
src/playground/formatter.spec.ts Tests TSX formatting.
src/playground/ErrorBoundary.tsx Handles preview render failures.
src/playground/editorTheme.ts Defines Monaco themes.
src/playground/Editor.tsx Manages the Monaco editor.
src/playground/Editor.styles.ts Sizes the editor container.
src/playground/defaultCode.ts Supplies the initial example.
src/playground/compiler.ts Compiles TSX in Monaco’s worker.
src/index.ts Exports the addon API.
src/decorators/withOpenInPlaygroundButton.ts Injects the Docs launcher.
src/decorators/withOpenInPlaygroundButton.spec.ts Tests launcher behavior.
README.md Documents usage and development.
project.json Adds build targets.
preset.js Registers annotations and static assets.
package.json Defines the private package.
LICENSE Adds package licensing.
jest.config.js Configures Jest.
etc/react-storybook-addon-playground.api.md Records the public API.
eslint.config.js Configures package linting.
config/tests.js Adds Jest setup.
config/api-extractor.json Configures API Extractor.
.swcrc Configures SWC compilation.
.babelrc.json Adds Babel annotations.
sandbox-utils.ts Excludes the playground button.
change/...json Adds the sandbox-addon patch entry.
apps/public-docsite-v9/project.json Builds the playground dependency.
apps/public-docsite-v9/package.json Adds the workspace dependency.
apps/public-docsite-v9/.storybook/main.js Excludes addon sources from stories.
.storybook/preview.js Loads launcher styles.
.storybook/main.js Registers the addon globally.
.github/CODEOWNERS Assigns package ownership.
Review details
  • Files reviewed: 65/67 changed files
  • Comments generated: 6
  • Review effort level: Balanced

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +123 to +126
// Evaluating user code is the whole point of the playground; imports are restricted to the allowlist above.
// eslint-disable-next-line no-new-func
const fn = new Function('require', 'exports', 'module', code);
fn(require, module.exports, module);
Comment thread .storybook/main.js
Comment on lines +52 to +53
/** {@link file://./../packages/react-components/react-storybook-addon-playground/package.json} */
loadWorkspaceAddon('@fluentui/react-storybook-addon-playground', { tsConfigPath }),
Comment on lines +27 to +44
rootLight: {
'--pg-canvas': '#f4f5f7',
'--pg-surface': tokens.colorNeutralBackground1,
'--pg-border': '#e3e5e9',
'--pg-muted': '#6b7280',
'--pg-success': '#1a7f37',
'--pg-danger': '#cf222e',
'--pg-warning': '#9a6700',
},
rootDark: {
'--pg-canvas': '#1b1b1c',
'--pg-surface': tokens.colorNeutralBackground1,
'--pg-border': 'rgba(255, 255, 255, 0.1)',
'--pg-muted': '#9ca3af',
'--pg-success': '#3fb950',
'--pg-danger': '#f85149',
'--pg-warning': '#d29922',
},
Comment on lines +305 to +308
const url = `${targetWindow.location.origin}${targetWindow.location.pathname}${createCodeHash(code)}`;
await targetWindow.navigator.clipboard.writeText(url);

notify('Link copied to clipboard', 'success');
Comment on lines +31 to +35
// Curated palettes (GitHub-inspired) that read well on the Fluent light / dark surfaces
const LIGHT_PALETTE: SyntaxPalette = {
foreground: '#1f2328',
comment: '#6e7781',
keyword: '#cf222e',
Comment on lines +26 to +30
## Installation

```sh
yarn add @fluentui/react-storybook-addon-playground
```
@github-actions

Copy link
Copy Markdown

Pull request demo site: URL

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants