Corex.Collapsible (Corex v0.1.2)

View Source

Phoenix implementation of Zag.js Collapsible.

Anatomy

Minimal

<.collapsible class="collapsible">
  <:trigger>Toggle</:trigger>
  <:content>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  </:content>
</.collapsible>

With indicator

Optional :closed and :opened slots render after the trigger label. Use one chevron in :closed and default CSS rotates it when open.

<.collapsible class="collapsible">
  <:trigger>Toggle</:trigger>
  <:closed>
    <.heroicon name="hero-chevron-right" />
  </:closed>
  <:content>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  </:content>
</.collapsible>

Custom slots

Use :let={collapsible} on slots to read collapsible.open and collapsible.disabled.

<.collapsible class="collapsible">
  <:trigger :let={c}>
    {if c.open, do: "Collapse", else: "Expand"}
  </:trigger>
  <:closed>
    <.heroicon name="hero-chevron-down" />
  </:closed>
  <:opened>
    <.heroicon name="hero-chevron-up" />
  </:opened>
  <:content :let={_c}>
    Panel body with custom opened/closed adornments.
  </:content>
</.collapsible>

API

Requires a stable id on <.collapsible>.

FunctionActionReturns
set_open/2Set open state (client)%Phoenix.LiveView.JS{}
set_open/3Set open state (server)socket

set_open

<.action phx-click={Corex.Collapsible.set_open("collapsible-api", true)} class="button button--sm">
  Open
</.action>
<.collapsible id="collapsible-api" class="collapsible">
  <:trigger>Toggle</:trigger>
  <:closed>
    <.heroicon name="hero-chevron-right" />
  </:closed>
  <:content>Lorem ipsum dolor sit amet.</:content>
</.collapsible>
def handle_event("open_collapsible", _, socket) do
  {:noreply, Corex.Collapsible.set_open(socket, "collapsible-api", true)}
end

Events

Server events

EventWhenPayload
on_open_change="collapsible_open_changed"Open state changes%{"id" => id, "open" => open}

on_open_change

<.collapsible
  class="collapsible"
  on_open_change="collapsible_open_changed"
>
  <:trigger>Toggle</:trigger>
  <:closed>
    <.heroicon name="hero-chevron-right" />
  </:closed>
  <:content>Lorem ipsum dolor sit amet.</:content>
</.collapsible>
def handle_event("collapsible_open_changed", %{"id" => id, "open" => open}, socket) do
  {:noreply, assign(socket, :open, open)}
end

Client events

EventWhenevent.detail
on_open_change_client="collapsible-open-changed"Open state changesid, open, previousOpen

on_open_change_client

<.collapsible
  id="collapsible-events-client"
  class="collapsible"
  on_open_change_client="collapsible-open-changed"
>
  <:trigger>Toggle</:trigger>
  <:closed>
    <.heroicon name="hero-chevron-right" />
  </:closed>
  <:content>Lorem ipsum dolor sit amet.</:content>
</.collapsible>
document.getElementById("collapsible-events-client")?.addEventListener("collapsible-open-changed", (e) => {
  console.log(e.detail);
});

Patterns

Async

<.async_result :let={panel} assign={@collapsible}>
  <:loading>
    <.collapsible_skeleton class="collapsible" />
  </:loading>
  <.collapsible class="collapsible" open={panel.open}>
    <:trigger>Details</:trigger>
    <:closed>
      <.heroicon name="hero-chevron-right" />
    </:closed>
    <:content>{panel.body}</:content>
  </.collapsible>
</.async_result>
socket =
  assign_async(socket, :collapsible, fn ->
    {:ok, %{collapsible: %{open: false, body: "Loaded after async."}}}
  end)

Controlled

<.collapsible
  class="collapsible"
  controlled
  open={@open}
  on_open_change="patterns_collapsible_changed"
>
  <:trigger>Controlled</:trigger>
  <:closed>
    <.heroicon name="hero-chevron-right" />
  </:closed>
  <:content>LiveView owns open.</:content>
