Accordion
A vertically stacked set of interactive headings that each reveal a section of content.
Usage
Example
PhlexUI is a UI component library for Ruby devs who want to build better, faster.
Yes, PhlexUI is pure Ruby and works great with Rails. It's a Ruby gem that you can install into your Rails app.
div(class: "w-full") do Accordion do AccordionItem do AccordionTrigger do p(class: "font-medium") { "What is PhlexUI?" } AccordionIcon() end AccordionContent do p(class: "text-sm pb-4") do "PhlexUI is a UI component library for Ruby devs who want to build better, faster." end end end end Accordion do AccordionItem do AccordionTrigger do p(class: "font-medium") { "Can I use it with Rils?" } AccordionIcon() end AccordionContent do p(class: "text-sm pb-4") do "Yes, PhlexUI is pure Ruby and works great with Rails. It's a Ruby gem that you can install into your Rails app." end end end end end
Installation
Using RubyUI CLI
Run the install command
rails g ruby_ui:component Accordion
Manual installation
1
Add RubyUI::Accordion
to app/components/ruby_ui/accordion.rb
# frozen_string_literal: true module RubyUI class Accordion < Base def view_template(&) div(**attrs, &) end private def default_attrs { class: "w-full" } end end end
2
Add RubyUI::AccordionContent
to app/components/ruby_ui/accordion/accordion_content.rb
# frozen_string_literal: true module RubyUI class AccordionContent < Base def view_template(&) div(**attrs, &) end private def default_attrs { data: { ruby_ui__accordion_target: "content" }, class: "overflow-y-hidden", style: "height: 0px;" } end end end
3
Add RubyUI::AccordionDefaultContent
to app/components/ruby_ui/accordion/accordion_default_content.rb
# frozen_string_literal: true module RubyUI class AccordionDefaultContent < Base def view_template(&) div(**attrs, &) end private def default_attrs { class: "pb-4 pt-0 text-sm" } end end end
4
Add RubyUI::AccordionDefaultTrigger
to app/components/ruby_ui/accordion/accordion_default_trigger.rb
# frozen_string_literal: true module RubyUI class AccordionDefaultTrigger < Base def view_template(&block) div(class: "flex items-center justify-between w-full") do p(&block) RubyUI.AccordionIcon end end def default_attrs { data: {action: "click->ruby-ui--accordion#toggle"}, class: "w-full flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline" } end end end
5
Add RubyUI::AccordionIcon
to app/components/ruby_ui/accordion/accordion_icon.rb
# frozen_string_literal: true module RubyUI class AccordionIcon < Base def view_template(&block) span(**attrs) do if block block.call else icon end end end def icon svg( xmlns: "http://www.w3.org/2000/svg", viewbox: "0 0 20 20", fill: "currentColor", class: "w-4 h-4" ) do |s| s.path( fill_rule: "evenodd", d: "M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z", clip_rule: "evenodd" ) end end def default_attrs { class: "opacity-50", data: {ruby_ui__accordion_target: "icon"} } end end end
6
Add RubyUI::AccordionItem
to app/components/ruby_ui/accordion/accordion_item.rb
# frozen_string_literal: true module RubyUI class AccordionItem < Base def initialize(open: false, rotate_icon: 180, **attrs) @open = open @rotate_icon = rotate_icon super(**attrs) end def view_template(&) div(**attrs, &) end private def default_attrs { data: { controller: "ruby-ui--accordion", ruby_ui__accordion_open_value: @open, ruby_ui__accordion_rotate_icon_value: @rotate_icon }, class: "border-b" } end end end
7
Add RubyUI::AccordionTrigger
to app/components/ruby_ui/accordion/accordion_trigger.rb
# frozen_string_literal: true module RubyUI class AccordionTrigger < Base def view_template(&) button(**attrs, &) end def default_attrs { type: "button", data: {action: "click->ruby-ui--accordion#toggle"}, class: "w-full flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline" } end end end
8
Add accordion_controller.js
to app/javascript/controllers/ruby_ui/accordion_controller.js
import { Controller } from "@hotwired/stimulus"; import { animate } from "motion"; // Connects to data-controller="ruby-ui--accordion" export default class extends Controller { static targets = ["icon", "content"]; static values = { open: { type: Boolean, default: false, }, animationDuration: { type: Number, default: 0.15, // Default animation duration (in seconds) }, animationEasing: { type: String, default: "ease-in-out", // Default animation easing }, rotateIcon: { type: Number, default: 180, // Default icon rotation (in degrees) }, }; connect() { // Set the initial state of the accordion let originalAnimationDuration = this.animationDurationValue; this.animationDurationValue = 0; this.openValue ? this.open() : this.close(); this.animationDurationValue = originalAnimationDuration; } // Toggle the 'open' value toggle() { this.openValue = !this.openValue; } // Handle changes in the 'open' value openValueChanged(isOpen, wasOpen) { if (isOpen) { this.open(); } else { this.close(); } } // Open the accordion content open() { if (this.hasContentTarget) { this.revealContent(); this.hasIconTarget && this.rotateIcon(); this.openValue = true; } } // Close the accordion content close() { if (this.hasContentTarget) { this.hideContent(); this.hasIconTarget && this.rotateIcon(); this.openValue = false; } } // Reveal the accordion content with animation revealContent() { const contentHeight = this.contentTarget.scrollHeight; animate( this.contentTarget, { height: `${contentHeight}px` }, { duration: this.animationDurationValue, easing: this.animationEasingValue, }, ); } // Hide the accordion content with animation hideContent() { animate( this.contentTarget, { height: 0 }, { duration: this.animationDurationValue, easing: this.animationEasingValue, }, ); } // Rotate the accordion icon 180deg using animate function rotateIcon() { animate(this.iconTarget, { rotate: `${this.openValue ? this.rotateIconValue : 0}deg`, }); } }
9
Update the Stimulus controllers manifest file
Importmap!
rake stimulus:manifest:update
10
Install motion
Javascript dependency
// with yarn yarn add motion // with npm npm install motion // with importmaps // Add to your config/importmap.rb pin "motion", to: "https://cdn.jsdelivr.net/npm/motion@10.18.0/+esm"
Components
Component | Built using | Source |
---|---|---|
Accordion | Phlex | |
AccordionContent | Phlex | |
AccordionDefaultContent | Phlex | |
AccordionDefaultTrigger | Phlex | |
AccordionIcon | Phlex | |
AccordionItem | Phlex | |
AccordionTrigger | Phlex | |
AccordionController | Stimulus JS |