storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.8k stars 9.33k forks source link

[Documentation]: Svelte - Better documentation for decorators/render function #26896

Open rChaoz opened 7 months ago

rChaoz commented 7 months ago

Describe the problem

It's not very clear how to use decorators/the render function in Svelte correctly.

For example, the return value of a decorator function is a Story Result - but it's not quite clear how to use that in Svelte. In React, it's just some JSX, but in Svelte it's an object with 4 undocumented properties - Component and props (self-explaining), but also on and decorator (no idea what they do). It is mentioned that you can choose what component to render and with what props:

return {
    Component: SomeComponent,
    props: { prop1: 1, prop2: 2 }
}

But nothing more than that. The section about custom render functions linked in a couple of pages is missing (choose Svelte and click on "Custom render function" in the table of contents on the right). I tried returning some component in the decorator property, which I realised exists because of TypeScript autocomplete, but it doesn't seem to do anything:

return {
    Component: SomeComponent,
    props: { prop1: 1, prop2: 2 },
    decorator: MyWrapper,
}

I would expect this to render this:

<MyWrapper>
    <SomeComponent {...props} />
</MyWrapper>

But the wrapper component is never actually instantiated. It also seems like you can actually use React hooks in decorators, even when using Svelte. By doing some more digging, this seems to work:

// preview.ts
import type { Preview } from "@storybook/svelte"
import { global } from "@storybook/global"
import { useEffect } from "@storybook/preview-api"

const preview: Preview = {
    decorators: [
        (Story) => {
            useEffect(() => {
                console.log("In effect", global.document.getElementById("storybook-root"))
            }, [])
            return Story()
        }
    ]
}

But this isn't really documented anywhere, The only time this is mentioned is in the "Writing addons" sections. But even there, global is not documented. Both are extremely useful for decorators.

Finally, in this PR it is stated you can set Svelte contexts, which is not stated anywhere in the docs. This is incredibly useful, as the only decorator example given in the docs is () => MarginDecorator, and it doesn't explain how one would set the margin size, for example, based on global arguments. This is actually possible to do (as described in the PR above) like so:

import { setContext } from 'svelte'

const decorator = () => {
    setContext('marginDecoratorSize', 12)
    return MarginDecorator
}

The MarginDecorator component can then do getContext('marginDecoratorSize').

Additional context

No response

JReinhold commented 6 months ago

Some additional context:

... in Svelte it's an object with 4 undocumented properties - Component and props (self-explaining), but also on and decorator (no idea what they do). It is mentioned that you can choose what component to render and with what props:

The decorator in the render-function is an internal construct, and using it when defining stories is not supposed to work - I believe you've figured this out. Using decorators as documented is the way to do it.

It also seems like you can actually use React hooks in decorators, even when using Svelte.

Yes this is a rather strange API choice, the hooks from @storybook/preview-api aren't actually React hooks at all, they just behave very similar and so the same names where chosen. That's why they are framework-agnostic.

But this isn't really documented anywhere, The only time this is mentioned is in the "Writing addons" sections.

We have a long backlog of API docs missing, and preview-api and manager-api are big omissions currently. I can understand why that is frustrating some times. In due time.

But even there, global is not documented. Both are extremely useful for decorators.

global from @storybook/global shouldn't be necessary at all. It's an old artifact that was needed eons ago before browsers and runtimes converged on globalThis. You should be able to do console.log("In effect", document.getElementById("storybook-root")) directly as you'd expect to, and omit the global altogether. We haven't yet confirmed if @storybook/global can be removed everywhere internally, which is why we haven't touched the docs on it yet too.

Finally, in this PR it is stated you can set Svelte contexts, which is not stated anywhere in the docs. This is incredibly useful, as the only decorator example given in the docs is () => MarginDecorator, and it doesn't explain how one would set the margin size, for example, based on global arguments. This is actually possible to do (as described in the PR above) like so:

import { setContext } from 'svelte'

const decorator = () => {
    setContext('marginDecoratorSize', 12)
    return MarginDecorator
}

The MarginDecorator component can then do getContext('marginDecoratorSize').

PRs welcome! If you can articulate this feature and it's usefulness in a meaningful way it would be great if you could submit a PR adding it to the docs!