UNPKG

@limetech/lime-elements

Version:
336 lines (335 loc) • 12 kB
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