Skip to content

Commit 2ccfef6

Browse files
author
selenil
committed
feat: add accordion component
1 parent f707d5d commit 2ccfef6

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

lib/bloom/components/accordion.ex

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
defmodule BloomSiteWeb.Components.Accordion do
2+
use Phoenix.Component
3+
4+
@moduledoc """
5+
Accordion component.
6+
7+
## Examples
8+
9+
<.accordion id="my-accordion">
10+
<:header>
11+
Open me!
12+
</:header>
13+
<:panel>
14+
I'm open!
15+
</:panel>
16+
</.accordion>
17+
"""
18+
alias Phoenix.LiveView.JS
19+
20+
slot :header, required: true do
21+
attr(:color, :string)
22+
attr(:hover_color, :string)
23+
attr(:text_color, :string)
24+
attr(:icon_color, :string)
25+
end
26+
27+
slot :panel, required: true do
28+
attr(:color, :string)
29+
attr(:text_color, :string)
30+
end
31+
32+
attr(:id, :string, required: true, doc: "A unique id to identify the accordion")
33+
34+
attr(:open, :boolean,
35+
default: false,
36+
doc: "Whether the accordion should appear open or not."
37+
)
38+
39+
def accordion(assigns) do
40+
~H"""
41+
<div id={@id} class="relative overflow-hidden rounded-lg border border-gray-200">
42+
<button
43+
:for={header <- @header}
44+
id={"#{@id}-header"}
45+
aria-expanded={@open}
46+
aria-controls={"#{@id}-content"}
47+
class={[
48+
"flex w-full cursor-pointer items-center justify-between px-4 py-3 text-left transition-colors duration-200 ease-in-out focus:outline-none",
49+
"bg-#{Map.get(header, :color, "gray-100")}",
50+
"hover:bg-#{Map.get(header, :hover_color, "gray-200")}",
51+
"text-#{Map.get(header, :text_color, "gray-800")}"
52+
]}
53+
phx-click={toggle(@id)}
54+
>
55+
<span class="font-medium"><%= render_slot(header) %></span>
56+
<span
57+
id={"#{@id}-icon"}
58+
aria-hidden="true"
59+
class={[
60+
"#{if @open, do: "hero-chevron-up", else: "hero-chevron-down"}",
61+
"h-5 w-5 cursor-pointer",
62+
"text-#{Map.get(header, :icon_color, "gray-500")}"
63+
]}
64+
/>
65+
</button>
66+
<div
67+
:for={panel <- @panel}
68+
id={"#{@id}-content"}
69+
role="region"
70+
aria-hidden={!@open}
71+
aria-labelledby={"#{@id}-header"}
72+
class={[
73+
"#{if @open, do: "block", else: "hidden"}",
74+
"hero-#{if @open, do: "block", else: "hidden"}",
75+
"px-4 py-3",
76+
"bg-#{Map.get(panel, :color, "white")}",
77+
"text-#{Map.get(panel, :text_color, "gray-800")}"
78+
]}
79+
>
80+
<%= render_slot(panel) %>
81+
</div>
82+
</div>
83+
"""
84+
end
85+
86+
@doc """
87+
Toogles the accordion panel, opening or closing it.
88+
Also handles changing icons for each state.
89+
"""
90+
def toggle(js \\ %JS{}, id) do
91+
js
92+
|> JS.toggle(
93+
to: "##{id}-content",
94+
in:
95+
{"transition-all transform ease-out duration-300",
96+
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
97+
"opacity-100 translate-y-0 sm:scale-100"},
98+
out:
99+
{"transition-all transform ease-in duration-200",
100+
"opacity-100 translate-y-0 sm:scale-100",
101+
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
102+
)
103+
|> JS.toggle_class("rotate-180", to: "##{id}-icon")
104+
|> JS.toggle_attribute({"aria-expanded", "true", "false"}, to: "##{id}-header")
105+
end
106+
end

0 commit comments

Comments
 (0)