Corex.Carousel (Corex v0.1.0-rc.0)

View Source

Phoenix implementation of Zag.js Carousel.

Anatomy

GoalAPI
Image galleryitems={[Corex.Image.new("/images/a.jpg", alt: "A"), ...]} — renders <img> per slide
Custom slidesitems={@posts} + <:item :let={post}> — your markup per slide
Full structurecompound + carousel_* subcomponents

Items

Pass items for simple-mode slides. Image galleries use Corex.Image — see Corex.Image.new/2. Custom slides use any list plus the <:item> slot.

items<:item> slotResult
[%Corex.Image{}, ...]omitted<img src alt class> per slide
any list<:item :let={item}>your markup via render_slot/2
compound mode: set item_count and use carousel_item

Without <:item>, every entry must be %Corex.Image{}; other values raise at render time.

Slides may include links and other controls; off-view slides are marked inert on the client so they stay out of the tab order.

Images

<.carousel
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall"),
    Corex.Image.new("/images/sand.jpg", alt: "Sand"),
    Corex.Image.new("/images/star.jpg", alt: "Star"),
    Corex.Image.new("/images/winter.jpg", alt: "Winter")
  ]}
  class="carousel"
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>

Custom content

<.carousel items={@posts} class="carousel">
  <:item :let={post}>
    <article>
      <h3>{post.title}</h3>
      <p>{post.description}</p>
      <.navigate to="#" class="link">Read more</.navigate>
    </article>
  </:item>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>

Compound

<.carousel compound :let={ctx} item_count={2} class="carousel">
  <.carousel_root ctx={ctx}>
    <.carousel_item_group ctx={ctx}>
      <.carousel_item ctx={ctx} index={0}>First slide</.carousel_item>
      <.carousel_item ctx={ctx} index={1}>Second slide</.carousel_item>
    </.carousel_item_group>
    <.carousel_control ctx={ctx}>
      <.carousel_prev_trigger ctx={ctx}><.heroicon name="hero-arrow-left" /></.carousel_prev_trigger>
      <.carousel_indicator_group ctx={ctx}>
        <.carousel_indicator ctx={ctx} index={0} />
        <.carousel_indicator ctx={ctx} index={1} />
      </.carousel_indicator_group>
      <.carousel_next_trigger ctx={ctx}><.heroicon name="hero-arrow-right" /></.carousel_next_trigger>
    </.carousel_control>
  </.carousel_root>
</.carousel>

API

Requires a stable id on <.carousel>.

FunctionActionReturns
play/1Start or resume autoplay (client)%Phoenix.LiveView.JS{}
play/2Start or resume autoplay (server)socket
pause/1Pause autoplay (client)%Phoenix.LiveView.JS{}
pause/2Pause autoplay (server)socket
scroll_next/1Next page (client)%Phoenix.LiveView.JS{}
scroll_next/2Next page with instant (client)%Phoenix.LiveView.JS{}
scroll_next/3Next page (server)socket
scroll_prev/1Previous page (client)%Phoenix.LiveView.JS{}
scroll_prev/2Previous page with instant (client)%Phoenix.LiveView.JS{}
scroll_prev/3Previous page (server)socket

Events

Pick an event name and pass it to on_* on <.carousel>.

Server events

EventWhenPayload
on_page_change="carousel_page_changed"Active page changes%{"id" => id, "page" => page, "pageSnapPoint" => snap}

on_page_change

<.carousel
  class="carousel"
  on_page_change="carousel_page_changed"
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall"),
    Corex.Image.new("/images/sand.jpg", alt: "Sand"),
    Corex.Image.new("/images/star.jpg", alt: "Star"),
    Corex.Image.new("/images/winter.jpg", alt: "Winter")
  ]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
def handle_event("carousel_page_changed", %{"id" => _id, "page" => page}, socket) do
  {:noreply, assign(socket, :carousel_page, page)}
end

Client events

EventWhenevent.detail
on_page_change_client="carousel-page-changed"Active page changesid, page, pageSnapPoint

on_page_change_client

<.carousel
  id="carousel-events-client"
  class="carousel"
  on_page_change_client="carousel-page-changed"
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall"),
    Corex.Image.new("/images/sand.jpg", alt: "Sand"),
    Corex.Image.new("/images/star.jpg", alt: "Star"),
    Corex.Image.new("/images/winter.jpg", alt: "Winter")
  ]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
const el = document.getElementById("carousel-events-client");
el?.addEventListener("carousel-page-changed", (e) => console.log(e.detail));

Style

Target parts with data-scope and data-part, or use Corex Design: import tokens and carousel.css, then set class="carousel" on <.carousel>.

[data-scope="carousel"][data-part="root"] {}
[data-scope="carousel"][data-part="control"] {}
[data-scope="carousel"][data-part="item-group"] {}
[data-scope="carousel"][data-part="item"] {}
[data-scope="carousel"][data-part="prev-trigger"] {}
[data-scope="carousel"][data-part="next-trigger"] {}
[data-scope="carousel"][data-part="indicator-group"] {}
[data-scope="carousel"][data-part="indicator"] {}
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/carousel.css";

