This repository has been archived on 2023-08-07. You can view files and clone it, but cannot push or open issues or pull requests.
temple/guides/components.md
Mitchell Hanberg db231e7b6b
Align component model with HEEx/Surface (#182)
* Align component model with HEEx/Surface

This change aligns the component model with HEEx/Surface. This shoudl
allow one to interop components created in any syntax with any other
syntax.

The advantage of this is folks can utilize component packages created
using a different syntax.

This includes several enhancements and breaking changes, please see the changelog and the migration guide for further details.

Closes #130
2022-10-12 09:17:23 -04:00

229 lines
5.7 KiB
Markdown

# Components
Temple has the concept of components, which allow you an expressive and composable way to break up your templates into re-usable chunks.
A component is any arity-1 function that take an argument called `assigns` and returns the result of the `Temple.temple/1` macro.
## Definition
Here is an example of a simple Temple component. You can observe that it seems very similar to a regular Temple template, and that is because it is a regular template!
```elixir
defmodule MyApp.Components do
import Temple
def button(assigns) do
temple do
button type: "button", class: "bg-blue-800 text-white rounded #{@class}" do
@text
end
end
end
end
```
## Usage
To use a component, you will use the special `c` keyword. This is called a "keyword" because it is not a function or macro, but only exists inside of the `Temple.temple/1` block.
The first argument will be the function reference to your component function, followed by any assigns.
```elixir
defmodule MyApp.ConfirmDialog do
import Temple
import MyApp.Components
def render(assigns) do
temple do
dialog open: true do
p do: "Are you sure?"
form method: "dialog" do
c &button/1, class: "border border-white", text: "Yes"
end
end
end
end
end
```
## Slots
Temple components can take "slots" as well. This is the method for providing dynamic content from the call site into the component.
Slots are defined and rendered using the `slot` keyword. This is similar to the `c` keyword, in that it is not defined using a function or macro.
### Default Slot
The default slot can be rendered from within your component by passing the `slot` the `@inner_block` assign. Let's redefine our button component using slots.
```elixir
defmodule MyApp.Components do
import Temple
def button(assigns) do
temple do
button type: "button", class: "bg-blue-800 text-white rounded #{@class}" do
slot @inner_block
end
end
end
end
```
You can pass content through the "default" slot of your component simply by passing a `do/end` block to your component at the call site. This is a special case for the default slot.
```elixir
defmodule MyApp.ConfirmDialog do
import Temple
import MyApp.Components
def render(assigns) do
temple do
dialog open: true do
p do: "Are you sure?"
form method: "dialog" do
c &button/1, class: "border border-white" do
"Yes"
end
end
end
end
end
end
```
### Named Slots
You can also define a "named" slot, which allows you to pass more than one set of dynamic content to your component.
We'll use a "card" example to illustrate this. This example is adapted from the [Surface documentation](https://surface-ui.org/slots) on slots.
#### Definition
```elixir
defmodule MyApp.Components do
import Temple
def card(assigns) do
temple do
div class: "card" do
header class: "card-header", style: "background-color: @f5f5f5" do
p class: "card-header-title" do
slot @header
end
end
div class: "card-content" do
div class: "content" do
slot @inner_block
end
end
footer class: "card-footer", style: "background-color: #f5f5f5" do
slot @footer
end
end
end
end
end
```
#### Usage
```elixir
def MyApp.CardExample do
import Temple
import MyApp.Components
def render(assigns) do
temple do
c &card/1 do
slot :header do
"A simple card component"
end
"This example demonstrates how to create components with multiple, named slots"
slot :footer do
a href: "#", class: "card-footer-item", do: "Footer Item 1"
a href: "#", class: "card-footer-item", do: "Footer Item 2"
end
end
end
end
end
```
## Passing data to and through Slots
Sometimes it is necessary to pass data _into_ a slot (hereby known as *slot attributes*) from the call site and _from_ a component definition (hereby known as *slot arguments*) back to the call site.
Let's look at what a `table` component could look like. Here we observe we access an attribute in the slot in the header with `col.label`.
This example is taken from the HEEx documentation to demonstrate how you can build the same thing with Temple.
Note: Slot attributes can only be accessed on an individual slot, so if you define a single slot definition, you still need to loop through it to access it, as they are stored as a list.
#### Definition
```elixir
defmodule MyApp.Components do
import Temple
def table(assigns) do
temple do
table do
thead do
tr do
for col <- @col do
th do: col.label # 👈 accessing a slot attribute
end
end
end
tbody do
for row <- @rows do
tr do
for col <- @col do
td do
slot col, row
end
end
end
end
end
end
end
end
end
```
#### Usage
When we render the slot, we can pattern match on the data passed through the slot via the `:let` attribute.
```elixir
def MyApp.TableExample do
import Temple
import MyApp.Componens
def render(assigns) do
temple do
section do
h2 do: "Users"
c &table/1, rows: @users do
# 👇 defining the parameter for the slot argument
slot :col, let: user, label: "Name" do # 👈 passing a slot attribute
user.name
end
slot :col, let: user, label: "Address" do
user.address
end
end
end
end
end
end
```