From 967f6bec2097b289af5f0e562a696f24327b123a Mon Sep 17 00:00:00 2001 From: Nelson Glauber Date: Thu, 23 May 2024 11:00:55 -0300 Subject: [PATCH 1/5] wip --- .../live/user_login_live.jetpack.ex | 11 +++++++ .../live/user_registration_live.ex | 23 +++++++++++++++ .../live/user_registration_live.jetpack.ex | 27 ++++++++++++++++++ lib/form_demo_web/styles/app.jetpack.ex | 16 +++++++++++ priv/static/images/logo.svg.gz | Bin 0 -> 1613 bytes 5 files changed, 77 insertions(+) create mode 100644 lib/form_demo_web/live/user_login_live.jetpack.ex create mode 100644 lib/form_demo_web/live/user_registration_live.jetpack.ex create mode 100644 priv/static/images/logo.svg.gz diff --git a/lib/form_demo_web/live/user_login_live.jetpack.ex b/lib/form_demo_web/live/user_login_live.jetpack.ex new file mode 100644 index 0000000..99c144b --- /dev/null +++ b/lib/form_demo_web/live/user_login_live.jetpack.ex @@ -0,0 +1,11 @@ +defmodule FormDemoWeb.UserLoginLive.Jetpack do + use FormDemoNative, [:render_component, format: :jetpack] + + def render(assigns, _) do + ~LVN""" + + Hello + + """ + end +end diff --git a/lib/form_demo_web/live/user_registration_live.ex b/lib/form_demo_web/live/user_registration_live.ex index a2d6c3a..762a64a 100644 --- a/lib/form_demo_web/live/user_registration_live.ex +++ b/lib/form_demo_web/live/user_registration_live.ex @@ -38,6 +38,8 @@ defmodule FormDemoWeb.UserRegistrationLive do <:actions> <.button phx-disable-with="Creating account..." class="w-full">Create an account + + """ @@ -49,6 +51,8 @@ defmodule FormDemoWeb.UserRegistrationLive do socket = socket |> assign(trigger_submit: false, check_errors: false) + |> assign(:isVisible, "true") + |> assign(:isExpanded, "true") |> assign_form(changeset) {:ok, socket, temporary_assigns: [form: nil]} @@ -76,6 +80,25 @@ defmodule FormDemoWeb.UserRegistrationLive do {:noreply, assign_form(socket, Map.put(changeset, :action, :validate))} end + def handle_event("navigateToLogin", _params, socket) do + {:noreply, push_navigate(socket, to: "/users/log_in")} + end + + def handle_event("toggleVisibility", param, socket) do + {:noreply, assign(socket, :isVisible, param)} + end + + def handle_event("buttonSize", param, socket) do + newSize = if socket.assigns.isExpanded == "true" do "false" else "true" end + {:noreply, assign(socket, :isExpanded, newSize)} + end + + def handle_event("onAnimationFinished", param, socket) do + IO.puts "onAnimationFinished" + IO.inspect param + {:noreply, socket} + end + defp assign_form(socket, %Ecto.Changeset{} = changeset) do form = to_form(changeset, as: "user") diff --git a/lib/form_demo_web/live/user_registration_live.jetpack.ex b/lib/form_demo_web/live/user_registration_live.jetpack.ex new file mode 100644 index 0000000..79e8b8f --- /dev/null +++ b/lib/form_demo_web/live/user_registration_live.jetpack.ex @@ -0,0 +1,27 @@ +defmodule FormDemoWeb.UserRegistrationLive.Jetpack do + use FormDemoNative, [:render_component, format: :jetpack] + + def render(assigns, _) do + ~LVN""" + + + + Error loading image + + + + + Jetpack Compose!!! + + <%= if @isExpanded == "true" do %> + + <% end %> + + + + + """ + end +end diff --git a/lib/form_demo_web/styles/app.jetpack.ex b/lib/form_demo_web/styles/app.jetpack.ex index 0b3c873..13a124f 100644 --- a/lib/form_demo_web/styles/app.jetpack.ex +++ b/lib/form_demo_web/styles/app.jetpack.ex @@ -6,6 +6,22 @@ defmodule FormDemoWeb.Styles.App.Jetpack do # Refer to your client's documentation on what the proper syntax # is for defining rules within classes ~SHEET""" + "box-style" do + background(Color(255, 255, 0, 255)) + clickable(enabled: true, onClickLabel: "string", role: Role.Button, onClick: event("my-click-event")) + end + + "box-style2" do + progressSemantics(1.0, 1...100, 5) + align(Alignment.BottomEnd) + padding(Dp(20)) + end + + "box-size" do + background(Color.Red) + animateContentSize(finishedListener: event("onAnimationFinished")) + end + """ # If you need to have greater control over how your style rules are created diff --git a/priv/static/images/logo.svg.gz b/priv/static/images/logo.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..d8d1f38b39fd9260b40ceceae1fd11e34f4a9518 GIT binary patch literal 1613 zcmV-T2D14diwFP!000006HS)SZXP!h#P5EJf%ZHUn{2Ylg=HYgBDZ}3-Gt9hY(TaH zMTzqG`>P%w7O+P%{P?H4x~iJ*|NQ&+Ve!p+E z)_Z2$9e;oM^!D@je;)4YQID|0*WK~km*?k)yW3wcFQ2}>{__3#`^(+&^z!BD{QTwP z$4}oL?p|O1`gHf<-EqACs)0|XW3Ydr?1KBvl{EW zZB~TObFhvj(>NunW_Qz$QxIPXEIULL;TePA5rvq- z+ZAh<+PY^@lA6cV(k%@_E#l`e7nW5?^{SnEpIPt6uN|@~`v6d=OVC5GPdzvS5|RS- zEv-ot3*}m(D5>%)si17BSo_AfsL_(^#aN_?aQ4y+V@(rvz^FrSp1YGgRD{|KIH>IPFwT+05laM|?E+ z2X9llN&v#tg|=%1wi7ot+5l~KnUqwA1jK?WvuQFwSaQiI6c$BxMxk@0H%0$xN?LyW z_#^9O6`oYf3LUXkwJhS4XUdRbsHBt&iI|_gQt@$9afNpXl&S}^E2+-8Q~o36!d839 z`YM|tuO-eKQQ|M-Vt3$l5sF(ZL9qB%a&V{!O{A{Wm;xv-(G4=MlJ2yJfU=&R&QlK2 zu>yxY>?d@)B@aZ9t~yNW;=Fj*@dMUCdS62dJCRmR=NO|ue#J@QsGUMSvuTTep8#7xIVAacUgMG zOe>*M;i8YumFO%_RiCnu4xomOd6Q)kfVu+Ti8%}u3^HJf)YJ|H17ka>w*;|C6dP5A z%NvKOV5_Q#t%+&gv$yAaRDo8nhSD*?0h=J*poZEGJ4|add25Dy|_Sc;H-Bf4LPYFel=WIH(3aq^w>!P=90O_?v<03eu~ z`kxOq5naL1pX^=A-_mVesEDF87*8|pCaPF&TJz4ehprucM&8U|>*#S&LtK~Bxh_dU z!m8z6Ydz5h`ByLhf>C)JsuKuBC2Ufcm$Y#@9U*N(PEjs(f@t&qUwdA6n$V>rO<2Po zVxPBcI^t{goV|1_0%c&?g1WA!T_$3_o|l_ayvSz9RY3hC6*jVMC?<&{t!}8NTvwlv zqmAx)-PE$#@ Date: Thu, 23 May 2024 14:11:52 -0300 Subject: [PATCH 2/5] wip 2 --- .../layouts_jetpack/{app.swiftui.neex => app.jetpack.neex} | 0 .../layouts_jetpack/{root.swiftui.neex => root.jetpack.neex} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename lib/form_demo_web/components/layouts_jetpack/{app.swiftui.neex => app.jetpack.neex} (100%) rename lib/form_demo_web/components/layouts_jetpack/{root.swiftui.neex => root.jetpack.neex} (100%) diff --git a/lib/form_demo_web/components/layouts_jetpack/app.swiftui.neex b/lib/form_demo_web/components/layouts_jetpack/app.jetpack.neex similarity index 100% rename from lib/form_demo_web/components/layouts_jetpack/app.swiftui.neex rename to lib/form_demo_web/components/layouts_jetpack/app.jetpack.neex diff --git a/lib/form_demo_web/components/layouts_jetpack/root.swiftui.neex b/lib/form_demo_web/components/layouts_jetpack/root.jetpack.neex similarity index 100% rename from lib/form_demo_web/components/layouts_jetpack/root.swiftui.neex rename to lib/form_demo_web/components/layouts_jetpack/root.jetpack.neex From 9b4523703e9fb607edde2b63c5c526abee6cfa06 Mon Sep 17 00:00:00 2001 From: Nelson Glauber Date: Fri, 24 May 2024 13:11:49 -0300 Subject: [PATCH 3/5] add jetpack layouts --- lib/form_demo_native.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/form_demo_native.ex b/lib/form_demo_native.ex index 609a2a8..60df385 100644 --- a/lib/form_demo_native.ex +++ b/lib/form_demo_native.ex @@ -35,10 +35,12 @@ defmodule FormDemoNative do quote do use LiveViewNative.LiveView, formats: [ - :swiftui + :swiftui, + :jetpack ], layouts: [ - swiftui: {FormDemoWeb.Layouts.SwiftUI, :app} + swiftui: {FormDemoWeb.Layouts.SwiftUI, :app}, + jetpack: {FormDemoWeb.Layouts.Jetpack, :app} ] unquote(verified_routes()) From bde0509c9383e2c4f719d5681876a4bc0d0b962d Mon Sep 17 00:00:00 2001 From: Nelson Glauber Date: Wed, 20 Nov 2024 15:24:12 -0300 Subject: [PATCH 4/5] Version with Jetpack sample working. --- config/config.exs | 20 +- config/dev.exs | 5 +- lib/form_demo_native.ex | 29 +- .../components/core_components.jetpack.ex | 670 ++++++++++++++++++ .../layouts_jetpack/app.jetpack.neex | 1 + .../layouts_jetpack/root.jetpack.neex | 2 +- .../live/user_forgot_password.jetpack.ex | 26 + .../live/user_login_live.jetpack.ex | 30 +- .../live/user_registration_live.jetpack.ex | 55 +- .../live/user_settings_live.jetpack.ex | 71 ++ lib/form_demo_web/router.ex | 10 +- lib/form_demo_web/styles/app.jetpack.ex | 19 +- mix.lock | 58 +- 13 files changed, 909 insertions(+), 87 deletions(-) create mode 100644 lib/form_demo_web/components/core_components.jetpack.ex create mode 100644 lib/form_demo_web/live/user_forgot_password.jetpack.ex create mode 100644 lib/form_demo_web/live/user_settings_live.jetpack.ex diff --git a/config/config.exs b/config/config.exs index c1f30ca..7cc7857 100644 --- a/config/config.exs +++ b/config/config.exs @@ -62,25 +62,25 @@ config :logger, :console, config :phoenix, :json_library, Jason config :live_view_native, plugins: [ - LiveViewNative.SwiftUI, LiveViewNative.Jetpack, + LiveViewNative.SwiftUI ] config :mime, :types, %{ - "text/swiftui" => ["swiftui"], "text/jetpack" => ["jetpack"], - "text/styles" => ["styles"] + "text/styles" => ["styles"], + "text/swiftui" => ["swiftui"] } # LVN - Required, you must configure LiveView Native Stylesheets # on where class names shoudl be extracted from config :live_view_native_stylesheet, content: [ - swiftui: [ - "lib/**/*swiftui*" - ], jetpack: [ "lib/**/*jetpack*" + ], + swiftui: [ + "lib/**/*swiftui*" ] ], output: "priv/static/assets" @@ -88,13 +88,15 @@ config :live_view_native_stylesheet, # LVN - Required, you must configure Phoenix to know how # to encode for the swiftui format config :phoenix_template, :format_encoders, [ - swiftui: Phoenix.HTML.Engine, - jetpack: Phoenix.HTML.Engine + jetpack: Phoenix.HTML.Engine, + swiftui: Phoenix.HTML.Engine ] # LVN - Required, you must configure Phoenix so it knows # how to compile LVN's neex templates -config :phoenix, :template_engines, neex: LiveViewNative.Engine +config :phoenix, :template_engines, [ + neex: LiveViewNative.Engine +] # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/config/dev.exs b/config/dev.exs index 7047039..7e084c3 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -58,7 +58,10 @@ config :form_demo, FormDemoWeb.Endpoint, patterns: [ ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$", ~r"priv/gettext/.*(po)$", - ~r"lib/form_demo_web/(controllers|live|components|styles)/.*(ex|heex|neex)$" + ~r"lib/form_demo_web/(controllers|live|components|styles)/.*(ex|heex|neex)$", + ~r"lib/form_demo_web/(live|components)/.*neex$", + ~r"lib/form_demo_web/styles/*.ex$", + ~r"priv/static/*.styles$" ] ] diff --git a/lib/form_demo_native.ex b/lib/form_demo_native.ex index 60df385..fe0e693 100644 --- a/lib/form_demo_native.ex +++ b/lib/form_demo_native.ex @@ -35,12 +35,12 @@ defmodule FormDemoNative do quote do use LiveViewNative.LiveView, formats: [ - :swiftui, - :jetpack + :jetpack, + :swiftui ], layouts: [ - swiftui: {FormDemoWeb.Layouts.SwiftUI, :app}, - jetpack: {FormDemoWeb.Layouts.Jetpack, :app} + jetpack: {FormDemoWeb.Layouts.Jetpack, :app}, + swiftui: {FormDemoWeb.Layouts.SwiftUI, :app} ] unquote(verified_routes()) @@ -106,7 +106,7 @@ defmodule FormDemoNative do defmodule MyAppWeb.Layouts.SwiftUI do use MyAppNative, [:layout, format: :swiftui] - embed_tempaltes "layouts_swiftui/*" + embed_templates "layouts_swiftui/*" end ''' def layout(opts) do @@ -125,8 +125,9 @@ defmodule FormDemoNative do gettext_quoted = quote do import FormDemoWeb.Gettext end - + plugin = LiveViewNative.fetch_plugin!(format) + plugin_component_quoted = try do Code.ensure_compiled!(plugin.component) @@ -137,7 +138,7 @@ defmodule FormDemoNative do _ -> nil end - live_form_component_quoted = quote do + live_form_quoted = quote do import LiveViewNative.LiveForm.Component end @@ -154,13 +155,13 @@ defmodule FormDemoNative do end [ - gettext_quoted, - plugin_component_quoted, - live_form_component_quoted, - core_component_quoted, - verified_routes() - ] - + gettext_quoted, + plugin_component_quoted, + live_form_quoted, + core_component_quoted, + verified_routes() + ] + end @doc """ diff --git a/lib/form_demo_web/components/core_components.jetpack.ex b/lib/form_demo_web/components/core_components.jetpack.ex new file mode 100644 index 0000000..822a47e --- /dev/null +++ b/lib/form_demo_web/components/core_components.jetpack.ex @@ -0,0 +1,670 @@ +defmodule FormDemoWeb.CoreComponents.Jetpack do + @moduledoc """ + Provides core UI components built for Jetpack. + + This file contains feature parity components to your applications's CoreComponent module. + The goal is to retain a common API for fast prototyping. Leveraging your existing knowledge + of the `FormDemoWeb.CoreComponents` functions you should expect identical functionality for similarly named + components between web and native. That means utilizing your existing `handle_event/3` functions to manage state + and stay focused on adding new templates for your native applications. + + Icons are referenced by a system name. Read more about the [Xcode Asset Manager](https://developer.apple.com/documentation/xcode/asset-management) + to learn how to include different assets in your LiveView Native applications. In addition, you can also use [SF Symbols](https://developer.apple.com/sf-symbols/). + On any MacOS open Spotlight and search `SF Symbols`. The catalog application will provide a reference name that can be used. All SF Symbols + are incuded with all Jetpack applications. + + Most of this documentation was "borrowed" from the analog Phoenix generated file to ensure this project is expressing the same behavior. + """ + + use LiveViewNative.Component + + import LiveViewNative.LiveForm.Component + + @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 Jetpack input types, considering that: + + * You may also set `type="Picker"` to render a `` tag + + * `type="Toggle"` is used exclusively to render boolean values + + ## Examples + + + <.input field={@form[:email]} type="TextField" /> + <.input name="my-input" errors={["oh no!"]} /> + + + [INSERT LVATTRDOCS] + """ + @doc type: :component + + attr :id, :any, default: nil + attr :name, :any + attr :label, :string, default: nil + attr :value, :any + + attr :type, :string, + default: "TextField", + values: ~w(Checkbox DatePicker MultiDatePicker Picker SecureField Slider TextField hidden SingleChoiceSegmentedButtonRow) + + 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 :min, :any, default: nil + attr :max, :any, default: nil + + attr :placeholder, :string, default: nil + + attr :readonly, :boolean, default: false + + attr :autocomplete, :string, + default: "on", + values: ~w(on off) + + attr :rest, :global, + include: ~w(disabled step) + + slot :inner_block + + def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do + #dbg assigns + assigns + |> 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) + |> assign( + :rest, + Map.put(assigns.rest, :style, [ + Map.get(assigns.rest, :style, ""), + (if assigns.readonly or Map.get(assigns.rest, :disabled, false), do: "disabled(true)", else: ""), + (if assigns.autocomplete == "off", do: "textInputAutocapitalization(.never) autocorrectionDisabled()", else: "") + ] |> Enum.join(" ")) + ) + |> input() + end + + def input(%{type: "hidden"} = assigns) do + ~LVN""" + + """ + end + + # def input(%{type: "TextFieldLink"} = assigns) do + # ~LVN""" + # + # + # <%= @label %> + # + # <%= @label %> + # + # + # <.error :for={msg <- @errors}><%= msg %> + # + # """ + # end + + def input(%{type: "DatePicker"} = assigns) do + ~LVN""" + + + <%= @label %> + + <.error :for={msg <- @errors}><%= msg %> + + """ + end + + def input(%{type: "MultiDatePicker"} = assigns) do + [start_date, end_date] = Jason.decode!(assigns.value) + assigns = assigns + |> assign(:start_date, start_date) + |> assign(:end_date, end_date) + ~LVN""" + + + <%= @label %> + + <.error :for={msg <- @errors}><%= msg %> + + """ + end + + def input(%{type: "Picker"} = assigns) do + ~LVN""" + + + elem(0)} readOnly="true" style="menuAnchor()" > + <%= @label %> + <%= @placeholder %> + + + + <%= name %> + + + + <.error :for={msg <- @errors}><%= msg %> + + """ + end + + def input(%{type: "SingleChoiceSegmentedButtonRow"} = assigns) do + ~LVN""" + + <%= @label %> + + + <%= name %> + + + <.error :for={msg <- @errors}><%= msg %> + + """ + end + + + def input(%{type: "Slider"} = assigns) do + ~LVN""" + + <%= @label %> + + <.error :for={msg <- @errors}><%= msg %> + + """ + end + + # def input(%{type: "Stepper"} = assigns) do + # ~LVN""" + # + # + # <%= @label %> + # + # + # <.error :for={msg <- @errors}><%= msg %> + # + # """ + # end + + # def input(%{type: "TextEditor"} = assigns) do + # ~LVN""" + # + # <%= @label %> + # + # <.error :for={msg <- @errors}><%= msg %> + # + # """ + # end + + def input(%{type: "TextField"} = assigns) do + ~LVN""" + + + <%= @label %> + <%= @placeholder %> + + <.error :for={msg <- @errors}><%= msg %> + + """ + end + + def input(%{type: "SecureField"} = assigns) do + ~LVN""" + + + <%= @label %> + <%= @placeholder %> + + <.error :for={msg <- @errors}><%= msg %> + + """ + end + + def input(%{type: "Checkbox"} = assigns) do + ~LVN""" + + + + <%= @label %> + + <.error :for={msg <- @errors}><%= msg %> + + """ + end + + # def input(%{type: "Radio"} = assigns) do + # ~LVN""" + # + # + # + # <%= @label %> + # + # <.error :for={msg <- @errors}><%= msg %> + # + # """ + # end + + @doc """ + Generates a generic error message. + """ + @doc type: :component + slot :inner_block, required: true + + def error(assigns) do + ~LVN""" + + <%= render_slot(@inner_block) %> + + """ + end + + @doc """ + Renders a header with title. + + [INSERT LVATTRDOCS] + """ + @doc type: :component + + slot :inner_block, required: true + slot :subtitle + slot :actions + + def header(assigns) do + ~LVN""" + + + + <%= render_slot(@inner_block) %> + + + <%= render_slot(@subtitle) %> + + + + <%= render_slot(@actions) %> + + + """ + end + + @doc """ + Renders a modal. + + ## Examples + + <.modal show={@show} id="confirm-modal"> + This is a modal. + + + An event name may be passed to the `:on_cancel` to configure + the closing/cancel event, for example: + + <.modal show={@show} id="confirm" on_cancel="toggle-show"> + This is another modal. + + + """ + attr :id, :string, required: true + attr :show, :boolean, default: false + attr :on_cancel, :string, default: nil + slot :inner_block, required: true + + def modal(assigns) do + ~LVN""" + <%= if @show do %> + + <%= render_slot(@inner_block) %> + + <% end %> + """ + end + + @doc """ + Renders flash notices. + + ## Examples + + <.flash kind={:info} flash={@flash} /> + <.flash kind={:info} 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 :title, :string, default: nil + attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" + attr :rest, :global, doc: "the arbitrary 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) + + ~LVN""" + <% msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind) %> + + <%= @title %> + <%= msg %> + + + """ + 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 + ~LVN""" + + <.flash kind={:info} title={"Success!"} flash={@flash} /> + <.flash kind={:error} title={"Error!"} flash={@flash} /> + + """ + end + + @doc """ + Renders a simple form. + + ## Examples + + <.simple_form for={@form} phx-change="validate" phx-submit="save"> + <.input field={@form[:email]} label="Email"/> + <.input field={@form[:username]} label="Username" /> + <:actions> + <.button type="submit">Save + + + + [INSERT LVATTRDOCS] + """ + @doc type: :component + + 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, + include: ~w(autocomplete name rel action enctype method novalidate target multipart), + doc: "the arbitrary 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" + + def simple_form(assigns) do + ~LVN""" + <.form :let={f} for={@for} as={@as} {@rest}> + <%= render_slot(@inner_block, f) %> + + <%= for action <- @actions do %> + <%= render_slot(action, f) %> + <% end %> + + """ + end + + @doc """ + Renders a button. + + ## Examples + + <.button type="submit">Send! + <.button phx-click="go">Send! + """ + @doc type: :component + + attr :type, :string, default: nil + attr :rest, :global + + slot :inner_block, required: true + + def button(%{ type: "submit" } = assigns) do + ~LVN""" + + <%= render_slot(@inner_block) %> + + """ + end + + def button(assigns) do + ~LVN""" + + """ + end + + @doc ~S""" + Renders a table with generic styling. + + ## Examples + + <.table id="users" rows={@users}> + <:col :let={user} label="id"><%= user.id %> + <:col :let={user} label="username"><%= user.username %> + + """ + # @doc type: :component + + # 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_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 + # end + + # slot :action, doc: "the slot for showing user actions in the last table column" + + # def table(assigns) do + # ~LVN""" + # + # + # <%= col[:label] %> + # + # + # + # + # + # <%= render_slot(col, @row_item.(row)) %> + # + # + # <%= for action <- @action do %> + # <%= render_slot(action, @row_item.(row)) %> + # <% end %> + # + # + # + #
+ # """ + # end + + @doc """ + Renders a data list. + + ## Examples + + <.list> + <:item title="Title"><%= @post.title %> + <:item title="Views"><%= @post.views %> + + """ + attr :rest, :global + + slot :item, required: true do + attr :title, :string, required: true + end + + def list(assigns) do + ~LVN""" + + + <%= item.title %> + <%= render_slot(item) %> + + + """ + end + + @doc """ + Renders a system image from the Asset Manager in Xcode + or from SF Symbols. + + ## Examples + + <.icon name="xmark.diamond" /> + """ + @doc type: :component + + attr :name, :string, required: true + attr :rest, :global + + def icon(assigns) do + ~LVN""" + + """ + end + + @doc """ + Renders an image from a url + + Will render an [`AsyncImage`](https://developer.apple.com/documentation/jetpack/asyncimage) + You can customize the lifecycle states of with the slots. + """ + + attr :url, :string, required: true + attr :rest, :global + slot :empty, doc: """ + The empty state that will render before has successfully been downloaded. + + <.image url={~p"/assets/images/logo.png"}> + <:empty> + + + + + [See Jetpack docs](https://developer.apple.com/documentation/jetpack/asyncimagephase/success(_:)) + """ + slot :success, doc: """ + The success state that will render when the image has successfully been downloaded. + + <.image url={~p"/assets/images/logo.png"}> + <:success class="main-logo"/> + + + [See Jetpack docs](https://developer.apple.com/documentation/jetpack/asyncimagephase/success(_:)) + """ + do + attr :class, :string + attr :style, :string + end + slot :failure, doc: """ + The failure state that will render when the image fails to downloaded. + + <.image url={~p"/assets/images/logo.png"}> + <:failure class="image-fail"/> + + + [See Jetpack docs](https://developer.apple.com/documentation/jetpack/asyncimagephase/failure(_:)) + + """ + do + attr :class, :string + attr :style, :string + end + + def image(assigns) do + ~LVN""" + + + <%= render_slot(@empty) %> + + <.image_success slot={@success} /> + <.image_failure slot={@failure} /> + + """ + end + + defp image_success(%{ slot: [%{ inner_block: nil }] } = assigns) do + ~LVN""" + + """ + end + + defp image_success(assigns) do + ~LVN""" + + <%= render_slot(@slot) %> + + """ + end + + defp image_failure(%{ slot: [%{ inner_block: nil }] } = assigns) do + ~LVN""" + + """ + end + + defp image_failure(assigns) do + ~LVN""" + + <%= render_slot(@slot) %> + + """ + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # However the error messages in our forms and APIs are generated + # dynamically, so we need to translate them by calling Gettext + # with our gettext backend as first argument. Translations are + # available in the errors.po file (as we use the "errors" domain). + if count = opts[:count] do + Gettext.dngettext(FormDemoWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(FormDemoWeb.Gettext, "errors", msg, opts) + end + end + + @doc """ + Translates the errors for a field from a keyword list of errors. + """ + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end +end diff --git a/lib/form_demo_web/components/layouts_jetpack/app.jetpack.neex b/lib/form_demo_web/components/layouts_jetpack/app.jetpack.neex index 0543398..4eb5cf9 100644 --- a/lib/form_demo_web/components/layouts_jetpack/app.jetpack.neex +++ b/lib/form_demo_web/components/layouts_jetpack/app.jetpack.neex @@ -1 +1,2 @@ <%= @inner_content %> +<.flash_group flash={@flash} /> \ No newline at end of file diff --git a/lib/form_demo_web/components/layouts_jetpack/root.jetpack.neex b/lib/form_demo_web/components/layouts_jetpack/root.jetpack.neex index 1b0302b..57bc57b 100644 --- a/lib/form_demo_web/components/layouts_jetpack/root.jetpack.neex +++ b/lib/form_demo_web/components/layouts_jetpack/root.jetpack.neex @@ -1,3 +1,3 @@ <.csrf_token />