A lightweight JavaScript library for creating expandable side panel components with multiple visual variants.
Test 1
Lorem ipsum dolor sit amet consectetur. Sed pulvinar odio velit fermentum etiam consectetur pretium fringilla metus.
Link inside panel 1
Test 1
Lorem ipsum dolor sit amet consectetur. Sed pulvinar odio velit fermentum etiam consectetur pretium fringilla metus.
<div class="c--side-panel-a"
data-sidepanel-root
data-hide-title="true"
role="tablist">
<div class="c--side-panel-a__item" data-sidepanel-item="panel-1">
<button class="c--side-panel-a__item__hd"
data-sidepanel-hd
id="tab-panel-1"
role="tab"
aria-controls="panel-panel-1"
aria-expanded="true"
aria-selected="true">
<span class="c--side-panel-a__item__hd__title">Title</span>
</button>
<div class="c--side-panel-a__item__bd"
data-sidepanel-bd
id="panel-panel-1"
role="tabpanel"
aria-labelledby="tab-panel-1"
aria-hidden="false">
<div class="c--side-panel-a__item__bd__wrapper" data-sidepanel-wrapper>
<!-- Content -->
</div>
</div>
</div>
<div class="c--side-panel-a__item" data-sidepanel-item="panel-2">
<button class="c--side-panel-a__item__hd"
data-sidepanel-hd
id="tab-panel-2"
role="tab"
aria-controls="panel-panel-2"
aria-expanded="false"
aria-selected="false">
<span class="c--side-panel-a__item__hd__title">Title</span>
</button>
<div class="c--side-panel-a__item__bd"
data-sidepanel-bd
id="panel-panel-2"
role="tabpanel"
aria-labelledby="tab-panel-2"
aria-hidden="true">
<div class="c--side-panel-a__item__bd__wrapper" data-sidepanel-wrapper>
<!-- Content -->
</div>
</div>
</div>
</div>
<!-- Optional: external controls -->
<button data-sidepanel-control="panel-1">Go to Panel 1</button>
<button data-sidepanel-control="panel-2">Go to Panel 2</button>
<!-- Data attributes:
data-hide-title="true|false"
data-direction="responsive|horizontal|vertical"
data-breakpoint="810"
data-duration="0.6"
data-ease="cubic-bezier(0.76, 0, 0.24, 1)"
data-initial-index="0"
data-scroll-to-active="100" (vertical only)
-->@use "sass:map";
.c--side-panel-a {
width: 100%;
overflow: hidden;
@media all and ($viewport-type: $tabletm) {
display: flex;
max-height: 80vh;
}
&__item {
overflow: hidden;
border: 1px solid map.get($color-options, i);
flex: 0 0 auto;
min-width: 0;
@media all and ($viewport-type: $tabletm) {
display: flex;
}
&__hd {
display: block;
width: 100%;
text-align: left;
white-space: nowrap;
@media all and ($viewport-type: $tabletm) {
width: auto;
writing-mode: vertical-lr;
transform: rotate(180deg);
}
&__title {
display: block;
padding: $measure*2;
}
}
&__bd {
overflow: hidden;
max-height: 100%;
scrollbar-gutter: stable;
&__wrapper {
width: 100%;
padding: $measure*3 $measure*2;
}
}
}
&[data-direction="vertical"] {
display: block;
.c--side-panel-a {
&__item {
display: block;
&__hd {
width: 100%;
writing-mode: horizontal-tb;
transform: none;
}
}
}
}
}import SidePanel from './core/SidePanel';
document.querySelectorAll('[data-sidepanel-root]').forEach((el) => {
new SidePanel({
element: el,
nameSpace: 'sidepanel',
initialIndex: parseInt(el.dataset.initialIndex) || 0,
mobileBreakpoint: parseInt(el.dataset.breakpoint) || 810,
direction: el.dataset.direction || 'responsive',
hideTitleOnActive: el.dataset.hideTitle !== 'false',
duration: parseFloat(el.dataset.duration) || 0.6,
ease: el.dataset.ease || 'cubic-bezier(0.76, 0, 0.24, 1)',
scrollToActive: el.dataset.scrollToActive != null
? parseFloat(el.dataset.scrollToActive)
: null,
});
});