defmodule TarakanWeb.CoreComponents do
@moduledoc """
Provides core UI components.
At first glance, this module may seem daunting, but its goal is to provide
core building blocks for your application, such as tables, forms, and
inputs. The components consist mostly of markup and are well-documented
with doc strings and declarative assigns. You may customize and style
them in any way you want, based on your application growth and needs.
The foundation for styling is Tailwind CSS, a utility-first CSS framework.
Here are useful references:
* [Tailwind CSS](https://tailwindcss.com) - the foundational framework
we build on. You will use it for layout, sizing, flexbox, grid, and
spacing.
* [Heroicons](https://heroicons.com) - see `icon/1` for usage.
* [Phoenix.Component](https://phoenix-live-view.hexdocs.pm/Phoenix.Component.html) -
the component system used by Phoenix. Some components, such as `<.link>`
and `<.form>`, are defined there.
"""
use Phoenix.Component
use Gettext, backend: TarakanWeb.Gettext
alias Phoenix.LiveView.JS
@doc """
Renders flash notices.
## Examples
<.flash kind={:info} flash={@flash} />
<.flash
id="welcome-back"
kind={:info}
phx-mounted={show("#welcome-back") |> JS.remove_attribute("hidden")}
hidden
>
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 :title, :string, default: nil
attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
attr :auto_dismiss, :boolean, default: true, doc: "automatically clears ordinary toast messages"
attr :dismiss_after, :integer, default: nil, doc: "auto-dismiss delay in milliseconds"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
slot :inner_block, doc: "the optional inner block that renders the flash message"
def flash(assigns) do
assigns =
assigns
|> assign_new(:id, fn -> "flash-#{assigns.kind}" end)
|> assign(:dismiss_after, assigns.dismiss_after || default_dismiss_after(assigns.kind))
~H"""
"""
end
defp default_dismiss_after(:info), do: 5_000
defp default_dismiss_after(:error), do: 8_000
@doc """
Small notched badge with a continuous outline (or solid fill via `bg-ink` /
`bg-signal`). Use instead of `border` + `clip-notch-sm`.
"""
attr :id, :string, default: nil
attr :class, :any, default: nil
attr :rest, :global
slot :inner_block, required: true
def notch_badge(assigns) do
~H"""
{render_slot(@inner_block)}
"""
end
@doc """
Renders a button with navigation support.
Controls sit outside the container border scale (2px = page region, 1px rule =
division inside a region). A 2px stroke on a button competes with the region
boundary it sits in, so secondary/danger buttons take a 1px stroke and primary
takes none at all - the fill is its edge.
Reach for `variant`, never a hand-rolled class string: the point of the
component is that Confirm and Dispute cannot silently drift apart.
## Examples
<.button>Send!
<.button phx-click="go" variant="primary">Send!
<.button variant="danger" size="sm">Dispute
<.button navigate={~p"/"}>Home
"""
attr :rest, :global,
include: ~w(href navigate patch method download name value disabled form type)
attr :class, :any, default: nil, doc: "extras only (margin, layout) - never competing styling"
attr :variant, :string, default: nil, values: [nil, "primary", "danger", "quiet"]
attr :size, :string, default: "md", values: ~w(sm md)
slot :inner_block, required: true
def button(%{rest: rest} = assigns) do
# Primary is filled + clip-notch (solid edge). Outline buttons omit clip-notch:
# border + clip-path removes the top stroke.
variants = %{
"primary" => "clip-notch bg-btn text-btn-fg hover:opacity-90",
"danger" => "border border-signal text-signal hover:bg-signal hover:text-btn-fg",
"quiet" => "text-ink-muted hover:text-ink",
nil => "border border-strong text-ink hover:bg-panel"
}
sizes = %{
"md" => "px-4 py-2 text-sm",
"sm" => "px-3 py-1.5 text-[11px]"
}
assigns =
assign(assigns, :button_class, [
"inline-flex items-center justify-center gap-1.5 font-display uppercase tracking-[0.12em]",
"transition focus:outline-none focus:ring-2 focus:ring-phosphor",
"disabled:cursor-not-allowed disabled:opacity-50",
Map.fetch!(sizes, assigns.size),
Map.fetch!(variants, assigns.variant),
assigns.class
])
if rest[:href] || rest[:navigate] || rest[:patch] do
~H"""
<.link class={@button_class} {@rest}>
{render_slot(@inner_block)}
"""
else
~H"""
"""
end
end
@doc """
Renders an input with label and error messages.
A `Phoenix.HTML.FormField` may be passed as argument,
which is used to retrieve the input name, id, and values.
Otherwise all attributes may be passed explicitly.
## Types
This function accepts all HTML input types, considering that:
* You may also set `type="select"` to render a `