fix(flash): write custom styles, update translations

This commit is contained in:
Ivan R. 2024-07-09 23:07:57 +05:00
parent 1a394277d2
commit 560c2c1c8c
Signed by: lumin
GPG key ID: E0937DC7CD6D3817
14 changed files with 487 additions and 162 deletions

View file

@ -1,4 +1,5 @@
@import "./core_components.css";
@import "./flash.css";
:root {
--bg: #13151a;
@ -46,3 +47,8 @@ footer {
width: 100%;
margin: auto;
}
.icon {
width: 20px;
height: 20px;
}

View file

@ -126,46 +126,6 @@
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.flash {
position: fixed;
top: 0.5rem;
right: 0.5rem;
z-index: 50;
padding: 0.75rem;
margin-right: 0.5rem;
border-radius: 0.5rem;
box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
width: 20rem;
}
@media (min-width: 640px) {
.flash {
width: 24rem;
}
}
.flash-title {
display: flex;
gap: 0.375rem;
align-items: center;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.5rem;
}
.flash-close-button {
position: absolute;
top: 0.25rem;
right: 0.25rem;
padding: 0.5rem;
}
.flash-close-button-icon {
width: 1.25rem;
height: 1.25rem;
opacity: 0.4;
}
.simple-form {
margin-top: 2rem;
background-color: #ffffff;

51
assets/css/flash.css Normal file
View file

@ -0,0 +1,51 @@
.flash {
position: fixed;
top: 0.5rem;
right: 0.5rem;
z-index: 50;
padding: 16px;
border-radius: 0.3rem;
box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
width: 20rem;
}
.flash-info {
background-color: #15803d;
}
.flash-error {
background-color: #b91c1c;
}
@media (min-width: 640px) {
.flash {
width: 24rem;
}
}
.flash-title {
margin: 0;
display: flex;
gap: 8px;
align-items: center;
font-weight: 600;
}
.flash-body {
margin-bottom: 0;
text-indent: 28px;
}
.flash-close-button {
padding: 0;
position: absolute;
width: 28px;
height: 28px;
top: 0.5rem;
right: 0.5rem;
cursor: pointer;
}
.flash-close-button-icon {
opacity: 0.6;
}

View file

@ -11,6 +11,8 @@ config :comfycamp,
ecto_repos: [Comfycamp.Repo],
generators: [timestamp_type: :utc_datetime]
config :comfycamp, ComfycampWeb.Gettext, locales: ~w(en ru), default_locale: "ru"
# Configures the endpoint
config :comfycamp, ComfycampWeb.Endpoint,
url: [host: "localhost"],

View file

@ -84,7 +84,9 @@ defmodule ComfycampWeb do
# HTML escaping functionality
import Phoenix.HTML
# Core UI components and translation
import ComfycampWeb.Icons
import ComfycampWeb.CoreComponents
import ComfycampWeb.Flash
import ComfycampWeb.Gettext
# Shortcut for generating JS commands

View file

@ -18,6 +18,7 @@ defmodule ComfycampWeb.CoreComponents do
alias Phoenix.LiveView.JS
import ComfycampWeb.Gettext
import ComfycampWeb.Icons
@doc """
Renders a modal.
@ -85,93 +86,6 @@ defmodule ComfycampWeb.CoreComponents do
"""
end
@doc """
Renders flash notices.
## Examples
<.flash kind={:info} flash={@flash} />
<.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!</.flash>
"""
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 :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 = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)
~H"""
<div
:if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
id={@id}
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
role="alert"
class={[
"flash",
@kind == :info && "flash-info",
@kind == :error && "flash-error"
]}
{@rest}
>
<p :if={@title} class="flash-title">
<.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
<.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
<%= @title %>
</p>
<p class="mt-2 text-sm leading-5"><%= msg %></p>
<button type="button" class="flash-close-button" aria-label={gettext("close")}>
<.icon name="hero-x-mark-solid" class="flash-close-button-icon" />
</button>
</div>
"""
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"
attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
def flash_group(assigns) do
~H"""
<div id={@id}>
<.flash kind={:info} title={gettext("Success!")} flash={@flash} />
<.flash kind={:error} title={gettext("Error!")} flash={@flash} />
<.flash
id="client-error"
kind={:error}
title={gettext("We can't find the internet")}
phx-disconnected={show(".phx-client-error #client-error")}
phx-connected={hide("#client-error")}
hidden
>
<%= gettext("Attempting to reconnect") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
<.flash
id="server-error"
kind={:error}
title={gettext("Something went wrong!")}
phx-disconnected={show(".phx-server-error #server-error")}
phx-connected={hide("#server-error")}
hidden
>
<%= gettext("Hang in there while we get back on track") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
</div>
"""
end
@doc """
Renders a simple form.
@ -565,33 +479,6 @@ defmodule ComfycampWeb.CoreComponents do
"""
end
@doc """
Renders a [Heroicon](https://heroicons.com).
Heroicons come in three styles outline, solid, and mini.
By default, the outline style is used, but solid and mini may
be applied by using the `-solid` and `-mini` suffix.
You can customize the size and colors of the icons by setting
width, height, and background color classes.
Icons are extracted from the `deps/heroicons` directory and bundled within
your compiled app.css by the plugin in your `assets/tailwind.config.js`.
## Examples
<.icon name="hero-x-mark-solid" />
<.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" />
"""
attr :name, :string, required: true
attr :class, :string, default: nil
def icon(%{name: "hero-" <> _} = assigns) do
~H"""
<span class={[@name, @class]} />
"""
end
## JS Commands
def show(js \\ %JS{}, selector) do

View file

@ -0,0 +1,97 @@
defmodule ComfycampWeb.Flash do
@moduledoc """
Default flash component.
"""
use Phoenix.Component
alias Phoenix.LiveView.JS
import ComfycampWeb.Gettext
import ComfycampWeb.CoreComponents
import ComfycampWeb.Icons
@doc """
Renders flash notices.
## Examples
<.flash kind={:info} flash={@flash} />
<.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!</.flash>
"""
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 :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 = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)
~H"""
<div
:if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
id={@id}
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
role="alert"
class={[
"flash",
@kind == :info && "flash-info",
@kind == :error && "flash-error"
]}
{@rest}
>
<p :if={@title} class="flash-title">
<.icon :if={@kind == :error} name="hero-exclamation-circle" />
<.icon :if={@kind == :info} name="hero-information-circle" />
<%= @title %>
</p>
<p class="flash-body"><%= msg %></p>
<button type="button" class="flash-close-button" aria-label={gettext("close")}>
<.icon name="hero-x-mark" class="flash-close-button-icon" />
</button>
</div>
"""
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"
attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
def flash_group(assigns) do
~H"""
<div id={@id}>
<.flash kind={:info} title={gettext("Success!")} flash={@flash} />
<.flash kind={:error} title={gettext("Error!")} flash={@flash} />
<.flash
id="client-error"
kind={:error}
title={gettext("We can't find the internet")}
phx-disconnected={show(".phx-client-error #client-error")}
phx-connected={hide("#client-error")}
hidden
>
<%= gettext("Attempting to reconnect") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
<.flash
id="server-error"
kind={:error}
title={gettext("Something went wrong!")}
phx-disconnected={show(".phx-server-error #server-error")}
phx-connected={hide("#server-error")}
hidden
>
<%= gettext("Hang in there while we get back on track") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
</div>
"""
end
end

