` element."
attr :row_id, :any,
default: nil,
doc: """
Overrides the default function that retrieves the row ID from a stream item.
"""
attr :row_click, :any,
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, :list,
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, :list,
doc: """
If set, a ` ` element is rendered and the attributes are added
to the ` ` element of the respective column.
"""
end
slot :foot,
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
```heex
<: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: """
Aria 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 button that toggles a state.
## Examples
With a boolean assign `@muted` and an event name:
```heex
Mute
```
With a `Phoenix.LiveView.JS` command:
```heex
Mute
```
## Accessibility
The button state is conveyed via the `aria-pressed` attribute and the button
styling. The button text should not change depending on the state. You may
however include an icon that changes depending on the state.
## CSS
A toggle button can be identified with an attribute selector for the
`aria-pressed` attribute.
To select any toggle button:
```css
// any toggle button regardless of state
button[aria-pressed] {}
// unpressed toggle buttons
button[aria-pressed="false"] {}
// pressed toggle buttons
button[aria-pressed="true"] {}
```
"""
@doc type: :component
attr :pressed, :boolean, default: false
attr :on_click, :any,
required: true,
doc: """
Phoenix.LiveView.JS command or event name to trigger when the button is
clicked.
"""
attr :variant, :atom,
values: [:primary, :secondary, :info, :success, :warning, :danger],
default: :primary
attr :fill, :atom, values: [:solid, :outline, :text], default: :solid
attr :size, :atom,
values: [:small, :normal, :medium, :large],
default: :normal
attr :shape, :atom, values: [nil, :circle, :pill], default: nil
attr :disabled, :boolean, default: nil
attr :rest, :global
slot :inner_block, required: true
def toggle_button(assigns) do
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders content with a tooltip.
There are different ways to render a tooltip. This component renders a ``
with the `tooltip` role, which is hidden unless the element is hovered on or
focused. For example CSS for this kind of tooltip, refer to
[ARIA: tooltip role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tooltip_role).
A simpler alternative for styled text-only tooltips is to use a data attribute
and the [`attr` CSS function](https://developer.mozilla.org/en-US/docs/Web/CSS/attr).
Doggo does not provide a component for that kind of tooltip, since it is
controlled by attributes only. You can check
[Pico CSS](https://v2.picocss.com/docs/tooltip) for an example implementation.
## Example
With an inline text:
```heex
Did you know that the
Labrador Retriever
<:tooltip>
Labrador Retriever
Labradors are known for their friendly nature and excellent
swimming abilities.
is one of the most popular dog breeds in the world?
```
If the inner block contains a link, add the `:contains_link` attribute:
```heex
Did you know that the
<.link navigate={~p"/labradors"}>Labrador Retriever
<:tooltip>
Labrador Retriever
Labradors are known for their friendly nature and excellent
swimming abilities.
is one of the most popular dog breeds in the world?
```
"""
@doc type: :component
attr :id, :string, required: true
attr :contains_link, :boolean,
default: false,
doc: """
If `false`, the component sets `tabindex="0"` on the element wrapping the
inner block, so that the tooltip can be made visible by focusing the
element.
If the inner block already contains an element that is focusable, such as
a link or a button, set this attribute to `true`.
"""
slot :inner_block, required: true
slot :tooltip, required: true
def tooltip(assigns) do
~H"""
<%= render_slot(@inner_block) %>
<%= render_slot(@tooltip) %>
"""
end
@doc """
Applies a vertical margin between the child elements.
## Example
```heex
some block
some other block
```
To apply a vertical margin on nested elements as well, set `recursive` to
`true`.
```heex
some nested block
another nested block
some other block
```
"""
@doc type: :component
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
@doc """
Renders a tag, typically used for displaying labels, categories, or keywords.
## Examples
Plain tag:
```heex
Well-Trained
```
With icon:
```heex
Puppy
```
With delete button:
```heex
High Energy
```
"""
@doc type: :component
attr :size, :atom,
values: [:small, :normal, :medium, :large],
default: :normal
attr :variant, :atom,
values: [nil, :primary, :secondary, :info, :success, :warning, :danger],
default: nil
attr :shape, :atom, values: [nil, :pill], default: nil
slot :inner_block, required: true
def tag(assigns) do
~H"""
<%= render_slot(@inner_block) %>
"""
end
## Helpers
defp humanize(atom) when is_atom(atom) do
atom
|> Atom.to_string()
|> humanize()
end
defp humanize(s) when is_binary(s) do
if String.ends_with?(s, "_id") do
s |> binary_part(0, byte_size(s) - 3) |> to_titlecase()
else
to_titlecase(s)
end
end
defp to_titlecase(s) do
s
|> String.replace("_", " ")
|> :string.titlecase()
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-by-1"
defp ratio_class({3, 2}), do: "is-3-by-2"
defp ratio_class({2, 3}), do: "is-2-by-3"
defp ratio_class({4, 3}), do: "is-4-by-3"
defp ratio_class({3, 4}), do: "is-3-by-4"
defp ratio_class({5, 4}), do: "is-5-by-4"
defp ratio_class({4, 5}), do: "is-4-by-5"
defp ratio_class({16, 9}), do: "is-16-by-9"
defp ratio_class({9, 16}), do: "is-9-by-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 skeleton_type_class(:text_line), do: "is-text-line"
defp skeleton_type_class(:text_block), do: "is-text-block"
defp skeleton_type_class(:image), do: "is-image"
defp skeleton_type_class(:circle), do: "is-circle"
defp skeleton_type_class(:rectangle), do: "is-rectangle"
defp skeleton_type_class(:square), do: "is-square"
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(_), do: nil
end