@limetech/lime-elements
Version:
692 lines (691 loc) • 27.3 kB
JavaScript
import { h, Host, } from "@stencil/core";
import { getRel } from "../../util/link-helper";
import { getIconName } from "../icon/get-icon-props";
import { makeEnterClickable, removeEnterClickable, } from "../../util/make-enter-clickable";
import translate from "../../global/translations";
import { BACKSPACE, DELETE } from "../../util/keycodes";
import { isEmpty } from "lodash-es";
/**
* Chips and buttons are both interactive elements in UI design,
* but they serve different purposes and are used in different contexts.
*
* :::warning
* Do not use the chip component carelessly, as an alternative for
* [`limel-button`](#/component/limel-button/) in the UI design!
*
* **Buttons:**
* Buttons are used to trigger actions. They are typically used to
* submit forms, open dialogs, initiate a process, or perform any action
* that changes the state of the application.
* Buttons' labels usually contain action words, in other words, the labels is
* a _verb in imperative mood_ such as "Submit" or "Delete".
* Buttons are placed in areas where it's clear they will initiate
* an action when clicked.
*
* **Chips:**
* Chips however are elements which may look like buttons, but they are
* representing choices, filters, or tags, in a small block
* or clearly bundled into a group. Chips are rarely used alone in the
* user interface.
* They are often used in a so called "chip-set", or placed together in
* a section of the UI, where the user can expect more than one chip to be present.
*
* For example, a chip may represent a filter in a filter bar, or a tag in a tag list,
* or an item in a shopping list.
* Clicking a chip can also trigger an action, for example toggling a filter ON or OFF,
* or opening a page with all posts tagged with the tag represented by the chip,
* or navigating to a page with more information about the item in the shopping list.
* :::
*
* @exampleComponent limel-example-chip-button
* @exampleComponent limel-example-chip-link
* @exampleComponent limel-example-chip-icon-colors
* @exampleComponent limel-example-chip-image
* @exampleComponent limel-example-chip-badge
* @exampleComponent limel-example-chip-filter
* @exampleComponent limel-example-chip-removable
* @exampleComponent limel-example-chip-menu
* @exampleComponent limel-example-chip-loading
* @exampleComponent limel-example-chip-progress
* @exampleComponent limel-example-chip-size
* @exampleComponent limel-example-chip-readonly-border
* @exampleComponent limel-example-chip-aria-role
*/
export class Chip {
constructor() {
/**
* Defines the language for translations.
* Will translate the translatable strings on the components.
*/
this.language = 'en';
/**
* Set to `true` to disable the chip.
*/
this.disabled = false;
/**
* Set to `true` to render the chip as a static UI element.
* Useful when the parent component has a `readonly` state.
*/
this.readonly = false;
/**
* Set to `true` to visualize the chip in a "selected" state.
* This is typically used when the chip is used in a chip-set
* along with other chips.
*/
this.selected = false;
/**
* Set to `true` to visualize the chip in an "invalid" or "error" state.
*/
this.invalid = false;
/**
* Set to `true` to render a remove button on the chip.
*/
this.removable = false;
/**
* Set to `filter` to render the chip with a distinct style
* suitable for visualizing filters.
*
*/
this.type = 'default';
/**
* Set to `true` to put the component in the `loading` state,
* and render an indeterminate progress indicator inside the chip.
* This does _not_ disable the interactivity of the chip!
*/
this.loading = false;
/**
* Identifier for the chip. Must be unique.
*/
this.identifier = crypto.randomUUID();
/**
* Defines the size of the chip.
*/
this.size = 'default';
/**
* When provided, the chip will render an ellipsis menu with the supplied items.
* Also, this will hide the "remove button" when `removable={true}`, as
* the remove button will automatically become the last item in the menu.
*/
this.menuItems = [];
this.renderAsButton = () => {
return [
h("button", { id: 'chip-' + this.identifier, class: "chip", role: "button", disabled: this.disabled || this.readonly, "aria-busy": this.loading ? 'true' : 'false', "aria-live": "polite", onKeyDown: this.handleDeleteKeyDown }, this.renderSpinner(), this.renderPicture(), this.renderLabel(), this.renderBadge(), this.renderProgressBar()),
this.renderRemoveButton(),
this.renderActionsMenu(),
];
};
this.renderAsLink = () => {
var _a, _b;
const rel = getRel((_a = this.link) === null || _a === void 0 ? void 0 : _a.target, (_b = this.link) === null || _b === void 0 ? void 0 : _b.rel);
return [
h("a", { id: 'chip-' + this.identifier, class: "chip", href: this.link.href, title: this.link.title, target: this.link.target, rel: rel, "aria-disabled": this.disabled || this.readonly, tabindex: this.disabled || this.readonly ? -1 : 0, onKeyDown: this.handleDeleteKeyDown }, this.renderSpinner(), this.renderPicture(), this.renderLabel(), this.renderBadge(), this.renderProgressBar()),
this.renderRemoveButton(),
this.renderActionsMenu(),
];
};
this.renderLabel = () => {
return h("span", { class: "text" }, this.text);
};
this.filterClickWhenDisabled = (e) => {
if (this.disabled || this.readonly) {
e.preventDefault();
}
};
this.handleRemoveClick = (event) => {
event.stopPropagation();
this.remove.emit(this.identifier);
};
this.handleDeleteKeyDown = (event) => {
if (!this.removable) {
return;
}
const keys = [DELETE, BACKSPACE];
if (keys.includes(event.key)) {
this.handleRemoveClick(event);
}
};
this.removeChipLabel = () => {
return `${this.getTranslation('remove')} ${this.text}`;
};
this.actionMenuLabel = () => {
return this.getTranslation('file-viewer.more-actions');
};
this.getTranslation = (key) => {
return translate.get(key, this.language);
};
this.handleActionMenuSelect = (event) => {
const menuItem = event.detail;
if (!menuItem) {
return;
}
if (menuItem.value === '_remove') {
this.remove.emit(this.identifier);
return;
}
this.menuItemSelected.emit(menuItem);
};
this.handleActionMenuCancel = (event) => {
event.stopPropagation();
};
}
componentWillLoad() {
makeEnterClickable(this.host);
}
disconnectedCallback() {
removeEnterClickable(this.host);
}
render() {
return (h(Host, { key: 'ff0578a9545aa295b7a5c58dd7c17605e4a8acc2', onClick: this.filterClickWhenDisabled }, this.link ? this.renderAsLink() : this.renderAsButton()));
}
renderPicture() {
var _a, _b;
const icon = getIconName(this.icon);
if (!icon && !this.image) {
return;
}
if (!isEmpty(this.image)) {
return (h("img", { src: this.image.src, alt: this.image.alt, loading: "lazy" }));
}
return (h("limel-icon", { badge: true, name: icon, style: {
color: `${(_a = this.icon) === null || _a === void 0 ? void 0 : _a.color}`,
'background-color': `${(_b = this.icon) === null || _b === void 0 ? void 0 : _b.backgroundColor}`,
} }));
}
renderBadge() {
if (!this.badge) {
return;
}
return h("limel-badge", { label: this.badge });
}
renderRemoveButton() {
var _a;
if (!this.removable ||
this.readonly ||
this.disabled ||
!!((_a = this.menuItems) === null || _a === void 0 ? void 0 : _a.length)) {
return;
}
const svgData = '<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" stroke-width="2" d="m8 8 16 16M24 8 8 24"/></svg>';
return (h("button", { class: "trailing-button remove-button", tabIndex: -1, "aria-label": this.removeChipLabel(), "aria-controls": 'chip-' + this.identifier, innerHTML: svgData, onClick: this.handleRemoveClick }));
}
renderActionsMenu() {
var _a;
if (!((_a = this.menuItems) === null || _a === void 0 ? void 0 : _a.length)) {
return;
}
const svgData = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" xml:space="preserve"><circle fill="currentColor" cx="16" cy="16" r="2"/><circle fill="currentColor" cx="16" cy="24" r="2"/><circle fill="currentColor" cx="16" cy="8" r="2"/></svg>';
const menuItems = this.getMenuItems();
return (h("limel-menu", { items: menuItems, onSelect: this.handleActionMenuSelect, openDirection: "bottom-end", onCancel: this.handleActionMenuCancel }, h("button", { slot: "trigger", disabled: this.disabled, class: "trailing-button", "aria-label": this.actionMenuLabel(), innerHTML: svgData })));
}
getMenuItems() {
let menuItems = [...this.menuItems];
if (this.removable) {
menuItems = [
...menuItems,
{ separator: true },
{
text: this.removeChipLabel(),
icon: {
name: 'delete_sign',
color: 'rgb(var(--color-red-default))',
},
value: '_remove',
},
];
}
return menuItems;
}
renderSpinner() {
if (!this.loading) {
return;
}
return h("limel-linear-progress", { indeterminate: true });
}
renderProgressBar() {
if (!this.progress) {
return;
}
const currentPercentage = this.progress + '%';
return (h("div", { role: "progressbar", "aria-label": "%", "aria-valuemin": "0", "aria-valuemax": "100", "aria-valuenow": this.progress, style: {
'--limel-chip-progress-percentage': currentPercentage,
} }));
}
static get is() { return "limel-chip"; }
static get encapsulation() { return "shadow"; }
static get delegatesFocus() { return true; }
static get originalStyleUrls() {
return {
"$": ["chip.scss"]
};
}
static get styleUrls() {
return {
"$": ["chip.css"]
};
}
static get properties() {
return {
"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",
"id": "src/components/date-picker/date.types.ts::Languages",
"referenceLocation": "Languages"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines the language for translations.\nWill translate the translatable strings on the components."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "language",
"defaultValue": "'en'"
},
"text": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Label displayed on the chip"
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "text"
},
"icon": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string | Icon",
"resolved": "Icon | string",
"references": {
"Icon": {
"location": "import",
"path": "../../global/shared-types/icon.types",
"id": "src/global/shared-types/icon.types.ts::Icon",
"referenceLocation": "Icon"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Icon of the chip."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "icon"
},
"image": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Image",
"resolved": "Image",
"references": {
"Image": {
"location": "import",
"path": "../../global/shared-types/image.types",
"id": "src/global/shared-types/image.types.ts::Image",
"referenceLocation": "Image"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "A picture to be displayed instead of the icon on the chip."
},
"getter": false,
"setter": false
},
"link": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Omit<Link, 'text'>",
"resolved": "\"text\" | Link",
"references": {
"Omit": {
"location": "global",
"id": "global::Omit"
},
"Link": {
"location": "import",
"path": "../../global/shared-types/link.types",
"id": "src/global/shared-types/link.types.ts::Link",
"referenceLocation": "Link"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "If supplied, the chip will become a clickable link."
},
"getter": false,
"setter": false
},
"badge": {
"type": "any",
"mutable": false,
"complexType": {
"original": "string | number",
"resolved": "number | string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The value of the badge, displayed on the chip."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "badge"
},
"disabled": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to disable the chip."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "disabled",
"defaultValue": "false"
},
"readonly": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to render the chip as a static UI element.\nUseful when the parent component has a `readonly` state."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "readonly",
"defaultValue": "false"
},
"selected": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to visualize the chip in a \"selected\" state.\nThis is typically used when the chip is used in a chip-set\nalong with other chips."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "selected",
"defaultValue": "false"
},
"invalid": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to visualize the chip in an \"invalid\" or \"error\" state."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "invalid",
"defaultValue": "false"
},
"removable": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to render a remove button on the chip."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "removable",
"defaultValue": "false"
},
"type": {
"type": "string",
"mutable": false,
"complexType": {
"original": "ChipType",
"resolved": "\"default\" | \"filter\"",
"references": {
"ChipType": {
"location": "import",
"path": "../chip-set/chip.types",
"id": "src/components/chip-set/chip.types.ts::ChipType",
"referenceLocation": "ChipType"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Set to `filter` to render the chip with a distinct style\nsuitable for visualizing filters."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "type",
"defaultValue": "'default'"
},
"loading": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Set to `true` to put the component in the `loading` state,\nand render an indeterminate progress indicator inside the chip.\nThis does _not_ disable the interactivity of the chip!"
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "loading",
"defaultValue": "false"
},
"progress": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Reflects the current value of a progress bar on the chip,\nvisualizing the percentage of an ongoing process.\nMust be a number between `0` and `100`."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "progress"
},
"identifier": {
"type": "any",
"mutable": false,
"complexType": {
"original": "number | string",
"resolved": "number | string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Identifier for the chip. Must be unique."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "identifier",
"defaultValue": "crypto.randomUUID()"
},
"size": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'small' | 'default'",
"resolved": "\"default\" | \"small\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines the size of the chip."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "size",
"defaultValue": "'default'"
},
"menuItems": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Array<MenuItem | ListSeparator>",
"resolved": "any[]",
"references": {
"Array": {
"location": "global",
"id": "global::Array"
},
"MenuItem": {
"location": "global",
"id": "global::MenuItem"
},
"ListSeparator": {
"location": "import",
"path": "../list-item/list-item.types",
"id": "src/components/list-item/list-item.types.ts::ListSeparator",
"referenceLocation": "ListSeparator"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "When provided, the chip will render an ellipsis menu with the supplied items.\nAlso, this will hide the \"remove button\" when `removable={true}`, as\nthe remove button will automatically become the last item in the menu."
},
"getter": false,
"setter": false,
"defaultValue": "[]"
}
};
}
static get events() {
return [{
"method": "remove",
"name": "remove",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Fired when clicking on the remove button of a `removable` chip.\nThe value of `identifier` is emitted as the event detail."
},
"complexType": {
"original": "number | string",
"resolved": "number | string",
"references": {}
}
}, {
"method": "menuItemSelected",
"name": "menuItemSelected",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when a menu item is selected from the actions menu."
},
"complexType": {
"original": "MenuItem",
"resolved": "MenuItem",
"references": {
"MenuItem": {
"location": "global",
"id": "global::MenuItem"
}
}
}
}];
}
static get elementRef() { return "host"; }
}