PhoenixStorybook

repository·main·Indexed 21 days ago

https://github.com/phenixdigital/phoenix_storybook

A Storybook-like UI interface for Phoenix components, enabling developers to explore, document, and interactively test components and LiveViews. It supports three story types (component, page, and example) and features automatic discovery of stories, a component playground, color mode support, and integration with Elixir doc tags for automatic documentation.

Tokens
15.9K
Snippets
53
Records
64
Agent score
74%

What's inside phoenix_storybook

  1. How PhoenixStorybook styling and sandboxing works

    main

    Styling Isolation

    PhoenixStorybook uses TailwindCSS 4.x with preflight and a custom prefix psb:. To prevent styling leaks, only elements with the .psb class are preflighted. This ensures that unless your components use psb or psb: prefixed classes, the storybook's base styles won't affect them.

    Component Styling via Scoped Styles

    To provide your component's styles to the storybook without affecting the storybook UI, you should use scoped styles via a custom sandbox class:

    1. Define a sandbox_class: In your storybook.ex configuration, specify a custom class name (e.g., sandbox_class: "my-app").
    2. Inject Stylesheets: Set the css_path option in storybook.ex to a remote path (e.g., "/assets/css/storybook.css"). These are loaded within the app CSS layer, giving them priority over storybook styles.
    3. Scope CSS: In your stylesheet, nest your component-specific CSS under your chosen sandbox class.
    4. Apply to Application: Add the sandbox class to the <body> element of your main application layout.
  2. Wrap variations in templates

    main

    You can wrap component variations in markup by defining a template/0 function in your story module. Use the <.psb-variation/> placeholder to indicate where the variation should be injected.

    Variation Templates

    Every variation will be rendered within the defined template. You can override the template per variation or variation_group by setting the :template key. Setting it to a falsy value disables templating for that specific item.

    Variation Group Templates

    • Per-variation wrapping: Wrap every variation in its own template.
    • Group wrapping: Wrap all variations in a single template using <.psb-variation-group/>.

    Template Features

    • Dynamic IDs: Use :variation_id to inject the current variation or group ID at rendering time.
    • Placeholder Attributes: Pass extra attributes to the variation by adding them to the placeholder: <.psb-variation form={f}/>.
    • Hiding Template Code: To prevent the surrounding template markup from appearing in the code preview, add the psb-code-hidden HTML attribute to the wrapper.
    def template do
      """
      <div class="my-custom-wrapper">
        <.psb-variation/>
      </div>
      """
    end
  3. How PhoenixStorybook works

    main

    PhoenixStorybook provides a Storybook-like UI for Phoenix components, allowing you to explore component variations, browse documentation, and use an interactive playground.

    It is mounted in your application router and performs automatic discovery of stories under a specified :content_path. Every module detected in that folder is loaded as a storybook entry.

    There are three supported story types:

    • component: For stateless function components or LiveComponents.
    • page: For documenting UI guidelines or general content.
    • example: For showing how components are used within real UI pages.
  4. Declare icons in PhoenixStorybook

    main

    Icons in PhoenixStorybook are provided as tuples. The structure depends on whether you are using a remote provider (FontAwesome, HeroIcons) or a local icon.

    For FontAwesome and HeroIcons: {icon_provider, icon_name, :icon_style, additional_css_classes}

    • icon_provider: :fa for FontAwesome, :hero for HeroIcons.
    • icon_name: The name of the icon (e.g., "book" for FontAwesome, omitting the fa- prefix).
    • :icon_style (optional): The style of the icon (e.g., :solid, :outline, :duotone).
    • additional_css_classes (optional): A string of CSS classes.

    For Local icons: {icon_provider, icon_name, additional_css_classes}

    • icon_provider: :local.
    • icon_name: The CSS class name used to render the icon.
    • additional_css_classes (optional): A string of CSS classes. Note: :icon_style is not supported for local icons; the third element is always treated as custom CSS.
    # FontAwesome examples
    {:fa, "book"}
    {:fa, "book", :solid}
    {:fa, "skull", :duotone, "psb:px-2"}
    
    # HeroIcons examples
    {:hero, "cake"}
    {:hero, "cake", :outline, "psb:w-2 psb:h-2"}
    
    # Local icon examples
    {:local, "hero-cake"}
    {:local, "hero-cake", "psb:w-2 psb:h-2"}
  5. Configure Dev Watcher and Live Reload for Storybook

    main

    In config/dev.exs, configure a watcher for the Storybook Tailwind profile and add a live-reload pattern for .exs story files to enable instant updates during development.

    # config/dev.exs
    config :my_app, MyAppWeb.Endpoint,
      watchers: [
        ...
        storybook_tailwind: {Tailwind, :install_and_run, [:storybook, ~w(--watch)]}
      ],
      live_reload: [
        patterns: [
          ...
          ~r"storybook/.*\.exs$"
        ]
      ]
  6. Configure the JS bundle for Storybook

    main

    PhoenixStorybook requires a dedicated JS bundle. This script is loaded immediately before the library's own JS and is used to expose LiveView Hooks, Params, and Uploaders to the window.storybook object.

    1. Create assets/js/storybook.js to export your assets to window.storybook.
    2. Add js/storybook.js as an entry point in your esbuild configuration in config/config.exs.
    // 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 };
    })();
    # config/config.exs
    config :esbuild,
      my_app: [
        args:
          ~w(js/app.js js/storybook.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.),
        cd: Path.expand("../assets", __DIR__),
        env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]}
      ]
  7. Enable 'Open in Editor' functionality

    main

    The Storybook UI includes a Source tab with a button to open the current file in your editor. This is enabled by setting the PLUG_EDITOR environment variable using your editor's URL scheme.

    Note: This should only be set in your local development environment.

    export PLUG_EDITOR="vscode://file/__FILE__:__LINE__"   # VS Code
    # export PLUG_EDITOR="cursor://file/__FILE__:__LINE__" # Cursor
    # export PLUG_EDITOR="zed://file/__FILE__:__LINE__"    # Zed
  8. Create your storybook backend module

    main

    Create a backend module in your application's lib folder using use PhoenixStorybook/0. This module configures the storybook environment.

    Key configuration options:

    • otp_app: The name of your application's OTP app.
    • content_path: The file-system path to your storybook stories (required).
    • css_path: The remote URL path to your storybook CSS bundle (e.g., /assets/css/storybook.css).
    • js_path: The remote URL path to your storybook JS bundle (e.g., /assets/js/storybook.js).
    • sandbox_class: The CSS class used to scope styles within the storybook sandbox.
    # lib/my_app_web/storybook.ex
    defmodule MyAppWeb.Storybook do
      use PhoenixStorybook,
        otp_app: :my_app,
        content_path: Path.expand("../../storybook", __DIR__),
        # assets path are remote path, not local file-system paths
        css_path: "/assets/css/storybook.css",
        js_path: "/assets/js/storybook.js",
        sandbox_class: "my-app"
    end
  9. Configure the CSS bundle for Storybook

    main

    PhoenixStorybook uses a dedicated CSS bundle (css_path) instead of your main app.css. You must mirror your application's styling (Tailwind plugins, themes, etc.) in this file.

    1. Create assets/css/storybook.css and use @source directives to include your app's CSS and JS files.
    2. Add a storybook profile to your tailwind configuration in config/config.exs to build this specific bundle.
    /* assets/css/storybook.css */
    @import "tailwindcss" source(none);
    @source "../css";
    @source "../js";
    @source "../../lib/my_app_web";
    @source "../../storybook";
    # config/config.exs
    config :tailwind,
      my_app: [
        ...
      ],
      storybook: [
        args: ~w(
          --input=assets/css/storybook.css
          --output=priv/static/assets/css/storybook.css
        ),
        cd: Path.expand("..", __DIR__)
      ]
  10. Define component documentation from doc tags

    main

    PhoenixStorybook automatically fetches component documentation from your Elixir doc tags:

    • For a live_component, it fetches @moduledoc content.
    • For a function component, it fetches @doc content from the matching function.

    Important for Production Releases: If you are deploying with an Elixir release, ensure your doc chunks are not stripped out. You can configure this in your release definition:

    releases: [
      my_app_web: [
        strip_beams: [
          keep: ["Docs"]
        ]
      ]
    ]
    releases: [
      my_app_web: [
        strip_beams: [
          keep: ["Docs"]
        ]
      ]
    ]