View file

@ -0,0 +1,58 @@
defmodule ComfycampWeb.Icons do
@moduledoc """
Provides reusable svg icons.
I would like to store icons as .svg files,
but I dont want to write a custom loader.
When trying to use the img element, we lose the ability to inherit color.
For now, I find this approach tolerable.
"""
use Phoenix.Component
@doc """
Icon component.
The default icon is face-frown from heroicons.
## Examples
<.icon name="hero-x-mark-solid" />
<.icon name="hero-arrow-path" class="custom-class" />
"""
attr :name, :string, required: true
attr :class, :string, default: nil
def icon(%{name: "hero-exclamation-circle"} = assigns) do
~H"""
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class={["icon", @class]}>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
"""
end
def icon(%{name: "hero-information-circle"} = assigns) do
~H"""
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class={["icon", @class]}>
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>
"""
end
def icon(%{name: "hero-x-mark"} = assigns) do
~H"""
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class={["icon", @class]}>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
"""
end
def icon(assigns) do
~H"""
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class={["icon", @class]}>
<path stroke-linecap="round" stroke-linejoin="round" d="M15.182 16.318A4.486 4.486 0 0 0 12.016 15a4.486 4.486 0 0 0-3.198 1.318M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM9.75 9.75c0 .414-.168.75-.375.75S9 10.164 9 9.75 9.168 9 9.375 9s.375.336.375.75Zm-.375 0h.008v.015h-.008V9.75Zm5.625 0c0 .414-.168.75-.375.75s-.375-.336-.375-.75.168-.75.375-.75.375.336.375.75Zm-.375 0h.008v.015h-.008V9.75Z" />
</svg>
"""
end
end

