Skip to content

Commit

Permalink
IBX-2658: Introduce adaptive filters (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
tischsoic committed May 19, 2022
1 parent 75a6da6 commit e628576
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/bundle/Resources/encore/ibexa.js.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const layout = [
path.resolve(__dirname, '../public/js/scripts/core/toggle.button.js'),
path.resolve(__dirname, '../public/js/scripts/core/slug.value.input.autogenerator.js'),
path.resolve(__dirname, '../public/js/scripts/core/date.time.picker.js'),
path.resolve(__dirname, '../public/js/scripts/adaptive.filters.js'),
path.resolve(__dirname, '../public/js/scripts/admin.notifications.js'),
path.resolve(__dirname, '../public/js/scripts/button.trigger.js'),
path.resolve(__dirname, '../public/js/scripts/button.prevent.default.js'),
Expand Down
43 changes: 43 additions & 0 deletions src/bundle/Resources/public/js/scripts/adaptive.filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
(function (global, doc, ibexa, bootstrap) {
const adaptiveFilters = doc.querySelectorAll('.ibexa-adaptive-filters');
const initializeAdaptiveFilters = (adaptiveFilter) => {
const adaptiveItemsContainer = adaptiveFilter.querySelector('.ibexa-adaptive-filters__items');
const adaptiveItemsCollapsibleContainer = adaptiveFilter.querySelector('.ibexa-adaptive-filters__collapsible');
const adaptiveItemsCollapsibleContentContainer = adaptiveFilter.querySelector('.ibexa-adaptive-filters__collapsible-content');
const actionsContainer = adaptiveFilter.querySelector('.ibexa-adaptive-filters__actions');
const toggleBtn = adaptiveFilter.querySelector('.ibexa-adaptive-filters__toggler');
const collapse = bootstrap.Collapse.getOrCreateInstance(adaptiveItemsCollapsibleContainer, {
toggle: false,
});
const adaptiveItems = new ibexa.core.AdaptiveItems({
itemHiddenClass: 'ibexa-adaptive-filters__item--hidden',
container: adaptiveItemsContainer,
getActiveItem: () => null,
prepareItemsBeforeAdapt: () => {
[...adaptiveItemsCollapsibleContentContainer.children].forEach((child) =>
adaptiveItemsContainer.insertBefore(child, actionsContainer),
);
},
onAdapted: (visibleItems, hiddenItems) => {
if (hiddenItems.size === 0) {
collapse.hide();
}

hiddenItems.forEach((hiddenItem) => adaptiveItemsCollapsibleContentContainer.append(hiddenItem));
},
});
adaptiveItemsCollapsibleContainer.addEventListener('hide.bs.collapse', () => {
toggleBtn.classList.add('ibexa-adaptive-filters__toggler--collapsed');
adaptiveItemsCollapsibleContainer.classList.add('ibexa-adaptive-filters__collapsible--collapsed');
adaptiveItemsCollapsibleContentContainer.classList.add('ibexa-adaptive-filters__collapsible-content--collapsed');
});
adaptiveItemsCollapsibleContainer.addEventListener('show.bs.collapse', () => {
toggleBtn.classList.remove('ibexa-adaptive-filters__toggler--collapsed');
adaptiveItemsCollapsibleContainer.classList.remove('ibexa-adaptive-filters__collapsible--collapsed');
adaptiveItemsCollapsibleContentContainer.classList.remove('ibexa-adaptive-filters__collapsible-content--collapsed');
});
adaptiveItems.init();
};

adaptiveFilters.forEach(initializeAdaptiveFilters);
})(window, window.document, window.ibexa, window.bootstrap);
24 changes: 18 additions & 6 deletions src/bundle/Resources/public/js/scripts/core/adaptive.items.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
class AdaptiveItems {
constructor(config) {
this.isVertical = config.isVertical ?? false;
this.prepareItemsBeforeAdapt = config.prepareItemsBeforeAdapt ?? (() => {});
this.container = config.container;
this.items =
config.items ||
this.container.querySelectorAll(':scope > .ibexa-adaptive-items__item:not(.ibexa-adaptive-items__item--selector)');
this.items = config.items
? [...config.items]
: [...this.container.querySelectorAll(':scope > .ibexa-adaptive-items__item:not(.ibexa-adaptive-items__item--selector)')];
this.selectorItem = config.selectorItem ?? this.container.querySelector(':scope > .ibexa-adaptive-items__item--selector');
this.itemHiddenClass = config.itemHiddenClass;
this.getActiveItem = config.getActiveItem;
Expand All @@ -31,13 +32,16 @@
}

adapt() {
const sizeProperty = this.isVertical ? 'offsetHeight' : 'offsetWidth';
const maxTotalSize = this.container[sizeProperty] - OFFSET_ROUNDING_COMPENSATOR;

this.prepareItemsBeforeAdapt();

[this.selectorItem, ...this.items].forEach((item) => item.classList.remove(this.itemHiddenClass));

const activeItem = this.getActiveItem();
const sizeProperty = this.isVertical ? 'offsetHeight' : 'offsetWidth';
const activeItemSize = activeItem ? activeItem[sizeProperty] + OFFSET_ROUNDING_COMPENSATOR : 0;
const selectorSize = this.selectorItem[sizeProperty] + OFFSET_ROUNDING_COMPENSATOR;
const maxTotalSize = this.container[sizeProperty] - OFFSET_ROUNDING_COMPENSATOR;
const forceVisibleItemsSize = [...this.items].reduce((totalSize, item) => {
const computedSize = item.classList.contains(this.classForceShow) ? item[sizeProperty] + OFFSET_ROUNDING_COMPENSATOR : 0;

Expand All @@ -46,6 +50,13 @@
const hiddenItemsWithoutSelector = new Set();
let currentSize = selectorSize + activeItemSize + forceVisibleItemsSize;

const itemsWithoutForce = this.items.filter((item) => {
const isForceHide = item.classList.contains(this.classForceHide);
const isForceVisible = item.classList.contains(this.classForceShow);

return !isForceHide && !isForceVisible;
});

for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
const isForceHide = item.classList.contains(this.classForceHide);
Expand All @@ -62,7 +73,8 @@
}

const lastItem = this.items[this.items.length - 1];
const isLastNonactiveItem = lastItem === activeItem ? i === this.items.length - 2 : i === this.items.length - 1;
const isLastNonactiveItem =
lastItem === activeItem ? i === itemsWithoutForce.length - 2 : i === itemsWithoutForce.length - 1;
const allPreviousItemsVisible = hiddenItemsWithoutSelector.size === 0;
const fitsInsteadOfSelector = item[sizeProperty] + OFFSET_ROUNDING_COMPENSATOR < maxTotalSize - currentSize + selectorSize;

Expand Down
148 changes: 148 additions & 0 deletions src/bundle/Resources/public/scss/_adaptive-filters.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
.ibexa-adaptive-filters {
display: flex;
flex-direction: column;
padding: calculateRem(4px) calculateRem(16px) 0 calculateRem(16px);
border-radius: $ibexa-border-radius;
border: calculateRem(1px) solid $ibexa-color-light;
box-shadow: calculateRem(4px) calculateRem(22px) calculateRem(47px) 0 rgba($ibexa-color-info, 0.05);

&--no-labels {
padding-top: calculateRem(16px);

.ibexa-adaptive-filters {
&__items {
height: 0;
}
}
}

&--no-collapsible-items {
.ibexa-adaptive-filters {
&__static-left {
width: calculateRem(450px);
border-right-width: 0;
}
}
}

&--inside-container {
margin: calculateRem(-33px) calculateRem(-33px) calculateRem(24px) calculateRem(-33px);
}

&__visible {
display: flex;
align-items: flex-end;
width: 100%;
margin-bottom: calculateRem(16px);
}

&__static-left {
flex-grow: 0;
display: flex;
align-items: flex-end;
height: calculateRem(48px);
width: calculateRem(300px);
padding-right: calculateRem(16px);
margin-right: calculateRem(16px);
border-right: calculateRem(1px) solid $ibexa-color-light;

.form-group {
width: 100%;
}
}

&__actions {
display: flex;
align-items: flex-end;
}

&__toggler-wrapper {
display: flex;
align-items: flex-end;
margin-left: auto;
padding: calculateRem(8px) calculateRem(2px) calculateRem(8px);
border-left: calculateRem(1px) solid $ibexa-color-light;

&.ibexa-adaptive-filters__item--hidden {
display: none;
}
}

&__items {
flex-grow: 2;
display: flex;
align-items: flex-end;
height: calculateRem(79px);
overflow: hidden;
}

&__collapsible {
border-top: calculateRem(1px) solid $ibexa-color-light;
transition: all $ibexa-admin-transition-duration $ibexa-admin-transition;

&--collapsed {
border-top-color: transparent;
}
}

&__collapsible-content {
display: flex;
padding: calculateRem(8px) 0 calculateRem(16px);
opacity: 1;
transition: all $ibexa-admin-transition-duration $ibexa-admin-transition;

&--collapsed {
opacity: 0;
}
}

&__item {
display: flex;
align-items: flex-end;
padding-right: calculateRem(16px);

.ibexa-label {
margin-top: 0;
}
}

&__toggler {
font-weight: bold;
opacity: 1;

.ibexa-icon {
transform: rotate(-90deg);
transition: all $ibexa-admin-transition-duration $ibexa-admin-transition;
}

.ibexa-btn {
&__label {
&--more {
display: none;
}
}
}

&.ibexa-adaptive-filters__item--hidden {
opacity: 0;
}

&--collapsed {
.ibexa-icon {
transform: rotate(90deg);
}

.ibexa-btn {
&__label {
&--less {
display: none;
}

&--more {
display: initial;
}
}
}
}
}
}
1 change: 1 addition & 0 deletions src/bundle/Resources/public/scss/ibexa.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import 'animations';
@import 'mixins';
@import 'general';
@import 'adaptive-filters';
@import 'backdrop';
@import 'tabs';
@import 'tables';
Expand Down
31 changes: 31 additions & 0 deletions src/bundle/Resources/translations/adaptive_filters.en.xliff
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file source-language="en" target-language="en" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>
</header>
<body>
<trans-unit id="c01c090bbfe9cd32a0a37ffc5727688abe01c30f" resname="actions.apply_btn">
<source>Apply</source>
<target state="new">Apply</target>
<note>key: actions.apply_btn</note>
</trans-unit>
<trans-unit id="e3eeaff256b4c7e7597477c8ab94aca52265d5b8" resname="actions.clear_btn">
<source>Clear filters</source>
<target state="new">Clear filters</target>
<note>key: actions.clear_btn</note>
</trans-unit>
<trans-unit id="67fc4ec6b05265db6a275d88f8df4e09c727fa58" resname="toggler.close">
<source>Less filters</source>
<target state="new">Less filters</target>
<note>key: toggler.close</note>
</trans-unit>
<trans-unit id="3d0affb0e0a5f08df3390232a861d63d283d6b1d" resname="toggler.open">
<source>More filters</source>
<target state="new">More filters</target>
<note>key: toggler.open</note>
</trans-unit>
</body>
</file>
</xliff>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{% import "@ibexadesign/ui/component/macros.html.twig" as html %}

{% trans_default_domain 'adaptive_filters' %}

{% set attr = attr|default({})|merge({
class: ('ibexa-adaptive-filters '
~ (no_labels|default(false) ? 'ibexa-adaptive-filters--no-labels ')
~ (no_collapsible_items|default(false) ? 'ibexa-adaptive-filters--no-collapsible-items ')
~ (is_inside_container|default(false) ? 'ibexa-adaptive-filters--inside-container ')
~ attr.class|default(''))|trim,
}) %}
{% set attr_form = attr_form|default({}) %}
{% set collapse_id = collapse_id|default('ibexa-adaptive-filters-collapse-' ~ random()) %}

{{ form_start(form, { attr: attr|merge(attr_form)}) }}
<div class="ibexa-adaptive-filters__visible">
<div class="ibexa-adaptive-filters__static-left">
{% block static_left %}
<div class="form-group">
<label class="ibexa-label"></label>
{% block static_left_input %}{% endblock %}
</div>
{% endblock %}
</div>
<div class="ibexa-adaptive-filters__items ibexa-adaptive-filters__items--empty ibexa-adaptive-items">
{% if not no_collapsible_items|default(false) %}
{% block collapsible_items %}
{% for item in collapsible_items %}
{% include '@ibexadesign/ui/component/adaptive_filters/adaptive_filters_item.html.twig' with {
content: item,
} %}
{% endfor %}
{% endblock %}
{% endif %}
<div class="ibexa-adaptive-filters__actions ibexa-adaptive-items__item ibexa-adaptive-items__item--force-show">
{% block actions %}
<button
type="submit"
class="btn ibexa-btn ibexa-btn--secondary ibexa-adaptive-filters__submit-btn"
>
{{ 'actions.apply_btn'|trans|desc('Apply') }}
</button>
<button
type="button"
class="btn ibexa-btn ibexa-btn--ghost ibexa-adaptive-filters__clear-btn"
>
<svg class="ibexa-icon ibexa-icon--small">
<use xlink:href="{{ ibexa_icon_path('discard') }}"></use>
</svg>
<span class="ibexa-btn__label">
{{ 'actions.clear_btn'|trans|desc('Clear filters') }}
</span>
</button>
{% endblock %}
</div>
<div
class="
ibexa-adaptive-filters__toggler-wrapper
ibexa-adaptive-filters__item--hidden
ibexa-adaptive-items__item
ibexa-adaptive-items__item--selector"
>
<button
type="button"
class="
btn ibexa-btn ibexa-btn--ghost ibexa-btn--small
ibexa-adaptive-filters__toggler ibexa-adaptive-filters__toggler--collapsed"
data-bs-toggle="collapse"
data-bs-target="#{{ collapse_id }}"
aria-expanded="false"
aria-controls="{{ collapse_id }}"
>
<svg class="ibexa-icon ibexa-icon--small">
<use xlink:href="{{ ibexa_icon_path('caret-next') }}"></use>
</svg>
<span class="ibexa-btn__label ibexa-btn__label--more">
{{ 'toggler.open'|trans|desc('More filters') }}
</span>
<span class="ibexa-btn__label ibexa-btn__label--less">
{{ 'toggler.close'|trans|desc('Less filters') }}
</span>
</button>
</div>
</div>
</div>
<div class="ibexa-adaptive-filters__collapsible collapse" id="{{ collapse_id }}">
<div class="ibexa-adaptive-filters__collapsible-content"></div>
</div>
{{ form_end(form) }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="ibexa-adaptive-filters__item ibexa-adaptive-items__item">
{% block content %}
{{ item|default('')|raw }}
{% endblock %}
</div>

0 comments on commit e628576

Please sign in to comment.