View Source Sandboxing components
In PhoenixStorybook
your components live within the storybook, so they share some context with
the storybook: styling and scripts.
While the original Storybook for React only relies on iframes, we find them quite slow and don't want them to be the default choice.
This guide will explain:
- what JS context do your components share with the storybook?
- how is the storybook styled to prevent most styling clashes?
- how you should provide the style of your components with scoped styles.
- how to, as a last resort, enable iframe rendering.
1. What JS context do your components share with the storybook?
PhoenixStorybook
runs with Phoenix LiveView and therefore requires its LiveSocket
. This
LiveSocket is the same used by your components: you just need to inject it with your own Hooks
,
Params
and Uploaders
.
To do so, create a JS file that will declare your Hooks
, Params
and Uploaders
and set them in
window.storybook
. This script will be loaded immediately before the storybook's script.
:information_source: If you used
mix phx.gen.storybook
this file has already been created for you.
// assets/js/storybook.js
import * as Hooks from "./hooks";
import * as Params from "./params";
import * as Uploaders from "./uploaders";
(function () {
window.storybook = { Hooks, Params, Uploaders };
})();
Then set the js_path: "/assets/storybook.js"
option to the storybook within your storybook.ex
file. This is a remote path (not a local file-system path) which means this file should be served
by your own application endpoint with the given path.
You can also use this script to inject whatever content you want into document HEAD
, such as
external scripts.
The Params
will be available in page stories as connect_params
assign.
There is currently no way to access them in component or live component stories.
2. How is the storybook styled?
PhoenixStorybook
is using TailwindCSS with
preflight (which means all default HTML styles from your
browser are removed) and a custom prefix:
psb-
(which means that instead of using bg-blue-400
the storybook uses psb-bg-blue-400
).
Only elements with the .psb
class are preflighted, in order to let your component styling as-is.
So unless your components use psb
or psb-
prefixed classes there should be no styling leak from
the storybook to you components.
3. How should you provide the style of your components?
You need to inject your component's stylesheets into the storybook. Set the
css_path: "/assets/storybook.css"
option in storybook.ex
. This is a remote path (not a local
file-system path) which means this file should be served by your own application endpoint with the
given path.
The previous part (2.) was about storybook styles not leaking into your components. This part is about the opposite: don't accidentally mess up Storybook styling with your styles.
All containers rendering your components in the storybook (stories
, playground
, pages
...)
carry the .psb-sandbox
CSS class and a custom sandboxing class of your choice.
You can leverage this to scope your styles with this class. Here is how you can do it with
TailwindCSS
:
- configure
phoenix_storybook
with a customsandbox_class
:
# lib/my_app_web/storybook.ex
defmodule MyAppWeb.Storybook do
use PhoenixStorybook,
...
sandbox_class: "my-app-sandbox",
- use Tailwind important selector strategy with this class. It will prefix all your tailwind classes increasing their specificity, hence their priority.
// assets/tailwind.config.js
module.exports = {
// ...
important: ".my-app-sandbox",
};
- nest your custom styles under Tailwind
@layer utilities
. This way, your styling will also benefit from sandboxing.
/* assets/css/storybook.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
/* this style will be generated as .my-app-sandbox * { ... } */
* {
font-family: "MyComponentsFont";
@apply text-slate-600;
}
/* this style will be generated as .my-app-sandbox h1 { ... } */
h1 {
@apply text-2xl font-bold text-slate-700 mt-2 mb-6;
}
/* this style will be generated as .my-app-sandbox h2 { ... } */
h2 {
@apply text-xl font-bold text-slate-700 mt-2 mb-4;
}
}
4. Enabling iframe rendering
As a last resort, if for whatever reason you cannot make your component live within the storybook, it is possible to enable iframe rendering, component per component.
This could be required e.g. if you need to bind listeners on document
or when
you want to make sure responsive css works as expected.
Just add the iframe
option to it.
# storybook/components/button.exs
defmodule MyAppWeb.Storybook.Components.Button do
alias MyAppWeb.Components.Button
use PhoenixStorybook.Story, :component
def function, do: &Button.button/1
def container, do: :iframe
# ...
end