ExComponent v0.1.0 ExComponent View Source

A DSL for easily building dynamic, reusable components for your frontend framework in Elixir.

defcontenttag :card, tag: :div, class: "card"

card do
  "Content!"
end
#=> <div class="card">Content!</div>

defcontenttag :alert, tag: :div, class: "alert", variants: [:primary, :success]

alert :primary, "Alert!"
#=> <div class="alert alert-primary">Alert!</div>

alert :primary, "Alert!", class: "extra"
#=> <div class="alert alert-primary extra">Alert!</div>

alert :success, "Alert!"
#=> <div class="alert alert-success">Alert!</div>

Generated function clauses accept a block and a list of opts.

alert :primary, class: "extra" do
  "Alert!"
end
#=> <div class="alert alert-primary extra">Alert!</div>

Usage

The lib defines two macros: deftag and defcontenttag.

The deftag macro defines void components, those that do not accept their own content, like hr, while the defcontenttag macro defines components that accept their own content, like div.

Function Delegation

The :tag option accepts an atom and an anonymous function, which allows you to generate components that defer execution to another function.

This is useful if you want to use Phoenix.HTML.Link.link/2, for example.

defcontenttag :list_group_item, tag: &Phoenix.HTML.Link.link/2, class: "list-group-item"

list_group_item "Action", to: "#"
#=> <a href="#" class: "list-group-item">Action</a>

CSS Class

The :class option is the base class of the component and is used to build variant classes in the form class="{class class-variant}".

Variants

The :variants option adds a modifier class to the component and generates component/3 clauses for each variant, where the variant is the first argument.

defcontenttag :alert, tag: :div, class: "alert", variants: [:success]

alert :success, class: "extra" do
  "Alert!"
end
#=> <div class="alert alert-success extra">Alert!</div>

Some components have multiple variants. You can use component/2 and pass a list to the :variants option.

list_group variants: [:flush, :horizontal], class: "extra" do
  "..."
end
#=> <div class="list-group list-group-flush list-group-horizontal ">...</div>

Variant Class

Some components have variants that do not inherit the component's base class, or have a custom class. For example, the dropup and dropleft variants of Bootstrap's dropdowns.

Set variant_class_prefix: false to define a variant without a class prefix. You can also provide your own custom prefix.

defcontenttag :dropdown, tag: :div, class: "dropdown", variants: [:dropup, :dropleft], variant_class_prefix: false

dropdown :dropleft do
  ...
end
#=> <div class="dropdown dropleft">...</div>

Appending and Prepending Content

Use :append and/or :prepend to add additional content your component. For example, a Bootstrap alert that has a close button.

defcontenttag :close, tag: :button, wrap_content: :span, class: "close", data: [dismiss: "alert"], aria: [label: "Close"]
defcontenttag :alert, tag: :div, class: "alert", prepend: close("&times;"), variants: [:primary]

alert :primary do
  "Content"
end
<div class="alert alert-primary">
  <button aria-label="Close" class="close" data-dismiss="alert">
    <span>&times;</span>
  </button>
  Content
</div>

Both options accept an atom, or a tuple in one the forms {:safe, iodata}, {:tag, opts}, {:tag, "content"}, and {:tag, "content", opts}.

Nesting Components

The :parent option is useful for nesting a component in an additional tag.

You can pass an atom, or a tuple with either a function or an atom, and a list of parent options.

For example, breadcrumbs in Bootstrap are built with an ol tag wrapped in a nav tag.

<nav role="nav">
  <ol class="breadcrumbs">
    <li class="breadcrumbs-item">...</li>
  </ol>
</nav>

You can use the parent: :nav or parent: {:nav, [role: "nav"]} to address this case.

defcontenttag :breadcrumbs, tag: :ol, ..., parent: :nav
defcontenttag :breadcrumbs, tag: :ol, ..., parent: {:nav, [role: "nav"]}

You can also pass an anonymouse function to the parent option.

defcontenttag :breadcrumbs, tag: :ol, ..., parent: &fun/1

Wrapping Content

The :wrap_content option works exactly like the :parent option except that it wraps the content of the component rather than the component itself.

For example, a Bootstrap button whose text is wrapped in a span.

defcontenttag :button, tag: :button, ..., wrap_content: :span

Default HTML Options

You can pass a list of HTML options to :html_opts, which gets forwarded to the underlying HTML. Any default options can be overriden during function calls.

Options

  • :class - the component's class name. This option is required.

  • :html_opts - a list of opts to forward onto the HTML.

  • :parent - wraps the component in the given tag. Accepts an atom, a anonymous function, or a tuple where the first element is the parent tag and the second is a list of parent options. For example, {:div, [class: "something"]}.

  • :prepend - prepends the given tag to the component's content. Accepts a tuple in the following format: {:safe, iodata}, {:tag, opts}, {:tag, "content"}, or {:tag, "content", opts}. For example, {:hr, [class: "divider"]} or {:button, "Dropdown", class: "extra"}.

  • :append - appends the given content to the component. See the :prepend option for usage.

  • :wrap_content - wraps the inner content of the component in the given tag. See the :parent option for usage.

  • :variants - a list of component variants. Each variant generates a component/3 (component/2 for deftag) function clause where an atom variant name is the first argument.

  • :variant_class_prefix - the class prefix to use when composing variants. Defaults to the class option. Use false for no prefix.

Link to this section Summary

Link to this section Functions

Link to this macro

defcontenttag(name, options)

View Source (macro)
Link to this macro

deftag(name, options)

View Source (macro)