<div class="filter js-filter">
<ul class="filter__list">
<li class="filter__list-item">
<button class="filter-item js-filter-item is-selected" type="button" data-key="all">
<span class="filter-item__inner">
<span class="filter-item__text">Kõik</span>
</span>
</button>
</li>
<li class="filter__list-item">
<button class="filter-item js-filter-item" type="button" data-key="awaiting">
<span class="filter-item__inner">
<span class="filter-item__bullet">
<span class="filter-item__count">2</span>
</span>
<span class="filter-item__text">Kinnituse ootel</span>
</span>
</button>
</li>
<li class="filter__list-item">
<button class="filter-item js-filter-item" type="button" data-key="in-progress">
<span class="filter-item__inner">
<span class="filter-item__bullet">
<span class="filter-item__count">3</span>
</span>
<span class="filter-item__text">Koostamisel</span>
</span>
</button>
</li>
<li class="filter__list-item">
<button class="filter-item js-filter-item" type="button" data-key="confirmed">
<span class="filter-item__inner">
<span class="filter-item__bullet">
<span class="filter-item__count">5</span>
</span>
<span class="filter-item__text">Kinnitatud</span>
</span>
</button>
</li>
</ul>
<button class="filter__button filter__button--left is-hidden" type="button" aria-label="Scroll left">
<svg class="icon filter__button-icon" focusable="false">
<use href="../../inc/svg/global.4609ec92109fc41e7ad4764ef897ea8e.svg#chevron-left"></use>
</svg>
</button>
<button class="filter__button filter__button--right is-hidden" type="button" aria-label="Scroll right">
<svg class="icon filter__button-icon" focusable="false">
<use href="../../inc/svg/global.4609ec92109fc41e7ad4764ef897ea8e.svg#chevron-right"></use>
</svg>
</button>
</div>
{% set BEM -%}
filter js-filter
{%- if modifier %} {{ modifier }}{% endif %}
{%- if class %} {{ class }}{% endif %}
{% endset %}
<div class="{{ BEM }}">
<ul class="filter__list">
{% for item in data.items %}
<li class="filter__list-item">
{% include '@filter-item' with { data: item, class: 'js-filter-item', modifier: '' } %}
</li>
{% endfor %}
</ul>
<button class="filter__button filter__button--left is-hidden" type="button" aria-label="Scroll left">
{% include '@icon' with { class: 'filter__button-icon', modifier: '', name: 'chevron-left' } %}
</button>
<button class="filter__button filter__button--right is-hidden" type="button" aria-label="Scroll right">
{% include '@icon' with { class: 'filter__button-icon', modifier: '', name: 'chevron-right' } %}
</button>
</div>
{
"language": "en-US",
"data": {
"items": [
{
"key": "all",
"isSelected": true,
"text": "Kõik"
},
{
"key": "awaiting",
"text": "Kinnituse ootel",
"count": 2
},
{
"key": "in-progress",
"text": "Koostamisel",
"count": 3
},
{
"key": "confirmed",
"text": "Kinnitatud",
"count": 5
}
]
}
}
.filter {
position: relative;
padding: 12px $container-padding;
@include bp(md-min) {
padding: 0;
}
}
.filter__list {
display: flex;
gap: 8px;
overflow: auto hidden;
-ms-overflow-style: scroll;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.filter__list-item {
flex: 0 0 auto;
}
.filter__button {
position: absolute;
top: 50%;
width: 48px;
height: 48px;
padding: 0;
border: none;
outline: none;
-webkit-appearance: none;
cursor: pointer;
transform: translateY(-44%);
transition: opacity $transition-duration-xs $transition-easing,
visibility $transition-duration-xs $transition-easing;
&.is-hidden {
opacity: 0;
visibility: hidden;
}
}
.filter__button--left {
left: 0;
background: linear-gradient(270deg, rgba(248, 249, 250, 0) 0, #f8f9fa 76.86%); /* stylelint-disable-line plugin/no-unsupported-browser-features */
}
.filter__button--right {
right: 0;
background: linear-gradient(90deg, rgba(248, 249, 250, 0) 0, #f8f9fa 76.86%); /* stylelint-disable-line plugin/no-unsupported-browser-features */
}
.filter__button-icon {
position: absolute;
top: 50%;
left: 50%;
font-size: 24px;
color: $L-text;
transform: translate(-50%, -50%);
}
import {debounce} from 'throttle-debounce';
import Component from '../../component/component';
import './filter.scss';
export interface IFilterSettings {
itemSelector?: string;
listSelector?: string;
navBtnLeftSelector?: string;
navBtnRightSelector?: string;
scrollSpeed?: number,
scrollStep?: number;
selectedClass?: string;
}
export const profileSettings: IFilterSettings = {
itemSelector: '.js-filter-item',
listSelector: '.filter__list',
navBtnLeftSelector: '.filter__button--left',
navBtnRightSelector: '.filter__button--right',
scrollSpeed: 50,
scrollStep: 10,
selectedClass: 'is-selected',
};
export default class Filter extends Component {
static initSelector: string = '.js-filter';
readonly debounceUpdate: debounce<() => void>;
items: JQuery<HTMLElement>;
list: JQuery<HTMLElement>;
navBtnLeft: JQuery<HTMLElement>;
navBtnRight: JQuery<HTMLElement>;
scrollAmount: number;
settings: IFilterSettings;
constructor(target: HTMLElement) {
super(target);
this.settings = profileSettings;
this.scrollAmount = 0;
this.items = this.element.find(this.settings.itemSelector);
this.list = this.element.find(this.settings.listSelector);
this.navBtnLeft = this.element.find(this.settings.navBtnLeftSelector);
this.navBtnRight = this.element.find(this.settings.navBtnRightSelector);
this.items.on('click', this.onItemClick.bind(this));
this.navBtnLeft.on('click', this.scroll.bind(this, 'left'));
this.navBtnRight.on('click', this.scroll.bind(this, 'right'));
this.checkForOverflow();
this.debounceUpdate = debounce(100, this.checkForOverflow.bind(this));
$(window).on('resize', this.debounceUpdate);
this.list.on('scroll', this.checkForOverflow.bind(this));
}
checkForOverflow(): void {
const frameWidth: number = this.list.width(); // width of div showed on display
const scrollElmtWidth: number = this.list[0].scrollWidth; // width of div including hidden part
const maxDistance: number = Math.floor(scrollElmtWidth - frameWidth);
const scrollLeft: number = this.list.scrollLeft();
if (maxDistance <= 0) {
// Hide both buttons if no scroll available
this.navBtnRight.addClass('is-hidden');
this.navBtnLeft.addClass('is-hidden');
} else {
// Show both buttons if there is scroll available
this.navBtnRight.removeClass('is-hidden');
this.navBtnLeft.removeClass('is-hidden');
// Hide the appropriate button(s) if the scroll has reached an end
if (scrollLeft >= maxDistance) {
this.navBtnRight.addClass('is-hidden');
}
if (scrollLeft <= 0) {
this.navBtnLeft.addClass('is-hidden');
}
}
}
onItemClick(event: JQuery.ClickEvent): void {
const target: JQuery = $(event.currentTarget);
const targetKey: string = target.data('key');
const isSelected: boolean = target.hasClass(this.settings.selectedClass);
if (!isSelected) {
target.addClass(this.settings.selectedClass);
this.items.each((_index: number, item: HTMLElement) => {
const $item: JQuery = $(item);
const itemKey: string = $item.data('key');
if (itemKey !== targetKey) {
$item.removeClass(this.settings.selectedClass);
}
});
}
}
scroll(direction: 'right' | 'left'): void {
const step: number = this.settings.scrollStep;
let scroll: number = 0;
const slideTimer: number = window.setInterval(() => {
if (direction === 'left') {
this.list.scrollLeft(this.list.scrollLeft() - step);
this.scrollAmount = this.list.scrollLeft();
} else {
this.list.scrollLeft(this.list.scrollLeft() + step);
this.scrollAmount = this.list.scrollLeft();
}
this.checkForOverflow();
const maxDistance: number = Math.floor(this.list[0].scrollWidth - this.list.width());
if (this.list.scrollLeft() === 0) {
this.navBtnLeft.addClass('is-hidden');
} else {
this.navBtnLeft.removeClass('is-hidden');
}
if (this.list.scrollLeft() >= maxDistance) {
this.navBtnRight.addClass('is-hidden');
} else {
this.navBtnRight.removeClass('is-hidden');
}
scroll += step;
if (scroll >= 100) {
window.clearInterval(slideTimer);
}
}, this.settings.scrollSpeed);
}
}