Stack modifiers on the host (class on <.carousel>).

Color

ModifierClasses
Defaultcarousel
Accentcarousel carousel--accent
Brandcarousel carousel--brand
Alertcarousel carousel--alert
Infocarousel carousel--info
Successcarousel carousel--success
<.carousel
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall"),
    Corex.Image.new("/images/sand.jpg", alt: "Sand"),
    Corex.Image.new("/images/star.jpg", alt: "Star"),
    Corex.Image.new("/images/winter.jpg", alt: "Winter")
  ]}
  class="carousel"
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
<.carousel
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall"),
    Corex.Image.new("/images/sand.jpg", alt: "Sand"),
    Corex.Image.new("/images/star.jpg", alt: "Star"),
    Corex.Image.new("/images/winter.jpg", alt: "Winter")
  ]}
  class="carousel carousel--accent"
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>

Size

Layout density for the root, control bar, prev/next triggers, autoplay control, and indicators.

ModifierClasses
SMcarousel carousel--sm
MDcarousel carousel--md
LGcarousel carousel--lg
XLcarousel carousel--xl
<.carousel
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall"),
    Corex.Image.new("/images/sand.jpg", alt: "Sand"),
    Corex.Image.new("/images/star.jpg", alt: "Star"),
    Corex.Image.new("/images/winter.jpg", alt: "Winter")
  ]}
  class="carousel carousel--sm"
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
<.carousel
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall"),
    Corex.Image.new("/images/sand.jpg", alt: "Sand"),
    Corex.Image.new("/images/star.jpg", alt: "Star"),
    Corex.Image.new("/images/winter.jpg", alt: "Winter")
  ]}
  class="carousel carousel--lg"
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>

Radius

Corner radius for the slide area and prev/next triggers.

ModifierClasses
Nonecarousel carousel--rounded-none
SMcarousel carousel--rounded-sm
MDcarousel carousel--rounded-md
LGcarousel carousel--rounded-lg
XLcarousel carousel--rounded-xl
Fullcarousel carousel--rounded-full
<.carousel
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall"),
    Corex.Image.new("/images/sand.jpg", alt: "Sand"),
    Corex.Image.new("/images/star.jpg", alt: "Star"),
    Corex.Image.new("/images/winter.jpg", alt: "Winter")
  ]}
  class="carousel carousel--rounded-md"
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>

Summary

API

Pause autoplay from the client. Dispatches corex:carousel:pause on the carousel root.

Pause autoplay from the server. push_event("carousel_pause", %{"id" => carousel_id}).

Start or resume autoplay from the client. Dispatches corex:carousel:play on the carousel root.

Start or resume autoplay from the server. push_event("carousel_play", %{"id" => carousel_id}).

Same as scroll_next/2 with instant: false.

Scroll to the next page from the client. Dispatches corex:carousel:scroll-next. Pass true as the second argument for an instant jump (no animation).

Scroll to the next page from the server. push_event("carousel_scroll_next", %{"id" => id, "instant" => boolean}).

Same as scroll_prev/2 with instant: false.

Scroll to the previous page from the client. Dispatches corex:carousel:scroll-prev. Pass true as the second argument for an instant jump (no animation).

Scroll to the previous page from the server. push_event("carousel_scroll_prev", %{"id" => id, "instant" => boolean}).

Components

carousel(assigns)

Attributes

  • id (:string)
  • aria_label (:string) - Defaults to nil.
  • items (:list) - List of %Corex.Image{} for image slides, or arbitrary data when <:item> is set. Omit in compound mode; use item_count when children do not pass through items. Defaults to nil.
  • item_count (:integer) - When set, overrides the slide count used for the hook and compound context (use in compound mode without items). Defaults to nil.
  • page (:integer) - Active page (1-based, same as pagination; first page is 1). Defaults to 1.
  • dir (:string) - Defaults to nil.Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Defaults to "horizontal". Must be one of "horizontal", or "vertical".
  • slides_per_page (:integer) - Defaults to 1.
  • slides_per_move (:any) - Number of slides to move per step, or "auto". Defaults to nil.
  • loop (:boolean) - Defaults to false.
  • autoplay (:boolean) - Defaults to false.
  • autoplay_delay (:integer) - Defaults to 4000.
  • allow_mouse_drag (:boolean) - Defaults to false.
  • spacing (:string) - Defaults to "0px".
  • padding (:string) - Defaults to nil.
  • in_view_threshold (:float) - Defaults to 0.6.
  • snap_type (:string) - Defaults to "mandatory". Must be one of "proximity", or "mandatory".
  • auto_size (:boolean) - Defaults to false.
  • on_page_change (:string) - Defaults to nil.
  • on_page_change_client (:string) - Defaults to nil.
  • compound (:boolean) - Enable compound mode with :let={ctx} and carousel_root, carousel_item_group, carousel_item, carousel_control, carousel_prev_trigger, carousel_next_trigger, carousel_indicator_group, carousel_indicator. Defaults to false.
  • Global attributes are accepted.

