defmodule LiveDebuggerWeb.Components do
@moduledoc """
This module provides reusable components for LiveDebugger.
"""
use Phoenix.Component
alias LiveDebugger.Utils.Parsers
alias Phoenix.LiveView.JS
@report_issue_url "https://github.com/software-mansion/live-debugger/issues/new/choose"
@doc """
Renders an alert
Right now we have styles only for `danger` variant, but it'll change soon
"""
attr(:variant, :string, required: true, values: ["danger"])
attr(:class, :any, default: nil, doc: "Additional classes to add to the alert.")
attr(:with_icon, :boolean, default: false, doc: "Whether to show an icon.")
attr(:heading, :string, default: nil, doc: "Heading for the alert.")
attr(:rest, :global)
slot(:inner_block, required: true)
def alert(assigns) do
~H"""
"""
end
attr(:value_field, Phoenix.HTML.FormField, required: true)
attr(:unit_field, Phoenix.HTML.FormField, required: true)
attr(:units, :list, required: true)
attr(:rest, :global, include: ~w(min max placeholder))
def input_with_units(assigns) do
assigns =
assigns
|> assign(:errors, assigns.value_field.errors)
~H"""
"""
end
@doc """
Renders a button.
"""
attr(:variant, :string, default: "primary", values: ["primary", "secondary"])
attr(:size, :string, default: "md", values: ["md", "sm"])
attr(:class, :any, default: nil, doc: "Additional classes to add to the button.")
attr(:rest, :global)
slot(:inner_block, required: true)
def button(assigns) do
~H"""
"""
end
@doc """
Collapsible element that can be toggled open and closed.
It uses the `details` and `summary` HTML elements.
`hide-on-open` and `show-on-open` css classes are used to hide or show elements based on the open state of the collapsible.
## Examples
<.collapsible id="collapsible" open={true}>
<:label>
Collapsible
Info when closed
Content
"""
attr(:id, :string, required: true)
attr(:class, :any, default: nil, doc: "CSS class for parent container")
attr(:label_class, :any, default: nil, doc: "CSS class for the label")
attr(:chevron_class, :any, default: nil, doc: "CSS class for the chevron icon")
attr(:open, :boolean, default: false, doc: "Whether the collapsible is open by default")
attr(:icon, :string,
default: "icon-chevron-right",
doc: "Icon for chevron. It will be rotated 90 degrees when the collapsible is open"
)
attr(:rest, :global)
slot(:label, required: true)
slot(:inner_block, required: true)
def collapsible(assigns) do
assigns = assign(assigns, :open, to_string(assigns.open))
~H"""
"-summary"}
class={["flex items-center cursor-pointer" | List.wrap(@label_class)]}
{@rest}
>
<.icon name={@icon} class={["rotate-icon shrink-0" | List.wrap(@chevron_class)]} />
<%= render_slot(@label) %>
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders flash notices.
## Examples
<.flash flash={@flash} />
<.flash phx-mounted={show("#flash")}>Welcome Back!
"""
attr(:id, :string, doc: "the optional id of flash container")
attr(:flash, :map, default: %{}, doc: "the map of flash messages to display")
attr(:rest, :global, doc: "the arbitrary HTML attributes to add to the flash container")
def flash(assigns) do
message = Phoenix.Flash.get(assigns.flash, :error)
assigns =
assigns
|> assign_new(:id, fn -> "flash" end)
|> assign(:message, message)
~H"""
"""
end
@doc """
Typography component to render headings.
"""
attr(:class, :any, default: nil, doc: "Additional classes to add to the heading.")
attr(:rest, :global)
slot(:inner_block, required: true)
def h1(assigns) do
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders an icon.
Not all icons are available. If you want to use an icon check if it exists in the `assets/icons` folder.
`name` must start with `icon-`
## Examples
<.icon name="icon-play" />
"""
attr(:name, :string, required: true, doc: "The name of the icon. Must start with `icon-`.")
attr(:class, :any, default: nil, doc: "Additional classes to add to the icon.")
attr(:rest, :global)
def icon(%{name: "icon-" <> _} = assigns) do
~H"""
"""
end
@doc """
Renders a button with an icon in it.
"""
attr(:icon, :string, required: true, doc: "Icon to be displayed as a button.")
attr(:variant, :string,
default: "primary",
values: ["primary", "secondary"],
doc: "Variant of the button."
)
attr(:class, :any, default: nil, doc: "Additional classes to add to the button.")
attr(:rest, :global, include: ~w(id))
def icon_button(assigns) do
assigns =
assign(assigns, :aria_label, assigns[:"aria-label"] || Parsers.kebab_to_text(assigns.icon))
~H"""
<.button
aria-label={@aria_label}
class={["w-7! h-7! px-[0.2rem] py-[0.2rem]" | List.wrap(@class)]}
variant={@variant}
{@rest}
>
<.icon name={@icon} class="h-4 w-4" />
"""
end
attr(:elements, :list,
required: true,
doc: "Elements that will be displayed in the list's `item` slot."
)
attr(:class, :any, default: nil, doc: "Additional classes.")
attr(:item_class, :any, default: nil, doc: "Additional classes for each item.")
slot(:item, required: true)
def list(assigns) do
~H"""
<%= render_slot(@item, elem) %>
"""
end
attr(:sidebar_hidden?, :boolean, default: true, doc: "The default state of the sidebar")
slot(:inner_block)
def sidebar_slide_over(assigns) do
~H"""
"""
end
@doc """
Renders a fullscreen using Fullscreen hook.
It can be opened and via browser "open" event (by default) with JS.dispatch or via server event (check example in fullscreen button).
You can use `fullscreen_button` to open this fullscreen.
You can close the fullscreen using X button or by pressing ESC key.
"""
attr(:id, :string, required: true)
attr(:title, :string, default: "", doc: "Title of the fullscreen.")
attr(:class, :any,
default: nil,
doc: "Additional classes to be added to the fullscreen element."
)
slot(:inner_block, required: true)
def fullscreen(assigns) do
~H"""
"""
end
@doc """
Renders a button which will show a fullscreen when clicked.
You can override `phx-click` value, but remember to push correct event at the end of `handle_event` function.
## Examples
<.fullscreen_button
id="my-fullscreen"
phx-click="open-fullscreen"
icon="icon-expand"
/>
@impl true
def handle_event("open-fullscreen", _, socket) do
trace_id = String.to_integer(string_id)
socket
|> push_event("my-fullscreen-open", %{})
|> noreply()
end
"""
attr(:id, :string, required: true, doc: "Same as `id` of the fullscreen.")
attr(:icon, :string,
default: "icon-expand",
doc: "Icon to be displayed as a button"
)
attr(:rest, :global, include: ~w(class))
def fullscreen_button(assigns) do
~H"""
<.icon_button
id={"#{@id}-button"}
phx-click={@rest[:"phx-click"] || JS.dispatch("open", to: "##{@id}")}
icon={@icon}
data-fullscreen-id={@id}
variant="secondary"
{@rest}
/>
"""
end
attr(:class, :any, default: nil, doc: "CSS class")
attr(:size, :string,
default: "md",
values: ["xs", "sm", "md", "lg", "xl"],
doc: "Size of the spinner"
)
attr(:show, :boolean, default: true, doc: "show or hide spinner")
attr(:rest, :global)
def spinner(assigns) do
size_class =
case assigns.size do
"xs" -> "h-4 w-4"
"sm" -> "h-6 w-6"
"md" -> "h-8 w-8"
"lg" -> "h-10 w-10"
"xl" -> "h-12 w-12"
end
assigns = assign(assigns, :size_class, size_class)
~H"""
"""
end
attr(:text, :string, required: true)
attr(:icon, :string, required: true)
def badge(assigns) do
~H"""