carbon-components
Version:
The Carbon Design System is IBM’s open-source design system for products and experiences.
162 lines (151 loc) • 6.42 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import mixin from '../../globals/js/misc/mixin';
import createComponent from '../../globals/js/mixins/create-component';
import settings from '../../globals/js/settings';
import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
import handles from '../../globals/js/mixins/handles';
import on from '../../globals/js/misc/on';
import eventMatches from '../../globals/js/misc/event-matches';
const { prefix } = settings;
class SideNav extends mixin(createComponent, initComponentBySearch, handles) {
/**
* The map associating DOM element and copy button UI instance.
* @member SideNav.components
* @type {WeakMap}
*/
static components /* #__PURE_CLASS_PROPERTY__ */ = new WeakMap();
/**
* Side nav.
* @extends CreateComponent
* @extends InitComponentBySearch
* @extends Handles
* @param {HTMLElement} element The element working as a side nav.
* @param {object} [options] The component options.
* @param {string} [options.selectorSideNavToggle]
* The CSS selector to find the toggle button.
* @param {string} [options.selectorSideNavSubmenu] The CSS selector to find the trigger buttons for sub nav items.
* @param {string} [options.selectorSideNavItem] The CSS selector to find the nav items.
* @param {string} [options.selectorSideNavLink] The CSS selector to find the interactive potions in non-nested nav items.
* @param {string} [options.selectorSideNavLinkCurrent]
* The CSS selector to find the interactive potion in active non-nested nav item.
* @param {string} [options.classSideNavExpanded] The CSS class for the expanded state.
* @param {string} [options.classSideNavItemActive]
* The CSS class for the active/inactive state for nav items.
* @param {string} [options.classSideNavLinkCurrent]
* The CSS class for the active/inactive state of the interactive potion in non-nested nav items.
*/
constructor(element, options) {
super(element, options);
this.manage(on(element, 'click', this._handleClick));
}
/**
* Enum for toggling side nav visibility
* @readonly
* @member SideNav.state
* @type {object}
* @property {string} EXPANDED Opening/visible
* @property {string} COLLAPSED Closing/hidden
*/
static state /* #__PURE_CLASS_PROPERTY__ */ = {
EXPANDED: 'expanded',
COLLAPSED: 'collapsed',
};
/**
* @returns {boolean} `true` if the nav is expanded.
*/
isNavExpanded() {
return this.element.classList.contains(this.options.classSideNavExpanded);
}
/**
* Changes the expanded/collapsed state.
*/
changeState(state) {
this.element.classList.toggle(
this.options.classSideNavExpanded,
state === this.constructor.state.EXPANDED
);
}
_handleClick = (evt) => {
const matchesToggle = eventMatches(evt, this.options.selectorSideNavToggle);
const matchesNavSubmenu = eventMatches(
evt,
this.options.selectorSideNavSubmenu
);
const matchesSideNavLink = eventMatches(
evt,
this.options.selectorSideNavLink
);
if (!matchesToggle && !matchesNavSubmenu && !matchesSideNavLink) {
return;
}
if (matchesToggle) {
this.changeState(
!this.isNavExpanded()
? this.constructor.state.EXPANDED
: this.constructor.state.COLLAPSED
);
return;
}
if (matchesNavSubmenu) {
const isSubmenuExpanded =
matchesNavSubmenu.getAttribute('aria-expanded') === 'true';
matchesNavSubmenu.setAttribute('aria-expanded', `${!isSubmenuExpanded}`);
return;
}
if (matchesSideNavLink) {
[
...this.element.querySelectorAll(
this.options.selectorSideNavLinkCurrent
),
].forEach((el) => {
el.classList.remove(
this.options.classSideNavItemActive,
this.options.classSideNavLinkCurrent
);
el.removeAttribute('aria-current');
});
matchesSideNavLink.classList.add(this.options.classSideNavLinkCurrent);
const closestSideNavItem = matchesSideNavLink.closest(
this.options.selectorSideNavItem
);
if (closestSideNavItem) {
closestSideNavItem.classList.add(this.options.classSideNavItemActive);
}
}
};
/**
* The component options.
* If `options` is specified in the constructor, {@linkcode SideNav.create .create()}, or {@linkcode SideNav.init .init()},
* properties in this object are overridden for the instance being create and how {@linkcode SideNav.init .init()} works.
* @member SideNav.options
* @type {object}
* @property {string} selectorInit The data attribute to find side navs.
* @property {string} [selectorSideNavToggle] The CSS selector to find the toggle button.
* @property {string} [selectorSideNavSubmenu] The CSS selector to find the trigger buttons for sub nav items.
* @property {string} [selectorSideNavItem] The CSS selector to find the nav items.
* @property {string} [selectorSideNavLink] The CSS selector to find the interactive portions in non-nested nav items.
* @property {string} [selectorSideNavLinkCurrent]
* The CSS selector to find the interactive potion in active non-nested nav item.
* @property {string} [classSideNavExpanded] The CSS class for the expanded state.
* @property {string} [classSideNavItemActive] The CSS class for the active/inactive state for nav items.
* @property {string} [classSideNavLinkCurrent]
* The CSS class for the active/inactive state of the interactive portion in non-nested nav items.
*/
static options /* #__PURE_CLASS_PROPERTY__ */ = {
selectorInit: '[data-side-nav]',
selectorSideNavToggle: `.${prefix}--side-nav__toggle`,
selectorSideNavSubmenu: `.${prefix}--side-nav__submenu`,
selectorSideNavItem: `.${prefix}--side-nav__item`,
selectorSideNavLink: `.${prefix}--side-nav__link`,
selectorSideNavLinkCurrent: `[aria-current="page"],.${prefix}--side-nav__link--current,.${prefix}--side-nav__item--active`,
classSideNavExpanded: `${prefix}--side-nav--expanded`,
classSideNavItemActive: `${prefix}--side-nav__item--active`,
classSideNavLinkCurrent: `${prefix}--side-nav__link--current`,
};
}
export default SideNav;