Slots

  • inner_block - Compound mode. Use compound and :let={ctx}.
  • prev_trigger - Accepts attributes:
    • class (:string)
  • next_trigger - Accepts attributes:
    • class (:string)
  • item - Custom markup for each slide. Use :let={item} to receive each entry from items. Required when items are not %Corex.Image{} structs. Accepts attributes:
    • class (:string)

Compounds

API

pause(carousel_id)

Pause autoplay from the client. Dispatches corex:carousel:pause on the carousel root.

<.action phx-click={Corex.Carousel.pause("my-carousel")}>Pause</.action>
<.carousel
  id="my-carousel"
  autoplay
  loop
  class="carousel"
  items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
document.getElementById("my-carousel")?.dispatchEvent(
  new CustomEvent("corex:carousel:pause", { bubbles: false, detail: {} })
);

pause(socket, carousel_id)

Pause autoplay from the server. push_event("carousel_pause", %{"id" => carousel_id}).

<.action phx-click="carousel_pause">Pause</.action>
<.carousel
  id="my-carousel"
  autoplay
  loop
  class="carousel"
  items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
def handle_event("carousel_pause", _params, socket) do
  {:noreply, Corex.Carousel.pause(socket, "my-carousel")}
end

play(carousel_id)

Start or resume autoplay from the client. Dispatches corex:carousel:play on the carousel root.

<.action phx-click={Corex.Carousel.play("my-carousel")}>Play</.action>
<.action phx-click={Corex.Carousel.pause("my-carousel")}>Pause</.action>
<.carousel
  id="my-carousel"
  autoplay
  loop
  class="carousel"
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall")
  ]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
document.getElementById("my-carousel")?.dispatchEvent(
  new CustomEvent("corex:carousel:play", { bubbles: false, detail: {} })
);

play(socket, carousel_id)

Start or resume autoplay from the server. push_event("carousel_play", %{"id" => carousel_id}).

<.action phx-click="carousel_play">Play</.action>
<.carousel
  id="my-carousel"
  autoplay
  loop
  class="carousel"
  items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
def handle_event("carousel_play", _params, socket) do
  {:noreply, Corex.Carousel.play(socket, "my-carousel")}
end

scroll_next(carousel_id)

Same as scroll_next/2 with instant: false.

scroll_next(carousel_id, instant)

Scroll to the next page from the client. Dispatches corex:carousel:scroll-next. Pass true as the second argument for an instant jump (no animation).

<.action phx-click={Corex.Carousel.scroll_next("my-carousel")}>Next</.action>
<.action phx-click={Corex.Carousel.scroll_next("my-carousel", true)}>Next (instant)</.action>
<.carousel
  id="my-carousel"
  loop
  class="carousel"
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall")
  ]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
document.getElementById("my-carousel")?.dispatchEvent(
  new CustomEvent("corex:carousel:scroll-next", { bubbles: false, detail: { instant: true } })
);

scroll_next(socket, carousel_id, instant)

Scroll to the next page from the server. push_event("carousel_scroll_next", %{"id" => id, "instant" => boolean}).

<.action phx-click="carousel_next">Next</.action>
<.carousel
  id="my-carousel"
  loop
  class="carousel"
  items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
def handle_event("carousel_next", _params, socket) do
  {:noreply, Corex.Carousel.scroll_next(socket, "my-carousel")}
end

scroll_prev(carousel_id)

Same as scroll_prev/2 with instant: false.

scroll_prev(carousel_id, instant)

Scroll to the previous page from the client. Dispatches corex:carousel:scroll-prev. Pass true as the second argument for an instant jump (no animation).

<.action phx-click={Corex.Carousel.scroll_prev("my-carousel")}>Prev</.action>
<.action phx-click={Corex.Carousel.scroll_prev("my-carousel", true)}>Prev (instant)</.action>
<.carousel
  id="my-carousel"
  loop
  class="carousel"
  items={[
    Corex.Image.new("/images/beach.jpg", alt: "Beach"),
    Corex.Image.new("/images/fall.jpg", alt: "Fall")
  ]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
document.getElementById("my-carousel")?.dispatchEvent(
  new CustomEvent("corex:carousel:scroll-prev", { bubbles: false, detail: {} })
);

scroll_prev(socket, carousel_id, instant)

Scroll to the previous page from the server. push_event("carousel_scroll_prev", %{"id" => id, "instant" => boolean}).

<.action phx-click="carousel_prev">Prev</.action>
<.carousel
  id="my-carousel"
  loop
  class="carousel"
  items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
  <:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
  <:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
def handle_event("carousel_prev", _params, socket) do
  {:noreply, Corex.Carousel.scroll_prev(socket, "my-carousel")}
end