Packages

A UI component library for Phoenix LiveView

Current section

Files

Jump to
flint_ui lib fluint_ui components badge.ex
Raw

lib/fluint_ui/components/badge.ex

defmodule FlintUI.Badge do
@moduledoc false
use FlintUI.Component
# --------------------------------------
# Badge
# --------------------------------------
@doc """
Renders a badge component.
A badge (also known as badge or badge) are used to display small
bits of information (e.g. unread counts, status, etc).
## Examples
<.badge>Badge</.badge>
<.badge size={:sm}>Badge</.badge>
<.badge variant={:outline}>Badge</.badge>
<.badge radius={:full}>Badge</.badge>
<.badge label="Badge" />
"""
FlintAttr.size()
FlintAttr.color()
FlintAttr.radius()
attr(:variant, :atom,
values: ~w(subtle light outline dashed solid dot)a,
default: theme(:badge).default.variant,
doc: "The badge variant/style."
)
attr(:uppercase, :boolean,
default: false,
doc: "Whether to render the badge text in uppercase."
)
attr(:label, :string,
default: nil,
doc: "The label of the badge, rendered if no inner block is provided."
)
attr(:show_remove_button, :boolean,
default: false,
doc: "Wether to render a remove button."
)
attr(:on_remove, JS,
default: %JS{},
doc: "The remove button on `phx-click` event handler."
)
attr(:class, :string,
default: nil,
doc: "The class of the badge."
)
attr(:rest, :global)
slot(:inner_block,
doc: "The inner block of the badge. If no content is provided,
the value of the attr `label` is used instead."
)
slot :left_addon,
doc: "Render and addon (icon, etc.)
in the left side of the badge content." do
attr(:class, :any)
end
slot :right_addon,
doc: "Render and addon (icon, etc.)
in the right side of the badge content." do
attr(:class, :any)
end
def badge(assigns) do
assigns = assigns |> assign_classes()
~H"""
<div class={@root_class} {@rest}>
<span class={@inner_class}>
<span :for={left_addon <- @left_addon} :if={@left_addon != []} class={[@left_addon_class, left_addon[:class]]}>
<%= render_slot(@left_addon) %>
</span>
<%= render_slot(@inner_block) || @label %>
<span :for={right_addon <- @right_addon} :if={@right_addon != []} class={[@right_addon_class, right_addon[:class]]}>
<%= render_slot(@right_addon) %>
</span>
<span :if={@show_remove_button} class={@right_addon_class}>
<.badge_button on_remove={@on_remove} />
</span>
</span>
</div>
"""
end
defp assign_classes(assigns) do
classes = badge_classes(assigns)
assigns
|> assign(:root_class, classes.root)
|> assign(:inner_class, classes.inner)
|> assign(:left_addon_class, classes.left_addon)
|> assign(:right_addon_class, classes.right_addon)
end
# --------------------------------------
# Badge button
# --------------------------------------
@doc """
Renders a badge button component.
By default it shows a remove icon (x).
"""
FlintAttr.color(name: :badge_button)
FlintAttr.size(name: :badge_button)
attr(:variant, :atom,
values: ~w(subtle light outline dashed solid dot gradient)a,
default: theme(:badge).default.variant,
doc: "The badge button variant/style."
)
attr(:on_remove, JS,
default: %JS{},
doc: "The on `phx-click` event handler."
)
attr(:class, :string,
default: nil,
doc: "The class of the badge button."
)
attr(:badge_radius, :atom,
values: radius_variants(),
default: :full,
doc: "The radius of the badge to calculate radius to use on the button.
Defaults to `:full`."
)
attr(:sr_label, :string,
default: "Remove",
doc: "The screen reader label of the badge button."
)
attr(:rest, :global)
slot(:inner_block,
doc: "The inner block of the button. If no content is provided, the default icon is used."
)
def badge_button(assigns) do
classes = badge_button_classes(assigns)
assigns =
assigns
|> assign(:root_class, classes.root)
|> assign(:overlay_class, classes.overlay)
|> assign_badge_button_radius()
~H"""
<button type="button" class={@root_class} phx-click={@on_remove} {@rest}>
<span class="sr-only"><%= @sr_label %></span>
<%= render_slot(@inner_block) || button_default_icon(assigns) %>
<span class={@overlay_class}></span>
</button>
"""
end
# TODO: Adjust colors/style size, etc. to match the badge variant
defp button_default_icon(assigns) do
~H"""
<svg viewBox="0 0 14 14" class="h-3.5 w-3.5 stroke-gray-600/50 group-hover:stroke-gray-600/75">
<path d="M4 4l6 6m0-6l-6 6" />
</svg>
"""
end
# The radius of the inner element should follow the rule:
# outer_radius = inner_radius + distance_between_elements
defp assign_badge_button_radius(assigns) do
# radius = assigns.badge_radius
assigns
end
# --------------------------------------
# Badge status dot
# --------------------------------------
# TODO: badge status dot
@doc """
Renders a badge status dot component.
"""
def badge_status_dot(assigns) do
~H"""
<div></div>
"""
end
# --------------------------------------
# Styles
# --------------------------------------
def badge_classes(%{size: size, radius: radius, variant: variant, color: color} = assigns) do
classes = theme(:badge)
root =
cx([
classes.base.root,
classes.base.font,
assigns.uppercase && "uppercase",
classes.base.size[size],
classes.base.radius[radius],
classes.variants[variant][color],
assigns.class
])
# TODO: Adjust margin to size
left_addon =
cx([
classes.base.addon,
case size do
:xs -> "-ml-1"
:sm -> "-ml-1"
:md -> "-ml-1"
:lg -> "-ml-1"
:xl -> "-ml-1"
end
])
# TODO: Adjust margin to size
right_addon =
cx([
classes.base.addon,
case size do
:xs -> "-mr-1"
:sm -> "-mr-1"
:md -> "-mr-1"
:lg -> "-mr-1"
:xl -> "-mr-1"
end
])
%{
root: root,
inner: classes.base.inner,
addon: classes.base.addon,
left_addon: left_addon,
right_addon: right_addon
}
end
defp badge_button_classes(
%{size: size, badge_radius: badge_radius, variant: variant, color: color} = assigns
) do
root =
cx([
"fl-badge-button group relative",
"h-3.5 w-3.5",
"rounded-full",
"hover:bg-green-600/20"
])
overlay =
cx([
"absolute",
case size do
:xs -> "-inset-0.5"
:sm -> "-inset-0.5"
:md -> "-inset-0.5"
:lg -> "-inset-1"
:xl -> "-inset-1"
end
])
%{root: root, overlay: overlay}
end
end