Alternatives to nested slots?

I am trying to write a component for the daisyui timeline: Tailwind Timeline Component — Tailwind CSS Components ( version 4 update is here ). Here a screenshot to save you a click:

In every element there is a start, middle and end element (the year, the icon and the box showng above). What makes it somewhat tricky is that it uses a <hr/> tag to display lines. So if you have a 3 items the first item has an <hr/> tag at the end of its li element, the middle item has an <hr/> at the start and at the end of its li element and the last item has an <hr/> at the start of its li element. In this example, I omit the different classes for brevity:

<ul>
  <li>
    some text
    <hr /> <-- this one
  </li>
  <li>
    <hr /> <-- and here
    some text
    <hr /> <-- and here
  </li>
  <li>
    <hr /> <-- and here
    end text
  </li>
</ul>

In my ideal liveview world I imagine a syntax like this:

<.timeline>
  <:item>
    <:item.start>some text</:item.start>
    <:item.middle>some more text<:item.middle>
    <:item.end>some text at the end</:item.end>
  </:item>
</.timeline>

Now, afaik this is not possible. The only alternative I could think of is this:

<.timeline>
  <:item> <-- we need this nesting to be able to loop through the items and conditionally display the <hr> tags!
    <.timeline_item>
      <:item_start>some text</:item_start>
      <:item_middle>some more text<:item_middle>
      <:item_end>some text at the end</:item_end>
    </.timeline_item>
  </:item>
</.timeline>

I find this quite inelegant because the programmer needs to be aware of 2 components, and they need to nest them in a “useless” slot. Rendering the default slot with a for loop is not possible I think? At least, I couldn’t get it to work.

What is the best way to do this in liveview?

Is there are reason you can’t just use css to do this?

I would do two components still though:

<Timeline.root>
<Timeline.item :for={item <- @items} {item} />
</Timeline.root>

above for default style, below for custom

<Timeline.root>
  <Timeline.item :for={item <- @items} key={item.id} >
    <:start></:start>
    <:middle></:middle>
    <:end></:end>
  </Timeline.item>
</Timeline.root>

The only problem is not being to enforce that <Timeline.item> needs to be called within <Timeline.root>

you can do another thing is to use grid and place the items accordingly.

<Timeline.root>
   <:item>
      <Timeline.item_start />
      <Timeline.item_middle />
      <Timeline.item_end />
   <:item>
</Timeline.root>

Having nested slots is difficult though, i’d prefer to have the ability to at compile time enforce function components are called at a certain function.

I’ve shared this a while ago to show how you can use grid to achieve “slots”

If I understand you correctly you mean something like this?

<.timeline>
  <:item>
    <div class="timeline-start">1984</div>
    <div class="timeline-middle">some icon</div>
    <div class="timeline-end">some text at the bottom</div>
  </:item>
  <:item>
    <div class="timeline-start">2084</div>
    <div class="timeline-middle">some icon</div>
    <div class="timeline-end">second icon</div>
  </:item>
</.timeline>

I thought it might be more elegant to abstract the actual timeline classes away from the user. This way they also don’t need to worry about daisyui changes.

However, in hindsight this might be a bad idea?

I mean you can still abstract it away like i’ve mentioned:

<Timeline.root>
  <:item>
    <Timeline.item_start>1984</Timeline.item_start>
    <Timeline.item_middle>some icon</Timeline.item_middle>
    <Timeline.item_end>some text at the bottom</Timeline.item_end>
  </:item>
  <:item>
    <Timeline.item_start>2084</Timeline.item_start>
    <Timeline.item_middle>some icon</Timeline.item_middle>
    <Timeline.item_end>second icon</Timeline.item_end>
  </:item>
</Timeline.root>

Or something like

<Timeline.root>
  <Timeline.item>
    <:start>1984</:start>
    <:middle>some icon</:middle>
    <:end>some text at the bottom</:end>
  </Timeline.item>
  <Timeline.item>
    <:start>2084</:start>
    <:middle>some icon</:middle>
    <:end>second icon</:end>
  </Timeline.item>
</Timeline.root>

Both have cons and pros, but i wouldn’t expose the end user to specific classes, that should be abstracted away