` element."
attr :row_id, :any,
default: nil,
doc: """
Overrides the default function that retrieves the row ID from a stream item.
"""
attr :row_click, JS,
default: nil,
doc: """
Sets the `phx-click` function attribute for each row `td`. Expects to be a
function that receives a row item as an argument. This does not add the
`phx-click` attribute to the `action` slot.
Example:
```elixir
row_click={&JS.navigate(~p"/users/\#{&1}")}
```
"""
attr :row_item, :any,
default: &Function.identity/1,
doc: """
This function is called on the row item before it is passed to the :col
and :action slots.
"""
slot :col,
required: true,
doc: """
For each column to render, add one `<:col>` element.
```elixir
<:col :let={pet} label="Name" field={:name} col_style="width: 20%;">
<%= pet.name %>
```
Any additional assigns will be added as attributes to the `` elements.
""" do
attr :label, :any, doc: "The content for the header column."
attr :col_attrs, :string,
doc: """
If set, a `` element is rendered and the attributes are added
to the ` ` element of the respective column.
"""
end
slot :action,
doc: """
The slot for showing user actions in the last table column. These columns
do not receive the `row_click` attribute.
```elixir
<:action :let={user}>
<.link navigate={~p"/users/\#{user}"}>Show
```
""" do
attr :label, :string, doc: "The content for the header column."
attr :col_attrs, :string,
doc: """
If set, a ` ` element is rendered and the attributes are added
to the ` ` element of the respective column.
"""
end
slot :foot,
default: nil,
doc: """
You can optionally add a `foot`. The inner block will be rendered inside
a `tfoot` element.
<:foot>
Total: <%= @total %>
"""
def table(assigns) do
assigns =
with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
end
~H"""
<%= @caption %>
<%= col[:label] %>
<%= action[:label] %>
"-tbody"}
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
>
<%= render_slot(col, @row_item.(row)) %>
<%= render_slot(action, @row_item.(row)) %>
<%= render_slot(@foot) %>
"""
end
@doc """
Renders navigation tabs.
## Example
<.tab_navigation current_value={@live_action}>
<:item
patch={~p"/pets/\#{@pet}"}
value={[:show, :edit]}
>
Profile
<:item
patch={~p"/pets/\#{@pet}/appointments"}
value={:appointments}
>
Appointments
<:item
patch={~p"/pets/\#{@pet}/messages"}
value={:messages}
>
Messages
"""
@doc type: :component
attr :label, :string,
default: "Tabs",
doc: """
Label for the `` element. The label is especially important if you have
multiple `` elements on the same page. If the page is localized, the
label should be translated, too. Do not include "navigation" in the label,
since screen readers will already announce the "navigation" role as part
of the label.
"""
attr :current_value, :any,
required: true,
doc: """
The current value used to compare the item values with. If you use this
component to patch between different view actions, this could be the
`@live_action` attribute.
"""
attr :class, :any,
default: [],
doc: "Additional CSS classes. Can be a string or a list of strings."
attr :rest, :global, doc: "Any additional HTML attributes."
slot :item, required: true do
attr :href, :string, doc: "Passed to `Phoenix.Component.link/1`."
attr :navigate, :string, doc: "Passed to `Phoenix.Component.link/1`."
attr :patch, :string, doc: "Passed to `Phoenix.Component.link/1`."
attr :value, :any,
doc: """
The value of the item is compared to the `current_value` attribute to
determine whether to add the `aria-current` attribute. This can be a
single value or a list of values, e.g. multiple live actions for which
the item should be marked as current.
"""
end
def tab_navigation(assigns) do
~H"""
<.link
href={item[:href]}
navigate={item[:navigate]}
patch={item[:patch]}
aria-current={@current_value in List.wrap(item.value) && "page"}
>
<%= render_slot(item) %>
"""
end
@doc """
Renders a drawer with a `brand`, `top`, and `bottom` slot.
Within the slots, you can use the `drawer_nav/1` and `drawer_section/1`
components.
## Example
<.drawer>
<:brand>
<.link navigate={~p"/"}>App
<:top>
<.drawer_nav aria-label="Main navigation">
<:item>
<.link navigate={~p"/dashboard"}>Dashboard
<:item>
<.drawer_nested_nav>
<:title>Content
<:item current_page>
<.link navigate={~p"/posts"}>Posts
<:item>
<.link navigate={~p"/comments"}>Comments
<.drawer_section>
<:title>Search
<:item>
<:bottom>
<.drawer_nav aria-label="User menu">
<:item>
<.link navigate={~p"/settings"}>Settings
<:item>
<.link navigate={~p"/logout"}>Logout
"""
@doc type: :component
attr :class, :any,
default: [],
doc: "Additional CSS classes. Can be a string or a list of strings."
attr :rest, :global, doc: "Any additional HTML attributes."
slot :brand, doc: "Optional slot for the brand name or logo."
slot :top,
doc: """
Slot for content that is rendered after the brand, at the start of the
side bar.
"""
slot :bottom,
doc: """
Slot for content that is rendered at the end of the drawer, pinned to the
bottom, if there is enough room.
"""
def drawer(assigns) do
~H"""
<%= render_slot(@brand) %>
<%= render_slot(@top) %>
<%= render_slot(@bottom) %>
"""
end
@doc """
Renders a navigation menu as a drawer section.
This component must be placed within the `:top` or `:bottom` slot of the
`drawer/1` component.
To nest the navigation, use the `drawer_nested_nav/1` component within the
`:item` slot.
To render a drawer section that is not a navigation menu, use
`drawer_section/1` instead.
## Example
<.drawer_nav aria-label="Main navigation">
<:item>
<.link navigate={~p"/dashboard"}>Dashboard
<:item>
<.drawer_nested_nav>
<:title>Content
<:item current_page>
<.link navigate={~p"/posts"}>Posts
<:item>
<.link navigate={~p"/comments"}>Comments
"""
@doc type: :component
attr :rest, :global, doc: "Any additional HTML attributes."
slot :title, doc: "An optional slot for the title of the menu."
slot :item, required: true, doc: "Items" do
attr :current_page, :boolean
end
def drawer_nav(assigns) do
~H"""
<%= render_slot(@title) %>
"""
end
@doc """
Renders nested navigation items within the `:item` slot of the `drawer_nav/1`
component.
## Example
<.drawer_nav aria-label="Main navigation">
<:item>
<.drawer_nested_nav>
<:title>Content
<:item current_page>
<.link navigate={~p"/posts"}>Posts
<:item>
<.link navigate={~p"/comments"}>Comments
"""
@doc type: :component
slot :title, doc: "An optional slot for the title of the nested menu section."
slot :item, required: true, doc: "Items" do
attr :current_page, :boolean
end
def drawer_nested_nav(assigns) do
~H"""
<%= render_slot(@title) %>
"""
end
@doc """
Renders a section in a drawer that contains one or more items, which are not
navigation links.
To render a drawer navigation, use `drawer_nav/1` instead.
## Example
<.drawer_section>
<:title>Search
<:item>
"""
@doc type: :component
attr :class, :any,
default: [],
doc: "Additional CSS classes. Can be a string or a list of strings."
attr :rest, :global, doc: "Any additional HTML attributes."
slot :title, doc: "An optional slot for the title of the section."
slot :item, required: true, doc: "Items" do
attr :class, :any,
doc: "Additional CSS classes. Can be a string or a list of strings."
end
def drawer_section(assigns) do
~H"""
<%= render_slot(@title) %>
Map.get(:class, []) |> List.wrap()]}
>
<%= render_slot(item) %>
"""
end
## Layouts
@doc """
Applies a vertical margin between the child elements.
## Example
<.stack>
some block
some other block
To apply a vertical margin on nested elements as well, set `recursive` to
`true`.
<.stack recursive={true}>
some nested block
another nested block
some other block
"""
@doc type: :layout
slot :inner_block, required: true
attr :recursive, :boolean,
default: false,
doc:
"If `true`, the stack margins will be applied to nested elements as well."
attr :class, :any,
default: [],
doc: "Additional CSS classes. Can be a string or a list of strings."
attr :rest, :global, doc: "Any additional HTML attributes."
def stack(assigns) do
~H"""
<%= render_slot(@inner_block) %>
"""
end
## Modifier classes
defp fill_class(:solid), do: "is-solid"
defp fill_class(:outline), do: "is-outline"
defp fill_class(:text), do: "is-text"
defp ratio_class({1, 1}), do: "is-1-to-1"
defp ratio_class({3, 2}), do: "is-3-to-2"
defp ratio_class({2, 3}), do: "is-2-to-3"
defp ratio_class({4, 3}), do: "is-4-to-3"
defp ratio_class({3, 4}), do: "is-3-to-4"
defp ratio_class({5, 4}), do: "is-5-to-4"
defp ratio_class({4, 5}), do: "is-4-to-5"
defp ratio_class({16, 9}), do: "is-16-to-9"
defp ratio_class({9, 16}), do: "is-9-to-16"
defp ratio_class(nil), do: nil
defp size_class(:small), do: "is-small"
defp size_class(:normal), do: nil
defp size_class(:medium), do: "is-medium"
defp size_class(:large), do: "is-large"
defp shape_class(:circle), do: "is-circle"
defp shape_class(:pill), do: "is-pill"
defp shape_class(nil), do: nil
defp variant_class(:primary), do: "is-primary"
defp variant_class(:secondary), do: "is-secondary"
defp variant_class(:info), do: "is-info"
defp variant_class(:success), do: "is-success"
defp variant_class(:warning), do: "is-warning"
defp variant_class(:danger), do: "is-danger"
defp variant_class(:error), do: "is-danger"
defp variant_class(_), do: nil
end