Skip to content

Commit e1d7348

Browse files
committed
refactor!: Improve accordion logic and styling
BREAKING CHANGE: Created and implemented an Alpine.js store for communicating father and child components in a reactive way, ensuring only one accordion is open at a time. PS.: For better legibility, Tailwind animation classes were replaced with a custom CSS class.
1 parent e3d1487 commit e1d7348

File tree

3 files changed

+88
-18
lines changed

3 files changed

+88
-18
lines changed

src/components/Accordion.astro

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,63 @@
11
---
22
import downIcon from "../../public/icons/downIcon.svg?raw";
3-
const { title, description, id } = Astro.props;
3+
const { title, description, id, last } = Astro.props;
44
---
5+
<!--
6+
Child component: Alpine.js logic
7+
8+
1. Each child observes the `activeId` state from the `faqs` store by using the reactive `visible` property.
9+
2. When clicking on a child component, the global `activeId` state in the store is updated:
10+
- If accordion is open (in child: `visible` === `true`), it sets `activeId` to `null` and closes.
11+
- If accordion is closed (in child: `visible` === `false`), it replaces the `activeId` value with its own `id` value and opens.
12+
3. When `activeId` changes, the child's classes related to its visibility are automatically updated.
13+
14+
This way, each child component can respond reactively to the `activeId` global state.
15+
-->
516
<div
6-
x-data="{
7-
visible: false,
8-
toggle() {
9-
this.visible = !this.visible
10-
}
11-
}"
17+
x-data=`{
18+
get visible() {
19+
return Alpine.store && Alpine.store('faqs')?.isVisible(${id});
20+
},
21+
22+
toggle(id){
23+
if ($store.faqs.activeId === id) {
24+
$store.faqs.setActiveId(null);
25+
} else {
26+
$store.faqs.setActiveId(id);
27+
};
28+
29+
}
30+
}`
31+
1232
class:list={[
13-
"bg-white py-6 transition-all duration-300 ease-in-out overflow-hidden",
14-
{ "border-b-0": id === null },
15-
{ "border-b-2": id },
33+
"bg-white py-6 accordion-transition overflow-hidden",
34+
{"border-b-0": last === id},
35+
{"border-b-2": last != id}
1636
]}
1737
>
1838
<div
19-
@click="toggle()"
39+
@click=`toggle(${id})`
2040
class="flex flex-row items-center text-lg font-semibold cursor-pointer"
21-
>
41+
:class="visible && 'text-blue-500'"
42+
>
2243
<div class="w-[95%] px-4">
2344
{title}
2445
</div>
2546
<div
2647
x-transition
2748
class="w-fit"
28-
:class="visible ? 'transform rotate-180 transition-transform duration-400' : 'transform rotate-0 transition-transform duration-400'"
49+
:class="visible ? 'transform rotate-180 accordion-transition' : 'transform rotate-0 accordion-transition'"
2950
>
3051
<Fragment set:html={downIcon} />
3152
</div>
3253
</div>
3354

3455
<div
3556
x-transition
36-
class="transition-all duration-300 ease-in-out overflow-hidden"
57+
class="accordion-transition overflow-hidden"
3758
:class="visible ? 'max-h-72 opacity-100' : 'max-h-0 opacity-0'"
3859
>
39-
<p class="w-[90%] text-gray-600 mt-2 px-4 overflow-hidden">
60+
<p class="w-[90%] text-gray-600 text-base mt-2 px-4 overflow-hidden">
4061
{description}
4162
</p>
4263
</div>

src/components/Faq.astro

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,59 @@
22
import commonQuestions from "../assets/common-questions.json";
33
import Accordion from "./Accordion.astro";
44
---
5+
<!--
56
6-
<section class="py-16 bg-gray-50">
7+
Father component: Alpine.js logic
8+
9+
1. The global store `faqs` manages the `activeId` state, which represents the ID of the last accordion opened by the user.
10+
2. Each child component observes the `activeId` state using the store's `isVisible` function.
11+
3. When the user clicks a child component, the store's `activeId` is updated, and this change automatically propagates to all children.
12+
4. Every child checks if the new active ID matches its own, and opens or closes accordingly.
13+
14+
This ensures that only one accordion is open at a time.
15+
16+
-->
17+
<section class="py-16 bg-white">
718
<div class="container mx-auto px-4">
819
<h2 class="text-3xl font-bold text-center mb-12">Preguntas frecuentes</h2>
9-
<div class="container faqs-container mx-auto shadow-md p-6 pt-0">
20+
<div
21+
class="container faqs-container "
22+
>
1023
{
1124
commonQuestions.map((question, index) => (
1225
<Accordion
1326
{...question}
14-
id={index++ < commonQuestions.length - 1 ? index++ : null}
27+
id={++index}
28+
last={commonQuestions.length}
1529
/>
1630
))
1731
}
1832
</div>
1933
</div>
2034
</section>
35+
36+
<!-- Client side rendering -->
37+
<script>
38+
import Alpine from "alpinejs";
39+
40+
interface FaqStore {
41+
activeId: number | null;
42+
setActiveId(id: number | null): void;
43+
isVisible(id:number | null): boolean
44+
}
45+
46+
Alpine.store('faqs', {
47+
activeId: null,
48+
49+
setActiveId(id:number | null) {
50+
this.activeId = id;
51+
},
52+
53+
isVisible(id:number) {
54+
return this.activeId === id;
55+
},
56+
57+
} as FaqStore)
58+
59+
Alpine.start()
60+
</script>

src/styles/globals.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,13 @@
3636
svg {
3737
@apply text-sm;
3838
}
39+
40+
/* Components custom styles */
41+
.faqs-container {
42+
@apply mx-auto shadow-md p-6 pt-0 rounded-xl;
43+
}
44+
45+
.accordion-transition {
46+
transition: all 400ms ease-in-out;
47+
}
3948
}

0 commit comments

Comments
 (0)