-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add scopes to pages, components and stories #18235
Comments
Hi @Dschungelabenteuer !! This looks fantastic and resembles a proposal we are kicking around: https://www.notion.so/chromatic-ui/Story-tags-586c7d9ff24f4792bc42a7d840b1142b. Can you please check this out and let's discuss how to merge the two proposals? We were planning to work on this in 7.x, but could potentially do it as part of 7.0 especially if you can help make it happen. Also, related issue: #7711 |
Hey @shilman ! This is awesome, thanks for letting me know! I gave it a quick read, this indeed looks pretty similar (which may mean I did not completely miss the point, I'm so glad 😊). Of course I'd be happy to help make it happen! I'll give it an in-depth read tomorrow. Should I comment the notion page or is it rather for maintainers/chromatic team? |
I was just looking for a way to make it possible to search my storybook library according to tags, and not just component names, so this looks awesome! |
Thanks for such a comprehensive doc. We now have tags implemented behind the scenes and are considering the UX for it. @Dschungelabenteuer if you're still open to it, we'd love to get your feedback on a few design prototypes. |
@domyen of course I am :) I gather we're talking about UI design prototypes here, since tags are now a thing? |
Yup, UI design prototypes. |
Proposal: Storybook scopes
Filter out stories and documentation pages based on pre-defined scopes.
Try it out | Demo repository
Motivation
I use Storybook on a daily basis: besides being a major part of my UI development workflow, it also holds some technical documentation I write for my dear colleagues. Some might prefer using dedicated tools, but I do like to keep things centralized and Storybook's docs addon just works great to me. I'm especially addicted to MDX pages which let me include React components to get my documentation even more interactive and impactful.
It ended up being the single source of truth for a few different target audiences who would occasionally browse the Storybook to read the few pages which are relevant to them:
I would like to keep things as much clean and straight-forward as possible for these target audiences: I would like to be able to share my Storybook to different target audiences in a way that only the pages, components and stories that are relevant to them are displayed.
Principles and concerns
From the perspective of any maintainer of any Storybook
As the main maintainer of our Storybook, there are three concerns I want to keep in mind while meeting my above needs:
From the perspective of Storybook's governance
preview.ts
).storyStorev7
.Early considerations
StorybookConfig
interface explicitly sets thestories
property as an array and I have no idea if this could fulfill my needs.Proposal overview
The simplest and most generic solution i've came up with is the idea of scopes. One would define specific pages, components or stories as part of a specific scope (which could represent what I previously called a target audience or just a documentation category). These scopes could then be applied to the Storybook to filter out pages and stories to only display the ones relevant to the applied scope. This solution could work through three different steps:
1. Define the scope list
We first need to define the list of scopes which will be available in the Storybook. I'd assume the best place to define these would be as a
globalType
within thepreview
configuration file, since they can easily be accessed from addons and the Storybook manager.2. Scope pages, components or stories
To keep things consistent and facilitate adoption, the ideal approach to define page/component or story scopes would be to stick as much as possible to the way parameters are defined, which is a known pattern in Storybook (parameters, decorators, etc). Then why wouldn't we just define scopes as parameters? Here are some reasons:
Meta-level (page/component) scope definition
Story-level scope definition
3. Apply scope
Implementation details
Unfortunately, this does not seem to be achievable independantly from Storybook's codebase by writing a simple standalone addon because these can only interact with tabs, panels and toolbar. What we are trying to achieve, however, requires changes within Storybook's sidebar.
Scope API
We need a new API module which will add a state property to Storybook's context:
When initialized, the module sets the following state property with these default values:
The initialization process waits for the
SET_GLOBALS
core event to get the available scopes and the default scope fromglobalTypes
if the active scope was not set by ascope
global (e.g. from URL Query Parameters):The API exposes the following methods:
getActiveScope
async setActiveScope
scope: string
getScopes
async setScopes
scopes: string[]
async setScopeGlobal
scopes: string[]
Implement scope logic in Sidebar
The logic is pretty straight-forward: when a scope is applied (
state.scope.active !== undefined
), stories displayed in the sidebar are filtered to only show the ones matching the applied scope or the ones with no scopes defined (this means they're not scoped and visible for everyone). This happens in the@storybook/ui
package:useScope
function to filter out stories before they're passed down to consumer child components.@storybook/ui
, let's also develop a ScopePicker component to let users easily switch scopes from the Sidebar.The base logic is ready, but we're still missing a major point: the list of stories (as we know it in the Sidebar component) do not hold any information about scopes. It can't just work out of the box.
Have the list of stories know about scopes
If you use the on-demand architecture (
storyStoreV7
feature)If you use the (until then) default architecture
Despite lazy-loading stories, Storybook still calculates an initial Story Index so that all stories appear in the Sidebar, even though they still haven't been evaluated. This Story Index is served as soon as possible from
@storybook/core-server
via the./stories.json
endpoint. Under the hood, this Story Index is built with the help of the@storybook/csf-tools
package, and more specifically with theCsfFile
class. It basically traverses a CSF file's AST and feeds properties which represent CSF content: "meta" and "stories".@storybook/csf-tools
's side, we have to feed both properties with possibly defined scopes.@storybook/core-server
's side, we have to make sure scopes are actually included in the raw output as we extract stories. This is where we apply the "inheritance logic" (1).All of the stories are loaded at bootup time and available client-side. The Story Index is served based on
@storybook/client-api
'sStoryStoreFacade
which gathers stories-related data it collected when booting up Storybook. Under the hood, it uses@storybook/store
's CSF processing utilities to build the list of raw stories:prepareStory
function, we have to include the possibly defined scopes of the story. This is where we apply the "inheritance logic" (1):normalizeComponentAnnotations
since it does not filter out properties it doesn't know.normalizeStory
function output otherwise it will get filtered out even though they're part of the story annotations.(1): The inheritance logic makes story-level scopes take precedence over page/component-level ones (without merging any of them). This means scopes will use the following decision tree:
Have pages, components and stories propagate their scopes up the hierarchy
Imagine a story which is only scoped for "Designers" in a component which is only scoped for "Developers (Team A)". As a designer, to be able to navigate to this story from the sidebar, I need to have access to the component it belongs to: how can a leaf still live in the tree if its branch is cut down? (not sure that image makes any sense :-]).
Why aren't we doing this directly where we're applying the "inheritance logic"?
This is precisely and probably when we want to propagate scopes up the hierarchy. This happens in
@storybook/api
'stransformStoriesRawToStoriesHash
which is eventually called from both default and on-demand architectures.What about MDX?
Since MDX is transformed into CSF under the hood, we need to step into this transformation process. This is handled by
@storybook/mdx1-csf
and@storybook/mdx2-csf
packages which both are outside Storybook's monorepository.We need a quick change in both of them so that this:
gets transformed into some CSF code containing our scopes:
Caveats
Summary
This is not a Pull Request yet as I assume this should rather be discussed beforehand, but I took the liberty of trying to implement this myself. Here is what it implies:
Changes on Storybook's monorepo
@storybook/api
@storybook/client-api
@storybook/core-server
@storybook/csf-tools
@storybook/store
@storybook/ui
See changes
Changes on Storybook's other packages
@storybook/mdx1-csf
@storybook/mdx2-csf
@storybook/csf
Notes
The text was updated successfully, but these errors were encountered: