diff --git a/blocks/accordion/accordion.js b/blocks/accordion/accordion.js
index 98dd613c..bb7ce8ed 100644
--- a/blocks/accordion/accordion.js
+++ b/blocks/accordion/accordion.js
@@ -34,10 +34,18 @@ export default class Accordion extends ComponentBase {
this.setupContent(children.filter((_, ind) => ind % 2 === 1));
}
+ createIcon(elem) {
+ const icon = document.createElement('raqn-icon');
+ icon.dataset.icon = 'chevron-right';
+
+ const hasIcon = elem?.querySelectorAll(`raqn-icon[data-icon="${icon.dataset.icon}"]`)?.length;
+ if (!hasIcon) {
+ elem.append(icon);
+ }
+ }
+
setupControls(controls) {
controls.forEach((control, index) => {
- const icon = document.createElement('raqn-icon');
- icon.dataset.icon = 'chevron-right';
const children = Array.from(control.children);
if (children.length === 0) {
const child = document.createElement('span');
@@ -45,7 +53,7 @@ export default class Accordion extends ComponentBase {
control.innerHTML = '';
control.append(child);
}
- control.children[0].append(icon);
+ this.createIcon(control.children[0]);
control.setAttribute('role', 'button');
control.setAttribute('aria-expanded', 'false');
control.setAttribute('tabindex', '0');
diff --git a/blocks/footer/footer.js b/blocks/footer/footer.js
index ec0a61a1..b1ffc13d 100644
--- a/blocks/footer/footer.js
+++ b/blocks/footer/footer.js
@@ -28,7 +28,7 @@ export default class Footer extends ComponentBase {
if (!child) return;
child.replaceWith(...child.children);
this.nav = this.querySelector('ul');
- this.nav.setAttribute('role', 'navigation');
+ this.nav?.setAttribute('role', 'navigation');
this.classList.add('full-width');
this.classList.add('horizontal');
}
diff --git a/blocks/navigation/navigation.css b/blocks/navigation/navigation.css
index 4f999d7f..0890d919 100644
--- a/blocks/navigation/navigation.css
+++ b/blocks/navigation/navigation.css
@@ -16,6 +16,10 @@ raqn-navigation > nav p {
padding: 0;
}
+raqn-navigation > nav ul {
+ list-style: none;
+}
+
raqn-navigation .level-1 a:not(:hover) {
color: var(--accent-background, #000);
}
@@ -34,6 +38,10 @@ raqn-navigation.active > nav p {
display: block;
}
+raqn-navigation > nav a {
+ display: inline-block;
+}
+
raqn-navigation.active > nav a {
display: inline-flex;
align-items: center;
@@ -73,7 +81,6 @@ raqn-navigation.active button {
raqn-navigation.active > nav > ul {
position: fixed;
display: block;
- list-style: none;
max-width: 0;
background: var(--background, #fff);
min-width: 100%;
@@ -82,12 +89,9 @@ raqn-navigation.active > nav > ul {
height: 100%;
max-height: calc(100vh - var(--header-height, 64px));
margin: 0 auto;
- padding: 0;
-}
-
-raqn-navigation.active > nav > ul li {
- max-width: var(--max-width, 100%);
- margin: 0 auto;
+ padding-block: 0;
+ padding-inline: var(--container-width);
+ overflow-y: auto;
}
raqn-navigation.active > nav > ul li a {
@@ -105,6 +109,8 @@ raqn-navigation:not([data-compact='true']) > nav a {
raqn-navigation:not([data-compact='true']) > nav ul {
list-style: none;
display: flex;
+ column-gap: var(--padding-vertical, 40px);
+ margin: 0;
}
raqn-navigation:not([data-compact='true']) > nav > ul {
@@ -120,8 +126,8 @@ raqn-navigation:not([data-compact='true']) > nav [data-icon='chevron-right'] {
transform: rotate(90deg);
}
-raqn-navigation:not([data-compact='true']) > nav .level-1 a {
- padding: var(--padding-vertical, 10px) var(--padding-horizontal, 20px);
+raqn-navigation:not([data-compact='true']) > nav :where(.level-1, .level-2) > a {
+ padding-block: var(--padding-horizontal, 20px);
}
raqn-navigation:not([data-compact='true']) > nav .level-2 > a {
@@ -150,9 +156,11 @@ raqn-navigation:not([data-compact='true']) > nav .level-1 > ul {
position: absolute;
padding: 0;
inset-block-start: var(--header-height, 64px);
- inset-inline-start: calc((100vw - var(--max-width)) / 2);
+ inset-inline-start: 0;
+ width: 100%;
transition: clip-path 0.4s ease-in-out;
overflow: visible;
+ padding-inline: var(--container-width);
}
raqn-navigation:not([data-compact='true']) > nav .level-1 > ul .level-2 {
@@ -164,10 +172,9 @@ raqn-navigation:not([data-compact='true']) > nav .level-1 > ul .level-2 {
raqn-navigation:not([data-compact='true']) > nav .level-1 > ul::after {
content: ' ';
- margin-inline: calc(-1 * ((100vw - var(--max-width)) / 2));
position: absolute;
height: 100%;
- width: 100vw;
+ width: 100%;
inset-inline-start: 0;
background: var(--background, #fff);
border-block-start: 1px solid var(--accent-background, #000);
diff --git a/blocks/navigation/navigation.js b/blocks/navigation/navigation.js
index bd18bfd5..95e72eaf 100644
--- a/blocks/navigation/navigation.js
+++ b/blocks/navigation/navigation.js
@@ -1,88 +1,218 @@
+import component from '../../scripts/init.js';
+import { blockBodyScroll } from '../../scripts/libs.js';
import Column from '../column/column.js';
export default class Navigation extends Column {
- static observedAttributes = ['data-icon', 'data-compact', 'data-justify'];
+ static observedAttributes = ['data-menu-icon', 'data-item-icon', 'data-compact', ...Column.observedAttributes];
dependencies = ['icon', 'accordion'];
- createButton() {
- const button = document.createElement('button');
- button.setAttribute('aria-label', 'Menu');
- button.setAttribute('aria-expanded', 'false');
- button.setAttribute('aria-controls', 'navigation');
- button.setAttribute('aria-haspopup', 'true');
- button.setAttribute('type', 'button');
- button.setAttribute('tabindex', '0');
- button.innerHTML = '';
- button.addEventListener('click', () => {
- this.classList.toggle('active');
- button.setAttribute('aria-expanded', this.classList.contains('active'));
- });
- return button;
- }
+ attributesValues = {
+ all: {
+ data: {
+ menu: {
+ icon: 'menu__close',
+ },
+ item: {
+ icon: 'chevron-right',
+ },
+ },
+ },
+ m: {
+ data: {
+ compact: true,
+ },
+ },
+ x: {
+ data: {
+ compact: true,
+ },
+ },
+ xs: {
+ data: {
+ compact: true,
+ },
+ },
+ };
- ready() {
+ setDefaults() {
+ super.setDefaults();
this.active = {};
- this.list = this.querySelector('ul');
+ this.isActive = false;
+ this.navContentInit = false;
+ this.navCompactedContentInit = false;
+ }
+
+ async ready() {
+ this.navContent = this.querySelector('ul');
+ this.innerHTML = '';
+ this.navCompactedContent = this.navContent.cloneNode(true); // the clone need to be done before `this.navContent` is modified
this.nav = document.createElement('nav');
- this.nav.append(this.list);
- this.setAttribute('role', 'navigation');
- this.compact = this.getAttribute('compact') === 'true' || false;
- this.icon = this.getAttribute('icon') || 'menu';
- if (this.compact) {
- this.nav.append(this.createButton());
- }
+ this.isCompact = this.dataset.compact === 'true';
this.append(this.nav);
- this.setupClasses(this.list);
- if (this.compact) {
- this.addEventListener('click', (e) => this.activate(e));
+ this.nav.setAttribute('role', 'navigation');
+ this.nav.setAttribute('id', 'navigation');
+
+ if (this.isCompact) {
+ await this.setupCompactedNav();
+ } else {
+ this.setupNav();
+ }
+ }
+
+ setupNav() {
+ if (!this.navContentInit) {
+ this.navContentInit = true;
+ this.setupClasses(this.navContent);
}
+ this.navButton?.remove();
+ this.nav.append(this.navContent);
}
- createIcon(name = this.icon) {
+ async setupCompactedNav() {
+ if (!this.navCompactedContentInit) {
+ this.navCompactedContentInit = true;
+ await component.multiLoadAndDefine(['accordion', 'icon']);
+ this.setupClasses(this.navCompactedContent, true);
+ this.navCompactedContent.addEventListener('click', (e) => this.activate(e));
+ }
+
+ this.prepend(this.createButton());
+ this.nav.append(this.navCompactedContent);
+ }
+
+ onAttributeCompactChanged({ oldValue, newValue }) {
+ if (!this.initialized) return;
+ if (oldValue === newValue) return;
+ this.isCompact = newValue === 'true';
+ this.nav.innerHTML = '';
+
+ if (this.isCompact) {
+ this.setupCompactedNav();
+ } else {
+ if (this.navButton) {
+ this.isActive = false;
+ this.classList.remove('active');
+ this.navButton.removeAttribute('aria-expanded');
+ this.navIcon.dataset.active = this.isActive;
+ this.closeAllLevels();
+ }
+ this.setupNav();
+ }
+ }
+
+ onAttributeIconChanged({ newValue }) {
+ if (!this.initialized) return;
+ if (!this.isCompact) return;
+ this.navIcon.dataset.icon = newValue;
+ }
+
+ createButton() {
+ this.navButton = document.createElement('button');
+ this.navButton.setAttribute('aria-label', 'Menu');
+ this.navButton.setAttribute('aria-expanded', 'false');
+ this.navButton.setAttribute('aria-controls', 'navigation');
+ this.navButton.setAttribute('aria-haspopup', 'true');
+ this.navButton.setAttribute('type', 'button');
+ this.navButton.innerHTML = ``;
+ this.navIcon = this.navButton.querySelector('raqn-icon');
+ this.navButton.addEventListener('click', () => {
+ this.isActive = !this.isActive;
+ this.classList.toggle('active');
+ this.navButton.setAttribute('aria-expanded', this.isActive);
+ this.navIcon.dataset.active = this.isActive;
+ blockBodyScroll(this.isActive);
+ this.closeAllLevels();
+ });
+ return this.navButton;
+ }
+
+ addIcon(elem) {
const icon = document.createElement('raqn-icon');
- icon.setAttribute('icon', name);
- return icon;
+ icon.dataset.icon = this.dataset.itemIcon;
+ elem.append(icon);
}
- creaeteAccordion(replaceChildrenElement) {
+ createAccordionOld(elem) {
+ component.init({
+ componentName: 'accordion',
+ targets: [elem],
+ componentConfig: {
+ addToTargetMethod: 'append',
+ },
+ nestedComponentsConfig: {
+ button: { active: false },
+ },
+ });
+ }
+
+ createAccordion(replaceChildrenElement) {
const accordion = document.createElement('raqn-accordion');
- const children = Array.from(replaceChildrenElement.children);
- accordion.append(...children);
+ accordion.append(...replaceChildrenElement.childNodes);
replaceChildrenElement.append(accordion);
}
- setupClasses(ul, level = 1) {
+ setupClasses(ul, isCompact, level = 1) {
const children = Array.from(ul.children);
- children.forEach((child) => {
+
+ children.forEach(async (child) => {
const hasChildren = child.querySelector('ul');
child.classList.add(`level-${level}`);
child.dataset.level = level;
if (hasChildren) {
- const anchor = child.querySelector('a');
- if (this.compact) {
- this.creaeteAccordion(child);
+ if (isCompact) {
+ this.createAccordion(child);
} else if (level === 1) {
- anchor.append(this.createIcon('chevron-right'));
+ const anchor = child.querySelector('a');
+
+ this.addIcon(anchor);
}
child.classList.add('has-children');
- this.setupClasses(hasChildren, level + 1);
+ this.setupClasses(hasChildren, isCompact, level + 1);
}
});
}
activate(e) {
- e.preventDefault();
- if (e.target.tagName.toLowerCase() === 'a') {
+ if (e.target.tagName.toLowerCase() === 'raqn-icon' || e.target.closest('raqn-icon')) {
+ e.preventDefault();
+
const current = e.target.closest('li');
const { level } = current.dataset;
- if (this.active[level] && this.active[level] !== current) {
- this.active[level].classList.remove('active');
+ const currentLevel = Number(level);
+ const activeLevel = Number(this.getAttribute('active'));
+ const isCurrentLevel = this.active[currentLevel] && this.active[currentLevel] === current;
+ const hasActiveChildren = currentLevel < activeLevel;
+
+ if (!isCurrentLevel || hasActiveChildren) {
+ const whileCurrentLevel = isCurrentLevel && hasActiveChildren ? currentLevel + 1 : currentLevel;
+ this.closeLevels(activeLevel, whileCurrentLevel);
}
- this.active[level] = current;
- this.setAttribute('active', level);
- this.active[level].classList.toggle('active');
+
+ this.setAttribute('active', isCurrentLevel ? Math.max(0, currentLevel - 1) || '' : currentLevel);
+ this.active[currentLevel]?.classList.toggle('active');
+ this.active[currentLevel] = isCurrentLevel ? null : current;
+ }
+ }
+
+ closeLevels(activeLevel, currentLevel = 1) {
+ let whileCurrentLevel = currentLevel;
+ while (whileCurrentLevel <= activeLevel) {
+ this.active[whileCurrentLevel].classList.remove('active');
+ const accordion = this.active[whileCurrentLevel].querySelector('raqn-accordion');
+ const control = accordion.querySelector('.accordion-control');
+ accordion.toggleControl(control);
+ this.active[whileCurrentLevel] = null;
+ whileCurrentLevel += 1;
+ }
+ }
+
+ closeAllLevels() {
+ const activeLevel = Number(this.getAttribute('active'));
+ if (activeLevel) {
+ this.closeLevels(activeLevel);
+ this.removeAttribute('active');
}
}
}
diff --git a/blocks/popup/popup.js b/blocks/popup/popup.js
index 11560e19..df7515e7 100644
--- a/blocks/popup/popup.js
+++ b/blocks/popup/popup.js
@@ -5,6 +5,7 @@ import {
popupState,
focusTrap,
focusFirstElementInContainer,
+ blockBodyScroll,
} from '../../scripts/libs.js';
/**
@@ -46,7 +47,6 @@ export default class Popup extends ComponentBase {
classes: {
popupClosing: 'popup__base--closing',
popupFlyout: 'popup__base--flyout',
- noScroll: 'no-scroll',
hide: 'hide',
},
},
@@ -129,7 +129,7 @@ export default class Popup extends ComponentBase {
popupState.closeActivePopup();
popupState.activePopup = this;
- this.blockBodyScroll(true);
+ blockBodyScroll(true);
this.showPopup(true);
this.toggleCloseOnEsc(true);
focusFirstElementInContainer(this.elements.popupContainer);
@@ -215,7 +215,7 @@ export default class Popup extends ComponentBase {
closePopup() {
popupState.activePopup = null;
- this.blockBodyScroll(false);
+ blockBodyScroll(false);
this.updatePopupTrigger(false);
this.toggleCloseOnEsc(false);
this.classList.add('popup--closing');
@@ -230,7 +230,7 @@ export default class Popup extends ComponentBase {
popupState.closeActivePopup();
popupState.activePopup = this;
- this.blockBodyScroll(true);
+ blockBodyScroll(true);
await this.addFragmentContent();
this.setInnerBlocks();
await this.initChildComponents();
@@ -259,11 +259,6 @@ export default class Popup extends ComponentBase {
if (this.popupTrigger) this.popupTrigger.dataset.active = isActive;
}
- blockBodyScroll(boolean) {
- const { noScroll } = this.config.classes;
- document.body.classList.toggle(noScroll, boolean);
- }
-
showPopup(boolean) {
const { hide } = this.config.classes;
this.classList.toggle(hide, !boolean);
diff --git a/scripts/component-base.js b/scripts/component-base.js
index 3a5036c9..d7203a20 100644
--- a/scripts/component-base.js
+++ b/scripts/component-base.js
@@ -129,6 +129,7 @@ export default class ComponentBase extends HTMLElement {
// ! Needs to be called after the element is created;
async init(initOptions) {
try {
+ await this.Handler;
this.wasInitBeforeConnected = true;
this.initOptions = initOptions || {};
this.setInitialAttributesValues();
@@ -153,7 +154,7 @@ export default class ComponentBase extends HTMLElement {
* When the element was created with data attributes before the ini() method is called
* use the data attr values as default for attributesValues
*/
- setInitialAttributesValues() {
+ async setInitialAttributesValues() {
const initialAttributesValues = { all: {} };
this.Handler.observedAttributes.map((dataAttr) => {
@@ -221,6 +222,7 @@ export default class ComponentBase extends HTMLElement {
async initOnConnected() {
if (this.wasInitBeforeConnected) return;
+ await this.Handler;
this.setInitialAttributesValues();
await this.buildExternalConfig();
this.runConfigsByViewport();
@@ -305,7 +307,7 @@ export default class ComponentBase extends HTMLElement {
runConfigsByViewport() {
const { name } = getBreakPoints().active;
const current = deepMerge({}, this.attributesValues.all, this.attributesValues[name]);
- this.className = '';
+ this.removeAttribute('class');
Object.keys(current).forEach((key) => {
const action = `apply${key.charAt(0).toUpperCase() + key.slice(1)}`;
if (typeof this[action] === 'function') {
@@ -344,7 +346,7 @@ export default class ComponentBase extends HTMLElement {
if (isObject(className)) {
// if an object is passed, it's flat and splited
this.classList.add(...flatAsValue(className).split(' '));
- } else {
+ } else if (className) {
// strings are added as is
this.setAttribute('class', className);
}
diff --git a/scripts/libs.js b/scripts/libs.js
index 520e20c3..11c0be2b 100644
--- a/scripts/libs.js
+++ b/scripts/libs.js
@@ -14,6 +14,9 @@ export const globalConfig = {
medium: 500,
bold: 700,
},
+ classes: {
+ noScroll: 'no-scroll',
+ },
};
export const metaTags = {
@@ -467,3 +470,8 @@ export const classToFlat = (classes = [], valueLength = 1, extend = {}) =>
return acc;
}, extend),
);
+
+export function blockBodyScroll(boolean) {
+ const { noScroll } = globalConfig.classes;
+ document.body.classList.toggle(noScroll, boolean);
+}
diff --git a/styles/styles.css b/styles/styles.css
index b0bd7387..743467c6 100644
--- a/styles/styles.css
+++ b/styles/styles.css
@@ -2,6 +2,7 @@
--max-width: 80%;
--padding-container: 20px;
--header-height: 110px;
+ --container-width: max(calc((100% - var(--max-width)) / 2), var(--padding-container));
}
@media screen and (max-width: 768px) {
@@ -182,7 +183,7 @@ main > div > *:not(.full-width) {
}
.full-width {
- padding-inline: max(calc((100% - var(--max-width)) / 2), var(--padding-container));
+ padding-inline: var(--container-width);
}
main > div > div {
@@ -236,7 +237,8 @@ button {
}
.raqn-grid {
- width: var(--max-width, 100%);
+ width: 100%;
+ padding-inline: var(--container-width);
margin: 0 auto;
display: grid;
grid-template-columns: var(--grid-template-columns, 1fr);