</.collapsible>
def handle_event("patterns_collapsible_changed", %{"open" => open}, socket) do
  {:noreply, assign(socket, :open, open)}
end

Style

Target parts with data-scope and data-part. Content exposes --height, --collapsed-height, and related CSS variables for height animations.

[data-scope="collapsible"][data-part="root"] {}
[data-scope="collapsible"][data-part="trigger"] {}
[data-scope="collapsible"][data-part="content"] {}
[data-scope="collapsible"][data-part="closed"] {}
[data-scope="collapsible"][data-part="opened"] {}
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/collapsible.css";

Color

ModifierClasses
Defaultcollapsible collapsible--md
Accentcollapsible collapsible--md collapsible--accent
Brandcollapsible collapsible--md collapsible--brand

Size

ModifierClasses
SMcollapsible collapsible--sm
MDcollapsible collapsible--md
LGcollapsible collapsible--lg

Summary

Components

Renders a collapsible component.

Renders a loading skeleton for the collapsible component.

API

Set expanded or collapsed state from a control (phx-click).

Set open state from handle_event.

Components

collapsible(assigns)

Renders a collapsible component.

Requires :trigger and :content slots. Optional :closed and :opened slots add visual surfaces after the trigger label (Connect data-part only, no Zag props). Use :let={collapsible} on slots to access collapsible.open and collapsible.disabled.

Attributes

  • id (:string) - The id of the collapsible, useful for API to identify the collapsible.
  • open (:boolean) - The initial open state or the controlled open state. Defaults to false.
  • controlled (:boolean) - Whether the collapsible is controlled. Only in LiveView, the on_open_change event is required. Defaults to false.
  • disabled (:boolean) - Whether the collapsible is disabled. Defaults to false.
  • dir (:string) - The direction of the collapsible. When nil, derived from document (html lang + config :rtl_locales). Defaults to nil. Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Layout orientation for CSS. Defaults to "vertical". Must be one of "horizontal", or "vertical".
  • on_open_change (:string) - The server event name when the open state changes. Defaults to nil.
  • on_open_change_client (:string) - The client event name when the open state changes. Defaults to nil.
  • Global attributes are accepted.

Slots

  • trigger (required) - Trigger button content. Use :let={collapsible} to access open and disabled state. Accepts attributes:
    • class (:string)
  • content (required) - Expandable content. Use :let={collapsible} to access open and disabled state. Accepts attributes:
    • class (:string)
  • closed - Optional surface after the trigger, visible when closed (or use with :opened to swap content by state). Accepts attributes:
    • class (:string)
  • opened - Optional surface after the trigger, visible when open (use with :closed to swap content by state). Accepts attributes:
    • class (:string)

collapsible_skeleton(assigns)

Renders a loading skeleton for the collapsible component.

Attributes

  • dir (:string) - Same as collapsible: logical direction for the skeleton root. Defaults to nil. Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Same as collapsible: layout orientation for CSS. Defaults to "vertical". Must be one of "horizontal", or "vertical".
  • Global attributes are accepted.

API

set_open(collapsible_id, open)

Set expanded or collapsed state from a control (phx-click).

<.action phx-click={Corex.Collapsible.set_open("my-collapsible", true)}>Expand</.action>
<.collapsible id="my-collapsible" class="collapsible collapsible--md">
  <:trigger :let={c}>{if c.open, do: "Hide", else: "Show"}</:trigger>
  <:content>Details.</:content>
</.collapsible>
document.getElementById("my-collapsible")?.dispatchEvent(
  new CustomEvent("corex:collapsible:set-open", {
    bubbles: false,
    detail: { open: true },
  })
);

set_open(socket, collapsible_id, open)

Set open state from handle_event.

<.action phx-click="expand_collapsible">Expand</.action>
<.collapsible id="my-collapsible" class="collapsible collapsible--md">
  <:trigger :let={c}>{if c.open, do: "Hide", else: "Show"}</:trigger>
  <:content>Details.</:content>
</.collapsible>
def handle_event("expand_collapsible", _, socket) do
  {:noreply, Corex.Collapsible.set_open(socket, "my-collapsible", true)}
end