@telekom/scale-components
Version:
Scale is the digital design system for Telekom products and experiences.
251 lines (250 loc) • 6.83 kB
JavaScript
/**
* @license
* Scale https://github.com/telekom/scale
*
* Copyright (c) 2021 Egor Kirpichev and contributors, Deutsche Telekom AG
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { Component, h, Prop, Host, Watch, Element } from '@stencil/core';
import classNames from 'classnames';
const SR_ACTIVE_TEXT = ' Zurzeit aktiv';
const isActive = (current) => {
try {
return !!JSON.parse(current);
}
catch (e) {
if (typeof current === 'string') {
return true;
}
return !!current;
}
};
const getScreenReaderText = (current) => {
let text;
try {
text = JSON.parse(current);
}
catch (e) {
text = current;
}
return typeof text === 'string' && text.length > 0
? ` ${text}`
: SR_ACTIVE_TEXT;
};
export class SidebarNavItem {
constructor() {
/** Used normally for third level items, remove the bottom border */
this.condensed = false;
/** Bold text */
this.bold = false;
/** Text gets the active color */
this.active = false;
/**
* Mark the child link as "current" with `aria-current=page`.
* Provide the text hint if needed, default is: "Zurzeit aktiv"
*/
this.current = null;
}
nestingLevelChanged(newValue) {
if (newValue === 1) {
this.bold = true;
}
}
currentChanged(newValue) {
this.handleAriaCurrentInSlottedA(newValue);
this.syncActiveToCurrent(newValue);
}
componentDidLoad() {
this.handleAriaCurrentInSlottedA(this.current);
if (this.current) {
this.syncActiveToCurrent(this.current);
}
}
/**
* If an item is `current`, it should be `active` as well
*/
syncActiveToCurrent(newValue) {
this.active = isActive(newValue);
}
/**
* When `current` is set, this will:
* - set the aria-current=page attribute on the link
* - append a text-only hint for screen readers
* so we end up with something like this:
* <a href="..." aria-current="page">
* Example<span style="...visible to SR only..."> Active link</span>
* </a>
* @param current this.current
*/
handleAriaCurrentInSlottedA(current) {
const a = this.el.querySelector('a');
if (this.srOnlyElement != null) {
a.removeChild(this.srOnlyElement);
this.srOnlyElement = null;
}
if (a != null) {
a.removeAttribute('aria-current');
}
if (isActive(current) && a != null) {
this.srOnlyElement = this.createScreenReaderOnlySpan();
a.appendChild(this.srOnlyElement);
a.setAttribute('aria-current', 'page');
}
}
createScreenReaderOnlySpan() {
const text = getScreenReaderText(this.current);
const span = document.createElement('span');
// .sr-only but inline
Object.assign(span.style, {
position: 'absolute',
left: '-10000px',
overflow: 'hidden',
});
span.textContent = text;
return span;
}
render() {
return (h(Host, null,
this.styles && h("style", null, this.styles),
h("li", { part: this.getBasePartMap(), class: this.getCssClassMap(), role: "listitem" },
h("slot", null))));
}
getBasePartMap() {
return this.getCssOrBasePartMap('basePart');
}
getCssClassMap() {
return this.getCssOrBasePartMap('css');
}
getCssOrBasePartMap(mode) {
const component = 'sidebar-nav-item';
const prefix = mode === 'basePart' ? '' : `${component}--`;
return classNames(component, this.bold && `${prefix}bold`, this.condensed && `${prefix}condensed`, this.active && `${prefix}active`);
}
static get is() { return "scale-sidebar-nav-item"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() { return {
"$": ["sidebar-nav-item.css"]
}; }
static get styleUrls() { return {
"$": ["sidebar-nav-item.css"]
}; }
static get properties() { return {
"condensed": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Used normally for third level items, remove the bottom border"
},
"attribute": "condensed",
"reflect": false,
"defaultValue": "false"
},
"bold": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Bold text"
},
"attribute": "bold",
"reflect": false,
"defaultValue": "false"
},
"active": {
"type": "boolean",
"mutable": true,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Text gets the active color"
},
"attribute": "active",
"reflect": true,
"defaultValue": "false"
},
"current": {
"type": "any",
"mutable": false,
"complexType": {
"original": "string | null | boolean",
"resolved": "boolean | string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Mark the child link as \"current\" with `aria-current=page`.\nProvide the text hint if needed, default is: \"Zurzeit aktiv\""
},
"attribute": "current",
"reflect": false,
"defaultValue": "null"
},
"nestingLevel": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Nesting level within the <scale-sidebar-nav> parent, gets set automatically"
},
"attribute": "nesting-level",
"reflect": false
},
"styles": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "(optional) Extra styles"
},
"attribute": "styles",
"reflect": false
}
}; }
static get elementRef() { return "el"; }
static get watchers() { return [{
"propName": "nestingLevel",
"methodName": "nestingLevelChanged"
}, {
"propName": "current",
"methodName": "currentChanged"
}]; }
}