Carousel
A carousel with motion and swipe built using Embla.
Usage
Example
1
2
3
4
5
Carousel(options: {loop:false}, class: "w-full max-w-xs") do CarouselContent do 5.times do |index| CarouselItem do div(class: "p-1") do Card do CardContent(class: "flex aspect-square items-center justify-center p-6") do span(class: "text-4xl font-semibold") { index + 1 } end end end end end end CarouselPrevious() CarouselNext() end
Copied!
Copy failed!
Sizes
1
2
3
4
5
Carousel(class: "w-full max-w-sm") do CarouselContent do 5.times do |index| CarouselItem(class: "md:basis-1/2 lg:basis-1/3") do div(class: "p-1") do Card do CardContent(class: "flex aspect-square items-center justify-center p-6") do span(class: "text-3xl font-semibold") { index + 1 } end end end end end end CarouselPrevious() CarouselNext() end
Copied!
Copy failed!
Spacing
1
2
3
4
5
Carousel(class: "w-full max-w-sm") do CarouselContent(class: "-ml-1") do 5.times do |index| CarouselItem(class: "pl-1 md:basis-1/2 lg:basis-1/3") do div(class: "p-1") do Card do CardContent(class: "flex aspect-square items-center justify-center p-6") do span(class: "text-2xl font-semibold") { index + 1 } end end end end end end CarouselPrevious() CarouselNext() end
Copied!
Copy failed!
Orientation
1
2
3
4
5
Carousel(orientation: :vertical, options: {align: "start"}, class: "w-full max-w-xs") do CarouselContent(class: "-mt-1 h-[200px]") do 5.times do |index| CarouselItem(class: "pt-1 md:basis-1/2") do div(class: "p-1") do Card do CardContent(class: "flex items-center justify-center p-6") do span(class: "text-3xl font-semibold") { index + 1 } end end end end end end CarouselPrevious() CarouselNext() end
Copied!
Copy failed!
Installation
Using RubyUI CLI
Run the install command
rails g ruby_ui:component Carousel
Copied!
Copy failed!
Manual installation
1
Add RubyUI::Carousel
to app/components/ruby_ui/carousel/carousel.rb
# frozen_string_literal: true module RubyUI class Carousel < Base def initialize(orientation: :horizontal, options: {}, **user_attrs) @orientation = orientation @options = options super(**user_attrs) end def view_template(&) div(**attrs, &) end private def default_attrs { class: ["relative group", orientation_classes], role: "region", aria_roledescription: "carousel", data: { controller: "ruby-ui--carousel", ruby_ui__carousel_options_value: default_options.merge(@options).to_json, action: %w[ keydown.right->ruby-ui--carousel#scrollNext:prevent keydown.left->ruby-ui--carousel#scrollPrev:prevent ] } } end def default_options { axis: (@orientation == :horizontal) ? "x" : "y" } end def orientation_classes (@orientation == :horizontal) ? "is-horizontal" : "is-vertical" end end end
Copied!
Copy failed!
2
Add RubyUI::CarouselContent
to app/components/ruby_ui/carousel/carousel_content.rb
# frozen_string_literal: true module RubyUI class CarouselContent < Base def view_template(&) div(class: "overflow-hidden", data: {ruby_ui__carousel_target: "viewport"}) do div(**attrs, &) end end private def default_attrs { class: [ "flex", "group-[.is-horizontal]:-ml-4", "group-[.is-vertical]:-mt-4 group-[.is-vertical]:flex-col" ] } end end end
Copied!
Copy failed!
3
Add RubyUI::CarouselItem
to app/components/ruby_ui/carousel/carousel_item.rb
# frozen_string_literal: true module RubyUI class CarouselItem < Base def view_template(&) div(**attrs, &) end private def default_attrs { role: "group", aria_roledescription: "slide", class: [ "min-w-0 shrink-0 grow-0 basis-full", "group-[.is-horizontal]:pl-4", "group-[.is-vertical]:pt-4" ] } end end end
Copied!
Copy failed!
4
Add RubyUI::CarouselNext
to app/components/ruby_ui/carousel/carousel_next.rb
# frozen_string_literal: true module RubyUI class CarouselNext < Base def view_template(&) Button(**attrs) do icon end end private def default_attrs { variant: :outline, icon: true, class: [ "absolute h-8 w-8 rounded-full", "group-[.is-horizontal]:-right-12 group-[.is-horizontal]:top-1/2 group-[.is-horizontal]:-translate-y-1/2", "group-[.is-vertical]:-bottom-12 group-[.is-vertical]:left-1/2 group-[.is-vertical]:-translate-x-1/2 group-[.is-vertical]:rotate-90" ], disabled: true, data: { action: "click->ruby-ui--carousel#scrollNext", ruby_ui__carousel_target: "nextButton" } } end def icon svg( width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round", xmlns: "http://www.w3.org/2000/svg", class: "w-4 h-4" ) do |s| s.path(d: "M5 12h14") s.path(d: "m12 5 7 7-7 7") end end end end
Copied!
Copy failed!
5
Add RubyUI::CarouselPrevious
to app/components/ruby_ui/carousel/carousel_previous.rb
# frozen_string_literal: true module RubyUI class CarouselPrevious < Base def view_template(&) Button(**attrs) do icon span(class: "sr-only") { "Next slide" } end end private def default_attrs { variant: :outline, icon: true, class: [ "absolute h-8 w-8 rounded-full", "group-[.is-horizontal]:-left-12 group-[.is-horizontal]:top-1/2 group-[.is-horizontal]:-translate-y-1/2", "group-[.is-vertical]:-top-12 group-[.is-vertical]:left-1/2 group-[.is-vertical]:-translate-x-1/2 group-[.is-vertical]:rotate-90" ], disabled: true, data: { action: "click->ruby-ui--carousel#scrollPrev", ruby_ui__carousel_target: "prevButton" } } end def icon svg( width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round", xmlns: "http://www.w3.org/2000/svg", class: "w-4 h-4" ) do |s| s.path(d: "m12 19-7-7 7-7") s.path(d: "M19 12H5") end end end end
Copied!
Copy failed!
6
Add carousel_controller.js
to app/javascript/controllers/ruby_ui/carousel_controller.js
import { Controller } from "@hotwired/stimulus"; import EmblaCarousel from 'embla-carousel' const DEFAULT_OPTIONS = { loop: true } export default class extends Controller { static values = { options: { type: Object, default: {}, } } static targets = ["viewport", "nextButton", "prevButton"] connect() { this.initCarousel(this.#mergedOptions) } disconnect() { this.destroyCarousel() } initCarousel(options, plugins = []) { this.carousel = EmblaCarousel(this.viewportTarget, options, plugins) this.carousel.on("init", this.#updateControls.bind(this)) this.carousel.on("reInit", this.#updateControls.bind(this)) this.carousel.on("select", this.#updateControls.bind(this)) } destroyCarousel() { this.carousel.destroy() } scrollNext() { this.carousel.scrollNext() } scrollPrev() { this.carousel.scrollPrev() } #updateControls() { this.#toggleButtonsDisabledState(this.nextButtonTargets, !this.carousel.canScrollNext()) this.#toggleButtonsDisabledState(this.prevButtonTargets, !this.carousel.canScrollPrev()) } #toggleButtonsDisabledState(buttons, isDisabled) { buttons.forEach((button) => button.disabled = isDisabled) } get #mergedOptions() { return { ...DEFAULT_OPTIONS, ...this.optionsValue } } }
Copied!
Copy failed!
7
Update the Stimulus controllers manifest file
Importmap!
You don't need to run this command if you are using Importmap
rake stimulus:manifest:update
Copied!
Copy failed!
8
Install embla-carousel
Javascript dependency
// with yarn yarn add embla-carousel // with npm npm install embla-carousel // with importmaps bin/importmap pin embla-carousel
Copied!
Copy failed!
Components
Component | Built using | Source |
---|---|---|
Carousel | Phlex | |
CarouselContent | Phlex | |
CarouselItem | Phlex | |
CarouselNext | Phlex | |
CarouselPrevious | Phlex | |
CarouselController | Stimulus JS |