Skip to content

Commit

Permalink
Update accordion so that you can set a particular item to be open at …
Browse files Browse the repository at this point in the history
…render
  • Loading branch information
nhobes committed Oct 7, 2024
1 parent f651fe8 commit 478260d
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 87 deletions.
177 changes: 90 additions & 87 deletions lib/petal_components/accordion.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ defmodule PetalComponents.Accordion do
attr(:js_lib, :string,
default: PetalComponents.default_js_lib(),
values: ["alpine_js", "live_view_js"],
doc: "javascript library used for toggling"
doc: "JavaScript library used for toggling"
)

attr(:open_index, :integer, default: nil, doc: "Index of item to be open at render")
attr(:rest, :global)

slot :item, required: true, doc: "CSS class for parent container" do
Expand Down Expand Up @@ -48,21 +49,26 @@ defmodule PetalComponents.Accordion do
id={@container_id}
class={@class}
{@rest}
{js_attributes("container", @js_lib, @container_id, nil, nil)}
{js_attributes("container", @js_lib, @container_id, nil, nil, @open_index)}
>
<%= for {current_item, i} <- Enum.with_index(@item) do %>
<div {js_attributes("item", @js_lib, @container_id, i, length(@item))} data-i={i}>
<% is_open = i == @open_index %>
<div
{js_attributes("item", @js_lib, @container_id, i, length(@item), is_open)}
data-i={i}
data-open={if is_open, do: "true", else: "false"}
>
<h2 id={content_panel_header_id(@container_id, i)}>
<button
type="button"
{js_attributes("button", @js_lib, @container_id, i, length(@item))}
{js_attributes("button", @js_lib, @container_id, i, length(@item), is_open)}
class={[
"pc-accordion-item accordion-button",
if(i == 0, do: "pc-accordion-item--first"),
unless(i == length(@item) - 1, do: "pc-accordion-item--all-except-last"),
if(i == length(@item) - 1,
do:
"pc-accordion-item--last #{if @js_lib == "live_view_js", do: "pc-accordion-item--last--closed"}"
"pc-accordion-item--last #{if @js_lib == "live_view_js" and !is_open, do: "pc-accordion-item--last--closed"}"
)
]}
>
Expand All @@ -72,13 +78,13 @@ defmodule PetalComponents.Accordion do
<.icon
name="hero-chevron-down-solid"
class="pc-accordion-item__chevron"
{js_attributes("icon", @js_lib, @container_id, i, length(@item))}
class={["pc-accordion-item__chevron", if(is_open, do: "rotate-180")]}
{js_attributes("icon", @js_lib, @container_id, i, length(@item), is_open)}
/>
</button>
</h2>
<div
{js_attributes("content_container", @js_lib, @container_id, i, length(@item))}
{js_attributes("content_container", @js_lib, @container_id, i, length(@item), is_open)}
class="accordion-content-container"
>
<div class={[
Expand Down Expand Up @@ -130,93 +136,90 @@ defmodule PetalComponents.Accordion do
"""
end

defp js_attributes("container", "alpine_js", _container_id, _i, _) do
%{
"x-data": "{ active: null }"
}
end

defp js_attributes("item", "alpine_js", _container_id, i, _) do
%{
"x-data": "{
id: #{i},
get expanded() {
return this.active === this.id
},
set expanded(value) {
this.active = value ? this.id : null
},
}"
}
end

defp js_attributes("button", "alpine_js", container_id, i, l) when i == l - 1 do
%{
"x-on:click": "expanded = !expanded",
":class":
"expanded ? 'pc-accordion-item__content-container--highlight-accordion-button-on-expanded-js-attributes' : 'pc-accordion-item--last--closed'",
":aria-expanded": "expanded",
"aria-controls": content_panel_id(container_id, i)
}
end
defp js_attributes(type, js_lib, container_id, i, l, open) do
case {type, js_lib} do
{"container", "alpine_js"} ->
%{"x-data": "{ active: #{open || "null"} }"}

{"item", "alpine_js"} ->
%{
"x-data": "{
id: #{i},
get expanded() {
return this.active === this.id
},
set expanded(value) {
this.active = value ? this.id : null
},
}"
}

defp js_attributes("button", "alpine_js", container_id, i, _l) do
%{
"x-on:click": "expanded = !expanded",
":class":
"expanded ? 'pc-accordion-item__content-container--highlight-accordion-button-on-expanded-js-attributes' : ''",
":aria-expanded": "expanded",
"aria-controls": content_panel_id(container_id, i)
}
end
{"button", "alpine_js"} when i == l - 1 ->
%{
"x-on:click": "expanded = !expanded",
":class":
"expanded ? 'pc-accordion-item__content-container--highlight-accordion-button-on-expanded-js-attributes' : 'pc-accordion-item--last--closed'",
":aria-expanded": "expanded",
"aria-controls": content_panel_id(container_id, i)
}

defp js_attributes("content_container", "alpine_js", container_id, i, _) do
%{
id: content_panel_id(container_id, i),
role: "region",
"aria-labelledby": content_panel_header_id(container_id, i),
"x-show": "expanded",
"x-cloak": true,
"x-collapse": true
}
end
{"button", "alpine_js"} ->
%{
"x-on:click": "expanded = !expanded",
":class":
"expanded ? 'pc-accordion-item__content-container--highlight-accordion-button-on-expanded-js-attributes' : ''",
":aria-expanded": "expanded",
"aria-controls": content_panel_id(container_id, i)
}

defp js_attributes("icon", "alpine_js", _container_id, _, _) do
%{
":class": "{ 'rotate-180': expanded }"
}
end
{"content_container", "alpine_js"} ->
%{
id: content_panel_id(container_id, i),
role: "region",
"aria-labelledby": content_panel_header_id(container_id, i),
"x-show": "expanded",
"x-cloak": true,
"x-collapse": true
}

defp js_attributes("container", "live_view_js", _container_id, _i, _) do
%{}
end
{"icon", "alpine_js"} ->
%{
":class": "{ 'rotate-180': expanded }"
}

defp js_attributes("item", "live_view_js", _container_id, _i, _) do
%{}
end
{"container", "live_view_js"} ->
%{}

{"item", "live_view_js"} ->
%{}

{"button", "live_view_js"} ->
%{
"phx-click":
JS.dispatch("click_accordion",
to: "##{container_id} [data-i='#{i}']",
detail: %{container_id: container_id, index: i, length: l}
),
"aria-controls": content_panel_id(container_id, i),
"aria-expanded": "#{open}"
}

defp js_attributes("button", "live_view_js", container_id, i, l) do
%{
"phx-click":
JS.dispatch("click_accordion",
to: "##{container_id} [data-i='#{i}']",
detail: %{container_id: container_id, index: i, length: l}
),
"aria-controls": content_panel_id(container_id, i)
}
end
{"content_container", "live_view_js"} ->
%{
id: content_panel_id(container_id, i),
role: "region",
"aria-labelledby": content_panel_header_id(container_id, i),
style: if(open, do: "display: block;", else: "display: none;")
}

defp js_attributes("content_container", "live_view_js", container_id, i, _) do
%{
id: content_panel_id(container_id, i),
role: "region",
"aria-labelledby": content_panel_header_id(container_id, i),
style: "display: none;"
}
end
{"icon", "live_view_js"} ->
%{
class: if(open, do: "rotate-180", else: "")
}

defp js_attributes("icon", "live_view_js", _container_id, _, _) do
%{}
_ ->
%{}
end
end

defp content_panel_header_id(container_id, idx) do
Expand Down
57 changes: 57 additions & 0 deletions test/petal/accordion_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,61 @@ defmodule PetalComponents.AccordionTest do

assert Exception.message(exception) =~ "either :item or :entries"
end

test "accordion with open_index" do
assigns = %{container_id: "test_accordion"}

html =
rendered_to_string(~H"""
<.accordion container_id={@container_id} open_index={1}>
<:item heading="Accordion 1">
Content 1
</:item>
<:item heading="Accordion 2">
Content 2
</:item>
<:item heading="Accordion 3">
Content 3
</:item>
</.accordion>
""")

# Extract the HTML for each accordion item
items_html =
String.split(html, ~r{<div[^>]+data-i="}, trim: true)
|> Enum.filter(fn x ->
String.starts_with?(x, "0") or String.starts_with?(x, "1") or String.starts_with?(x, "2")
end)
|> Enum.map(fn x -> "<div data-i=\"" <> x end)

for item_html <- items_html do
# Extract the index from data-i attribute
[_, idx_str | _] = Regex.run(~r{data-i="(\d+)"}, item_html)
idx = String.to_integer(idx_str)

if idx == 1 do
# For the open item, assert that it has 'rotate-180' in the chevron class
assert Regex.match?(
~r{<span[^>]*class="[^"]*pc-accordion-item__chevron[^"]*rotate-180[^"]*"}s,
item_html
)

# Also assert data-open="true"
assert item_html =~ ~s{data-open="true"}
else
# For closed items, refute that they have 'rotate-180' in the chevron class
refute Regex.match?(
~r{<span[^>]*class="[^"]*pc-accordion-item__chevron[^"]*rotate-180[^"]*"}s,
item_html
)

# Also assert data-open="false"
assert item_html =~ ~s{data-open="false"}
end
end

# Additional assertions for the content panels
assert html =~ ~s{id="acc-content-panel-test_accordion-1"}
assert Regex.match?(~r{id="acc-content-panel-test_accordion-1".*?x-show="expanded"}s, html)
end
end

0 comments on commit 478260d

Please sign in to comment.