|
| 1 | +defmodule Prima.Component do |
| 2 | + @moduledoc false |
| 3 | + import Phoenix.Component, only: [dynamic_tag: 1] |
| 4 | + |
| 5 | + @doc """ |
| 6 | + Renders a component using either a custom component function, a custom tag name, or a default HTML tag. |
| 7 | +
|
| 8 | + This helper function provides a consistent pattern for components that support |
| 9 | + the `as` attribute for custom rendering. |
| 10 | +
|
| 11 | + ## Parameters |
| 12 | +
|
| 13 | + * `assigns` - The component assigns map |
| 14 | + * `default_tag_attrs` - Map of attributes for the default tag, including `:tag_name`. |
| 15 | + This map is passed directly to `dynamic_tag/1` when rendering the default element. |
| 16 | + Attributes in this map are NOT passed to custom components. |
| 17 | +
|
| 18 | + ## Returns |
| 19 | +
|
| 20 | + Returns the rendered component by either: |
| 21 | + * Calling the custom component function with merged assigns (if `as` is a function) |
| 22 | + * Rendering a dynamic tag with the custom tag name (if `as` is a string) |
| 23 | + * Rendering a dynamic tag with default_tag_attrs (if `as` is not provided) |
| 24 | +
|
| 25 | + ## Behavior |
| 26 | +
|
| 27 | + 1. Pops the `:as` attribute from assigns |
| 28 | + 2. Pops and merges the `:rest` attributes into assigns |
| 29 | + 3. If `as` is a function, calls it with the merged assigns (without default_tag_attrs) |
| 30 | + 4. If `as` is a string, renders a dynamic tag with that tag name (without default_tag_attrs) |
| 31 | + 5. Otherwise, renders a dynamic tag with default_tag_attrs merged into assigns |
| 32 | +
|
| 33 | + ## Examples |
| 34 | +
|
| 35 | + # With default button element |
| 36 | + def my_button(assigns) do |
| 37 | + assigns = assign(assigns, %{"aria-label": "My Button"}) |
| 38 | + render_as(assigns, %{tag_name: "button", type: "button"}) |
| 39 | + end |
| 40 | +
|
| 41 | + # Can be used with as={&custom_component/1} |
| 42 | + # Custom component won't receive the type="button" attribute |
| 43 | + <.my_button as={&custom_component/1} /> |
| 44 | +
|
| 45 | + # Can be used with as="span" |
| 46 | + # Renders a span without type="button" |
| 47 | + <.my_button as="span" /> |
| 48 | +
|
| 49 | + """ |
| 50 | + def render_as(assigns, default_tag_attrs) when is_map(default_tag_attrs) do |
| 51 | + {as, assigns} = Map.pop(assigns, :as) |
| 52 | + {rest, assigns} = Map.pop(assigns, :rest, %{}) |
| 53 | + assigns = Map.merge(assigns, rest) |
| 54 | + |
| 55 | + cond do |
| 56 | + is_nil(as) -> |
| 57 | + dynamic_tag(Map.merge(assigns, default_tag_attrs)) |
| 58 | + |
| 59 | + is_function(as) -> |
| 60 | + as.(assigns) |
| 61 | + |
| 62 | + is_binary(as) -> |
| 63 | + dynamic_tag(Map.merge(assigns, %{tag_name: as})) |
| 64 | + |
| 65 | + true -> |
| 66 | + raise "Cannot render component `as` #{inspect(as)}. Expected a function or string" |
| 67 | + end |
| 68 | + end |
| 69 | +end |
0 commit comments