Skip to content

Commit

Permalink
Add RBUI Accordion (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacksonpires authored Aug 20, 2024
1 parent 644ce99 commit b96045e
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 34 deletions.
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ThemeToggleController from "./lib/rbui/theme_toggle/theme_toggle_controll
import TooltipController from "./lib/rbui/tooltip/tooltip_controller";
import SelectController from "./lib/rbui/select/select_controller";
import SelectItemController from "./lib/rbui/select/select_item_controller";
import AccordionController from "./lib/rbui/accordion/accordion_controller";

// Register all controllers
application.register("rbui--combobox", ComboboxController);
Expand All @@ -24,6 +25,7 @@ application.register("rbui--theme-toggle", ThemeToggleController);
application.register("rbui--tooltip", TooltipController);
application.register("rbui--select", SelectController);
application.register("rbui--select-item", SelectItemController);
application.register("rbui--accordion", AccordionController);

// Export all controllers so user of npm package can lazy load controllers
export { default as ComboboxController } from "./lib/rbui/combobox/combobox_controller";
Expand All @@ -33,6 +35,7 @@ export { default as ThemeToggleController } from "./lib/rbui/theme_toggle/theme_
export { default as TooltipController } from "./lib/rbui/tooltip/tooltip_controller";
export { default as SelectController } from "./lib/rbui/select/select_controller";
export { default as SelectItemController } from "./lib/rbui/select/select_item_controller";
export { default as AccordionController } from "./lib/rbui/accordion/accordion_controller";

// Export application
export { application };
1 change: 1 addition & 0 deletions lib/phlex_ui.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
loader.collapse("#{__dir__}/rbui/theme_toggle")
loader.collapse("#{__dir__}/rbui/tooltip")
loader.collapse("#{__dir__}/rbui/select")
loader.collapse("#{__dir__}/rbui/accordion")

loader.setup # ready!

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

module PhlexUI
module RBUI
class Accordion < Base
def view_template(&)
div(**attrs, &)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

module PhlexUI
module RBUI
class AccordionContent < Base
def view_template(&)
div(**attrs, &)
Expand All @@ -11,7 +11,7 @@ def view_template(&)
def default_attrs
{
data: {
accordion_target: "content"
rbui__accordion_target: "content"
},
class: "overflow-y-hidden",
style: "height: 0px;"
Expand Down
81 changes: 81 additions & 0 deletions lib/rbui/accordion/accordion_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Controller } from "@hotwired/stimulus"
import { animate } from "motion"

// Connects to data-controller="rbui--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` })
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

module PhlexUI
module RBUI
class AccordionDefaultContent < Base
def view_template(&)
div(**attrs, &)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# frozen_string_literal: true

module PhlexUI
module RBUI
class AccordionDefaultTrigger < Base
def view_template(&block)
div(class: "flex items-center justify-between w-full") do
p(&block)
PhlexUI.AccordionIcon
RBUI.AccordionIcon
end
end

def default_attrs
{
data: {action: "click->accordion#toggle"},
data: {action: "click->rbui--accordion#toggle"},
class: "w-full flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline"
}
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

module PhlexUI
module RBUI
class AccordionIcon < Base
def view_template(&block)
span(**attrs) do
Expand Down Expand Up @@ -31,7 +31,7 @@ def icon
def default_attrs
{
class: "opacity-50",
data: {accordion_target: "icon"}
data: {rbui__accordion_target: "icon"}
}
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

module PhlexUI
module RBUI
class AccordionItem < Base
def initialize(open: false, rotate_icon: 180, **attrs)
@open = open
Expand All @@ -17,9 +17,9 @@ def view_template(&)
def default_attrs
{
data: {
controller: "accordion",
accordion_open_value: @open,
accordion_rotate_icon_value: @rotate_icon
controller: "rbui--accordion",
rbui__accordion_open_value: @open,
rbui__accordion_rotate_icon_value: @rotate_icon
},
class: "border-b"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# frozen_string_literal: true

module PhlexUI
module RBUI
class AccordionTrigger < Base
def view_template(&)
button(**attrs, &)
end

def default_attrs
{
data: {action: "click->accordion#toggle"},
data: {action: "click->rbui--accordion#toggle"},
class: "w-full flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline"
}
end
Expand Down
38 changes: 19 additions & 19 deletions test/phlex_ui/accordion_test.rb → test/rbui/accordion_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@

require "test_helper"

class PhlexUI::AccordionTest < Minitest::Test
class RBUI::AccordionTest < Minitest::Test
include Phlex::Testing::ViewHelper

def test_render_with_default_items
output = phlex_context do
PhlexUI.Accordion do
PhlexUI.AccordionItem do
PhlexUI.AccordionDefaultTrigger { "Title" }
PhlexUI.AccordionDefaultContent { "Content" }
RBUI.Accordion do
RBUI.AccordionItem do
RBUI.AccordionDefaultTrigger { "Title" }
RBUI.AccordionDefaultContent { "Content" }
end
end
end

assert_match(/<div data-controller="accordion"/, output)
assert_match(/<div data-controller="rbui--accordion"/, output)
end

def test_render_with_all_items
output = phlex_context do
PhlexUI.Accordion do
PhlexUI.AccordionItem do
PhlexUI.AccordionTrigger do |trigger|
RBUI.Accordion do
RBUI.AccordionItem do
RBUI.AccordionTrigger do |trigger|
trigger.div do |div|
PhlexUI.AccordionIcon do |icon|
RBUI.AccordionIcon do |icon|
icon.svg(
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
Expand All @@ -40,19 +40,19 @@ def test_render_with_all_items
)
end
end
div.p { "What is PhlexUI?" }
div.p { "What is RBUI?" }
end
end

PhlexUI.AccordionContent do |content|
content.p { "PhlexUI is a UI component library for Ruby devs who want to build better, faster." }
RBUI.AccordionContent do |content|
content.p { "RBUI is a UI component library for Ruby devs who want to build better, faster." }
end
end

PhlexUI.AccordionItem do
PhlexUI.AccordionTrigger do |trigger|
RBUI.AccordionItem do
RBUI.AccordionTrigger do |trigger|
trigger.div do |div|
PhlexUI.AccordionIcon do |icon|
RBUI.AccordionIcon do |icon|
icon.svg(
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
Expand All @@ -72,15 +72,15 @@ def test_render_with_all_items
end
end

PhlexUI.AccordionContent do |content|
RBUI.AccordionContent do |content|
content.p do
"Yes, PhlexUI is pure Ruby and works great with Rails. It's a Ruby gem that you can install into your Rails app."
"Yes, RBUI 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

assert_match(/Yes, PhlexUI is pure Ruby and works great with Rails/, output)
assert_match(/Yes, RBUI is pure Ruby and works great with Rails/, output)
end
end

0 comments on commit b96045e

Please sign in to comment.