-
+ <.icon name="hero-x-mark-solid" class="w-5 h-5" />
@@ -82,7 +86,11 @@ defmodule PhxWCStorybookWeb.CoreComponents do
<%= render_slot(@title) %>
-
+
<%= render_slot(@subtitle) %>
@@ -122,15 +130,15 @@ defmodule PhxWCStorybookWeb.CoreComponents do
<.flash kind={:info} flash={@flash} />
<.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!
"""
- attr(:id, :string, default: "flash", 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(:autoshow, :boolean, default: true, doc: "whether to auto show the flash on mount")
- attr(:close, :boolean, default: true, doc: "whether the flash can be closed")
- attr(:rest, :global, doc: "the arbitrary HTML attributes to add to the flash container")
+ attr :id, :string, default: "flash", 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 :autoshow, :boolean, default: true, doc: "whether to auto show the flash on mount"
+ attr :close, :boolean, default: true, doc: "whether the flash can be closed"
+ 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")
+ slot :inner_block, doc: "the optional inner block that renders the flash message"
def flash(assigns) do
~H"""
@@ -138,7 +146,7 @@ defmodule PhxWCStorybookWeb.CoreComponents do
:if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
id={@id}
phx-mounted={@autoshow && show("##{@id}")}
- phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("#flash")}
+ phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
role="alert"
class={[
"fixed hidden top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 shadow-md shadow-zinc-900/5 ring-1",
@@ -148,8 +156,8 @@ defmodule PhxWCStorybookWeb.CoreComponents do
{@rest}
>
-
-
+ <.icon :if={@kind == :info} name="hero-information-circle-mini" class="w-4 h-4" />
+ <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="w-4 h-4" />
<%= @title %>
<%= msg %>
@@ -159,35 +167,61 @@ defmodule PhxWCStorybookWeb.CoreComponents do
class="group absolute top-2 right-1 p-2"
aria-label={gettext("close")}
>
-
+ <.icon name="hero-x-mark-solid" class="w-5 h-5 opacity-40 group-hover:opacity-70" />
"""
end
+ @doc """
+ Shows the flash group with standard titles and content.
+
+ ## Examples
+
+ <.flash_group flash={@flash} />
+ """
+ attr :flash, :map, required: true, doc: "the map of flash messages"
+
+ def flash_group(assigns) do
+ ~H"""
+ <.flash kind={:info} title="Success!" flash={@flash} />
+ <.flash kind={:error} title="Error!" flash={@flash} />
+ <.flash
+ id="disconnected"
+ kind={:error}
+ title="We can't find the internet"
+ close={false}
+ autoshow={false}
+ phx-disconnected={show("#disconnected")}
+ phx-connected={hide("#disconnected")}
+ >
+ Attempting to reconnect <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" />
+
+ """
+ end
+
@doc """
Renders a simple form.
## Examples
- <.simple_form :let={f} for={:user} phx-change="validate" phx-submit="save">
- <.input field={{f, :email}} label="Email"/>
- <.input field={{f, :username}} label="Username" />
+ <.simple_form for={@form} phx-change="validate" phx-submit="save">
+ <.input field={@form[:email]} label="Email"/>
+ <.input field={@form[:username]} label="Username" />
<:actions>
<.button>Save
"""
- attr(:for, :any, default: nil, doc: "the datastructure for the form")
- attr(:as, :any, default: nil, doc: "the server side parameter to collect all input under")
+ attr :for, :any, required: true, doc: "the datastructure for the form"
+ attr :as, :any, default: nil, doc: "the server side parameter to collect all input under"
- attr(:rest, :global,
+ attr :rest, :global,
include: ~w(autocomplete name rel action enctype method novalidate target),
doc: "the arbitrary HTML attributes to apply to the form tag"
- )
- slot(:inner_block, required: true)
- slot(:actions, doc: "the slot for form actions, such as a submit button")
+ slot :inner_block, required: true
+ slot :actions, doc: "the slot for form actions, such as a submit button"
def simple_form(assigns) do
~H"""
@@ -210,11 +244,11 @@ defmodule PhxWCStorybookWeb.CoreComponents do
<.button>Send!
<.button phx-click="go" class="ml-2">Send!
"""
- attr(:type, :string, default: nil)
- attr(:class, :string, default: nil)
- attr(:rest, :global, include: ~w(disabled form name value))
+ attr :type, :string, default: nil
+ attr :class, :string, default: nil
+ attr :rest, :global, include: ~w(disabled form name value)
- slot(:inner_block, required: true)
+ slot :inner_block, required: true
def button(assigns) do
~H"""
@@ -241,60 +275,61 @@ defmodule PhxWCStorybookWeb.CoreComponents do
## Examples
- <.input field={{f, :email}} type="email" />
+ <.input field={@form[:email]} type="email" />
<.input name="my-input" errors={["oh no!"]} />
"""
- attr(:id, :any)
- attr(:name, :any)
- attr(:label, :string, default: nil)
+ attr :id, :any, default: nil
+ attr :name, :any
+ attr :label, :string, default: nil
+ attr :value, :any
- attr(:type, :string,
+ attr :type, :string,
default: "text",
values: ~w(checkbox color date datetime-local email file hidden month number password
range radio search select tel text textarea time url week)
- )
-
- attr(:value, :any)
- attr(:field, :any, doc: "a %Phoenix.HTML.Form{}/field name tuple, for example: {f, :email}")
- attr(:errors, :list)
- attr(:checked, :boolean, doc: "the checked flag for checkbox inputs")
- attr(:prompt, :string, default: nil, doc: "the prompt for select inputs")
- attr(:options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2")
- attr(:multiple, :boolean, default: false, doc: "the multiple flag for select inputs")
- attr(:rest, :global, include: ~w(autocomplete disabled form max maxlength min minlength
- pattern placeholder readonly required size step))
- slot(:inner_block)
-
- def input(%{field: {f, field}} = assigns) do
+
+ attr :field, Phoenix.HTML.FormField,
+ doc: "a form field struct retrieved from the form, for example: @form[:email]"
+
+ attr :errors, :list, default: []
+ attr :checked, :boolean, doc: "the checked flag for checkbox inputs"
+ attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
+ attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
+ attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"
+ attr :rest, :global, include: ~w(autocomplete cols disabled form max maxlength min minlength
+ pattern placeholder readonly required rows size step)
+ slot :inner_block
+
+ def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
assigns
- |> assign(field: nil)
- |> assign_new(:name, fn ->
- name = Phoenix.HTML.Form.input_name(f, field)
- if assigns.multiple, do: name <> "[]", else: name
- end)
- |> assign_new(:id, fn -> Phoenix.HTML.Form.input_id(f, field) end)
- |> assign_new(:value, fn -> Phoenix.HTML.Form.input_value(f, field) end)
- |> assign_new(:errors, fn -> translate_errors(f.errors || [], field) end)
+ |> assign(field: nil, id: assigns.id || field.id)
+ |> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
+ |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
+ |> assign_new(:value, fn -> field.value end)
|> input()
end
- def input(%{type: "checkbox"} = assigns) do
- assigns = assign_new(assigns, :checked, fn -> input_equals?(assigns.value, "true") end)
+ def input(%{type: "checkbox", value: value} = assigns) do
+ assigns =
+ assign_new(assigns, :checked, fn -> Phoenix.HTML.Form.normalize_value("checkbox", value) end)
~H"""
-
+
+
+ <.error :for={msg <- @errors}><%= msg %>
+
"""
end
@@ -309,7 +344,7 @@ defmodule PhxWCStorybookWeb.CoreComponents do
multiple={@multiple}
{@rest}
>
-
+
<%= Phoenix.HTML.Form.options_for_select(@options, @value) %>
<.error :for={msg <- @errors}><%= msg %>
@@ -325,13 +360,14 @@ defmodule PhxWCStorybookWeb.CoreComponents do
id={@id || @name}
name={@name}
class={[
- input_border(@errors),
"mt-2 block min-h-[6rem] w-full rounded-lg border-zinc-300 py-[7px] px-[11px]",
"text-zinc-900 focus:border-zinc-400 focus:outline-none focus:ring-4 focus:ring-zinc-800/5 sm:text-sm sm:leading-6",
- "phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5"
+ "phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5",
+ "border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5",
+ @errors != [] && "border-rose-400 focus:border-rose-400 focus:ring-rose-400/10"
]}
{@rest}
- ><%= @value %>
+ ><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %>
<.error :for={msg <- @errors}><%= msg %>
"""
@@ -345,12 +381,13 @@ defmodule PhxWCStorybookWeb.CoreComponents do
type={@type}
name={@name}
id={@id || @name}
- value={@value}
+ value={Phoenix.HTML.Form.normalize_value(@type, @value)}
class={[
- input_border(@errors),
"mt-2 block w-full rounded-lg border-zinc-300 py-[7px] px-[11px]",
"text-zinc-900 focus:outline-none focus:ring-4 sm:text-sm sm:leading-6",
- "phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5"
+ "phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5",
+ "border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5",
+ @errors != [] && "border-rose-400 focus:border-rose-400 focus:ring-rose-400/10"
]}
{@rest}
/>
@@ -359,17 +396,11 @@ defmodule PhxWCStorybookWeb.CoreComponents do
"""
end
- defp input_border([] = _errors),
- do: "border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5"
-
- defp input_border([_ | _] = _errors),
- do: "border-rose-400 focus:border-rose-400 focus:ring-rose-400/10"
-
@doc """
Renders a label.
"""
- attr(:for, :string, default: nil)
- slot(:inner_block, required: true)
+ attr :for, :string, default: nil
+ slot :inner_block, required: true
def label(assigns) do
~H"""
@@ -382,12 +413,12 @@ defmodule PhxWCStorybookWeb.CoreComponents do
@doc """
Generates a generic error message.
"""
- slot(:inner_block, required: true)
+ slot :inner_block, required: true
def error(assigns) do
~H"""
"""
@@ -396,11 +427,11 @@ defmodule PhxWCStorybookWeb.CoreComponents do
@doc """
Renders a header with title.
"""
- attr(:class, :string, default: nil)
+ attr :class, :string, default: nil
- slot(:inner_block, required: true)
- slot(:subtitle)
- slot(:actions)
+ slot :inner_block, required: true
+ slot :subtitle
+ slot :actions
def header(assigns) do
~H"""
@@ -428,19 +459,29 @@ defmodule PhxWCStorybookWeb.CoreComponents do
<:col :let={user} label="username"><%= user.username %>
"""
- attr(:id, :string, required: true)
- attr(:row_click, :any, default: nil)
- attr(:rows, :list, required: true)
+ attr :id, :string, required: true
+ attr :rows, :list, required: true
+ attr :row_id, :any, default: nil, doc: "the function for generating the row id"
+ attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"
+
+ attr :row_item, :any,
+ default: &Function.identity/1,
+ doc: "the function for mapping each row before calling the :col and :action slots"
slot :col, required: true do
- attr(:label, :string)
+ attr :label, :string
end
- slot(:action, doc: "the slot for showing user actions in the last table column")
+ slot :action, doc: "the slot for showing user actions in the last table column"
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"""
-