use document.body as default canvasElement instead of injecting a root element

This commit is contained in:
Jeppe Reinhold 2024-03-08 14:32:15 +01:00
parent fc222b9b97
commit 93b7ed44cb
7 changed files with 108 additions and 243 deletions

View File

@ -4,7 +4,6 @@
- [Portable stories](#portable-stories)
- [Project annotations are now merged instead of overwritten in composeStory](#project-annotations-are-now-merged-instead-of-overwritten-in-composestory)
- [Type change in `composeStories` API](#type-change-in-composestories-api)
- [DOM structure changed in portable stories](#dom-structure-changed-in-portable-stories)
- [Composed Vue stories are now components instead of functions](#composed-vue-stories-are-now-components-instead-of-functions)
- [Tab addons are now routed to a query parameter](#tab-addons-are-now-routed-to-a-query-parameter)
- [Default keyboard shortcuts changed](#default-keyboard-shortcuts-changed)
@ -440,35 +439,6 @@ await Primary.play!(...) // if you want a runtime error when the play function d
There are plans to make the type of the play function be inferred based on your imported story's play function in a near future, so the types will be 100% accurate.
#### DOM structure changed in portable stories
The portable stories API now adds a wrapper to your stories with a unique id based on your story id, such as:
```html
<div data-story="true" id="#storybook-story-button--primary">
<!-- your story here -->
</div>
```
This means that if you take DOM snapshots of your stories, they will be affected and you will have to update them.
The id calculation is based on different heuristics based on your Meta title and Story name. When using `composeStories`, the id can be inferred automatically. However, when using `composeStory` and your story does not explicitly have a `storyName` property, the story name can't be inferred automatically. As a result, its name will be "Unnamed Story", resulting in a wrapper id like `"#storybook-story-button--unnamed-story"`. If the id matters to you and you want to fix it, you have to specify the `exportsName` property like so:
```ts
test("snapshots the story with custom id", () => {
const Primary = composeStory(
stories.Primary,
stories.default,
undefined,
// If you do not want the `unnamed-story` id, you have to pass the name of the story as a parameter
"Primary"
);
const { baseElement } = render(<Primary />);
expect(baseElement).toMatchSnapshot();
});
```
#### Composed Vue stories are now components instead of functions
`composeStory` (and `composeStories`) from `@storybook/vue3` now return Vue components rather than story functions that return components. This means that when rendering these composed stories you just pass the composed story _without_ first calling it.
@ -1105,6 +1075,7 @@ const preview: Preview = {
export default preview;
```
To learn more about the available icons and their names, see the [Storybook documentation](https://storybook.js.org/docs/8.0/faq#what-icons-are-available-for-my-toolbar-or-my-addon).
#### Removals in @storybook/types

View File

@ -56,7 +56,6 @@ export {
filterArgTypes,
sanitizeStoryContextUpdate,
setProjectAnnotations,
getPortableStoryWrapperId,
inferControls,
userOrAutoTitleFromSpecifier,
userOrAutoTitle,

View File

@ -26,10 +26,6 @@ import { normalizeProjectAnnotations } from './normalizeProjectAnnotations';
let globalProjectAnnotations: ProjectAnnotations<any> = {};
export function getPortableStoryWrapperId(storyId: string) {
return `storybook-story-${storyId}`;
}
export function setProjectAnnotations<TRenderer extends Renderer = Renderer>(
projectAnnotations: ProjectAnnotations<TRenderer> | ProjectAnnotations<TRenderer>[]
) {
@ -99,11 +95,7 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
story.playFunction!({
...context,
...extraContext,
// if canvasElement is not provided, we default to the root element, which comes from a decorator
// the decorator has to be implemented in the defaultAnnotations of each integrator of portable stories
canvasElement:
extraContext?.canvasElement ??
globalThis.document?.getElementById(getPortableStoryWrapperId(context.id)),
canvasElement: extraContext?.canvasElement ?? globalThis.document?.body,
})
: undefined;

View File

@ -3,17 +3,12 @@
exports[`Renders CSF2Secondary story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-2-secondary"
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
Children coming from story args!
</button>
</div>
Children coming from story args!
</button>
</div>
</body>
`;
@ -21,17 +16,12 @@ exports[`Renders CSF2Secondary story 1`] = `
exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-2-story-with-params-and-decorator"
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
foo
</button>
</div>
foo
</button>
</div>
</body>
`;
@ -39,17 +29,12 @@ exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
exports[`Renders CSF3Button story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-button"
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
foo
</button>
</div>
foo
</button>
</div>
</body>
`;
@ -57,23 +42,18 @@ exports[`Renders CSF3Button story 1`] = `
exports[`Renders CSF3ButtonWithRender story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-button-with-render"
>
<div>
<p
data-testid="custom-render"
>
I am a custom render function
</p>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
foo
</button>
</div>
<div>
<p
data-testid="custom-render"
>
I am a custom render function
</p>
<button
class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
foo
</button>
</div>
</div>
</body>
@ -82,14 +62,9 @@ exports[`Renders CSF3ButtonWithRender story 1`] = `
exports[`Renders CSF3InputFieldFilled story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-input-field-filled"
>
<input
data-testid="input"
/>
</div>
<input
data-testid="input"
/>
</div>
</body>
`;
@ -97,17 +72,12 @@ exports[`Renders CSF3InputFieldFilled story 1`] = `
exports[`Renders CSF3Primary story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-primary"
<button
class="storybook-button storybook-button--large storybook-button--primary"
type="button"
>
<button
class="storybook-button storybook-button--large storybook-button--primary"
type="button"
>
foo
</button>
</div>
foo
</button>
</div>
</body>
`;
@ -115,21 +85,16 @@ exports[`Renders CSF3Primary story 1`] = `
exports[`Renders LoaderStory story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--loader-story"
>
<div>
<div
data-testid="loaded-data"
>
loaded data
</div>
<div
data-testid="spy-data"
>
mockFn return value
</div>
<div>
<div
data-testid="loaded-data"
>
loaded data
</div>
<div
data-testid="spy-data"
>
mockFn return value
</div>
</div>
</div>

View File

@ -1,9 +1,7 @@
import React from 'react';
import {
composeStory as originalComposeStory,
composeStories as originalComposeStories,
setProjectAnnotations as originalSetProjectAnnotations,
getPortableStoryWrapperId,
} from '@storybook/preview-api';
import type {
Args,
@ -13,7 +11,7 @@ import type {
StoriesWithPartialProps,
} from '@storybook/types';
import * as reactProjectAnnotations from './entry-preview';
import * as defaultProjectAnnotations from './entry-preview';
import type { Meta } from './public-types';
import type { ReactRenderer } from './types';
@ -38,20 +36,6 @@ export function setProjectAnnotations(
originalSetProjectAnnotations<ReactRenderer>(projectAnnotations);
}
// This will not be necessary once we have auto preset loading
const defaultProjectAnnotations: ProjectAnnotations<ReactRenderer> = {
...reactProjectAnnotations,
decorators: [
function addStorybookId(StoryFn, { id }) {
return (
<div data-story id={getPortableStoryWrapperId(id)}>
<StoryFn />
</div>
);
},
],
};
/**
* Function that will receive a story along with meta (e.g. a default export from a .stories file)
* and optionally projectAnnotations e.g. (import * from '../.storybook/preview)

View File

@ -3,17 +3,12 @@
exports[`Renders CSF2Secondary story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-2-secondary"
<button
class="storybook-button storybook-button--secondary storybook-button--medium"
type="button"
>
<button
class="storybook-button storybook-button--secondary storybook-button--medium"
type="button"
>
label coming from story args!
</button>
</div>
label coming from story args!
</button>
</div>
</body>
`;
@ -22,30 +17,7 @@ exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-2-story-with-params-and-decorator"
>
<div
style="margin: 3em;"
>
<button
class="storybook-button storybook-button--secondary storybook-button--medium"
type="button"
>
foo
</button>
</div>
</div>
</div>
</body>
`;
exports[`Renders CSF3Button story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-button"
style="margin: 3em;"
>
<button
class="storybook-button storybook-button--secondary storybook-button--medium"
@ -58,55 +30,30 @@ exports[`Renders CSF3Button story 1`] = `
</body>
`;
exports[`Renders CSF3Button story 1`] = `
<body>
<div>
<button
class="storybook-button storybook-button--secondary storybook-button--medium"
type="button"
>
foo
</button>
</div>
</body>
`;
exports[`Renders CSF3ButtonWithRender story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-button-with-render"
>
<div>
<p
data-testid="custom-render"
>
I am a custom render function
</p>
<button
class="storybook-button storybook-button--secondary storybook-button--medium"
type="button"
>
foo
</button>
</div>
</div>
</div>
</body>
`;
exports[`Renders CSF3InputFieldFilled story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-input-field-filled"
>
<input
data-testid="input"
/>
</div>
</div>
</body>
`;
exports[`Renders CSF3Primary story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--csf-3-primary"
>
<div>
<p
data-testid="custom-render"
>
I am a custom render function
</p>
<button
class="storybook-button storybook-button--primary storybook-button--large"
class="storybook-button storybook-button--secondary storybook-button--medium"
type="button"
>
foo
@ -116,24 +63,42 @@ exports[`Renders CSF3Primary story 1`] = `
</body>
`;
exports[`Renders CSF3InputFieldFilled story 1`] = `
<body>
<div>
<input
data-testid="input"
/>
</div>
</body>
`;
exports[`Renders CSF3Primary story 1`] = `
<body>
<div>
<button
class="storybook-button storybook-button--primary storybook-button--large"
type="button"
>
foo
</button>
</div>
</body>
`;
exports[`Renders LoaderStory story 1`] = `
<body>
<div>
<div
data-story="true"
id="storybook-story-example-button--loader-story"
>
<div>
<div
data-testid="loaded-data"
>
loaded data
</div>
<div
data-testid="spy-data"
>
mockFn return value
</div>
<div>
<div
data-testid="loaded-data"
>
loaded data
</div>
<div
data-testid="spy-data"
>
mockFn return value
</div>
</div>
</div>

View File

@ -2,7 +2,6 @@ import {
composeStory as originalComposeStory,
composeStories as originalComposeStories,
setProjectAnnotations as originalSetProjectAnnotations,
getPortableStoryWrapperId,
} from '@storybook/preview-api';
import type {
Args,
@ -13,20 +12,10 @@ import type {
} from '@storybook/types';
import { h } from 'vue';
import * as vueProjectAnnotations from './entry-preview';
import * as defaultProjectAnnotations from './entry-preview';
import type { Meta } from './public-types';
import type { VueRenderer } from './types';
const defaultProjectAnnotations: ProjectAnnotations<VueRenderer> = {
...vueProjectAnnotations,
decorators: [
function (story, { id }) {
const wrapperProps = { 'data-story': true, id: getPortableStoryWrapperId(id) };
return h('div', wrapperProps, h(story()));
},
],
};
/** Function that sets the globalConfig of your Storybook. The global config is the preview module of your .storybook folder.
*
* It should be run a single time, so that your global config (e.g. decorators) is applied to your stories when using `composeStories` or `composeStory`.