README.md

# Surface Catalogue

This is mostly a prototype, meant to validate a few ideas to have something similar to
<https://storybook.js.org/> for [Surface](https://github.com/msaraiva/surface).

## Installation

Add `surface_catalogue` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:surface_catalogue, "~> 0.6.3", only: :dev}
  ]
end
```

Update your `router.ex` configuration:

```elixir
# lib/my_app_web/router.ex
use MyAppWeb, :router
import Surface.Catalogue.Router

...

if Mix.env() == :dev do
  scope "/" do
    pipe_through :browser
    surface_catalogue "/catalogue"
  end
end
```

Add a `:catalogue` entry in the `:esbuild` config in `config.exs`:

```elixir
config :esbuild,
  default: ...,
  # Add a new entry for the catalogue
  catalogue: [
    args: ~w(../deps/surface_catalogue/assets/js/app.js --bundle --target=es2016 --minify --outdir=../priv/static/assets/catalogue),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]
```

Then update the endpoint configuration in `config/dev.exs` to add an esbuild watcher
for `catalogue`:

```elixir
config :my_app, MyAppWeb.Endpoint,
  ...
  watchers: [
    esbuild: ...,
    # Add an esbuild watcher for :catalogue
    esbuild: {Esbuild, :install_and_run, [:catalogue, ~w(--sourcemap=inline --watch)]},
  ]
```

That's all!

Run `mix phx.server` and access "/catalogue" to see the list of all available components in
your project.

## Loading Examples and Playgrounds

If you want to access examples and playgrounds for components, edit your `mix.exs` file,
adding a new entry for `elixirc_paths` along with a `catalogues` function listing the
catalogues you want to be loaded:

```elixir
...

def catalogues do
  [
    # Local catalogue
    "priv/catalogue",
    # Dependencies catalogues
    "deps/surface/priv/catalogue",
    "deps/surface_bulma/priv/catalogue",
    # External catalogues
    Path.expand("../my_components/priv/catalogue"),
    "/Users/johndoe/workspace/other_components/priv/catalogue"
  ]
end

defp elixirc_paths(:dev), do: ["lib"] ++ catalogues()

...
```

Then update the endpoint configuration in `config/dev.exs` to set up live reloading
for your catalogue:

```elixir
config :my_app, MyAppWeb.Endpoint,
  live_reload: [
    patterns: [
      ~r"priv/catalogue/.*(ex)$",
      ...
    ]
  ]
```

> **Note**: Without the above configurations, the list of available components is
> still presented in the catalogue page. However, when selecting a component, only
> its documentation and API will be shown. No example/playground will be loaded nor
> tracked by Phoenix's live reloader.

## Sharing catalogues

If you're working on a suite of components that you want to share as a library, you
may need to provide additional information about the catalogue. This will be necessary
whenever your components require any `css` or `js` code that might not be available
on the host project.

To provide that additional information you must create a module implementing the
`Surface.Catalogue` behaviour in your `priv/catalogue/` folder. Example:

```elixir
defmodule MySuite.Catalogue do
  use Surface.Catalogue

  @impl true
  def config() do
    [
      head_css: """
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.8.2/css/bulma.min.css" />
      """
    ]
  end
end
```

## Running the built-in catalogue server

In case you're working on a lib that doesn't initialize its own Phoenix endpoint, you
can use the built-in server provided by the `surface_catalogue` following these steps:

Add the required dependencies to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:plug_cowboy, "~> 2.0", only: :dev},
    {:phoenix_live_reload, "~> 1.2", only: :dev},
    {:jason, "~> 1.0", only: :dev}
  ]
end
```

Create a `dev.exs` script at the root of your project with the following content:

```elixir
# iex -S mix dev

Logger.configure(level: :debug)

# Start the catalogue server
Surface.Catalogue.Server.start(
  watchers: [
    esbuild: {Esbuild, :install_and_run, [:catalogue, ~w(--sourcemap=inline --watch)]}
  ],
  live_reload: [
    patterns: [
      ~r"priv/static/.*(js|css)$",
      ~r"lib/my_lib_web/.*(ex)$"
    ],
    notify: [
      live_view: [
        # Colocated sface and css can be reloaded without a hard refresh
        ~r"lib/my_lib_web/.*(sface|css)$"
      ]
    ]
  ]
)
```

Surface Catalogue's assets need to be generated by your project to make it
compatible with your Phoenix dependency versions.

Add the following content to your `config/config.exs` or `config/dev.exs`:

```elixir
import Config

if Mix.env() == :dev do
  config :surface_catalogue,
    title: "My Lib Web",

  config :esbuild,
    ...
    catalogue: [
      args:
        ~w(#{Mix.Project.deps_paths().surface_catalogue}/assets/js/app.js --bundle --target=es2016 --minify --outdir=priv/static/assets/catalogue),
      env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
    ]

  # Required at compile time
  config :surface_catalogue, Surface.Catalogue.Server.Endpoint,
    code_reloader: true,
    debug_errors: true
end
```

Make sure you set the `patterns` option according to your project.

To make things easier, add an alias run the script in your `mix.exs`:

```elixir
def project do
  [
    ...,
    aliases: aliases()
  ]
end

...

defp aliases do
  [
    dev: "run --no-halt dev.exs",
    ...
  ]
end
```

Run the server with:

```bash
mix dev
```

or using `iex`:

```bash
iex -S mix dev
```

You can now access the catalogue at [localhost:4000](http://localhost:4000/catalogue/).

If you need, you can also start the server using a different port:

```bash
PORT=4444 iex -S mix dev
```

## Credits

The `Surface.Catalogue.Server` implementation was mostly extracted from the `dev.exs` script
from [phoenix_live_dashboard](https://github.com/phoenixframework/phoenix_live_dashboard).
All credits to the Phoenix Core Team.

## License

Copyright (c) 2021, Marlus Saraiva.

Surface source code is licensed under the [MIT License](LICENSE.md).