Surface

Build StatusHex.pm version

A component based library for Phoenix LiveView.

Built on top of the new LiveComponent API, Surface provides a more declarative way to express and use components in Phoenix.

Full documentation and live examples can be found at surface-demo.msaraiva.io.

A VS Code extension that adds support for syntax highlighting is available at marketplace.visualstudio.com.

Example

Example

A lot of the concepts behind it were borrowed from some of the most popular frontend solutions like React and Vue.js.

How does it work?

At compile time, Surface translates components defined in an extended HTML-like syntax into regular Phoenix templates. It also translates standard HTML nodes allowing us to extend their behaviour adding new features like syntatic sugar on attributes definition, directives, static validation and more.

In order to have your code translated, you need to use the ~H sigil when defining your templates.

Features

Note: Some of the features are still experimental and subject to change.

Installation

Install Phoenix LiveView following the installation guide. Then add surface to the list of dependencies in mix.exs:

def deps do
  [
    {:surface, "~> 0.1.0-alpha.1"}
  ]
end

In order to have ~H available for any Phoenix view, add the following import to your web file in lib/my_app_web.ex:

  # lib/my_app_web.ex

  ...

  def view do
    quote do
      ...
      import Surface
    end
  end

Defining components

To create a component you need to define a module and use one of the available component types:

Example

  # A stateless component

  defmodule Button do
    use Surface.Component

    property click, :event
    property kind, :string, default: "is-info"

    def render(assigns) do
      ~H"""
      <button class="button {{ @kind }}" :on-phx-click={{ @click }}>
        <slot/>
      </button>
      """
    end
  end

  # A live stateful component

  defmodule Dialog do
    use Surface.LiveComponent

    @doc "The title of the dialog"
    property title, :string, required: true

    data show, :boolean, default: false

    def render(assigns) do
      ~H"""
      <div class={{ "modal", "is-active": @show }}>
        <div class="modal-background"></div>
        <div class="modal-card">
          <header class="modal-card-head">
            <p class="modal-card-title">{{ @title }}</p>
          </header>
          <section class="modal-card-body">
            <slot/>
          </section>
          <footer class="modal-card-foot" style="justify-content: flex-end">
            <Button click="hide">Ok</Button>
          </footer>
        </div>
      </div>
      """
    end

    # Public API

    def show(dialog_id) do
      send_update(__MODULE__, id: dialog_id, show: true)
    end

    # Event handlers

    def handle_event("show", _, socket) do
      {:noreply, assign(socket, show: true)}
    end

    def handle_event("hide", _, socket) do
      {:noreply, assign(socket, show: false)}
    end
  end

  # A live view component

  defmodule Example do
    use Surface.LiveView

    def render(assigns) do
      ~H"""
      <Dialog title="Alert" id="dialog">
        This <b>Dialog</b> is a stateful component. Cool!
      </Dialog>

      <Button click="show_dialog">Click to open the dialog</Button>
      """
    end

    def handle_event("show_dialog", _, socket) do
      Dialog.show("dialog")
      {:noreply, socket}
    end
  end

Static checking

Since components are ordinary Elixir modules, some static checking is already provided by the compiler. Additionally, we added a few extra warnings to improve user experience. Here are some examples:

Module not available

Example

Missing required property

Example

Unknown property

Example

Tooling

Some experimental work on tooling around the library has been done. Here's a few of them:

VS Code

ElixirSense

Other tools

Having a standard way of defining components with typed properties allows us to enhance tools that introspect information from modules. One already discussed was the possibility to have ex_doc query that information to provide standard documentation for properties, events, slots, etc.

License

Copyright (c) 2019, Marlus Saraiva.

Surface source code is licensed under the MIT License.