See a problem with the code? Need a new component? Have feedback? We'd love to hear it all! The success of the Bluemix Design System relies on great people like yourself. Fill out the form below to submit your issue to our GitHub repo.
A member of the Bluemix Design System's team will be in touch within 48 hours.
Your issue, , is now in the bluemix-components repo. Click the issue number to track the progress or make edits to your issue.
We will get back to you within 48 hours. Feel free to reach out to us on one of our slack channels if you have additional questions:
#bluemix-componentscomponents
Interior left navigation organizes the content structure and provides context to support user orientation. This pattern accommodates the breadth of content and tasks users expect to see.
<nav role='navigation' aria-label='Inline Left Navigation' data-inline-left-nav class='bx--inline-left-nav bx--inline-left-nav--collapseable'>
<ul role='menubar' class='left-nav-list' data-inline-left-nav-list aria-hidden='false'>
<li role='menuitem' tabindex='0' class='left-nav-list__item' data-inline-left-nav-item>
<a class='left-nav-list__item-link'>
Example Item 1
</a>
</li>
<li role='menuitem' tabindex='0' class='left-nav-list__item' data-inline-left-nav-item>
<a class='left-nav-list__item-link'>
Example Item 2
</a>
</li>
<li role='menuitem' tabindex='0' class='left-nav-list__item left-nav-list__item--has-children' data-inline-left-nav-item data-inline-left-nav-with-children>
<a class='left-nav-list__item-link'>
Example Item 3
<div class='left-nav-list__item-icon'>
<svg class='bx--inline-left-nav__icon'>
<use xlink:href='https://dev-console.stage1.ng.bluemix.net/api/v4/img/sprite.svg#support--chevron-down'></use>
</svg>
</div>
</a>
<ul role='menu' aria-hidden='true' class='left-nav-list left-nav-list--nested' data-inline-left-nav-nested-list>
<li class='left-nav-list__item' data-inline-left-nav-nested-item role='menuitem' tabindex='-1'>
<a href='#example-item-1A' class='left-nav-list__item-link' data-inline-left-nav-item-link tabindex='-1'>Example Item 1A</a>
</li>
<li class='left-nav-list__item' data-inline-left-nav-nested-item role='menuitem' tabindex='-1'>
<a href='#example-item-1B' class='left-nav-list__item-link' data-inline-left-nav-item-link tabindex='-1'>Example Item 1B</a>
</li>
<li class='left-nav-list__item' data-inline-left-nav-nested-item role='menuitem' tabindex='-1'>
<a href='#example-item-1C' class='left-nav-list__item-link' data-inline-left-nav-item-link tabindex='-1'>Example Item 1C</a>
</li>
<li class='left-nav-list__item' data-inline-left-nav-nested-item role='menuitem' tabindex='-1'>
<a href='#example-item-1D' class='left-nav-list__item-link' data-inline-left-nav-item-link tabindex='-1'>Example Item 1D</a>
</li>
</ul>
</li>
<li role='menuitem' tabindex='0' class='left-nav-list__item left-nav-list__item--has-children' data-inline-left-nav-item data-inline-left-nav-with-children>
<a class='left-nav-list__item-link'>
Example Item 4
<div class='left-nav-list__item-icon'>
<svg class='bx--inline-left-nav__icon'>
<use xlink:href='https://dev-console.stage1.ng.bluemix.net/api/v4/img/sprite.svg#support--chevron-down'></use>
</svg>
</div>
</a>
<ul role='menu' aria-hidden='true' class='left-nav-list left-nav-list--nested' data-inline-left-nav-nested-list>
<li class='left-nav-list__item' data-inline-left-nav-nested-item role='menuitem' tabindex='-1'>
<a href='#example-item-2A' class='left-nav-list__item-link' data-inline-left-nav-item-link tabindex='-1'>Example Item 2A</a>
</li>
<li class='left-nav-list__item' data-inline-left-nav-nested-item role='menuitem' tabindex='-1'>
<a href='#example-item-2B' class='left-nav-list__item-link' data-inline-left-nav-item-link tabindex='-1'>Example Item 2B</a>
</li>
<li class='left-nav-list__item' data-inline-left-nav-nested-item role='menuitem' tabindex='-1'>
<a href='#example-item-2C' class='left-nav-list__item-link' data-inline-left-nav-item-link tabindex='-1'>Example Item 2C</a>
</li>
<li class='left-nav-list__item' data-inline-left-nav-nested-item role='menuitem' tabindex='-1'>
<a href='#example-item-2D' class='left-nav-list__item-link' data-inline-left-nav-item-link tabindex='-1'>Example Item 2D</a>
</li>
</ul>
</li>
</ul>
<div class='bx--inline-left-nav-collapse' data-inline-left-nav-collapse>
<a class='bx--inline-left-nav-collapse__link' href='#'>
<svg class='bx--inline-left-nav-collapse__arrow'>
<use xlink:href='https://dev-console.stage1.ng.bluemix.net/api/v5/img/bluemix-icons.svg#chevron--left'></use>
</svg>
</a>
</div>
</nav>
import mixin from '../misc/mixin';
import createComponent from '../mixins/create-component';
import initComponent from '../mixins/init-component-by-search';
import '../polyfills/array-from';
import '../polyfills/element-matches';
import '../polyfills/object-assign';
import '../polyfills/custom-event';
import toggleClass from '../polyfills/toggle-class';
import eventMatches from '../polyfills/event-matches';
class InlineLeftNav extends mixin(createComponent, initComponent) {
/**
* Spinner indicating loading state.
* @extends CreateComponent
* @extends InitComponentBySearch
* @param {HTMLElement} element The element working as a spinner.
* @param {Object} options The component options.
*/
constructor(element, options) {
super(element, options);
this.constructor.components.set(this.element, this);
this.hookListItemsEvents();
}
hookListItemsEvents = () => {
this.element.addEventListener('click', (evt) => {
const leftNavItem = eventMatches(evt, this.options.selectorLeftNavListItem);
const collapseEl = eventMatches(evt, this.options.selectorLeftNavCollapse);
const collapsedBar = eventMatches(evt, `.${this.options.classLeftNavCollapsed}`);
if (leftNavItem) {
const childItem = eventMatches(evt, this.options.selectorLeftNavNestedListItem);
const hasChildren = leftNavItem.classList.contains('left-nav-list__item--has-children');
if (childItem) {
this.addActiveListItem(childItem);
} else if (hasChildren) {
this.handleNestedListClick(leftNavItem, evt);
} else {
this.addActiveListItem(leftNavItem);
}
}
if (collapseEl || collapsedBar) {
evt.preventDefault();
this.toggleLeftNav();
}
});
this.element.addEventListener('keydown', (evt) => {
const leftNavItemWithChildren = eventMatches(evt, this.options.selectorLeftNavListItemHasChildren);
const leftNavItem = eventMatches(evt, this.options.selectorLeftNavListItem);
if (leftNavItemWithChildren && evt.which === 13) {
this.handleNestedListClick(leftNavItemWithChildren, evt);
} else if (leftNavItem && evt.which === 13) {
this.addActiveListItem(leftNavItem);
}
});
}
addActiveListItem(item) {
[...this.element.querySelectorAll(this.options.selectorLeftNavListItem)].forEach((currentItem) => {
if (!(item === currentItem)) {
currentItem.classList.remove(this.options.classActiveLeftNavListItem);
}
});
[...this.element.querySelectorAll(this.options.selectorLeftNavNestedListItem)].forEach((currentItem) => {
if (!(item === currentItem)) {
currentItem.classList.remove(this.options.classActiveLeftNavListItem);
}
});
item.classList.add(this.options.classActiveLeftNavListItem);
}
/**
* Handles click on a list item that contains a nested list in the left navigation.
* The nested list is expanded and the icon is rotated.
* @param {HTMLElement} listItem The list item that was clicked.
* @param {Event} event The event triggering this method.
*/
handleNestedListClick(listItem, evt) {
const allNestedItems = [...document.querySelectorAll(this.options.selectorLeftNavListItemHasChildren)];
const isOpen = listItem.classList.contains(this.options.classExpandedLeftNavListItem);
allNestedItems.forEach((currentItem) => {
if (currentItem !== listItem) {
toggleClass(currentItem, this.options.classExpandedLeftNavListItem, false);
}
});
if (!('inlineLeftNavItemLink' in evt.target.dataset)) {
toggleClass(listItem, this.options.classExpandedLeftNavListItem, !isOpen);
}
const list = listItem.querySelector(this.options.selectorLeftNavNestedList);
const listItems = [...list.querySelectorAll(this.options.selectorLeftNavNestedListItem)];
listItems.forEach((item) => {
if (isOpen) {
// eslint-disable-next-line no-param-reassign
item.querySelector(this.options.selectorLeftNavListItemLink).tabIndex = -1;
} else {
// eslint-disable-next-line no-param-reassign
item.querySelector(this.options.selectorLeftNavListItemLink).tabIndex = 0;
}
});
}
toggleLeftNav = () => {
const collapsed = this.element.dataset.collapsed === 'true';
if (!collapsed) {
this.element.dataset.collapsed = true;
this.element.classList.add(this.options.classLeftNavCollapsing);
window.setTimeout(() => {
this.element.classList.add(this.options.classLeftNavCollapsed);
}, 250);
} else {
this.element.dataset.collapsed = false;
this.element.classList.remove(this.options.classLeftNavCollapsed);
this.element.classList.remove(this.options.classLeftNavCollapsing);
this.element.classList.add(this.options.classLeftNavExpanding);
window.setTimeout(() => {
this.element.classList.remove(this.options.classLeftNavExpanding);
}, 250);
}
}
/**
* The map associating DOM element and spinner instance.
* @member InlineLeftNav.components
* @type {WeakMap}
*/
static components = new WeakMap();
/**
* The component options.
* If `options` is specified in the constructor,
* {@linkcode InlineLeftNav.create .create()}, or {@linkcode InlineLeftNav.init .init()},
* properties in this object are overriden for the instance being create and how {@linkcode InlineLeftNav.init .init()} works.
* @member InlineLeftNav.options
* @type {Object}
* @property {string} selectorInit The CSS selector to find inline left navs.
*/
static options = {
selectorInit: '[data-inline-left-nav]',
// Data Attribute selectors
selectorLeftNavList: '[data-inline-left-nav-list]',
selectorLeftNavNestedList: '[data-inline-left-nav-nested-list]',
selectorLeftNavListItem: '[data-inline-left-nav-item]',
selectorLeftNavListItemLink: '[data-inline-left-nav-item-link]',
selectorLeftNavNestedListItem: '[data-inline-left-nav-nested-item]',
selectorLeftNavListItemHasChildren: '[data-inline-left-nav-with-children]',
selectorLeftNavCollapse: '[data-inline-left-nav-collapse]',
// CSS Class Selectors
classActiveLeftNavListItem: 'left-nav-list__item--active',
classExpandedLeftNavListItem: 'left-nav-list__item--expanded',
classLeftNavCollapsing: 'bx--inline-left-nav--collapsing',
classLeftNavCollapsed: 'bx--inline-left-nav--collapsed',
classLeftNavExpanding: 'bx--inline-left-nav--expanding',
};
}
export default InlineLeftNav;