diff --git a/packages/live_ui/lib/live_ui/renderer.ex b/packages/live_ui/lib/live_ui/renderer.ex index 14f6072e..b3340c01 100644 --- a/packages/live_ui/lib/live_ui/renderer.ex +++ b/packages/live_ui/lib/live_ui/renderer.ex @@ -104,6 +104,38 @@ defmodule LiveUi.Renderer do <%= for child <- child_elements(@element, :default) do %> <.render element={child} event_target={@event_target} /> <% end %> +
+ <%= if get_in(@element.attributes, [:shell, :search_event]) do %> + + <% end %> + <%= if get_in(@element.attributes, [:shell, :settings_event]) do %> + + <% end %> + <%= if get_in(@element.attributes, [:shell, :user_avatar_url]) do %> + User profile + <% end %> +
""" end diff --git a/packages/live_ui/test/live_ui/renderer_test.exs b/packages/live_ui/test/live_ui/renderer_test.exs index b73fcf01..9e29050c 100644 --- a/packages/live_ui/test/live_ui/renderer_test.exs +++ b/packages/live_ui/test/live_ui/renderer_test.exs @@ -4,7 +4,7 @@ defmodule LiveUi.RendererTest do import Phoenix.LiveViewTest alias UnifiedIUR.{Container, Element, Forms, Interaction, Layout} - alias UnifiedIUR.Widgets.{Foundational, Input, Navigation} + alias UnifiedIUR.Widgets.{Components, Foundational, Input, Navigation} test "renderer maps foundational canonical widgets and layouts into native components" do element = @@ -251,4 +251,76 @@ defmodule LiveUi.RendererTest do assert html =~ ~s(name="element_id" value="profile-name") assert html =~ ~s(name="widget" value="text_input") end + + describe "top_strip trailing-region affordances" do + test "renders basic top_strip without trailing affordances" do + element = Components.top_strip([], brand: "Ariston", context: "Dashboard") + html = render_component(&LiveUi.Renderer.render/1, %{element: element}) + + assert html =~ ~s(data-live-ui-shell-position="top") + assert html =~ "live-ui-top-strip-brand" + assert html =~ "Ariston" + assert html =~ "live-ui-top-strip-trailing" + refute html =~ "live-ui-top-strip-search" + refute html =~ "live-ui-top-strip-settings" + refute html =~ "live-ui-top-strip-avatar" + end + + test "renders search button when search_event is set" do + element = Components.top_strip([], brand: "Ariston", search_event: "open_search") + html = render_component(&LiveUi.Renderer.render/1, %{element: element}) + + assert html =~ "live-ui-top-strip-search" + assert html =~ ~s(aria-label="Open search") + assert html =~ ~s(phx-click="open_search") + refute html =~ "live-ui-top-strip-settings" + refute html =~ "live-ui-top-strip-avatar" + end + + test "renders settings button when settings_event is set" do + element = Components.top_strip([], brand: "Ariston", settings_event: "open_settings") + html = render_component(&LiveUi.Renderer.render/1, %{element: element}) + + assert html =~ "live-ui-top-strip-settings" + assert html =~ ~s(aria-label="Open settings") + assert html =~ ~s(phx-click="open_settings") + refute html =~ "live-ui-top-strip-search" + refute html =~ "live-ui-top-strip-avatar" + end + + test "renders avatar img when user_avatar_url is set" do + element = + Components.top_strip([], + brand: "Ariston", + user_avatar_url: "https://example.com/avatar.png" + ) + + html = render_component(&LiveUi.Renderer.render/1, %{element: element}) + + assert html =~ "live-ui-top-strip-avatar" + assert html =~ ~s(src="https://example.com/avatar.png") + assert html =~ ~s(aria-label="User profile") + refute html =~ "live-ui-top-strip-search" + refute html =~ "live-ui-top-strip-settings" + end + + test "renders all three trailing affordances together" do + element = + Components.top_strip([], + brand: "Ariston", + search_event: "open_search", + settings_event: "open_settings", + user_avatar_url: "https://example.com/avatar.png" + ) + + html = render_component(&LiveUi.Renderer.render/1, %{element: element}) + + assert html =~ "live-ui-top-strip-search" + assert html =~ ~s(phx-click="open_search") + assert html =~ "live-ui-top-strip-settings" + assert html =~ ~s(phx-click="open_settings") + assert html =~ "live-ui-top-strip-avatar" + assert html =~ ~s(src="https://example.com/avatar.png") + end + end end diff --git a/packages/unified_iur/lib/unified_iur/widgets/components.ex b/packages/unified_iur/lib/unified_iur/widgets/components.ex index 983a4910..c6b6c195 100644 --- a/packages/unified_iur/lib/unified_iur/widgets/components.ex +++ b/packages/unified_iur/lib/unified_iur/widgets/components.ex @@ -521,13 +521,17 @@ defmodule UnifiedIUR.Widgets.Components do :top_strip, :layer_shell_and_callout, %{ - shell: %{ - position: :top, - brand: option(opts, :brand, ""), - context: option(opts, :context, ""), - theme: option(opts, :theme, :light), - pane_open?: option(opts, :pane_open?, false) - } + shell: + %{ + position: :top, + brand: option(opts, :brand, ""), + context: option(opts, :context, ""), + theme: option(opts, :theme, :light), + pane_open?: option(opts, :pane_open?, false) + } + |> maybe_put(:search_event, option(opts, :search_event)) + |> maybe_put(:settings_event, option(opts, :settings_event)) + |> maybe_put(:user_avatar_url, option(opts, :user_avatar_url)) }, Map.put(opts, :children, children) ) diff --git a/packages/unified_iur/test/unified_iur/widgets/components_test.exs b/packages/unified_iur/test/unified_iur/widgets/components_test.exs index fe650c6e..d291cebf 100644 --- a/packages/unified_iur/test/unified_iur/widgets/components_test.exs +++ b/packages/unified_iur/test/unified_iur/widgets/components_test.exs @@ -295,6 +295,56 @@ defmodule UnifiedIUR.Widgets.ComponentsTest do assert code.attributes.text_safety == %{content: :plain_text} end + test "top_strip stores trailing-region affordance attrs in shell map" do + strip_no_trailing = Components.top_strip([], brand: "Ariston", context: "Dashboard") + + assert strip_no_trailing.kind == :top_strip + assert strip_no_trailing.attributes.shell.brand == "Ariston" + assert strip_no_trailing.attributes.shell.context == "Dashboard" + refute Map.has_key?(strip_no_trailing.attributes.shell, :search_event) + refute Map.has_key?(strip_no_trailing.attributes.shell, :settings_event) + refute Map.has_key?(strip_no_trailing.attributes.shell, :user_avatar_url) + + strip_with_search = + Components.top_strip([], brand: "Ariston", search_event: "open_search") + + assert strip_with_search.attributes.shell.search_event == "open_search" + refute Map.has_key?(strip_with_search.attributes.shell, :settings_event) + refute Map.has_key?(strip_with_search.attributes.shell, :user_avatar_url) + + strip_with_settings = + Components.top_strip([], brand: "Ariston", settings_event: "open_settings") + + assert strip_with_settings.attributes.shell.settings_event == "open_settings" + + strip_with_avatar = + Components.top_strip([], + brand: "Ariston", + user_avatar_url: "https://example.com/avatar.png" + ) + + assert strip_with_avatar.attributes.shell.user_avatar_url == "https://example.com/avatar.png" + + strip_all_trailing = + Components.top_strip([], + brand: "Ariston", + search_event: "open_search", + settings_event: "open_settings", + user_avatar_url: "https://example.com/avatar.png" + ) + + assert strip_all_trailing.attributes.shell == %{ + position: :top, + brand: "Ariston", + context: "", + theme: :light, + pane_open?: false, + search_event: "open_search", + settings_event: "open_settings", + user_avatar_url: "https://example.com/avatar.png" + } + end + test "represents list repeat metadata and hydrated row children" do template = Components.artifact_row("Template", [], row_identity: :id) diff --git a/packages/unified_ui/lib/unified_ui/widget_components.ex b/packages/unified_ui/lib/unified_ui/widget_components.ex index 513bd4c0..38fc703e 100644 --- a/packages/unified_ui/lib/unified_ui/widget_components.ex +++ b/packages/unified_ui/lib/unified_ui/widget_components.ex @@ -171,7 +171,7 @@ defmodule UnifiedUi.WidgetComponents do kind: :top_strip, family: :layer_shell_and_callout, summary: - "Top shell strip with brand, context, theme controls, and mode navigation children.", + "Top shell strip with brand, context, theme controls, mode navigation children, and optional trailing-region affordances (search_event, settings_event, user_avatar_url).", aliases: [] }, %{