storybook-addon-code-editor
Version:
A Storybook add-on for live editing stories. Supports React and TypeScript.
273 lines (199 loc) • 6.98 kB
Markdown
add-on for live editing stories. Supports React and TypeScript.
[ ](https://jeremyrh.github.io/storybook-addon-code-editor)
[ ](./example)
This is a [Storybook addon](https://storybook.js.org/addons) that enables live editing of React components with real-time previews.
Think of it like a lightweight [CodeSandbox](https://codesandbox.io), directly in stories or MDX pages.
It uses [Monaco Editor](https://github.com/microsoft/monaco-editor) (VS Code for the browser) for an excellent TypeScript editing experience.
1. Install as a dev dependency:
```sh
npm install --save-dev storybook-addon-code-editor
yarn add --dev storybook-addon-code-editor
```
2. Add `storybook-addon-code-editor` in your `.storybook/main.ts` file and ensure the `staticDirs`, `addons`, and `framework` fields contain the following:
```ts
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
import { getCodeEditorStaticDirs } from 'storybook-addon-code-editor/getStaticDirs';
const config: StorybookConfig = {
staticDirs: [...getCodeEditorStaticDirs(__filename)],
addons: ['storybook-addon-code-editor'],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
```
<details>
<summary>About `staticDirs`</summary>
`staticDirs` sets a list of directories of static files to be loaded by Storybook.
The editor ([monaco-editor](https://github.com/microsoft/monaco-editor)) requires these extra static files to be available at runtime.
Additional static files can be added using the `getExtraStaticDir` helper from `storybook-addon-code-editor/getStaticDirs`:
```ts
// .storybook/main.ts
import {
getCodeEditorStaticDirs,
getExtraStaticDir,
} from 'storybook-addon-code-editor/getStaticDirs';
const config: StorybookConfig = {
staticDirs: [
...getCodeEditorStaticDirs(__filename),
// files will be available at: /monaco-editor/esm/*
getExtraStaticDir('monaco-editor/esm'),
```
</details>
<br />
**Important:**
`@storybook/react-vite` is the only supported framework at this time.
<br />
Use the `Playground` component in [MDX format](https://storybook.js.org/docs/writing-docs/mdx).
```mdx
// MyComponent.stories.mdx
import { Playground } from 'storybook-addon-code-editor'
<Playground code="export default () => <h1>Hello</h1>" />
```
<details>
<summary>More advanced example</summary>
```mdx
// MyComponent.stories.mdx
import { Playground } from 'storybook-addon-code-editor';
import \* as MyLibrary from './index';
import storyCode from './MyStory.source.tsx?raw';
// TypeScript might complain about not finding this import or
// importing things from .d.ts files wihtout `import type`.
// Ignore this, we need the string contents of this file.
// @ts-ignore
import MyLibraryTypes from '../dist/types.d.ts?raw';
<Playground
availableImports={{ 'my-library': MyLibrary }}
code={storyCode}
height="560px"
id="unique id used to save edited code until the page is reloaded"
modifyEditor={(monaco, editor) => {
// editor docs: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneCodeEditor.html
// monaco docs: https://microsoft.github.io/monaco-editor/api/modules/monaco.html
editor.getModel().updateOptions({ tabSize: 2 });
monaco.editor.setTheme('vs-dark');
monaco.languages.typescript.typescriptDefaults.addExtraLib(
MyLibraryTypes,
'file:///node_modules/my-library/index.d.ts',
);
}}
/>
```
</details>
<br />
`Playground` props:
```ts
interface PlaygroundProps {
availableImports?: {
[ ]: {
[ ]: any;
};
};
code?: string;
defaultEditorOptions?: Monaco.editor.IEditorOptions;
height?: string;
id?: string | number | symbol;
modifyEditor?: (monaco: Monaco, editor: Monaco.editor.IStandaloneCodeEditor) => any;
}
```
`React` is automatically imported if `code` does not import it.
React TypeScript definitions will be automatically loaded if `@types/react` is available.
Use the `makeLiveEditStory` function in traditional stories to show a code editor panel:
```ts
// MyComponent.stories.ts
import type { Meta, StoryObj } from '@storybook/react';
import { makeLiveEditStory } from 'storybook-addon-code-editor';
import * as MyLibrary from './index';
import storyCode from './MyStory.source.tsx?raw';
const meta = {
// Story defaults
} satisfies Meta<typeof MyLibrary.MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const MyStory: Story = {
// Story config
};
makeLiveEditStory(MyStory, {
availableImports: { 'my-library': MyLibrary },
code: storyCode,
});
```
`makeLiveEditStory` options:
```ts
interface LiveEditStoryOptions {
availableImports?: {
[ ]: {
[ ]: any;
};
};
code: string;
modifyEditor?: (monaco: Monaco, editor: Monaco.editor.IStandaloneCodeEditor) => any;
defaultEditorOptions?: Monaco.editor.IEditorOptions;
}
```
`setupMonaco` allows customization of [`monaco-editor`](https://github.com/microsoft/monaco-editor).
Use this in your `.storybook/preview.ts` to add type definitions or integrations.
Check out [examples of `monaco-editor`](https://github.com/microsoft/monaco-editor/tree/ae158a25246af016a0c56e2b47df83bd4b1c2426/samples) with different configurations.
```ts
// .storybook/preview.ts
import { setupMonaco } from 'storybook-addon-code-editor';
setupMonaco({
// https://microsoft.github.io/monaco-editor/typedoc/interfaces/Environment.html
monacoEnvironment: {
getWorker(moduleId, label) {
...
},
},
// onMonacoLoad is called when monaco is first loaded, before an editor instance is created.
onMonacoLoad(monaco) {
...
},
});
```
`setupMonaco` options:
```ts
interface MonacoSetup {
monacoEnvironment?: Monaco.Environment;
onMonacoLoad?: (monaco: Monaco) => any;
}
```
<br />
```sh
npm install
```
```sh
npm run start-example
```
When making changes to the library, the server needs to be manually restarted.
```sh
npm run test
```
```sh
npm run format
```
```sh
npm run build
```
Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) to allow automatic versioned releases.
- `fix:` represents bug fixes, and correlates to a SemVer patch.
- `feat:` represents a new feature, and correlates to a SemVer minor.
- `feat!:`, or `fix!:`, `refactor!:`, etc., represent a breaking change (indicated by the !) and will result in a SemVer major.
### Publishing
The automated [release-please](https://github.com/googleapis/release-please) PR to the main branch can be merged to deploy a release.
A Storybook