@limetech/lime-elements
Version:
336 lines (335 loc) • 12 kB
JavaScript
import { h, Host, } from '@stencil/core';
import translate from './../../global/translations';
import { isItem } from './is-item';
/**
* An action bar is a user interface element commonly found in software applications and websites.
* It typically appears at the top of the screen or within a specific section
* and serves as a centralized hub for accessing various actions and commands
* relevant to the current context or page.
*
* The action bar often contains a set of clickable icons or buttons (icons + labels)
* that represent specific actions, such as saving, deleting, editing, sharing,
* or bulk operations for selected items.
*
* The purpose of an action bar is to provide quick and convenient access to
* frequently used functionalities, enabling users to perform common tasks efficiently.
* It enhances usability by organizing important actions in a visually prominent and easily accessible location.
*
* The action bar's design and layout can vary based on the platform or application,
* but its primary goal remains consistent—to
* empower users to interact with the software and perform desired actions effortlessly.
*
* @exampleComponent limel-example-action-bar-basic
* @exampleComponent limel-example-action-bar-overflow-menu
* @exampleComponent limel-example-action-bar-selected-item
* @exampleComponent limel-example-action-bar-colors
* @exampleComponent limel-example-action-bar-floating
* @exampleComponent limel-example-action-bar-floating-expand
* @exampleComponent limel-example-action-bar-styling
* @exampleComponent limel-example-action-bar-as-primary-component
* @exampleComponent limel-example-action-bar-icon-title
*/
export class ActionBar {
constructor() {
this.hasRendered = false;
this.isFirstIntersectionCheck = true;
this.actionBarItems = [];
this.renderActionBarItem = (item, index) => {
return (h("limel-action-bar-item", { item: item, onSelect: this.handleSelect, isVisible: this.isVisible(index), role: "gridcell" }));
};
this.renderOverflowMenu = (items) => {
if (!(this.actions.length - this.overflowCutoff)) {
return;
}
const shrunkOverFlowIcon = {
name: 'more',
color: 'rgb(var(--contrast-1000))',
title: this.getTranslation('action-bar.actions'),
};
return (h("limel-action-bar-overflow-menu", { openDirection: this.openDirection, items: items, onSelect: this.handleSelect, role: "gridcell", overFlowIcon: this.actionBarIsShrunk ? shrunkOverFlowIcon : undefined }));
};
this.handleCollapseExpandClick = () => {
this.actionBarIsShrunk = !this.actionBarIsShrunk;
};
this.handleSelect = (event) => {
event.stopPropagation();
if (isItem(event.detail)) {
this.itemSelected.emit(event.detail);
}
};
this.getTranslation = (key) => {
return translate.get(key, this.language);
};
this.handleIntersection = (entries) => {
const intersectingItems = entries.filter((entry) => entry.isIntersecting);
const notIntersectingItems = entries.filter((entry) => !entry.isIntersecting);
if (this.isFirstIntersectionCheck) {
this.overflowCutoff = intersectingItems.length;
}
else {
this.overflowCutoff =
this.overflowCutoff +
intersectingItems.length -
notIntersectingItems.length;
}
this.isFirstIntersectionCheck = false;
};
this.actions = [];
this.language = document.documentElement.lang;
this.accessibleLabel = undefined;
this.layout = undefined;
this.collapsible = false;
this.openDirection = undefined;
this.overflowCutoff = this.actions.length;
this.actionBarIsShrunk = false;
}
connectedCallback() {
if (this.hasRendered) {
this.createIntersectionObserver();
}
}
componentDidRender() {
var _a;
if (this.haveItemsChanged()) {
(_a = this.intersectionObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
this.createIntersectionObserver();
}
}
disconnectedCallback() {
var _a;
(_a = this.intersectionObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
this.intersectionObserver = undefined;
this.actionBarItems = [];
}
render() {
this.hasRendered = true;
let overflowActions = [];
if (this.actions.length > 0) {
overflowActions = this.actions.slice(this.overflowCutoff);
}
return (h(Host, { "aria-label": this.accessibleLabel, class: {
'is-full-width': this.layout === 'fullWidth',
'is-floating': this.layout === 'floating',
'is-shrunk': this.actionBarIsShrunk && this.collapsible,
'can-be-shrunk': !!this.collapsible,
}, role: "grid" }, h("div", { class: "items", role: "rowgroup" }, this.actions.map(this.renderActionBarItem)), this.renderOverflowMenu(overflowActions), this.renderCollapseExpandButton()));
}
renderCollapseExpandButton() {
if (!this.collapsible || this.actions.length <= 1) {
return;
}
return (h("button", { class: {
'expand-shrink': true,
}, "aria-label": this.tooltipLabel, type: "button", onClick: this.handleCollapseExpandClick }, h("limel-icon", { name: "double_left", id: "tooltip-expand-shrink-button" }), h("limel-tooltip", { label: this.tooltipLabel, elementId: "tooltip-expand-shrink-button" })));
}
isVisible(index) {
return index < this.overflowCutoff;
}
get tooltipLabel() {
let key = 'action-bar.collapse';
if (this.actionBarIsShrunk) {
key = 'action-bar.expand';
}
return this.getTranslation(key);
}
createIntersectionObserver() {
const options = {
root: this.host.shadowRoot.querySelector('.items'),
rootMargin: '0px',
threshold: 1,
};
this.overflowCutoff = this.actions.length;
this.isFirstIntersectionCheck = true;
this.actionBarItems = [];
this.intersectionObserver = new IntersectionObserver(this.handleIntersection, options);
for (const actionBarItem of this.host.shadowRoot.querySelectorAll('limel-action-bar-item')) {
this.observe(actionBarItem);
}
}
observe(actionBarItem) {
this.intersectionObserver.observe(actionBarItem);
this.actionBarItems.push(actionBarItem);
}
haveItemsChanged() {
const someItemRemoved = this.actionBarItems.some((actionBarItem) => !this.host.shadowRoot.contains(actionBarItem));
const someItemAdded = [
...this.host.shadowRoot.querySelectorAll('limel-action-bar-item'),
].some((actionBarItem) => !this.actionBarItems.includes(actionBarItem));
return someItemRemoved || someItemAdded;
}
static get is() { return "limel-action-bar"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["action-bar.scss"]
};
}
static get styleUrls() {
return {
"$": ["action-bar.css"]
};
}
static get properties() {
return {
"actions": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Array<ActionBarItem | ListSeparator>",
"resolved": "(ListSeparator | ActionBarItem)[]",
"references": {
"Array": {
"location": "global"
},
"ActionBarItem": {
"location": "import",
"path": "./action-bar.types"
},
"ListSeparator": {
"location": "import",
"path": "../list/list-item.types"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Items that are placed in the action bar.\nThese represent primary actions."
},
"defaultValue": "[]"
},
"language": {
"type": "string",
"mutable": false,
"complexType": {
"original": "Languages",
"resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"",
"references": {
"Languages": {
"location": "import",
"path": "./../date-picker/date.types"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines the language for translations."
},
"attribute": "language",
"reflect": true,
"defaultValue": "document.documentElement.lang as Languages"
},
"accessibleLabel": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "A label used to describe the purpose of the element to users\nof assistive technologies, like screen readers.\nExample value: \"toolbar\""
},
"attribute": "accessible-label",
"reflect": true
},
"layout": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'fullWidth' | 'floating'",
"resolved": "\"floating\" | \"fullWidth\"",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "- When set to `fullWidth`, the component will take the\nentire width of its container.\n- When set to `floating`, the component will get basic stylings\nto visualize the floating state.\n:::note\nYou should still properly position the component\naccording to the structure of your user interface.\nFor example, use an `absolute` or `fixed` position.\n:::"
},
"attribute": "layout",
"reflect": true
},
"collapsible": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "When set to `true`, the action bar will be collapsible."
},
"attribute": "collapsible",
"reflect": true,
"defaultValue": "false"
},
"openDirection": {
"type": "string",
"mutable": false,
"complexType": {
"original": "OpenDirection",
"resolved": "\"bottom\" | \"bottom-end\" | \"bottom-start\" | \"left\" | \"left-end\" | \"left-start\" | \"right\" | \"right-end\" | \"right-start\" | \"top\" | \"top-end\" | \"top-start\"",
"references": {
"OpenDirection": {
"location": "import",
"path": "../menu/menu.types"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines the location that the content of the overflow menu\nappears, in relation to its trigger."
},
"attribute": "open-direction",
"reflect": true
}
};
}
static get states() {
return {
"overflowCutoff": {},
"actionBarIsShrunk": {}
};
}
static get events() {
return [{
"method": "itemSelected",
"name": "itemSelected",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [{
"name": "public",
"text": undefined
}],
"text": "Fired when a action bar item has been clicked."
},
"complexType": {
"original": "ActionBarItem",
"resolved": "ActionBarItemOnlyIcon<any> | ActionBarItemWithLabel<any>",
"references": {
"ActionBarItem": {
"location": "import",
"path": "./action-bar.types"
}
}
}
}];
}
static get elementRef() { return "host"; }
}
//# sourceMappingURL=action-bar.js.map