@terrahq/side-panel

A lightweight JavaScript library for creating expandable side panel components with multiple visual variants.

Side Panel — hideTitleOnActive: true

Test 1

Lorem ipsum dolor sit amet consectetur.

Lorem ipsum dolor sit amet consectetur. Sed pulvinar odio velit fermentum etiam consectetur pretium fringilla metus.

Link inside panel 1
alt text

Side Panel — hideTitleOnActive: false

Test 1

Lorem ipsum dolor sit amet consectetur.

Lorem ipsum dolor sit amet consectetur. Sed pulvinar odio velit fermentum etiam consectetur pretium fringilla metus.

alt text

Side Panel — direction: horizontal

Brand strategy and positioning work across multiple touchpoints.

Side Panel — direction: vertical

Brand strategy and positioning work across multiple touchpoints.
<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,
    });
});