View file

@ -42,13 +42,6 @@ defmodule Comfycamp.MixProject do
{:floki, ">= 0.30.0", only: :test},
{:phoenix_live_dashboard, "~> 0.8.3"},
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
{:heroicons,
github: "tailwindlabs/heroicons",
tag: "v2.1.1",
sparse: "optimized",
app: false,
compile: false,
depth: 1},
{:swoosh, "~> 1.5"},
{:finch, "~> 0.13"},
{:telemetry_metrics, "~> 1.0"},

View file

@ -12,7 +12,6 @@
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
"gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"},
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized"]},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},

53
priv/gettext/default.pot Normal file
View file

@ -0,0 +1,53 @@
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new messages manually only if they're dynamic
## messages that can't be statically extracted.
##
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
#
msgid ""
msgstr ""
#: lib/comfycamp_web/components/core_components.ex:390
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:78
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:69
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:90
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:85
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:68
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:73
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/comfycamp_web/components/core_components.ex:74
#: lib/comfycamp_web/components/flash.ex:48
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""

View file

@ -0,0 +1,53 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: lib/comfycamp_web/components/core_components.ex:390
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:78
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:69
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:90
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:85
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:68
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:73
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/comfycamp_web/components/core_components.ex:74
#: lib/comfycamp_web/components/flash.ex:48
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""

View file

@ -0,0 +1,53 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: ru\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100 != 11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n"
#: lib/comfycamp_web/components/core_components.ex:390
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:78
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:69
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr "Ошибка!"
#: lib/comfycamp_web/components/flash.ex:90
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:85
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/comfycamp_web/components/flash.ex:68
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr "Успех!"
#: lib/comfycamp_web/components/flash.ex:73
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/comfycamp_web/components/core_components.ex:74
#: lib/comfycamp_web/components/flash.ex:48
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""

View file

@ -0,0 +1,111 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: ru\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100 != 11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n"
msgid "can't be blank"
msgstr ""
msgid "has already been taken"
msgstr ""
msgid "is invalid"
msgstr ""
msgid "must be accepted"
msgstr ""
msgid "has invalid format"
msgstr ""
msgid "has an invalid entry"
msgstr ""
msgid "is reserved"
msgstr ""
msgid "does not match confirmation"
msgstr ""
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should be %{count} byte(s)"
msgid_plural "should be %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should be at least %{count} byte(s)"
msgid_plural "should be at least %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should be at most %{count} byte(s)"
msgid_plural "should be at most %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""