Lavash.Components.ComponentsMacro (Lavash v0.4.0-rc.3)

Copy Markdown View Source

Block-structured DSL for defining function components.

Phoenix.Component's attr and slot macros attach themselves to the next-defined function via an @on_definition callback. That's positional — the schema lines must come immediately before the def, with nothing in between. Interleave anything and you get weird behaviour; refactoring moves attrs onto the wrong function silently.

This block makes the function-to-schema relationship explicit:

components do
  component :button do
    prop :rest, :global, include: ~w(disabled type)
    prop :class, :string, default: nil
    prop :variant, :atom, values: [:primary, :secondary], default: :primary
    slot :inner_block, required: true

    render fn assigns ->
      ~H"""
      <button class={["btn", "btn-#{@variant}", @class]} {@rest}>
        {render_slot(@inner_block)}
      </button>
      """
    end
  end

  component :badge do
    prop :label, :string, required: true
    prop :count, :integer, default: 0

    render fn assigns ->
      ~H"""
      <span class="badge">
        <span class="label">{@label}</span>
        <span :if={@count > 0} class="count">{@count}</span>
      </span>
      """
    end
  end
end

Compiles to plain Phoenix function components — one def per component clause, with the Phoenix.Component.Declarative attribute/slot calls emitted immediately above. Consumers (other templates, other modules) call <.button ...> as normal — no runtime distinction.

Vocabulary

prop is lavash's term for what Phoenix.Component calls attr. Identical semantics — prop :foo, :string, default: nil becomes attr :foo, :string, default: nil in the compiled output. The reason for the rename: lavash already uses prop for parent- passed values in LiveComponents (use Lavash.Component). Standardising on prop across both kinds of component makes the DSL internally consistent. Coming from Phoenix, "prop = attr" is the only translation you need.

slot is unchanged — same name, same options as Phoenix.Component.

render fn assigns -> ~H"..." end is the function body. The lambda takes assigns and returns rendered HEEx. (Same shape as Phoenix.Component's def name(assigns), do: ~H"...".)

Summary

Functions

A single component :name do ... end clause. The body is a sequence of prop / slot declarations followed by a single render fn assigns -> ~H"..." end.

Top-level components do ... end block. Contains one or more component/2 calls.

Functions

component(name, list)

(macro)

A single component :name do ... end clause. The body is a sequence of prop / slot declarations followed by a single render fn assigns -> ~H"..." end.

components(list)

(macro)

Top-level components do ... end block. Contains one or more component/2 calls.