diff options
Diffstat (limited to 'browser/components/shopping/content/shopping-card.mjs')
-rw-r--r-- | browser/components/shopping/content/shopping-card.mjs | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/browser/components/shopping/content/shopping-card.mjs b/browser/components/shopping/content/shopping-card.mjs new file mode 100644 index 0000000000..7b2448df36 --- /dev/null +++ b/browser/components/shopping/content/shopping-card.mjs @@ -0,0 +1,204 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * 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 http://mozilla.org/MPL/2.0/. */ + +import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs"; +import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; + +const MIN_SHOW_MORE_HEIGHT = 200; +/** + * A card container to be used in the shopping sidebar. There are three card types. + * The default type where no type attribute is required and the card will have no extra functionality. + * The "accordion" type will initially not show any content. The card will contain a arrow to expand the + * card so all of the content is visible. + * + * @property {string} label - The label text that will be used for the card header + * @property {string} type - (optional) The type of card. No type specified + * will be the default card. The other available types are "accordion" and "show-more". + */ +class ShoppingCard extends MozLitElement { + static properties = { + label: { type: String }, + type: { type: String }, + _isExpanded: { type: Boolean }, + }; + + static get queries() { + return { + detailsEl: "#shopping-details", + contentEl: "#content", + }; + } + + labelTemplate() { + if (this.label) { + if (this.type === "accordion") { + return html` + <div id="label-wrapper"> + <h2 id="header">${this.label}</h2> + <button + tabindex="-1" + class="icon chevron-icon ghost-button" + aria-labelledby="header" + @click=${this.handleChevronButtonClick} + ></button> + </div> + `; + } + return html` + <div id="label-wrapper"> + <h2 id="header">${this.label}</h2> + <slot name="rating"></slot> + </div> + `; + } + return ""; + } + + cardTemplate() { + if (this.type === "accordion") { + return html` + <details id="shopping-details" @toggle=${this.onCardToggle}> + <summary>${this.labelTemplate()}</summary> + <div id="content"><slot name="content"></slot></div> + </details> + `; + } else if (this.type === "show-more") { + return html` + ${this.labelTemplate()} + <article + id="content" + class="show-more" + aria-describedby="content" + expanded="false" + > + <slot name="content"></slot> + + <footer> + <button + aria-controls="content" + class="small-button shopping-button" + data-l10n-id="shopping-show-more-button" + @click=${this.handleShowMoreButtonClick} + ></button> + </footer> + </article> + `; + } + return html` + ${this.labelTemplate()} + <div id="content" aria-describedby="content"> + <slot name="content"></slot> + </div> + `; + } + + onCardToggle() { + const action = this.detailsEl.open ? "expanded" : "collapsed"; + let l10nId = this.getAttribute("data-l10n-id"); + switch (l10nId) { + case "shopping-settings-label": + Glean.shopping.surfaceSettingsExpandClicked.record({ action }); + break; + case "shopping-analysis-explainer-label": + Glean.shopping.surfaceShowQualityExplainerClicked.record({ + action, + }); + break; + } + } + + handleShowMoreButtonClick(e) { + this._isExpanded = !this._isExpanded; + // toggle show more/show less text + e.target.setAttribute( + "data-l10n-id", + this._isExpanded + ? "shopping-show-less-button" + : "shopping-show-more-button" + ); + // toggle content expanded attribute + this.contentEl.attributes.expanded.value = this._isExpanded; + + let action = this._isExpanded ? "expanded" : "collapsed"; + Glean.shopping.surfaceShowMoreReviewsButtonClicked.record({ + action, + }); + } + + enableShowMoreButton() { + this._isExpanded = false; + this.toggleAttribute("showMoreButtonDisabled", false); + this.contentEl.attributes.expanded.value = false; + } + + disableShowMoreButton() { + this._isExpanded = true; + this.toggleAttribute("showMoreButtonDisabled", true); + this.contentEl.attributes.expanded.value = true; + } + + handleChevronButtonClick() { + this.detailsEl.open = !this.detailsEl.open; + } + + firstUpdated() { + if (this.type !== "show-more") { + return; + } + + let contentSlot = this.shadowRoot.querySelector("slot[name='content']"); + let contentSlotEls = contentSlot.assignedElements(); + if (!contentSlotEls.length) { + return; + } + + let slottedDiv = contentSlotEls[0]; + + this.handleContentSlotResize = this.handleContentSlotResize.bind(this); + this.contentResizeObserver = new ResizeObserver( + this.handleContentSlotResize + ); + this.contentResizeObserver.observe(slottedDiv); + } + + disconnectedCallback() { + this.contentResizeObserver?.disconnect(); + } + + handleContentSlotResize(entries) { + for (let entry of entries) { + if (entry.contentRect.height === 0) { + return; + } + + if (entry.contentRect.height < MIN_SHOW_MORE_HEIGHT) { + this.disableShowMoreButton(); + } else if (this.hasAttribute("showMoreButtonDisabled")) { + this.enableShowMoreButton(); + } + } + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://browser/content/shopping/shopping-card.css" + /> + <link + rel="stylesheet" + href="chrome://browser/content/shopping/shopping-page.css" + /> + <article + class="shopping-card" + aria-labelledby="header" + aria-label=${ifDefined(this.label)} + > + ${this.cardTemplate()} + </article> + `; + } +} +customElements.define("shopping-card", ShoppingCard); |