Hello I’m working on Live Pane. It’s basically a pure javascript, dependency-free, port of paneforge / react-resizable-panels (for svelte and react resp.).
It’s still pretty much a work in progress and it’s my first time making a library!
It provides 3 hooks and 3 liveview components to create resizable panels:
The basic mechanism works and you could already use it. It’s just missing keyboard and JS ↔ LV events support (e.g. send an event from server to collapse a pane, and viceversa send an event from client when a pane is collapsed). And of course I need a ton of documentation as well.
Thanks a ton to live_toast as well cause I looked at that repo a lot to setup mine.
This is great, nice work and congrats on your first Elixir lib!
For some feedback: It doesn’t seem there is any value in having these as LiveComponents. They are all static and don’t encapsulate any behaviour. They would also be simpler, for example the resizer:
defmodule LivePane.Resizer do
@moduledoc """
TODO
"""
use Phoenix.LiveComponent
@impl true
def update(assigns, socket) do
direction = assigns[:direction] || "horizontal"
active = assigns[:active] || "pointer"
enabled = assigns[:disabled] || true
tab_index = assigns[:tab_index] || 0
socket =
socket
|> assign(assigns)
|> assign(:direction, direction)
|> assign(:active, active)
|> assign(:enabled, enabled)
|> assign(:tab_index, tab_index)
{:ok, socket}
end
@impl true
def render(assigns) do
~H"""
<div
id={@id}
role="separator"
phx-update="ignore"
phx-hook="live_pane_resizer"
data-pane-resizer=""
data-pane-resizer-id={@id}
data-pane-group-id={@group_id}
data-pane-direction={@direction}
data-pane-active={@active}
data-pane-enabled={@enabled}
tabindex={@tab_index}
class={@class}
>
{render_slot(@inner_block)}
</div>
"""
end
end
This could be:
defmodule LivePane.Resizer do
use Phoenix.Component
attr :direction, :string, values: ["horizontal", "vertical"], default: "horizontal"
attr :active, :string, default: "pointer" # (not sure what the other values are)
attr :enabled, :boolean, default: true
attr :tab_index, :integer, default: 0
slot :inner_block, required: true
def resizer(assigns) do
~H"""
<div
id={@id}
role="separator"
phx-update="ignore"
phx-hook="live_pane_resizer"
data-pane-resizer=""
data-pane-resizer-id={@id}
data-pane-group-id={@group_id}
data-pane-direction={@direction}
data-pane-active={@active}
data-pane-enabled={@enabled}
tabindex={@tab_index}
class={@class}
>
{render_slot(@inner_block)}
</div>
"""
end
end
It’s actually quite a bit simpler having them as function components.
I think he mentioned wanting to be able to send events back and forth and maybe that’s why it’s a LV, still work in progress. But either way, really cool library and interesting idea
I think it’s better to keep attribute names like tabindex the same rather than renaming them in the attr list (:tab_index). You can even use attr :rest, :global which will include tabindex and others by default.
And yes, I read so quickly while taking a little break from work and was more interested in looking at the code.
Makes sense if there are going to be events, though it still feels a bit heavy-handed if it’s simply for showing and hiding. You could generally have a generic component for this in your app that you could wrap any component with. But I’m not exactly thinking this through, mostly that I would like to use this library and would like a simple function component that I can resize
TBH I also don’t see that much value to send events from the panes back to liveview, but perhaps someone will want to react to a panel that gets collapsed
My personal use case was to use this as a draggable sidebar with a button to collapse/expand it, which will only use client-side events.
I will turn them into simple function components like you suggested.
If/when someone will need to have that extra functionality I can always add another set of components for LiveView so the lib can be used both with vanilla Phoenix and with LiveView (and lib’s name keeps making sense).
Now you can push_event("collapse/expand", %{pane_id: "the_pane_id"}) from your liveview to the pane to programmatically collapse/expand a collapsible pane (the collapsible example in the website has a button that does that).
I’ve changed the attribute name default_size to starting_size cause it was more clear to me.
Removed phx-update="ignore" from the components. I was making a “conditional panes” example and was losing my mind why my <%= if hide_pane do %> wasn’t doing anything.
Updated the docs site again!
Now I need to add a way to persist the sizes of the panes and the lib should more or less have feature parity with the others mentioned.
This time I’ve added tracking to the pane state change: collapsed, collapsing to expanding, expanded when programmatically changed. When dragging with mouse it’s just collapsed to expanded and viceversa.
The data attribute data-pane-state gets updated with these values so you can use it to add custom styling during these changes.
Now you can animate the collapsing/expanding with:
In addition there are 2 new attributes on panes: on_collapse and on_expand which accept JS commands so you can send events to the server with JS.push (or any other command).