I am using Vue with Nuxt 2 and having Problem in my Custom Accordion component.
i want to toggle one accordion at a time. But what actually happening is that if i click on first accordion Link, the body of first Link(accordion) is opening. And without closing the first one, if i open on second link(accordion), the second accordion body also opening. Which i want to avoid. If i wanna open other accordion, the opened ones should first close. So how do i achieve this in vue way, without using any library.
Accordion Toggling Process: I am using ACTIVE value to toggle the accordion by adding aria-expended = "true" on my anchor Tag whenever it get clicked. At the same time i am showing or hiding the accordion-item-body based on ACTIVE value.
My component Accordion:
<template>
<div id="accordion" ref="accordion" >
<slot />
</div>
</template>
<script lang="ts">
import Vue from "vue"
import { props } from "./props"
export default Vue.extend({
name: "Accordion",
props,
created() {
this.$nuxt.$off("eventName")
this.$nuxt.$on("eventName", ($event: any) => this.toggleActive($event))
},
methods: {
toggleActive(e: any) {
console.log(e)
},
},
})
</script>
My AccordionItem component:
<template>
<div role="tab">
<div >
<a
:aria-expanded="active"
@click.prevent=";(active = !active), $nuxt.$emit('eventName', active)"
>
{{ title }}
<span
>
expand_more
</span>
</a>
</div>
<transition name="accordion">
<div v-show="active" role="tabpanel">
<slot />
</div>
</transition>
</div>
</template>
<script lang="ts">
import Vue from "vue"
export default Vue.extend({
name: "AccordionItem",
props: {
title: {
type: String,
required: true,
},
id: {
type: String,
required: true,
},
},
data() {
return {
active: false,
}
},
})
</script>
Usage of Accordion and AccordionItem in Parent:
<Accordion :items="items">
<AccordionItem
v-for="item in items"
:id="item.id"
:key="item.id"
:title="item.title"
>
<div v-html="item.description"></div>
</AccordionItem>
</Accordion>
CodePudding user response:
Here is a simple yet working solution (I didn't implemented the styling of course, but the functionality is working fine).
For the wrapper
<template>
<div>
<accordion
v-for="(item, index) in list"
:key="item.id"
:item="item"
:active-index="currentlyActiveIndex"
:item-index="index"
@update:itemIndex="currentlyActiveIndex = $event"
></accordion>
</div>
</template>
<script>
export default {
name: 'AccordionList',
data() {
return {
list: [
{ id: 1, name: 'hello' },
{ id: 2, name: 'world' },
{ id: 3, name: 'swag' },
],
currentlyActiveIndex: null,
}
},
}
</script>
Accordion.vue
<template>
<div>
<button @click="updateIndex">get this index: {{ itemIndex }}</button>
<span v-show="activeIndex === itemIndex">{{ item.name }}</span>
</div>
</template>
<script>
export default {
name: 'Accordion',
props: {
item: {
type: Object,
default: () => {},
},
activeIndex: {
type: Number,
default: null,
},
itemIndex: {
type: Number,
default: null,
},
},
methods: {
updateIndex() {
console.log('index updated:', this.itemIndex)
this.$emit('update:itemIndex', this.itemIndex)
},
},
}
</script>
This could also be achieved with something like { id: 1, name: 'hello', isActive: false }
, a bit cleaner but both methods are valid here.
PS: I don't know how to use TS neither, but I'm sure you can figure this part yourself (not the blocker here I guess).