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("×"), variants: [:primary]
alert :primary do
"Content"
end
<div class="alert alert-primary">
<button aria-label="Close" class="close" data-dismiss="alert">
<span>×</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 acomponent/3
(component/2
fordeftag
) function clause where an atom variant name is the first argument.:variant_class_prefix
- the class prefix to use when composing variants. Defaults to theclass
option. Usefalse
for no prefix.