BAccordion is a simple Vue component that created an expanding and collapsing block. It uses the vue transition expand and can be paired with the Sass mixin accordion()
.
BpAccordion
@mixin accordion()
None.
<bp-accordion class="accordion" :id="'content1'">
<template #heading>
<h3 class="accordion__heading">Content 1</h3>
</template>
<p>
Amet consectetur dignissimos at sequi officiis? Debitis accusamus veritatis voluptatem quas quis? Optio velit maxime ipsam temporibus ratione, temporibus. Fugiat eligendi officiis <a href="#">impedit</a> voluptate recusandae. Veniam expedita velit aut ad.
</p>
</bp-accordion>
<bp-accordion class="accordion" :id="'content2'">
<template #heading>
<h3 class="accordion__heading">Content 2</h3>
</template>
<p>
Elit laboriosam ipsum dolorem earum quisquam consequuntur? Voluptatibus qui sed!
</p>
</bp-accordion>
<bp-accordion class="accordion">
<template #heading>
<h3 class="accordion__heading">Content 3</h3>
</template>
<p>
Ipsum voluptatem provident sit ab odio magni, repudiandae veritatis. Beatae nisi reprehenderit earum voluptates fugit nam. Explicabo laboriosam corporis sed est iusto veniam error. Sequi corrupti iusto sed itaque repudiandae saepe voluptas ipsa Ut et ab vitae temporibus repellat Cumque omnis autem culpa accusantium voluptatibus est? Molestiae voluptatem eaque vero?
</p>
</bp-accordion>
<bp-accordion class="accordion" :id="'content4'">
<template #heading>
<h3 class="accordion__heading">Content 4</h3>
</template>
<p>
Dolor reprehenderit ut fugiat necessitatibus debitis veniam in Tenetur deleniti suscipit dignissimos corporis expedita Vitae eum quia quia in modi adipisci voluptate nihil Ullam delectus assumenda consequatur aperiam nam explicabo!
</p>
</bp-accordion>
<bp-accordion class="accordion" :id="'content5'">
<template #heading>
<h3 class="accordion__heading">Content 5</h3>
</template>
<p>
Dolor quibusdam beatae sint provident nihil. Dolorem minus consectetur cumque voluptas fuga Soluta eaque quos aspernatur aliquam rerum. Exercitationem illum vel quod nemo fugiat totam. Optio et ducimus consequatur at
</p>
</bp-accordion>
<template>
<div :class="block">
<button
:id="`${id}-control`"
:class="`${block}__${headerElement}`"
:aria-expanded="`${isOpen}`"
:aria-controls="id"
@click="open"
>
<slot name="heading" />
<slot name="icon">
<svg
:class="[`${block}__${iconElement}`, { '-open': isOpen }]"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
>
<polyline points="6 9 12 15 18 9" />
</svg>
</slot>
</button>
<Transition
name="accordion__transition"
@enter="startTransition"
@leave="endTransition"
>
<div
v-if="isOpen"
:id="id"
ref="contentWrapper"
:class="`${block}__${contentWrapperElement}`"
>
<div
ref="content"
:class="`${block}__${contentElement}`"
:aria-labelledby="`${id}-control`"
>
<slot />
</div>
</div>
</Transition>
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
defineProps({
block: { type: String, default: 'accordion' },
headerElement: { type: String, default: 'header' },
iconElement: { type: String, default: 'icon' },
contentElement: { type: String, default: 'content' },
contentWrapperElement: { type: String, default: 'contentWrapper' },
id: { type: String, default: () => Math.random().toString(36).substr(2) },
})
const isOpen = ref(false)
const content = ref(null)
const contentWrapper = ref(null)
const open = () => {
isOpen.value = !isOpen.value
}
const startTransition = async (el) => {
await nextTick()
el.style.height = `${content.value.scrollHeight}px`
}
const endTransition = (el) => {
el.removeAttribute('style')
}
addEventListener('resize', () => {
if (!content.value) {
return
}
const newHeight = `${content.value.scrollHeight}px`
requestAnimationFrame(() => {
contentWrapper.value.style.height = newHeight
})
})
</script>
@use "/resources/styles/config";
@mixin accordion() {
--accordion-color-hover: var(--color-hover, #{config.$primary-hover});
$b: &;
box-shadow: config.$med-shadow;
margin-bottom: 1.5rem;
display: block;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
text-align: left;
padding: 1rem;
transition: background-color var(--duration-fast) config.$fade-easing;
width: 100%;
&:hover {
background-color: var(--accordion-color-hover);
}
}
&__heading {
margin-bottom: 0;
}
&__content {
border-top: 1px solid config.$gray-100;
margin: 0 1rem;
padding: .5rem 0;
}
&__contentWrapper {
overflow: hidden;
height: 0;
}
&__icon {
flex-shrink: 0;
margin-left: 1rem;
transition: transform var(--duration-fast) config.$ease-out-quint;
&.-open {
transform: rotate(-180deg);
}
}
&__transition {
&-enter-from,
&-enter-active,
&-leave-active,
&-leave-to {
transition: height config.$slow config.$entrance-easing;
}
}
}
.accordion {
@include accordion;
}