When building a component library, it is often useful to give users the ability to customize the underlying element or component to use.
For example, a dialog component may use a by default to trigger its modal, but could allow users to substitute the default for a
Other frontend frameworks enable this by allowing the user to pass an asChild
attribute that tells the component to delegate rendering of the component to the component passed in the inner block.
In React frameworks like shadcn/ui for example you can declare a trigger for a dropdown menu like this:
# React
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
Which would render a regular button by default:
# Default button trigger
<button data-item=“trigger”>Open</button>
Or you can pass an asChild
attribute which would delegate rendering to whatever child component you specify like so:
# React
<DropdownMenuTrigger asChild>
<Button>Open With Icon <Icon name=“plus” /></Button>
</DropdownMenuTrigger>
Which would render:
# Custom button with icon
<button data-item=“trigger”>Open With Icon <span>…svg icon...</span><</button>
One important thing to note is that the parent DropdownMenuTrigger
component is able to pass props down to the rendered child component so both the default button and the custom button component get the data-item=“trigger”
attribute applied.
Because I can’t pass attributes to the render_slot/2
function I haven’t been able to figure out a way to accomplish something similar using LiveView.
What I’d really like to have is something like a dynamic_slot/1
component that I could use like this:
attr :as_child, :boolean, default: false
attr :class, :string, default: ""
attr :rest, :global
slot :inner_block
def dropdown_menu_trigger(assigns) do
if assigns.as_child do
~H"""
<.dynamic_slot slot={@inner_block} data-part="trigger" class={@class} {@rest} />
"""
else
~H"""
<.button data-part="trigger" class={@class} {@rest}>
{render_slot(@inner_block)}
</.button>
"""
end
end
Where the dynamic_slot/1
component would merge its assigns with those of the @inner_block
passed in the slot
attribute and then render the @inner_block
.
I don’t know if something like this is already possible using the tools provided by LiveView but this would be really helpful for people like me who are trying to build out reusable component libraries for LiveView.